@tallyui/storage-sqlite 0.2.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.
@@ -0,0 +1,265 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import type { RxJsonSchema, RxDocumentData, RxStorageInstanceCreationParams } from 'rxdb';
3
+ import { createMockSQLiteDatabase } from './mock-sqlite';
4
+ import { getRxStorageSQLite } from './rx-storage-sqlite';
5
+ import type { SQLiteStorageSettings } from './types';
6
+ import type { RxStorageInstanceSQLite } from './storage-instance';
7
+
8
+ // Simple doc type for testing
9
+ interface TestDoc {
10
+ id: string;
11
+ name: string;
12
+ age: number;
13
+ }
14
+
15
+ const testSchema: RxJsonSchema<RxDocumentData<TestDoc>> = {
16
+ version: 0,
17
+ primaryKey: 'id',
18
+ type: 'object',
19
+ properties: {
20
+ id: { type: 'string', maxLength: 100 },
21
+ name: { type: 'string' },
22
+ age: { type: 'number' },
23
+ _deleted: { type: 'boolean' },
24
+ _rev: { type: 'string' },
25
+ _meta: {
26
+ type: 'object',
27
+ properties: {
28
+ lwt: { type: 'number' },
29
+ },
30
+ required: ['lwt'],
31
+ },
32
+ _attachments: { type: 'object' },
33
+ },
34
+ required: ['id', 'name', 'age', '_deleted', '_rev', '_meta', '_attachments'],
35
+ };
36
+
37
+ function makeDoc(
38
+ overrides: Partial<TestDoc & RxDocumentData<TestDoc>> = {},
39
+ ): RxDocumentData<TestDoc> {
40
+ return {
41
+ id: 'doc1',
42
+ name: 'Alice',
43
+ age: 30,
44
+ _deleted: false,
45
+ _rev: '1-abc',
46
+ _meta: { lwt: Date.now() },
47
+ _attachments: {},
48
+ ...overrides,
49
+ } as RxDocumentData<TestDoc>;
50
+ }
51
+
52
+ describe('RxStorageInstanceSQLite', () => {
53
+ let storage: ReturnType<typeof getRxStorageSQLite>;
54
+ let instance: RxStorageInstanceSQLite<TestDoc>;
55
+
56
+ beforeEach(async () => {
57
+ const db = createMockSQLiteDatabase();
58
+ storage = getRxStorageSQLite(db);
59
+ instance = (await storage.createStorageInstance({
60
+ databaseInstanceToken: 'test-token',
61
+ databaseName: 'testdb',
62
+ collectionName: 'testcol',
63
+ schema: testSchema,
64
+ options: { database: db },
65
+ multiInstance: false,
66
+ devMode: false,
67
+ })) as RxStorageInstanceSQLite<TestDoc>;
68
+ });
69
+
70
+ afterEach(async () => {
71
+ await instance.close();
72
+ });
73
+
74
+ describe('bulkWrite', () => {
75
+ it('should insert a new document', async () => {
76
+ const doc = makeDoc({ id: 'doc1', name: 'Alice', age: 30 });
77
+ const result = await instance.bulkWrite(
78
+ [{ document: doc }],
79
+ 'test-insert',
80
+ );
81
+ expect(result.error).toHaveLength(0);
82
+
83
+ // Verify we can read it back
84
+ const found = await instance.findDocumentsById(['doc1'], false);
85
+ expect(found).toHaveLength(1);
86
+ expect(found[0].name).toBe('Alice');
87
+ });
88
+
89
+ it('should insert multiple documents', async () => {
90
+ const doc1 = makeDoc({ id: 'doc1', name: 'Alice', age: 30, _meta: { lwt: 1000 } });
91
+ const doc2 = makeDoc({ id: 'doc2', name: 'Bob', age: 25, _meta: { lwt: 1001 } });
92
+ const result = await instance.bulkWrite(
93
+ [{ document: doc1 }, { document: doc2 }],
94
+ 'test-multi-insert',
95
+ );
96
+ expect(result.error).toHaveLength(0);
97
+
98
+ const found = await instance.findDocumentsById(['doc1', 'doc2'], false);
99
+ expect(found).toHaveLength(2);
100
+ });
101
+
102
+ it('should update an existing document when previous matches', async () => {
103
+ const doc1 = makeDoc({ id: 'doc1', name: 'Alice', _rev: '1-aaa', _meta: { lwt: 1000 } });
104
+ await instance.bulkWrite([{ document: doc1 }], 'insert');
105
+
106
+ const updated = makeDoc({
107
+ id: 'doc1',
108
+ name: 'Alice Updated',
109
+ _rev: '2-bbb',
110
+ _meta: { lwt: 2000 },
111
+ });
112
+ const result = await instance.bulkWrite(
113
+ [{ previous: doc1, document: updated }],
114
+ 'update',
115
+ );
116
+ expect(result.error).toHaveLength(0);
117
+
118
+ const found = await instance.findDocumentsById(['doc1'], false);
119
+ expect(found).toHaveLength(1);
120
+ expect(found[0].name).toBe('Alice Updated');
121
+ expect(found[0]._rev).toBe('2-bbb');
122
+ });
123
+
124
+ it('should return a conflict error when previous rev does not match', async () => {
125
+ const doc1 = makeDoc({ id: 'doc1', name: 'Alice', _rev: '1-aaa', _meta: { lwt: 1000 } });
126
+ await instance.bulkWrite([{ document: doc1 }], 'insert');
127
+
128
+ const wrongPrevious = makeDoc({
129
+ id: 'doc1',
130
+ name: 'Alice',
131
+ _rev: '1-wrong',
132
+ _meta: { lwt: 1000 },
133
+ });
134
+ const updated = makeDoc({
135
+ id: 'doc1',
136
+ name: 'Alice Updated',
137
+ _rev: '2-bbb',
138
+ _meta: { lwt: 2000 },
139
+ });
140
+ const result = await instance.bulkWrite(
141
+ [{ previous: wrongPrevious, document: updated }],
142
+ 'conflict-update',
143
+ );
144
+ expect(result.error).toHaveLength(1);
145
+ expect(result.error[0].status).toBe(409);
146
+ });
147
+
148
+ it('should emit events on the change stream', async () => {
149
+ const events: any[] = [];
150
+ const sub = instance.changeStream().subscribe((eventBulk) => {
151
+ events.push(...eventBulk.events);
152
+ });
153
+
154
+ const doc = makeDoc({ id: 'doc1', name: 'Alice', _meta: { lwt: 1000 } });
155
+ await instance.bulkWrite([{ document: doc }], 'insert');
156
+
157
+ expect(events).toHaveLength(1);
158
+ expect(events[0].operation).toBe('INSERT');
159
+ expect(events[0].documentId).toBe('doc1');
160
+
161
+ sub.unsubscribe();
162
+ });
163
+ });
164
+
165
+ describe('findDocumentsById', () => {
166
+ it('should return empty array for non-existent ids', async () => {
167
+ const found = await instance.findDocumentsById(['nonexistent'], false);
168
+ expect(found).toHaveLength(0);
169
+ });
170
+
171
+ it('should not return deleted documents when withDeleted is false', async () => {
172
+ const doc = makeDoc({ id: 'doc1', _deleted: true, _meta: { lwt: 1000 } });
173
+ await instance.bulkWrite([{ document: doc }], 'insert-deleted');
174
+
175
+ const found = await instance.findDocumentsById(['doc1'], false);
176
+ expect(found).toHaveLength(0);
177
+ });
178
+
179
+ it('should return deleted documents when withDeleted is true', async () => {
180
+ const doc = makeDoc({ id: 'doc1', _deleted: true, _meta: { lwt: 1000 } });
181
+ await instance.bulkWrite([{ document: doc }], 'insert-deleted');
182
+
183
+ const found = await instance.findDocumentsById(['doc1'], true);
184
+ expect(found).toHaveLength(1);
185
+ });
186
+ });
187
+
188
+ describe('getChangedDocumentsSince', () => {
189
+ it('should return documents ordered by lwt', async () => {
190
+ const doc1 = makeDoc({ id: 'a', name: 'First', _meta: { lwt: 100 } });
191
+ const doc2 = makeDoc({ id: 'b', name: 'Second', _meta: { lwt: 200 } });
192
+ const doc3 = makeDoc({ id: 'c', name: 'Third', _meta: { lwt: 300 } });
193
+ await instance.bulkWrite(
194
+ [{ document: doc1 }, { document: doc2 }, { document: doc3 }],
195
+ 'insert-all',
196
+ );
197
+
198
+ const result = await instance.getChangedDocumentsSince(10);
199
+ expect(result.documents).toHaveLength(3);
200
+ expect(result.documents[0].name).toBe('First');
201
+ expect(result.documents[2].name).toBe('Third');
202
+ expect(result.checkpoint).toEqual({ id: 'c', lwt: 300 });
203
+ });
204
+
205
+ it('should respect the checkpoint for pagination', async () => {
206
+ const doc1 = makeDoc({ id: 'a', name: 'First', _meta: { lwt: 100 } });
207
+ const doc2 = makeDoc({ id: 'b', name: 'Second', _meta: { lwt: 200 } });
208
+ const doc3 = makeDoc({ id: 'c', name: 'Third', _meta: { lwt: 300 } });
209
+ await instance.bulkWrite(
210
+ [{ document: doc1 }, { document: doc2 }, { document: doc3 }],
211
+ 'insert-all',
212
+ );
213
+
214
+ const result = await instance.getChangedDocumentsSince(10, { id: 'a', lwt: 100 });
215
+ expect(result.documents).toHaveLength(2);
216
+ expect(result.documents[0].name).toBe('Second');
217
+ expect(result.documents[1].name).toBe('Third');
218
+ });
219
+ });
220
+
221
+ describe('cleanup', () => {
222
+ it('should remove deleted documents older than minimumDeletedTime', async () => {
223
+ const oldLwt = Date.now() - 100000;
224
+ const doc = makeDoc({
225
+ id: 'doc1',
226
+ _deleted: true,
227
+ _meta: { lwt: oldLwt },
228
+ });
229
+ await instance.bulkWrite([{ document: doc }], 'insert-deleted');
230
+
231
+ // Cleanup with a 1000ms threshold
232
+ const result = await instance.cleanup(1000);
233
+ expect(result).toBe(true);
234
+
235
+ // Document should be gone
236
+ const found = await instance.findDocumentsById(['doc1'], true);
237
+ expect(found).toHaveLength(0);
238
+ });
239
+ });
240
+
241
+ describe('remove', () => {
242
+ it('should drop the table and close', async () => {
243
+ const doc = makeDoc({ id: 'doc1', _meta: { lwt: 1000 } });
244
+ await instance.bulkWrite([{ document: doc }], 'insert');
245
+
246
+ await instance.remove();
247
+
248
+ // After remove, creating a new instance should have no data
249
+ const db = createMockSQLiteDatabase();
250
+ const newStorage = getRxStorageSQLite(db);
251
+ const newInstance = await newStorage.createStorageInstance({
252
+ databaseInstanceToken: 'test-token-2',
253
+ databaseName: 'testdb',
254
+ collectionName: 'testcol',
255
+ schema: testSchema,
256
+ options: { database: db },
257
+ multiInstance: false,
258
+ devMode: false,
259
+ });
260
+ const found = await newInstance.findDocumentsById(['doc1'], true);
261
+ expect(found).toHaveLength(0);
262
+ await newInstance.close();
263
+ });
264
+ });
265
+ });
@@ -0,0 +1,333 @@
1
+ import type {
2
+ BulkWriteRow,
3
+ EventBulk,
4
+ PreparedQuery,
5
+ RxDocumentData,
6
+ RxJsonSchema,
7
+ RxStorageBulkWriteResponse,
8
+ RxStorageChangeEvent,
9
+ RxStorageCountResult,
10
+ RxStorageDefaultCheckpoint,
11
+ RxStorageInstance,
12
+ RxStorageInstanceCreationParams,
13
+ RxStorageQueryResult,
14
+ StringKeys,
15
+ } from 'rxdb';
16
+ import { categorizeBulkWriteRows, getQueryMatcher, getSortComparator } from 'rxdb';
17
+ import { now, ensureNotFalsy, randomToken } from 'rxdb/plugins/utils';
18
+ import { Subject, type Observable } from 'rxjs';
19
+ import type { SQLiteDatabase, SQLiteStorageSettings } from './types';
20
+ import type { RxStorageSQLite } from './rx-storage-sqlite';
21
+ import { buildQuerySQL, buildCountSQL } from './mango-to-sql';
22
+
23
+ export interface SQLiteStorageInternals {
24
+ database: SQLiteDatabase;
25
+ tableName: string;
26
+ }
27
+
28
+ function sanitizeTableName(databaseName: string, collectionName: string, schemaVersion: number): string {
29
+ return `${databaseName}_${collectionName}_v${schemaVersion}`.replace(/[^a-zA-Z0-9_]/g, '_');
30
+ }
31
+
32
+ export class RxStorageInstanceSQLite<RxDocType>
33
+ implements RxStorageInstance<
34
+ RxDocType,
35
+ SQLiteStorageInternals,
36
+ SQLiteStorageSettings,
37
+ RxStorageDefaultCheckpoint
38
+ >
39
+ {
40
+ readonly databaseName: string;
41
+ readonly collectionName: string;
42
+ readonly schema: Readonly<RxJsonSchema<RxDocumentData<RxDocType>>>;
43
+ readonly internals: SQLiteStorageInternals;
44
+ readonly options: Readonly<SQLiteStorageSettings>;
45
+ readonly primaryPath: StringKeys<RxDocumentData<RxDocType>>;
46
+ private changes$ = new Subject<EventBulk<RxStorageChangeEvent<RxDocumentData<RxDocType>>, RxStorageDefaultCheckpoint>>();
47
+ private closed = false;
48
+
49
+ constructor(
50
+ readonly storage: RxStorageSQLite,
51
+ params: RxStorageInstanceCreationParams<RxDocType, SQLiteStorageSettings>,
52
+ private database: SQLiteDatabase,
53
+ ) {
54
+ this.databaseName = params.databaseName;
55
+ this.collectionName = params.collectionName;
56
+ this.schema = params.schema;
57
+ this.options = params.options;
58
+ this.primaryPath = (
59
+ typeof params.schema.primaryKey === 'string'
60
+ ? params.schema.primaryKey
61
+ : params.schema.primaryKey.key
62
+ ) as StringKeys<RxDocumentData<RxDocType>>;
63
+
64
+ const tableName = sanitizeTableName(params.databaseName, params.collectionName, params.schema.version);
65
+ this.internals = { database, tableName };
66
+
67
+ this.createTable();
68
+ }
69
+
70
+ /**
71
+ * Create the SQLite table for this collection if it does not exist.
72
+ * Stores documents as JSON with indexed columns for _deleted, _rev, _meta.lwt and the primary key.
73
+ */
74
+ private createTable(): void {
75
+ this.database.execSync(
76
+ `CREATE TABLE IF NOT EXISTS "${this.internals.tableName}" (` +
77
+ ` id TEXT PRIMARY KEY NOT NULL,` +
78
+ ` data TEXT NOT NULL,` +
79
+ ` _deleted INTEGER NOT NULL DEFAULT 0,` +
80
+ ` _meta_lwt REAL NOT NULL DEFAULT 0` +
81
+ `)`
82
+ );
83
+ }
84
+
85
+ /**
86
+ * Read documents from SQLite by their primary keys.
87
+ * Returns a Map<primaryKey, RxDocumentData> for use with categorizeBulkWriteRows.
88
+ */
89
+ private getDocsByIdMap(ids: string[]): Map<string, RxDocumentData<RxDocType>> {
90
+ const map = new Map<string, RxDocumentData<RxDocType>>();
91
+ if (ids.length === 0) return map;
92
+
93
+ // Batch query using IN clause
94
+ const placeholders = ids.map(() => '?').join(',');
95
+ const rows = this.database.getAllSync<{ id: string; data: string }>(
96
+ `SELECT id, data FROM "${this.internals.tableName}" WHERE id IN (${placeholders})`,
97
+ ids
98
+ );
99
+ for (const row of rows) {
100
+ const doc = JSON.parse(row.data) as RxDocumentData<RxDocType>;
101
+ map.set(row.id, doc);
102
+ }
103
+ return map;
104
+ }
105
+
106
+ async bulkWrite(
107
+ documentWrites: BulkWriteRow<RxDocType>[],
108
+ context: string,
109
+ ): Promise<RxStorageBulkWriteResponse<RxDocType>> {
110
+ if (this.closed) {
111
+ throw new Error('RxStorageInstanceSQLite: storage is closed');
112
+ }
113
+
114
+ // Collect all document IDs we need to check for conflicts
115
+ const ids = documentWrites.map(
116
+ (row) => (row.document as any)[this.primaryPath] as string
117
+ );
118
+ const docsInDb = this.getDocsByIdMap(ids);
119
+
120
+ // Use RxDB's built-in categorization for conflict detection and event generation
121
+ const categorized = categorizeBulkWriteRows<RxDocType>(
122
+ this,
123
+ this.primaryPath as any,
124
+ docsInDb,
125
+ documentWrites,
126
+ context,
127
+ );
128
+
129
+ const error = categorized.errors;
130
+
131
+ // Perform inserts
132
+ for (const row of categorized.bulkInsertDocs) {
133
+ const doc = row.document;
134
+ const docId = (doc as any)[this.primaryPath] as string;
135
+ const json = JSON.stringify(doc);
136
+ this.database.runSync(
137
+ `INSERT OR REPLACE INTO "${this.internals.tableName}" (id, data, _deleted, _meta_lwt) VALUES (?, ?, ?, ?)`,
138
+ [docId, json, doc._deleted ? 1 : 0, doc._meta.lwt]
139
+ );
140
+ }
141
+
142
+ // Perform updates
143
+ for (const row of categorized.bulkUpdateDocs) {
144
+ const doc = row.document;
145
+ const docId = (doc as any)[this.primaryPath] as string;
146
+ const json = JSON.stringify(doc);
147
+ this.database.runSync(
148
+ `UPDATE "${this.internals.tableName}" SET data = ?, _deleted = ?, _meta_lwt = ? WHERE id = ?`,
149
+ [json, doc._deleted ? 1 : 0, doc._meta.lwt, docId]
150
+ );
151
+ }
152
+
153
+ // Emit events for change stream
154
+ if (categorized.eventBulk.events.length > 0) {
155
+ const lastState = ensureNotFalsy(categorized.newestRow).document;
156
+ categorized.eventBulk.checkpoint = {
157
+ id: (lastState as any)[this.primaryPath],
158
+ lwt: lastState._meta.lwt,
159
+ };
160
+ this.changes$.next(categorized.eventBulk);
161
+ }
162
+
163
+ return { error };
164
+ }
165
+
166
+ async findDocumentsById(
167
+ ids: string[],
168
+ withDeleted: boolean,
169
+ ): Promise<RxDocumentData<RxDocType>[]> {
170
+ if (ids.length === 0) return [];
171
+
172
+ const placeholders = ids.map(() => '?').join(',');
173
+ let sql = `SELECT data FROM "${this.internals.tableName}" WHERE id IN (${placeholders})`;
174
+ if (!withDeleted) {
175
+ sql += ' AND _deleted = 0';
176
+ }
177
+ const rows = this.database.getAllSync<{ data: string }>(sql, ids);
178
+ return rows.map((row) => JSON.parse(row.data) as RxDocumentData<RxDocType>);
179
+ }
180
+
181
+ async query(
182
+ preparedQuery: PreparedQuery<RxDocType>,
183
+ ): Promise<RxStorageQueryResult<RxDocType>> {
184
+ const { query } = preparedQuery;
185
+
186
+ // Try the Mango-to-SQL path first. If the SQLite database supports
187
+ // json_extract (all modern versions do), this is the fast path.
188
+ // Fall back to in-memory filtering if the SQL query fails.
189
+ try {
190
+ const { sql, params } = buildQuerySQL(this.internals.tableName, query);
191
+ const rows = this.database.getAllSync<{ data: string }>(sql, params);
192
+ const documents = rows.map(
193
+ (row) => JSON.parse(row.data) as RxDocumentData<RxDocType>,
194
+ );
195
+ return { documents };
196
+ } catch {
197
+ // Fallback: load all non-deleted docs and filter in-memory
198
+ return this.queryInMemory(query);
199
+ }
200
+ }
201
+
202
+ async count(
203
+ preparedQuery: PreparedQuery<RxDocType>,
204
+ ): Promise<RxStorageCountResult> {
205
+ const { query } = preparedQuery;
206
+
207
+ // Try the SQL COUNT path first.
208
+ try {
209
+ const { sql, params } = buildCountSQL(this.internals.tableName, query);
210
+ const rows = this.database.getAllSync<{ count: number }>(sql, params);
211
+ if (rows.length > 0 && typeof rows[0].count === 'number') {
212
+ return { count: rows[0].count, mode: 'fast' };
213
+ }
214
+ } catch {
215
+ // fall through to in-memory
216
+ }
217
+
218
+ // Fallback: do a full query and count the results
219
+ const result = await this.queryInMemory(query);
220
+ return { count: result.documents.length, mode: 'slow' };
221
+ }
222
+
223
+ /**
224
+ * In-memory query fallback using RxDB's built-in query matcher and sort comparator.
225
+ * Used when the Mango-to-SQL translation fails (e.g., unsupported operators).
226
+ */
227
+ private async queryInMemory(
228
+ query: PreparedQuery<RxDocType>['query'],
229
+ ): Promise<RxStorageQueryResult<RxDocType>> {
230
+ const rows = this.database.getAllSync<{ data: string }>(
231
+ `SELECT data FROM "${this.internals.tableName}" WHERE _deleted = 0`,
232
+ );
233
+
234
+ let documents = rows.map(
235
+ (row) => JSON.parse(row.data) as RxDocumentData<RxDocType>,
236
+ );
237
+
238
+ const queryMatcher = getQueryMatcher(this.schema, query);
239
+ documents = documents.filter((doc) => queryMatcher(doc));
240
+
241
+ const sortComparator = getSortComparator(this.schema, query);
242
+ documents.sort(sortComparator);
243
+
244
+ const skip = query.skip ?? 0;
245
+ const limit = query.limit ?? Infinity;
246
+ documents = documents.slice(skip, skip + limit);
247
+
248
+ return { documents };
249
+ }
250
+
251
+ getAttachmentData(
252
+ _documentId: string,
253
+ _attachmentId: string,
254
+ _digest: string,
255
+ ): Promise<string> {
256
+ throw new Error('Attachments not supported by SQLite storage');
257
+ }
258
+
259
+ async getChangedDocumentsSince(
260
+ limit: number,
261
+ checkpoint?: RxStorageDefaultCheckpoint,
262
+ ): Promise<{
263
+ documents: RxDocumentData<RxDocType>[];
264
+ checkpoint: RxStorageDefaultCheckpoint;
265
+ }> {
266
+ let sql: string;
267
+ let params: any[];
268
+
269
+ if (checkpoint) {
270
+ // Get documents changed after the checkpoint.
271
+ // Order by lwt then by id to break ties deterministically.
272
+ sql = `SELECT data FROM "${this.internals.tableName}" ` +
273
+ `WHERE (_meta_lwt > ?) OR (_meta_lwt = ? AND id > ?) ` +
274
+ `ORDER BY _meta_lwt ASC, id ASC LIMIT ?`;
275
+ params = [checkpoint.lwt, checkpoint.lwt, checkpoint.id, limit];
276
+ } else {
277
+ sql = `SELECT data FROM "${this.internals.tableName}" ` +
278
+ `ORDER BY _meta_lwt ASC, id ASC LIMIT ?`;
279
+ params = [limit];
280
+ }
281
+
282
+ const rows = this.database.getAllSync<{ data: string }>(sql, params);
283
+ const documents = rows.map(
284
+ (row) => JSON.parse(row.data) as RxDocumentData<RxDocType>
285
+ );
286
+
287
+ const lastDoc = documents.length > 0 ? documents[documents.length - 1] : undefined;
288
+ const newCheckpoint: RxStorageDefaultCheckpoint = lastDoc
289
+ ? {
290
+ id: (lastDoc as any)[this.primaryPath],
291
+ lwt: lastDoc._meta.lwt,
292
+ }
293
+ : checkpoint ?? { id: '', lwt: 0 };
294
+
295
+ return {
296
+ documents,
297
+ checkpoint: newCheckpoint,
298
+ };
299
+ }
300
+
301
+ changeStream(): Observable<EventBulk<RxStorageChangeEvent<RxDocumentData<RxDocType>>, RxStorageDefaultCheckpoint>> {
302
+ return this.changes$.asObservable();
303
+ }
304
+
305
+ async cleanup(minimumDeletedTime: number): Promise<boolean> {
306
+ const maxDeletionTime = now() - minimumDeletedTime;
307
+ this.database.runSync(
308
+ `DELETE FROM "${this.internals.tableName}" WHERE _deleted = 1 AND _meta_lwt < ?`,
309
+ [maxDeletionTime]
310
+ );
311
+ return true;
312
+ }
313
+
314
+ async close(): Promise<void> {
315
+ if (this.closed) return;
316
+ this.closed = true;
317
+ this.changes$.complete();
318
+ }
319
+
320
+ async remove(): Promise<void> {
321
+ this.database.execSync(`DROP TABLE IF EXISTS "${this.internals.tableName}"`);
322
+ await this.close();
323
+ }
324
+ }
325
+
326
+ export async function createSQLiteStorageInstance<RxDocType>(
327
+ storage: RxStorageSQLite,
328
+ params: RxStorageInstanceCreationParams<RxDocType, SQLiteStorageSettings>,
329
+ database: SQLiteDatabase,
330
+ ): Promise<RxStorageInstanceSQLite<RxDocType>> {
331
+ const instance = new RxStorageInstanceSQLite(storage, params, database);
332
+ return instance;
333
+ }
package/src/types.ts ADDED
@@ -0,0 +1,9 @@
1
+ export interface SQLiteDatabase {
2
+ execSync(source: string): void;
3
+ getAllSync<T>(source: string, params?: any[]): T[];
4
+ runSync(source: string, params?: any[]): { changes: number; lastInsertRowId: number };
5
+ }
6
+
7
+ export interface SQLiteStorageSettings {
8
+ database: SQLiteDatabase;
9
+ }