@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,550 @@
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/opfs.ts
21
+ var opfs_exports = {};
22
+ __export(opfs_exports, {
23
+ opfsDriver: () => opfsDriver
24
+ });
25
+ module.exports = __toCommonJS(opfs_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 toStorageEngineError = (error, fallbackMessage) => {
46
+ if (error instanceof StorageEngineError) {
47
+ return error;
48
+ }
49
+ if (error instanceof Error) {
50
+ return new StorageEngineError(`${fallbackMessage}: ${error.message}`, {
51
+ cause: error
52
+ });
53
+ }
54
+ return new StorageEngineError(fallbackMessage, { cause: error });
55
+ };
56
+ var toErrorInstance = (error, fallbackMessage) => {
57
+ if (error instanceof Error) {
58
+ return error;
59
+ }
60
+ return new Error(fallbackMessage, { cause: error });
61
+ };
62
+
63
+ // src/storage/config/config.shared.ts
64
+ var BYTE_SIZE_MULTIPLIER = {
65
+ B: 1,
66
+ KB: 1024,
67
+ MB: 1024 * 1024,
68
+ GB: 1024 * 1024 * 1024
69
+ };
70
+ var FREQUENCY_REGEX = /^(\d+)(ms|s|m|h)$/;
71
+ var FREQUENCY_MULTIPLIER = {
72
+ ms: 1,
73
+ s: 1e3,
74
+ m: 60 * 1e3,
75
+ h: 60 * 60 * 1e3
76
+ };
77
+ var parseFrequencyString = (frequency) => {
78
+ const matched = FREQUENCY_REGEX.exec(frequency);
79
+ if (matched === null) {
80
+ throw new ConfigurationError(
81
+ "autoCommit.frequency string must be one of: <positive>ms, <positive>s, <positive>m, <positive>h."
82
+ );
83
+ }
84
+ const amount = Number(matched[1]);
85
+ if (!Number.isSafeInteger(amount) || amount <= 0) {
86
+ throw new ConfigurationError(
87
+ "autoCommit.frequency string amount must be a positive safe integer."
88
+ );
89
+ }
90
+ const unit = matched[2];
91
+ const multiplier = FREQUENCY_MULTIPLIER[unit];
92
+ const intervalMs = amount * multiplier;
93
+ if (!Number.isSafeInteger(intervalMs) || intervalMs <= 0) {
94
+ throw new ConfigurationError(
95
+ "autoCommit.frequency exceeds safe integer range."
96
+ );
97
+ }
98
+ return intervalMs;
99
+ };
100
+ var parseAutoCommitConfig = (autoCommit) => {
101
+ if (autoCommit?.maxPendingBytes !== void 0) {
102
+ if (!Number.isSafeInteger(autoCommit.maxPendingBytes) || autoCommit.maxPendingBytes <= 0) {
103
+ throw new ConfigurationError(
104
+ "autoCommit.maxPendingBytes must be a positive safe integer."
105
+ );
106
+ }
107
+ }
108
+ const maxPendingBytes = autoCommit?.maxPendingBytes ?? null;
109
+ const frequency = autoCommit?.frequency;
110
+ if (frequency === void 0 || frequency === "immediate") {
111
+ return { frequency: "immediate", intervalMs: null, maxPendingBytes };
112
+ }
113
+ if (typeof frequency === "number") {
114
+ if (!Number.isSafeInteger(frequency) || frequency <= 0) {
115
+ throw new ConfigurationError(
116
+ "autoCommit.frequency number must be a positive safe integer."
117
+ );
118
+ }
119
+ return { frequency: "scheduled", intervalMs: frequency, maxPendingBytes };
120
+ }
121
+ const intervalMs = parseFrequencyString(frequency);
122
+ return { frequency: "scheduled", intervalMs, maxPendingBytes };
123
+ };
124
+
125
+ // src/storage/backend/asyncDurableAutoCommitController.ts
126
+ var AsyncDurableAutoCommitController = class {
127
+ constructor(autoCommit, onAutoCommitError) {
128
+ this.autoCommit = autoCommit;
129
+ this.onAutoCommitError = onAutoCommitError;
130
+ this.pendingAutoCommitBytes = 0;
131
+ this.dirtyFromClear = false;
132
+ this.autoCommitTimer = null;
133
+ this.commitInFlight = null;
134
+ this.pendingForegroundCommitRequest = false;
135
+ this.pendingBackgroundCommitRequest = false;
136
+ this.closed = false;
137
+ this.startAutoCommitSchedule();
138
+ }
139
+ handleRecordAppended(encodedBytes) {
140
+ if (this.autoCommit.frequency === "immediate") {
141
+ return this.commitNow();
142
+ }
143
+ this.pendingAutoCommitBytes += encodedBytes;
144
+ if (this.autoCommit.maxPendingBytes !== null && this.pendingAutoCommitBytes >= this.autoCommit.maxPendingBytes) {
145
+ return this.queueCommitRequest("foreground");
146
+ }
147
+ return Promise.resolve();
148
+ }
149
+ handleCleared() {
150
+ this.dirtyFromClear = true;
151
+ if (this.autoCommit.frequency === "immediate") {
152
+ return this.commitNow();
153
+ }
154
+ return this.queueCommitRequest("background");
155
+ }
156
+ commitNow() {
157
+ return this.queueCommitRequest("foreground");
158
+ }
159
+ async close() {
160
+ if (this.closed) {
161
+ return;
162
+ }
163
+ this.closed = true;
164
+ this.stopAutoCommitSchedule();
165
+ await this.waitForCommitSettlement();
166
+ let flushError = null;
167
+ if (this.pendingAutoCommitBytes > 0 || this.dirtyFromClear) {
168
+ try {
169
+ await this.executeSingleCommit();
170
+ this.pendingAutoCommitBytes = 0;
171
+ this.dirtyFromClear = false;
172
+ } catch (error) {
173
+ flushError = toErrorInstance(
174
+ error,
175
+ "Final close-time flush commit failed with a non-Error value."
176
+ );
177
+ }
178
+ }
179
+ let drainError = null;
180
+ try {
181
+ await this.onCloseAfterDrain();
182
+ } catch (error) {
183
+ drainError = toErrorInstance(
184
+ error,
185
+ "onCloseAfterDrain failed with a non-Error value."
186
+ );
187
+ }
188
+ if (flushError !== null && drainError !== null) {
189
+ throw createCloseAggregateError(flushError, drainError);
190
+ }
191
+ if (flushError !== null) {
192
+ throw flushError;
193
+ }
194
+ if (drainError !== null) {
195
+ throw drainError;
196
+ }
197
+ }
198
+ getPendingAutoCommitBytes() {
199
+ return this.pendingAutoCommitBytes;
200
+ }
201
+ onCloseAfterDrain() {
202
+ return Promise.resolve();
203
+ }
204
+ waitForCommitSettlement() {
205
+ if (this.commitInFlight === null) {
206
+ return Promise.resolve();
207
+ }
208
+ return this.commitInFlight.then(() => void 0).catch(() => void 0);
209
+ }
210
+ queueCommitRequest(requestType) {
211
+ if (requestType === "foreground") {
212
+ this.pendingForegroundCommitRequest = true;
213
+ } else {
214
+ this.pendingBackgroundCommitRequest = true;
215
+ }
216
+ if (this.commitInFlight === null) {
217
+ this.commitInFlight = this.runCommitLoop().finally(() => {
218
+ this.commitInFlight = null;
219
+ });
220
+ }
221
+ if (requestType === "background") {
222
+ return Promise.resolve();
223
+ }
224
+ return this.commitInFlight;
225
+ }
226
+ async runCommitLoop() {
227
+ let shouldContinue = true;
228
+ while (shouldContinue) {
229
+ const runForeground = this.pendingForegroundCommitRequest;
230
+ const runBackground = this.pendingBackgroundCommitRequest;
231
+ const runClear = this.dirtyFromClear;
232
+ this.pendingForegroundCommitRequest = false;
233
+ this.pendingBackgroundCommitRequest = false;
234
+ this.dirtyFromClear = false;
235
+ const shouldRunCommit = runForeground || runBackground && (this.pendingAutoCommitBytes > 0 || runClear);
236
+ if (!shouldRunCommit) {
237
+ shouldContinue = false;
238
+ continue;
239
+ }
240
+ try {
241
+ const committedPendingBytes = this.pendingAutoCommitBytes;
242
+ await this.executeSingleCommit();
243
+ this.pendingAutoCommitBytes = Math.max(
244
+ 0,
245
+ this.pendingAutoCommitBytes - committedPendingBytes
246
+ );
247
+ } catch (error) {
248
+ if (runClear) {
249
+ this.dirtyFromClear = true;
250
+ }
251
+ if (runForeground) {
252
+ throw toErrorInstance(
253
+ error,
254
+ "Foreground auto-commit failed with a non-Error value."
255
+ );
256
+ }
257
+ this.onAutoCommitError(error);
258
+ }
259
+ if (!this.pendingForegroundCommitRequest && !this.pendingBackgroundCommitRequest) {
260
+ shouldContinue = false;
261
+ }
262
+ }
263
+ }
264
+ startAutoCommitSchedule() {
265
+ if (this.autoCommit.frequency !== "scheduled" || this.autoCommit.intervalMs === null) {
266
+ return;
267
+ }
268
+ this.autoCommitTimer = setInterval(() => {
269
+ this.handleAutoCommitTick();
270
+ }, this.autoCommit.intervalMs);
271
+ if (typeof this.autoCommitTimer === "object" && this.autoCommitTimer !== null && "unref" in this.autoCommitTimer) {
272
+ this.autoCommitTimer.unref();
273
+ }
274
+ }
275
+ stopAutoCommitSchedule() {
276
+ if (this.autoCommitTimer === null) {
277
+ return;
278
+ }
279
+ clearInterval(this.autoCommitTimer);
280
+ this.autoCommitTimer = null;
281
+ }
282
+ handleAutoCommitTick() {
283
+ if (this.closed) {
284
+ return;
285
+ }
286
+ if (this.pendingAutoCommitBytes <= 0 && !this.dirtyFromClear) {
287
+ return;
288
+ }
289
+ void this.queueCommitRequest("background");
290
+ }
291
+ };
292
+ var readAggregateErrorConstructor = () => {
293
+ const candidate = globalThis.AggregateError;
294
+ if (typeof candidate !== "function") {
295
+ return null;
296
+ }
297
+ return candidate;
298
+ };
299
+ var createCloseAggregateError = (flushError, drainError) => {
300
+ const aggregateErrorConstructor = readAggregateErrorConstructor();
301
+ if (aggregateErrorConstructor !== null) {
302
+ return new aggregateErrorConstructor(
303
+ [flushError, drainError],
304
+ "Close failed: both final flush and drain produced errors."
305
+ );
306
+ }
307
+ const fallbackError = new Error(
308
+ "Close failed: both final flush and drain produced errors."
309
+ );
310
+ fallbackError.errors = [flushError, drainError];
311
+ return fallbackError;
312
+ };
313
+
314
+ // src/validation/metadata.ts
315
+ var parseNonNegativeSafeInteger = (value, fieldName, backendName) => {
316
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
317
+ throw new StorageEngineError(
318
+ `${backendName} ${fieldName} must be a non-negative safe integer.`
319
+ );
320
+ }
321
+ return value;
322
+ };
323
+
324
+ // src/storage/backend/encoding.ts
325
+ var computeUtf8ByteLengthJs = (value) => {
326
+ let bytes = 0;
327
+ for (let i = 0; i < value.length; i++) {
328
+ const code = value.charCodeAt(i);
329
+ if (code <= 127) {
330
+ bytes += 1;
331
+ } else if (code <= 2047) {
332
+ bytes += 2;
333
+ } else if (code >= 55296 && code <= 56319) {
334
+ const next = i + 1 < value.length ? value.charCodeAt(i + 1) : 0;
335
+ if (next >= 56320 && next <= 57343) {
336
+ bytes += 4;
337
+ i++;
338
+ } else {
339
+ bytes += 3;
340
+ }
341
+ } else if (code >= 56320 && code <= 57343) {
342
+ bytes += 3;
343
+ } else {
344
+ bytes += 3;
345
+ }
346
+ }
347
+ return bytes;
348
+ };
349
+ var hasBuffer = typeof Buffer !== "undefined" && typeof Buffer.byteLength === "function";
350
+ var computeUtf8ByteLength = hasBuffer ? (value) => Buffer.byteLength(value, "utf8") : computeUtf8ByteLengthJs;
351
+
352
+ // src/storage/drivers/opfs/opfsBackend.ts
353
+ var OPFS_MAGIC = "FPOPFS_META";
354
+ var OPFS_VERSION_VALUE = 2;
355
+ var META_FILE = "meta.json";
356
+ var DATA_FILE_A = "data-a.json";
357
+ var DATA_FILE_B = "data-b.json";
358
+ var isNotFoundError = (error) => {
359
+ if (!(error instanceof Error)) {
360
+ return false;
361
+ }
362
+ return error.name === "NotFoundError";
363
+ };
364
+ var isManifestObject = (value) => {
365
+ return value !== null && typeof value === "object" && !Array.isArray(value);
366
+ };
367
+ var detectGlobalOpfs = () => {
368
+ try {
369
+ const nav = globalThis;
370
+ if (typeof nav.navigator?.storage?.getDirectory === "function") {
371
+ return nav.navigator.storage;
372
+ }
373
+ return null;
374
+ } catch {
375
+ return null;
376
+ }
377
+ };
378
+ var openOpfsDirectory = async (storageRoot, directoryName) => {
379
+ const root = await storageRoot.getDirectory();
380
+ return root.getDirectoryHandle(directoryName, { create: true });
381
+ };
382
+ var parseOpfsManifest = (metaText) => {
383
+ let manifestRaw;
384
+ try {
385
+ manifestRaw = JSON.parse(metaText);
386
+ } catch {
387
+ throw new StorageEngineError("OPFS meta.json JSON is malformed.");
388
+ }
389
+ if (!isManifestObject(manifestRaw)) {
390
+ throw new StorageEngineError("OPFS meta.json must be a JSON object.");
391
+ }
392
+ const manifest = manifestRaw;
393
+ if (manifest.magic !== OPFS_MAGIC || manifest.version !== OPFS_VERSION_VALUE) {
394
+ throw new StorageEngineError("OPFS meta.json magic/version mismatch.");
395
+ }
396
+ if (manifest.activeData !== "a" && manifest.activeData !== "b") {
397
+ throw new StorageEngineError('OPFS meta.json activeData must be "a" or "b".');
398
+ }
399
+ const commitId = parseNonNegativeSafeInteger(
400
+ manifest.commitId,
401
+ "meta.json commitId",
402
+ "OPFS"
403
+ );
404
+ return { manifest, commitId, activeData: manifest.activeData };
405
+ };
406
+ var loadOpfsDataFile = async (dir, dataFileName) => {
407
+ let dataText;
408
+ try {
409
+ const dataHandle = await dir.getFileHandle(dataFileName, { create: false });
410
+ const dataFile = await dataHandle.getFile();
411
+ dataText = await dataFile.text();
412
+ } catch {
413
+ throw new StorageEngineError(`OPFS active data file "${dataFileName}" not found.`);
414
+ }
415
+ let parsedTreeJSON;
416
+ try {
417
+ parsedTreeJSON = JSON.parse(dataText);
418
+ } catch {
419
+ throw new StorageEngineError("OPFS data file JSON is malformed.");
420
+ }
421
+ if (typeof parsedTreeJSON !== "object" || parsedTreeJSON === null || Array.isArray(parsedTreeJSON)) {
422
+ throw new PageCorruptionError("treeJSON must be a non-null plain object.");
423
+ }
424
+ return {
425
+ treeJSON: parsedTreeJSON,
426
+ rawJsonLength: computeUtf8ByteLength(dataText)
427
+ };
428
+ };
429
+ var loadOpfsSnapshot = async (dir) => {
430
+ let metaText;
431
+ try {
432
+ const metaHandle = await dir.getFileHandle(META_FILE, { create: false });
433
+ const metaFile = await metaHandle.getFile();
434
+ metaText = await metaFile.text();
435
+ } catch (error) {
436
+ if (!isNotFoundError(error)) {
437
+ throw toStorageEngineError(error, "OPFS meta.json read failed");
438
+ }
439
+ return {
440
+ treeJSON: null,
441
+ currentSizeBytes: 0,
442
+ commitId: 0,
443
+ activeData: "a"
444
+ };
445
+ }
446
+ const { commitId, activeData } = parseOpfsManifest(metaText);
447
+ const dataFileName = activeData === "a" ? DATA_FILE_A : DATA_FILE_B;
448
+ const { treeJSON, rawJsonLength } = await loadOpfsDataFile(dir, dataFileName);
449
+ const currentSizeBytes = rawJsonLength;
450
+ return { treeJSON, currentSizeBytes, commitId, activeData };
451
+ };
452
+ var commitOpfsSnapshot = async (dir, currentActiveData, treeJSON, commitId) => {
453
+ const nextActiveData = currentActiveData === "a" ? "b" : "a";
454
+ const dataFileName = nextActiveData === "a" ? DATA_FILE_A : DATA_FILE_B;
455
+ const dataJson = JSON.stringify(treeJSON);
456
+ try {
457
+ const dataHandle = await dir.getFileHandle(dataFileName, { create: true });
458
+ const dataWritable = await dataHandle.createWritable();
459
+ await dataWritable.write(dataJson);
460
+ await dataWritable.close();
461
+ const newManifest = {
462
+ magic: OPFS_MAGIC,
463
+ version: OPFS_VERSION_VALUE,
464
+ activeData: nextActiveData,
465
+ commitId
466
+ };
467
+ const metaHandle = await dir.getFileHandle(META_FILE, { create: true });
468
+ const metaWritable = await metaHandle.createWritable();
469
+ await metaWritable.write(JSON.stringify(newManifest));
470
+ await metaWritable.close();
471
+ } catch (error) {
472
+ throw toStorageEngineError(error, "OPFS commit failed");
473
+ }
474
+ return nextActiveData;
475
+ };
476
+
477
+ // src/storage/drivers/opfs/opfsBackendController.ts
478
+ var DEFAULT_DIRECTORY_NAME = "frostpillar";
479
+ var OpfsBackendController = class _OpfsBackendController extends AsyncDurableAutoCommitController {
480
+ constructor(dir, activeData, commitId, autoCommit, getSnapshot, onAutoCommitError) {
481
+ super(autoCommit, onAutoCommitError);
482
+ this.dir = dir;
483
+ this.activeData = activeData;
484
+ this.commitId = commitId;
485
+ this.getSnapshot = getSnapshot;
486
+ }
487
+ static async create(options) {
488
+ const storageRoot = detectGlobalOpfs();
489
+ if (storageRoot === null) {
490
+ throw new UnsupportedBackendError(
491
+ "opfs (Origin Private File System) is not available in the current runtime environment."
492
+ );
493
+ }
494
+ const opfsConfig = options.config;
495
+ const directoryName = opfsConfig?.directoryName ?? DEFAULT_DIRECTORY_NAME;
496
+ const autoCommit = parseAutoCommitConfig(options.autoCommit);
497
+ const dir = await openOpfsDirectory(storageRoot, directoryName);
498
+ const loaded = await loadOpfsSnapshot(dir);
499
+ const controller = new _OpfsBackendController(
500
+ dir,
501
+ loaded.activeData,
502
+ loaded.commitId,
503
+ autoCommit,
504
+ options.getSnapshot,
505
+ options.onAutoCommitError
506
+ );
507
+ return {
508
+ controller,
509
+ initialTreeJSON: loaded.treeJSON,
510
+ initialCurrentSizeBytes: loaded.currentSizeBytes
511
+ };
512
+ }
513
+ async executeSingleCommit() {
514
+ const snapshot = this.getSnapshot();
515
+ if (this.commitId >= Number.MAX_SAFE_INTEGER) {
516
+ throw new StorageEngineError("OPFS commitId has reached Number.MAX_SAFE_INTEGER.");
517
+ }
518
+ const nextCommitId = this.commitId + 1;
519
+ this.activeData = await commitOpfsSnapshot(
520
+ this.dir,
521
+ this.activeData,
522
+ snapshot.treeJSON,
523
+ nextCommitId
524
+ );
525
+ this.commitId = nextCommitId;
526
+ }
527
+ };
528
+
529
+ // src/drivers/opfs.ts
530
+ var opfsDriver = (options = {}) => {
531
+ return {
532
+ init: async (callbacks) => {
533
+ const result = await OpfsBackendController.create({
534
+ config: options,
535
+ autoCommit: callbacks.autoCommit,
536
+ getSnapshot: callbacks.getSnapshot,
537
+ onAutoCommitError: callbacks.onAutoCommitError
538
+ });
539
+ return {
540
+ controller: result.controller,
541
+ initialTreeJSON: result.initialTreeJSON,
542
+ initialCurrentSizeBytes: result.initialCurrentSizeBytes
543
+ };
544
+ }
545
+ };
546
+ };
547
+ // Annotate the CommonJS export names for ESM import in node:
548
+ 0 && (module.exports = {
549
+ opfsDriver
550
+ });
@@ -0,0 +1,3 @@
1
+ import type { DatastoreDriver, OpfsConfig } from '../types.js';
2
+ export type OpfsDriverOptions = OpfsConfig;
3
+ export declare const opfsDriver: (options?: OpfsDriverOptions) => DatastoreDriver;
@@ -0,0 +1,18 @@
1
+ import { OpfsBackendController, } from '../storage/drivers/opfs/opfsBackendController.js';
2
+ export const opfsDriver = (options = {}) => {
3
+ return {
4
+ init: async (callbacks) => {
5
+ const result = await OpfsBackendController.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
+ };