@trestleinc/replicate 1.1.1 → 1.1.2-preview.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 (91) hide show
  1. package/README.md +395 -146
  2. package/dist/client/index.d.ts +311 -19
  3. package/dist/client/index.js +4027 -0
  4. package/dist/component/_generated/api.d.ts +13 -17
  5. package/dist/component/_generated/api.js +24 -4
  6. package/dist/component/_generated/component.d.ts +79 -77
  7. package/dist/component/_generated/component.js +1 -0
  8. package/dist/component/_generated/dataModel.d.ts +12 -15
  9. package/dist/component/_generated/dataModel.js +1 -0
  10. package/dist/component/_generated/server.d.ts +19 -22
  11. package/dist/component/_generated/server.js +65 -1
  12. package/dist/component/_virtual/rolldown_runtime.js +18 -0
  13. package/dist/component/convex.config.d.ts +6 -2
  14. package/dist/component/convex.config.js +7 -3
  15. package/dist/component/logger.d.ts +10 -6
  16. package/dist/component/logger.js +25 -28
  17. package/dist/component/public.d.ts +70 -61
  18. package/dist/component/public.js +311 -295
  19. package/dist/component/schema.d.ts +53 -45
  20. package/dist/component/schema.js +26 -32
  21. package/dist/component/shared/types.d.ts +9 -0
  22. package/dist/component/shared/types.js +15 -0
  23. package/dist/server/index.d.ts +134 -13
  24. package/dist/server/index.js +368 -0
  25. package/dist/shared/index.d.ts +27 -3
  26. package/dist/shared/index.js +1 -2
  27. package/package.json +34 -29
  28. package/src/client/collection.ts +339 -306
  29. package/src/client/errors.ts +9 -9
  30. package/src/client/index.ts +13 -32
  31. package/src/client/logger.ts +2 -2
  32. package/src/client/merge.ts +37 -34
  33. package/src/client/persistence/custom.ts +84 -0
  34. package/src/client/persistence/index.ts +9 -46
  35. package/src/client/persistence/indexeddb.ts +111 -84
  36. package/src/client/persistence/memory.ts +3 -3
  37. package/src/client/persistence/sqlite/browser.ts +168 -0
  38. package/src/client/persistence/sqlite/native.ts +29 -0
  39. package/src/client/persistence/sqlite/schema.ts +124 -0
  40. package/src/client/persistence/types.ts +32 -28
  41. package/src/client/prose-schema.ts +55 -0
  42. package/src/client/prose.ts +28 -25
  43. package/src/client/replicate.ts +5 -5
  44. package/src/client/services/cursor.ts +109 -0
  45. package/src/component/_generated/component.ts +31 -29
  46. package/src/component/convex.config.ts +2 -2
  47. package/src/component/logger.ts +7 -7
  48. package/src/component/public.ts +225 -237
  49. package/src/component/schema.ts +18 -15
  50. package/src/server/builder.ts +20 -7
  51. package/src/server/index.ts +3 -5
  52. package/src/server/schema.ts +5 -5
  53. package/src/server/storage.ts +113 -59
  54. package/src/shared/index.ts +5 -5
  55. package/src/shared/types.ts +51 -14
  56. package/dist/client/collection.d.ts +0 -96
  57. package/dist/client/errors.d.ts +0 -59
  58. package/dist/client/logger.d.ts +0 -2
  59. package/dist/client/merge.d.ts +0 -77
  60. package/dist/client/persistence/adapters/index.d.ts +0 -8
  61. package/dist/client/persistence/adapters/opsqlite.d.ts +0 -46
  62. package/dist/client/persistence/adapters/sqljs.d.ts +0 -83
  63. package/dist/client/persistence/index.d.ts +0 -49
  64. package/dist/client/persistence/indexeddb.d.ts +0 -17
  65. package/dist/client/persistence/memory.d.ts +0 -16
  66. package/dist/client/persistence/sqlite-browser.d.ts +0 -51
  67. package/dist/client/persistence/sqlite-level.d.ts +0 -63
  68. package/dist/client/persistence/sqlite-rn.d.ts +0 -36
  69. package/dist/client/persistence/sqlite.d.ts +0 -47
  70. package/dist/client/persistence/types.d.ts +0 -42
  71. package/dist/client/prose.d.ts +0 -56
  72. package/dist/client/replicate.d.ts +0 -40
  73. package/dist/client/services/checkpoint.d.ts +0 -18
  74. package/dist/client/services/reconciliation.d.ts +0 -24
  75. package/dist/index.js +0 -1618
  76. package/dist/server/builder.d.ts +0 -94
  77. package/dist/server/schema.d.ts +0 -27
  78. package/dist/server/storage.d.ts +0 -80
  79. package/dist/server.js +0 -281
  80. package/dist/shared/types.d.ts +0 -50
  81. package/dist/shared/types.js +0 -6
  82. package/dist/shared.js +0 -6
  83. package/src/client/persistence/adapters/index.ts +0 -8
  84. package/src/client/persistence/adapters/opsqlite.ts +0 -54
  85. package/src/client/persistence/adapters/sqljs.ts +0 -128
  86. package/src/client/persistence/sqlite-browser.ts +0 -107
  87. package/src/client/persistence/sqlite-level.ts +0 -407
  88. package/src/client/persistence/sqlite-rn.ts +0 -44
  89. package/src/client/persistence/sqlite.ts +0 -160
  90. package/src/client/services/checkpoint.ts +0 -86
  91. package/src/client/services/reconciliation.ts +0 -108
