AxarDB Documentation

High-performance NoSQL database server powered by a JavaScript-based query language

AxarDB is a NoSQL database server that works with JavaScript-based queries using the Jint JavaScript engine. It runs on ASP.NET Core 8.0.

🚀 Latest Updates:
  • System Configuration (sysconfig): Server-wide settings (memoryLimitPercentage, bulkStoreMaxCacheBytes, maxRecursionDepth, queryTimeoutMinutes, queuePollIntervalSeconds) can now be managed via the sysconfig collection.
  • Sys-prefix Protection: Custom collections with names starting with sys are now blocked to prevent conflicts with internal system infrastructure.
  • Bulk Query Memory Optimization: Bulk (JSONL) queries now use chunked processing with temporary lightweight indexes, keeping memory bounded by bulkStoreMaxCacheBytes even for very large files.
  • Multi-Language UTF-8 HTTP Encoding: Full UTF-8 support for query string parameters, resolving encoding issues for non-Latin alphabets (Turkish, Arabic, Russian, Chinese, Japanese, etc.).
  • Lazy Collection Directory Creation: Collection folders are generated only when the first write operation (insert) occurs, preventing clutter from read-only checks on non-existent collections.
  • UUID v7 as Default ID Scheme: All collections (Standard, Memory, Bulk) now generate RFC 9562 compliant UUID v7 IDs by default. New query functions guidv7(), guidv7(datetime), and guidv7CreatedAt(guid) are available in all script contexts.
  • Multi-Engine Benchmark Tool (compare.py): A Python-based benchmark measures AxarDB (db/memory/bulk) against PostgreSQL, MariaDB, and MongoDB. Generates an interactive Chart.js HTML report (output.html) with per-operation timing, a "How many times faster is AxarDB (memory)?" speedup chart, and a feature comparison matrix. The tool automatically raises queryTimeoutMinutes to 30 minutes before running and awaits each queue job with a 300-second timeout to prevent false cancellations.

Core Features

📦 Collection Based

MongoDB-like structure. Automatic creation.

🔍 JavaScript Query

Data processing with native JS syntax.

⚡ High Performance

Lazy evaluation via PLINQ and strictly capped 40% RAM cache usage.

🧠 Memory Store

TTL-based temporary RAM-only storage for sessions and caching.

💾 Bulk Store

High-performance JSONL static storage with auto-reload and LRU caching.

📊 Monitoring

Real-time query metrics, memory usage, and cost tracing dashboard.

📄 CSV Engine

Convert lists of objects to CSV, or parse raw CSV strings instantly.

📊 Index Support

Performance with ASC/DESC sorting.

🔗 Join Operations

Joining multiple collections.

👁️ Views

Server-side stored queries.

⚡ Triggers

Automated scripts that respond automatically.

Data Reading

The db object is the entry point for all database operations.

// Find a single record (returns the first match)
var user = db.users.find(u => u.age > 18);

// List all matches (convert to array with toList())
var admins = db.users.findall(u => u.role == 'admin');

// Check if an item is in an array (Contains logic)
var targetCats = [66, 69, 74];
var myCats = db.categories.findall(x => x.rowNumber != null && x.rowNumber.includes(targetCats));

// Compare two arrays for exact equality (SequenceEqual logic)
var targetTags = ["developer", "senior"];
var matches = db.users.findall(x => x.tags.includes(targetTags));

// Get the entire collection
var allUsers = db.users.findall();

// Get the first record
var first = db.users.findall().first();

// Record count
var count = db.users.findall().count();
var adults = db.users.findall().count(u => u.age > 18);

// Distinct unique values
var roles = db.users.findall().distinct(u => u.role);

// Case-insensitive search
var devs = db.users.contains(x => x.title == "developer");

// Pagination (skip and take)
var page2 = db.users.findall().skip(10).take(10);

// Sorting (orderBy and orderByDesc)
var oldestFirst = db.users.findall().orderBy(u => u.age);
var youngestFirst = db.users.findall().orderByDesc(u => u.age);

// Min and Max values
var maxAge = db.users.findall().max(u => u.age);
var minAge = db.users.findall().min(u => u.age);
💡 Important: findall() returns a ResultSet. To convert it into a standard JavaScript array, you can optionally call .toList().

Data Writing

insert

