@frostpillar/frostpillar-storage-engine 0.0.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.
Files changed (107) hide show
  1. package/LICENSE +21 -0
  2. package/README-JA.md +1205 -0
  3. package/README.md +1204 -0
  4. package/dist/drivers/file.cjs +960 -0
  5. package/dist/drivers/file.d.ts +3 -0
  6. package/dist/drivers/file.js +18 -0
  7. package/dist/drivers/indexedDB.cjs +570 -0
  8. package/dist/drivers/indexedDB.d.ts +3 -0
  9. package/dist/drivers/indexedDB.js +18 -0
  10. package/dist/drivers/localStorage.cjs +668 -0
  11. package/dist/drivers/localStorage.d.ts +3 -0
  12. package/dist/drivers/localStorage.js +23 -0
  13. package/dist/drivers/opfs.cjs +550 -0
  14. package/dist/drivers/opfs.d.ts +3 -0
  15. package/dist/drivers/opfs.js +18 -0
  16. package/dist/drivers/syncStorage.cjs +898 -0
  17. package/dist/drivers/syncStorage.d.ts +3 -0
  18. package/dist/drivers/syncStorage.js +22 -0
  19. package/dist/drivers/validation.d.ts +1 -0
  20. package/dist/drivers/validation.js +8 -0
  21. package/dist/errors/index.d.ts +32 -0
  22. package/dist/errors/index.js +48 -0
  23. package/dist/frostpillar-storage-engine.min.js +1 -0
  24. package/dist/index.cjs +2957 -0
  25. package/dist/index.d.ts +7 -0
  26. package/dist/index.js +6 -0
  27. package/dist/storage/backend/asyncDurableAutoCommitController.d.ts +26 -0
  28. package/dist/storage/backend/asyncDurableAutoCommitController.js +188 -0
  29. package/dist/storage/backend/asyncMutex.d.ts +7 -0
  30. package/dist/storage/backend/asyncMutex.js +38 -0
  31. package/dist/storage/backend/autoCommit.d.ts +2 -0
  32. package/dist/storage/backend/autoCommit.js +22 -0
  33. package/dist/storage/backend/capacity.d.ts +2 -0
  34. package/dist/storage/backend/capacity.js +27 -0
  35. package/dist/storage/backend/capacityResolver.d.ts +3 -0
  36. package/dist/storage/backend/capacityResolver.js +25 -0
  37. package/dist/storage/backend/encoding.d.ts +17 -0
  38. package/dist/storage/backend/encoding.js +148 -0
  39. package/dist/storage/backend/types.d.ts +184 -0
  40. package/dist/storage/backend/types.js +1 -0
  41. package/dist/storage/btree/recordKeyIndexBTree.d.ts +39 -0
  42. package/dist/storage/btree/recordKeyIndexBTree.js +104 -0
  43. package/dist/storage/config/config.browser.d.ts +4 -0
  44. package/dist/storage/config/config.browser.js +8 -0
  45. package/dist/storage/config/config.d.ts +1 -0
  46. package/dist/storage/config/config.js +1 -0
  47. package/dist/storage/config/config.node.d.ts +4 -0
  48. package/dist/storage/config/config.node.js +74 -0
  49. package/dist/storage/config/config.shared.d.ts +6 -0
  50. package/dist/storage/config/config.shared.js +105 -0
  51. package/dist/storage/datastore/Datastore.d.ts +47 -0
  52. package/dist/storage/datastore/Datastore.js +525 -0
  53. package/dist/storage/datastore/datastoreClose.d.ts +12 -0
  54. package/dist/storage/datastore/datastoreClose.js +60 -0
  55. package/dist/storage/datastore/datastoreKeyDefinition.d.ts +7 -0
  56. package/dist/storage/datastore/datastoreKeyDefinition.js +60 -0
  57. package/dist/storage/datastore/datastoreLifecycle.d.ts +18 -0
  58. package/dist/storage/datastore/datastoreLifecycle.js +63 -0
  59. package/dist/storage/datastore/mutationById.d.ts +29 -0
  60. package/dist/storage/datastore/mutationById.js +71 -0
  61. package/dist/storage/drivers/IndexedDB/indexedDBBackend.d.ts +11 -0
  62. package/dist/storage/drivers/IndexedDB/indexedDBBackend.js +109 -0
  63. package/dist/storage/drivers/IndexedDB/indexedDBBackendController.d.ts +27 -0
  64. package/dist/storage/drivers/IndexedDB/indexedDBBackendController.js +60 -0
  65. package/dist/storage/drivers/IndexedDB/indexedDBConfig.d.ts +7 -0
  66. package/dist/storage/drivers/IndexedDB/indexedDBConfig.js +24 -0
  67. package/dist/storage/drivers/file/fileBackend.d.ts +5 -0
  68. package/dist/storage/drivers/file/fileBackend.js +168 -0
  69. package/dist/storage/drivers/file/fileBackendController.d.ts +31 -0
  70. package/dist/storage/drivers/file/fileBackendController.js +72 -0
  71. package/dist/storage/drivers/file/fileBackendSnapshot.d.ts +10 -0
  72. package/dist/storage/drivers/file/fileBackendSnapshot.js +166 -0
  73. package/dist/storage/drivers/localStorage/localStorageBackend.d.ts +10 -0
  74. package/dist/storage/drivers/localStorage/localStorageBackend.js +156 -0
  75. package/dist/storage/drivers/localStorage/localStorageBackendController.d.ts +24 -0
  76. package/dist/storage/drivers/localStorage/localStorageBackendController.js +35 -0
  77. package/dist/storage/drivers/localStorage/localStorageConfig.d.ts +10 -0
  78. package/dist/storage/drivers/localStorage/localStorageConfig.js +16 -0
  79. package/dist/storage/drivers/localStorage/localStorageLayout.d.ts +5 -0
  80. package/dist/storage/drivers/localStorage/localStorageLayout.js +29 -0
  81. package/dist/storage/drivers/opfs/opfsBackend.d.ts +12 -0
  82. package/dist/storage/drivers/opfs/opfsBackend.js +142 -0
  83. package/dist/storage/drivers/opfs/opfsBackendController.d.ts +26 -0
  84. package/dist/storage/drivers/opfs/opfsBackendController.js +44 -0
  85. package/dist/storage/drivers/syncStorage/syncStorageAdapter.d.ts +2 -0
  86. package/dist/storage/drivers/syncStorage/syncStorageAdapter.js +123 -0
  87. package/dist/storage/drivers/syncStorage/syncStorageBackend.d.ts +11 -0
  88. package/dist/storage/drivers/syncStorage/syncStorageBackend.js +169 -0
  89. package/dist/storage/drivers/syncStorage/syncStorageBackendController.d.ts +24 -0
  90. package/dist/storage/drivers/syncStorage/syncStorageBackendController.js +34 -0
  91. package/dist/storage/drivers/syncStorage/syncStorageChunkMaintenance.d.ts +2 -0
  92. package/dist/storage/drivers/syncStorage/syncStorageChunkMaintenance.js +28 -0
  93. package/dist/storage/drivers/syncStorage/syncStorageConfig.d.ts +13 -0
  94. package/dist/storage/drivers/syncStorage/syncStorageConfig.js +42 -0
  95. package/dist/storage/drivers/syncStorage/syncStorageQuota.d.ts +3 -0
  96. package/dist/storage/drivers/syncStorage/syncStorageQuota.js +45 -0
  97. package/dist/storage/record/ordering.d.ts +3 -0
  98. package/dist/storage/record/ordering.js +7 -0
  99. package/dist/types.d.ts +125 -0
  100. package/dist/types.js +1 -0
  101. package/dist/validation/metadata.d.ts +1 -0
  102. package/dist/validation/metadata.js +7 -0
  103. package/dist/validation/payload.d.ts +7 -0
  104. package/dist/validation/payload.js +135 -0
  105. package/dist/validation/typeGuards.d.ts +1 -0
  106. package/dist/validation/typeGuards.js +7 -0
  107. package/package.json +110 -0
