silosdk 0.0.9 → 0.0.10

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 (52) hide show
  1. package/README.md +16 -5
  2. package/dist/internal/indexed-db.cjs +44 -0
  3. package/dist/internal/indexed-db.mjs +41 -0
  4. package/dist/media/shared.cjs +99 -0
  5. package/dist/media/shared.d.cts +26 -0
  6. package/dist/media/shared.d.mts +26 -0
  7. package/dist/media/shared.mjs +93 -0
  8. package/dist/media/storage.native.cjs +102 -0
  9. package/dist/media/storage.native.mjs +101 -0
  10. package/dist/media/storage.web.cjs +79 -0
  11. package/dist/media/storage.web.mjs +78 -0
  12. package/dist/media.cjs +5 -163
  13. package/dist/media.d.cts +7 -27
  14. package/dist/media.d.mts +7 -27
  15. package/dist/media.mjs +4 -161
  16. package/dist/media.web.cjs +9 -0
  17. package/dist/media.web.d.cts +12 -0
  18. package/dist/media.web.d.mts +12 -0
  19. package/dist/media.web.mjs +8 -0
  20. package/dist/settings/shared.cjs +63 -0
  21. package/dist/settings/shared.d.cts +19 -0
  22. package/dist/settings/shared.d.mts +19 -0
  23. package/dist/settings/shared.mjs +59 -0
  24. package/dist/settings/storage.native.cjs +24 -0
  25. package/dist/settings/storage.native.mjs +22 -0
  26. package/dist/settings/storage.web.cjs +34 -0
  27. package/dist/settings/storage.web.mjs +34 -0
  28. package/dist/settings.cjs +4 -66
  29. package/dist/settings.d.cts +4 -18
  30. package/dist/settings.d.mts +4 -18
  31. package/dist/settings.mjs +4 -64
  32. package/dist/settings.web.cjs +8 -0
  33. package/dist/settings.web.d.cts +6 -0
  34. package/dist/settings.web.d.mts +6 -0
  35. package/dist/settings.web.mjs +8 -0
  36. package/dist/store/shared.cjs +338 -0
  37. package/dist/store/shared.d.cts +58 -0
  38. package/dist/store/shared.d.mts +58 -0
  39. package/dist/store/shared.mjs +333 -0
  40. package/dist/store/storage.native.cjs +133 -0
  41. package/dist/store/storage.native.mjs +132 -0
  42. package/dist/store/storage.web.cjs +142 -0
  43. package/dist/store/storage.web.mjs +142 -0
  44. package/dist/store.cjs +4 -421
  45. package/dist/store.d.cts +4 -58
  46. package/dist/store.d.mts +4 -58
  47. package/dist/store.mjs +4 -421
  48. package/dist/store.web.cjs +15 -0
  49. package/dist/store.web.d.cts +7 -0
  50. package/dist/store.web.d.mts +7 -0
  51. package/dist/store.web.mjs +12 -0
  52. package/package.json +31 -1
