# @feltdb/core

The main FeltDB API. This package provides a simple, state-first interface for working with FeltDB.

## Installation

```bash
npm install @feltdb/core
```

This 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`.

```bash
npx --package @feltdb/core create-feltdb my-app
npx --package @feltdb/core feltdb studio
```

React bindings and migration tooling ship as subpath exports of this package:

```typescript
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.

## Canonical managed Service API

Use one scoped client for managed application discovery, authorized data
inspection, and the Proposal lifecycle:

```typescript
import { createClient } from '@feltdb/core';

const feltdb = createClient({
  url: process.env.FELTDB_URL!,
  token: process.env.FELTDB_TOKEN!,
  applicationId: process.env.FELTDB_APPLICATION_ID!,
  environment: 'production',
});

const application = await feltdb.application.get();
const users = await feltdb.data.list('User', { limit: 50 });
const proposals = await feltdb.proposals.list({ status: 'previewed' });
```

All these operations use the application-facing `/v1` Service API and retain
the same application/environment scope. Proposal approval is available here;
repository application intentionally is not. Only `feltdb ai apply <id>` owns
repository mutation.

## Quick Start

```typescript
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:

```typescript
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.

## Atomic conditional transactions

Core 0.7.1 can fence a multi-record transaction on the exact version read by
the caller. The transaction either commits every operation or writes nothing:

```typescript
import { ConditionalConflictError } from '@feltdb/core';

const current = await db.collection('accounts').get('primary');

try {
  await db.transaction({
    transactionId: 'transfer-42',
    preconditions: [
      { collection: 'accounts', id: 'primary', ifVersion: current.__version },
    ],
    operations: [
      { collection: 'accounts', id: 'primary', value: { balance: 90 } },
      { collection: 'ledger', id: 'transfer-42', requireAbsent: true, value: { amount: 10 } },
    ],
  });
} catch (error) {
  if (error instanceof ConditionalConflictError) {
    console.log(error.conflict);
  }
}
```

Reusing a successful `transactionId` safely replays its result without
advancing record versions twice. This API requires a FeltDB authority that
supports transaction-level `ifVersion` preconditions.

A write with `requireAbsent: true` is an atomic create. The authority assigns
it `__version: 1`—overriding any caller-supplied version—so it can immediately
be updated with `ifVersion: 1`.

## Bounded queries

All runtimes can filter, order, and limit records at their storage boundary:

```typescript
const page = await db.query({
  collection: 'outbox',
  where: [
    { field: 'status', eq: 'pending' },
    { field: 'nextAttemptAt', lte: Date.now() },
  ],
  orderBy: [{ field: 'nextAttemptAt', direction: 'asc' }],
  limit: 100,
  cursor,
});
```

FeltDB adds record identity as a deterministic final tie-breaker and returns an
opaque `nextCursor`. Managed authorities pin page snapshots across concurrent
writes; embedded runtimes use a query-bound keyset cursor over unchanged
committed state. Declare compound embedded indexes with `collection.createIndex`
and inspect selection with `db.explain(query)`. `Collection.find()` retains its
array result and accepts optional ordering and limit options.

## 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.

```typescript
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 existed
```

### Transition Operations (atomic lifecycle)

Guarantee: **all-or-nothing state transitions** with version-based Compare-And-Set semantics.

```typescript
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.

```typescript
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);        // true
```

### Operation Types

```typescript
import {
  OperationAdmissionInput,
  OperationAdmissionResult,
  DurableOperation,
  OperationStatus,
  OperationTransitionInput,
  OperationTransitionResult,
} from '@feltdb/core';
```

## Error Semantics

All FeltDB APIs return **deterministic, semantic error codes** (never empty `{}`).

### Error Codes

```typescript
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

```typescript
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

```typescript
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 isolation
- `server` - Durable authenticated FeltDB server (`url` and `token`)
- `memory: true` - Explicit ephemeral development/test runtime; never use for customer data
- `browser: 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<string> - Item ID

### collection.find(query)

Query items from the collection.

**Parameters:**
- `query` (object) - Query filter

**Returns:** Promise<Array> - Matching items

### collection.update(id, updates)

Update an item in the collection.

**Parameters:**
- `id` (string) - Item ID
- `updates` (object) - Fields to update

**Returns:** Promise<void>

### collection.delete(id)

Remove an item from the collection.

**Parameters:**
- `id` (string) - Item ID

**Returns:** Promise<void>

### collection.subscribe(callback)

Subscribe to collection changes.

**Parameters:**
- `callback` (function) - Called when collection changes

**Returns:** Function - Unsubscribe function

### Network acquisition and state-first execution

```typescript
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

```typescript
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
