taladb 0.9.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -96,6 +96,57 @@ type Update<T extends Document = Document> = {
96
96
  interface Schema<T> {
97
97
  parse(data: unknown): T;
98
98
  }
99
+ /** Primitive field type for a {@link SyncSchema}. `'any'` requires presence
100
+ * without constraining the value's type. */
101
+ type SyncFieldType = 'bool' | 'int' | 'float' | 'str' | 'bytes' | 'array' | 'object' | 'any';
102
+ /**
103
+ * A tolerant, structural schema applied to documents **arriving via sync**
104
+ * (`db.sync()` pull). Distinct from {@link Schema} (Zod/Valibot), which is
105
+ * strict and runs on the *local* `insert` path: sync import is the boundary you
106
+ * don't control, so it validates structurally and never hard-rejects.
107
+ *
108
+ * On import, per document:
109
+ * - `_v` **below** `version` → upgraded in place (missing `defaults` filled,
110
+ * `_v` stamped) — additive-only migration.
111
+ * - `_v` **above** `version` → accepted untouched (the peer is ahead).
112
+ * - a missing/`null` `required` field or a `types` mismatch → **quarantined**
113
+ * (set aside, recoverable via {@link TalaDB.quarantined}), never dropped and
114
+ * never aborting the batch.
115
+ *
116
+ * @example
117
+ * const users = db.collection<User>('users', {
118
+ * schema: User, // strict, on insert
119
+ * syncSchema: { // tolerant, on import
120
+ * version: 1,
121
+ * required: ['name'],
122
+ * types: { name: 'str', age: 'int' },
123
+ * defaults: { age: 0 },
124
+ * },
125
+ * });
126
+ */
127
+ interface SyncSchema {
128
+ /**
129
+ * Current document shape version. Omit or `0` to disable the migration step
130
+ * entirely — in which case {@link renames} and {@link defaults} never run, so
131
+ * declaring either without a `version` is rejected at `db.collection()` rather
132
+ * than silently quarantining the documents they were meant to upgrade.
133
+ */
134
+ version?: number;
135
+ /** Fields that must be present and non-null, or the document is quarantined. */
136
+ required?: string[];
137
+ /** Expected primitive type per field. Fields absent here accept any type. */
138
+ types?: Record<string, SyncFieldType>;
139
+ /** Values applied to missing fields when upgrading a below-`version` document. */
140
+ defaults?: Record<string, Value>;
141
+ /**
142
+ * Field renames applied when upgrading a below-`version` document, as
143
+ * `{ oldName: newName }`. If the old field is present and the new one absent,
144
+ * the value moves. Applied before {@link defaults}. Structural (runs in the
145
+ * engine at import) — for renames that need computation, use
146
+ * {@link CollectionOptions.migrateDocument}.
147
+ */
148
+ renames?: Record<string, string>;
149
+ }
99
150
  /** Options passed to `db.collection()`. */
100
151
  interface CollectionOptions<T extends Document = Document> {
101
152
  /**
@@ -113,6 +164,53 @@ interface CollectionOptions<T extends Document = Document> {
113
164
  * Defaults to `false`.
114
165
  */
115
166
  validateOnRead?: boolean;
167
+ /**
168
+ * Tolerant structural schema applied to documents arriving via `db.sync()`.
169
+ * See {@link SyncSchema}. Enables validate-on-import ("validate, never cast")
170
+ * in the core sync path, with `_v` migration and quarantine of bad shapes.
171
+ * Wired on browser (OPFS worker), Node.js, and React Native; a binding whose
172
+ * native module predates 0.9.2 falls back to unvalidated import.
173
+ *
174
+ * Declaring a `version` also makes locally-inserted documents carry that `_v`,
175
+ * so they are never mistaken for legacy documents on read.
176
+ */
177
+ syncSchema?: SyncSchema;
178
+ /**
179
+ * Lazy, read-time document migration — the arbitrary-JS complement to the
180
+ * structural {@link SyncSchema}. When set, every document returned by `find`
181
+ * / `findOne` whose `_v` is **below** `syncSchema.version` is passed through
182
+ * `migrateDocument(doc, fromVersion)` and stamped to the current version
183
+ * before you see it, so application code always reads the current shape even
184
+ * for documents that predate the schema (renames, computed/derived fields,
185
+ * splits/merges). Runs on **every runtime** (it's a pure read transform in
186
+ * the client — no binding support needed).
187
+ *
188
+ * Requires `syncSchema.version` (the migration target). The transform is
189
+ * applied to the returned value only; it is not persisted back to storage —
190
+ * pair with `openDB({ migrations })` or a `syncSchema` rename to rewrite
191
+ * stored documents eagerly. Must be pure and deterministic.
192
+ *
193
+ * @example
194
+ * const users = db.collection<User>('users', {
195
+ * syncSchema: { version: 2 },
196
+ * migrateDocument: (doc, from) =>
197
+ * from < 2 ? { ...doc, fullName: `${doc.first} ${doc.last}` } : doc,
198
+ * });
199
+ */
200
+ migrateDocument?: (doc: T, fromVersion: number) => T;
201
+ /**
202
+ * When `true`, a document upgraded by {@link migrateDocument} on read is
203
+ * **written back** to storage (a best-effort `updateOne` computing the
204
+ * `$set`/`$unset` diff) so the migration becomes permanent — after which
205
+ * filters and indexes on the new shape match it. Default `false` (the
206
+ * migrated shape is returned but not persisted).
207
+ *
208
+ * Trade-offs: reads that encounter un-migrated documents now issue writes
209
+ * (which fire live-query and sync-hook notifications like any other write);
210
+ * a failed write is swallowed and simply retried on the next read. For a
211
+ * one-shot eager rewrite instead, prefer `openDB({ migrations })`.
212
+ */
213
+ persistMigrations?: boolean;
116
214
  }
117
215
  /** A single MongoDB-style aggregation stage. */
118
216
  type AggregateStage<T extends Document = Document> = {
@@ -296,9 +394,30 @@ interface SyncResult {
296
394
  pushed: number;
297
395
  /** Number of documents changed locally by the pulled remote changeset. */
298
396
  pulled: number;
397
+ /**
398
+ * Documents in the pulled changeset skipped by an import validator (a
399
+ * collection this client does not model). Always `0` when no `syncSchema`
400
+ * applied to the pass.
401
+ */
402
+ skipped?: number;
403
+ /**
404
+ * Documents in the pulled changeset set aside by an import validator because
405
+ * they failed structural validation. Recoverable via {@link TalaDB.quarantined}.
406
+ * Always `0` when no `syncSchema` applied to the pass.
407
+ */
408
+ quarantined?: number;
299
409
  /** Active sync cursor. Currently `0` because timestamp adapters replay safely. */
300
410
  cursor: number;
301
411
  }
412
+ /** A document set aside during a validated sync import, with its rejection reason. */
413
+ interface QuarantinedDocument<T extends Document = Document> {
414
+ /** The rejected document, retained verbatim. */
415
+ document: T;
416
+ /** Human-readable reason the document was quarantined. */
417
+ reason: string;
418
+ /** The `changed_at` (ms epoch) the rejected change carried. */
419
+ changedAt: number;
420
+ }
302
421
  interface TalaDB {
303
422
  collection<T extends Document = Document>(name: string, options?: CollectionOptions<T>): Collection<T>;
304
423
  /**
@@ -334,6 +453,20 @@ interface TalaDB {
334
453
  * await db.compact();
335
454
  */
336
455
  compact(): Promise<void>;
456
+ /**
457
+ * Return the documents set aside in `collection`'s quarantine table by a
458
+ * validated sync import (see {@link SyncSchema}). Empty when nothing was
459
+ * quarantined. Wired on browser and Node.js; resolves to `[]` on runtimes
460
+ * without support.
461
+ */
462
+ quarantined?<T extends Document = Document>(collection: string): Promise<QuarantinedDocument<T>[]>;
463
+ /**
464
+ * Force any batched (eventual-durability) writes to durable storage, and on
465
+ * the browser also write the IndexedDB fallback snapshot immediately. A
466
+ * no-op under the default `flush_every_write: true` durability. Use for
467
+ * "save now" moments (before checkout, on `visibilitychange`).
468
+ */
469
+ flush?(): Promise<void>;
337
470
  /** Browser HTTP-push queue health, when supported by the active binding. */
338
471
  syncStatus?(): Promise<{
339
472
  pending: number;
@@ -377,10 +510,28 @@ interface SyncConfig {
377
510
  */
378
511
  exclude_fields?: string[];
379
512
  }
513
+ /** Storage durability settings. */
514
+ interface DurabilityConfig {
515
+ /**
516
+ * When `true` (default), every write commit is fsync'd immediately — a crash
517
+ * never loses an acknowledged write. When `false`, commits are batched for
518
+ * higher write throughput; call `db.flush()` to force a durable sync. Applies
519
+ * to Node (file) and browser OPFS storage; in-memory ignores it.
520
+ */
521
+ flush_every_write?: boolean;
522
+ /**
523
+ * Browser IndexedDB-fallback snapshot debounce, in milliseconds (default
524
+ * 500). Only affects the non-OPFS browser fallback path — the OPFS and Node
525
+ * paths use `flush_every_write`.
526
+ */
527
+ flush_ms?: number;
528
+ }
380
529
  /** Top-level TalaDB configuration. */
381
530
  interface TalaDbConfig {
382
531
  /** HTTP push sync configuration. Disabled by default. */
383
532
  sync?: SyncConfig;
533
+ /** Storage durability configuration. */
534
+ durability?: DurabilityConfig;
384
535
  }
385
536
 
386
537
  interface HttpSyncAdapterOptions {
@@ -430,11 +581,70 @@ declare class TalaDbValidationError extends Error {
430
581
  readonly cause: unknown;
431
582
  constructor(cause: unknown, context?: string);
432
583
  }
584
+ /**
585
+ * Wraps a `Collection<T>` to intercept writes through a schema validator
586
+ * (`schema`), stamp `_v` on insert when a `syncSchema.version` is declared, and
587
+ * normalize reads through a lazy `migrateDocument`. Returns the collection
588
+ * unchanged when none of those apply.
589
+ *
590
+ * Read normalization covers `find`, `findOne`, and `subscribe` (live queries).
591
+ * It cannot cover `aggregate`, `findNearest`, or FTS `search`: those run inside
592
+ * the engine against the *stored* shape, so a below-version document is matched
593
+ * and projected as it sits on disk. Use `persistMigrations` or
594
+ * `openDB({ migrations })` to rewrite storage if you query old documents that way.
595
+ *
596
+ * @internal Exported for unit testing; not part of the public API surface.
597
+ */
598
+ declare function applySchema<T extends Document>(col: Collection<T>, options: CollectionOptions<T>): Collection<T>;
433
599
 
600
+ /**
601
+ * A single application schema migration, run once at `openDB` when its
602
+ * `version` is greater than the database's stored migration version.
603
+ */
604
+ interface Migration {
605
+ /** Monotonic version. Must be a positive integer, unique across the array. */
606
+ version: number;
607
+ /** Optional human-readable label for logs. */
608
+ description?: string;
609
+ /**
610
+ * The migration body. Receives the open database and may use the full
611
+ * collection API. Runs to completion before the version is advanced.
612
+ *
613
+ * **Write migrations idempotently.** TalaDB checkpoints per version (the
614
+ * stored version advances only after `up` fully resolves), but a single `up`
615
+ * is not wrapped in one atomic transaction — if it throws partway, the writes
616
+ * it already made persist and `up` re-runs from the start on the next open.
617
+ */
618
+ up: (db: TalaDB) => Promise<void> | void;
619
+ }
620
+ /**
621
+ * Runtime-agnostic migration runner. Each binding supplies `getVersion` /
622
+ * `setVersion` (its own persisted counter); the loop is identical everywhere.
623
+ *
624
+ * Runs pending migrations (`version` > stored) in ascending order, advancing
625
+ * the stored version after each `up` resolves — checkpoint per version. If an
626
+ * `up` throws, the loop stops and the error propagates; the stored version
627
+ * reflects the last fully-applied migration, so the next open resumes there.
628
+ *
629
+ * @internal Exported for unit testing; not part of the public API surface.
630
+ */
631
+ declare function runMigrations(db: TalaDB, getVersion: () => Promise<number>, setVersion: (v: number) => Promise<void>, migrations: Migration[]): Promise<void>;
434
632
  /** Options for `openDB`. */
435
633
  interface OpenDBOptions {
436
634
  /** Encrypt native database values at rest. Never hard-code this value. */
437
635
  passphrase?: string;
636
+ /**
637
+ * Ordered application schema migrations, run once each at open in ascending
638
+ * `version` order (only those newer than the stored migration version). The
639
+ * stored version advances after each migration succeeds — checkpoint per
640
+ * version, resuming from the last applied one on the next open.
641
+ *
642
+ * Supported on Node.js, the browser (via the OPFS worker), and React Native.
643
+ * On a binding whose native module predates 0.9.2 (no `userVersion` /
644
+ * `setUserVersion`), `openDB` throws rather than silently skipping the
645
+ * migrations — rebuild or update the native module.
646
+ */
647
+ migrations?: Migration[];
438
648
  /**
439
649
  * Explicit path to a `taladb.config.yml` / `taladb.config.json` file.
440
650
  * If omitted, TalaDB auto-discovers the file from `process.cwd()` on Node.js.
@@ -448,6 +658,14 @@ interface OpenDBOptions {
448
658
  * Useful for passing config programmatically without a config file on disk.
449
659
  */
450
660
  config?: TalaDbConfig;
661
+ /**
662
+ * Storage durability, e.g. `{ flush_every_write: false }` to batch commits
663
+ * for write throughput (call `db.flush()` to force a sync), or `{ flush_ms }`
664
+ * to tune the browser IndexedDB-fallback snapshot debounce. Merged into
665
+ * `config.durability`. Node + browser; on React Native pass it in the config
666
+ * JSON to `TalaDBModule.initialize`.
667
+ */
668
+ durability?: DurabilityConfig;
451
669
  }
452
670
  /**
453
671
  * Open a TalaDB database.
@@ -466,4 +684,4 @@ interface OpenDBOptions {
466
684
  */
467
685
  declare function openDB(dbName?: string, options?: OpenDBOptions): Promise<TalaDB>;
468
686
 
469
- export { type AggregatePipeline, type AggregateStage, type Collection, type CollectionIndexInfo, type CollectionOptions, type Document, type Filter, HttpSyncAdapter, type OpenDBOptions, type Schema, type SerializedChangeset, type SyncAdapter, type SyncConfig, type SyncDirection, type SyncOptions, type SyncResult, type TalaDB, type TalaDbConfig, TalaDbValidationError, type Update, type Value, type VectorIndexOptions, type VectorMetric, type VectorSearchResult, openDB };
687
+ export { type AggregatePipeline, type AggregateStage, type Collection, type CollectionIndexInfo, type CollectionOptions, type Document, type DurabilityConfig, type Filter, HttpSyncAdapter, type Migration, type OpenDBOptions, type Schema, type SerializedChangeset, type SyncAdapter, type SyncConfig, type SyncDirection, type SyncOptions, type SyncResult, type TalaDB, type TalaDbConfig, TalaDbValidationError, type Update, type Value, type VectorIndexOptions, type VectorMetric, type VectorSearchResult, applySchema, openDB, runMigrations };