1. Read the boundary
Start with the request path and layer matrix so each playground has a place in the architecture.
Jumentix keeps technology decisions at the adapter boundary, where they can be tested and replaced.

Bun-powered tooling
Jumentix standardizes on Bun as the pinned monorepo runtime, package manager, script runner, test runner and browser-spec bundler. That keeps local work, CI gates, package checks and website publishing on one toolchain.
The repository uses Bun where it actually reduces friction: fast installs with workspaces, direct TypeScript execution, repeatable package scripts, focused branch gates, and browser-test bundling before Cypress runs against real IndexedDB and DOM APIs.
bun install
bun run website:dev
bun run ci:affectedBrowser in-memory playgrounds
These runnable examples use Category and Task records across contract-compatible in-memory adapters and browser-native local storage. They execute entirely in the browser, so teams can inspect package behavior before adding Redis, RabbitMQ, databases, PM2 processes or a backend runtime.
Start with the request path and layer matrix so each playground has a place in the architecture.
Use the browser lab to see Category and Task flow through controller, use-case, adapter and client state.
Move to Message Mediator, SDKs and storage ports when the example needs cross-domain data or a consumer contract.
Finish with the bulk mutex + DLQ playground to watch real lock rejection, replay and IndexedDB commits in the canvas.
The full lab starts from a visual service model, validates the domain shape, then feeds runtime contracts from the same Category and Task vocabulary.
App surfaceThe request path mirrors controller, use-case, repository, mediator and client boundaries without exposing users to infrastructure setup.
App runtimeIn-memory stores and key/value state keep Category filters, Task records and UI preferences in the same service-result shape as external adapters.
@jumentix/key-value-storageMessage Mediator, Mutex Service and Dead Letter Queue show request/response, events, write protection and recoverable rejection before durable infrastructure is introduced.
@jumentix/message-mediatorThe DLQ playground sends rejected bulk Task requests back through the controller workflow, so replay still reacquires the Category mutex before writing.
@jumentix/dead-letter-queuePlayground directory
Every registered code playground is listed here with a direct link to the live widget below. Use it as a map across Cana, messaging, persistence, mutex, SDK and design examples.
Complete product slices with Category and Task records, from first MVP to heavy-data recovery.
A complete browser-only product slice using Category and Task records.
Jumentix browser lab · Zero to first MVPStress pathConcurrent writes, locks, DLQ replay and browser persistence in one heavy-data scenario.
Jumentix browser lab · Mutex + Dead Letter Queue + CanaMVP pathA complete browser-only product slice using Category and Task records.
Jumentix browser lab · Zero to first MVPMVP pathA complete browser-only product slice using Category and Task records.
Jumentix browser lab · Zero to first MVPMVP pathA complete browser-only product slice using Category and Task records.
Jumentix browser lab · Zero to first MVPMVP pathA complete browser-only product slice using Category and Task records.
Jumentix browser lab · Zero to first MVPMVP pathA complete browser-only product slice using Category and Task records.
Jumentix browser lab · Zero to first MVPMVP pathA complete browser-only product slice using Category and Task records.
Jumentix browser lab · Zero to first MVPMVP pathA complete browser-only product slice using Category and Task records.
Jumentix browser lab · Zero to first MVPRelational browser data, workers, event listeners, IndexedDB and UI state integration.
Local relational state, workers, events and IndexedDB-backed UI examples.
Cana · Cana architectureFrontend dataLocal relational state, workers, events and IndexedDB-backed UI examples.
Cana · Cana architectureFrontend dataLocal relational state, workers, events and IndexedDB-backed UI examples.
Cana · Cana architectureFrontend dataLocal relational state, workers, events and IndexedDB-backed UI examples.
Cana · Cana architectureFrontend dataLocal relational state, workers, events and IndexedDB-backed UI examples.
Cana · Cana architectureFrontend dataLocal relational state, workers, events and IndexedDB-backed UI examples.
Cana · Cana architectureFrontend dataLocal relational state, workers, events and IndexedDB-backed UI examples.
Cana · Cana architectureFrontend dataLocal relational state, workers, events and IndexedDB-backed UI examples.
Cana · Cana architectureFrontend dataLocal relational state, workers, events and IndexedDB-backed UI examples.
Cana · Cana architectureFrontend dataLocal relational state, workers, events and IndexedDB-backed UI examples.
Cana · Cana architectureFrontend dataLocal relational state, workers, events and IndexedDB-backed UI examples.
Cana · Cana architectureFrontend dataLocal relational state, workers, events and IndexedDB-backed UI examples.
Cana · Cana architectureFrontend dataLocal relational state, workers, events and IndexedDB-backed UI examples.
Cana · Cana architectureFrontend dataLocal relational state, workers, events and IndexedDB-backed UI examples.
Cana · Cana architectureFrontend dataLocal relational state, workers, events and IndexedDB-backed UI examples.
Cana · Cana architectureFrontend dataLocal relational state, workers, events and IndexedDB-backed UI examples.
Cana · Cana architectureFrontend dataLocal relational state, workers, events and IndexedDB-backed UI examples.
Cana · Cana architectureFrontend dataLocal relational state, workers, events and IndexedDB-backed UI examples.
Cana · Cana architectureFrontend dataLocal relational state, workers, events and IndexedDB-backed UI examples.
Cana · Cana architectureMediator, mutex and key/value examples that explain the architecture boundaries around shared work.
Request/response and event composition across independent domain modules.
Message Mediator · Event-driven boundariesDomain messagingRequest/response and event composition across independent domain modules.
Message Mediator · Event-driven boundariesLocal state portSmall state records with the same contract shape used by replaceable adapters.
Key/value storage · Persistence portsConcurrency guardCategory-scoped locks around Task workflows.
Mutex Service · Operational consistencySDK clients and Designer Core examples that connect UI modeling to runnable consumers.
A client-side consumer calling the same Task behavior through a stable API surface.
REST SDK · Contract-first clientsConsumer contractA client-side consumer calling the same Task behavior through a stable API surface.
WebSocket SDK · Contract-first clientsDesign modelDomain shape, entities and relationships before runtime code.
Designer Core · Service Management source of truthA complete browser-only product slice using Category and Task records.
Run Category and Task as separate domains that exchange messages through the mediator to compose a task board without a server.
### Full Jumentix browser app
```ts
const database = api.createInMemoryDatabase({
stores: ['categories', 'tasks']
});
const keyValue = api.createKeyValueStorage();
const mutex = api.createMutex(keyValue);
const mediator = api.createMessageMediator();
const emittedEvents = [];
await database.connect();
await keyValue.connect();
const model = api.createServiceModel({
app: 'service-management',
domain: 'Tasks'
});
const designReport = api.validateDesign(model);
await database.stores.categories.create('work', {
id: 'work',
name: 'Work',
color: '#2563eb'
});
await database.stores.categories.create('home', {
id: 'home',
name: 'Home',
color: '#16a34a'
});
mediator.registerHandler('categories.get.v1', async (message) => {
const category = await database.stores.categories.getOneById(message.payload.id);
return {
ok: Boolean(category.result),
result: category.result,
metadata: {
domain: 'Categories',
servedBy: 'categories.get.v1'
}
};
});
await mediator.subscribe('tasks.created', async (event) => {
emittedEvents.push({
title: event.payload.title,
categoryId: event.payload.categoryId
});
});
mediator.registerHandler('tasks.create.v1', async (message) => {
const task = {
...message.payload,
completed: false,
createdAt: Date.now()
};
const lock = await mutex.lock('category', task.categoryId);
if (!lock.result.locked) {
return { ok: false, error: 'category is busy' };
}
try {
await database.stores.tasks.create(task.id, task);
await keyValue.set(`category:${task.categoryId}:lastTask`, task.id);
await mediator.publish({ name: 'tasks.created', payload: task });
return { ok: true, result: task };
} finally {
await mutex.unlock('category', task.categoryId);
}
});
mediator.registerHandler('tasks.board.v1', async (message) => {
const taskList = await database.stores.tasks.getAll(
{ completed: message.payload.completed },
{ page: 1, size: 20 }
);
const cards = await Promise.all(taskList.result.map(async (task) => {
const category = await mediator.request({
contract: 'categories.get.v1',
payload: { id: task.categoryId },
metadata: {
sourceDomain: 'Tasks',
reason: 'compose task board'
}
});
return {
id: task.id,
title: task.title,
completed: task.completed,
category: category.result
? {
id: category.result.id,
name: category.result.name,
color: category.result.color
}
: null
};
}));
return {
ok: true,
result: {
view: 'task-board',
composedBy: ['Tasks', 'Categories'],
cards
}
};
});
const restClient = api.createRestClient((request) => mediator.request({
contract: 'tasks.create.v1',
payload: request.body,
metadata: { transport: 'rest' }
}));
const websocketClient = api.createWebSocketClient((request) => mediator.request({
contract: 'tasks.create.v1',
payload: request.input,
metadata: { transport: 'websocket' }
}));
const firstTask = await restClient.request({
operationId: 'createTask',
method: 'POST',
path: '/tasks',
body: {
id: 'task-1',
title: 'Publish in-memory playgrounds',
categoryId: 'work'
}
});
await websocketClient.connect();
const secondTask = await websocketClient.request({
operationId: 'tasks.create',
input: {
id: 'task-2',
title: 'Review browser contract flow',
categoryId: 'home'
}
});
await websocketClient.disconnect();
const tasks = await database.stores.tasks.getAll({}, { page: 1, size: 10 });
const lastWorkTask = await keyValue.get('category:work:lastTask');
const taskBoard = await mediator.request({
contract: 'tasks.board.v1',
payload: { completed: false },
metadata: { source: 'browser-playground' }
});
return {
designOk: designReport.ok,
taskCount: tasks.total,
createdByRest: firstTask.result.title,
createdByWebSocket: secondTask.result.title,
lastWorkTask: lastWorkTask.result,
composedDomains: taskBoard.result.composedBy,
taskBoard: taskBoard.result.cards,
emittedEvents
};
```const database = api.createInMemoryDatabase({
stores: ['categories', 'tasks']
});
const keyValue = api.createKeyValueStorage();
const mutex = api.createMutex(keyValue);
const mediator = api.createMessageMediator();
const emittedEvents = [];
await database.connect();
await keyValue.connect();
const model = api.createServiceModel({
app: 'service-management',
domain: 'Tasks'
});
const designReport = api.validateDesign(model);
await database.stores.categories.create('work', {
id: 'work',
name: 'Work',
color: '#2563eb'
});
await database.stores.categories.create('home', {
id: 'home',
name: 'Home',
color: '#16a34a'
});
mediator.registerHandler('categories.get.v1', async (message) => {
const category = await database.stores.categories.getOneById(message.payload.id);
return {
ok: Boolean(category.result),
result: category.result,
metadata: {
domain: 'Categories',
servedBy: 'categories.get.v1'
}
};
});
await mediator.subscribe('tasks.created', async (event) => {
emittedEvents.push({
title: event.payload.title,
categoryId: event.payload.categoryId
});
});
mediator.registerHandler('tasks.create.v1', async (message) => {
const task = {
...message.payload,
completed: false,
createdAt: Date.now()
};
const lock = await mutex.lock('category', task.categoryId);
if (!lock.result.locked) {
return { ok: false, error: 'category is busy' };
}
try {
await database.stores.tasks.create(task.id, task);
await keyValue.set(`category:${task.categoryId}:lastTask`, task.id);
await mediator.publish({ name: 'tasks.created', payload: task });
return { ok: true, result: task };
} finally {
await mutex.unlock('category', task.categoryId);
}
});
mediator.registerHandler('tasks.board.v1', async (message) => {
const taskList = await database.stores.tasks.getAll(
{ completed: message.payload.completed },
{ page: 1, size: 20 }
);
const cards = await Promise.all(taskList.result.map(async (task) => {
const category = await mediator.request({
contract: 'categories.get.v1',
payload: { id: task.categoryId },
metadata: {
sourceDomain: 'Tasks',
reason: 'compose task board'
}
});
return {
id: task.id,
title: task.title,
completed: task.completed,
category: category.result
? {
id: category.result.id,
name: category.result.name,
color: category.result.color
}
: null
};
}));
return {
ok: true,
result: {
view: 'task-board',
composedBy: ['Tasks', 'Categories'],
cards
}
};
});
const restClient = api.createRestClient((request) => mediator.request({
contract: 'tasks.create.v1',
payload: request.body,
metadata: { transport: 'rest' }
}));
const websocketClient = api.createWebSocketClient((request) => mediator.request({
contract: 'tasks.create.v1',
payload: request.input,
metadata: { transport: 'websocket' }
}));
const firstTask = await restClient.request({
operationId: 'createTask',
method: 'POST',
path: '/tasks',
body: {
id: 'task-1',
title: 'Publish in-memory playgrounds',
categoryId: 'work'
}
});
await websocketClient.connect();
const secondTask = await websocketClient.request({
operationId: 'tasks.create',
input: {
id: 'task-2',
title: 'Review browser contract flow',
categoryId: 'home'
}
});
await websocketClient.disconnect();
const tasks = await database.stores.tasks.getAll({}, { page: 1, size: 10 });
const lastWorkTask = await keyValue.get('category:work:lastTask');
const taskBoard = await mediator.request({
contract: 'tasks.board.v1',
payload: { completed: false },
metadata: { source: 'browser-playground' }
});
return {
designOk: designReport.ok,
taskCount: tasks.total,
createdByRest: firstTask.result.title,
createdByWebSocket: secondTask.result.title,
lastWorkTask: lastWorkTask.result,
composedDomains: taskBoard.result.composedBy,
taskBoard: taskBoard.result.cards,
emittedEvents
};Concurrent writes, locks, DLQ replay and browser persistence in one heavy-data scenario.
Create many Task records for one Category, force lock contention, enqueue rejected controller requests in a dead-letter queue, then replay them through the controller workflow.
### Bulk writes with mutex + DLQ
```ts
const taskSchema = {
version: 1,
stores: [
{
name: 'categories',
keyPath: 'id',
indexes: [{ name: 'byName', keyPath: 'name', unique: true }]
},
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' },
{ name: 'bySource', keyPath: 'source' },
{ name: 'byClient', keyPath: 'clientId' },
{ name: 'byWorker', keyPath: 'workerId' }
]
}
]
};
const database = api.createCanaDatabaseClient({
name: api.createCanaDatabaseName('bulk-mutex-dlq-workers'),
schema: taskSchema,
operationLedger: true
});
const keyValue = api.createKeyValueStorage();
const mutex = api.createMutex(keyValue);
const mediator = api.createMessageMediator();
const deadLetterQueue = api.createDeadLetterQueue({ maxAttempts: 3 });
const replayInbox = [];
const timeline = [];
let totalTimelineEvents = 0;
const canaEvents = [];
const storageUsageSamples = [];
const workerShards = [];
const React = api.React;
const BulkTaskImportContext = React.createContext(null);
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const realtimeMetrics = {
attempted: 0,
processed: 0,
rejected: 0,
replayed: 0
};
const criticalTimelineSteps = new Set([
'client-ingestion-stopped',
'controller-replay-batch',
'controller-replay',
'react-component-render'
]);
function publishRealtimeMetrics(phase, request = {}) {
if (typeof reportPlaygroundProgress === 'function') {
reportPlaygroundProgress({
...realtimeMetrics,
taskId: request.taskId,
clientId: request.clientId,
workerId: request.workerId,
phase,
timestamp: Date.now()
});
}
}
function recordTimeline(entry) {
totalTimelineEvents += 1;
if (timeline.length < 320) {
timeline.push(entry);
} else if (criticalTimelineSteps.has(entry.step)) {
timeline.shift();
timeline.push(entry);
}
}
async function recordIndexedDbQuota(label, tasks = 0) {
const estimate = navigator.storage && navigator.storage.estimate
? await navigator.storage.estimate()
: {};
const usage = Number(estimate.usage ?? 0);
const quota = Number(estimate.quota ?? 0);
const percent = quota > 0 ? (usage / quota) * 100 : 0;
storageUsageSamples.push({ label, usage, quota, percent, tasks });
return storageUsageSamples[storageUsageSamples.length - 1];
}
const stopCanaEvents = database.subscribe((event) => {
canaEvents.push({
cursor: event.cursor,
type: event.type,
store: event.store,
key: event.key,
taskId: event.store === 'tasks' && event.record ? event.record.id : undefined,
categoryId: event.record && event.record.categoryId ? event.record.categoryId : event.key,
clientId: event.record && event.record.clientId ? event.record.clientId : undefined,
workerId: event.record && event.record.workerId ? event.record.workerId : undefined,
source: event.record && event.record.source ? event.record.source : 'category-seed'
});
});
function createCanaWorkerShard(id) {
const channel = new MessageChannel();
channel.port1.start?.();
channel.port2.start?.();
const shard = {
id,
status: 'starting',
handledRequests: 0,
events: 0,
database: database.cana.name,
channel,
router: null,
host: null,
client: null
};
shard.router = api.createCanaRouter({
port: channel.port1,
timeoutMs: 5000,
onBroadcast(event) {
shard.events += 1;
canaEvents.push({
cursor: event.cursor,
type: event.type,
store: event.store,
key: event.key,
taskId: event.store === 'tasks' && event.record ? event.record.id : undefined,
categoryId: event.record && event.record.categoryId ? event.record.categoryId : event.key,
clientId: event.record && event.record.clientId ? event.record.clientId : undefined,
workerId: id,
source: event.record && event.record.source ? event.record.source : 'worker-broadcast'
});
}
});
shard.host = api.createCanaWorkerHost({
port: channel.port2,
name: database.cana.name,
schema: taskSchema,
originId: `docs-${id}`,
retainedEvents: 50,
operationLedger: true
});
shard.client = api.createCanaWorkerClient(shard.router);
return shard;
}
function closeWorkerShard(shard) {
return Promise.resolve()
.then(() => shard.client.close())
.catch(() => undefined)
.then(() => {
shard.router.dispose();
shard.channel.port1.close();
shard.channel.port2.close();
return shard.host.dispose();
});
}
function createBulkTaskImportProvider({ actorId, clientId }) {
const state = {
actorId,
clientId,
submittedBatches: 0,
lastBatchSize: 0
};
const contextValue = {
actorId,
clientId,
getState: () => ({ ...state }),
submitBulkImport: async (tasks, options = {}) => {
state.submittedBatches += 1;
state.lastBatchSize = tasks.length;
recordTimeline({
step: 'react-context-submit',
component: 'BulkTaskImportProvider',
actorId,
clientId,
taskCount: tasks.length
});
const responses = await bulkImportController({
body: { tasks },
actorId,
client: clientId,
stopNewRequestsAfterMs: options.stopNewRequestsAfterMs ?? Number.POSITIVE_INFINITY
});
state.accepted = (state.accepted ?? 0) + responses.filter((response) => response.ok).length;
state.rejected = (state.rejected ?? 0) + responses.filter((response) => response.deadLetterId).length;
state.interrupted = (state.interrupted ?? 0) + responses.filter((response) => response.interrupted).length;
recordTimeline({
step: 'react-context-complete',
component: 'BulkTaskImportProvider',
clientId,
accepted: state.accepted,
rejected: state.rejected,
interrupted: state.interrupted
});
return responses;
}
};
return {
Context: BulkTaskImportContext,
value: contextValue
};
}
function BulkImportPanel({ provider, createNextTask, streamConfig }) {
const previewTree = React.createElement(
provider.Context.Provider,
{ value: provider.value },
'BulkImportButton'
);
return {
component: 'BulkImportPanel',
previewElementType: previewTree.type === provider.Context.Provider
? 'BulkTaskImportContext.Provider'
: 'unknown',
startStream: async () => {
recordTimeline({
step: 'react-component-click',
component: 'BulkImportPanel',
clientId: provider.value.clientId,
mode: 'concurrent-30s-stream',
durationMs: streamConfig.durationMs,
maxConcurrentRequests: streamConfig.maxConcurrentRequests
});
const responses = [];
let stopped = false;
let inFlight = 0;
const startedAt = Date.now();
await new Promise((resolve) => {
const launchNext = () => {
if (Date.now() - startedAt >= streamConfig.durationMs) {
if (!stopped) {
stopped = true;
recordTimeline({
step: 'client-ingestion-stopped',
component: 'BulkImportPanel',
clientId: provider.value.clientId,
elapsedMs: Date.now() - startedAt,
reason: '30 second stream window completed'
});
}
if (inFlight === 0) resolve();
return;
}
while (inFlight < streamConfig.maxConcurrentRequests && Date.now() - startedAt < streamConfig.durationMs) {
const task = createNextTask(provider.value.clientId);
inFlight += 1;
provider.value.submitBulkImport([task])
.then((batchResponses) => {
responses.push(...batchResponses);
})
.finally(() => {
inFlight -= 1;
launchNext();
});
}
};
launchNext();
});
recordTimeline({
step: 'react-component-render',
component: 'BulkImportPanel',
clientId: provider.value.clientId,
state: provider.value.getState()
});
return responses;
}
};
}
await database.connect();
await keyValue.connect();
workerShards.push(
createCanaWorkerShard('worker-a'),
createCanaWorkerShard('worker-b'),
createCanaWorkerShard('worker-c')
);
await Promise.all(workerShards.map(async (shard) => {
await shard.client.open();
shard.status = 'ready';
}));
await recordIndexedDbQuota('opened', 0);
await database.stores.categories.add({
id: 'work',
name: 'Work',
color: '#2563eb',
createdAt: Date.now(),
updatedAt: Date.now()
});
await recordIndexedDbQuota('category seeded', 0);
await mediator.subscribe('dead-letter.enqueued', async (event) => {
replayInbox.push(event.payload.recordId);
recordTimeline({
step: 'dead-letter-listener-received',
taskId: event.payload.taskId,
recordId: event.payload.recordId
});
});
await mediator.subscribe('tasks.created', async (event) => {
recordTimeline({
step: 'task-created-event',
taskId: event.payload.id,
source: event.payload.source
});
});
let committedTaskCount = 0;
async function recordWriteQuota(taskId) {
committedTaskCount += 1;
if (committedTaskCount <= 3 || committedTaskCount % 500 === 0) {
await recordIndexedDbQuota(`${taskId}: ${committedTaskCount} tasks`, committedTaskCount);
}
}
function createTaskRecord(input, source) {
return {
id: input.id,
title: input.title,
categoryId: input.categoryId,
completed: false,
clientId: input.clientId,
workerId: input.workerId,
source,
createdAt: new Date().toISOString(),
updatedAt: Date.now()
};
}
async function writeTasksWithCanaWorkers(tasks, source) {
const groups = new Map();
for (const task of tasks) {
const selectedWorker = workerShards.find((shard) => shard.id === task.workerId)
?? workerShards[groups.size % workerShards.length];
const record = {
...task,
workerId: selectedWorker.id,
source
};
const current = groups.get(selectedWorker) ?? [];
current.push(record);
groups.set(selectedWorker, current);
}
const reports = [];
for (const [worker, records] of groups.entries()) {
worker.status = records.length > 1 ? 'bulk-writing' : 'writing';
const write = records.length === 1
? await worker.client.add('tasks', records[0])
: await worker.client.bulkAdd('tasks', records);
worker.handledRequests += records.length;
worker.status = 'ready';
for (const task of records) {
await recordWriteQuota(task.id);
}
const lastTask = records[records.length - 1];
await keyValue.set(`category:${lastTask.categoryId}:lastTask`, lastTask.id);
reports.push({
workerId: worker.id,
count: records.length,
emittedEvents: write.events ? write.events.length : records.length
});
}
return reports;
}
async function createTaskUseCase(input) {
const lock = await mutex.lock('category', input.categoryId);
if (!lock.result.locked) {
const record = await deadLetterQueue.enqueue({
entityName: 'Task',
resourceId: input.categoryId,
operation: 'tasks.create.v1',
payload: input,
actorId: input.requestedBy
});
await mediator.publish({
name: 'dead-letter.enqueued',
payload: {
recordId: record.id,
taskId: input.id,
categoryId: input.categoryId
},
metadata: {
source: 'tasks.create.use-case',
reason: 'category resource is already locked'
}
});
realtimeMetrics.rejected += 1;
publishRealtimeMetrics('rejected', {
taskId: input.id,
clientId: input.clientId,
workerId: input.workerId
});
return {
ok: false,
status: 409,
error: 'category is locked; request queued for replay',
deadLetterId: record.id
};
}
try {
recordTimeline({
step: 'lock-acquired',
taskId: input.id,
categoryId: input.categoryId
});
publishRealtimeMetrics('accepted', {
taskId: input.id,
clientId: input.clientId,
workerId: input.workerId
});
await sleep(input.processingMs);
const task = createTaskRecord(input, input.source);
const [write] = await writeTasksWithCanaWorkers([task], input.source);
recordTimeline({
step: 'cana-task-written',
taskId: task.id,
categoryId: task.categoryId,
clientId: task.clientId,
workerId: write.workerId,
source: task.source,
emittedEvents: write.emittedEvents
});
await mediator.publish({ name: 'tasks.created', payload: task });
realtimeMetrics.processed += 1;
publishRealtimeMetrics('processed', {
taskId: task.id,
clientId: task.clientId,
workerId: write.workerId
});
return { ok: true, status: 201, result: task };
} finally {
await mutex.unlock('category', input.categoryId);
recordTimeline({
step: 'lock-released',
taskId: input.id,
categoryId: input.categoryId
});
}
}
async function createTaskController(request) {
if (!request.replay) {
realtimeMetrics.attempted += 1;
publishRealtimeMetrics('submitted', {
taskId: request.body.id,
clientId: request.body.clientId,
workerId: request.body.workerId
});
}
recordTimeline({
step: request.replay ? 'controller-replay' : 'controller-create',
taskId: request.body.id,
clientId: request.body.clientId,
workerId: request.body.workerId
});
return createTaskUseCase({
...request.body,
requestedBy: request.actorId,
source: request.replay ? 'dead-letter-replay' : 'bulk-import'
});
}
async function bulkImportController(request) {
const startedAt = Date.now();
let stopRecorded = false;
recordTimeline({
step: 'bulk-import-controller',
component: 'BulkImportController',
actorId: request.actorId,
client: request.client,
clientId: request.client,
taskCount: request.body.tasks.length,
stopNewRequestsAfterMs: request.stopNewRequestsAfterMs
});
return Promise.all(request.body.tasks.map(async (task) => {
if (task.clientDelayMs > 0) {
await sleep(task.clientDelayMs);
}
const elapsedMs = Date.now() - startedAt;
if (elapsedMs > request.stopNewRequestsAfterMs) {
if (!stopRecorded) {
stopRecorded = true;
recordTimeline({
step: 'client-ingestion-stopped',
component: 'BulkImportController',
clientId: request.client,
elapsedMs,
reason: 'stop accepting new client requests'
});
}
recordTimeline({
step: 'client-request-interrupted',
component: 'BulkImportController',
taskId: task.id,
clientId: request.client,
workerId: task.workerId,
elapsedMs
});
publishRealtimeMetrics('interrupted', {
taskId: task.id,
clientId: request.client,
workerId: task.workerId
});
return {
ok: false,
status: 202,
interrupted: true,
error: 'client stopped sending new requests before controller admission',
taskId: task.id
};
}
return createTaskController({
body: task,
actorId: request.actorId
});
}));
}
async function replayDeadLettersController() {
const pendingBefore = await deadLetterQueue.pending();
const replayableRecords = pendingBefore.filter((record) => record.operation === 'tasks.create.v1');
const skippedRecords = pendingBefore.filter((record) => record.operation !== 'tasks.create.v1');
recordTimeline({
step: 'controller-replay-batch',
component: 'ReplayDeadLettersController',
taskCount: replayableRecords.length,
mode: 'worker-bulk-add'
});
const replayTasks = replayableRecords.map((record) => createTaskRecord(
{
...record.payload,
processingMs: 0
},
'dead-letter-replay'
));
const bulkReports = await writeTasksWithCanaWorkers(replayTasks, 'dead-letter-replay');
await Promise.all(replayableRecords.map((record) => deadLetterQueue.settle(record.id, 'succeeded')));
realtimeMetrics.replayed += replayableRecords.length;
replayableRecords.slice(-900).forEach((record) => {
publishRealtimeMetrics('replayed', {
taskId: record.payload.id,
clientId: record.payload.clientId,
workerId: record.payload.workerId
});
});
publishRealtimeMetrics('replayed');
replayableRecords.slice(0, 80).forEach((record) => {
recordTimeline({
step: 'controller-replay',
taskId: record.payload.id,
clientId: record.payload.clientId,
workerId: record.payload.workerId
});
});
const report = {
replayed: replayableRecords.map((record) => record.id),
retried: [],
abandoned: [],
skipped: skippedRecords.map((record) => record.id)
};
const records = await deadLetterQueue.list();
return {
ok: true,
status: 200,
pendingBefore: pendingBefore.length,
report,
bulkReports,
records: records.map((record) => ({
id: record.id,
taskId: record.payload.id,
status: record.status,
attempts: record.attempts
}))
};
}
const reactClientIds = ['react-client-a', 'react-client-b', 'react-client-c'];
const streamDurationMs = 30000;
const maxConcurrentRequestsPerClient = 12;
const requestPaceMs = 25;
let globalSequence = 0;
function createNextTask(clientId) {
const sequence = globalSequence;
globalSequence += 1;
return {
id: `task-${sequence + 1}`,
title: `Concurrent Task ${sequence + 1}`,
categoryId: 'work',
clientId,
workerId: workerShards[sequence % workerShards.length].id,
sequence,
processingMs: 12,
clientDelayMs: requestPaceMs
};
}
const reactClients = reactClientIds.map((clientId) => {
const provider = createBulkTaskImportProvider({
actorId: `${clientId}-controller`,
clientId
});
return {
id: clientId,
provider,
panel: BulkImportPanel({
provider,
createNextTask,
streamConfig: {
durationMs: streamDurationMs,
maxConcurrentRequests: maxConcurrentRequestsPerClient
}
})
};
});
const streamStartedAt = Date.now();
const bulkResponseGroups = await Promise.all(
reactClients.map((client) => client.panel.startStream())
);
const actualRunDurationMs = Date.now() - streamStartedAt;
const bulkResponses = bulkResponseGroups.flat();
const pendingAfterBulk = await deadLetterQueue.pending();
const replay = await replayDeadLettersController();
const pendingAfterReplay = await deadLetterQueue.pending();
const taskRows = await database.stores.tasks.query({ index: 'byUpdatedAt' });
const categoryRows = await database.stores.categories.query({ index: 'byName' });
const lastTask = await keyValue.get('category:work:lastTask');
await recordIndexedDbQuota('final', taskRows.length);
const storage = await database.cana.storageState();
const workerShardSummary = workerShards.map((shard) => ({
id: shard.id,
database: shard.database,
status: shard.status,
handledRequests: shard.handledRequests,
events: shard.events
}));
const databaseSnapshot = {
adapter: 'Cana database adapter',
backend: database.cana.backend,
workerMode: 'createWorkerHost + createWorkerClient',
storage,
indexedDbDatabase: database.cana.name,
stores: {
categories: categoryRows.length,
tasks: taskRows.length
},
categoryIds: categoryRows.map((category) => category.id),
taskIds: taskRows.map((task) => task.id)
};
stopCanaEvents();
await Promise.all(workerShards.map(closeWorkerShard));
await database.disconnect();
const createdDuringBulk = bulkResponses.filter((response) => response.ok).length;
const submittedToController = bulkResponses.filter((response) => !response.interrupted).length;
const interruptedBeforeController = bulkResponses.filter((response) => response.interrupted).length;
const rejectedToDeadLetterQueue = bulkResponses.filter((response) => response.deadLetterId).length;
const deadLetterQueueFullyProcessed = pendingAfterReplay.length === 0
&& replay.records.every((record) => record.status === 'succeeded');
const jobsAccountedFor = taskRows.length + interruptedBeforeController;
publishRealtimeMetrics('complete');
return {
databaseAdapter: 'Cana',
databaseBackend: databaseSnapshot.backend,
requestMode: 'concurrent-30s-stream',
streamDurationMs,
actualRunDurationMs,
maxConcurrentRequestsPerClient,
requestPaceMs,
attemptedBulkCount: bulkResponses.length,
createdDuringBulk,
submittedToController,
interruptedBeforeController,
rejectedToDeadLetterQueue,
shutdownReport: {
stopNewRequestsAfterMs: streamDurationMs,
pendingDeadLettersAfterReplay: pendingAfterReplay.length,
deadLetterQueueFullyProcessed,
jobsAccountedFor,
noLostJobs: deadLetterQueueFullyProcessed && jobsAccountedFor === bulkResponses.length
},
pendingBeforeReplay: pendingAfterBulk.map((record) => ({
id: record.id,
taskId: record.payload.id,
resourceId: record.resourceId,
status: record.status
})),
replayInbox,
replayReport: replay.report,
finalTaskCount: taskRows.length,
lastTaskInCategory: lastTask.result,
reactClients: reactClients.map((client) => ({
id: client.id,
taskCount: client.provider.value.getState().accepted
+ client.provider.value.getState().rejected
+ client.provider.value.getState().interrupted,
accepted: client.provider.value.getState().accepted,
rejected: client.provider.value.getState().rejected,
interrupted: client.provider.value.getState().interrupted
})),
workerShards: workerShardSummary,
requestTimeline: timeline,
totalTimelineEvents,
canaEvents: canaEvents.slice(-500),
totalCanaEvents: canaEvents.length,
storageUsageSamples,
databaseSnapshot,
controllerLevelReplay: timeline
.filter((entry) => entry.step.startsWith('controller'))
.slice(0, 40)
.map((entry) => entry.step),
storedTasks: taskRows.slice(0, 20).map((task) => ({
id: task.id,
title: task.title,
source: task.source,
clientId: task.clientId,
workerId: task.workerId
})),
omittedStoredTasks: Math.max(0, taskRows.length - 20),
deadLetterRecords: replay.records
};
```const taskSchema = {
version: 1,
stores: [
{
name: 'categories',
keyPath: 'id',
indexes: [{ name: 'byName', keyPath: 'name', unique: true }]
},
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' },
{ name: 'bySource', keyPath: 'source' },
{ name: 'byClient', keyPath: 'clientId' },
{ name: 'byWorker', keyPath: 'workerId' }
]
}
]
};
const database = api.createCanaDatabaseClient({
name: api.createCanaDatabaseName('bulk-mutex-dlq-workers'),
schema: taskSchema,
operationLedger: true
});
const keyValue = api.createKeyValueStorage();
const mutex = api.createMutex(keyValue);
const mediator = api.createMessageMediator();
const deadLetterQueue = api.createDeadLetterQueue({ maxAttempts: 3 });
const replayInbox = [];
const timeline = [];
let totalTimelineEvents = 0;
const canaEvents = [];
const storageUsageSamples = [];
const workerShards = [];
const React = api.React;
const BulkTaskImportContext = React.createContext(null);
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const realtimeMetrics = {
attempted: 0,
processed: 0,
rejected: 0,
replayed: 0
};
const criticalTimelineSteps = new Set([
'client-ingestion-stopped',
'controller-replay-batch',
'controller-replay',
'react-component-render'
]);
function publishRealtimeMetrics(phase, request = {}) {
if (typeof reportPlaygroundProgress === 'function') {
reportPlaygroundProgress({
...realtimeMetrics,
taskId: request.taskId,
clientId: request.clientId,
workerId: request.workerId,
phase,
timestamp: Date.now()
});
}
}
function recordTimeline(entry) {
totalTimelineEvents += 1;
if (timeline.length < 320) {
timeline.push(entry);
} else if (criticalTimelineSteps.has(entry.step)) {
timeline.shift();
timeline.push(entry);
}
}
async function recordIndexedDbQuota(label, tasks = 0) {
const estimate = navigator.storage && navigator.storage.estimate
? await navigator.storage.estimate()
: {};
const usage = Number(estimate.usage ?? 0);
const quota = Number(estimate.quota ?? 0);
const percent = quota > 0 ? (usage / quota) * 100 : 0;
storageUsageSamples.push({ label, usage, quota, percent, tasks });
return storageUsageSamples[storageUsageSamples.length - 1];
}
const stopCanaEvents = database.subscribe((event) => {
canaEvents.push({
cursor: event.cursor,
type: event.type,
store: event.store,
key: event.key,
taskId: event.store === 'tasks' && event.record ? event.record.id : undefined,
categoryId: event.record && event.record.categoryId ? event.record.categoryId : event.key,
clientId: event.record && event.record.clientId ? event.record.clientId : undefined,
workerId: event.record && event.record.workerId ? event.record.workerId : undefined,
source: event.record && event.record.source ? event.record.source : 'category-seed'
});
});
function createCanaWorkerShard(id) {
const channel = new MessageChannel();
channel.port1.start?.();
channel.port2.start?.();
const shard = {
id,
status: 'starting',
handledRequests: 0,
events: 0,
database: database.cana.name,
channel,
router: null,
host: null,
client: null
};
shard.router = api.createCanaRouter({
port: channel.port1,
timeoutMs: 5000,
onBroadcast(event) {
shard.events += 1;
canaEvents.push({
cursor: event.cursor,
type: event.type,
store: event.store,
key: event.key,
taskId: event.store === 'tasks' && event.record ? event.record.id : undefined,
categoryId: event.record && event.record.categoryId ? event.record.categoryId : event.key,
clientId: event.record && event.record.clientId ? event.record.clientId : undefined,
workerId: id,
source: event.record && event.record.source ? event.record.source : 'worker-broadcast'
});
}
});
shard.host = api.createCanaWorkerHost({
port: channel.port2,
name: database.cana.name,
schema: taskSchema,
originId: `docs-${id}`,
retainedEvents: 50,
operationLedger: true
});
shard.client = api.createCanaWorkerClient(shard.router);
return shard;
}
function closeWorkerShard(shard) {
return Promise.resolve()
.then(() => shard.client.close())
.catch(() => undefined)
.then(() => {
shard.router.dispose();
shard.channel.port1.close();
shard.channel.port2.close();
return shard.host.dispose();
});
}
function createBulkTaskImportProvider({ actorId, clientId }) {
const state = {
actorId,
clientId,
submittedBatches: 0,
lastBatchSize: 0
};
const contextValue = {
actorId,
clientId,
getState: () => ({ ...state }),
submitBulkImport: async (tasks, options = {}) => {
state.submittedBatches += 1;
state.lastBatchSize = tasks.length;
recordTimeline({
step: 'react-context-submit',
component: 'BulkTaskImportProvider',
actorId,
clientId,
taskCount: tasks.length
});
const responses = await bulkImportController({
body: { tasks },
actorId,
client: clientId,
stopNewRequestsAfterMs: options.stopNewRequestsAfterMs ?? Number.POSITIVE_INFINITY
});
state.accepted = (state.accepted ?? 0) + responses.filter((response) => response.ok).length;
state.rejected = (state.rejected ?? 0) + responses.filter((response) => response.deadLetterId).length;
state.interrupted = (state.interrupted ?? 0) + responses.filter((response) => response.interrupted).length;
recordTimeline({
step: 'react-context-complete',
component: 'BulkTaskImportProvider',
clientId,
accepted: state.accepted,
rejected: state.rejected,
interrupted: state.interrupted
});
return responses;
}
};
return {
Context: BulkTaskImportContext,
value: contextValue
};
}
function BulkImportPanel({ provider, createNextTask, streamConfig }) {
const previewTree = React.createElement(
provider.Context.Provider,
{ value: provider.value },
'BulkImportButton'
);
return {
component: 'BulkImportPanel',
previewElementType: previewTree.type === provider.Context.Provider
? 'BulkTaskImportContext.Provider'
: 'unknown',
startStream: async () => {
recordTimeline({
step: 'react-component-click',
component: 'BulkImportPanel',
clientId: provider.value.clientId,
mode: 'concurrent-30s-stream',
durationMs: streamConfig.durationMs,
maxConcurrentRequests: streamConfig.maxConcurrentRequests
});
const responses = [];
let stopped = false;
let inFlight = 0;
const startedAt = Date.now();
await new Promise((resolve) => {
const launchNext = () => {
if (Date.now() - startedAt >= streamConfig.durationMs) {
if (!stopped) {
stopped = true;
recordTimeline({
step: 'client-ingestion-stopped',
component: 'BulkImportPanel',
clientId: provider.value.clientId,
elapsedMs: Date.now() - startedAt,
reason: '30 second stream window completed'
});
}
if (inFlight === 0) resolve();
return;
}
while (inFlight < streamConfig.maxConcurrentRequests && Date.now() - startedAt < streamConfig.durationMs) {
const task = createNextTask(provider.value.clientId);
inFlight += 1;
provider.value.submitBulkImport([task])
.then((batchResponses) => {
responses.push(...batchResponses);
})
.finally(() => {
inFlight -= 1;
launchNext();
});
}
};
launchNext();
});
recordTimeline({
step: 'react-component-render',
component: 'BulkImportPanel',
clientId: provider.value.clientId,
state: provider.value.getState()
});
return responses;
}
};
}
await database.connect();
await keyValue.connect();
workerShards.push(
createCanaWorkerShard('worker-a'),
createCanaWorkerShard('worker-b'),
createCanaWorkerShard('worker-c')
);
await Promise.all(workerShards.map(async (shard) => {
await shard.client.open();
shard.status = 'ready';
}));
await recordIndexedDbQuota('opened', 0);
await database.stores.categories.add({
id: 'work',
name: 'Work',
color: '#2563eb',
createdAt: Date.now(),
updatedAt: Date.now()
});
await recordIndexedDbQuota('category seeded', 0);
await mediator.subscribe('dead-letter.enqueued', async (event) => {
replayInbox.push(event.payload.recordId);
recordTimeline({
step: 'dead-letter-listener-received',
taskId: event.payload.taskId,
recordId: event.payload.recordId
});
});
await mediator.subscribe('tasks.created', async (event) => {
recordTimeline({
step: 'task-created-event',
taskId: event.payload.id,
source: event.payload.source
});
});
let committedTaskCount = 0;
async function recordWriteQuota(taskId) {
committedTaskCount += 1;
if (committedTaskCount <= 3 || committedTaskCount % 500 === 0) {
await recordIndexedDbQuota(`${taskId}: ${committedTaskCount} tasks`, committedTaskCount);
}
}
function createTaskRecord(input, source) {
return {
id: input.id,
title: input.title,
categoryId: input.categoryId,
completed: false,
clientId: input.clientId,
workerId: input.workerId,
source,
createdAt: new Date().toISOString(),
updatedAt: Date.now()
};
}
async function writeTasksWithCanaWorkers(tasks, source) {
const groups = new Map();
for (const task of tasks) {
const selectedWorker = workerShards.find((shard) => shard.id === task.workerId)
?? workerShards[groups.size % workerShards.length];
const record = {
...task,
workerId: selectedWorker.id,
source
};
const current = groups.get(selectedWorker) ?? [];
current.push(record);
groups.set(selectedWorker, current);
}
const reports = [];
for (const [worker, records] of groups.entries()) {
worker.status = records.length > 1 ? 'bulk-writing' : 'writing';
const write = records.length === 1
? await worker.client.add('tasks', records[0])
: await worker.client.bulkAdd('tasks', records);
worker.handledRequests += records.length;
worker.status = 'ready';
for (const task of records) {
await recordWriteQuota(task.id);
}
const lastTask = records[records.length - 1];
await keyValue.set(`category:${lastTask.categoryId}:lastTask`, lastTask.id);
reports.push({
workerId: worker.id,
count: records.length,
emittedEvents: write.events ? write.events.length : records.length
});
}
return reports;
}
async function createTaskUseCase(input) {
const lock = await mutex.lock('category', input.categoryId);
if (!lock.result.locked) {
const record = await deadLetterQueue.enqueue({
entityName: 'Task',
resourceId: input.categoryId,
operation: 'tasks.create.v1',
payload: input,
actorId: input.requestedBy
});
await mediator.publish({
name: 'dead-letter.enqueued',
payload: {
recordId: record.id,
taskId: input.id,
categoryId: input.categoryId
},
metadata: {
source: 'tasks.create.use-case',
reason: 'category resource is already locked'
}
});
realtimeMetrics.rejected += 1;
publishRealtimeMetrics('rejected', {
taskId: input.id,
clientId: input.clientId,
workerId: input.workerId
});
return {
ok: false,
status: 409,
error: 'category is locked; request queued for replay',
deadLetterId: record.id
};
}
try {
recordTimeline({
step: 'lock-acquired',
taskId: input.id,
categoryId: input.categoryId
});
publishRealtimeMetrics('accepted', {
taskId: input.id,
clientId: input.clientId,
workerId: input.workerId
});
await sleep(input.processingMs);
const task = createTaskRecord(input, input.source);
const [write] = await writeTasksWithCanaWorkers([task], input.source);
recordTimeline({
step: 'cana-task-written',
taskId: task.id,
categoryId: task.categoryId,
clientId: task.clientId,
workerId: write.workerId,
source: task.source,
emittedEvents: write.emittedEvents
});
await mediator.publish({ name: 'tasks.created', payload: task });
realtimeMetrics.processed += 1;
publishRealtimeMetrics('processed', {
taskId: task.id,
clientId: task.clientId,
workerId: write.workerId
});
return { ok: true, status: 201, result: task };
} finally {
await mutex.unlock('category', input.categoryId);
recordTimeline({
step: 'lock-released',
taskId: input.id,
categoryId: input.categoryId
});
}
}
async function createTaskController(request) {
if (!request.replay) {
realtimeMetrics.attempted += 1;
publishRealtimeMetrics('submitted', {
taskId: request.body.id,
clientId: request.body.clientId,
workerId: request.body.workerId
});
}
recordTimeline({
step: request.replay ? 'controller-replay' : 'controller-create',
taskId: request.body.id,
clientId: request.body.clientId,
workerId: request.body.workerId
});
return createTaskUseCase({
...request.body,
requestedBy: request.actorId,
source: request.replay ? 'dead-letter-replay' : 'bulk-import'
});
}
async function bulkImportController(request) {
const startedAt = Date.now();
let stopRecorded = false;
recordTimeline({
step: 'bulk-import-controller',
component: 'BulkImportController',
actorId: request.actorId,
client: request.client,
clientId: request.client,
taskCount: request.body.tasks.length,
stopNewRequestsAfterMs: request.stopNewRequestsAfterMs
});
return Promise.all(request.body.tasks.map(async (task) => {
if (task.clientDelayMs > 0) {
await sleep(task.clientDelayMs);
}
const elapsedMs = Date.now() - startedAt;
if (elapsedMs > request.stopNewRequestsAfterMs) {
if (!stopRecorded) {
stopRecorded = true;
recordTimeline({
step: 'client-ingestion-stopped',
component: 'BulkImportController',
clientId: request.client,
elapsedMs,
reason: 'stop accepting new client requests'
});
}
recordTimeline({
step: 'client-request-interrupted',
component: 'BulkImportController',
taskId: task.id,
clientId: request.client,
workerId: task.workerId,
elapsedMs
});
publishRealtimeMetrics('interrupted', {
taskId: task.id,
clientId: request.client,
workerId: task.workerId
});
return {
ok: false,
status: 202,
interrupted: true,
error: 'client stopped sending new requests before controller admission',
taskId: task.id
};
}
return createTaskController({
body: task,
actorId: request.actorId
});
}));
}
async function replayDeadLettersController() {
const pendingBefore = await deadLetterQueue.pending();
const replayableRecords = pendingBefore.filter((record) => record.operation === 'tasks.create.v1');
const skippedRecords = pendingBefore.filter((record) => record.operation !== 'tasks.create.v1');
recordTimeline({
step: 'controller-replay-batch',
component: 'ReplayDeadLettersController',
taskCount: replayableRecords.length,
mode: 'worker-bulk-add'
});
const replayTasks = replayableRecords.map((record) => createTaskRecord(
{
...record.payload,
processingMs: 0
},
'dead-letter-replay'
));
const bulkReports = await writeTasksWithCanaWorkers(replayTasks, 'dead-letter-replay');
await Promise.all(replayableRecords.map((record) => deadLetterQueue.settle(record.id, 'succeeded')));
realtimeMetrics.replayed += replayableRecords.length;
replayableRecords.slice(-900).forEach((record) => {
publishRealtimeMetrics('replayed', {
taskId: record.payload.id,
clientId: record.payload.clientId,
workerId: record.payload.workerId
});
});
publishRealtimeMetrics('replayed');
replayableRecords.slice(0, 80).forEach((record) => {
recordTimeline({
step: 'controller-replay',
taskId: record.payload.id,
clientId: record.payload.clientId,
workerId: record.payload.workerId
});
});
const report = {
replayed: replayableRecords.map((record) => record.id),
retried: [],
abandoned: [],
skipped: skippedRecords.map((record) => record.id)
};
const records = await deadLetterQueue.list();
return {
ok: true,
status: 200,
pendingBefore: pendingBefore.length,
report,
bulkReports,
records: records.map((record) => ({
id: record.id,
taskId: record.payload.id,
status: record.status,
attempts: record.attempts
}))
};
}
const reactClientIds = ['react-client-a', 'react-client-b', 'react-client-c'];
const streamDurationMs = 30000;
const maxConcurrentRequestsPerClient = 12;
const requestPaceMs = 25;
let globalSequence = 0;
function createNextTask(clientId) {
const sequence = globalSequence;
globalSequence += 1;
return {
id: `task-${sequence + 1}`,
title: `Concurrent Task ${sequence + 1}`,
categoryId: 'work',
clientId,
workerId: workerShards[sequence % workerShards.length].id,
sequence,
processingMs: 12,
clientDelayMs: requestPaceMs
};
}
const reactClients = reactClientIds.map((clientId) => {
const provider = createBulkTaskImportProvider({
actorId: `${clientId}-controller`,
clientId
});
return {
id: clientId,
provider,
panel: BulkImportPanel({
provider,
createNextTask,
streamConfig: {
durationMs: streamDurationMs,
maxConcurrentRequests: maxConcurrentRequestsPerClient
}
})
};
});
const streamStartedAt = Date.now();
const bulkResponseGroups = await Promise.all(
reactClients.map((client) => client.panel.startStream())
);
const actualRunDurationMs = Date.now() - streamStartedAt;
const bulkResponses = bulkResponseGroups.flat();
const pendingAfterBulk = await deadLetterQueue.pending();
const replay = await replayDeadLettersController();
const pendingAfterReplay = await deadLetterQueue.pending();
const taskRows = await database.stores.tasks.query({ index: 'byUpdatedAt' });
const categoryRows = await database.stores.categories.query({ index: 'byName' });
const lastTask = await keyValue.get('category:work:lastTask');
await recordIndexedDbQuota('final', taskRows.length);
const storage = await database.cana.storageState();
const workerShardSummary = workerShards.map((shard) => ({
id: shard.id,
database: shard.database,
status: shard.status,
handledRequests: shard.handledRequests,
events: shard.events
}));
const databaseSnapshot = {
adapter: 'Cana database adapter',
backend: database.cana.backend,
workerMode: 'createWorkerHost + createWorkerClient',
storage,
indexedDbDatabase: database.cana.name,
stores: {
categories: categoryRows.length,
tasks: taskRows.length
},
categoryIds: categoryRows.map((category) => category.id),
taskIds: taskRows.map((task) => task.id)
};
stopCanaEvents();
await Promise.all(workerShards.map(closeWorkerShard));
await database.disconnect();
const createdDuringBulk = bulkResponses.filter((response) => response.ok).length;
const submittedToController = bulkResponses.filter((response) => !response.interrupted).length;
const interruptedBeforeController = bulkResponses.filter((response) => response.interrupted).length;
const rejectedToDeadLetterQueue = bulkResponses.filter((response) => response.deadLetterId).length;
const deadLetterQueueFullyProcessed = pendingAfterReplay.length === 0
&& replay.records.every((record) => record.status === 'succeeded');
const jobsAccountedFor = taskRows.length + interruptedBeforeController;
publishRealtimeMetrics('complete');
return {
databaseAdapter: 'Cana',
databaseBackend: databaseSnapshot.backend,
requestMode: 'concurrent-30s-stream',
streamDurationMs,
actualRunDurationMs,
maxConcurrentRequestsPerClient,
requestPaceMs,
attemptedBulkCount: bulkResponses.length,
createdDuringBulk,
submittedToController,
interruptedBeforeController,
rejectedToDeadLetterQueue,
shutdownReport: {
stopNewRequestsAfterMs: streamDurationMs,
pendingDeadLettersAfterReplay: pendingAfterReplay.length,
deadLetterQueueFullyProcessed,
jobsAccountedFor,
noLostJobs: deadLetterQueueFullyProcessed && jobsAccountedFor === bulkResponses.length
},
pendingBeforeReplay: pendingAfterBulk.map((record) => ({
id: record.id,
taskId: record.payload.id,
resourceId: record.resourceId,
status: record.status
})),
replayInbox,
replayReport: replay.report,
finalTaskCount: taskRows.length,
lastTaskInCategory: lastTask.result,
reactClients: reactClients.map((client) => ({
id: client.id,
taskCount: client.provider.value.getState().accepted
+ client.provider.value.getState().rejected
+ client.provider.value.getState().interrupted,
accepted: client.provider.value.getState().accepted,
rejected: client.provider.value.getState().rejected,
interrupted: client.provider.value.getState().interrupted
})),
workerShards: workerShardSummary,
requestTimeline: timeline,
totalTimelineEvents,
canaEvents: canaEvents.slice(-500),
totalCanaEvents: canaEvents.length,
storageUsageSamples,
databaseSnapshot,
controllerLevelReplay: timeline
.filter((entry) => entry.step.startsWith('controller'))
.slice(0, 40)
.map((entry) => entry.step),
storedTasks: taskRows.slice(0, 20).map((task) => ({
id: task.id,
title: task.title,
source: task.source,
clientId: task.clientId,
workerId: task.workerId
})),
omittedStoredTasks: Math.max(0, taskRows.length - 20),
deadLetterRecords: replay.records
};One merged canvas follows concurrent requests for 30 seconds: each recent request becomes its own token across the React client, Context API, controller, mutex, DLQ, Message Mediator, replay, Cana workers, IndexedDB commits, subscribe events, and the return path to the client. Lock rejections are red.
0 requests were recovered after lock rejection; 0 reached the database.
A complete browser-only product slice using Category and Task records.
Run the create task use-case against the real in-memory database adapter and prove the 201/400/404 rules.
### REST MVP Day 1 — use-case on the in-memory adapter
```ts
const database = api.createInMemoryDatabase({ stores: ['categories', 'tasks'] });
await database.connect();
await database.stores.categories.create('work', { id: 'work', name: 'Work' });
async function createTaskUseCase(input) {
if (!input.title || !String(input.title).trim()) {
return { status: 400, body: { error: 'title is required' } };
}
const category = await database.stores.categories.getOneById(input.categoryId);
if (!category.result) {
return { status: 404, body: { error: 'category not found' } };
}
const task = {
id: crypto.randomUUID(),
title: input.title,
categoryId: input.categoryId,
completed: false
};
await database.stores.tasks.create(task.id, task);
return { status: 201, body: task };
}
const created = await createTaskUseCase({ title: 'Ship the MVP', categoryId: 'work' });
const invalid = await createTaskUseCase({ title: ' ', categoryId: 'work' });
const orphan = await createTaskUseCase({ title: 'No owner', categoryId: 'missing' });
const listed = await database.stores.tasks.getAll({}, { page: 1, size: 10 });
return {
created: created.status,
invalid: invalid.status,
unknownCategory: orphan.status,
storedTotal: listed.total,
firstTask: listed.result[0].title
};
```const database = api.createInMemoryDatabase({ stores: ['categories', 'tasks'] });
await database.connect();
await database.stores.categories.create('work', { id: 'work', name: 'Work' });
async function createTaskUseCase(input) {
if (!input.title || !String(input.title).trim()) {
return { status: 400, body: { error: 'title is required' } };
}
const category = await database.stores.categories.getOneById(input.categoryId);
if (!category.result) {
return { status: 404, body: { error: 'category not found' } };
}
const task = {
id: crypto.randomUUID(),
title: input.title,
categoryId: input.categoryId,
completed: false
};
await database.stores.tasks.create(task.id, task);
return { status: 201, body: task };
}
const created = await createTaskUseCase({ title: 'Ship the MVP', categoryId: 'work' });
const invalid = await createTaskUseCase({ title: ' ', categoryId: 'work' });
const orphan = await createTaskUseCase({ title: 'No owner', categoryId: 'missing' });
const listed = await database.stores.tasks.getAll({}, { page: 1, size: 10 });
return {
created: created.status,
invalid: invalid.status,
unknownCategory: orphan.status,
storedTotal: listed.total,
firstTask: listed.result[0].title
};A complete browser-only product slice using Category and Task records.
Route OpenAPI operationIds through a REST client into a handler backed by the in-memory adapter.
### REST MVP Day 2 — typed client over the same adapter
```ts
const database = api.createInMemoryDatabase({ stores: ['categories', 'tasks'] });
await database.connect();
await database.stores.categories.create('work', { id: 'work', name: 'Work' });
const client = api.createRestClient(async (request) => {
if (request.operationId === 'createTask') {
const input = request.body ?? {};
if (!input.title || !String(input.title).trim()) {
return { ok: false, status: 400, error: 'title is required' };
}
const category = await database.stores.categories.getOneById(input.categoryId);
if (!category.result) {
return { ok: false, status: 404, error: 'category not found' };
}
const task = { id: crypto.randomUUID(), ...input, completed: false };
await database.stores.tasks.create(task.id, task);
return { ok: true, status: 201, result: task };
}
if (request.operationId === 'listTasks') {
const tasks = await database.stores.tasks.getAll({}, { page: 1, size: 20 });
return { ok: true, status: 200, result: tasks.result };
}
return { ok: false, status: 404, error: 'unknown operationId' };
});
const created = await client.request({
operationId: 'createTask',
method: 'POST',
path: '/tasks',
body: { title: 'Publish first REST MVP', categoryId: 'work' }
});
const rejected = await client.request({
operationId: 'createTask',
method: 'POST',
path: '/tasks',
body: { title: '', categoryId: 'work' }
});
const listed = await client.request({ operationId: 'listTasks', method: 'GET', path: '/tasks' });
return {
created: created.status,
rejected: rejected.status,
listed: listed.status,
total: listed.result.length,
firstTask: listed.result[0].title
};
```const database = api.createInMemoryDatabase({ stores: ['categories', 'tasks'] });
await database.connect();
await database.stores.categories.create('work', { id: 'work', name: 'Work' });
const client = api.createRestClient(async (request) => {
if (request.operationId === 'createTask') {
const input = request.body ?? {};
if (!input.title || !String(input.title).trim()) {
return { ok: false, status: 400, error: 'title is required' };
}
const category = await database.stores.categories.getOneById(input.categoryId);
if (!category.result) {
return { ok: false, status: 404, error: 'category not found' };
}
const task = { id: crypto.randomUUID(), ...input, completed: false };
await database.stores.tasks.create(task.id, task);
return { ok: true, status: 201, result: task };
}
if (request.operationId === 'listTasks') {
const tasks = await database.stores.tasks.getAll({}, { page: 1, size: 20 });
return { ok: true, status: 200, result: tasks.result };
}
return { ok: false, status: 404, error: 'unknown operationId' };
});
const created = await client.request({
operationId: 'createTask',
method: 'POST',
path: '/tasks',
body: { title: 'Publish first REST MVP', categoryId: 'work' }
});
const rejected = await client.request({
operationId: 'createTask',
method: 'POST',
path: '/tasks',
body: { title: '', categoryId: 'work' }
});
const listed = await client.request({ operationId: 'listTasks', method: 'GET', path: '/tasks' });
return {
created: created.status,
rejected: rejected.status,
listed: listed.status,
total: listed.result.length,
firstTask: listed.result[0].title
};A complete browser-only product slice using Category and Task records.
Wire a WebSocket client to a mediator handler that persists through the in-memory adapter and broadcasts the created event.
### Realtime MVP Day 1 — live command with ack and broadcast
```ts
const database = api.createInMemoryDatabase({ stores: ['categories', 'tasks'] });
const mediator = api.createMessageMediator();
await database.connect();
await database.stores.categories.create('work', { id: 'work', name: 'Work' });
const liveCards = [];
await mediator.subscribe('tasks.created', async (event) => {
liveCards.push(event.payload.title);
});
mediator.registerHandler('tasks.create.v1', async (message) => {
const category = await database.stores.categories.getOneById(message.payload.categoryId);
if (!category.result) {
return { ok: false, error: 'category not found' };
}
const task = {
id: crypto.randomUUID(),
title: message.payload.title,
categoryId: message.payload.categoryId,
completed: false
};
await database.stores.tasks.create(task.id, task);
await mediator.publish({ name: 'tasks.created', payload: task });
return { ok: true, result: task };
});
const socket = api.createWebSocketClient((request) => mediator.request({
contract: 'tasks.create.v1',
payload: request.input,
metadata: { transport: 'websocket' }
}));
await socket.connect();
const ack = await socket.request({
operationId: 'tasks.create',
input: { title: 'Show realtime status', categoryId: 'work' }
});
const rejected = await socket.request({
operationId: 'tasks.create',
input: { title: 'No owner', categoryId: 'missing' }
});
await socket.disconnect();
return {
ack: ack.ok,
createdTask: ack.result.title,
rejectedError: rejected.error,
broadcastedToUi: liveCards
};
```const database = api.createInMemoryDatabase({ stores: ['categories', 'tasks'] });
const mediator = api.createMessageMediator();
await database.connect();
await database.stores.categories.create('work', { id: 'work', name: 'Work' });
const liveCards = [];
await mediator.subscribe('tasks.created', async (event) => {
liveCards.push(event.payload.title);
});
mediator.registerHandler('tasks.create.v1', async (message) => {
const category = await database.stores.categories.getOneById(message.payload.categoryId);
if (!category.result) {
return { ok: false, error: 'category not found' };
}
const task = {
id: crypto.randomUUID(),
title: message.payload.title,
categoryId: message.payload.categoryId,
completed: false
};
await database.stores.tasks.create(task.id, task);
await mediator.publish({ name: 'tasks.created', payload: task });
return { ok: true, result: task };
});
const socket = api.createWebSocketClient((request) => mediator.request({
contract: 'tasks.create.v1',
payload: request.input,
metadata: { transport: 'websocket' }
}));
await socket.connect();
const ack = await socket.request({
operationId: 'tasks.create',
input: { title: 'Show realtime status', categoryId: 'work' }
});
const rejected = await socket.request({
operationId: 'tasks.create',
input: { title: 'No owner', categoryId: 'missing' }
});
await socket.disconnect();
return {
ack: ack.ok,
createdTask: ack.result.title,
rejectedError: rejected.error,
broadcastedToUi: liveCards
};A complete browser-only product slice using Category and Task records.
Kill the socket handler and prove the REST fallback returns the same business result through the same in-memory adapter.
### Realtime MVP Day 2 — REST fallback parity drill
```ts
const database = api.createInMemoryDatabase({ stores: ['categories', 'tasks'] });
await database.connect();
await database.stores.categories.create('work', { id: 'work', name: 'Work' });
async function createTaskUseCase(input) {
const task = {
id: crypto.randomUUID(),
title: input.title,
categoryId: input.categoryId,
completed: false
};
await database.stores.tasks.create(task.id, task);
return { ok: true, result: task };
}
const downSocket = api.createWebSocketClient(async () => {
throw new Error('socket unavailable');
});
const restFallback = api.createRestClient((request) => createTaskUseCase(request.body));
async function createTaskWithFallback(input) {
try {
const live = await downSocket.request({ operationId: 'tasks.create', input });
return { transport: 'websocket', result: live };
} catch {
const fallback = await restFallback.request({
operationId: 'createTask',
method: 'POST',
path: '/tasks',
body: input
});
return { transport: 'rest', result: fallback };
}
}
const response = await createTaskWithFallback({ title: 'Fallback parity task', categoryId: 'work' });
const stored = await database.stores.tasks.getAll({}, { page: 1, size: 10 });
return {
transportUsed: response.transport,
ok: response.result.ok,
storedTotal: stored.total,
storedTitle: response.result.result.title
};
```const database = api.createInMemoryDatabase({ stores: ['categories', 'tasks'] });
await database.connect();
await database.stores.categories.create('work', { id: 'work', name: 'Work' });
async function createTaskUseCase(input) {
const task = {
id: crypto.randomUUID(),
title: input.title,
categoryId: input.categoryId,
completed: false
};
await database.stores.tasks.create(task.id, task);
return { ok: true, result: task };
}
const downSocket = api.createWebSocketClient(async () => {
throw new Error('socket unavailable');
});
const restFallback = api.createRestClient((request) => createTaskUseCase(request.body));
async function createTaskWithFallback(input) {
try {
const live = await downSocket.request({ operationId: 'tasks.create', input });
return { transport: 'websocket', result: live };
} catch {
const fallback = await restFallback.request({
operationId: 'createTask',
method: 'POST',
path: '/tasks',
body: input
});
return { transport: 'rest', result: fallback };
}
}
const response = await createTaskWithFallback({ title: 'Fallback parity task', categoryId: 'work' });
const stored = await database.stores.tasks.getAll({}, { page: 1, size: 10 });
return {
transportUsed: response.transport,
ok: response.result.ok,
storedTotal: stored.total,
storedTitle: response.result.result.title
};A complete browser-only product slice using Category and Task records.
Prove the tenant guard: org-1 writes its own task, org-2 is denied, and the store only holds the legitimate record.
### SaaS MVP Day 1 — tenant policy on the in-memory adapter
```ts
const database = api.createInMemoryDatabase({ stores: ['categories', 'tasks'] });
await database.connect();
await database.stores.categories.create('work', { id: 'work', name: 'Work' });
async function createTenantTask(context, input) {
if (context.organizationId !== input.organizationId) {
return { ok: false, status: 403, error: 'tenant access denied' };
}
const task = {
id: crypto.randomUUID(),
organizationId: input.organizationId,
title: input.title,
categoryId: input.categoryId
};
await database.stores.tasks.create(task.id, task);
return { ok: true, status: 201, result: task };
}
const own = await createTenantTask(
{ organizationId: 'org-1', userId: 'user-1' },
{ organizationId: 'org-1', title: 'Tenant scoped task', categoryId: 'work' }
);
const denied = await createTenantTask(
{ organizationId: 'org-2', userId: 'user-2' },
{ organizationId: 'org-1', title: 'Cross-tenant write', categoryId: 'work' }
);
const org1Tasks = await database.stores.tasks.getByRelation('organizationId', 'org-1');
const org2Tasks = await database.stores.tasks.getByRelation('organizationId', 'org-2');
return {
ownWrite: own.status,
crossTenantWrite: denied.status,
org1Sees: org1Tasks.result.map((task) => task.title),
org2Sees: org2Tasks.result.length
};
```const database = api.createInMemoryDatabase({ stores: ['categories', 'tasks'] });
await database.connect();
await database.stores.categories.create('work', { id: 'work', name: 'Work' });
async function createTenantTask(context, input) {
if (context.organizationId !== input.organizationId) {
return { ok: false, status: 403, error: 'tenant access denied' };
}
const task = {
id: crypto.randomUUID(),
organizationId: input.organizationId,
title: input.title,
categoryId: input.categoryId
};
await database.stores.tasks.create(task.id, task);
return { ok: true, status: 201, result: task };
}
const own = await createTenantTask(
{ organizationId: 'org-1', userId: 'user-1' },
{ organizationId: 'org-1', title: 'Tenant scoped task', categoryId: 'work' }
);
const denied = await createTenantTask(
{ organizationId: 'org-2', userId: 'user-2' },
{ organizationId: 'org-1', title: 'Cross-tenant write', categoryId: 'work' }
);
const org1Tasks = await database.stores.tasks.getByRelation('organizationId', 'org-1');
const org2Tasks = await database.stores.tasks.getByRelation('organizationId', 'org-2');
return {
ownWrite: own.status,
crossTenantWrite: denied.status,
org1Sees: org1Tasks.result.map((task) => task.title),
org2Sees: org2Tasks.result.length
};A complete browser-only product slice using Category and Task records.
Subscribe a notification worker to the mediator and persist each delivery in its own in-memory store — no fake HTTP endpoint.
### Microservices MVP Day 2 — worker persisting real notifications
```ts
const database = api.createInMemoryDatabase({ stores: ['tasks', 'notifications'] });
const mediator = api.createMessageMediator();
await database.connect();
await mediator.subscribe('tasks.created.v1', async (event) => {
await database.stores.notifications.create(crypto.randomUUID(), {
template: 'task-created',
taskId: event.payload.id,
title: event.payload.title
});
});
mediator.registerHandler('tasks.create.v1', async (message) => {
const task = {
id: crypto.randomUUID(),
title: message.payload.title,
categoryId: message.payload.categoryId,
completed: false
};
await database.stores.tasks.create(task.id, task);
await mediator.publish({ name: 'tasks.created.v1', payload: task });
return { ok: true, result: task };
});
const created = await mediator.request({
contract: 'tasks.create.v1',
payload: { title: 'Notify assignee', categoryId: 'work' }
});
const sent = await database.stores.notifications.getAll({}, { page: 1, size: 10 });
return {
task: created.result.title,
notificationsDelivered: sent.total,
firstNotification: sent.result[0]
};
```const database = api.createInMemoryDatabase({ stores: ['tasks', 'notifications'] });
const mediator = api.createMessageMediator();
await database.connect();
await mediator.subscribe('tasks.created.v1', async (event) => {
await database.stores.notifications.create(crypto.randomUUID(), {
template: 'task-created',
taskId: event.payload.id,
title: event.payload.title
});
});
mediator.registerHandler('tasks.create.v1', async (message) => {
const task = {
id: crypto.randomUUID(),
title: message.payload.title,
categoryId: message.payload.categoryId,
completed: false
};
await database.stores.tasks.create(task.id, task);
await mediator.publish({ name: 'tasks.created.v1', payload: task });
return { ok: true, result: task };
});
const created = await mediator.request({
contract: 'tasks.create.v1',
payload: { title: 'Notify assignee', categoryId: 'work' }
});
const sent = await database.stores.notifications.getAll({}, { page: 1, size: 10 });
return {
task: created.result.title,
notificationsDelivered: sent.total,
firstNotification: sent.result[0]
};A complete browser-only product slice using Category and Task records.
Route a poison message to the dead-letter queue and replay it, proving the failure mode is explicit and measured.
### Microservices MVP Release — explicit failure with DLQ replay
```ts
const deadLetterQueue = api.createDeadLetterQueue({ maxAttempts: 2 });
const mediator = api.createMessageMediator();
mediator.registerHandler('tasks.create.v1', async (message) => {
if (!message.payload.title) {
const record = await deadLetterQueue.enqueue({
entityName: 'Task',
resourceId: message.payload.categoryId ?? 'unknown',
operation: 'tasks.create.v1',
payload: message.payload
});
return { ok: false, error: 'queued for replay', deadLetterId: record.id };
}
return { ok: true, result: { id: crypto.randomUUID(), ...message.payload } };
});
const failed = await mediator.request({
contract: 'tasks.create.v1',
payload: { categoryId: 'work' }
});
const report = await deadLetterQueue.replay({
'tasks.create.v1': async (record) => {
if (!record.payload.title) throw new Error('title is required');
return record.id;
}
});
const after = await deadLetterQueue.find(failed.deadLetterId);
return {
firstAttempt: failed.error,
replayReport: report,
statusAfterReplay: after.status,
lastError: after.lastError
};
```const deadLetterQueue = api.createDeadLetterQueue({ maxAttempts: 2 });
const mediator = api.createMessageMediator();
mediator.registerHandler('tasks.create.v1', async (message) => {
if (!message.payload.title) {
const record = await deadLetterQueue.enqueue({
entityName: 'Task',
resourceId: message.payload.categoryId ?? 'unknown',
operation: 'tasks.create.v1',
payload: message.payload
});
return { ok: false, error: 'queued for replay', deadLetterId: record.id };
}
return { ok: true, result: { id: crypto.randomUUID(), ...message.payload } };
});
const failed = await mediator.request({
contract: 'tasks.create.v1',
payload: { categoryId: 'work' }
});
const report = await deadLetterQueue.replay({
'tasks.create.v1': async (record) => {
if (!record.payload.title) throw new Error('title is required');
return record.id;
}
});
const after = await deadLetterQueue.find(failed.deadLetterId);
return {
firstAttempt: failed.error,
replayReport: report,
statusAfterReplay: after.status,
lastError: after.lastError
};Local relational state, workers, events and IndexedDB-backed UI examples.
Open a client, create Category and Task records, then read them back.
### Getting started
```ts
const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
await client.table('categories').add({
id: 'work',
name: 'Work',
color: '#2563eb',
createdAt: Date.now(),
updatedAt: Date.now()
});
await client.table('tasks').add({
id: 'task-1',
title: 'Write the Cana tutorial',
categoryId: 'work',
completed: false,
priority: 'high',
createdAt: Date.now(),
updatedAt: Date.now()
});
return {
backend: client.backend,
category: await client.table('categories').get('work'),
task: await client.table('tasks').get('task-1')
};
```const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
await client.table('categories').add({
id: 'work',
name: 'Work',
color: '#2563eb',
createdAt: Date.now(),
updatedAt: Date.now()
});
await client.table('tasks').add({
id: 'task-1',
title: 'Write the Cana tutorial',
categoryId: 'work',
completed: false,
priority: 'high',
createdAt: Date.now(),
updatedAt: Date.now()
});
return {
backend: client.backend,
category: await client.table('categories').get('work'),
task: await client.table('tasks').get('task-1')
};Local relational state, workers, events and IndexedDB-backed UI examples.
Start with Category, then raise the version and add the Task table.
### Schema upgrade
```ts
const v1 = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [{ name: 'categories', keyPath: 'id' }]
}
});
await v1.open();
await v1.table('categories').add({ id: 'work', name: 'Work' });
await v1.close();
const v2 = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await v2.open();
await v2.table('tasks').add({
id: 'task-1',
title: 'Created after upgrade',
categoryId: 'work',
completed: false,
priority: 'medium',
createdAt: Date.now(),
updatedAt: Date.now()
});
return {
categories: await v2.table('categories').query(),
tasks: await v2.table('tasks').query()
};
```const v1 = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [{ name: 'categories', keyPath: 'id' }]
}
});
await v1.open();
await v1.table('categories').add({ id: 'work', name: 'Work' });
await v1.close();
const v2 = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await v2.open();
await v2.table('tasks').add({
id: 'task-1',
title: 'Created after upgrade',
categoryId: 'work',
completed: false,
priority: 'medium',
createdAt: Date.now(),
updatedAt: Date.now()
});
return {
categories: await v2.table('categories').query(),
tasks: await v2.table('tasks').query()
};Local relational state, workers, events and IndexedDB-backed UI examples.
Use stable ids in Category and Task records.
### Keys
```ts
const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
await client.table('categories').add({
id: 'docs',
name: 'Docs',
color: '#0f766e',
createdAt: 1,
updatedAt: 1
});
await client.table('tasks').add({
id: 'docs-1',
title: 'Document stable keys',
categoryId: 'docs',
completed: false,
priority: 'medium',
createdAt: 2,
updatedAt: 2
});
return {
categoryKey: 'docs',
taskKey: 'docs-1',
task: await client.table('tasks').get('docs-1')
};
```const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
await client.table('categories').add({
id: 'docs',
name: 'Docs',
color: '#0f766e',
createdAt: 1,
updatedAt: 1
});
await client.table('tasks').add({
id: 'docs-1',
title: 'Document stable keys',
categoryId: 'docs',
completed: false,
priority: 'medium',
createdAt: 2,
updatedAt: 2
});
return {
categoryKey: 'docs',
taskKey: 'docs-1',
task: await client.table('tasks').get('docs-1')
};Local relational state, workers, events and IndexedDB-backed UI examples.
Create, read, update and delete one Task.
### CRUD
```ts
const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
await client.table('categories').add({ id: 'work', name: 'Work', color: '#2563eb' });
const tasks = client.table('tasks');
await tasks.add({
id: 'task-1',
title: 'Draft the tutorial',
categoryId: 'work',
completed: false,
priority: 'high',
createdAt: 1,
updatedAt: 1
});
await tasks.update('task-1', { completed: true, updatedAt: 2 });
const afterUpdate = await tasks.get('task-1');
await tasks.delete('task-1');
return { afterUpdate, afterDelete: await tasks.get('task-1') };
```const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
await client.table('categories').add({ id: 'work', name: 'Work', color: '#2563eb' });
const tasks = client.table('tasks');
await tasks.add({
id: 'task-1',
title: 'Draft the tutorial',
categoryId: 'work',
completed: false,
priority: 'high',
createdAt: 1,
updatedAt: 1
});
await tasks.update('task-1', { completed: true, updatedAt: 2 });
const afterUpdate = await tasks.get('task-1');
await tasks.delete('task-1');
return { afterUpdate, afterDelete: await tasks.get('task-1') };Local relational state, workers, events and IndexedDB-backed UI examples.
Seed Category and Task records with bulk operations.
### Bulk operations
```ts
const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
const now = Date.now();
await client.table('categories').bulkAdd([
{ id: 'work', name: 'Work', color: '#2563eb', createdAt: now, updatedAt: now },
{ id: 'home', name: 'Home', color: '#16a34a', createdAt: now, updatedAt: now }
]);
await client.table('tasks').bulkAdd([
{
id: 'task-1',
title: 'Write the Cana tutorial',
categoryId: 'work',
completed: false,
priority: 'high',
createdAt: now,
updatedAt: now
},
{
id: 'task-2',
title: 'Review category filters',
categoryId: 'home',
completed: true,
priority: 'medium',
createdAt: now,
updatedAt: now + 1
}
]);
const put = await client.table('tasks').bulkPut([
{
id: 'task-2',
title: 'Review category filters',
categoryId: 'home',
completed: false,
priority: 'high',
createdAt: Date.now(),
updatedAt: Date.now()
},
{
id: 'task-3',
title: 'Publish the example app',
categoryId: 'work',
completed: false,
priority: 'medium',
createdAt: Date.now(),
updatedAt: Date.now()
}
]);
return {
put,
categories: await client.table('categories').query({ index: 'byName' }),
tasks: await client.table('tasks').query({ index: 'byUpdatedAt' })
};
```const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
const now = Date.now();
await client.table('categories').bulkAdd([
{ id: 'work', name: 'Work', color: '#2563eb', createdAt: now, updatedAt: now },
{ id: 'home', name: 'Home', color: '#16a34a', createdAt: now, updatedAt: now }
]);
await client.table('tasks').bulkAdd([
{
id: 'task-1',
title: 'Write the Cana tutorial',
categoryId: 'work',
completed: false,
priority: 'high',
createdAt: now,
updatedAt: now
},
{
id: 'task-2',
title: 'Review category filters',
categoryId: 'home',
completed: true,
priority: 'medium',
createdAt: now,
updatedAt: now + 1
}
]);
const put = await client.table('tasks').bulkPut([
{
id: 'task-2',
title: 'Review category filters',
categoryId: 'home',
completed: false,
priority: 'high',
createdAt: Date.now(),
updatedAt: Date.now()
},
{
id: 'task-3',
title: 'Publish the example app',
categoryId: 'work',
completed: false,
priority: 'medium',
createdAt: Date.now(),
updatedAt: Date.now()
}
]);
return {
put,
categories: await client.table('categories').query({ index: 'byName' }),
tasks: await client.table('tasks').query({ index: 'byUpdatedAt' })
};Local relational state, workers, events and IndexedDB-backed UI examples.
Run an indexed Task query by Category and inspect the plan.
### Query + explain
```ts
const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
const now = Date.now();
await client.table('categories').bulkAdd([
{ id: 'work', name: 'Work', color: '#2563eb', createdAt: now, updatedAt: now },
{ id: 'home', name: 'Home', color: '#16a34a', createdAt: now, updatedAt: now }
]);
await client.table('tasks').bulkAdd([
{
id: 'task-1',
title: 'Write the Cana tutorial',
categoryId: 'work',
completed: false,
priority: 'high',
createdAt: now,
updatedAt: now
},
{
id: 'task-2',
title: 'Review category filters',
categoryId: 'home',
completed: true,
priority: 'medium',
createdAt: now,
updatedAt: now + 1
}
]);
const { records, plan } = await client.table('tasks').explain({
index: 'byCategory',
equals: 'work'
});
return { records, plan };
```const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
const now = Date.now();
await client.table('categories').bulkAdd([
{ id: 'work', name: 'Work', color: '#2563eb', createdAt: now, updatedAt: now },
{ id: 'home', name: 'Home', color: '#16a34a', createdAt: now, updatedAt: now }
]);
await client.table('tasks').bulkAdd([
{
id: 'task-1',
title: 'Write the Cana tutorial',
categoryId: 'work',
completed: false,
priority: 'high',
createdAt: now,
updatedAt: now
},
{
id: 'task-2',
title: 'Review category filters',
categoryId: 'home',
completed: true,
priority: 'medium',
createdAt: now,
updatedAt: now + 1
}
]);
const { records, plan } = await client.table('tasks').explain({
index: 'byCategory',
equals: 'work'
});
return { records, plan };Local relational state, workers, events and IndexedDB-backed UI examples.
Create one Category and its first Task in a single commit.
### Transactions
```ts
const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
const tx = await client.transaction('readwrite', ['categories', 'tasks'], async (scope) => {
const now = Date.now();
await scope.table('categories').put({
id: 'ops',
name: 'Operations',
color: '#f97316',
createdAt: now,
updatedAt: now
});
await scope.table('tasks').put({
id: 'ops-1',
title: 'Created with the category',
categoryId: 'ops',
completed: false,
priority: 'high',
createdAt: now,
updatedAt: now
});
return 'ok';
});
return {
outcome: tx.outcome,
result: tx.result,
categories: await client.table('categories').query(),
tasks: await client.table('tasks').query()
};
```const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
const tx = await client.transaction('readwrite', ['categories', 'tasks'], async (scope) => {
const now = Date.now();
await scope.table('categories').put({
id: 'ops',
name: 'Operations',
color: '#f97316',
createdAt: now,
updatedAt: now
});
await scope.table('tasks').put({
id: 'ops-1',
title: 'Created with the category',
categoryId: 'ops',
completed: false,
priority: 'high',
createdAt: now,
updatedAt: now
});
return 'ok';
});
return {
outcome: tx.outcome,
result: tx.result,
categories: await client.table('categories').query(),
tasks: await client.table('tasks').query()
};Local relational state, workers, events and IndexedDB-backed UI examples.
Subscribe and collect committed Task events.
### Change events
```ts
const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
const seen = [];
const stop = client.subscribe((event) => {
seen.push({ cursor: event.cursor, type: event.type, store: event.store, key: event.key });
});
await client.table('categories').add({ id: 'docs', name: 'Docs', color: '#0f766e' });
await client.table('tasks').add({
id: 'docs-1',
title: 'Listen to Cana events',
categoryId: 'docs',
completed: false,
priority: 'medium',
createdAt: 1,
updatedAt: 1
});
await client.table('tasks').update('docs-1', { completed: true, updatedAt: 2 });
stop();
return { events: seen };
```const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
const seen = [];
const stop = client.subscribe((event) => {
seen.push({ cursor: event.cursor, type: event.type, store: event.store, key: event.key });
});
await client.table('categories').add({ id: 'docs', name: 'Docs', color: '#0f766e' });
await client.table('tasks').add({
id: 'docs-1',
title: 'Listen to Cana events',
categoryId: 'docs',
completed: false,
priority: 'medium',
createdAt: 1,
updatedAt: 1
});
await client.table('tasks').update('docs-1', { completed: true, updatedAt: 2 });
stop();
return { events: seen };Local relational state, workers, events and IndexedDB-backed UI examples.
Run beforeWrite and afterCommit hooks around Task writes.
### Hooks
```ts
const trail = [];
const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
},
hooks: {
beforeWrite: (ctx) => { trail.push('before:' + ctx.store + ':' + ctx.type); },
afterCommit: (events) => { trail.push('commit:' + events.length); }
}
});
await client.open();
await client.table('categories').add({ id: 'docs', name: 'Docs', color: '#0f766e' });
await client.table('tasks').add({
id: 'docs-1',
title: 'Passes through hooks',
categoryId: 'docs',
completed: false,
priority: 'medium',
createdAt: 1,
updatedAt: 1
});
return { trail, task: await client.table('tasks').get('docs-1') };
```const trail = [];
const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
},
hooks: {
beforeWrite: (ctx) => { trail.push('before:' + ctx.store + ':' + ctx.type); },
afterCommit: (events) => { trail.push('commit:' + events.length); }
}
});
await client.open();
await client.table('categories').add({ id: 'docs', name: 'Docs', color: '#0f766e' });
await client.table('tasks').add({
id: 'docs-1',
title: 'Passes through hooks',
categoryId: 'docs',
completed: false,
priority: 'medium',
createdAt: 1,
updatedAt: 1
});
return { trail, task: await client.table('tasks').get('docs-1') };Local relational state, workers, events and IndexedDB-backed UI examples.
Detect duplicate Category ids with isCanaErrorCode.
### Errors
```ts
const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
await client.table('categories').add({ id: 'docs', name: 'Docs', color: '#0f766e' });
try {
await client.table('categories').add({ id: 'docs', name: 'Duplicada', color: '#dc2626' });
return { unexpected: 'no error' };
} catch (error) {
return {
isCanaError: cana.isCanaError(error),
constraint: cana.isCanaErrorCode(error, 'ConstraintViolation'),
code: error && error.code
};
}
```const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
await client.table('categories').add({ id: 'docs', name: 'Docs', color: '#0f766e' });
try {
await client.table('categories').add({ id: 'docs', name: 'Duplicada', color: '#dc2626' });
return { unexpected: 'no error' };
} catch (error) {
return {
isCanaError: cana.isCanaError(error),
constraint: cana.isCanaErrorCode(error, 'ConstraintViolation'),
code: error && error.code
};
}Local relational state, workers, events and IndexedDB-backed UI examples.
Read storageState and durabilityAssessment after writing Task data.
### Storage assessment
```ts
const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
await client.table('categories').add({ id: 'docs', name: 'Docs', color: '#0f766e' });
await client.table('tasks').add({
id: 'docs-1',
title: 'Durable data',
categoryId: 'docs',
completed: false,
priority: 'high',
createdAt: 1,
updatedAt: 1
});
const storage = await client.storageState();
const durability = await client.durabilityAssessment();
return { backend: client.backend, storage, durability };
```const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
await client.table('categories').add({ id: 'docs', name: 'Docs', color: '#0f766e' });
await client.table('tasks').add({
id: 'docs-1',
title: 'Durable data',
categoryId: 'docs',
completed: false,
priority: 'high',
createdAt: 1,
updatedAt: 1
});
const storage = await client.storageState();
const durability = await client.durabilityAssessment();
return { backend: client.backend, storage, durability };Local relational state, workers, events and IndexedDB-backed UI examples.
Resolve a committed Task write with the operation ledger enabled.
### Operation ledger
```ts
const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
},
operationLedger: true
});
await client.open();
const tx = await client.transaction('readwrite', ['categories', 'tasks'], async (scope) => {
const now = Date.now();
await scope.table('categories').put({ id: 'docs', name: 'Docs', color: '#0f766e' });
await scope.table('tasks').put({
id: 'docs-1',
title: 'Reconcile uncertain write',
categoryId: 'docs',
completed: false,
priority: 'high',
createdAt: now,
updatedAt: now
});
return 'wrote';
});
const resolved = await client.resolveWrite(tx.correlationId, tx.attemptedAt);
return { outcome: tx.outcome, resolved, task: await client.table('tasks').get('docs-1') };
```const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
},
operationLedger: true
});
await client.open();
const tx = await client.transaction('readwrite', ['categories', 'tasks'], async (scope) => {
const now = Date.now();
await scope.table('categories').put({ id: 'docs', name: 'Docs', color: '#0f766e' });
await scope.table('tasks').put({
id: 'docs-1',
title: 'Reconcile uncertain write',
categoryId: 'docs',
completed: false,
priority: 'high',
createdAt: now,
updatedAt: now
});
return 'wrote';
});
const resolved = await client.resolveWrite(tx.correlationId, tx.attemptedAt);
return { outcome: tx.outcome, resolved, task: await client.table('tasks').get('docs-1') };Local relational state, workers, events and IndexedDB-backed UI examples.
Export Category and Task stores as plain data.
### Export
```ts
const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
const now = Date.now();
await client.table('categories').bulkAdd([
{ id: 'work', name: 'Work', color: '#2563eb', createdAt: now, updatedAt: now },
{ id: 'home', name: 'Home', color: '#16a34a', createdAt: now, updatedAt: now }
]);
await client.table('tasks').bulkAdd([
{
id: 'task-1',
title: 'Write the Cana tutorial',
categoryId: 'work',
completed: false,
priority: 'high',
createdAt: now,
updatedAt: now
},
{
id: 'task-2',
title: 'Review category filters',
categoryId: 'home',
completed: true,
priority: 'medium',
createdAt: now,
updatedAt: now + 1
}
]);
const dump = await client.exportAll();
return dump;
```const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
const now = Date.now();
await client.table('categories').bulkAdd([
{ id: 'work', name: 'Work', color: '#2563eb', createdAt: now, updatedAt: now },
{ id: 'home', name: 'Home', color: '#16a34a', createdAt: now, updatedAt: now }
]);
await client.table('tasks').bulkAdd([
{
id: 'task-1',
title: 'Write the Cana tutorial',
categoryId: 'work',
completed: false,
priority: 'high',
createdAt: now,
updatedAt: now
},
{
id: 'task-2',
title: 'Review category filters',
categoryId: 'home',
completed: true,
priority: 'medium',
createdAt: now,
updatedAt: now + 1
}
]);
const dump = await client.exportAll();
return dump;Local relational state, workers, events and IndexedDB-backed UI examples.
Show client.backend after opening the task database.
### Backend selection
```ts
const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
},
fallback: 'localStorage'
});
await client.open();
await client.table('categories').add({ id: 'docs', name: 'Docs', color: '#0f766e' });
return { backend: client.backend, categories: await client.table('categories').query() };
```const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
},
fallback: 'localStorage'
});
await client.open();
await client.table('categories').add({ id: 'docs', name: 'Docs', color: '#0f766e' });
return { backend: client.backend, categories: await client.table('categories').query() };Local relational state, workers, events and IndexedDB-backed UI examples.
Use createCanaDatabaseClient with Category and Task stores.
### Factory adapter
```ts
const adapter = cana.createCanaDatabaseClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await adapter.connect();
await adapter.stores.categories.add({ id: 'docs', name: 'Docs', color: '#0f766e' });
await adapter.stores.tasks.add({
id: 'docs-1',
title: 'Created through the adapter',
categoryId: 'docs',
completed: false,
priority: 'medium',
createdAt: 1,
updatedAt: 1
});
const task = await adapter.stores.tasks.get('docs-1');
await adapter.disconnect();
return {
backend: adapter.cana.backend,
stores: Object.keys(adapter.stores),
task
};
```const adapter = cana.createCanaDatabaseClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await adapter.connect();
await adapter.stores.categories.add({ id: 'docs', name: 'Docs', color: '#0f766e' });
await adapter.stores.tasks.add({
id: 'docs-1',
title: 'Created through the adapter',
categoryId: 'docs',
completed: false,
priority: 'medium',
createdAt: 1,
updatedAt: 1
});
const task = await adapter.stores.tasks.get('docs-1');
await adapter.disconnect();
return {
backend: adapter.cana.backend,
stores: Object.keys(adapter.stores),
task
};Local relational state, workers, events and IndexedDB-backed UI examples.
Drive Cana through createWorkerHost, createRouter and createWorkerClient using MessageChannel.
### Worker client flow
```ts
const channel = new MessageChannel();
channel.port1.start?.();
channel.port2.start?.();
const broadcasts = [];
const router = cana.createRouter({
port: channel.port1,
timeoutMs: 5000,
onBroadcast(event) {
broadcasts.push({
cursor: event.cursor,
type: event.type,
store: event.store,
key: event.key
});
}
});
const host = cana.createWorkerHost({
port: channel.port2,
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
},
originId: 'docs-worker-host',
retainedEvents: 20,
operationLedger: true
});
const workerClient = cana.createWorkerClient(router);
try {
await workerClient.open();
const now = Date.now();
await workerClient.put('categories', {
id: 'work',
name: 'Work',
color: '#2563eb',
createdAt: now,
updatedAt: now
});
await workerClient.add('tasks', {
id: 'task-worker-1',
title: 'Persist through the worker boundary',
categoryId: 'work',
completed: false,
priority: 'medium',
createdAt: now,
updatedAt: now
});
const tasks = await workerClient.query('tasks', {
index: 'byCategory',
equals: 'work'
});
const count = await workerClient.count('tasks');
return {
ping: await workerClient.ping(),
count,
tasks,
broadcasts
};
} finally {
await workerClient.close();
router.dispose();
await host.dispose();
channel.port1.close();
channel.port2.close();
}
```const channel = new MessageChannel();
channel.port1.start?.();
channel.port2.start?.();
const broadcasts = [];
const router = cana.createRouter({
port: channel.port1,
timeoutMs: 5000,
onBroadcast(event) {
broadcasts.push({
cursor: event.cursor,
type: event.type,
store: event.store,
key: event.key
});
}
});
const host = cana.createWorkerHost({
port: channel.port2,
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
},
originId: 'docs-worker-host',
retainedEvents: 20,
operationLedger: true
});
const workerClient = cana.createWorkerClient(router);
try {
await workerClient.open();
const now = Date.now();
await workerClient.put('categories', {
id: 'work',
name: 'Work',
color: '#2563eb',
createdAt: now,
updatedAt: now
});
await workerClient.add('tasks', {
id: 'task-worker-1',
title: 'Persist through the worker boundary',
categoryId: 'work',
completed: false,
priority: 'medium',
createdAt: now,
updatedAt: now
});
const tasks = await workerClient.query('tasks', {
index: 'byCategory',
equals: 'work'
});
const count = await workerClient.count('tasks');
return {
ping: await workerClient.ping(),
count,
tasks,
broadcasts
};
} finally {
await workerClient.close();
router.dispose();
await host.dispose();
channel.port1.close();
channel.port2.close();
}Local relational state, workers, events and IndexedDB-backed UI examples.
Open a real Cana IndexedDB client, seed Category, and create the first Task — the whole workflow runs in the browser.
### SPA MVP Day 1 — offline records in Cana
```ts
const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
const now = Date.now();
await client.table('categories').add({
id: 'work',
name: 'Work',
color: '#2563eb',
createdAt: now,
updatedAt: now
});
await client.table('tasks').add({
id: 'task-1',
title: 'Offline task',
categoryId: 'work',
completed: false,
priority: 'high',
createdAt: now,
updatedAt: now
});
return {
backend: client.backend,
category: await client.table('categories').get('work'),
task: await client.table('tasks').get('task-1')
};
```const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
const now = Date.now();
await client.table('categories').add({
id: 'work',
name: 'Work',
color: '#2563eb',
createdAt: now,
updatedAt: now
});
await client.table('tasks').add({
id: 'task-1',
title: 'Offline task',
categoryId: 'work',
completed: false,
priority: 'high',
createdAt: now,
updatedAt: now
});
return {
backend: client.backend,
category: await client.table('categories').get('work'),
task: await client.table('tasks').get('task-1')
};Local relational state, workers, events and IndexedDB-backed UI examples.
Subscribe to committed change events, write and update a Task, then read it back through the byCategory index.
### SPA MVP Day 2 — UI state from Cana events and indexed queries
```ts
const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
const seen = [];
const stop = client.subscribe((event) => {
seen.push({ type: event.type, store: event.store, key: event.key });
});
const now = Date.now();
await client.table('categories').add({
id: 'work',
name: 'Work',
color: '#2563eb',
createdAt: now,
updatedAt: now
});
await client.table('tasks').add({
id: 'task-1',
title: 'Listen to local changes',
categoryId: 'work',
completed: false,
priority: 'medium',
createdAt: now,
updatedAt: now
});
await client.table('tasks').update('task-1', { completed: true, updatedAt: now + 1 });
stop();
const workTasks = await client.table('tasks').query({
index: 'byCategory',
equals: 'work'
});
return {
events: seen,
workTasks: workTasks.map((task) => ({ title: task.title, completed: task.completed }))
};
```const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
const seen = [];
const stop = client.subscribe((event) => {
seen.push({ type: event.type, store: event.store, key: event.key });
});
const now = Date.now();
await client.table('categories').add({
id: 'work',
name: 'Work',
color: '#2563eb',
createdAt: now,
updatedAt: now
});
await client.table('tasks').add({
id: 'task-1',
title: 'Listen to local changes',
categoryId: 'work',
completed: false,
priority: 'medium',
createdAt: now,
updatedAt: now
});
await client.table('tasks').update('task-1', { completed: true, updatedAt: now + 1 });
stop();
const workTasks = await client.table('tasks').query({
index: 'byCategory',
equals: 'work'
});
return {
events: seen,
workTasks: workTasks.map((task) => ({ title: task.title, completed: task.completed }))
};Local relational state, workers, events and IndexedDB-backed UI examples.
Close the client and reopen the same database: the offline records survive, proving durable local state.
### SPA MVP Release — durability across reopen
```ts
const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
const now = Date.now();
await client.table('categories').add({
id: 'work',
name: 'Work',
color: '#2563eb',
createdAt: now,
updatedAt: now
});
await client.table('tasks').add({
id: 'task-1',
title: 'Survives reload',
categoryId: 'work',
completed: false,
priority: 'high',
createdAt: now,
updatedAt: now
});
await client.close();
const reopened = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await reopened.open();
const tasksAfterReopen = await reopened.table('tasks').query();
const categoriesAfterReopen = await reopened.table('categories').query();
return {
backend: reopened.backend,
categoriesAfterReopen: categoriesAfterReopen.length,
tasksAfterReopen: tasksAfterReopen.map((task) => task.title)
};
```const client = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await client.open();
const now = Date.now();
await client.table('categories').add({
id: 'work',
name: 'Work',
color: '#2563eb',
createdAt: now,
updatedAt: now
});
await client.table('tasks').add({
id: 'task-1',
title: 'Survives reload',
categoryId: 'work',
completed: false,
priority: 'high',
createdAt: now,
updatedAt: now
});
await client.close();
const reopened = cana.createClient({
name: dbName,
schema: {
version: 1,
stores: [
{ name: 'categories', keyPath: 'id', indexes: [{ name: 'byName', keyPath: 'name', unique: true }] },
{
name: 'tasks',
keyPath: 'id',
indexes: [
{ name: 'byCategory', keyPath: 'categoryId' },
{ name: 'byCompleted', keyPath: 'completed' },
{ name: 'byUpdatedAt', keyPath: 'updatedAt' }
]
}
]
}
});
await reopened.open();
const tasksAfterReopen = await reopened.table('tasks').query();
const categoriesAfterReopen = await reopened.table('categories').query();
return {
backend: reopened.backend,
categoriesAfterReopen: categoriesAfterReopen.length,
tasksAfterReopen: tasksAfterReopen.map((task) => task.title)
};Request/response and event composition across independent domain modules.
Exchange messages between Category and Task domains, then compose a read model through mediator request/response.
### In-memory mediator
```ts
const domainMessages = [];
const events = [];
const mediator = api.createInMemory();
const categories = new Map([
['work', { id: 'work', name: 'Work', color: '#2563eb' }],
['home', { id: 'home', name: 'Home', color: '#16a34a' }]
]);
const tasks = [];
await mediator.subscribe('tasks.created', async (event) => {
events.push({
title: event.payload.title,
categoryId: event.payload.categoryId
});
});
mediator.registerHandler('categories.get.v1', async (message) => {
domainMessages.push({
from: message.metadata.sourceDomain,
to: 'Categories',
contract: 'categories.get.v1',
categoryId: message.payload.id
});
return {
ok: true,
result: categories.get(message.payload.id) ?? null
};
});
mediator.registerHandler('tasks.create.v1', async (message) => {
const task = {
id: message.payload.id,
title: message.payload.title,
categoryId: message.payload.categoryId,
completed: false
};
tasks.push(task);
await mediator.publish({ name: 'tasks.created', payload: task });
return { ok: true, result: task };
});
mediator.registerHandler('tasks.board.v1', async () => {
const cards = await Promise.all(tasks.map(async (task) => {
const category = await mediator.request({
contract: 'categories.get.v1',
payload: { id: task.categoryId },
metadata: {
sourceDomain: 'Tasks',
reason: 'compose task board read model'
}
});
return {
id: task.id,
title: task.title,
categoryName: category.result?.name ?? 'Uncategorized',
categoryColor: category.result?.color ?? '#64748b'
};
}));
return {
ok: true,
result: {
readModel: 'TaskBoard',
composedFrom: ['Tasks', 'Categories'],
cards
}
};
});
const response = await mediator.request({
contract: 'tasks.create.v1',
payload: {
id: 'task-1',
title: 'Wire mediator events',
categoryId: 'work'
},
metadata: { source: 'browser-playground' }
});
const board = await mediator.request({
contract: 'tasks.board.v1',
payload: {},
metadata: { source: 'task-board-page' }
});
return {
createdTask: response.result,
board: board.result,
domainMessages,
events
};
```const domainMessages = [];
const events = [];
const mediator = api.createInMemory();
const categories = new Map([
['work', { id: 'work', name: 'Work', color: '#2563eb' }],
['home', { id: 'home', name: 'Home', color: '#16a34a' }]
]);
const tasks = [];
await mediator.subscribe('tasks.created', async (event) => {
events.push({
title: event.payload.title,
categoryId: event.payload.categoryId
});
});
mediator.registerHandler('categories.get.v1', async (message) => {
domainMessages.push({
from: message.metadata.sourceDomain,
to: 'Categories',
contract: 'categories.get.v1',
categoryId: message.payload.id
});
return {
ok: true,
result: categories.get(message.payload.id) ?? null
};
});
mediator.registerHandler('tasks.create.v1', async (message) => {
const task = {
id: message.payload.id,
title: message.payload.title,
categoryId: message.payload.categoryId,
completed: false
};
tasks.push(task);
await mediator.publish({ name: 'tasks.created', payload: task });
return { ok: true, result: task };
});
mediator.registerHandler('tasks.board.v1', async () => {
const cards = await Promise.all(tasks.map(async (task) => {
const category = await mediator.request({
contract: 'categories.get.v1',
payload: { id: task.categoryId },
metadata: {
sourceDomain: 'Tasks',
reason: 'compose task board read model'
}
});
return {
id: task.id,
title: task.title,
categoryName: category.result?.name ?? 'Uncategorized',
categoryColor: category.result?.color ?? '#64748b'
};
}));
return {
ok: true,
result: {
readModel: 'TaskBoard',
composedFrom: ['Tasks', 'Categories'],
cards
}
};
});
const response = await mediator.request({
contract: 'tasks.create.v1',
payload: {
id: 'task-1',
title: 'Wire mediator events',
categoryId: 'work'
},
metadata: { source: 'browser-playground' }
});
const board = await mediator.request({
contract: 'tasks.board.v1',
payload: {},
metadata: { source: 'task-board-page' }
});
return {
createdTask: response.result,
board: board.result,
domainMessages,
events
};Request/response and event composition across independent domain modules.
Prove request/response and publish/listen on the real in-memory mediator, including the explicit error for an unknown contract.
### Microservices MVP Day 1 — contract over the in-memory mediator
```ts
const mediator = api.createInMemory();
const receivedEvents = [];
await mediator.subscribe('tasks.created.v1', async (event) => {
receivedEvents.push(event.payload.title);
});
mediator.registerHandler('tasks.create.v1', async (message) => {
const task = {
id: crypto.randomUUID(),
title: message.payload.title,
categoryId: message.payload.categoryId,
completed: false
};
await mediator.publish({ name: 'tasks.created.v1', payload: task });
return { ok: true, result: task };
});
const created = await mediator.request({
contract: 'tasks.create.v1',
payload: { title: 'Notify assignee', categoryId: 'work' }
});
const unknown = await mediator.request({
contract: 'tasks.unknown.v1',
payload: {}
});
return {
createdTask: created.result.title,
eventDelivered: receivedEvents,
unknownContractError: unknown.error
};
```const mediator = api.createInMemory();
const receivedEvents = [];
await mediator.subscribe('tasks.created.v1', async (event) => {
receivedEvents.push(event.payload.title);
});
mediator.registerHandler('tasks.create.v1', async (message) => {
const task = {
id: crypto.randomUUID(),
title: message.payload.title,
categoryId: message.payload.categoryId,
completed: false
};
await mediator.publish({ name: 'tasks.created.v1', payload: task });
return { ok: true, result: task };
});
const created = await mediator.request({
contract: 'tasks.create.v1',
payload: { title: 'Notify assignee', categoryId: 'work' }
});
const unknown = await mediator.request({
contract: 'tasks.unknown.v1',
payload: {}
});
return {
createdTask: created.result.title,
eventDelivered: receivedEvents,
unknownContractError: unknown.error
};Small state records with the same contract shape used by replaceable adapters.
Cache UI preferences for the Task list with the same service-result shape used by package adapters.
### In-memory key/value
```ts
const client = api.createInMemory();
await client.connect();
await client.set('ui:selected-category', {
id: 'work',
name: 'Work',
visibleTaskIds: ['task-1', 'task-3']
});
await client.set('ui:last-sort', 'priority-desc');
const selectedCategory = await client.get('ui:selected-category');
const lastSort = await client.get('ui:last-sort');
await client.del('ui:last-sort');
const deletedSort = await client.get('ui:last-sort');
await client.disconnect();
return {
selectedCategory: selectedCategory.result,
lastSort: lastSort.result,
deletedSort: deletedSort.result
};
```const client = api.createInMemory();
await client.connect();
await client.set('ui:selected-category', {
id: 'work',
name: 'Work',
visibleTaskIds: ['task-1', 'task-3']
});
await client.set('ui:last-sort', 'priority-desc');
const selectedCategory = await client.get('ui:selected-category');
const lastSort = await client.get('ui:last-sort');
await client.del('ui:last-sort');
const deletedSort = await client.get('ui:last-sort');
await client.disconnect();
return {
selectedCategory: selectedCategory.result,
lastSort: lastSort.result,
deletedSort: deletedSort.result
};Category-scoped locks around Task workflows.
Protect a Category update while two Task writers compete for the same resource.
### Mutex with in-memory KV
```ts
const keyValue = api.createKeyValueStorage();
const mutex = api.create(keyValue);
const firstWriter = await mutex.lock('category', 'work');
const secondWriter = await mutex.lock('category', 'work');
const lockedBeforeRelease = await mutex.isLocked('category', 'work');
await mutex.unlock('category', 'work');
const lockedAfterRelease = await mutex.isLocked('category', 'work');
return {
firstWriter: firstWriter.result,
secondWriter: secondWriter.result,
lockedBeforeRelease: lockedBeforeRelease.result,
lockedAfterRelease: lockedAfterRelease.result
};
```const keyValue = api.createKeyValueStorage();
const mutex = api.create(keyValue);
const firstWriter = await mutex.lock('category', 'work');
const secondWriter = await mutex.lock('category', 'work');
const lockedBeforeRelease = await mutex.isLocked('category', 'work');
await mutex.unlock('category', 'work');
const lockedAfterRelease = await mutex.isLocked('category', 'work');
return {
firstWriter: firstWriter.result,
secondWriter: secondWriter.result,
lockedBeforeRelease: lockedBeforeRelease.result,
lockedAfterRelease: lockedAfterRelease.result
};A client-side consumer calling the same Task behavior through a stable API surface.
Call Task OpenAPI operations with a browser-safe mock client.
### REST client with mock fetch
```ts
const client = api.createMockClient();
const created = await client.request({
operationId: 'createTask',
method: 'POST',
path: '/tasks',
body: {
id: 'task-1',
title: 'Generate REST SDK example',
categoryId: 'work',
completed: false
}
});
const listed = await client.request({
operationId: 'listTasks',
method: 'GET',
path: '/tasks?categoryId=work'
});
return {
created,
listed
};
```const client = api.createMockClient();
const created = await client.request({
operationId: 'createTask',
method: 'POST',
path: '/tasks',
body: {
id: 'task-1',
title: 'Generate REST SDK example',
categoryId: 'work',
completed: false
}
});
const listed = await client.request({
operationId: 'listTasks',
method: 'GET',
path: '/tasks?categoryId=work'
});
return {
created,
listed
};A client-side consumer calling the same Task behavior through a stable API surface.
Use the realtime client contract to create and list Task records in the browser.
### WebSocket client with fake socket
```ts
const client = api.createFakeClient();
const status = await client.connect();
const created = await client.request({
operationId: 'tasks.create',
input: {
id: 'task-1',
title: 'Render realtime updates',
categoryId: 'home',
completed: false
}
});
const listed = await client.request({
operationId: 'tasks.list',
input: { categoryId: 'home' }
});
const closed = await client.disconnect();
return {
status,
created,
listed,
closed
};
```const client = api.createFakeClient();
const status = await client.connect();
const created = await client.request({
operationId: 'tasks.create',
input: {
id: 'task-1',
title: 'Render realtime updates',
categoryId: 'home',
completed: false
}
});
const listed = await client.request({
operationId: 'tasks.list',
input: { categoryId: 'home' }
});
const closed = await client.disconnect();
return {
status,
created,
listed,
closed
};Domain shape, entities and relationships before runtime code.
Normalize the sample model and collect validation issues (real designer-core API).
### Validate a design
```ts
const raw = api.buildSampleModelPayload();
const state = api.normalizeStatePayload(raw);
const issues = api.collectModelIssues(state);
const errors = issues.filter((issue) => issue.severity === 'error');
return {
ok: errors.length === 0,
issueCount: issues.length,
errorCount: errors.length,
sample: issues.slice(0, 3)
};
```const raw = api.buildSampleModelPayload();
const state = api.normalizeStatePayload(raw);
const issues = api.collectModelIssues(state);
const errors = issues.filter((issue) => issue.severity === 'error');
return {
ok: errors.length === 0,
issueCount: issues.length,
errorCount: errors.length,
sample: issues.slice(0, 3)
};PM2 operations
Jumentix uses PM2 where a long-running VM or container needs process supervision: environment-specific profiles, logs, status, metrics, reloads and startup recovery. The ecosystem files keep REST, WebSocket, gRPC and Service Management processes explicit.
PM2 is not the only deployment option, but it is a strong operational bridge for teams that run Node/Bun services on persistent machines. It daemonizes apps, restarts failed processes, exposes logs and metrics, supports cluster mode and preserves process lists across restarts.
bun run pm2:start:dev:restapi
bun run pm2:start:staging:websocket-rest
bun run pm2:start:prod:grpc-restAdapter catalog
Each adapter sits outside the domain. The application selects ports and contracts; the composition root decides which technology runs in each environment.
Express, Fastify, Restify, AWS Lambda, Cloudflare Workers, Vercel Functions, LoopBack, Sails, Feathers, Derby, AdonisJS, and Total.js.
Socket.IO, Redis Streams, cluster adapter, gRPC, REST fallback, AsyncAPI documents, and generated realtime clients.
PostgreSQL, MySQL, SQL Server, Oracle, SQLite, Aurora DSQL, and RDS through shared persistence contracts.
MongoDB, DynamoDB, Cassandra, Firebase, key-value storage, and in-memory adapters for local tests and prototypes.
In-memory Message Mediator for local/browser flows, with RabbitMQ and BullMQ-compatible contracts for durable Node workers.
PM2, Docker, Serverless, AWS, Azure, Google Cloud, Vercel, Cloudflare, and environment-specific runtime profiles.
Bootstrap adapters read environment configuration, compile the selected database and key-value clients, and inject contracts into the application composition root. That keeps business code independent from Sequelize, Postgres.js, Mongo clients, queues, or HTTP framework request objects.
const database = await compileDatabaseClient({
driver: process.env.JUMENTIX_DATABASE_DRIVER,
});
const taskStore = database.stores.Tasks;
await taskStore.create(task);Selection guide
The goal is not to support every technology for its own sake. The goal is to let each service choose the smallest reliable infrastructure that fits its data, latency, and operations profile.
| Decision | Prefer this when | Jumentix mechanism | Avoids |
|---|---|---|---|
| Express/Fastify/Restify | You want a long-running Node service with standard REST semantics and mature middleware. | HTTP adapter maps request/response to controller methods and OpenAPI validation. | Framework-specific request objects leaking into use-cases. |
| Cloudflare/Vercel/Lambda | Traffic is bursty, globally distributed, or owned by platform function routing. | Function adapter wraps the same operation contracts used by REST controllers. | A separate business implementation for serverless. |
| PostgreSQL/MySQL/SQL Server | You need relational constraints, transactions, reporting, or familiar operations. | Database client factory composes repository adapters behind store contracts. | SQL decisions coupled to domain entities. |
| MongoDB/DynamoDB/Cassandra | Data shape, throughput, distribution, or access patterns fit document/key-value/wide-column storage. | External persistence adapters implement the same repository contract expected by use-cases. | A NoSQL rewrite of the application layer. |
| RabbitMQ/BullMQ | Commands or events must survive process restarts and cross service boundaries. | Message Mediator contracts define subjects, payloads, request/response, and publish/listen behavior. | Consumers importing producer implementations. |
Package map
Centralizes runtime and infrastructure wiring so services avoid one-off bootstraps.
Runtime compositionCompiles the configured database client and keeps selection rules out of business modules.
Database selectionDefines stable store/repository contracts shared by in-memory and external adapters.
PortsCoordinates commands, requests, responses, and events across modules or services.
MessagingReads canonical OpenAPI contracts and exposes typed REST client behavior.
Client SDKProvides local relational state, event listening, and browser persistence for frontend examples and future apps.
Frontend dataExplore the source, run the factory locally, and turn your next Node.js service into a repeatable platform capability.