db.users.insert({
    name: "Ahmet",
    age: 25,
    role: "developer"
});

update

// Update records that match the condition
db.users.update(u => u.name == "Ahmet", { age: 26 });

// Update via ResultSet
db.users.findall(u => u.inactive).update({ status: "archived" });

delete

// Delete Documents (Filtered)
db.users.findall(u => u.inactive == true).delete();

// Delete Entire Collection (RAM + Disk)
db.users.delete();
db.deleteCollection("users");

foreach

db.users.findall().foreach(user => {
    console.log(user.name);
});

Memory Store (TTL-based Temporary Storage)

The memory object provides transient, high-speed RAM-only storage. Ideal for sessions, transient state, and cache tables. Data is never persisted to disk, and records expire after a specified TTL (Time-To-Live).

Key Features

  • RAM-Only: Zero disk write overhead, providing maximum throughput.
  • TTL Expiration: Supports automatic expiration with custom intervals (in hours). Defaults to 1 hour if not specified.
  • Background Cleanup: Expired entries are evicted lazily on access and periodically via a background thread.

Usage Examples

// Insert with default TTL of 1 hour
memory.sessions.insert({ userId: "user_123", token: "xyz" });

// Insert with custom TTL (e.g. 2.5 hours)
memory.sessions.insert({ userId: "user_456" }, 2.5);

// Query active memory records
var active = memory.sessions.findall(s => s.token != null);

// Delete from memory
memory.sessions.findall(s => s.userId == "user_123").delete();

Bulk Store (JSONL for Large Static Datasets)

The bulk object allows fast read and write operations on large static datasets (e.g. zip codes, countries, log exports) stored as JSON Lines (JSONL) files in the Bulk/ folder.

Key Features

  • High-Performance JSONL: Optimized for bulk loading and static lookups without server-level locks.
  • LRU Memory Cache: Automatically caches records in memory up to a configurable byte budget (default: 50 MB), evicting the least recently used collections.
  • Memory-Bounded Chunked Queries: Filtered queries on large Bulk collections use chunked processing with temporary lightweight indexes. Data is read line-by-line, keeping memory usage bounded by bulkStoreMaxCacheBytes.
  • Auto-Reload & Watcher: Monitor changes to files on disk via a FileSystemWatcher to automatically refresh memory cache. Can also be reloaded manually.

Usage Examples

// Bulk insert an array of objects
bulk.countries.insert([
    { name: "Turkey", code: "TR" },
    { name: "Germany", code: "DE" }
]);

// Find record from bulk store (uses LRU Cache)
var tr = bulk.countries.find(c => c.code == "TR");

// Manually reload cache for a collection
bulk.reload("countries");

Join Operations

// Recommended: Join with Aliases
var result = db.join(
    alias(db.users, "u"), 
    alias(db.orders, "o")
)
.where(x => x.u._id == x.o.userId);

// Default: Join with indexed properties (j1, j2...)
var data = db.join(db.products, db.categories).select(x => ({
    name: x.j1.productName,
    category: x.j2.categoryName
}));

UUID v7 Support

AxarDB uses native RFC 9562 compliant UUID v7 as its default _id generation strategy for all collections. This guarantees chronologically ordered, time-sortable IDs that enhance lookup speeds. Older UUID v4 keys are fully compatible and can coexist within the same collections.

Global Functions

Function Description Example
guidv7() Generates a new UUID v7 using current UTC time. guidv7()
guidv7(datetime) Generates a new UUID v7 using the specified ISO 8601 date string. guidv7("2024-01-15T10:30:00Z")
guidv7CreatedAt(id) Extracts the UTC creation timestamp from a valid UUID v7 string. Returns Date or null. guidv7CreatedAt(doc._id)
guid() Generates a standard UUID v4. Maintained for legacy compatibility. guid()

Usage Example

// Query creation date of a document
var doc = db.products.find(p => p.name == "Super Phone 0");
if (doc) {
    var createdTime = guidv7CreatedAt(doc._id);
    console.log("Product created at: " + createdTime);
}

Creating Index

// ASC (default)
db.users.index(x => x.email);

// DESC sorting
db.users.index(x => x.createdAt, "DESC");

// Chaining
db.users.insert({...}).index(x => x.name);

Built-in Prototype Extensions & Case-Insensitive Search

