Cosmos DB

Lightweight, dependency-free fake of Azure Cosmos DB that speaks the real Cosmos DB SQL (Core) REST API, so application code using the @azure/cosmos client can run against it with zero cost and zero side effects.

KeyValue
Port4591
ProtocolAzure Cosmos DB SQL (Core) REST API (HTTP/1.1 + JSON)
Compatible client@azure/cosmos (v4)
Size~80 KB
Startup< 100ms
StateIn-memory, ephemeral, resettable

Quick Start

Start the server:

import { CosmosdbServer } from "./services/cosmosdb/src/server.js";

const server = new CosmosdbServer(4591);
await server.start();
// ... use it ...
await server.stop();

Connect with the real Cosmos DB client. The fake never validates the authorization header, so any key works — the well-known Cosmos emulator key is convenient. Disable endpoint discovery so all traffic is routed to the single parlel endpoint.

import { CosmosClient } from "@azure/cosmos";

const client = new CosmosClient({
  endpoint: "http://127.0.0.1:4591/",
  key: "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",
  // Single-region fake: skip multi-region endpoint discovery.
  connectionPolicy: { enableEndpointDiscovery: false },
});

// Create a database + container
const { database } = await client.databases.createIfNotExists({ id: "appdb" });
const { container } = await database.containers.createIfNotExists({
  id: "users",
  partitionKey: { paths: ["/pk"] },
});

// Write
await container.items.create({ id: "alice", pk: "tenant-1", name: "Alice", age: 30 });

// Read (id + partition key)
const { resource } = await container.item("alice", "tenant-1").read();
console.log(resource.name); // "Alice"

// Query (cross-partition by default)
const { resources } = await container.items
  .query("SELECT * FROM c WHERE c.age >= 18 ORDER BY c.age DESC")
  .fetchAll();

// Parameterized query
const { resources: bobs } = await container.items
  .query({
    query: "SELECT * FROM c WHERE c.name = @name",
    parameters: [{ name: "@name", value: "Bob" }],
  })
  .fetchAll();

// Patch
await container.item("alice", "tenant-1").patch([{ op: "incr", path: "/age", value: 1 }]);

Reset all state between tests:

await fetch("http://127.0.0.1:4591/_parlel/reset", { method: "POST" });

Environment variables

The manifest exposes:

VariableValue
COSMOS_ENDPOINThttp://127.0.0.1:4591/
COSMOS_KEYwell-known emulator key (see Quick Start)
COSMOS_CONNECTION_STRINGAccountEndpoint=http://127.0.0.1:4591/;AccountKey=...;

Implemented operations

All paths are resource-link addressed exactly like the real service.

Database account

Databases (client.databases / client.database(id))

Containers (database.containers / database.container(id))

Items (container.items / container.item(id, pk))

Query language (subset)

Stored procedures / triggers / UDFs (container.scripts)

Users & permissions (database.users / database.user(id))

Offers / throughput (client.offers / client.offer(id))

Conflicts (container.conflicts)

Control plane (parlel-only)

Surface coverage

This emulator faithfully replicates the API surface most application code and agents exercise. Anything below the supported lines is either an intentional design choice for a fast, zero-cost local emulator (✓ By design) or a candidate for a future release (⟳ Roadmap) — never a silent inaccuracy.

Legend: ✅ fully supported · ◐ accepted (stored, not strictly enforced) · ✓ by design · ⟳ on the roadmap.

FeatureStatus
Databases / containers / items CRUD✅ Supported
Partition keys (single & hierarchical paths)✅ Supported
Upsert, replace, delete, patch (add/set/replace/remove/incr)✅ Supported
SQL queries (filter, order, top, offset/limit, distinct, aggregates, functions)✅ Supported
Parameterized queries✅ Supported
Query plan negotiation + partition key ranges✅ Supported
Pagination (maxItemCount + continuation token)✅ Supported
Change feed (incremental, continuation-aware)✅ Supported
Transactional batch (atomic, rollback on failure)✅ Supported
Bulk operations✅ Supported
Stored procedures (with a JS execution shim)✅ Supported
Triggers & UDFs (CRUD; not executed during writes)✅ Supported (storage only)
Users, permissions, resource tokens (synthetic)✅ Supported
Offers / provisioned throughput (read + replace)✅ Supported
Optimistic concurrency (If-Match / ETag → 412)✅ Supported
Conflicts feed✅ Supported (always empty)
HMAC auth signature verification✓ By design — Structurally faithful tokens; cryptographic verification is skipped for local use
TLS / HTTPS✓ By design — Plain in-memory storage — transport/at-rest crypto is unnecessary locally
Multi-region replication & endpoint discovery⟳ Roadmap — Single endpoint (disable discovery)
Indexing policy enforcement✓ By design — Stored, not enforced (full scans)
TTL expiry✓ By design — defaultTtl stored, not enforced
Server-side trigger execution / UDF evaluation in queries⟳ Roadmap — Not executed
Vector / full-text / hybrid search⟳ Roadmap
Continuation-token opaqueness⚠️ Simplified (numeric offsets / LSNs)

Error codes & shapes

Errors are returned as JSON { "code": "<string>", "message": "<string>" } with the matching HTTP status and Cosmos headers (x-ms-activity-id, x-ms-request-charge, x-ms-session-token, and x-ms-substatus where relevant). The @azure/cosmos client surfaces error.code as the HTTP status number.

HTTPcodeWhen
400BadRequestinvalid id / body / malformed SQL
404NotFound (substatus 0)missing database / container / item / script
409Conflictcreating a resource whose id already exists
412PreconditionFailedIf-Match ETag mismatch on replace/delete/patch
207(per-op statuses)transactional batch where an operation failed (others → 424)
304(empty)change feed with no new changes since the continuation
500InternalServerErrorunexpected server error

Per-operation batch/bulk results carry { statusCode, requestCharge, resourceBody?, eTag? }. A failed atomic batch returns each subsequent operation as 424 (Failed Dependency) and rolls back all applied mutations.

<!-- parlel:testenv:start -->

Configuration — test.env

Copy these into your test.env (used by the bridge sidecar flow). Tokens are Parlel's seeded test credentials — any non-empty value is accepted by the emulator, so you rarely need to change them. Swap in real credentials only when pointing at the live service in prod.env.

COSMOS_ENDPOINT=http://parlel-bridge:4591/
COSMOS_KEY=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==
COSMOS_CONNECTION_STRING=AccountEndpoint=http://127.0.0.1:4591/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==;
<!-- parlel:testenv:end -->