@@ -0,0 +1,333 @@
1
+ //#region src/store/shared.ts
2
+ const sourceNames = /* @__PURE__ */ new Set();
3
+ const sourceNamePattern = /^[A-Za-z][A-Za-z0-9_-]*$/;
4
+ const reservedDocumentFields = new Set([
5
+ "id",
6
+ "data",
7
+ "set"
8
+ ]);
9
+ const deletedDocs = /* @__PURE__ */ new WeakSet();
10
+ function createStoreApi(storage) {
11
+ function createStore(defaults) {
12
+ const names = Object.keys(defaults);
13
+ const nameSet = new Set(names);
14
+ for (const name of names) {
15
+ assertSourceName(name);
16
+ assertFlatDefaults(name, defaults[name]);
17
+ }
18
+ if (new Set(names).size !== names.length) throw new Error("Store collection names must be unique.");
19
+ return {
20
+ names,
21
+ collectionOptions(name, options = {}) {
22
+ assertStoreCollectionName(nameSet, name);
23
+ return createSourceCollectionOptions(storage, name, defaults[name], options);
24
+ },
25
+ linkCollectionOptions(options = {}) {
26
+ return createLinkCollectionOptions(storage, names, nameSet, options);
27
+ }
28
+ };
29
+ }
30
+ function source(name, defaults) {
31
+ assertSourceName(name);
32
+ assertFlatDefaults(name, defaults);
33
+ if (sourceNames.has(name)) throw new Error(`Source "${name}" is already registered. Define each source once and import the existing source instead.`);
34
+ sourceNames.add(name);
35
+ return {
36
+ name,
37
+ defaults,
38
+ schema: createSourceSchema(name, defaults),
39
+ collectionOptions(options = {}) {
40
+ return createSourceCollectionOptions(storage, name, defaults, options);
41
+ }
42
+ };
43
+ }
44
+ return {
45
+ createStore,
46
+ source
47
+ };
48
+ }
49
+ function createSourceCollectionOptions(storage, name, defaults, options = {}) {
50
+ return {
51
+ id: name,
52
+ getKey: (item) => item.id,
53
+ schema: createSourceSchema(name, defaults),
54
+ startSync: options.startSync,
55
+ sync: { sync({ begin, write, commit, markReady, truncate }) {
56
+ Promise.resolve(storage.loadRows(name)).then((rows) => {
57
+ begin();
58
+ truncate();
59
+ for (const row of rows) write({
60
+ type: "insert",
61
+ value: row
62
+ });
63
+ commit();
64
+ markReady();
65
+ }).catch((error) => {
66
+ reportSyncError(name, error);
67
+ begin();
68
+ truncate();
69
+ commit();
70
+ markReady();
71
+ });
72
+ } },
73
+ onInsert: async ({ transaction }) => {
74
+ await storage.insertRows(name, transaction.mutations.map((mutation) => mutation.modified));
75
+ },
76
+ onUpdate: async ({ transaction }) => {
77
+ await storage.updateRows(name, transaction.mutations);
78
+ },
79
+ onDelete: async ({ transaction }) => {
80
+ await storage.deleteRows(name, transaction.mutations);
81
+ }
82
+ };
83
+ }
84
+ function createLinkCollectionOptions(storage, names, nameSet, options = {}) {
85
+ const schema = createLinkSchema();
86
+ let syncBegin;
87
+ let syncWrite;
88
+ let syncCommit;
89
+ const writeSyncedLink = (type, row) => {
90
+ if (!syncBegin || !syncWrite || !syncCommit) return;
91
+ syncBegin();
92
+ syncWrite({
93
+ type,
94
+ value: row
95
+ });
96
+ syncCommit();
97
+ };
98
+ return {
99
+ id: "silo-links",
100
+ getKey: (item) => item.id,
101
+ schema,
102
+ startSync: options.startSync,
103
+ sync: { sync({ begin, write, commit, markReady, truncate }) {
104
+ syncBegin = begin;
105
+ syncWrite = write;
106
+ syncCommit = commit;
107
+ Promise.resolve(storage.loadLinkRows()).then((storedRows) => {
108
+ const rows = storedRows.map((row) => createLinkRow(names, row));
109
+ begin();
110
+ truncate();
111
+ for (const row of rows) write({
112
+ type: "insert",
113
+ value: row
114
+ });
115
+ commit();
116
+ markReady();
117
+ }).catch((error) => {
118
+ reportSyncError("silo-links", error);
119
+ begin();
120
+ truncate();
121
+ commit();
122
+ markReady();
123
+ });
124
+ } },
125
+ onInsert: async ({ transaction }) => {
126
+ for (const mutation of transaction.mutations) await storage.insertLinkRow(extractStoredLinkRow(mutation.modified));
127
+ },
128
+ onDelete: async ({ transaction }) => {
129
+ for (const mutation of transaction.mutations) await storage.deleteLinkRow(String(mutation.key));
130
+ },
131
+ utils: {
132
+ async link(collectionA, idA, collectionB, idB) {
133
+ const row = createStoredLinkRow(nameSet, collectionA, idA, collectionB, idB);
134
+ if (!await storage.insertLinkRow(row)) writeSyncedLink("insert", createLinkRow(names, row));
135
+ },
136
+ async unlink(collectionA, idA, collectionB, idB) {
137
+ const row = createStoredLinkRow(nameSet, collectionA, idA, collectionB, idB);
138
+ if (await storage.deleteLinkRow(row.id)) writeSyncedLink("delete", createLinkRow(names, row));
139
+ },
140
+ has(collectionA, idA, collectionB, idB) {
141
+ const row = createStoredLinkRow(nameSet, collectionA, idA, collectionB, idB);
142
+ return storage.hasLinkRow(row.id);
143
+ }
144
+ }
145
+ };
146
+ }
147
+ function assertSourceName(name) {
148
+ if (!sourceNamePattern.test(name)) throw new Error(`Source names must start with a letter and contain only letters, numbers, underscores, or hyphens. Received "${name}".`);
149
+ }
150
+ function assertStoreCollectionName(nameSet, name) {
151
+ if (!nameSet.has(name)) throw new Error(`Store collection "${name}" is not registered.`);
152
+ }
153
+ function assertLinkCollectionName(nameSet, name) {
154
+ if (!nameSet.has(name)) throw new Error(`Cannot link unknown collection "${name}".`);
155
+ }
156
+ function createStoredLinkRow(nameSet, collectionA, idA, collectionB, idB) {
157
+ assertLinkCollectionName(nameSet, collectionA);
158
+ assertLinkCollectionName(nameSet, collectionB);
159
+ if (collectionA === collectionB) throw new Error(`Links between the same collection are not supported. Received "${collectionA}".`);
160
+ const [firstCollection, firstId, secondCollection, secondId] = collectionA < collectionB ? [
161
+ collectionA,
162
+ idA,
163
+ collectionB,
164
+ idB
165
+ ] : [
166
+ collectionB,
167
+ idB,
168
+ collectionA,
169
+ idA
170
+ ];
171
+ const collections = encodePair(firstCollection, secondCollection);
172
+ const ids = encodePair(firstId, secondId);
173
+ return {
174
+ id: encodePair(collections, ids),
175
+ collections,
176
+ ids,
177
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
178
+ };
179
+ }
180
+ function createLinkRow(names, row) {
181
+ const [firstCollection, secondCollection] = decodePair(row.collections);
182
+ const [firstId, secondId] = decodePair(row.ids);
183
+ const result = {
184
+ id: row.id,
185
+ collections: row.collections,
186
+ ids: row.ids,
187
+ createdAt: row.createdAt
188
+ };
189
+ for (const other of names) {
190
+ const byCurrent = {};
191
+ for (const current of names) {
192
+ let id = null;
193
+ if (other !== current) {
194
+ if (current === firstCollection && other === secondCollection) id = firstId;
195
+ else if (current === secondCollection && other === firstCollection) id = secondId;
196
+ }
197
+ byCurrent[current] = { id };
198
+ }
199
+ result[other] = byCurrent;
200
+ }
201
+ return result;
202
+ }
203
+ function extractStoredLinkRow(row) {
204
+ if (!isRecord(row)) throw new Error("Expected a link row object.");
205
+ if (typeof row.id !== "string" || typeof row.collections !== "string" || typeof row.ids !== "string" || typeof row.createdAt !== "string") throw new Error("Link rows must include string id, collections, ids, and createdAt fields.");
206
+ return {
207
+ id: row.id,
208
+ collections: row.collections,
209
+ ids: row.ids,
210
+ createdAt: row.createdAt
211
+ };
212
+ }
213
+ function encodePair(first, second) {
214
+ return `${encodePart(first)}:${encodePart(second)}`;
215
+ }
216
+ function decodePair(value) {
217
+ const parts = value.split(":");
218
+ if (parts.length !== 2) throw new Error(`Invalid encoded link pair "${value}".`);
219
+ return [decodePart(parts[0]), decodePart(parts[1])];
220
+ }
221
+ function encodePart(value) {
222
+ return encodeURIComponent(value);
223
+ }
224
+ function decodePart(value) {
225
+ return decodeURIComponent(value);
226
+ }
227
+ function assertFlatDefaults(sourceName, defaults) {
228
+ for (const [field, defaultValue] of Object.entries(defaults)) {
229
+ if (reservedDocumentFields.has(field)) throw new Error(`Source "${sourceName}" field "${field}" is reserved by Silo documents.`);
230
+ if (!isFieldValue(resolveDefault(defaultValue))) throw new Error(`Source "${sourceName}" field "${field}" must default to a string, number, boolean, null, or a function returning one of those values.`);
231
+ }
232
+ }
233
+ function createSourceSchema(sourceName, defaults) {
234
+ return { "~standard": {
235
+ version: 1,
236
+ vendor: "silo",
237
+ validate(value) {
238
+ if (!isRecord(value)) return failure("Expected a flat source row object.");
239
+ if (typeof value.id !== "string") return failure("Expected source document field \"id\" to be a string.", ["id"]);
240
+ const inputData = isRecord(value.data) ? value.data : extractDocumentData(value);
241
+ const data = {};
242
+ for (const [field, fieldValue] of Object.entries(inputData)) {
243
+ if (reservedDocumentFields.has(field)) return failure(`Source "${sourceName}" field "${field}" is reserved by Silo documents.`, ["data", field]);
244
+ if (!isFieldValue(fieldValue)) return failure(`Source "${sourceName}" field "${field}" must be a string, number, boolean, or null.`, ["data", field]);
245
+ data[field] = fieldValue;
246
+ }
247
+ for (const [field, defaultValue] of Object.entries(defaults)) {
248
+ if (field in data) continue;
249
+ data[field] = resolveDefault(defaultValue);
250
+ }
251
+ return { value: createDoc(value.id, data) };
252
+ }
253
+ } };
254
+ }
255
+ function createLinkSchema() {
256
+ return { "~standard": {
257
+ version: 1,
258
+ vendor: "silo",
259
+ validate(value) {
260
+ try {
261
+ extractStoredLinkRow(value);
262
+ return { value };
263
+ } catch (error) {
264
+ return failure(error instanceof Error ? error.message : "Expected a link row.");
265
+ }
266
+ }
267
+ } };
268
+ }
269
+ function createDoc(id, data) {
270
+ const doc = {};
271
+ Object.defineProperty(doc, "id", {
272
+ value: id,
273
+ enumerable: true,
274
+ writable: false,
275
+ configurable: false
276
+ });
277
+ for (const [field, value] of Object.entries(data)) Object.defineProperty(doc, field, {
278
+ value,
279
+ enumerable: true,
280
+ writable: true,
281
+ configurable: true
282
+ });
283
+ Object.defineProperty(doc, "data", {
284
+ value() {
285
+ return deletedDocs.has(this) ? null : extractDocumentData(this);
286
+ },
287
+ enumerable: true,
288
+ writable: false,
289
+ configurable: false
290
+ });
291
+ Object.defineProperty(doc, "set", {
292
+ get() {
293
+ return function setDocumentData(patch) {
294
+ Object.assign(this, patch);
295
+ };
296
+ },
297
+ enumerable: true,
298
+ configurable: false
299
+ });
300
+ return doc;
301
+ }
302
+ function extractDocumentData(value) {
303
+ if (!isRecord(value)) return {};
304
+ const data = {};
305
+ for (const [field, fieldValue] of Object.entries(value)) {
306
+ if (reservedDocumentFields.has(field) || field.startsWith("$") || typeof fieldValue === "function") continue;
307
+ if (isFieldValue(fieldValue)) data[field] = fieldValue;
308
+ }
309
+ return data;
310
+ }
311
+ function resolveDefault(value) {
312
+ const resolved = typeof value === "function" ? value() : value;
313
+ if (!isFieldValue(resolved)) throw new Error("Source defaults must resolve to a string, number, boolean, or null.");
314
+ return resolved;
315
+ }
316
+ function isRecord(value) {
317
+ return typeof value === "object" && value !== null && !Array.isArray(value);
318
+ }
319
+ function isFieldValue(value) {
320
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null;
321
+ }
322
+ function failure(message, path) {
323
+ return { issues: [{
324
+ message,
325
+ path
326
+ }] };
327
+ }
328
+ function reportSyncError(collectionName, error) {
329
+ console.error(`Failed to sync Silo collection "${collectionName}".`, error);
330
+ }
331
+
332
+ //#endregion
333
+ export { createDoc, createStoreApi, decodePair, deletedDocs, extractDocumentData };
@@ -0,0 +1,133 @@
1
+ const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
2
+ const require_shared = require('./shared.cjs');
3
+ let expo_sqlite = require("expo-sqlite");
4
+
5
+ //#region src/store/storage.native.ts
6
+ let database;
7
+ const storeStorage = {
8
+ loadRows,
9
+ insertRows,
10
+ updateRows,
11
+ deleteRows,
12
+ loadLinkRows,
13
+ insertLinkRow,
14
+ deleteLinkRow,
15
+ hasLinkRow
16
+ };
17
+ function getDatabase() {
18
+ if (!database) {
19
+ database = (0, expo_sqlite.openDatabaseSync)("silo.db");
20
+ database.execSync(`
21
+ PRAGMA journal_mode = WAL;
22
+
23
+ CREATE TABLE IF NOT EXISTS sources (
24
+ source TEXT NOT NULL,
25
+ id TEXT NOT NULL,
26
+ data TEXT NOT NULL,
27
+ createdAt TEXT NOT NULL,
28
+ updatedAt TEXT NOT NULL,
29
+ version INTEGER NOT NULL DEFAULT 1,
30
+ PRIMARY KEY (source, id)
31
+ );
32
+
33
+ CREATE TABLE IF NOT EXISTS links (
34
+ id TEXT PRIMARY KEY,
35
+ collections TEXT NOT NULL,
36
+ ids TEXT NOT NULL,
37
+ createdAt TEXT NOT NULL
38
+ );
39
+
40
+ CREATE UNIQUE INDEX IF NOT EXISTS links_collections_ids_idx
41
+ ON links(collections, ids);
42
+
43
+ CREATE INDEX IF NOT EXISTS links_collections_idx
44
+ ON links(collections);
45
+ `);
46
+ }
47
+ return database;
48
+ }
49
+ function loadRows(sourceName) {
50
+ return getDatabase().getAllSync(`SELECT id, data FROM sources WHERE source = ?`, [sourceName]).map((row) => require_shared.createDoc(row.id, JSON.parse(row.data)));
51
+ }
52
+ function insertRows(sourceName, rows) {
53
+ const db = getDatabase();
54
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
55
+ db.withTransactionSync(() => {
56
+ for (const row of rows) {
57
+ const data = require_shared.extractDocumentData(row);
58
+ db.runSync(`INSERT INTO sources (source, id, data, createdAt, updatedAt, version)
59
+ VALUES (?, ?, ?, ?, ?, 1)`, [
60
+ sourceName,
61
+ row.id,
62
+ JSON.stringify(data),
63
+ timestamp,
64
+ timestamp
65
+ ]);
66
+ }
67
+ });
68
+ return Promise.resolve();
69
+ }
70
+ function updateRows(sourceName, mutations) {
71
+ const db = getDatabase();
72
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
73
+ db.withTransactionSync(() => {
74
+ for (const mutation of mutations) {
75
+ const data = require_shared.extractDocumentData(mutation.modified);
76
+ mutation.modified = require_shared.createDoc(String(mutation.key), data);
77
+ db.runSync(`UPDATE sources
78
+ SET data = ?, updatedAt = ?, version = version + 1
79
+ WHERE source = ? AND id = ?`, [
80
+ JSON.stringify(data),
81
+ timestamp,
82
+ sourceName,
83
+ String(mutation.key)
84
+ ]);
85
+ }
86
+ });
87
+ return Promise.resolve();
88
+ }
89
+ function deleteRows(sourceName, mutations) {
90
+ const db = getDatabase();
91
+ db.withTransactionSync(() => {
92
+ for (const mutation of mutations) {
93
+ if (mutation.original) require_shared.deletedDocs.add(mutation.original);
94
+ require_shared.deletedDocs.add(mutation.modified);
95
+ db.runSync(`DELETE FROM sources WHERE source = ? AND id = ?`, [sourceName, String(mutation.key)]);
96
+ deleteLinksForSourceRow(sourceName, String(mutation.key));
97
+ }
98
+ });
99
+ return Promise.resolve();
100
+ }
101
+ function loadLinkRows() {
102
+ return getDatabase().getAllSync(`SELECT id, collections, ids, createdAt FROM links`);
103
+ }
104
+ function insertLinkRow(row) {
105
+ const exists = hasLinkRow(row.id);
106
+ getDatabase().runSync(`INSERT OR IGNORE INTO links (id, collections, ids, createdAt)
107
+ VALUES (?, ?, ?, ?)`, [
108
+ row.id,
109
+ row.collections,
110
+ row.ids,
111
+ row.createdAt
112
+ ]);
113
+ return exists;
114
+ }
115
+ function deleteLinkRow(id) {
116
+ const exists = hasLinkRow(id);
117
+ getDatabase().runSync(`DELETE FROM links WHERE id = ?`, [id]);
118
+ return exists;
119
+ }
120
+ function hasLinkRow(id) {
121
+ return !!getDatabase().getFirstSync(`SELECT id FROM links WHERE id = ?`, [id]);
122
+ }
123
+ function deleteLinksForSourceRow(sourceName, id) {
124
+ const rows = loadLinkRows();
125
+ for (const row of rows) {
126
+ const [firstCollection, secondCollection] = require_shared.decodePair(row.collections);
127
+ const [firstId, secondId] = require_shared.decodePair(row.ids);
128
+ if (firstCollection === sourceName && firstId === id || secondCollection === sourceName && secondId === id) deleteLinkRow(row.id);
129
+ }
130
+ }
131
+
132
+ //#endregion
133
+ exports.storeStorage = storeStorage;
@@ -0,0 +1,132 @@
1
+ import { createDoc, decodePair, deletedDocs, extractDocumentData } from "./shared.mjs";
2
+ import { openDatabaseSync } from "expo-sqlite";
3
+
4
+ //#region src/store/storage.native.ts
5
+ let database;
6
+ const storeStorage = {
7
+ loadRows,
8
+ insertRows,
9
+ updateRows,
10
+ deleteRows,
11
+ loadLinkRows,
12
+ insertLinkRow,
13
+ deleteLinkRow,
14
+ hasLinkRow
15
+ };
16
+ function getDatabase() {
17
+ if (!database) {
18
+ database = openDatabaseSync("silo.db");
19
+ database.execSync(`
20
+ PRAGMA journal_mode = WAL;
21
+
22
+ CREATE TABLE IF NOT EXISTS sources (
23
+ source TEXT NOT NULL,
24
+ id TEXT NOT NULL,
25
+ data TEXT NOT NULL,
26
+ createdAt TEXT NOT NULL,
27
+ updatedAt TEXT NOT NULL,
28
+ version INTEGER NOT NULL DEFAULT 1,
29
+ PRIMARY KEY (source, id)
30
+ );
31
+
32
+ CREATE TABLE IF NOT EXISTS links (
33
+ id TEXT PRIMARY KEY,
34
+ collections TEXT NOT NULL,
35
+ ids TEXT NOT NULL,
36
+ createdAt TEXT NOT NULL
37
+ );
38
+
39
+ CREATE UNIQUE INDEX IF NOT EXISTS links_collections_ids_idx
40
+ ON links(collections, ids);
41
+
42
+ CREATE INDEX IF NOT EXISTS links_collections_idx
43
+ ON links(collections);
44
+ `);
45
+ }
46
+ return database;
47
+ }
48
+ function loadRows(sourceName) {
49
+ return getDatabase().getAllSync(`SELECT id, data FROM sources WHERE source = ?`, [sourceName]).map((row) => createDoc(row.id, JSON.parse(row.data)));
50
+ }
51
+ function insertRows(sourceName, rows) {
52
+ const db = getDatabase();
53
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
54
+ db.withTransactionSync(() => {
55
+ for (const row of rows) {
56
+ const data = extractDocumentData(row);
57
+ db.runSync(`INSERT INTO sources (source, id, data, createdAt, updatedAt, version)
58
+ VALUES (?, ?, ?, ?, ?, 1)`, [
59
+ sourceName,
60
+ row.id,
61
+ JSON.stringify(data),
62
+ timestamp,
63
+ timestamp
64
+ ]);
65
+ }
66
+ });
67
+ return Promise.resolve();
68
+ }
69
+ function updateRows(sourceName, mutations) {
70
+ const db = getDatabase();
71
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
72
+ db.withTransactionSync(() => {
73
+ for (const mutation of mutations) {
74
+ const data = extractDocumentData(mutation.modified);
75
+ mutation.modified = createDoc(String(mutation.key), data);
76
+ db.runSync(`UPDATE sources
77
+ SET data = ?, updatedAt = ?, version = version + 1
78
+ WHERE source = ? AND id = ?`, [
79
+ JSON.stringify(data),
80
+ timestamp,
81
+ sourceName,
82
+ String(mutation.key)
83
+ ]);
84
+ }
85
+ });
86
+ return Promise.resolve();
87
+ }
88
+ function deleteRows(sourceName, mutations) {
89
+ const db = getDatabase();
90
+ db.withTransactionSync(() => {
91
+ for (const mutation of mutations) {
92
+ if (mutation.original) deletedDocs.add(mutation.original);
93
+ deletedDocs.add(mutation.modified);
94
+ db.runSync(`DELETE FROM sources WHERE source = ? AND id = ?`, [sourceName, String(mutation.key)]);
95
+ deleteLinksForSourceRow(sourceName, String(mutation.key));
96
+ }
97
+ });
98
+ return Promise.resolve();
99
+ }
100
+ function loadLinkRows() {
101
+ return getDatabase().getAllSync(`SELECT id, collections, ids, createdAt FROM links`);
102
+ }
103
+ function insertLinkRow(row) {
104
+ const exists = hasLinkRow(row.id);
105
+ getDatabase().runSync(`INSERT OR IGNORE INTO links (id, collections, ids, createdAt)
106
+ VALUES (?, ?, ?, ?)`, [
107
+ row.id,
108
+ row.collections,
109
+ row.ids,
110
+ row.createdAt
111
+ ]);
112
+ return exists;
113
+ }
114
+ function deleteLinkRow(id) {
115
+ const exists = hasLinkRow(id);
116
+ getDatabase().runSync(`DELETE FROM links WHERE id = ?`, [id]);
117
+ return exists;
118
+ }
119
+ function hasLinkRow(id) {
120
+ return !!getDatabase().getFirstSync(`SELECT id FROM links WHERE id = ?`, [id]);
121
+ }
122
+ function deleteLinksForSourceRow(sourceName, id) {
123
+ const rows = loadLinkRows();
124
+ for (const row of rows) {
125
+ const [firstCollection, secondCollection] = decodePair(row.collections);
126
+ const [firstId, secondId] = decodePair(row.ids);
127
+ if (firstCollection === sourceName && firstId === id || secondCollection === sourceName && secondId === id) deleteLinkRow(row.id);
128
+ }
129
+ }
130
+
131
+ //#endregion
132
+ export { storeStorage };