AxarDB extends native JavaScript prototypes on every script execution to add powerful utility methods. It also provides two distinct methods for case-insensitive searching.

Case-Insensitive Search Mechanisms

Do not confuse these two mechanisms as they serve different purposes:

  • Collection-Level contains (Exact Match): Uses a case-insensitive wrapper so property access is case-insensitive, performing exact matching.
  • String Prototype .contains(str) (Substring Search): Custom method injected into all string instances for case-insensitive substring matching.
// 1. Collection-Level exact match (case-insensitive properties)
var devs = db.users.contains(x => x.title == "developer");
// Matches "Developer", "DEVELOPER", "developer"

// 2. String Prototype case-insensitive substring search
var matches = bulk.postalcodes.findall(x => x.placeName.contains("esen"));
// Matches "Esenler", "ESENYURT", "Büyükçekmece" (contains "esen")

String Prototype Extensions

Method Description Example
.contains(str) Case-insensitive substring search. Turkish character aware. "İstanbul".contains("istan") → true
.startsWith(str) Case-insensitive prefix check. Turkish character aware. "İZMİR".startsWith("izm") → true
.toLowerCase() Turkish-aware lowercase conversion (handles İ → i, etc.). "İZMİR".toLowerCase() → "izmir"

Array & Object Prototype Extensions

Prototype Method Description Example
Array .count(predicate?) Counts matching elements, or returns array length if no predicate. arr.count(x => x.age > 18)
Array .distinct(selector?) Deduplicates array. Optional selector projects values first. arr.distinct(x => x.role)
Array / Object Converts resultsets/iterables to standard arrays. Identity on arrays. db.users.findall()

Views

Views are reusable queries stored on the server side in the Views/ folder.

Public View (Accessible to Everyone)

// Accessible via HTTP GET /views/activeUsers (No Auth required)
db.saveView("activeUsers", `
// @access public
return db.users.findall(u => u.active == true);
`);

Private View (Internal Use Only)

// Can only be called with db.view(), not accessible from HTTP
db.saveView("internalReport", `
// @access private
return db.orders.findall(o => o.status == "pending");
`);

View Usage

// Call internally
var result = db.view("activeUsers");

// Get view code
var code = db.getView("activeUsers");

// Access via HTTP (only public views)
// GET /views/activeUsers
💡 @access Metadata:
• // @access public → Accessible from outside via HTTP GET
• // @access private → Can only be used internally via db.view()

Triggers

Triggers are asynchronous scripts that run automatically by monitoring file changes in the Data/ folder. They are stored in the Triggers/ folder.

Creating a Trigger

// saveTrigger(triggerName, targetCollection, scriptCode)
db.saveTrigger("userNotifier", "users", `
// @target users
console.log("User changed: " + event.documentId);
webhook("https://api.example.com/notify", 
    { id: event.documentId, type: event.type },
    { "Authorization": "Bearer " + $API_TOKEN }
);
`);

@target Metadata

Determines which collection the trigger will react to changes in:

// Monitor changes only in the "orders" collection
// @target orders

// Monitor multiple collections
// @target users,orders,products

Event Object

event.type        // "changed" | "created" | "deleted"
event.collection  // Name of the changed collection
event.documentId  // _id value of the changed document
event.timestamp   // Server time (ISO format)

Event Types

  • created - When a new document is added
  • changed - When an existing document is updated
  • deleted - When a document is deleted
💡 Logging: Trigger executions are logged daily in the trigger_logs/ folder.

Vaults (Secure Storage)

Secure key-value store in the sysvaults collection for API keys and constants.

// Add value to Vault
addVault("API_KEY", "sk-xxxx...");
addVault("SLACK_WEBHOOK", "https://hooks.slack.com/...");

// Use as $Key in queries
webhook($SLACK_WEBHOOK, { text: "Notification!" }, {});
🔒 Security Restriction: Direct insertion into the sysvaults collection via db.sysvaults.insert() is disabled to prevent unauthorized configuration changes. You must use the addVault(key, value) function.

Webhooks

The webhook(url, data, headers) function is used to send HTTP POST requests to external services.

Basic Usage

// Simple webhook (without headers)
webhook("https://api.example.com/notify", {
    message: "New record added",
    timestamp: new Date().toISOString()
}, {});

Usage with Headers

