@trestleinc/replicate 0.1.0 → 1.1.0

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.
Files changed (94) hide show
  1. package/README.md +356 -420
  2. package/dist/client/collection.d.ts +78 -76
  3. package/dist/client/errors.d.ts +59 -0
  4. package/dist/client/index.d.ts +22 -18
  5. package/dist/client/logger.d.ts +0 -1
  6. package/dist/client/merge.d.ts +77 -0
  7. package/dist/client/persistence/adapters/index.d.ts +8 -0
  8. package/dist/client/persistence/adapters/opsqlite.d.ts +46 -0
  9. package/dist/client/persistence/adapters/sqljs.d.ts +83 -0
  10. package/dist/client/persistence/index.d.ts +49 -0
  11. package/dist/client/persistence/indexeddb.d.ts +17 -0
  12. package/dist/client/persistence/memory.d.ts +16 -0
  13. package/dist/client/persistence/sqlite-browser.d.ts +51 -0
  14. package/dist/client/persistence/sqlite-level.d.ts +63 -0
  15. package/dist/client/persistence/sqlite-rn.d.ts +36 -0
  16. package/dist/client/persistence/sqlite.d.ts +47 -0
  17. package/dist/client/persistence/types.d.ts +42 -0
  18. package/dist/client/prose.d.ts +56 -0
  19. package/dist/client/replicate.d.ts +40 -0
  20. package/dist/client/services/checkpoint.d.ts +18 -0
  21. package/dist/client/services/reconciliation.d.ts +24 -0
  22. package/dist/component/_generated/api.d.ts +35 -0
  23. package/dist/component/_generated/api.js +3 -3
  24. package/dist/component/_generated/component.d.ts +89 -0
  25. package/dist/component/_generated/component.js +0 -0
  26. package/dist/component/_generated/dataModel.d.ts +45 -0
  27. package/dist/component/_generated/dataModel.js +0 -0
  28. package/{src → dist}/component/_generated/server.d.ts +9 -38
  29. package/dist/component/convex.config.d.ts +2 -2
  30. package/dist/component/convex.config.js +2 -1
  31. package/dist/component/logger.d.ts +8 -0
  32. package/dist/component/logger.js +30 -0
  33. package/dist/component/public.d.ts +36 -61
  34. package/dist/component/public.js +232 -58
  35. package/dist/component/schema.d.ts +32 -8
  36. package/dist/component/schema.js +19 -6
  37. package/dist/index.js +1553 -308
  38. package/dist/server/builder.d.ts +94 -0
  39. package/dist/server/index.d.ts +14 -17
  40. package/dist/server/schema.d.ts +17 -63
  41. package/dist/server/storage.d.ts +80 -0
  42. package/dist/server.js +268 -83
  43. package/dist/shared/index.d.ts +5 -0
  44. package/dist/shared/index.js +2 -0
  45. package/dist/shared/types.d.ts +50 -0
  46. package/dist/shared/types.js +6 -0
  47. package/dist/shared.js +6 -0
  48. package/package.json +59 -49
  49. package/src/client/collection.ts +877 -450
  50. package/src/client/errors.ts +45 -0
  51. package/src/client/index.ts +52 -26
  52. package/src/client/logger.ts +2 -28
  53. package/src/client/merge.ts +374 -0
  54. package/src/client/persistence/adapters/index.ts +8 -0
  55. package/src/client/persistence/adapters/opsqlite.ts +54 -0
  56. package/src/client/persistence/adapters/sqljs.ts +128 -0
  57. package/src/client/persistence/index.ts +54 -0
  58. package/src/client/persistence/indexeddb.ts +110 -0
  59. package/src/client/persistence/memory.ts +61 -0
  60. package/src/client/persistence/sqlite-browser.ts +107 -0
  61. package/src/client/persistence/sqlite-level.ts +407 -0
  62. package/src/client/persistence/sqlite-rn.ts +44 -0
  63. package/src/client/persistence/sqlite.ts +161 -0
  64. package/src/client/persistence/types.ts +49 -0
  65. package/src/client/prose.ts +369 -0
  66. package/src/client/replicate.ts +80 -0
  67. package/src/client/services/checkpoint.ts +86 -0
  68. package/src/client/services/reconciliation.ts +108 -0
  69. package/src/component/_generated/api.ts +52 -0
  70. package/src/component/_generated/component.ts +103 -0
  71. package/src/component/_generated/{dataModel.d.ts → dataModel.ts} +1 -1
  72. package/src/component/_generated/server.ts +161 -0
  73. package/src/component/convex.config.ts +3 -1
  74. package/src/component/logger.ts +36 -0
  75. package/src/component/public.ts +364 -111
  76. package/src/component/schema.ts +18 -5
  77. package/src/env.d.ts +31 -0
  78. package/src/server/builder.ts +85 -0
  79. package/src/server/index.ts +9 -24
  80. package/src/server/schema.ts +20 -76
  81. package/src/server/storage.ts +313 -0
  82. package/src/shared/index.ts +5 -0
  83. package/src/shared/types.ts +52 -0
  84. package/LICENSE.package +0 -201
  85. package/dist/client/storage.d.ts +0 -143
  86. package/dist/server/replication.d.ts +0 -122
  87. package/dist/server/ssr.d.ts +0 -79
  88. package/dist/ssr.js +0 -19
  89. package/src/client/storage.ts +0 -206
  90. package/src/component/_generated/api.d.ts +0 -95
  91. package/src/component/_generated/api.js +0 -23
  92. package/src/component/_generated/server.js +0 -90
  93. package/src/server/replication.ts +0 -244
  94. package/src/server/ssr.ts +0 -106