@@ -1,128 +0,0 @@
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
- /**
24
- * Interface for sql.js Database.
25
- * Consumer must install sql.js and pass a Database instance.
26
- */
27
- export interface SqlJsDatabase {
28
- run(sql: string, params?: unknown[]): void;
29
- prepare(sql: string): {
30
- bind(params?: unknown[]): void;
31
- step(): boolean;
32
- getAsObject(): Record<string, unknown>;
33
- free(): void;
34
- };
35
- export(): Uint8Array;
36
- close(): void;
37
- }
38
-
39
- /**
40
- * Options for the SqlJsAdapter.
41
- */
42
- export interface SqlJsAdapterOptions {
43
- /**
44
- * Callback to persist database after write operations.
45
- * Called with the exported database bytes.
46
- *
47
- * @example OPFS persistence
48
- * ```typescript
49
- * onPersist: async (data) => {
50
- * const root = await navigator.storage.getDirectory();
51
- * const handle = await root.getFileHandle('myapp.sqlite', { create: true });
52
- * const writable = await handle.createWritable();
53
- * await writable.write(data.buffer);
54
- * await writable.close();
55
- * }
56
- * ```
57
- */
58
- onPersist?: (data: Uint8Array) => Promise<void>;
59
- }
60
-
61
- /**
62
- * Wraps a sql.js Database as a SqliteAdapter.
63
- *
64
- * @example
65
- * ```typescript
66
- * import initSqlJs from 'sql.js';
67
- * import { SqlJsAdapter } from '@trestleinc/replicate/client';
68
- *
69
- * const SQL = await initSqlJs();
70
- * const db = new SQL.Database();
71
- * const adapter = new SqlJsAdapter(db);
72
- * ```
73
- */
74
- export class SqlJsAdapter implements SqliteAdapter {
75
- private db: SqlJsDatabase;
76
- private onPersist?: (data: Uint8Array) => Promise<void>;
77
-
78
- constructor(db: SqlJsDatabase, options: SqlJsAdapterOptions = {}) {
79
- this.db = db;
80
- this.onPersist = options.onPersist;
81
- }
82
-
83
- async execute(sql: string, params?: unknown[]): Promise<{ rows: Record<string, unknown>[] }> {
84
- const rows: Record<string, unknown>[] = [];
85
-
86
- // Handle statements that don't return data
87
- if (
88
- sql.trim().toUpperCase().startsWith('CREATE') ||
89
- sql.trim().toUpperCase().startsWith('INSERT') ||
90
- sql.trim().toUpperCase().startsWith('UPDATE') ||
91
- sql.trim().toUpperCase().startsWith('DELETE') ||
92
- sql.trim().toUpperCase().startsWith('BEGIN') ||
93
- sql.trim().toUpperCase().startsWith('COMMIT') ||
94
- sql.trim().toUpperCase().startsWith('ROLLBACK')
95
- ) {
96
- this.db.run(sql, params);
97
- await this.persist();
98
- return { rows };
99
- }
100
-
101
- // Handle SELECT statements
102
- const stmt = this.db.prepare(sql);
103
- if (params && params.length > 0) {
104
- stmt.bind(params);
105
- }
106
-
107
- while (stmt.step()) {
108
- rows.push(stmt.getAsObject());
109
- }
110
- stmt.free();
111
-
112
- return { rows };
113
- }
114
-
115
- close(): void {
116
- this.db.close();
117
- }
118
-
119
- /**
120
- * Persist database using the onPersist callback if provided.
121
- */
122
- private async persist(): Promise<void> {
123
- if (this.onPersist) {
124
- const data = this.db.export();
125
- await this.onPersist(new Uint8Array(data));
126
- }
127
- }
128
- }
@@ -1,107 +0,0 @@
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 { SqlJsAdapter, type SqlJsDatabase } from './adapters/sqljs.js';
19
- import { sqlitePersistence } from './sqlite.js';
20
- import type { Persistence } from './types.js';
21
-
22
- /**
23
- * Interface for the sql.js module (the result of initSqlJs).
24
- */
25
- export interface SqlJsStatic {
26
- Database: new (data?: ArrayLike<number>) => SqlJsDatabase;
27
- }
28
-
29
- /**
30
- * Load existing database from OPFS if available.
31
- */
32
- async function loadFromOPFS(dbName: string): Promise<Uint8Array | null> {
33
- try {
34
- const root = await navigator.storage.getDirectory();
35
- const handle = await root.getFileHandle(`${dbName}.sqlite`);
36
- const file = await handle.getFile();
37
- const buffer = await file.arrayBuffer();
38
- return new Uint8Array(buffer);
39
- } catch {
40
- // File doesn't exist yet
41
- return null;
42
- }
43
- }
44
-
45
- /**
46
- * Save database to OPFS for durable storage.
47
- */
48
- function createOPFSSaver(dbName: string): (data: Uint8Array) => Promise<void> {
49
- return async (data: Uint8Array): Promise<void> => {
50
- try {
51
- const root = await navigator.storage.getDirectory();
52
- const handle = await root.getFileHandle(`${dbName}.sqlite`, { create: true });
53
- const writable = await handle.createWritable();
54
- // Copy to a new ArrayBuffer to satisfy TypeScript's strict ArrayBuffer type
55
- const buffer = new ArrayBuffer(data.length);
56
- new Uint8Array(buffer).set(data);
57
- await writable.write(buffer);
58
- await writable.close();
59
- } catch {
60
- // Silently fail - OPFS may not be available
61
- }
62
- };
63
- }
64
-
65
- /**
66
- * Create browser SQLite persistence with OPFS storage.
67
- *
68
- * This helper handles all the OPFS boilerplate:
69
- * - Loads existing database from OPFS on init
70
- * - Persists to OPFS after every write operation
71
- *
72
- * @param SQL - The initialized sql.js module (from `await initSqlJs()`)
73
- * @param dbName - Name for the database (used for OPFS filename: `{dbName}.sqlite`)
74
- *
75
- * @example
76
- * ```typescript
77
- * import { createBrowserSqlitePersistence } from '@trestleinc/replicate/client';
78
- * import initSqlJs from 'sql.js';
79
- *
80
- * const SQL = await initSqlJs({ locateFile: f => `https://sql.js.org/dist/${f}` });
81
- * const persistence = await createBrowserSqlitePersistence(SQL, 'intervals');
82
- *
83
- * // Use in collection options
84
- * convexCollectionOptions<Task>({
85
- * // ...
86
- * persistence,
87
- * });
88
- * ```
89
- */
90
- export async function createBrowserSqlitePersistence(
91
- SQL: SqlJsStatic,
92
- dbName: string
93
- ): Promise<Persistence> {
94
- // Load existing database from OPFS if available
95
- const existingData = await loadFromOPFS(dbName);
96
-
97
- // Create database (with existing data if found)
98
- const db = existingData ? new SQL.Database(existingData) : new SQL.Database();
99
-
100
- // Create adapter with OPFS persistence
101
- const adapter = new SqlJsAdapter(db, {
102
- onPersist: createOPFSSaver(dbName),
103
- });
104
-
105
- // Create and return persistence
106
- return sqlitePersistence({ adapter, dbName });
107
- }
@@ -1,407 +0,0 @@
1
- /**
2
- * SQLite-backed abstract-level implementation.
3
- *
4
- * Provides a LevelDB-compatible key-value store backed by SQLite,
5
- * enabling y-leveldb to work with SQLite databases.
6
- *
7
- * Supports both browser (sql.js WASM) and React Native (op-sqlite).
8
- */
9
- import {
10
- AbstractLevel,
11
- AbstractIterator,
12
- AbstractKeyIterator,
13
- AbstractValueIterator,
14
- } from 'abstract-level';
15
-
16
- /**
17
- * Interface for SQLite database operations.
18
- * Abstracts over sql.js (browser) and op-sqlite (React Native).
19
- */
20
- export interface SqliteAdapter {
21
- execute(sql: string, params?: unknown[]): Promise<{ rows: Record<string, unknown>[] }>;
22
- close(): void;
23
- }
24
-
25
- interface SqliteLevelOptions {
26
- /** Custom SQLite adapter (for testing or alternative backends) */
27
- adapter?: SqliteAdapter;
28
- /** Value encoding (default: 'utf8') */
29
- valueEncoding?: string;
30
- keyEncoding?: string;
31
- }
32
-
33
- /**
34
- * SQLite-backed implementation of abstract-level.
35
- *
36
- * Uses a simple key-value table with lexicographic ordering:
37
- * CREATE TABLE entries (key BLOB PRIMARY KEY, value BLOB)
38
- */
39
- export class SqliteLevel<K = string, V = string> extends AbstractLevel<K, V> {
40
- private adapter: SqliteAdapter | null = null;
41
- private adapterFactory: (() => Promise<SqliteAdapter>) | null = null;
42
-
43
- constructor(_location: string, options?: SqliteLevelOptions) {
44
- super(
45
- {
46
- encodings: { utf8: true, buffer: true, view: true },
47
- seek: true,
48
- permanence: true,
49
- createIfMissing: true,
50
- errorIfExists: false,
51
- additionalMethods: {},
52
- },
53
- {
54
- keyEncoding: options?.keyEncoding ?? 'utf8',
55
- valueEncoding: options?.valueEncoding ?? 'utf8',
56
- }
57
- );
58
-
59
- if (options?.adapter) {
60
- this.adapter = options.adapter;
61
- }
62
- }
63
-
64
- /**
65
- * Set the adapter factory for deferred initialization.
66
- * Call this before open() to configure the SQLite backend.
67
- */
68
- setAdapterFactory(factory: () => Promise<SqliteAdapter>): void {
69
- this.adapterFactory = factory;
70
- }
71
-
72
- async _open(): Promise<void> {
73
- if (!this.adapter) {
74
- if (this.adapterFactory) {
75
- this.adapter = await this.adapterFactory();
76
- } else {
77
- throw new Error('No SQLite adapter configured. Call setAdapterFactory() before open().');
78
- }
79
- }
80
-
81
- // Create the entries table if it doesn't exist
82
- await this.adapter.execute(`
83
- CREATE TABLE IF NOT EXISTS entries (
84
- key BLOB PRIMARY KEY,
85
- value BLOB NOT NULL
86
- )
87
- `);
88
-
89
- // Create index for range queries (lexicographic ordering)
90
- await this.adapter.execute(`
91
- CREATE INDEX IF NOT EXISTS entries_key_idx ON entries (key)
92
- `);
93
- }
94
-
95
- async _close(): Promise<void> {
96
- if (this.adapter) {
97
- this.adapter.close();
98
- this.adapter = null;
99
- }
100
- }
101
-
102
- async _get(key: K): Promise<V | undefined> {
103
- if (!this.adapter) throw new Error('Database not open');
104
-
105
- const keyBytes = this.encodeKey(key);
106
- const result = await this.adapter.execute('SELECT value FROM entries WHERE key = ?', [
107
- keyBytes,
108
- ]);
109
-
110
- if (result.rows.length === 0) {
111
- return undefined;
112
- }
113
-
114
- return this.decodeValue(result.rows[0].value as Uint8Array);
115
- }
116
-
117
- async _put(key: K, value: V): Promise<void> {
118
- if (!this.adapter) throw new Error('Database not open');
119
-
120
- const keyBytes = this.encodeKey(key);
121
- const valueBytes = this.encodeValue(value);
122
-
123
- await this.adapter.execute('INSERT OR REPLACE INTO entries (key, value) VALUES (?, ?)', [
124
- keyBytes,
125
- valueBytes,
126
- ]);
127
- }
128
-
129
- async _del(key: K): Promise<void> {
130
- if (!this.adapter) throw new Error('Database not open');
131
-
132
- const keyBytes = this.encodeKey(key);
133
- await this.adapter.execute('DELETE FROM entries WHERE key = ?', [keyBytes]);
134
- }
135
-
136
- async _batch(
137
- operations: Array<{ type: 'put'; key: K; value: V } | { type: 'del'; key: K }>
138
- ): Promise<void> {
139
- if (!this.adapter) throw new Error('Database not open');
140
-
141
- // Execute all operations in a transaction
142
- await this.adapter.execute('BEGIN TRANSACTION');
143
-
144
- try {
145
- for (const op of operations) {
146
- if (op.type === 'put') {
147
- const keyBytes = this.encodeKey(op.key);
148
- const valueBytes = this.encodeValue(op.value);
149
- await this.adapter.execute('INSERT OR REPLACE INTO entries (key, value) VALUES (?, ?)', [
150
- keyBytes,
151
- valueBytes,
152
- ]);
153
- } else if (op.type === 'del') {
154
- const keyBytes = this.encodeKey(op.key);
155
- await this.adapter.execute('DELETE FROM entries WHERE key = ?', [keyBytes]);
156
- }
157
- }
158
- await this.adapter.execute('COMMIT');
159
- } catch (error) {
160
- await this.adapter.execute('ROLLBACK');
161
- throw error;
162
- }
163
- }
164
-
165
- async _clear(): Promise<void> {
166
- if (!this.adapter) throw new Error('Database not open');
167
- await this.adapter.execute('DELETE FROM entries');
168
- }
169
-
170
- _iterator(options: Record<string, unknown>): AbstractIterator<typeof this, K, V> {
171
- if (!this.adapter) throw new Error('Database not open');
172
- return new SqliteIterator(this, this.adapter, options) as unknown as AbstractIterator<
173
- typeof this,
174
- K,
175
- V
176
- >;
177
- }
178
-
179
- _keys(options: Record<string, unknown>): AbstractKeyIterator<typeof this, K> {
180
- if (!this.adapter) throw new Error('Database not open');
181
- return new SqliteKeyIterator(this, this.adapter, options) as unknown as AbstractKeyIterator<
182
- typeof this,
183
- K
184
- >;
185
- }
186
-
187
- _values(options: Record<string, unknown>): AbstractValueIterator<typeof this, K, V> {
188
- if (!this.adapter) throw new Error('Database not open');
189
- return new SqliteValueIterator(this, this.adapter, options) as unknown as AbstractValueIterator<
190
- typeof this,
191
- K,
192
- V
193
- >;
194
- }
195
-
196
- // Helper methods for encoding/decoding
197
- private encodeKey(key: K): Uint8Array {
198
- if (key instanceof Uint8Array) {
199
- return key;
200
- }
201
- if (typeof key === 'string') {
202
- return new TextEncoder().encode(key);
203
- }
204
- return new TextEncoder().encode(String(key));
205
- }
206
-
207
- private encodeValue(value: V): Uint8Array {
208
- if (value instanceof Uint8Array) {
209
- return value;
210
- }
211
- if (typeof value === 'string') {
212
- return new TextEncoder().encode(value);
213
- }
214
- return new TextEncoder().encode(JSON.stringify(value));
215
- }
216
-
217
- private decodeValue(bytes: Uint8Array): V {
218
- return new TextDecoder().decode(bytes) as V;
219
- }
220
- }
221
-
222
- /**
223
- * Iterator for key-value pairs.
224
- */
225
- class SqliteIterator<K, V> extends AbstractIterator<SqliteLevel<K, V>, K, V> {
226
- private adapter: SqliteAdapter;
227
- private options: Record<string, unknown>;
228
- private rows: Array<{ key: Uint8Array; value: Uint8Array }> | null = null;
229
- private index = 0;
230
-
231
- constructor(db: SqliteLevel<K, V>, adapter: SqliteAdapter, options: Record<string, unknown>) {
232
- super(db, options);
233
- this.adapter = adapter;
234
- this.options = options;
235
- }
236
-
237
- async _next(): Promise<[K, V] | undefined> {
238
- if (this.rows === null) {
239
- await this.loadRows();
240
- }
241
-
242
- if (this.rows && this.index < this.rows.length) {
243
- const row = this.rows[this.index++];
244
- const key = new TextDecoder().decode(row.key) as K;
245
- const value = new TextDecoder().decode(row.value) as V;
246
- return [key, value];
247
- }
248
-
249
- return undefined;
250
- }
251
-
252
- async _nextv(size: number): Promise<Array<[K, V]>> {
253
- if (this.rows === null) {
254
- await this.loadRows();
255
- }
256
-
257
- const result: Array<[K, V]> = [];
258
- while (this.rows && this.index < this.rows.length && result.length < size) {
259
- const row = this.rows[this.index++];
260
- const key = new TextDecoder().decode(row.key) as K;
261
- const value = new TextDecoder().decode(row.value) as V;
262
- result.push([key, value]);
263
- }
264
- return result;
265
- }
266
-
267
- private async loadRows(): Promise<void> {
268
- const { reverse, limit, gt, gte, lt, lte } = this.options as {
269
- reverse?: boolean;
270
- limit?: number;
271
- gt?: K;
272
- gte?: K;
273
- lt?: K;
274
- lte?: K;
275
- };
276
-
277
- let sql = 'SELECT key, value FROM entries';
278
- const params: unknown[] = [];
279
- const conditions: string[] = [];
280
-
281
- if (gt !== undefined) {
282
- conditions.push('key > ?');
283
- params.push(this.encodeKey(gt));
284
- }
285
- if (gte !== undefined) {
286
- conditions.push('key >= ?');
287
- params.push(this.encodeKey(gte));
288
- }
289
- if (lt !== undefined) {
290
- conditions.push('key < ?');
291
- params.push(this.encodeKey(lt));
292
- }
293
- if (lte !== undefined) {
294
- conditions.push('key <= ?');
295
- params.push(this.encodeKey(lte));
296
- }
297
-
298
- if (conditions.length > 0) {
299
- sql += ` WHERE ${conditions.join(' AND ')}`;
300
- }
301
-
302
- sql += ` ORDER BY key ${reverse ? 'DESC' : 'ASC'}`;
303
-
304
- if (limit !== undefined && limit >= 0) {
305
- sql += ` LIMIT ${limit}`;
306
- }
307
-
308
- const result = await this.adapter.execute(sql, params);
309
- this.rows = result.rows as Array<{ key: Uint8Array; value: Uint8Array }>;
310
- }
311
-
312
- private encodeKey(key: K): Uint8Array {
313
- if (key instanceof Uint8Array) {
314
- return key;
315
- }
316
- if (typeof key === 'string') {
317
- return new TextEncoder().encode(key);
318
- }
319
- return new TextEncoder().encode(String(key));
320
- }
321
- }
322
-
323
- /**
324
- * Iterator for keys only.
325
- */
326
- class SqliteKeyIterator<K, V> extends AbstractKeyIterator<SqliteLevel<K, V>, K> {
327
- private adapter: SqliteAdapter;
328
- private options: Record<string, unknown>;
329
- private rows: Array<{ key: Uint8Array }> | null = null;
330
- private index = 0;
331
-
332
- constructor(db: SqliteLevel<K, V>, adapter: SqliteAdapter, options: Record<string, unknown>) {
333
- super(db, options);
334
- this.adapter = adapter;
335
- this.options = options;
336
- }
337
-
338
- async _next(): Promise<K | undefined> {
339
- if (this.rows === null) {
340
- await this.loadRows();
341
- }
342
-
343
- if (this.rows && this.index < this.rows.length) {
344
- const row = this.rows[this.index++];
345
- return new TextDecoder().decode(row.key) as K;
346
- }
347
-
348
- return undefined;
349
- }
350
-
351
- private async loadRows(): Promise<void> {
352
- const { reverse, limit } = this.options as { reverse?: boolean; limit?: number };
353
-
354
- let sql = 'SELECT key FROM entries';
355
- sql += ` ORDER BY key ${reverse ? 'DESC' : 'ASC'}`;
356
-
357
- if (limit !== undefined && limit >= 0) {
358
- sql += ` LIMIT ${limit}`;
359
- }
360
-
361
- const result = await this.adapter.execute(sql);
362
- this.rows = result.rows as Array<{ key: Uint8Array }>;
363
- }
364
- }
365
-
366
- /**
367
- * Iterator for values only.
368
- */
369
- class SqliteValueIterator<K, V> extends AbstractValueIterator<SqliteLevel<K, V>, K, V> {
370
- private adapter: SqliteAdapter;
371
- private options: Record<string, unknown>;
372
- private rows: Array<{ value: Uint8Array }> | null = null;
373
- private index = 0;
374
-
375
- constructor(db: SqliteLevel<K, V>, adapter: SqliteAdapter, options: Record<string, unknown>) {
376
- super(db, options);
377
- this.adapter = adapter;
378
- this.options = options;
379
- }
380
-
381
- async _next(): Promise<V | undefined> {
382
- if (this.rows === null) {
383
- await this.loadRows();
384
- }
385
-
386
- if (this.rows && this.index < this.rows.length) {
387
- const row = this.rows[this.index++];
388
- return new TextDecoder().decode(row.value) as V;
389
- }
390
-
391
- return undefined;
392
- }
393
-
394
- private async loadRows(): Promise<void> {
395
- const { reverse, limit } = this.options as { reverse?: boolean; limit?: number };
396
-
397
- let sql = 'SELECT value FROM entries';
398
- sql += ` ORDER BY key ${reverse ? 'DESC' : 'ASC'}`;
399
-
400
- if (limit !== undefined && limit >= 0) {
401
- sql += ` LIMIT ${limit}`;
402
- }
403
-
404
- const result = await this.adapter.execute(sql);
405
- this.rows = result.rows as Array<{ value: Uint8Array }>;
406
- }
407
- }
@@ -1,44 +0,0 @@
1
- /**
2
- * React Native SQLite persistence helper using op-sqlite.
3
- *
4
- * @example
5
- * ```typescript
6
- * import { createReactNativeSqlitePersistence } from '@trestleinc/replicate/client';
7
- * import { open } from '@op-engineering/op-sqlite';
8
- *
9
- * const db = open({ name: 'myapp.db' });
10
- * const persistence = await createReactNativeSqlitePersistence(db, 'myapp');
11
- * ```
12
- */
13
- import { OPSqliteAdapter, type OPSQLiteDatabase } from './adapters/opsqlite.js';
14
- import { sqlitePersistence } from './sqlite.js';
15
- import type { Persistence } from './types.js';
16
-
17
- /**
18
- * Create React Native SQLite persistence using op-sqlite.
19
- *
20
- * @param db - The opened op-sqlite database instance
21
- * @param dbName - Name for internal database identification
22
- *
23
- * @example
24
- * ```typescript
25
- * import { createReactNativeSqlitePersistence } from '@trestleinc/replicate/client';
26
- * import { open } from '@op-engineering/op-sqlite';
27
- *
28
- * const db = open({ name: 'myapp.db' });
29
- * const persistence = await createReactNativeSqlitePersistence(db, 'myapp');
30
- *
31
- * // Use in collection options
32
- * convexCollectionOptions<Task>({
33
- * // ...
34
- * persistence,
35
- * });
36
- * ```
37
- */
38
- export async function createReactNativeSqlitePersistence(
39
- db: OPSQLiteDatabase,
40
- dbName: string
41
- ): Promise<Persistence> {
42
- const adapter = new OPSqliteAdapter(db);
43
- return sqlitePersistence({ adapter, dbName });
44
- }