// With Bearer Token
webhook("https://api.example.com/data", 
    { userId: 123, action: "update" },
    { 
        "Authorization": "Bearer " + $API_TOKEN,
        "Content-Type": "application/json"
    }
);

// With API Key
webhook("https://api.stripe.com/v1/charges", 
    { amount: 1000, currency: "usd" },
    { "X-API-Key": $STRIPE_KEY }
);

// Slack Webhook
webhook($SLACK_WEBHOOK, 
    { text: "New order received!" },
    { "Content-Type": "application/json" }
);

External Databases

AxarDB can execute queries on external MySQL/MariaDB and PostgreSQL databases.

MySQL / MariaDB

var conn = "Server=localhost;Database=test;Uid=root;Pwd=pass;";
// Read data
var users = mysqlRead(conn, "SELECT * FROM users WHERE age > @age", { age: 18 });
// Execute command
var count = mysqlExec(conn, "UPDATE users SET active = 1 WHERE id = @id", { id: 5 });

PostgreSQL

var conn = "Host=localhost;Database=db;Username=pg;Password=pass";
// Read data
var data = pgsqlRead(conn, "SELECT * FROM public.logs WHERE type = @t", { t: 'error' });
// Execute command
var affected = pgsqlExec(conn, "DELETE FROM public.logs WHERE id = @id", { id: 10 });

Queue Operations

Schedule long-running tasks for background execution using queue().

// Simple background job
var jobId = queue("db.logs.insert({msg: 'Background task', time: guid()})");

// Job with parameters and priority
var highPriJob = queue(
    "db.users.update(u => u.id == @id, { active: true })", 
    { id: 123 }, 
    { priority: 1 }
);
💡 Priority: Higher priority jobs run first. Default is 0.
🔒 Security Restriction: Direct insertion into the sysqueue collection via db.sysqueue.insert() is disabled. You must use the queue() function to schedule background tasks.

Queue Schema (db.sysqueue)

All background tasks are recorded in the sysqueue collection with the following structure:

  • _id: Unique task ID (string)
  • queryTemplate: Query script template to execute (string)
  • parameters: Object containing values for template parameters (object)
  • options: Execution settings (object)
  • createdAt: Timestamp when the task was queued (datetime)
  • executionTime: Timestamp when task execution started (datetime, null if pending)
  • completedAt: Timestamp when task execution fully finished (datetime, null if pending/running)
  • priority: Task priority level (integer)
  • duration: Task execution time in milliseconds (long)
  • successResult: Execution result on success (object, null on failure)
  • errorMessage: Error message on failure (string, null on success)

Data Recovery (Fail-safe Backup)

AxarDB provides an automatic, zero-configuration fail-safe data recovery mechanism to protect against accidental data loss.

Every state-changing operation (such as insert, update, delete, drop, saveView, deleteTrigger, etc.) across the db, bulk, and sys collections automatically generates a reverse query. For example, an insert operation will generate a corresponding delete() query to easily undo the addition.

These recovery queries are appended sequentially into a daily file located at backup_queries/YYYY-MM-DD.txt. If an accidental deletion or modification occurs, you can manually execute these queries to restore your data to its previous state.

The backup logging operates in a fail-safe wrapper. Any I/O errors that occur during the backup logging process are silently handled and will never interrupt the main database transaction.

System Configuration (sysconfig)

AxarDB uses the sysconfig system collection to manage server-wide configuration settings. Changes to this collection are stored on disk and applied the next time the server restarts.

Available Configuration Fields

Field Type Default Description
memoryLimitPercentage double 40 Maximum percentage of total system RAM used for document caching (0-100)
bulkStoreMaxCacheBytes long 52428800 Maximum bytes for Bulk Store LRU memory cache (default: 50 MB)
maxRecursionDepth int 10 Maximum recursion depth for query operations to prevent infinite loops
queryTimeoutMinutes int 5 Maximum execution time (in minutes) before a query is automatically cancelled
queuePollIntervalSeconds double 30 Interval (in seconds) for the background queue processor polling cycle

Updating Configuration

// Reading current config
var config = db.sysconfig.findall().first();
console.log("Memory limit: " + config.memoryLimitPercentage + "%");