@@ -1,94 +1,96 @@
1
+ import * as Y from 'yjs';
2
+ import type { Persistence } from '$/client/persistence/types.js';
1
3
  import type { ConvexClient } from 'convex/browser';
2
4
  import type { FunctionReference } from 'convex/server';
3
5
  import type { CollectionConfig, Collection } from '@tanstack/db';
4
- /**
5
- * Configuration for convexCollectionOptions (Step 1)
6
- * All params go here - they'll be used to create the collection config
7
- */
6
+ import type { ProseFields } from '$/shared/types.js';
7
+ /** Server-rendered material data for SSR hydration */
8
+ export type Materialized<T> = {
9
+ documents: ReadonlyArray<T>;
10
+ checkpoint?: {
11
+ lastModified: number;
12
+ };
13
+ count?: number;
14
+ crdtBytes?: ArrayBuffer;
15
+ };
16
+ /** Configuration for creating a Convex-backed collection */
8
17
  export interface ConvexCollectionOptionsConfig<T extends object> {
9
- /** Function to extract unique key from items */
10
18
  getKey: (item: T) => string | number;
11
- /** Optional initial data to populate collection */
12
- initialData?: ReadonlyArray<T>;
13
- /** Convex client instance */
19
+ material?: Materialized<T>;
14
20
  convexClient: ConvexClient;
15
- /** Convex API functions for this collection */
16
21
  api: {
17
22
  stream: FunctionReference<'query'>;
18
- insertDocument: FunctionReference<'mutation'>;
19
- updateDocument: FunctionReference<'mutation'>;
20
- deleteDocument: FunctionReference<'mutation'>;
23
+ insert: FunctionReference<'mutation'>;
24
+ update: FunctionReference<'mutation'>;
25
+ remove: FunctionReference<'mutation'>;
26
+ recovery: FunctionReference<'query'>;
27
+ material?: FunctionReference<'query'>;
28
+ [key: string]: any;
21
29
  };
22
- /** Unique collection name */
23
- collectionName: string;
30
+ collection: string;
31
+ /** Fields that contain prose (rich text) content stored as Y.XmlFragment */
32
+ prose: Array<ProseFields<T>>;
33
+ /** Undo capture timeout in ms. Changes within this window merge into one undo. Default: 500 */
34
+ undoCaptureTimeout?: number;
35
+ /** Persistence provider for Y.Doc and key-value storage */
36
+ persistence: Persistence;
37
+ }
38
+ /** Editor binding for BlockNote/TipTap collaboration */
39
+ export interface EditorBinding {
40
+ /** The Y.XmlFragment bound to the editor */
41
+ readonly fragment: Y.XmlFragment;
42
+ /** Provider stub for BlockNote compatibility */
43
+ readonly provider: {
44
+ readonly awareness: null;
45
+ };
46
+ /** Current sync state - true if unsent changes exist */
47
+ readonly pending: boolean;
48
+ /** Subscribe to pending state changes. Returns unsubscribe function. */
49
+ onPendingChange(callback: (pending: boolean) => void): () => void;
50
+ /** Undo the last content edit */
51
+ undo(): void;
52
+ /** Redo the last undone edit */
53
+ redo(): void;
54
+ /** Check if undo is available */
55
+ canUndo(): boolean;
56
+ /** Check if redo is available */
57
+ canRedo(): boolean;
58
+ }
59
+ /** Utilities exposed on collection.utils */
60
+ interface ConvexCollectionUtils<T extends object> {
61
+ /**
62
+ * Get an editor binding for a prose field.
63
+ * Waits for Y.Doc to be ready (IndexedDB loaded) before returning.
64
+ * @param documentId - The document ID
65
+ * @param field - The prose field name (must be in `prose` config)
66
+ * @returns Promise resolving to EditorBinding
67
+ */
68
+ prose(documentId: string, field: ProseFields<T>): Promise<EditorBinding>;
69
+ }
70
+ /** Extended collection with prose field utilities */
71
+ export interface ConvexCollection<T extends object> extends Collection<T> {
72
+ /** Utilities for prose field operations */
73
+ utils: ConvexCollectionUtils<T>;
24
74
  }