@@ -0,0 +1,3 @@
1
+ import type { DatastoreDriver, FileBackendConfig } from '../types.js';
2
+ export type FileDriverOptions = FileBackendConfig;
3
+ export declare const fileDriver: (options?: FileDriverOptions) => DatastoreDriver;
@@ -0,0 +1,18 @@
1
+ import { FileBackendController, } from '../storage/drivers/file/fileBackendController.js';
2
+ export const fileDriver = (options = {}) => {
3
+ return {
4
+ init: (callbacks) => {
5
+ const result = FileBackendController.create({
6
+ config: options,
7
+ autoCommit: callbacks.autoCommit,
8
+ getSnapshot: callbacks.getSnapshot,
9
+ onAutoCommitError: callbacks.onAutoCommitError,
10
+ });
11
+ return {
12
+ controller: result.controller,
13
+ initialTreeJSON: result.initialTreeJSON,
14
+ initialCurrentSizeBytes: result.initialCurrentSizeBytes,
15
+ };
16
+ },
17
+ };
18
+ };
@@ -0,0 +1,570 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/drivers/indexedDB.ts
21
+ var indexedDB_exports = {};
22
+ __export(indexedDB_exports, {
23
+ indexedDBDriver: () => indexedDBDriver
24
+ });
25
+ module.exports = __toCommonJS(indexedDB_exports);
26
+
27
+ // src/errors/index.ts
28
+ var FrostpillarError = class extends Error {
29
+ constructor(message, options) {
30
+ super(message);
31
+ this.name = new.target.name;
32
+ if (options !== void 0) {
33
+ this.cause = options.cause;
34
+ }
35
+ }
36
+ };
37
+ var ConfigurationError = class extends FrostpillarError {
38
+ };
39
+ var UnsupportedBackendError = class extends FrostpillarError {
40
+ };
41
+ var StorageEngineError = class extends FrostpillarError {
42
+ };
43
+ var PageCorruptionError = class extends StorageEngineError {
44
+ };
45
+ var toErrorInstance = (error, fallbackMessage) => {
46
+ if (error instanceof Error) {
47
+ return error;
48
+ }
49
+ return new Error(fallbackMessage, { cause: error });
50
+ };
51
+
52
+ // src/storage/config/config.shared.ts
53
+ var BYTE_SIZE_MULTIPLIER = {
54
+ B: 1,
55
+ KB: 1024,
56
+ MB: 1024 * 1024,
57
+ GB: 1024 * 1024 * 1024
58
+ };
59
+ var FREQUENCY_REGEX = /^(\d+)(ms|s|m|h)$/;
60
+ var FREQUENCY_MULTIPLIER = {
61
+ ms: 1,
62
+ s: 1e3,
63
+ m: 60 * 1e3,
64
+ h: 60 * 60 * 1e3
65
+ };
66
+ var parseFrequencyString = (frequency) => {
67
+ const matched = FREQUENCY_REGEX.exec(frequency);
68
+ if (matched === null) {
69
+ throw new ConfigurationError(
70
+ "autoCommit.frequency string must be one of: <positive>ms, <positive>s, <positive>m, <positive>h."
71
+ );
72
+ }
73
+ const amount = Number(matched[1]);
74
+ if (!Number.isSafeInteger(amount) || amount <= 0) {
75
+ throw new ConfigurationError(
76
+ "autoCommit.frequency string amount must be a positive safe integer."
77
+ );
78
+ }
79
+ const unit = matched[2];
80
+ const multiplier = FREQUENCY_MULTIPLIER[unit];
81
+ const intervalMs = amount * multiplier;
82
+ if (!Number.isSafeInteger(intervalMs) || intervalMs <= 0) {
83
+ throw new ConfigurationError(
84
+ "autoCommit.frequency exceeds safe integer range."
85
+ );
86
+ }
87
+ return intervalMs;
88
+ };
89
+ var parseAutoCommitConfig = (autoCommit) => {
90
+ if (autoCommit?.maxPendingBytes !== void 0) {
91
+ if (!Number.isSafeInteger(autoCommit.maxPendingBytes) || autoCommit.maxPendingBytes <= 0) {
92
+ throw new ConfigurationError(
93
+ "autoCommit.maxPendingBytes must be a positive safe integer."
94
+ );
95
+ }
96
+ }
97
+ const maxPendingBytes = autoCommit?.maxPendingBytes ?? null;
98
+ const frequency = autoCommit?.frequency;
99
+ if (frequency === void 0 || frequency === "immediate") {
100
+ return { frequency: "immediate", intervalMs: null, maxPendingBytes };
101
+ }
102
+ if (typeof frequency === "number") {
103
+ if (!Number.isSafeInteger(frequency) || frequency <= 0) {
104
+ throw new ConfigurationError(
105
+ "autoCommit.frequency number must be a positive safe integer."
106
+ );
107
+ }
108
+ return { frequency: "scheduled", intervalMs: frequency, maxPendingBytes };
109
+ }
110
+ const intervalMs = parseFrequencyString(frequency);
111
+ return { frequency: "scheduled", intervalMs, maxPendingBytes };
112
+ };
113
+
114
+ // src/storage/drivers/IndexedDB/indexedDBConfig.ts
115
+ var ensureNonEmptyString = (value, optionName) => {
116
+ if (value.trim().length === 0) {
117
+ throw new ConfigurationError(`${optionName} must be a non-empty string.`);
118
+ }
119
+ };
120
+ var parseIndexedDBConfig = (config) => {
121
+ const databaseName = config?.databaseName ?? "frostpillar";
122
+ const objectStoreName = config?.objectStoreName ?? "frostpillar";
123
+ const version = config?.version ?? 1;
124
+ ensureNonEmptyString(databaseName, "indexedDB.databaseName");
125
+ ensureNonEmptyString(objectStoreName, "indexedDB.objectStoreName");
126
+ if (objectStoreName === "_meta") {
127
+ throw new ConfigurationError(
128
+ 'indexedDB.objectStoreName must not be "_meta" because it is reserved for internal metadata.'
129
+ );
130
+ }
131
+ if (!Number.isSafeInteger(version) || version <= 0) {
132
+ throw new ConfigurationError(
133
+ "indexedDB.version must be a positive safe integer."
134
+ );
135
+ }
136
+ return {
137
+ databaseName,
138
+ objectStoreName,
139
+ version
140
+ };
141
+ };
142
+
143
+ // src/storage/backend/asyncDurableAutoCommitController.ts
144
+ var AsyncDurableAutoCommitController = class {
145
+ constructor(autoCommit, onAutoCommitError) {
146
+ this.autoCommit = autoCommit;
147
+ this.onAutoCommitError = onAutoCommitError;
148
+ this.pendingAutoCommitBytes = 0;
149
+ this.dirtyFromClear = false;
150
+ this.autoCommitTimer = null;
151
+ this.commitInFlight = null;
152
+ this.pendingForegroundCommitRequest = false;
153
+ this.pendingBackgroundCommitRequest = false;
154
+ this.closed = false;
155
+ this.startAutoCommitSchedule();
156
+ }
157
+ handleRecordAppended(encodedBytes) {
158
+ if (this.autoCommit.frequency === "immediate") {
159
+ return this.commitNow();
160
+ }
161
+ this.pendingAutoCommitBytes += encodedBytes;
162
+ if (this.autoCommit.maxPendingBytes !== null && this.pendingAutoCommitBytes >= this.autoCommit.maxPendingBytes) {
163
+ return this.queueCommitRequest("foreground");
164
+ }
165
+ return Promise.resolve();
166
+ }
167
+ handleCleared() {
168
+ this.dirtyFromClear = true;
169
+ if (this.autoCommit.frequency === "immediate") {
170
+ return this.commitNow();
171
+ }
172
+ return this.queueCommitRequest("background");
173
+ }
174
+ commitNow() {
175
+ return this.queueCommitRequest("foreground");
176
+ }
177
+ async close() {
178
+ if (this.closed) {
179
+ return;
180
+ }
181
+ this.closed = true;
182
+ this.stopAutoCommitSchedule();
183
+ await this.waitForCommitSettlement();
184
+ let flushError = null;
185
+ if (this.pendingAutoCommitBytes > 0 || this.dirtyFromClear) {
186
+ try {
187
+ await this.executeSingleCommit();
188
+ this.pendingAutoCommitBytes = 0;
189
+ this.dirtyFromClear = false;
190
+ } catch (error) {
191
+ flushError = toErrorInstance(
192
+ error,
193
+ "Final close-time flush commit failed with a non-Error value."
194
+ );
195
+ }
196
+ }
197
+ let drainError = null;
198
+ try {
199
+ await this.onCloseAfterDrain();
200
+ } catch (error) {
201
+ drainError = toErrorInstance(
202
+ error,
203
+ "onCloseAfterDrain failed with a non-Error value."
204
+ );
205
+ }
206
+ if (flushError !== null && drainError !== null) {
207
+ throw createCloseAggregateError(flushError, drainError);
208
+ }
209
+ if (flushError !== null) {
210
+ throw flushError;
211
+ }
212
+ if (drainError !== null) {
213
+ throw drainError;
214
+ }
215
+ }
216
+ getPendingAutoCommitBytes() {
217
+ return this.pendingAutoCommitBytes;
218
+ }
219
+ onCloseAfterDrain() {
220
+ return Promise.resolve();
221
+ }
222
+ waitForCommitSettlement() {
223
+ if (this.commitInFlight === null) {
224
+ return Promise.resolve();
225
+ }
226
+ return this.commitInFlight.then(() => void 0).catch(() => void 0);
227
+ }
228
+ queueCommitRequest(requestType) {
229
+ if (requestType === "foreground") {
230
+ this.pendingForegroundCommitRequest = true;
231
+ } else {
232
+ this.pendingBackgroundCommitRequest = true;
233
+ }
234
+ if (this.commitInFlight === null) {
235
+ this.commitInFlight = this.runCommitLoop().finally(() => {
236
+ this.commitInFlight = null;
237
+ });
238
+ }
239
+ if (requestType === "background") {
240
+ return Promise.resolve();
241
+ }
242
+ return this.commitInFlight;
243
+ }
244
+ async runCommitLoop() {
245
+ let shouldContinue = true;
246
+ while (shouldContinue) {
247
+ const runForeground = this.pendingForegroundCommitRequest;
248
+ const runBackground = this.pendingBackgroundCommitRequest;
249
+ const runClear = this.dirtyFromClear;
250
+ this.pendingForegroundCommitRequest = false;
251
+ this.pendingBackgroundCommitRequest = false;
252
+ this.dirtyFromClear = false;
253
+ const shouldRunCommit = runForeground || runBackground && (this.pendingAutoCommitBytes > 0 || runClear);
254
+ if (!shouldRunCommit) {
255
+ shouldContinue = false;
256
+ continue;
257
+ }
258
+ try {
259
+ const committedPendingBytes = this.pendingAutoCommitBytes;
260
+ await this.executeSingleCommit();
261
+ this.pendingAutoCommitBytes = Math.max(
262
+ 0,
263
+ this.pendingAutoCommitBytes - committedPendingBytes
264
+ );
265
+ } catch (error) {
266
+ if (runClear) {
267
+ this.dirtyFromClear = true;
268
+ }
269
+ if (runForeground) {
270
+ throw toErrorInstance(
271
+ error,
272
+ "Foreground auto-commit failed with a non-Error value."
273
+ );
274
+ }
275
+ this.onAutoCommitError(error);
276
+ }
277
+ if (!this.pendingForegroundCommitRequest && !this.pendingBackgroundCommitRequest) {
278
+ shouldContinue = false;
279
+ }
280
+ }
281
+ }
282
+ startAutoCommitSchedule() {
283
+ if (this.autoCommit.frequency !== "scheduled" || this.autoCommit.intervalMs === null) {
284
+ return;
285
+ }
286
+ this.autoCommitTimer = setInterval(() => {
287
+ this.handleAutoCommitTick();
288
+ }, this.autoCommit.intervalMs);
289
+ if (typeof this.autoCommitTimer === "object" && this.autoCommitTimer !== null && "unref" in this.autoCommitTimer) {
290
+ this.autoCommitTimer.unref();
291
+ }
292
+ }
293
+ stopAutoCommitSchedule() {
294
+ if (this.autoCommitTimer === null) {
295
+ return;
296
+ }
297
+ clearInterval(this.autoCommitTimer);
298
+ this.autoCommitTimer = null;
299
+ }
300
+ handleAutoCommitTick() {
301
+ if (this.closed) {
302
+ return;
303
+ }
304
+ if (this.pendingAutoCommitBytes <= 0 && !this.dirtyFromClear) {
305
+ return;
306
+ }
307
+ void this.queueCommitRequest("background");
308
+ }
309
+ };
310
+ var readAggregateErrorConstructor = () => {
311
+ const candidate = globalThis.AggregateError;
312
+ if (typeof candidate !== "function") {
313
+ return null;
314
+ }
315
+ return candidate;
316
+ };
317
+ var createCloseAggregateError = (flushError, drainError) => {
318
+ const aggregateErrorConstructor = readAggregateErrorConstructor();
319
+ if (aggregateErrorConstructor !== null) {
320
+ return new aggregateErrorConstructor(
321
+ [flushError, drainError],
322
+ "Close failed: both final flush and drain produced errors."
323
+ );
324
+ }
325
+ const fallbackError = new Error(
326
+ "Close failed: both final flush and drain produced errors."
327
+ );
328
+ fallbackError.errors = [flushError, drainError];
329
+ return fallbackError;
330
+ };
331
+
332
+ // src/validation/metadata.ts
333
+ var parseNonNegativeSafeInteger = (value, fieldName, backendName) => {
334
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
335
+ throw new StorageEngineError(
336
+ `${backendName} ${fieldName} must be a non-negative safe integer.`
337
+ );
338
+ }
339
+ return value;
340
+ };
341
+
342
+ // src/storage/backend/encoding.ts
343
+ var computeUtf8ByteLengthJs = (value) => {
344
+ let bytes = 0;
345
+ for (let i = 0; i < value.length; i++) {
346
+ const code = value.charCodeAt(i);
347
+ if (code <= 127) {
348
+ bytes += 1;
349
+ } else if (code <= 2047) {
350
+ bytes += 2;
351
+ } else if (code >= 55296 && code <= 56319) {
352
+ const next = i + 1 < value.length ? value.charCodeAt(i + 1) : 0;
353
+ if (next >= 56320 && next <= 57343) {
354
+ bytes += 4;
355
+ i++;
356
+ } else {
357
+ bytes += 3;
358
+ }
359
+ } else if (code >= 56320 && code <= 57343) {
360
+ bytes += 3;
361
+ } else {
362
+ bytes += 3;
363
+ }
364
+ }
365
+ return bytes;
366
+ };
367
+ var hasBuffer = typeof Buffer !== "undefined" && typeof Buffer.byteLength === "function";
368
+ var computeUtf8ByteLength = hasBuffer ? (value) => Buffer.byteLength(value, "utf8") : computeUtf8ByteLengthJs;
369
+
370
+ // src/storage/drivers/IndexedDB/indexedDBBackend.ts
371
+ var IDB_MAGIC = "FPIDB_META";
372
+ var IDB_VERSION_VALUE = 2;
373
+ var IDB_META_STORE = "_meta";
374
+ var IDB_META_KEY = "config";
375
+ var detectGlobalIndexedDB = () => {
376
+ try {
377
+ const g = globalThis;
378
+ const idb = g.indexedDB;
379
+ if (idb === null || idb === void 0) {
380
+ return null;
381
+ }
382
+ return idb;
383
+ } catch {
384
+ return null;
385
+ }
386
+ };
387
+ var idbRequest = (req) => new Promise((resolve, reject) => {
388
+ req.onsuccess = (event) => {
389
+ resolve(event.target.result);
390
+ };
391
+ req.onerror = (event) => {
392
+ reject(
393
+ new StorageEngineError(
394
+ `IndexedDB request failed: ${String(event.target.error?.message ?? "unknown")}`
395
+ )
396
+ );
397
+ };
398
+ });
399
+ var idbTransaction = (tx) => new Promise((resolve, reject) => {
400
+ tx.oncomplete = () => {
401
+ resolve();
402
+ };
403
+ tx.onerror = () => {
404
+ reject(new StorageEngineError("IndexedDB transaction failed."));
405
+ };
406
+ });
407
+ var openIndexedDB = (factory, databaseName, objectStoreName, version) => new Promise((resolve, reject) => {
408
+ const request = factory.open(databaseName, version);
409
+ request.onupgradeneeded = (event) => {
410
+ const db = event.target.result;
411
+ if (db === null) {
412
+ return;
413
+ }
414
+ if (!db.objectStoreNames.contains(objectStoreName)) {
415
+ db.createObjectStore(objectStoreName);
416
+ }
417
+ if (!db.objectStoreNames.contains(IDB_META_STORE)) {
418
+ db.createObjectStore(IDB_META_STORE);
419
+ }
420
+ };
421
+ request.onsuccess = (event) => {
422
+ const db = event.target.result;
423
+ if (db === null) {
424
+ reject(new StorageEngineError("IndexedDB open returned null database."));
425
+ return;
426
+ }
427
+ resolve(db);
428
+ };
429
+ request.onerror = (event) => {
430
+ reject(
431
+ new StorageEngineError(
432
+ `IndexedDB open failed: ${String(event.target.error?.message ?? "unknown")}`
433
+ )
434
+ );
435
+ };
436
+ });
437
+ var loadIndexedDBSnapshot = async (db, _objectStoreName) => {
438
+ const tx = db.transaction(
439
+ [IDB_META_STORE],
440
+ "readonly"
441
+ );
442
+ const txDone = idbTransaction(tx);
443
+ const metaStore = tx.objectStore(IDB_META_STORE);
444
+ const metaRaw = await idbRequest(metaStore.get(IDB_META_KEY));
445
+ await txDone;
446
+ if (metaRaw === null || metaRaw === void 0) {
447
+ return { treeJSON: null, currentSizeBytes: 0, commitId: 0 };
448
+ }
449
+ const meta = metaRaw;
450
+ if (meta.magic !== IDB_MAGIC || meta.version !== IDB_VERSION_VALUE) {
451
+ throw new StorageEngineError("IndexedDB metadata magic/version mismatch.");
452
+ }
453
+ const commitId = parseNonNegativeSafeInteger(
454
+ meta.commitId,
455
+ "meta.commitId",
456
+ "IndexedDB"
457
+ );
458
+ const treeJSON = meta.treeJSON;
459
+ if (typeof treeJSON !== "object" || treeJSON === null || Array.isArray(treeJSON)) {
460
+ throw new PageCorruptionError("treeJSON must be a non-null plain object.");
461
+ }
462
+ const currentSizeBytes = computeUtf8ByteLength(JSON.stringify(treeJSON));
463
+ return { treeJSON, currentSizeBytes, commitId };
464
+ };
465
+ var commitIndexedDBSnapshot = async (db, _objectStoreName, treeJSON, commitId) => {
466
+ const tx = db.transaction(
467
+ [IDB_META_STORE],
468
+ "readwrite"
469
+ );
470
+ const txDone = idbTransaction(tx);
471
+ const metaStore = tx.objectStore(IDB_META_STORE);
472
+ const meta = {
473
+ magic: IDB_MAGIC,
474
+ version: IDB_VERSION_VALUE,
475
+ commitId,
476
+ treeJSON
477
+ };
478
+ metaStore.put(meta, IDB_META_KEY);
479
+ await txDone;
480
+ };
481
+
482
+ // src/storage/drivers/IndexedDB/indexedDBBackendController.ts
483
+ var IndexedDBBackendController = class _IndexedDBBackendController extends AsyncDurableAutoCommitController {
484
+ constructor(db, objectStoreName, commitId, autoCommit, getSnapshot, onAutoCommitError) {
485
+ super(autoCommit, onAutoCommitError);
486
+ this.db = db;
487
+ this.objectStoreName = objectStoreName;
488
+ this.commitId = commitId;
489
+ this.getSnapshot = getSnapshot;
490
+ }
491
+ static async create(options) {
492
+ const factory = detectGlobalIndexedDB();
493
+ if (factory === null) {
494
+ throw new UnsupportedBackendError(
495
+ "indexedDB is not available in the current runtime environment."
496
+ );
497
+ }
498
+ const idbConfig = parseIndexedDBConfig(options.config);
499
+ const { databaseName, objectStoreName, version } = idbConfig;
500
+ const autoCommit = parseAutoCommitConfig(options.autoCommit);
501
+ const db = await openIndexedDB(factory, databaseName, objectStoreName, version);
502
+ let loaded;
503
+ try {
504
+ loaded = await loadIndexedDBSnapshot(db, objectStoreName);
505
+ } catch (error) {
506
+ try {
507
+ db.close();
508
+ } catch {
509
+ }
510
+ throw toErrorInstance(
511
+ error,
512
+ "IndexedDB bootstrap failed with a non-Error value."
513
+ );
514
+ }
515
+ const controller = new _IndexedDBBackendController(
516
+ db,
517
+ objectStoreName,
518
+ loaded.commitId,
519
+ autoCommit,
520
+ options.getSnapshot,
521
+ options.onAutoCommitError
522
+ );
523
+ return {
524
+ controller,
525
+ initialTreeJSON: loaded.treeJSON,
526
+ initialCurrentSizeBytes: loaded.currentSizeBytes
527
+ };
528
+ }
529
+ async executeSingleCommit() {
530
+ const snapshot = this.getSnapshot();
531
+ if (this.commitId >= Number.MAX_SAFE_INTEGER) {
532
+ throw new StorageEngineError("IndexedDB commitId has reached Number.MAX_SAFE_INTEGER.");
533
+ }
534
+ const nextCommitId = this.commitId + 1;
535
+ await commitIndexedDBSnapshot(
536
+ this.db,
537
+ this.objectStoreName,
538
+ snapshot.treeJSON,
539
+ nextCommitId
540
+ );
541
+ this.commitId = nextCommitId;
542
+ }
543
+ onCloseAfterDrain() {
544
+ this.db.close();
545
+ return Promise.resolve();
546
+ }
547
+ };
548
+
549
+ // src/drivers/indexedDB.ts
550
+ var indexedDBDriver = (options = {}) => {
551
+ return {
552
+ init: async (callbacks) => {
553
+ const result = await IndexedDBBackendController.create({
554
+ config: options,
555
+ autoCommit: callbacks.autoCommit,
556
+ getSnapshot: callbacks.getSnapshot,
557
+ onAutoCommitError: callbacks.onAutoCommitError
558
+ });
559
+ return {
560
+ controller: result.controller,
561
+ initialTreeJSON: result.initialTreeJSON,
562
+ initialCurrentSizeBytes: result.initialCurrentSizeBytes
563
+ };
564
+ }
565
+ };
566
+ };
567
+ // Annotate the CommonJS export names for ESM import in node:
568
+ 0 && (module.exports = {
569
+ indexedDBDriver
570
+ });
@@ -0,0 +1,3 @@
1
+ import type { DatastoreDriver, IndexedDBConfig } from '../types.js';
2
+ export type IndexedDBDriverOptions = IndexedDBConfig;
3
+ export declare const indexedDBDriver: (options?: IndexedDBDriverOptions) => DatastoreDriver;
@@ -0,0 +1,18 @@
1
+ import { IndexedDBBackendController, } from '../storage/drivers/IndexedDB/indexedDBBackendController.js';
2
+ export const indexedDBDriver = (options = {}) => {
3
+ return {
4
+ init: async (callbacks) => {
5
+ const result = await IndexedDBBackendController.create({
6
+ config: options,
7
+ autoCommit: callbacks.autoCommit,
8
+ getSnapshot: callbacks.getSnapshot,
9
+ onAutoCommitError: callbacks.onAutoCommitError,
10
+ });
11
+ return {
12
+ controller: result.controller,
13
+ initialTreeJSON: result.initialTreeJSON,
14
+ initialCurrentSizeBytes: result.initialCurrentSizeBytes,
15
+ };
16
+ },
17
+ };
18
+ };