// Updating settings (requires server restart to take effect)
db.sysconfig.findall().update({
    memoryLimitPercentage: 60,
    queryTimeoutMinutes: 10,
    queuePollIntervalSeconds: 15
});
⚠️ Restart Required: Changes to the sysconfig collection take effect after a server restart. The management console displays a contextual banner only when you are actively working on the sysconfig collection.

System Collections Overview

AxarDB includes four system collections, each serving a critical infrastructure role:

Collection Purpose Insert Restriction
sysusers User accounts and credentials for Basic Authentication Direct insert restricted
sysvaults Secure key-value store for API keys and secrets Must use addVault()
sysconfig Server-wide configuration parameters Direct insert blocked
sysqueue Background task queue for deferred execution Must use queue()
syslogs Virtual read-only collection for querying system logs Read-only (modifications prohibited)

Reserved sys Prefix

Any collection name starting with sys is reserved for internal system use. Attempting to create, access, or insert into a custom collection with a sys prefix (e.g., db.sysnew) will throw an InvalidOperationException. This protection is enforced at multiple layers:

  • Bridge Layer: db.sysnew → blocked by AxarDBBridge.TryGetMember
  • Engine Layer: AxarDB("sysnew") → blocked by DatabaseEngine constructor binding
  • Collection Layer: db.sysnew.insert(...) → blocked by Collection.Insert
💡 Only Pre-defined System Collections: sysusers, sysqueue, sysvaults, sysconfig, and syslogs are the only allowed sys-prefixed collection names.

Real-time Monitoring Dashboard

AxarDB includes a built-in real-time monitoring dashboard accessible at /monitor or /monitoring.html. It provides a visual interface to monitor server performance, database activity, and active memory stores.

Dashboard Features

  • Metrics Charts: Displays live time-series tracking of queries/min and RAM allocation.
  • Database Logs: Shows a rolling log of the last 50 HTTP requests, including HTTP methods, endpoints, response statuses, and durations.
  • Memory Collections: Inspects active TTL collections, indicating active counts and memory state.
  • Cost Analytics: Tracks call counts, average, minimum, maximum durations, and error rates of Views, Triggers, and Background Queue Jobs.

HTTP API

Endpoint Method Description Auth
/query POST Execute JavaScript query ✓
/collections GET List collections ✓
/collections/{name} DELETE Delete collection (RAM + Disk) ✓
/views/{name} GET Run public view @access
/views/{name} DELETE Delete view ✓
/triggers/{name} DELETE Delete trigger ✓

Query Parameters

// POST /query?userName=John&userAge=30
db.users.insert({
    name: @userName,
    age: parseInt(@userAge)
});

Security

  • Basic Authentication: Required for /query and /collections
  • Default User: unlocker / unlocker
  • Password Hashing: Supports both plain text and SHA256 hashed passwords in the sysusers collection.
  • Injection Protection: @param placeholders are securely bound
  • Malicious Code Check: eval, Function are blocked