25
75
  /**
26
- * ConvexCollection is now just a standard TanStack DB Collection!
27
- * No custom wrapper, no special methods - uses built-in transaction system.
28
- */
29
- export type ConvexCollection<T extends object> = Collection<T>;
30
- /**
31
- * Step 1: Create TanStack DB CollectionConfig with REAL mutation handlers.
32
- *
33
- * This implements the CORRECT pattern:
34
- * - Uses onInsert/onUpdate/onDelete handlers (not custom wrapper)
35
- * - Yjs Y.Doc with 'update' event for delta encoding
36
- * - Stores Y.Map instances (not plain objects) for field-level CRDT
37
- * - Uses ydoc.transact() to batch changes into single 'update' event
76
+ * Create TanStack DB collection options with Convex + Yjs replication.
38
77
  *
39
78
  * @example
40
79
  * ```typescript
41
- * import { createCollection } from '@tanstack/react-db'
42
- * import { convexCollectionOptions } from '@trestleinc/convex-replicate-core'
43
- *
44
- * const rawCollection = createCollection(
45
- * convexCollectionOptions<Task>({
46
- * convexClient,
47
- * api: api.tasks,
48
- * collectionName: 'tasks',
49
- * getKey: (task) => task.id,
50
- * initialData,
51
- * })
52
- * )
80
+ * const options = convexCollectionOptions<Task>({
81
+ * getKey: (t) => t.id,
82
+ * convexClient,
83
+ * api: { stream: api.tasks.stream, insert: api.tasks.insert, ... },
84
+ * collection: 'tasks',
85
+ * });
86
+ * const collection = createCollection(options);
53
87
  * ```
54
88
  */
