@offlinejs/storage-sqlite 0.1.0 → 0.1.1
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 +27 -1
- package/dist/index.js +159 -20
- package/dist/index.js.map +1 -1
- package/package.json +9 -1
package/dist/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ declare class SQLiteStorageAdapter implements IndexableStorageAdapter {
|
|
|
14
14
|
readonly contractVersion = 1;
|
|
15
15
|
readonly capabilities: {
|
|
16
16
|
readonly indexes: true;
|
|
17
|
+
readonly bulkWrites: true;
|
|
17
18
|
readonly migrations: true;
|
|
18
19
|
readonly persistence: "durable";
|
|
19
20
|
readonly transactions: "best-effort";
|
|
@@ -25,6 +26,7 @@ declare class SQLiteStorageAdapter implements IndexableStorageAdapter {
|
|
|
25
26
|
initialize(): Promise<void>;
|
|
26
27
|
get<TRecord extends EntityRecord>(collection: string, id: string): Promise<TRecord | null>;
|
|
27
28
|
set<TRecord extends EntityRecord>(collection: string, value: TRecord): Promise<void>;
|
|
29
|
+
setMany<TRecord extends EntityRecord>(collection: string, values: TRecord[]): Promise<void>;
|
|
28
30
|
delete(collection: string, id: string): Promise<void>;
|
|
29
31
|
find<TRecord extends EntityRecord>(collection: string, query?: QueryOptions<TRecord>): Promise<TRecord[]>;
|
|
30
32
|
clear(collection?: string): Promise<void>;
|
|
@@ -33,11 +35,35 @@ declare class SQLiteStorageAdapter implements IndexableStorageAdapter {
|
|
|
33
35
|
createIndex<TRecord extends EntityRecord>(definition: IndexDefinition<TRecord>): Promise<void>;
|
|
34
36
|
dropIndex(collection: string, name: string): Promise<void>;
|
|
35
37
|
listIndexes(collection?: string): Promise<IndexDefinition[]>;
|
|
38
|
+
/**
|
|
39
|
+
* Push equality filters + order/limit into SQL when the whole query is engine-safe.
|
|
40
|
+
* Complex search/operators stay in JS `applyQuery`.
|
|
41
|
+
*/
|
|
42
|
+
private buildPushedFindSQL;
|
|
36
43
|
private findViaIndex;
|
|
37
44
|
private assertUniqueIndexes;
|
|
38
45
|
private writeIndexEntries;
|
|
39
46
|
private removeIndexEntries;
|
|
40
47
|
}
|
|
41
48
|
declare const createSQLiteStorage: (options: SQLiteStorageOptions) => SQLiteStorageAdapter;
|
|
49
|
+
interface BetterSqlite3Database {
|
|
50
|
+
prepare(sql: string): {
|
|
51
|
+
run: (...params: unknown[]) => unknown;
|
|
52
|
+
all: (...params: unknown[]) => unknown[];
|
|
53
|
+
};
|
|
54
|
+
exec(sql: string): unknown;
|
|
55
|
+
transaction<TValue>(fn: () => TValue): () => TValue;
|
|
56
|
+
close?: () => void;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Wrap a better-sqlite3 Database as an OfflineJS SQLiteDriver.
|
|
60
|
+
* Install optionally: `pnpm add better-sqlite3`
|
|
61
|
+
*/
|
|
62
|
+
declare const createBetterSqlite3Driver: (database: BetterSqlite3Database) => SQLiteDriver;
|
|
63
|
+
/**
|
|
64
|
+
* Create a sync-friendly better-sqlite3 driver that runs OfflineJS SQL statements
|
|
65
|
+
* without nesting async work inside native transactions. Prefer this helper.
|
|
66
|
+
*/
|
|
67
|
+
declare const createBetterSqlite3DriverAsync: (database: BetterSqlite3Database) => SQLiteDriver;
|
|
42
68
|
|
|
43
|
-
export { type SQLiteDriver, SQLiteStorageAdapter, type SQLiteStorageOptions, createSQLiteStorage };
|
|
69
|
+
export { type BetterSqlite3Database, type SQLiteDriver, SQLiteStorageAdapter, type SQLiteStorageOptions, createBetterSqlite3Driver, createBetterSqlite3DriverAsync, createSQLiteStorage };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { STORAGE_ADAPTER_CONTRACT_VERSION } from '@offlinejs/types';
|
|
2
|
-
import { applyQuery, findMatchingIndex, getEqualityFilterLookups, serializeCompoundIndexValue, readIndexFields } from '@offlinejs/utils';
|
|
2
|
+
import { applyQuery, findMatchingIndex, getEqualityFilterLookups, serializeCompoundIndexValue, indexSatisfiesQuery, queryPageWindow, readIndexFields } from '@offlinejs/utils';
|
|
3
3
|
|
|
4
4
|
// src/index.ts
|
|
5
5
|
var SQLiteStorageAdapter = class {
|
|
@@ -7,6 +7,7 @@ var SQLiteStorageAdapter = class {
|
|
|
7
7
|
contractVersion = STORAGE_ADAPTER_CONTRACT_VERSION;
|
|
8
8
|
capabilities = {
|
|
9
9
|
indexes: true,
|
|
10
|
+
bulkWrites: true,
|
|
10
11
|
migrations: true,
|
|
11
12
|
persistence: "durable",
|
|
12
13
|
transactions: "best-effort"
|
|
@@ -45,17 +46,37 @@ var SQLiteStorageAdapter = class {
|
|
|
45
46
|
return rows[0] ? JSON.parse(rows[0].value) : null;
|
|
46
47
|
}
|
|
47
48
|
async set(collection, value) {
|
|
49
|
+
await this.setMany(collection, [value]);
|
|
50
|
+
}
|
|
51
|
+
async setMany(collection, values) {
|
|
52
|
+
if (values.length === 0) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
48
55
|
await this.initialize();
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
const byId = /* @__PURE__ */ new Map();
|
|
57
|
+
for (const value of values) {
|
|
58
|
+
byId.set(value.id, value);
|
|
52
59
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
const records = [...byId.values()];
|
|
61
|
+
const writeBatch = async () => {
|
|
62
|
+
for (const value of records) {
|
|
63
|
+
const previous = await this.get(collection, value.id);
|
|
64
|
+
if (previous) {
|
|
65
|
+
await this.removeIndexEntries(collection, previous);
|
|
66
|
+
}
|
|
67
|
+
await this.assertUniqueIndexes(collection, value, previous?.id);
|
|
68
|
+
await this.driver.execute(
|
|
69
|
+
`INSERT OR REPLACE INTO ${this.tableName} (collection, id, value) VALUES (?, ?, ?)`,
|
|
70
|
+
[collection, value.id, JSON.stringify(value)]
|
|
71
|
+
);
|
|
72
|
+
await this.writeIndexEntries(collection, value);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
if (this.driver.transaction) {
|
|
76
|
+
await this.driver.transaction(writeBatch);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
await writeBatch();
|
|
59
80
|
}
|
|
60
81
|
async delete(collection, id) {
|
|
61
82
|
await this.initialize();
|
|
@@ -71,7 +92,20 @@ var SQLiteStorageAdapter = class {
|
|
|
71
92
|
async find(collection, query) {
|
|
72
93
|
await this.initialize();
|
|
73
94
|
const indexed = await this.findViaIndex(collection, query);
|
|
74
|
-
|
|
95
|
+
if (indexed?.complete) {
|
|
96
|
+
return indexed.records;
|
|
97
|
+
}
|
|
98
|
+
if (!indexed) {
|
|
99
|
+
const pushed = this.buildPushedFindSQL(collection, query);
|
|
100
|
+
if (pushed) {
|
|
101
|
+
try {
|
|
102
|
+
const rows = await this.driver.query(pushed.sql, pushed.params);
|
|
103
|
+
return rows.map((row) => JSON.parse(row.value));
|
|
104
|
+
} catch {
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const records = indexed?.records ?? (await this.driver.query(
|
|
75
109
|
`SELECT value FROM ${this.tableName} WHERE collection = ?`,
|
|
76
110
|
[collection]
|
|
77
111
|
)).map((row) => JSON.parse(row.value));
|
|
@@ -148,6 +182,51 @@ var SQLiteStorageAdapter = class {
|
|
|
148
182
|
) : await this.driver.query(`SELECT value FROM ${this.tableName}_indexes`);
|
|
149
183
|
return rows.map((row) => JSON.parse(row.value));
|
|
150
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Push equality filters + order/limit into SQL when the whole query is engine-safe.
|
|
187
|
+
* Complex search/operators stay in JS `applyQuery`.
|
|
188
|
+
*/
|
|
189
|
+
buildPushedFindSQL(collection, query) {
|
|
190
|
+
if (!query || query.search) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
const params = [collection];
|
|
194
|
+
const clauses = [`collection = ?`];
|
|
195
|
+
if (query.filters) {
|
|
196
|
+
for (const [field, expected] of Object.entries(query.filters)) {
|
|
197
|
+
if (expected === void 0) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
if (Array.isArray(expected) || expected === null || typeof expected !== "object") {
|
|
201
|
+
clauses.push(`json_extract(value, '$.${field}') = ?`);
|
|
202
|
+
params.push(expected);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if ("eq" in expected && Object.keys(expected).length === 1 && expected.eq !== void 0) {
|
|
206
|
+
clauses.push(`json_extract(value, '$.${field}') = ?`);
|
|
207
|
+
params.push(expected.eq);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
let sql = `SELECT value FROM ${this.tableName} WHERE ${clauses.join(" AND ")}`;
|
|
214
|
+
if (query.orderBy) {
|
|
215
|
+
sql += ` ORDER BY json_extract(value, '$.${String(query.orderBy)}') ${query.sort === "desc" ? "DESC" : "ASC"}`;
|
|
216
|
+
}
|
|
217
|
+
if (query.limit !== void 0) {
|
|
218
|
+
sql += ` LIMIT ?`;
|
|
219
|
+
params.push(query.limit);
|
|
220
|
+
}
|
|
221
|
+
if (query.offset !== void 0 && query.offset > 0) {
|
|
222
|
+
if (query.limit === void 0) {
|
|
223
|
+
sql += ` LIMIT -1`;
|
|
224
|
+
}
|
|
225
|
+
sql += ` OFFSET ?`;
|
|
226
|
+
params.push(query.offset);
|
|
227
|
+
}
|
|
228
|
+
return { sql, params };
|
|
229
|
+
}
|
|
151
230
|
async findViaIndex(collection, query) {
|
|
152
231
|
const match = findMatchingIndex(
|
|
153
232
|
await this.listIndexes(collection),
|
|
@@ -157,18 +236,27 @@ var SQLiteStorageAdapter = class {
|
|
|
157
236
|
return null;
|
|
158
237
|
}
|
|
159
238
|
const valueKey = serializeCompoundIndexValue(match.values);
|
|
160
|
-
|
|
239
|
+
let rows = await this.driver.query(
|
|
161
240
|
`SELECT record_id FROM ${this.tableName}_index_entries WHERE collection = ? AND index_name = ? AND value_key = ?`,
|
|
162
241
|
[collection, match.index.name, valueKey]
|
|
163
242
|
);
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
243
|
+
const complete = indexSatisfiesQuery(match, query);
|
|
244
|
+
if (complete) {
|
|
245
|
+
const { offset, limit } = queryPageWindow(query);
|
|
246
|
+
rows = limit === void 0 ? rows.slice(offset) : rows.slice(offset, offset + limit);
|
|
247
|
+
}
|
|
248
|
+
if (rows.length === 0) {
|
|
249
|
+
return { complete, records: [] };
|
|
170
250
|
}
|
|
171
|
-
|
|
251
|
+
const placeholders = rows.map(() => "?").join(", ");
|
|
252
|
+
const values = await this.driver.query(
|
|
253
|
+
`SELECT value FROM ${this.tableName} WHERE collection = ? AND id IN (${placeholders})`,
|
|
254
|
+
[collection, ...rows.map((row) => row.record_id)]
|
|
255
|
+
);
|
|
256
|
+
return {
|
|
257
|
+
complete,
|
|
258
|
+
records: values.map((row) => JSON.parse(row.value))
|
|
259
|
+
};
|
|
172
260
|
}
|
|
173
261
|
async assertUniqueIndexes(collection, record, ignoreId) {
|
|
174
262
|
for (const definition of await this.listIndexes(collection)) {
|
|
@@ -206,7 +294,58 @@ var SQLiteStorageAdapter = class {
|
|
|
206
294
|
}
|
|
207
295
|
};
|
|
208
296
|
var createSQLiteStorage = (options) => new SQLiteStorageAdapter(options);
|
|
297
|
+
var createBetterSqlite3Driver = (database) => ({
|
|
298
|
+
async execute(sql, params = []) {
|
|
299
|
+
if (/^\s*CREATE\b/i.test(sql) && !params.length) {
|
|
300
|
+
database.exec(sql);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
database.prepare(sql).run(...params);
|
|
304
|
+
},
|
|
305
|
+
async query(sql, params = []) {
|
|
306
|
+
return database.prepare(sql).all(...params);
|
|
307
|
+
},
|
|
308
|
+
async transaction(run) {
|
|
309
|
+
const wrapped = database.transaction(() => {
|
|
310
|
+
let result;
|
|
311
|
+
let error;
|
|
312
|
+
let settled = false;
|
|
313
|
+
void Promise.resolve(run()).then((value) => {
|
|
314
|
+
result = value;
|
|
315
|
+
settled = true;
|
|
316
|
+
}).catch((reason) => {
|
|
317
|
+
error = reason;
|
|
318
|
+
settled = true;
|
|
319
|
+
});
|
|
320
|
+
if (!settled) {
|
|
321
|
+
throw new Error(
|
|
322
|
+
"better-sqlite3 transactions require synchronous work; use await outside transaction or sync APIs"
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
if (error) {
|
|
326
|
+
throw error;
|
|
327
|
+
}
|
|
328
|
+
return result;
|
|
329
|
+
});
|
|
330
|
+
return wrapped();
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
var createBetterSqlite3DriverAsync = (database) => ({
|
|
334
|
+
async execute(sql, params = []) {
|
|
335
|
+
if (/^\s*CREATE\b/i.test(sql) && !params.length) {
|
|
336
|
+
database.exec(sql);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
database.prepare(sql).run(...params);
|
|
340
|
+
},
|
|
341
|
+
async query(sql, params = []) {
|
|
342
|
+
return database.prepare(sql).all(...params);
|
|
343
|
+
},
|
|
344
|
+
async transaction(run) {
|
|
345
|
+
return run();
|
|
346
|
+
}
|
|
347
|
+
});
|
|
209
348
|
|
|
210
|
-
export { SQLiteStorageAdapter, createSQLiteStorage };
|
|
349
|
+
export { SQLiteStorageAdapter, createBetterSqlite3Driver, createBetterSqlite3DriverAsync, createSQLiteStorage };
|
|
211
350
|
//# sourceMappingURL=index.js.map
|
|
212
351
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import {\n STORAGE_ADAPTER_CONTRACT_VERSION,\n type EntityRecord,\n type IndexDefinition,\n type IndexableStorageAdapter,\n type QueryOptions,\n type StorageMigration,\n type TransactionStore\n} from \"@offlinejs/types\";\nimport {\n applyQuery,\n findMatchingIndex,\n getEqualityFilterLookups,\n readIndexFields,\n serializeCompoundIndexValue\n} from \"@offlinejs/utils\";\n\nexport interface SQLiteDriver {\n execute(sql: string, params?: unknown[]): Promise<void>;\n query<TValue = unknown>(sql: string, params?: unknown[]): Promise<TValue[]>;\n transaction?<TValue>(run: () => Promise<TValue>): Promise<TValue>;\n}\n\nexport interface SQLiteStorageOptions {\n driver: SQLiteDriver;\n tableName?: string;\n}\n\ninterface SQLiteRecordRow {\n collection: string;\n id: string;\n value: string;\n}\n\nexport class SQLiteStorageAdapter implements IndexableStorageAdapter {\n readonly name = \"sqlite\";\n readonly contractVersion = STORAGE_ADAPTER_CONTRACT_VERSION;\n readonly capabilities = {\n indexes: true,\n migrations: true,\n persistence: \"durable\",\n transactions: \"best-effort\"\n } as const;\n\n private readonly driver: SQLiteDriver;\n private readonly tableName: string;\n private initialized = false;\n\n constructor(options: SQLiteStorageOptions) {\n this.driver = options.driver;\n this.tableName = options.tableName ?? \"offlinejs_records\";\n }\n\n async initialize(): Promise<void> {\n if (this.initialized) {\n return;\n }\n\n await this.driver.execute(\n `CREATE TABLE IF NOT EXISTS ${this.tableName} (collection TEXT NOT NULL, id TEXT NOT NULL, value TEXT NOT NULL, PRIMARY KEY (collection, id))`\n );\n await this.driver.execute(\n `CREATE TABLE IF NOT EXISTS ${this.tableName}_indexes (collection TEXT NOT NULL, name TEXT NOT NULL, value TEXT NOT NULL, PRIMARY KEY (collection, name))`\n );\n await this.driver.execute(\n `CREATE TABLE IF NOT EXISTS ${this.tableName}_index_entries (collection TEXT NOT NULL, index_name TEXT NOT NULL, value_key TEXT NOT NULL, record_id TEXT NOT NULL, PRIMARY KEY (collection, index_name, value_key, record_id))`\n );\n await this.driver.execute(\n `CREATE INDEX IF NOT EXISTS ${this.tableName}_index_entries_lookup ON ${this.tableName}_index_entries (collection, index_name, value_key)`\n );\n this.initialized = true;\n }\n\n async get<TRecord extends EntityRecord>(collection: string, id: string): Promise<TRecord | null> {\n await this.initialize();\n const rows = await this.driver.query<SQLiteRecordRow>(\n `SELECT value FROM ${this.tableName} WHERE collection = ? AND id = ? LIMIT 1`,\n [collection, id]\n );\n\n return rows[0] ? (JSON.parse(rows[0].value) as TRecord) : null;\n }\n\n async set<TRecord extends EntityRecord>(collection: string, value: TRecord): Promise<void> {\n await this.initialize();\n const previous = await this.get(collection, value.id);\n if (previous) {\n await this.removeIndexEntries(collection, previous);\n }\n\n await this.assertUniqueIndexes(collection, value, previous?.id);\n await this.driver.execute(\n `INSERT OR REPLACE INTO ${this.tableName} (collection, id, value) VALUES (?, ?, ?)`,\n [collection, value.id, JSON.stringify(value)]\n );\n await this.writeIndexEntries(collection, value);\n }\n\n async delete(collection: string, id: string): Promise<void> {\n await this.initialize();\n const previous = await this.get(collection, id);\n if (previous) {\n await this.removeIndexEntries(collection, previous);\n }\n await this.driver.execute(`DELETE FROM ${this.tableName} WHERE collection = ? AND id = ?`, [\n collection,\n id\n ]);\n }\n\n async find<TRecord extends EntityRecord>(\n collection: string,\n query?: QueryOptions<TRecord>\n ): Promise<TRecord[]> {\n await this.initialize();\n const indexed = await this.findViaIndex<TRecord>(collection, query);\n const records =\n indexed ??\n (\n await this.driver.query<SQLiteRecordRow>(\n `SELECT value FROM ${this.tableName} WHERE collection = ?`,\n [collection]\n )\n ).map((row) => JSON.parse(row.value) as TRecord);\n\n return applyQuery(records, query);\n }\n\n async clear(collection?: string): Promise<void> {\n await this.initialize();\n\n if (collection) {\n await this.driver.execute(`DELETE FROM ${this.tableName} WHERE collection = ?`, [collection]);\n await this.driver.execute(`DELETE FROM ${this.tableName}_indexes WHERE collection = ?`, [\n collection\n ]);\n await this.driver.execute(\n `DELETE FROM ${this.tableName}_index_entries WHERE collection = ?`,\n [collection]\n );\n return;\n }\n\n await this.driver.execute(`DELETE FROM ${this.tableName}`);\n await this.driver.execute(`DELETE FROM ${this.tableName}_indexes`);\n await this.driver.execute(`DELETE FROM ${this.tableName}_index_entries`);\n }\n\n async transaction<TValue>(\n _scope: string[],\n run: (store: TransactionStore) => Promise<TValue>\n ): Promise<TValue> {\n if (this.driver.transaction) {\n return this.driver.transaction(() => run(this));\n }\n\n return run(this);\n }\n\n async migrate(migrations: StorageMigration[]): Promise<void> {\n await this.initialize();\n const applied = new Set(\n (await this.find<EntityRecord>(\"__migrations\")).map((record) => record.id)\n );\n\n for (const migration of migrations) {\n if (applied.has(migration.name)) {\n continue;\n }\n\n await migration.up(this);\n await this.set(\"__migrations\", { id: migration.name, appliedAt: Date.now() });\n }\n }\n\n async createIndex<TRecord extends EntityRecord>(\n definition: IndexDefinition<TRecord>\n ): Promise<void> {\n await this.initialize();\n await this.driver.execute(\n `INSERT OR REPLACE INTO ${this.tableName}_indexes (collection, name, value) VALUES (?, ?, ?)`,\n [definition.collection, definition.name, JSON.stringify(definition)]\n );\n\n const rows = await this.driver.query<SQLiteRecordRow>(\n `SELECT value FROM ${this.tableName} WHERE collection = ?`,\n [definition.collection]\n );\n\n for (const row of rows) {\n const record = JSON.parse(row.value) as EntityRecord;\n await this.assertUniqueIndexes(definition.collection, record);\n await this.writeIndexEntries(definition.collection, record, [definition as IndexDefinition]);\n }\n }\n\n async dropIndex(collection: string, name: string): Promise<void> {\n await this.initialize();\n await this.driver.execute(\n `DELETE FROM ${this.tableName}_indexes WHERE collection = ? AND name = ?`,\n [collection, name]\n );\n await this.driver.execute(\n `DELETE FROM ${this.tableName}_index_entries WHERE collection = ? AND index_name = ?`,\n [collection, name]\n );\n }\n\n async listIndexes(collection?: string): Promise<IndexDefinition[]> {\n await this.initialize();\n const rows = collection\n ? await this.driver.query<{ value: string }>(\n `SELECT value FROM ${this.tableName}_indexes WHERE collection = ?`,\n [collection]\n )\n : await this.driver.query<{ value: string }>(`SELECT value FROM ${this.tableName}_indexes`);\n\n return rows.map((row) => JSON.parse(row.value) as IndexDefinition);\n }\n\n private async findViaIndex<TRecord extends EntityRecord>(\n collection: string,\n query?: QueryOptions<TRecord>\n ): Promise<TRecord[] | null> {\n const match = findMatchingIndex(\n await this.listIndexes(collection),\n getEqualityFilterLookups(query?.filters)\n );\n\n if (!match) {\n return null;\n }\n\n const valueKey = serializeCompoundIndexValue(match.values);\n const rows = await this.driver.query<{ record_id: string }>(\n `SELECT record_id FROM ${this.tableName}_index_entries WHERE collection = ? AND index_name = ? AND value_key = ?`,\n [collection, match.index.name, valueKey]\n );\n const records: TRecord[] = [];\n\n for (const row of rows) {\n const record = await this.get<TRecord>(collection, row.record_id);\n if (record) {\n records.push(record);\n }\n }\n\n return records;\n }\n\n private async assertUniqueIndexes(\n collection: string,\n record: EntityRecord,\n ignoreId?: string\n ): Promise<void> {\n for (const definition of await this.listIndexes(collection)) {\n if (!definition.unique) {\n continue;\n }\n\n const valueKey = serializeCompoundIndexValue(readIndexFields(record, definition.fields));\n const rows = await this.driver.query<{ record_id: string }>(\n `SELECT record_id FROM ${this.tableName}_index_entries WHERE collection = ? AND index_name = ? AND value_key = ?`,\n [collection, definition.name, valueKey]\n );\n\n if (rows.some((row) => row.record_id !== record.id && row.record_id !== ignoreId)) {\n throw new Error(`Unique index \"${definition.name}\" violated for ${collection}`);\n }\n }\n }\n\n private async writeIndexEntries(\n collection: string,\n record: EntityRecord,\n definitions?: IndexDefinition[]\n ): Promise<void> {\n const indexes = definitions ?? (await this.listIndexes(collection));\n\n for (const definition of indexes) {\n const valueKey = serializeCompoundIndexValue(readIndexFields(record, definition.fields));\n await this.driver.execute(\n `INSERT OR REPLACE INTO ${this.tableName}_index_entries (collection, index_name, value_key, record_id) VALUES (?, ?, ?, ?)`,\n [collection, definition.name, valueKey, record.id]\n );\n }\n }\n\n private async removeIndexEntries(collection: string, record: EntityRecord): Promise<void> {\n for (const definition of await this.listIndexes(collection)) {\n const valueKey = serializeCompoundIndexValue(readIndexFields(record, definition.fields));\n await this.driver.execute(\n `DELETE FROM ${this.tableName}_index_entries WHERE collection = ? AND index_name = ? AND value_key = ? AND record_id = ?`,\n [collection, definition.name, valueKey, record.id]\n );\n }\n }\n}\n\nexport const createSQLiteStorage = (options: SQLiteStorageOptions): SQLiteStorageAdapter =>\n new SQLiteStorageAdapter(options);\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import {\n STORAGE_ADAPTER_CONTRACT_VERSION,\n type EntityRecord,\n type IndexDefinition,\n type IndexableStorageAdapter,\n type QueryOptions,\n type StorageMigration,\n type TransactionStore\n} from \"@offlinejs/types\";\nimport {\n applyQuery,\n findMatchingIndex,\n getEqualityFilterLookups,\n indexSatisfiesQuery,\n queryPageWindow,\n readIndexFields,\n serializeCompoundIndexValue\n} from \"@offlinejs/utils\";\n\nexport interface SQLiteDriver {\n execute(sql: string, params?: unknown[]): Promise<void>;\n query<TValue = unknown>(sql: string, params?: unknown[]): Promise<TValue[]>;\n transaction?<TValue>(run: () => Promise<TValue>): Promise<TValue>;\n}\n\nexport interface SQLiteStorageOptions {\n driver: SQLiteDriver;\n tableName?: string;\n}\n\ninterface SQLiteRecordRow {\n collection: string;\n id: string;\n value: string;\n}\n\nexport class SQLiteStorageAdapter implements IndexableStorageAdapter {\n readonly name = \"sqlite\";\n readonly contractVersion = STORAGE_ADAPTER_CONTRACT_VERSION;\n readonly capabilities = {\n indexes: true,\n bulkWrites: true,\n migrations: true,\n persistence: \"durable\",\n transactions: \"best-effort\"\n } as const;\n\n private readonly driver: SQLiteDriver;\n private readonly tableName: string;\n private initialized = false;\n\n constructor(options: SQLiteStorageOptions) {\n this.driver = options.driver;\n this.tableName = options.tableName ?? \"offlinejs_records\";\n }\n\n async initialize(): Promise<void> {\n if (this.initialized) {\n return;\n }\n\n await this.driver.execute(\n `CREATE TABLE IF NOT EXISTS ${this.tableName} (collection TEXT NOT NULL, id TEXT NOT NULL, value TEXT NOT NULL, PRIMARY KEY (collection, id))`\n );\n await this.driver.execute(\n `CREATE TABLE IF NOT EXISTS ${this.tableName}_indexes (collection TEXT NOT NULL, name TEXT NOT NULL, value TEXT NOT NULL, PRIMARY KEY (collection, name))`\n );\n await this.driver.execute(\n `CREATE TABLE IF NOT EXISTS ${this.tableName}_index_entries (collection TEXT NOT NULL, index_name TEXT NOT NULL, value_key TEXT NOT NULL, record_id TEXT NOT NULL, PRIMARY KEY (collection, index_name, value_key, record_id))`\n );\n await this.driver.execute(\n `CREATE INDEX IF NOT EXISTS ${this.tableName}_index_entries_lookup ON ${this.tableName}_index_entries (collection, index_name, value_key)`\n );\n this.initialized = true;\n }\n\n async get<TRecord extends EntityRecord>(collection: string, id: string): Promise<TRecord | null> {\n await this.initialize();\n const rows = await this.driver.query<SQLiteRecordRow>(\n `SELECT value FROM ${this.tableName} WHERE collection = ? AND id = ? LIMIT 1`,\n [collection, id]\n );\n\n return rows[0] ? (JSON.parse(rows[0].value) as TRecord) : null;\n }\n\n async set<TRecord extends EntityRecord>(collection: string, value: TRecord): Promise<void> {\n await this.setMany(collection, [value]);\n }\n\n async setMany<TRecord extends EntityRecord>(collection: string, values: TRecord[]): Promise<void> {\n if (values.length === 0) {\n return;\n }\n\n await this.initialize();\n const byId = new Map<string, TRecord>();\n for (const value of values) {\n byId.set(value.id, value);\n }\n const records = [...byId.values()];\n\n const writeBatch = async (): Promise<void> => {\n for (const value of records) {\n const previous = await this.get(collection, value.id);\n if (previous) {\n await this.removeIndexEntries(collection, previous);\n }\n await this.assertUniqueIndexes(collection, value, previous?.id);\n await this.driver.execute(\n `INSERT OR REPLACE INTO ${this.tableName} (collection, id, value) VALUES (?, ?, ?)`,\n [collection, value.id, JSON.stringify(value)]\n );\n await this.writeIndexEntries(collection, value);\n }\n };\n\n if (this.driver.transaction) {\n await this.driver.transaction(writeBatch);\n return;\n }\n\n await writeBatch();\n }\n\n async delete(collection: string, id: string): Promise<void> {\n await this.initialize();\n const previous = await this.get(collection, id);\n if (previous) {\n await this.removeIndexEntries(collection, previous);\n }\n await this.driver.execute(`DELETE FROM ${this.tableName} WHERE collection = ? AND id = ?`, [\n collection,\n id\n ]);\n }\n\n async find<TRecord extends EntityRecord>(\n collection: string,\n query?: QueryOptions<TRecord>\n ): Promise<TRecord[]> {\n await this.initialize();\n const indexed = await this.findViaIndex<TRecord>(collection, query);\n if (indexed?.complete) {\n return indexed.records;\n }\n\n if (!indexed) {\n const pushed = this.buildPushedFindSQL(collection, query);\n if (pushed) {\n try {\n const rows = await this.driver.query<SQLiteRecordRow>(pushed.sql, pushed.params);\n return rows.map((row) => JSON.parse(row.value) as TRecord);\n } catch {\n // Driver may not support json_extract — fall through to full scan.\n }\n }\n }\n\n const records =\n indexed?.records ??\n (\n await this.driver.query<SQLiteRecordRow>(\n `SELECT value FROM ${this.tableName} WHERE collection = ?`,\n [collection]\n )\n ).map((row) => JSON.parse(row.value) as TRecord);\n\n return applyQuery(records, query);\n }\n\n async clear(collection?: string): Promise<void> {\n await this.initialize();\n\n if (collection) {\n await this.driver.execute(`DELETE FROM ${this.tableName} WHERE collection = ?`, [collection]);\n await this.driver.execute(`DELETE FROM ${this.tableName}_indexes WHERE collection = ?`, [\n collection\n ]);\n await this.driver.execute(\n `DELETE FROM ${this.tableName}_index_entries WHERE collection = ?`,\n [collection]\n );\n return;\n }\n\n await this.driver.execute(`DELETE FROM ${this.tableName}`);\n await this.driver.execute(`DELETE FROM ${this.tableName}_indexes`);\n await this.driver.execute(`DELETE FROM ${this.tableName}_index_entries`);\n }\n\n async transaction<TValue>(\n _scope: string[],\n run: (store: TransactionStore) => Promise<TValue>\n ): Promise<TValue> {\n if (this.driver.transaction) {\n return this.driver.transaction(() => run(this));\n }\n\n return run(this);\n }\n\n async migrate(migrations: StorageMigration[]): Promise<void> {\n await this.initialize();\n const applied = new Set(\n (await this.find<EntityRecord>(\"__migrations\")).map((record) => record.id)\n );\n\n for (const migration of migrations) {\n if (applied.has(migration.name)) {\n continue;\n }\n\n await migration.up(this);\n await this.set(\"__migrations\", { id: migration.name, appliedAt: Date.now() });\n }\n }\n\n async createIndex<TRecord extends EntityRecord>(\n definition: IndexDefinition<TRecord>\n ): Promise<void> {\n await this.initialize();\n await this.driver.execute(\n `INSERT OR REPLACE INTO ${this.tableName}_indexes (collection, name, value) VALUES (?, ?, ?)`,\n [definition.collection, definition.name, JSON.stringify(definition)]\n );\n\n const rows = await this.driver.query<SQLiteRecordRow>(\n `SELECT value FROM ${this.tableName} WHERE collection = ?`,\n [definition.collection]\n );\n\n for (const row of rows) {\n const record = JSON.parse(row.value) as EntityRecord;\n await this.assertUniqueIndexes(definition.collection, record);\n await this.writeIndexEntries(definition.collection, record, [definition as IndexDefinition]);\n }\n }\n\n async dropIndex(collection: string, name: string): Promise<void> {\n await this.initialize();\n await this.driver.execute(\n `DELETE FROM ${this.tableName}_indexes WHERE collection = ? AND name = ?`,\n [collection, name]\n );\n await this.driver.execute(\n `DELETE FROM ${this.tableName}_index_entries WHERE collection = ? AND index_name = ?`,\n [collection, name]\n );\n }\n\n async listIndexes(collection?: string): Promise<IndexDefinition[]> {\n await this.initialize();\n const rows = collection\n ? await this.driver.query<{ value: string }>(\n `SELECT value FROM ${this.tableName}_indexes WHERE collection = ?`,\n [collection]\n )\n : await this.driver.query<{ value: string }>(`SELECT value FROM ${this.tableName}_indexes`);\n\n return rows.map((row) => JSON.parse(row.value) as IndexDefinition);\n }\n\n /**\n * Push equality filters + order/limit into SQL when the whole query is engine-safe.\n * Complex search/operators stay in JS `applyQuery`.\n */\n private buildPushedFindSQL<TRecord extends EntityRecord>(\n collection: string,\n query?: QueryOptions<TRecord>\n ): { sql: string; params: unknown[] } | null {\n if (!query || query.search) {\n return null;\n }\n\n const params: unknown[] = [collection];\n const clauses = [`collection = ?`];\n\n if (query.filters) {\n for (const [field, expected] of Object.entries(query.filters)) {\n if (expected === undefined) {\n return null;\n }\n if (Array.isArray(expected) || expected === null || typeof expected !== \"object\") {\n clauses.push(`json_extract(value, '$.${field}') = ?`);\n params.push(expected);\n continue;\n }\n if (\"eq\" in expected && Object.keys(expected).length === 1 && expected.eq !== undefined) {\n clauses.push(`json_extract(value, '$.${field}') = ?`);\n params.push(expected.eq);\n continue;\n }\n return null;\n }\n }\n\n let sql = `SELECT value FROM ${this.tableName} WHERE ${clauses.join(\" AND \")}`;\n if (query.orderBy) {\n sql += ` ORDER BY json_extract(value, '$.${String(query.orderBy)}') ${\n query.sort === \"desc\" ? \"DESC\" : \"ASC\"\n }`;\n }\n if (query.limit !== undefined) {\n sql += ` LIMIT ?`;\n params.push(query.limit);\n }\n if (query.offset !== undefined && query.offset > 0) {\n if (query.limit === undefined) {\n sql += ` LIMIT -1`;\n }\n sql += ` OFFSET ?`;\n params.push(query.offset);\n }\n\n return { sql, params };\n }\n\n private async findViaIndex<TRecord extends EntityRecord>(\n collection: string,\n query?: QueryOptions<TRecord>\n ): Promise<{ complete: boolean; records: TRecord[] } | null> {\n const match = findMatchingIndex(\n await this.listIndexes(collection),\n getEqualityFilterLookups(query?.filters)\n );\n\n if (!match) {\n return null;\n }\n\n const valueKey = serializeCompoundIndexValue(match.values);\n let rows = await this.driver.query<{ record_id: string }>(\n `SELECT record_id FROM ${this.tableName}_index_entries WHERE collection = ? AND index_name = ? AND value_key = ?`,\n [collection, match.index.name, valueKey]\n );\n const complete = indexSatisfiesQuery(match, query);\n\n if (complete) {\n const { offset, limit } = queryPageWindow(query as Parameters<typeof queryPageWindow>[0]);\n rows = limit === undefined ? rows.slice(offset) : rows.slice(offset, offset + limit);\n }\n\n if (rows.length === 0) {\n return { complete, records: [] };\n }\n\n const placeholders = rows.map(() => \"?\").join(\", \");\n const values = await this.driver.query<SQLiteRecordRow>(\n `SELECT value FROM ${this.tableName} WHERE collection = ? AND id IN (${placeholders})`,\n [collection, ...rows.map((row) => row.record_id)]\n );\n\n return {\n complete,\n records: values.map((row) => JSON.parse(row.value) as TRecord)\n };\n }\n\n private async assertUniqueIndexes(\n collection: string,\n record: EntityRecord,\n ignoreId?: string\n ): Promise<void> {\n for (const definition of await this.listIndexes(collection)) {\n if (!definition.unique) {\n continue;\n }\n\n const valueKey = serializeCompoundIndexValue(readIndexFields(record, definition.fields));\n const rows = await this.driver.query<{ record_id: string }>(\n `SELECT record_id FROM ${this.tableName}_index_entries WHERE collection = ? AND index_name = ? AND value_key = ?`,\n [collection, definition.name, valueKey]\n );\n\n if (rows.some((row) => row.record_id !== record.id && row.record_id !== ignoreId)) {\n throw new Error(`Unique index \"${definition.name}\" violated for ${collection}`);\n }\n }\n }\n\n private async writeIndexEntries(\n collection: string,\n record: EntityRecord,\n definitions?: IndexDefinition[]\n ): Promise<void> {\n const indexes = definitions ?? (await this.listIndexes(collection));\n\n for (const definition of indexes) {\n const valueKey = serializeCompoundIndexValue(readIndexFields(record, definition.fields));\n await this.driver.execute(\n `INSERT OR REPLACE INTO ${this.tableName}_index_entries (collection, index_name, value_key, record_id) VALUES (?, ?, ?, ?)`,\n [collection, definition.name, valueKey, record.id]\n );\n }\n }\n\n private async removeIndexEntries(collection: string, record: EntityRecord): Promise<void> {\n for (const definition of await this.listIndexes(collection)) {\n const valueKey = serializeCompoundIndexValue(readIndexFields(record, definition.fields));\n await this.driver.execute(\n `DELETE FROM ${this.tableName}_index_entries WHERE collection = ? AND index_name = ? AND value_key = ? AND record_id = ?`,\n [collection, definition.name, valueKey, record.id]\n );\n }\n }\n}\n\nexport const createSQLiteStorage = (options: SQLiteStorageOptions): SQLiteStorageAdapter =>\n new SQLiteStorageAdapter(options);\n\nexport interface BetterSqlite3Database {\n prepare(sql: string): {\n run: (...params: unknown[]) => unknown;\n all: (...params: unknown[]) => unknown[];\n };\n exec(sql: string): unknown;\n transaction<TValue>(fn: () => TValue): () => TValue;\n close?: () => void;\n}\n\n/**\n * Wrap a better-sqlite3 Database as an OfflineJS SQLiteDriver.\n * Install optionally: `pnpm add better-sqlite3`\n */\nexport const createBetterSqlite3Driver = (database: BetterSqlite3Database): SQLiteDriver => ({\n async execute(sql: string, params: unknown[] = []) {\n if (/^\\s*CREATE\\b/i.test(sql) && !params.length) {\n database.exec(sql);\n return;\n }\n database.prepare(sql).run(...params);\n },\n async query<TValue = unknown>(sql: string, params: unknown[] = []) {\n return database.prepare(sql).all(...params) as TValue[];\n },\n async transaction<TValue>(run: () => Promise<TValue>) {\n const wrapped = database.transaction(() => {\n let result!: TValue;\n let error: unknown;\n let settled = false;\n void Promise.resolve(run())\n .then((value) => {\n result = value;\n settled = true;\n })\n .catch((reason) => {\n error = reason;\n settled = true;\n });\n if (!settled) {\n throw new Error(\n \"better-sqlite3 transactions require synchronous work; use await outside transaction or sync APIs\"\n );\n }\n if (error) {\n throw error;\n }\n return result;\n });\n return wrapped();\n }\n});\n\n/**\n * Create a sync-friendly better-sqlite3 driver that runs OfflineJS SQL statements\n * without nesting async work inside native transactions. Prefer this helper.\n */\nexport const createBetterSqlite3DriverAsync = (database: BetterSqlite3Database): SQLiteDriver => ({\n async execute(sql: string, params: unknown[] = []) {\n if (/^\\s*CREATE\\b/i.test(sql) && !params.length) {\n database.exec(sql);\n return;\n }\n database.prepare(sql).run(...params);\n },\n async query<TValue = unknown>(sql: string, params: unknown[] = []) {\n return database.prepare(sql).all(...params) as TValue[];\n },\n async transaction<TValue>(run: () => Promise<TValue>) {\n return run();\n }\n});\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@offlinejs/storage-sqlite",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "SQLite storage adapter for OfflineJS using a pluggable async driver.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -21,6 +21,14 @@
|
|
|
21
21
|
"tsup": "latest",
|
|
22
22
|
"typescript": "latest"
|
|
23
23
|
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"better-sqlite3": ">=9"
|
|
26
|
+
},
|
|
27
|
+
"peerDependenciesMeta": {
|
|
28
|
+
"better-sqlite3": {
|
|
29
|
+
"optional": true
|
|
30
|
+
}
|
|
31
|
+
},
|
|
24
32
|
"publishConfig": {
|
|
25
33
|
"access": "public",
|
|
26
34
|
"registry": "https://registry.npmjs.org/"
|