⚠️ Warning: Dangerous codes such as eval(, Function(, script in parameters are rejected.

.NET SDK (C#)

Integrate AxarDB into your .NET applications using the official AxarDB SDK.

using AxarDB.Sdk;

// Initialize Client
using var client = new AxarClient("http://localhost:5000", "unlocker", "unlocker");

// Insert Document
await client.InsertAsync("users", new { Name = "John", Age = 30 });

// Query with Parameters
var script = "db.users.findall(u => u.name == @name)";
var users = await client.QueryAsync<List<User>>(script, new { name = "John" });

// Update Documents
await client.UpdateAsync("users", "u => u.name == 'John'", new { Age = 31 });

// Builder Pattern (Fluent API)
var count = await client.Collection<User>("users").Where("age", ">", 18).CountAsync();
var first = await client.Collection<User>("users").FirstAsync();

// View Management
await client.CreateViewAsync("ActiveUsers", "return db.users.findall(u => u.active)");
var list = await client.CallViewAsync<List<User>>("ActiveUsers");
var filtered = await client.CallViewAsync<List<User>>("myview", new { minAge = 18 });

// Vault Storage
await client.AddVaultAsync("MY_SECRET", "12345");

Python SDK

Interact with AxarDB programmatically from your Python scripts and services.

from axardb import AxarClient

# Initialize Client
client = AxarClient("http://localhost:5000", "unlocker", "unlocker")

# Insert Document
client.insert("users", {"name": "John", "age": 30})

# Query via Builder API
users = client.collection("users").where("age", ">", 20).to_list()

# Document Count
count = client.collection("users").count()

# Delete Documents
client.collection("users").where("age", "<", 18).delete()

# View Management
client.create_view("MyView", "return db.users.take(10)")
res = client.call_view("MyView")
res = client.call_view("MyView", {"minAge": 18})

CLI Tool

AxarDB includes a Command Line Interface (CLI) tool for executing scripts, importing data, and running database commands directly from your terminal.

Usage Examples

# Interactive query login
./AxarDB.Cli -s "db.users.count()"

# Executing script from file
./AxarDB.Cli -f query.js

# Fully automated CI/CD mode with credentials and output file
./AxarDB.Cli -u admin -p pass -f query.js -o result.json

# List collections on the server
./AxarDB.Cli --show-collections

# Direct document insertion
./AxarDB.Cli --insert users "{\"name\":\"Alice\"}"
🌐 CORS Configuration:
By default, AxarDB allows all origins (*). You can restrict allowed origins when starting the database server using the CLI parameter:
dotnet run -- --cors "http://localhost:3000,http://example.com"

Multi-Engine Benchmark Tool

AxarDB comes with a Python-based benchmark runner (compare.py) to test database throughput (CRUD operations, indexing, and queries) against PostgreSQL, MariaDB, and MongoDB under the same workloads.

Running the Benchmark

python compare.py

Technical Specifications

  • Metrics Measured: Single and Bulk Insert times, Index Creation, Count (COUNT), filtered Query/Range searches, record Updates, and Deletes.
  • Latency Tracing: Server-side execution times are captured via the sysqueue Stopwatch mechanism, excluding HTTP network payload overhead to measure clean database engine performance.
  • Timeout Configuration: To avoid cancellations during deep range searches or heavy bulk inserts, the benchmark script automatically raises the queryTimeoutMinutes in sysconfig to 30 minutes, and waits up to 300 seconds for queue jobs.
  • Report Generation: Generates a clean interactive HTML output report (output.html) with Chart.js line and bar charts tracking operation duration and total speedup comparisons.

Utility Functions

// Create unique ID (UUID v4)
var id = guid();

// Create unique ID (UUID v7) using current time
var idV7 = guidv7();

// Create unique ID (UUID v7) using specified datetime string
var customIdV7 = guidv7("2024-01-15T10:30:00Z");

// Extract creation date from UUID v7
var createdDate = guidv7CreatedAt(idV7);

// Write log to console (server console and logs/)
console.log("Message");

// Send webhook (url, data, headers)
webhook("https://api.example.com/hook", 
    { data: "value" }, 
    { "Authorization": "Bearer token" }
);

// Add value to Vault
addVault("KEY_NAME", "value");

// List existing collections
var cols = showCollections();

// Schedule background job
var jobId = queue("db.logs.insert({ msg: @msg })", { msg: "Hello" }, { priority: 1 });

// Get list of indexes for a collection
var indexes = getIndexes("users");

// Create alias for joins
var userAlias = alias(db.users, "u");

// Create SHA256 hash
var hash = sha256("password");

// Create random string
var token = randomString(32);

// Parse CSV or Generate CSV
var parsed = csv("id,name\n1,john");
var strOut = csv([{id: 1, name: "john"}]);

// Deep copy object
var copy = deepcopy(original);

// HTTP GET request
var res = httpGet("https://api.example.com", { "Auth": "..." });

// OpenAI compatible LLM Client
var ai = openai(url, token);

// Add system message to LLM Client
ai.addSysMsg("You are a helpful assistant");

// Send message to LLM Client
var response = ai.msg("Hello", {}, null);

// AES Encryption / Decryption
var secret = encrypt("hello", "salt");
var plain = decrypt(secret, "salt");

// Hash functions (MD5)
var m = md5("test");

// Date manipulation
var nextWeek = addDays(new Date(), 7);
var later = addMinutes(new Date(), 15);
var hourLater = addHours(new Date(), 1);

// Converters & String Utilities
var str = toString(42);
var parts = split("a,b,c", ",");
var b64 = toBase64("text");
var txt = fromBase64(b64);
var num = toDecimal("10.5");
var json = toJson({a: 1});

// Randoms
var n = randomNumber(1, 100);
var d = randomDecimal("0.01", "99.99");