@feltdb/core
v0.6.9
Published
FeltDB Core - Application-facing database with Browser, Local, and Server runtimes. Durable state, reactive APIs.
Maintainers
Readme
@feltdb/core
The main FeltDB API. This package provides a simple, state-first interface for working with FeltDB.
Installation
npm install @feltdb/coreThis single package includes the database SDK, WASM runtime, React bindings,
migration tools, Studio application, feltdb CLI, and create-feltdb
scaffolder. The only separate FeltDB package is the optional @feltdb/webllm.
npx --package @feltdb/core create-feltdb my-app
npx --package @feltdb/core feltdb studioReact bindings and migration tooling ship as subpath exports of this package:
import { createFeltDB } from '@feltdb/core';
import { useCollection } from '@feltdb/core/react';
import { migrate } from '@feltdb/core/migrate';
import { StudioApp } from '@feltdb/core/studio';Install react and react-dom only when using the React bindings. The
@feltdb/webllm package remains an optional, separately installed AI runtime.
The feltdb studio command serves the Studio application bundled with core.
Quick Start
import { createFeltDB } from '@feltdb/core';
const db = createFeltDB({
namespace: 'my-app',
server: {
url: 'https://db.example.com',
token: process.env.FELTDB_API_KEY!,
},
});
const todos = db.collection('todos');
// Insert
await todos.insert({
id: '1',
title: 'Learn FeltDB',
completed: false,
});
// Query
const items = await todos.find({ completed: false });
// Update
await todos.update('1', { completed: true });
// Subscribe to changes
todos.subscribe((change) => {
console.log('Collection changed:', change);
});For a durable offline browser database using the same API:
const db = createFeltDB({ namespace: 'my-app', browser: true });
const todos = db.collection<Todo>('todos');Browser mutations resolve after their IndexedDB transaction commits. The
durable change journal replays after reload and coordinates live collections
across tabs through BroadcastChannel when available.
Durable Operation Management
FeltDB provides atomic operation admission and lifecycle management for systems that need to survive process crashes with guaranteed identity stability.
Admit Operations (with atomic identity)
Guarantee: exactly-once operation identity across process crashes and concurrent callers.
import { OperationAdmissionInput, DurableOperation } from '@feltdb/core';
const db = createFeltDB({ namespace: 'my-app', path: './state' });
const result = await db.admitOperation({
idempotencyKey: 'payment-123',
kind: 'payment-processing',
metadata: { amount: 99.99, currency: 'USD' }
});
// Same idempotencyKey always returns same operationId
console.log(result.operationId); // 'op-xxx-yyy' (stable)
console.log(result.admitted); // true if this caller admitted it, false if already existedTransition Operations (atomic lifecycle)
Guarantee: all-or-nothing state transitions with version-based Compare-And-Set semantics.
import { OperationTransitionInput } from '@feltdb/core';
const transition = await db.transitionOperation({
operationId: 'op-xxx-yyy',
expectedVersion: 0,
to: 'executing',
metadata: { started_at: Date.now() }
});
if (transition.transitioned) {
// We won the transition race
console.log('Now executing...');
// Do work...
// Complete the operation
await db.transitionOperation({
operationId: 'op-xxx-yyy',
expectedVersion: 1,
to: 'completed',
resultSnapshot: { paymentId: 'pay-456', timestamp: Date.now() }
});
} else if (transition.reason === 'VERSION_CONFLICT') {
// Another process already transitioned this operation
console.log('Conflict - another process is handling this');
console.log('Current status:', transition.operation.status);
}Operation lifecycle: accepted → executing → (completed | failed | cancelled)
Terminal states (completed, failed, cancelled) cannot be transitioned from.
Recover Revisions (audited recovery from corruption)
Guarantee: audit trail with permanent untrust markers, no silent rollback.
import { StateContractClient, RevisionRecoveryInput } from '@feltdb/core';
const client = new StateContractClient({
applicationId: 'my-app',
revisionId: 'rev-clean-122',
environment: 'staging'
});
const recovery = await client.recoverApplicationRevision({
applicationId: 'my-app',
expectedCurrentRevision: 'rev-corrupted-123',
targetRevision: 'rev-clean-122',
authorization: 'ELEVATED',
actor: 'sherpa-admission',
reason: 'Replace corrupt historical revision',
environment: 'staging',
recoveryId: 'sherpa-staging-recovery-v1'
});
// Result includes:
// - pointerMoved: Environment pointer moved to valid revision
// - sourceMarkedUntrusted: Corrupt revision permanently marked untrusted
// - auditDurable: Immutable audit trail persisted
console.log(recovery.pointerMoved); // true
console.log(recovery.sourceMarkedUntrusted); // true
console.log(recovery.auditDurable); // trueOperation Types
import {
OperationAdmissionInput,
OperationAdmissionResult,
DurableOperation,
OperationStatus,
OperationTransitionInput,
OperationTransitionResult,
} from '@feltdb/core';Error Semantics
All FeltDB APIs return deterministic, semantic error codes (never empty {}).
Error Codes
import { FeltDBErrorCode } from '@feltdb/core';
try {
await db.transitionOperation({ ... });
} catch (error) {
const felt_error = error.feltdb_error;
switch (felt_error.code) {
case FeltDBErrorCode.CONFLICT:
// Version mismatch; another process won the race
// → Retry with exponential backoff
console.log('Conflict; retrying...');
break;
case FeltDBErrorCode.PRECONDITION_FAILED:
// Validation or precondition error
// → Do not retry; fix the input
console.log('Validation error:', felt_error.message);
break;
case FeltDBErrorCode.TOO_BUSY:
// Queue depth exceeded
// → Retry with exponential backoff
console.log('Server busy; retrying...');
break;
case FeltDBErrorCode.INTERNAL_ERROR:
// Server error
// → Log and escalate; audit trail in request_id
console.log('Server error:', felt_error.request_id);
break;
}
}Error Response Structure
import { FeltDBErrorResponse } from '@feltdb/core';
interface FeltDBErrorResponse {
code: FeltDBErrorCode | string; // Semantic code (CONFLICT, PRECONDITION_FAILED, etc.)
message: string; // Human-readable message
request_id: string; // Unique ID for debugging
transaction_id?: string; // If applicable
http_status: number; // HTTP status for routing
recovery_hint?: 'retry_backoff' | 'dont_retry' | 'check_queue_depth' | 'contact_support';
}Error Handling Utilities
import { isRetryableError, getRetryStrategy } from '@feltdb/core';
// Check if error should be retried
if (isRetryableError(error.feltdb_error.code)) {
const strategy = getRetryStrategy(error.feltdb_error.code);
if (strategy === 'exponential_backoff') {
// Wait with exponential backoff before retry
}
}Concurrency Model
FeltDB 0.4.3 uses a single-writer, multi-reader model:
- Single concurrent writer: Only one process may call
database.mutate()at a time - Multiple readers: Any number of processes may call read operations (
collection.all(), etc.) - Enforcement: File lock acquired at database open, held for lifetime
- Multi-writer error: Attempting to open database from second process returns clear error: "Database is locked by another process. Only one process may write to a FeltDB database at a time."
Not suitable for: Multi-process concurrent writes. See Phase 3 roadmap for multi-writer replication model.
API
createFeltDB(options)
Initialize a new FeltDB instance.
Options:
namespace(string) - Application namespace for data isolationserver- Durable authenticated FeltDB server (urlandtoken)memory: true- Explicit ephemeral development/test runtime; never use for customer databrowser: true- Durable IndexedDB runtime with restart-safe change replay
Returns: Database instance
db.collection(name)
Get or create a collection.
Parameters:
name(string) - Collection name
Returns: Collection instance
collection.insert(item)
Insert a new item into the collection.
Parameters:
item(object) - Item to insert
Returns: Promise - Item ID
collection.find(query)
Query items from the collection.
Parameters:
query(object) - Query filter
Returns: Promise - Matching items
collection.update(id, updates)
Update an item in the collection.
Parameters:
id(string) - Item IDupdates(object) - Fields to update
Returns: Promise
collection.delete(id)
Remove an item from the collection.
Parameters:
id(string) - Item ID
Returns: Promise
collection.subscribe(callback)
Subscribe to collection changes.
Parameters:
callback(function) - Called when collection changes
Returns: Function - Unsubscribe function
Network acquisition and state-first execution
const task = await db.acquire<Task>('tasks', 'task-42');
const matches = await db.search<Task>('tasks', 'shipping blocker');
const artifact = await db.storeContent(new TextEncoder().encode('release artifact'));
const verifiedBytes = await db.acquireContent(artifact.hash);
await db.defineCapability('open-tasks', [
{ op: 'search', collection: 'tasks', query: '' },
{ op: 'filter_eq', field: 'done', value: false },
{ op: 'limit', count: 100 },
]);
await db.defineWorkflow('release', ['verify', 'publish']);
const run = await db.startWorkflow('release', { version: '1.0.0' });
const claimed = await db.claimWorkflowStep(run.value.id, 'verify', 'worker-1');
await db.completeWorkflowStep(
run.value.id, 'verify', claimed.value.steps[0].claim_id, { ok: true },
);
await db.defineStateAgent('triage', ['search']);
const agentRun = await db.startStateAgent('triage', 'resolve customer blocker');Acquisition synchronizes canonical causal operations from configured peers. Workflow and agent lifecycle records are canonical collections, so durability, live events, replication, authorization, and audit apply automatically. Execution claims use durable majority leases when peers are configured; stale or minority-partition workers cannot publish completion.
Embedded replica convergence and capability failover
const command = createFeltDB({ namespace: 'command', browser: true });
const field = createFeltDB({ namespace: 'field', browser: true });
// Both replicas continue accepting durable state while disconnected.
await command.collection('resources').insert(resource, 'water-team');
await field.collection('incidents').insert(incident, 'clinic');
// Transport-independent, bidirectional operation exchange. Replays deduplicate.
await command.synchronizeWith(field);
field.registerCapabilityWorker('AssessIncident', assessIncident);
const routed = await command.executeCapabilityWithFailover(
'AssessIncident', { incident }, [field],
);exportOperations() and applyOperations() are also available when the
application supplies its own transport. Merges use stable operation identity
and deterministic last-writer ordering, notify live collections, and retain
the imported operations in the local audit journal. Capability routing records
every provider attempt in _flow_capability_routes; each successful worker
execution is materialized in _flow_executions.
License
MIT
