/** * Store router / multiplexer. * * Dispatches `NoydbStore` operations to different backends based on * collection type, record size, record age, collection name, or vault name. * * ```ts * const db = await createNoydb({ * store: routeStore({ * default: dynamo({ table: 'myapp' }), * blobs: s3Store({ bucket: 'myapp-blobs' }), * }), * }) * ``` * * @module */ import type { NoydbStore } from '../kernel/types.js'; /** * Size-tiered blob routing configuration. * * Routes blob chunks to different stores based on byte size. Small blobs * (under `threshold`) stay in the primary or `small` store; large blobs * go to `large`. This lets you keep DynamoDB as the default while sending * large binary objects to S3. */ export interface BlobStoreRoute { /** Store for small blobs (under threshold). Falls back to `default`. */ readonly small?: NoydbStore; /** Store for large blobs (over threshold). */ readonly large: NoydbStore; /** Size threshold in bytes. Default: `400 * 1024` (DynamoDB item limit). */ readonly threshold?: number; } /** * Blob lifecycle management policies evaluated during `compact()`. * * Controls orphan cleanup, cold-tier archival, and hard deletion of * blobs that are no longer referenced by any record. */ export interface BlobLifecyclePolicy { /** Delete orphan blobs (refCount: 0) after this many days. Default: 7. */ readonly orphanRetentionDays?: number; /** Move blobs not accessed in this many days to the cold blob store. */ readonly archiveAfterDays?: number; /** Store for archived blobs. Required if archiveAfterDays is set. */ readonly archiveStore?: NoydbStore; /** Hard-delete archived blobs after this many days. */ readonly expireAfterDays?: number; } /** * Age-based hot/cold tiering configuration. * * Records whose `_ts` timestamp is older than `coldAfterDays` are migrated * to the `cold` store during `compact()`. Reads transparently fall through * to the cold store when the hot store returns null, so callers don't need * to know which tier a record lives in. */ export interface AgeRoute { /** Store for records older than the cutoff. */ readonly cold: NoydbStore; /** * Days after last modification before a record is cold-eligible for the * ROLLING `compact(vault)` migrator. Omit for period-driven archival only * (`compact(vault, { before })`), where the cutoff is supplied per call. */ readonly coldAfterDays?: number; /** * Collections that participate in age tiering. * Empty array or omitted = all user collections (excluding `_` prefixed). */ readonly collections?: string[]; } /** * Options for `routeStore()` — the store multiplexer. * * At minimum, provide a `default` store. All other fields are optional * extensions for specific routing scenarios (blobs → S3, geographic sharding, * age-based tiering, etc.). */ export interface RouteStoreOptions { /** Default store for all unmatched operations. */ readonly default: NoydbStore; /** * Route blob chunk data to a separate store. * - Pass a `NoydbStore` for simple prefix routing (all chunks → that store). * - Pass `{ small?, large, threshold? }` for size-tiered routing. */ readonly blobs?: NoydbStore | BlobStoreRoute; /** Route all blob metadata (index, slots, versions) to the blobs store too. Default: false. */ readonly routeBlobMeta?: boolean; /** Route specific user collections to dedicated stores. */ readonly routes?: Record; /** Route by vault name (prefix patterns, e.g. `'EU-'`). */ readonly vaultRoutes?: Record; /** * Age-based tiering: records older than `coldAfterDays` are read from * the cold store. A background `compact()` method migrates them. */ readonly age?: AgeRoute; /** * Content-aware blob routing. * Route blob chunks by MIME type glob pattern. The MIME type is stored * in `BlobObject` and matched at read time via `storeHint`. */ readonly blobRoutes?: Record; /** * Blob lifecycle policies. * Evaluated during `compact()`. */ readonly blobLifecycle?: BlobLifecyclePolicy; /** * Quota-aware overflow. * When the default store's usage exceeds the threshold, new writes * overflow to the specified store. */ readonly overflow?: NoydbStore; /** * Quota threshold (0-1). Default: 0.8 (overflow at 80% usage). * Only effective when `overflow` is set. */ readonly quotaThreshold?: number; } /** * Named route that can be overridden or suspended at runtime. * * Built-in names: `'default'`, `'blobs'`, `'cold'`. * Custom names: any collection name from `routes`, any vault prefix from * `vaultRoutes`, or any sync target label. */ export type OverrideTarget = 'default' | 'blobs' | 'cold' | (string & {}); /** * Options for `RoutedNoydbStore.override()`. * * Controls whether the new store is pre-populated with data from the * original store before the switch takes effect. */ export interface OverrideOptions { /** * Hydrate the override store from the original before activating. * - `true` — copy all data for all vaults. * - `string[]` — copy only named collections. * Makes `override()` async — returns a Promise. */ hydrate?: boolean | string[]; } /** * Options for `RoutedNoydbStore.suspend()`. * * A suspended route becomes a null store: reads return null/[], writes * are dropped (or buffered if `queue: true`). Useful for maintenance * windows or restricted-network scenarios. */ export interface SuspendOptions { /** * Buffer write operations during suspension. On `resume()`, queued * writes are replayed against the restored store. */ queue?: boolean; /** * Maximum queued operations. When exceeded, oldest entries are dropped. * Default: 10_000. */ maxQueueSize?: number; } /** * Snapshot of the current override and suspend state of a `RoutedNoydbStore`. * Returned by `routeStatus()` for diagnostics and health dashboards. */ export interface RouteStatus { /** Active overrides: route name → override store name. */ readonly overrides: Record; /** Currently suspended routes. */ readonly suspended: string[]; /** Queued writes per suspended route (only for routes suspended with `queue: true`). */ readonly queued: Record; } /** * Extended `NoydbStore` returned by `routeStore()`. * * Satisfies the full `NoydbStore` contract plus adds runtime control * methods for overriding, suspending, and inspecting routes. */ export interface RoutedNoydbStore extends NoydbStore { /** * Migrate records to the cold store. Only applies when `age.cold` is * configured. With `{ before }`, migrates records whose `_ts < before` * (period-driven archival); without, uses the rolling `coldAfterDays`. * Returns the number of records migrated. */ compact(vault: string, opts?: { before?: string; }): Promise; /** * Override a named route at runtime. * * The override persists until `clearOverride()` is called or the * instance is closed. In-flight operations complete on the original * store; new operations use the override. * * Options: * - `hydrate: true` — async: copies all data from the original store * into the override before activating the switch. * - `hydrate: ['invoices', 'clients']` — copies only named collections. * * Use cases: * - Shared device: `await store.override('default', memory(), { hydrate: true })` * - Restricted network: `store.override('blobs', localFile(...))` */ override(route: OverrideTarget, store: NoydbStore, opts?: OverrideOptions): void | Promise; /** Clear a runtime override, reverting to the original store. */ clearOverride(route: OverrideTarget): void; /** * Suspend a route entirely. Operations to suspended stores become * no-ops (puts silently dropped, gets return null, lists return []). * * Options: * - `queue: true` — buffer write operations (put/delete) during * suspension. When `resume()` is called, queued writes are replayed * against the restored store. * * Returns a `SuspendHandle` when `queue: true`, for inspecting queue state. */ suspend(route: OverrideTarget, opts?: SuspendOptions): void; /** * Resume a previously suspended route. * If the route was suspended with `queue: true`, replays queued writes. * Returns the number of replayed operations. */ resume(route: OverrideTarget): Promise; /** Snapshot the current override/suspend state for diagnostics. */ routeStatus(): RouteStatus; /** * Resolve the physical backend a vault id maps to via the geographic * `vaultRoutes` prefix routing (collection-independent), falling back to * the `default` store. Used by the federation data-residency guard * to read the placement backend's `capabilities.region`. */ resolveBackend(vaultId: string): NoydbStore; } /** * Create a store multiplexer that dispatches operations to different backends * based on collection type, record size, record age, vault prefix, or * runtime overrides. * * ```ts * const store = routeStore({ * default: dynamo({ table: 'myapp' }), * blobs: s3({ bucket: 'myapp-blobs' }), * routes: { auditLog: s3({ bucket: 'myapp-audit' }) }, * }) * ``` * * The returned store satisfies `NoydbStore` and can be passed directly to * `createNoydb({ store })`. It also exposes additional methods * (`override`, `suspend`, `resume`, `routeStatus`, `compact`) for runtime * control and maintenance. */ export declare function routeStore(opts: RouteStoreOptions): RoutedNoydbStore;