55
- export declare function convexCollectionOptions<T extends object>({ getKey, initialData, convexClient, api, collectionName, }: ConvexCollectionOptionsConfig<T>): CollectionConfig<T> & {
89
+ export declare function convexCollectionOptions<T extends object>({ getKey, material, convexClient, api, collection, prose: proseFields, undoCaptureTimeout, persistence, }: ConvexCollectionOptionsConfig<T>): CollectionConfig<T> & {
56
90
  _convexClient: ConvexClient;
57
- _collectionName: string;
91
+ _collection: string;
92
+ _proseFields: Array<ProseFields<T>>;
93
+ _persistence: Persistence;
94
+ utils: ConvexCollectionUtils<T>;
58
95
  };
59
- /**
60
- * Step 2: Wrap collection with offline support.
61
- *
62
- * This implements the CORRECT pattern:
63
- * - Wraps collection ONCE with startOfflineExecutor
64
- * - Returns raw collection (NO CUSTOM WRAPPER)
65
- * - Uses beforeRetry filter for stale transactions
66
- * - Connects to Convex connection state for retry triggers
67
- *
68
- * Config is automatically extracted from the rawCollection!
69
- *
70
- * @example
71
- * ```typescript
72
- * import { createCollection } from '@tanstack/react-db'
73
- * import { convexCollectionOptions, createConvexCollection } from '@trestleinc/convex-replicate-core'
74
- *
75
- * // Step 1: Create raw collection with ALL config
76
- * const rawCollection = createCollection(
77
- * convexCollectionOptions<Task>({
78
- * convexClient,
79
- * api: api.tasks,
80
- * collectionName: 'tasks',
81
- * getKey: (task) => task.id,
82
- * initialData,
83
- * })
84
- * )
85
- *
86
- * // Step 2: Wrap with offline support - params automatically extracted!
87
- * const collection = createConvexCollection(rawCollection)
88
- *
89
- * // Use like a normal TanStack DB collection
90
- * const tx = collection.insert({ id: '1', text: 'Buy milk', isCompleted: false })
91
- * await tx.isPersisted.promise // Built-in promise (not custom awaitReplication)
92
- * ```
93
- */
94
- export declare function createConvexCollection<T extends object>(rawCollection: Collection<T>): ConvexCollection<T>;
96
+ export {};
@@ -0,0 +1,59 @@
1
+ declare const NetworkError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => import("effect/Cause").YieldableError & {
2
+ readonly _tag: "NetworkError";
3
+ } & Readonly<A>;
4
+ export declare class NetworkError extends NetworkError_base<{
5
+ readonly cause: unknown;
6
+ readonly retryable: true;
7
+ readonly operation: string;
8
+ }> {
9
+ }
10
+ declare const IDBError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => import("effect/Cause").YieldableError & {
11
+ readonly _tag: "IDBError";
12
+ } & Readonly<A>;
13
+ export declare class IDBError extends IDBError_base<{
14
+ readonly operation: 'get' | 'set' | 'delete' | 'clear';
15
+ readonly store?: string;
16
+ readonly key?: string;
17
+ readonly cause: unknown;
18
+ }> {
19
+ }
20
+ declare const IDBWriteError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => import("effect/Cause").YieldableError & {
21
+ readonly _tag: "IDBWriteError";
22
+ } & Readonly<A>;
23
+ export declare class IDBWriteError extends IDBWriteError_base<{
24
+ readonly key: string;
25
+ readonly value: unknown;
26
+ readonly cause: unknown;
27
+ }> {
28
+ }
29
+ declare const ReconciliationError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => import("effect/Cause").YieldableError & {
30
+ readonly _tag: "ReconciliationError";
31
+ } & Readonly<A>;
32
+ export declare class ReconciliationError extends ReconciliationError_base<{
33
+ readonly collection: string;
34
+ readonly reason: string;
35
+ readonly cause?: unknown;
36
+ }> {
37
+ }
38
+ declare const ProseError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => import("effect/Cause").YieldableError & {
39
+ readonly _tag: "ProseError";
40
+ } & Readonly<A>;
41
+ export declare class ProseError extends ProseError_base<{
42
+ readonly documentId: string;
43
+ readonly field: string;
44
+ readonly collection: string;
45
+ }> {
46
+ }
47
+ declare const CollectionNotReadyError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => import("effect/Cause").YieldableError & {
48
+ readonly _tag: "CollectionNotReadyError";
49
+ } & Readonly<A>;
50
+ export declare class CollectionNotReadyError extends CollectionNotReadyError_base<{
51
+ readonly collection: string;
52
+ readonly reason: string;
53
+ }> {
54
+ }
55
+ /** Error that should not be retried (auth failures, validation errors) */
56
+ export declare class NonRetriableError extends Error {
57
+ constructor(message: string);
58
+ }
59
+ export {};
@@ -1,18 +1,22 @@
1
- /**
2
- * Client-side utilities for browser/React code.
3
- * Import this in your frontend components.
4
- *
5
- * @example
6
- * ```typescript
7
- * // src/useTasks.ts
8
- * import {
9
- * convexCollectionOptions,
10
- * createConvexCollection,
11
- * type ConvexCollection,
12
- * } from '@trestleinc/replicate/client';
13
- * ```
14
- */
15
- export { ReplicateStorage } from './storage.js';
16
- export { convexCollectionOptions, createConvexCollection, type ConvexCollection, type ConvexCollectionOptionsConfig, } from './collection.js';
17
- export * as Y from 'yjs';
18
- export { NonRetriableError } from '@tanstack/offline-transactions';
1
+ export { convexCollectionOptions, type ConvexCollection, type EditorBinding, } from '$/client/collection.js';
2
+ import { NetworkError, IDBError, IDBWriteError, ReconciliationError, ProseError, CollectionNotReadyError, NonRetriableError } from '$/client/errors.js';
3
+ export declare const errors: {
4
+ readonly Network: typeof NetworkError;
5
+ readonly IDB: typeof IDBError;
6
+ readonly IDBWrite: typeof IDBWriteError;
7
+ readonly Reconciliation: typeof ReconciliationError;
8
+ readonly Prose: typeof ProseError;
9
+ readonly CollectionNotReady: typeof CollectionNotReadyError;
10
+ readonly NonRetriable: typeof NonRetriableError;
11
+ };
12
+ import { extract } from '$/client/merge.js';
13
+ export declare const prose: {
14
+ readonly extract: typeof extract;
15
+ };
16
+ export { persistence, type Persistence, type PersistenceProvider, type KeyValueStore, type SqlitePersistenceOptions, type SqliteAdapter, type SqlJsStatic, } from '$/client/persistence/index.js';
17
+ import { SqlJsAdapter, OPSqliteAdapter } from '$/client/persistence/adapters/index.js';
18
+ export declare const adapters: {
19
+ readonly sqljs: typeof SqlJsAdapter;
20
+ readonly opsqlite: typeof OPSqliteAdapter;
21
+ };
22
+ export type { SqlJsDatabase, SqlJsAdapterOptions, OPSQLiteDatabase, } from '$/client/persistence/adapters/index.js';
@@ -1,3 +1,2 @@
1
1
  import { type Logger } from '@logtape/logtape';
2
- export declare function configureLogger(enableLogging?: boolean): Promise<void>;
3
2
  export declare function getLogger(category: string[]): Logger;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Merge Helpers - Plain functions for Yjs CRDT operations
3
+ *
4
+ * Provides document creation, state encoding, and merge operations.
5
+ */
6
+ import * as Y from 'yjs';
7
+ import type { KeyValueStore } from '$/client/persistence/types.js';
8
+ /**
9
+ * Create a Yjs document with a persistent clientId.
10
+ * The clientId ensures consistent identity across sessions for CRDT merging.
11
+ *
12
+ * @param collection - The collection name
13
+ * @param kv - Key-value store for persisting the clientId
14
+ */
15
+ export declare function createYjsDocument(collection: string, kv: KeyValueStore): Promise<Y.Doc>;
16
+ /**
17
+ * Apply a binary update to a Yjs document.
18
+ * Y.applyUpdateV2 is already atomic, no need for transaction wrapper.
19
+ */
20
+ export declare function applyUpdate(doc: Y.Doc, update: Uint8Array, origin?: string): void;
21
+ /**
22
+ * Get a Y.Map from a Yjs document by name.
23
+ */
24
+ export declare function getYMap<T = unknown>(doc: Y.Doc, name: string): Y.Map<T>;
25
+ /**
26
+ * Execute a function within a Yjs transaction.
27
+ */
28
+ export declare function yjsTransact<A>(doc: Y.Doc, fn: () => A, origin?: string): A;
29
+ /**
30
+ * Execute a function within a Yjs transaction and capture the delta.
31
+ * Returns both the function result and a delta containing only the changes made.
32
+ */
33
+ export declare function transactWithDelta<A>(doc: Y.Doc, fn: () => A, origin?: string): {
34
+ result: A;
35
+ delta: Uint8Array;
36
+ };
37
+ /**
38
+ * Serialize a Y.Map to a plain object.
39
+ */
40
+ export declare function serializeYMap(ymap: Y.Map<unknown>): Record<string, unknown>;
41
+ /**
42
+ * Extract all items from a Y.Map as plain objects.
43
+ */
44
+ export declare function extractItems<T>(ymap: Y.Map<unknown>): T[];
45
+ /**
46
+ * Extract a single item from a Y.Map by key.
47
+ */
48
+ export declare function extractItem<T>(ymap: Y.Map<unknown>, key: string): T | null;
49
+ import type { XmlFragmentJSON } from '$/shared/types.js';
50
+ /**
51
+ * Check if a value looks like ProseMirror/BlockNote JSON document.
52
+ * Used internally to auto-detect prose fields during insert/update.
53
+ */
54
+ export declare function isDoc(value: unknown): value is XmlFragmentJSON;
55
+ /**
56
+ * Convert a Y.XmlFragment to ProseMirror-compatible JSON.
57
+ */
58
+ export declare function fragmentToJSON(fragment: Y.XmlFragment): XmlFragmentJSON;
59
+ /**
60
+ * Initialize a Y.XmlFragment from ProseMirror-compatible JSON.
61
+ */
62
+ export declare function fragmentFromJSON(fragment: Y.XmlFragment, json: XmlFragmentJSON): void;
63
+ /**
64
+ * Extract plain text from ProseMirror/BlockNote JSON content.
65
+ * Handles various content structures defensively for search and display.
66
+ */
67
+ export declare function extract(content: unknown): string;
68
+ /**
69
+ * Serialize any value, handling Yjs types specially.
70
+ * Uses our custom serialization system that works across module instances.
71
+ */
72
+ export declare function serializeYMapValue(value: unknown): unknown;
73
+ /**
74
+ * Get a Y.XmlFragment from a document's field.
75
+ * Returns null if the document or field doesn't exist, or if the field is not an XmlFragment.
76
+ */
77
+ export declare function getFragmentFromYMap(ymap: Y.Map<unknown>, documentId: string, field: string): Y.XmlFragment | null;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * SQLite adapter wrappers for different platforms.
3
+ *
4
+ * These are wrapper classes - the consuming app imports and initializes
5
+ * the actual database packages, then passes them to these wrappers.
6
+ */
7
+ export { SqlJsAdapter, type SqlJsDatabase, type SqlJsAdapterOptions } from './sqljs.js';
8
+ export { OPSqliteAdapter, type OPSQLiteDatabase } from './opsqlite.js';
@@ -0,0 +1,46 @@
1
+ /**
2
+ * op-sqlite adapter wrapper for React Native SQLite.
3
+ *
4
+ * The consuming app imports @op-engineering/op-sqlite and opens the database,
5
+ * then passes it to this wrapper.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { open } from '@op-engineering/op-sqlite';
10
+ * import { OPSqliteAdapter } from '@trestleinc/replicate/client';
11
+ *
12
+ * const db = open({ name: 'myapp.db' });
13
+ * const adapter = new OPSqliteAdapter(db);
14
+ * ```
15
+ */
16
+ import type { SqliteAdapter } from '../sqlite-level.js';
17
+ /**
18
+ * Interface for op-sqlite Database.
19
+ * Consumer must install @op-engineering/op-sqlite and pass a Database instance.
20
+ */
21
+ export interface OPSQLiteDatabase {
22
+ execute(sql: string, params?: unknown[]): Promise<{
23
+ rows: Record<string, unknown>[];
24
+ }>;
25
+ close(): void;
26
+ }
27
+ /**
28
+ * Wraps an op-sqlite Database as a SqliteAdapter.
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * import { open } from '@op-engineering/op-sqlite';
33
+ * import { OPSqliteAdapter } from '@trestleinc/replicate/client';
34
+ *
35
+ * const db = open({ name: 'myapp.db' });
36
+ * const adapter = new OPSqliteAdapter(db);
37
+ * ```
38
+ */
39
+ export declare class OPSqliteAdapter implements SqliteAdapter {
40
+ private db;
41
+ constructor(db: OPSQLiteDatabase);
42
+ execute(sql: string, params?: unknown[]): Promise<{
43
+ rows: Record<string, unknown>[];
44
+ }>;
45
+ close(): void;
46
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * sql.js adapter wrapper for browser SQLite.
3
+ *
4
+ * The consuming app imports sql.js and creates the database,
5
+ * then passes it to this wrapper.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import initSqlJs from 'sql.js';
10
+ * import { SqlJsAdapter } from '@trestleinc/replicate/client';
11
+ *
12
+ * const SQL = await initSqlJs({ locateFile: f => `/wasm/${f}` });
13
+ * const db = new SQL.Database();
14
+ * const adapter = new SqlJsAdapter(db, {
15
+ * onPersist: async (data) => {
16
+ * // Persist to OPFS, localStorage, etc.
17
+ * }
18
+ * });
19
+ * ```
20
+ */
21
+ import type { SqliteAdapter } from '../sqlite-level.js';
22
+ /**
23
+ * Interface for sql.js Database.
24
+ * Consumer must install sql.js and pass a Database instance.
25
+ */
26
+ export interface SqlJsDatabase {
27
+ run(sql: string, params?: unknown[]): void;
28
+ prepare(sql: string): {
29
+ bind(params?: unknown[]): void;
30
+ step(): boolean;
31
+ getAsObject(): Record<string, unknown>;
32
+ free(): void;
33
+ };
34
+ export(): Uint8Array;
35
+ close(): void;
36
+ }
37
+ /**
38
+ * Options for the SqlJsAdapter.
39
+ */
40
+ export interface SqlJsAdapterOptions {
41
+ /**
42
+ * Callback to persist database after write operations.
43
+ * Called with the exported database bytes.
44
+ *
45
+ * @example OPFS persistence
46
+ * ```typescript
47
+ * onPersist: async (data) => {
48
+ * const root = await navigator.storage.getDirectory();
49
+ * const handle = await root.getFileHandle('myapp.sqlite', { create: true });
50
+ * const writable = await handle.createWritable();
51
+ * await writable.write(data.buffer);
52
+ * await writable.close();
53
+ * }
54
+ * ```
55
+ */
56
+ onPersist?: (data: Uint8Array) => Promise<void>;
57
+ }
58
+ /**
59
+ * Wraps a sql.js Database as a SqliteAdapter.
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * import initSqlJs from 'sql.js';
64
+ * import { SqlJsAdapter } from '@trestleinc/replicate/client';
65
+ *
66
+ * const SQL = await initSqlJs();
67
+ * const db = new SQL.Database();
68
+ * const adapter = new SqlJsAdapter(db);
69
+ * ```
70
+ */
71
+ export declare class SqlJsAdapter implements SqliteAdapter {
72
+ private db;
73
+ private onPersist?;
74
+ constructor(db: SqlJsDatabase, options?: SqlJsAdapterOptions);
75
+ execute(sql: string, params?: unknown[]): Promise<{
76
+ rows: Record<string, unknown>[];
77
+ }>;
78
+ close(): void;
79
+ /**
80
+ * Persist database using the onPersist callback if provided.
81
+ */
82
+ private persist;
83
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Persistence layer exports.
3
+ *
4
+ * Provides swappable storage backends for Y.Doc and key-value data.
5
+ */
6
+ export type { Persistence, PersistenceProvider, KeyValueStore } from './types.js';
7
+ export type { SqlitePersistenceOptions } from './sqlite.js';
8
+ export type { SqlJsStatic } from './sqlite-browser.js';
9
+ export type { SqliteAdapter } from './sqlite-level.js';
10
+ import { indexeddbPersistence } from './indexeddb.js';
11
+ import { memoryPersistence } from './memory.js';
12
+ import { sqlitePersistence } from './sqlite.js';
13
+ import { createBrowserSqlitePersistence } from './sqlite-browser.js';
14
+ import { createReactNativeSqlitePersistence } from './sqlite-rn.js';
15
+ /**
16
+ * Persistence API - nested object pattern for ergonomic access.
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * import { persistence } from '@trestleinc/replicate/client';
21
+ *
22
+ * // Browser SQLite (recommended for web)
23
+ * const p = await persistence.sqlite.browser(SQL, 'myapp');
24
+ *
25
+ * // React Native SQLite
26
+ * const p = await persistence.sqlite.native(db, 'myapp');
27
+ *
28
+ * // IndexedDB fallback
29
+ * const p = persistence.indexeddb('myapp');
30
+ *
31
+ * // In-memory (testing)
32
+ * const p = persistence.memory();
33
+ * ```
34
+ */
35
+ export declare const persistence: {
36
+ /** IndexedDB-backed persistence (browser) */
37
+ readonly indexeddb: typeof indexeddbPersistence;
38
+ /** In-memory persistence (testing/ephemeral) */
39
+ readonly memory: typeof memoryPersistence;
40
+ /** SQLite persistence variants */
41
+ readonly sqlite: {
42
+ /** Browser SQLite with OPFS (sql.js) */
43
+ readonly browser: typeof createBrowserSqlitePersistence;
44
+ /** React Native SQLite (op-sqlite) */
45
+ readonly native: typeof createReactNativeSqlitePersistence;
46
+ /** Custom SQLite adapter */
47
+ readonly create: typeof sqlitePersistence;
48
+ };
49
+ };
@@ -0,0 +1,17 @@
1
+ import type { Persistence } from './types.js';
2
+ /**
3
+ * Create an IndexedDB persistence factory.
4
+ *
5
+ * Uses y-indexeddb for Y.Doc persistence and browser-level for metadata storage.
6
+ *
7
+ * @param dbName - Name for the LevelDB database (default: 'replicate-kv')
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * convexCollectionOptions<Task>({
12
+ * // ... other options
13
+ * persistence: indexeddbPersistence(),
14
+ * });
15
+ * ```
16
+ */
17
+ export declare function indexeddbPersistence(dbName?: string): Persistence;
@@ -0,0 +1,16 @@
1
+ import type { Persistence } from './types.js';
2
+ /**
3
+ * Create an in-memory persistence factory.
4
+ *
5
+ * Useful for testing where you don't want IndexedDB side effects.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * // In tests
10
+ * convexCollectionOptions<Task>({
11
+ * // ... other options
12
+ * persistence: memoryPersistence(),
13
+ * });
14
+ * ```
15
+ */
16
+ export declare function memoryPersistence(): Persistence;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Browser SQLite persistence helper using sql.js and OPFS.
3
+ *
4
+ * Handles all the boilerplate for browser SQLite:
5
+ * - Loading existing database from OPFS
6
+ * - Persisting to OPFS on every write
7
+ * - Creating the SqlJsAdapter
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { createBrowserSqlitePersistence } from '@trestleinc/replicate/client';
12
+ * import initSqlJs from 'sql.js';
13
+ *
14
+ * const SQL = await initSqlJs({ locateFile: f => `https://sql.js.org/dist/${f}` });
15
+ * const persistence = await createBrowserSqlitePersistence(SQL, 'myapp');
16
+ * ```
17
+ */
18
+ import { type SqlJsDatabase } from './adapters/sqljs.js';
19
+ import type { Persistence } from './types.js';
20
+ /**
21
+ * Interface for the sql.js module (the result of initSqlJs).
22
+ */
23
+ export interface SqlJsStatic {
24
+ Database: new (data?: ArrayLike<number>) => SqlJsDatabase;
25
+ }
26
+ /**
27
+ * Create browser SQLite persistence with OPFS storage.
28
+ *
29
+ * This helper handles all the OPFS boilerplate:
30
+ * - Loads existing database from OPFS on init
31
+ * - Persists to OPFS after every write operation
32
+ *
33
+ * @param SQL - The initialized sql.js module (from `await initSqlJs()`)
34
+ * @param dbName - Name for the database (used for OPFS filename: `{dbName}.sqlite`)
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * import { createBrowserSqlitePersistence } from '@trestleinc/replicate/client';
39
+ * import initSqlJs from 'sql.js';
40
+ *
41
+ * const SQL = await initSqlJs({ locateFile: f => `https://sql.js.org/dist/${f}` });
42
+ * const persistence = await createBrowserSqlitePersistence(SQL, 'intervals');
43
+ *
44
+ * // Use in collection options
45
+ * convexCollectionOptions<Task>({
46
+ * // ...
47
+ * persistence,
48
+ * });
49
+ * ```
50
+ */
51
+ export declare function createBrowserSqlitePersistence(SQL: SqlJsStatic, dbName: string): Promise<Persistence>;