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.
| Key | Value |
|---|---|
| Port | 4591 |
| Protocol | Azure Cosmos DB SQL (Core) REST API (HTTP/1.1 + JSON) |
| Compatible client | @azure/cosmos (v4) |
| Size | ~80 KB |
| Startup | < 100ms |
| State | In-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:
| Variable | Value |
|---|---|
COSMOS_ENDPOINT | http://127.0.0.1:4591/ |
COSMOS_KEY | well-known emulator key (see Quick Start) |
COSMOS_CONNECTION_STRING | AccountEndpoint=http://127.0.0.1:4591/;AccountKey=...; |
Implemented operations
All paths are resource-link addressed exactly like the real service.
Database account
GET /— database account (consistency policy, read/write locations, query engine config). Powersclient.getDatabaseAccount(),getReadEndpoints(),getWriteEndpoints().
Databases (client.databases / client.database(id))
create,createIfNotExistsread,deletereadAll(list),queryreadOffer(database-level shared throughput)
Containers (database.containers / database.container(id))
create,createIfNotExistsread,replace,deletereadAll(list),queryreadOffer,getFeedRanges/readPartitionKeyRangesdeleteAllItemsForPartitionKey
Items (container.items / container.item(id, pk))
create,upsertread,replace,delete,patchreadAll(list)query(with query-plan negotiation, partition-key range routing, paging)changeFeed(incremental, partition-scoped, continuation-aware)batch(transactional batch, atomic with rollback)bulk(grouped per-partition operations)
Query language (subset)
SELECT *,SELECT VALUE expr,SELECT a, b [AS alias]WHEREwithAND/OR/NOT, comparisons (= != <> < > <= >=),IN (...)ORDER BY expr [ASC|DESC](multi-column)TOP n,OFFSET n LIMIT m,DISTINCT- Aggregates:
COUNT,SUM,AVG,MIN,MAX - Built-in functions:
CONTAINS,STARTSWITH,ENDSWITH,UPPER,LOWER,LENGTH/STRLEN,CONCAT,ABS,FLOOR,CEILING,ROUND,IS_DEFINED,IS_NULL,IS_STRING,IS_NUMBER,IS_BOOL,IS_ARRAY,IS_OBJECT,ARRAY_CONTAINS,ARRAY_LENGTH,TOSTRING - Parameterized queries (
@param)
Stored procedures / triggers / UDFs (container.scripts)
- StoredProcedures:
create,read,replace,delete,readAll,query,execute(a constrained JS runtime providesgetContext(),getResponse().setBody(), and agetCollection()shim supportingcreateDocument/replaceDocument/queryDocuments) - Triggers:
create,read,replace,delete,readAll,query - UserDefinedFunctions:
create,read,replace,delete,readAll,query
Users & permissions (database.users / database.user(id))
- Users:
create,upsert,read,replace,delete,readAll,query - Permissions:
create,upsert,read,replace,delete,readAll,query(returns a synthetic_tokenresource token)
Offers / throughput (client.offers / client.offer(id))
readAll,query(by resource link)read,replace(change provisioned throughput)
Conflicts (container.conflicts)
readAll,query, singleread,delete(empty by default — the fake has no multi-master replication, so conflicts never arise organically)
Control plane (parlel-only)
GET /_parlel/health—{ status, service, databases }POST /_parlel/reset— wipe all state
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.
| Feature | Status |
|---|---|
| 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.
| HTTP | code | When |
|---|---|---|
| 400 | BadRequest | invalid id / body / malformed SQL |
| 404 | NotFound (substatus 0) | missing database / container / item / script |
| 409 | Conflict | creating a resource whose id already exists |
| 412 | PreconditionFailed | If-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 |
| 500 | InternalServerError | unexpected 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.
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 -->