@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,668 @@
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/localStorage.ts
21
+ var localStorage_exports = {};
22
+ __export(localStorage_exports, {
23
+ localStorageDriver: () => localStorageDriver
24
+ });
25
+ module.exports = __toCommonJS(localStorage_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 QuotaExceededError = class extends FrostpillarError {
46
+ };
47
+ var toErrorInstance = (error, fallbackMessage) => {
48
+ if (error instanceof Error) {
49
+ return error;
50
+ }
51
+ return new Error(fallbackMessage, { cause: error });
52
+ };
53
+
54
+ // src/storage/backend/asyncDurableAutoCommitController.ts
55
+ var AsyncDurableAutoCommitController = class {
56
+ constructor(autoCommit, onAutoCommitError) {
57
+ this.autoCommit = autoCommit;
58
+ this.onAutoCommitError = onAutoCommitError;
59
+ this.pendingAutoCommitBytes = 0;
60
+ this.dirtyFromClear = false;
61
+ this.autoCommitTimer = null;
62
+ this.commitInFlight = null;
63
+ this.pendingForegroundCommitRequest = false;
64
+ this.pendingBackgroundCommitRequest = false;
65
+ this.closed = false;
66
+ this.startAutoCommitSchedule();
67
+ }
68
+ handleRecordAppended(encodedBytes) {
69
+ if (this.autoCommit.frequency === "immediate") {
70
+ return this.commitNow();
71
+ }
72
+ this.pendingAutoCommitBytes += encodedBytes;
73
+ if (this.autoCommit.maxPendingBytes !== null && this.pendingAutoCommitBytes >= this.autoCommit.maxPendingBytes) {
74
+ return this.queueCommitRequest("foreground");
75
+ }
76
+ return Promise.resolve();
77
+ }
78
+ handleCleared() {
79
+ this.dirtyFromClear = true;
80
+ if (this.autoCommit.frequency === "immediate") {
81
+ return this.commitNow();
82
+ }
83
+ return this.queueCommitRequest("background");
84
+ }
85
+ commitNow() {
86
+ return this.queueCommitRequest("foreground");
87
+ }
88
+ async close() {
89
+ if (this.closed) {
90
+ return;
91
+ }
92
+ this.closed = true;
93
+ this.stopAutoCommitSchedule();
94
+ await this.waitForCommitSettlement();
95
+ let flushError = null;
96
+ if (this.pendingAutoCommitBytes > 0 || this.dirtyFromClear) {
97
+ try {
98
+ await this.executeSingleCommit();
99
+ this.pendingAutoCommitBytes = 0;
100
+ this.dirtyFromClear = false;
101
+ } catch (error) {
102
+ flushError = toErrorInstance(
103
+ error,
104
+ "Final close-time flush commit failed with a non-Error value."
105
+ );
106
+ }
107
+ }
108
+ let drainError = null;
109
+ try {
110
+ await this.onCloseAfterDrain();
111
+ } catch (error) {
112
+ drainError = toErrorInstance(
113
+ error,
114
+ "onCloseAfterDrain failed with a non-Error value."
115
+ );
116
+ }
117
+ if (flushError !== null && drainError !== null) {
118
+ throw createCloseAggregateError(flushError, drainError);
119
+ }
120
+ if (flushError !== null) {
121
+ throw flushError;
122
+ }
123
+ if (drainError !== null) {
124
+ throw drainError;
125
+ }
126
+ }
127
+ getPendingAutoCommitBytes() {
128
+ return this.pendingAutoCommitBytes;
129
+ }
130
+ onCloseAfterDrain() {
131
+ return Promise.resolve();
132
+ }
133
+ waitForCommitSettlement() {
134
+ if (this.commitInFlight === null) {
135
+ return Promise.resolve();
136
+ }
137
+ return this.commitInFlight.then(() => void 0).catch(() => void 0);
138
+ }
139
+ queueCommitRequest(requestType) {
140
+ if (requestType === "foreground") {
141
+ this.pendingForegroundCommitRequest = true;
142
+ } else {
143
+ this.pendingBackgroundCommitRequest = true;
144
+ }
145
+ if (this.commitInFlight === null) {
146
+ this.commitInFlight = this.runCommitLoop().finally(() => {
147
+ this.commitInFlight = null;
148
+ });
149
+ }
150
+ if (requestType === "background") {
151
+ return Promise.resolve();
152
+ }
153
+ return this.commitInFlight;
154
+ }
155
+ async runCommitLoop() {
156
+ let shouldContinue = true;
157
+ while (shouldContinue) {
158
+ const runForeground = this.pendingForegroundCommitRequest;
159
+ const runBackground = this.pendingBackgroundCommitRequest;
160
+ const runClear = this.dirtyFromClear;
161
+ this.pendingForegroundCommitRequest = false;
162
+ this.pendingBackgroundCommitRequest = false;
163
+ this.dirtyFromClear = false;
164
+ const shouldRunCommit = runForeground || runBackground && (this.pendingAutoCommitBytes > 0 || runClear);
165
+ if (!shouldRunCommit) {
166
+ shouldContinue = false;
167
+ continue;
168
+ }
169
+ try {
170
+ const committedPendingBytes = this.pendingAutoCommitBytes;
171
+ await this.executeSingleCommit();
172
+ this.pendingAutoCommitBytes = Math.max(
173
+ 0,
174
+ this.pendingAutoCommitBytes - committedPendingBytes
175
+ );
176
+ } catch (error) {
177
+ if (runClear) {
178
+ this.dirtyFromClear = true;
179
+ }
180
+ if (runForeground) {
181
+ throw toErrorInstance(
182
+ error,
183
+ "Foreground auto-commit failed with a non-Error value."
184
+ );
185
+ }
186
+ this.onAutoCommitError(error);
187
+ }
188
+ if (!this.pendingForegroundCommitRequest && !this.pendingBackgroundCommitRequest) {
189
+ shouldContinue = false;
190
+ }
191
+ }
192
+ }
193
+ startAutoCommitSchedule() {
194
+ if (this.autoCommit.frequency !== "scheduled" || this.autoCommit.intervalMs === null) {
195
+ return;
196
+ }
197
+ this.autoCommitTimer = setInterval(() => {
198
+ this.handleAutoCommitTick();
199
+ }, this.autoCommit.intervalMs);
200
+ if (typeof this.autoCommitTimer === "object" && this.autoCommitTimer !== null && "unref" in this.autoCommitTimer) {
201
+ this.autoCommitTimer.unref();
202
+ }
203
+ }
204
+ stopAutoCommitSchedule() {
205
+ if (this.autoCommitTimer === null) {
206
+ return;
207
+ }
208
+ clearInterval(this.autoCommitTimer);
209
+ this.autoCommitTimer = null;
210
+ }
211
+ handleAutoCommitTick() {
212
+ if (this.closed) {
213
+ return;
214
+ }
215
+ if (this.pendingAutoCommitBytes <= 0 && !this.dirtyFromClear) {
216
+ return;
217
+ }
218
+ void this.queueCommitRequest("background");
219
+ }
220
+ };
221
+ var readAggregateErrorConstructor = () => {
222
+ const candidate = globalThis.AggregateError;
223
+ if (typeof candidate !== "function") {
224
+ return null;
225
+ }
226
+ return candidate;
227
+ };
228
+ var createCloseAggregateError = (flushError, drainError) => {
229
+ const aggregateErrorConstructor = readAggregateErrorConstructor();
230
+ if (aggregateErrorConstructor !== null) {
231
+ return new aggregateErrorConstructor(
232
+ [flushError, drainError],
233
+ "Close failed: both final flush and drain produced errors."
234
+ );
235
+ }
236
+ const fallbackError = new Error(
237
+ "Close failed: both final flush and drain produced errors."
238
+ );
239
+ fallbackError.errors = [flushError, drainError];
240
+ return fallbackError;
241
+ };
242
+
243
+ // src/storage/config/config.shared.ts
244
+ var BYTE_SIZE_MULTIPLIER = {
245
+ B: 1,
246
+ KB: 1024,
247
+ MB: 1024 * 1024,
248
+ GB: 1024 * 1024 * 1024
249
+ };
250
+ var FREQUENCY_REGEX = /^(\d+)(ms|s|m|h)$/;
251
+ var FREQUENCY_MULTIPLIER = {
252
+ ms: 1,
253
+ s: 1e3,
254
+ m: 60 * 1e3,
255
+ h: 60 * 60 * 1e3
256
+ };
257
+ var parseFrequencyString = (frequency) => {
258
+ const matched = FREQUENCY_REGEX.exec(frequency);
259
+ if (matched === null) {
260
+ throw new ConfigurationError(
261
+ "autoCommit.frequency string must be one of: <positive>ms, <positive>s, <positive>m, <positive>h."
262
+ );
263
+ }
264
+ const amount = Number(matched[1]);
265
+ if (!Number.isSafeInteger(amount) || amount <= 0) {
266
+ throw new ConfigurationError(
267
+ "autoCommit.frequency string amount must be a positive safe integer."
268
+ );
269
+ }
270
+ const unit = matched[2];
271
+ const multiplier = FREQUENCY_MULTIPLIER[unit];
272
+ const intervalMs = amount * multiplier;
273
+ if (!Number.isSafeInteger(intervalMs) || intervalMs <= 0) {
274
+ throw new ConfigurationError(
275
+ "autoCommit.frequency exceeds safe integer range."
276
+ );
277
+ }
278
+ return intervalMs;
279
+ };
280
+ var parseAutoCommitConfig = (autoCommit) => {
281
+ if (autoCommit?.maxPendingBytes !== void 0) {
282
+ if (!Number.isSafeInteger(autoCommit.maxPendingBytes) || autoCommit.maxPendingBytes <= 0) {
283
+ throw new ConfigurationError(
284
+ "autoCommit.maxPendingBytes must be a positive safe integer."
285
+ );
286
+ }
287
+ }
288
+ const maxPendingBytes = autoCommit?.maxPendingBytes ?? null;
289
+ const frequency = autoCommit?.frequency;
290
+ if (frequency === void 0 || frequency === "immediate") {
291
+ return { frequency: "immediate", intervalMs: null, maxPendingBytes };
292
+ }
293
+ if (typeof frequency === "number") {
294
+ if (!Number.isSafeInteger(frequency) || frequency <= 0) {
295
+ throw new ConfigurationError(
296
+ "autoCommit.frequency number must be a positive safe integer."
297
+ );
298
+ }
299
+ return { frequency: "scheduled", intervalMs: frequency, maxPendingBytes };
300
+ }
301
+ const intervalMs = parseFrequencyString(frequency);
302
+ return { frequency: "scheduled", intervalMs, maxPendingBytes };
303
+ };
304
+
305
+ // src/storage/drivers/localStorage/localStorageConfig.ts
306
+ var DEFAULT_LOCAL_STORAGE_MAX_CHUNK_CHARS = 32768;
307
+ var DEFAULT_LOCAL_STORAGE_MAX_CHUNKS = 64;
308
+ var parseLocalStorageConfig = (config) => {
309
+ const keyPrefix = config?.keyPrefix ?? "frostpillar";
310
+ const databaseKey = config?.databaseKey ?? "default";
311
+ const maxChunkChars = config?.maxChunkChars ?? DEFAULT_LOCAL_STORAGE_MAX_CHUNK_CHARS;
312
+ const maxChunks = config?.maxChunks ?? DEFAULT_LOCAL_STORAGE_MAX_CHUNKS;
313
+ if (!Number.isSafeInteger(maxChunkChars) || maxChunkChars <= 0) {
314
+ throw new ConfigurationError(
315
+ "localStorage.maxChunkChars must be a positive safe integer."
316
+ );
317
+ }
318
+ if (!Number.isSafeInteger(maxChunks) || maxChunks <= 0) {
319
+ throw new ConfigurationError(
320
+ "localStorage.maxChunks must be a positive safe integer."
321
+ );
322
+ }
323
+ return { keyPrefix, databaseKey, maxChunkChars, maxChunks };
324
+ };
325
+
326
+ // src/validation/metadata.ts
327
+ var parseNonNegativeSafeInteger = (value, fieldName, backendName) => {
328
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
329
+ throw new StorageEngineError(
330
+ `${backendName} ${fieldName} must be a non-negative safe integer.`
331
+ );
332
+ }
333
+ return value;
334
+ };
335
+
336
+ // src/storage/drivers/localStorage/localStorageLayout.ts
337
+ var manifestKey = (keyPrefix, databaseKey) => `${keyPrefix}:ls:${databaseKey}:manifest`;
338
+ var chunkKey = (keyPrefix, databaseKey, generation, index) => `${keyPrefix}:ls:${databaseKey}:g:${generation}:chunk:${index}`;
339
+ var cleanupGenerationChunks = (state, generation, knownChunkCount) => {
340
+ if (knownChunkCount !== null) {
341
+ if (knownChunkCount <= 0) {
342
+ return;
343
+ }
344
+ for (let i = 0; i < knownChunkCount; i += 1) {
345
+ state.adapter.removeItem(
346
+ chunkKey(state.keyPrefix, state.databaseKey, generation, i)
347
+ );
348
+ }
349
+ return;
350
+ }
351
+ if (state.maxChunks <= 0) {
352
+ return;
353
+ }
354
+ for (let i = 0; i < state.maxChunks; i += 1) {
355
+ const key = chunkKey(state.keyPrefix, state.databaseKey, generation, i);
356
+ if (state.adapter.getItem(key) !== null) {
357
+ state.adapter.removeItem(key);
358
+ }
359
+ }
360
+ };
361
+ var isQuotaBrowserError = (error) => {
362
+ if (!(error instanceof Error)) {
363
+ return false;
364
+ }
365
+ return error.name === "QuotaExceededError" || error.name === "NS_ERROR_DOM_QUOTA_REACHED";
366
+ };
367
+
368
+ // src/storage/backend/encoding.ts
369
+ var computeUtf8ByteLengthJs = (value) => {
370
+ let bytes = 0;
371
+ for (let i = 0; i < value.length; i++) {
372
+ const code = value.charCodeAt(i);
373
+ if (code <= 127) {
374
+ bytes += 1;
375
+ } else if (code <= 2047) {
376
+ bytes += 2;
377
+ } else if (code >= 55296 && code <= 56319) {
378
+ const next = i + 1 < value.length ? value.charCodeAt(i + 1) : 0;
379
+ if (next >= 56320 && next <= 57343) {
380
+ bytes += 4;
381
+ i++;
382
+ } else {
383
+ bytes += 3;
384
+ }
385
+ } else if (code >= 56320 && code <= 57343) {
386
+ bytes += 3;
387
+ } else {
388
+ bytes += 3;
389
+ }
390
+ }
391
+ return bytes;
392
+ };
393
+ var hasBuffer = typeof Buffer !== "undefined" && typeof Buffer.byteLength === "function";
394
+ var computeUtf8ByteLength = hasBuffer ? (value) => Buffer.byteLength(value, "utf8") : computeUtf8ByteLengthJs;
395
+
396
+ // src/storage/drivers/localStorage/localStorageBackend.ts
397
+ var LS_MAGIC = "FPLS_META";
398
+ var LS_VERSION = 2;
399
+ var detectGlobalLocalStorage = () => {
400
+ try {
401
+ const g = globalThis;
402
+ const ls = g.localStorage;
403
+ if (ls === null || ls === void 0) {
404
+ return null;
405
+ }
406
+ return ls;
407
+ } catch {
408
+ return null;
409
+ }
410
+ };
411
+ var createLocalStorageBackendState = (adapter, keyPrefix, databaseKey, maxChunkChars, maxChunks) => ({
412
+ adapter,
413
+ keyPrefix,
414
+ databaseKey,
415
+ maxChunkChars,
416
+ maxChunks,
417
+ activeGeneration: 0,
418
+ commitId: 0,
419
+ activeChunkCount: 0
420
+ });
421
+ var parseLocalStorageManifest = (manifestRaw, maxChunks) => {
422
+ let manifest;
423
+ try {
424
+ manifest = JSON.parse(manifestRaw);
425
+ } catch {
426
+ throw new StorageEngineError("localStorage manifest JSON is malformed.");
427
+ }
428
+ if (manifest.magic !== LS_MAGIC || manifest.version !== LS_VERSION) {
429
+ throw new StorageEngineError(
430
+ "localStorage manifest magic/version mismatch."
431
+ );
432
+ }
433
+ const chunkCount = parseNonNegativeSafeInteger(
434
+ manifest.chunkCount,
435
+ "manifest.chunkCount",
436
+ "localStorage"
437
+ );
438
+ if (chunkCount > maxChunks) {
439
+ throw new StorageEngineError(
440
+ `localStorage snapshot requires ${chunkCount} chunks but maxChunks is ${maxChunks}.`
441
+ );
442
+ }
443
+ return manifest;
444
+ };
445
+ var loadLocalStorageChunks = (state, activeGeneration, chunkCount) => {
446
+ const chunks = [];
447
+ for (let i = 0; i < chunkCount; i += 1) {
448
+ const cKey = chunkKey(
449
+ state.keyPrefix,
450
+ state.databaseKey,
451
+ activeGeneration,
452
+ i
453
+ );
454
+ const chunkValue = state.adapter.getItem(cKey);
455
+ if (typeof chunkValue !== "string") {
456
+ throw new StorageEngineError(
457
+ `localStorage chunk "${cKey}" is missing or not a string.`
458
+ );
459
+ }
460
+ chunks.push(chunkValue);
461
+ }
462
+ const treeJson = chunks.join("");
463
+ let parsedTreeJSON;
464
+ try {
465
+ parsedTreeJSON = JSON.parse(treeJson);
466
+ } catch {
467
+ throw new StorageEngineError("localStorage chunk data JSON is malformed.");
468
+ }
469
+ if (typeof parsedTreeJSON !== "object" || parsedTreeJSON === null || Array.isArray(parsedTreeJSON)) {
470
+ throw new PageCorruptionError("treeJSON must be a non-null plain object.");
471
+ }
472
+ return {
473
+ treeJSON: parsedTreeJSON,
474
+ rawJsonLength: computeUtf8ByteLength(treeJson)
475
+ };
476
+ };
477
+ var loadLocalStorageSnapshot = (state) => {
478
+ const mKey = manifestKey(state.keyPrefix, state.databaseKey);
479
+ const manifestRaw = state.adapter.getItem(mKey);
480
+ if (manifestRaw === null) {
481
+ return { treeJSON: null, currentSizeBytes: 0 };
482
+ }
483
+ const manifest = parseLocalStorageManifest(manifestRaw, state.maxChunks);
484
+ const activeGeneration = parseNonNegativeSafeInteger(
485
+ manifest.activeGeneration,
486
+ "manifest.activeGeneration",
487
+ "localStorage"
488
+ );
489
+ const commitId = parseNonNegativeSafeInteger(
490
+ manifest.commitId,
491
+ "manifest.commitId",
492
+ "localStorage"
493
+ );
494
+ const chunkCount = parseNonNegativeSafeInteger(
495
+ manifest.chunkCount,
496
+ "manifest.chunkCount",
497
+ "localStorage"
498
+ );
499
+ const { treeJSON, rawJsonLength } = loadLocalStorageChunks(state, activeGeneration, chunkCount);
500
+ const currentSizeBytes = rawJsonLength;
501
+ state.activeGeneration = activeGeneration;
502
+ state.commitId = commitId;
503
+ state.activeChunkCount = chunkCount;
504
+ return { treeJSON, currentSizeBytes };
505
+ };
506
+ var splitTreeJSONIntoChunks = (treeJSON, maxChunkChars, maxChunks, driverName) => {
507
+ const dataJson = JSON.stringify(treeJSON);
508
+ const chunks = [];
509
+ for (let i = 0; i < dataJson.length; i += maxChunkChars) {
510
+ chunks.push(dataJson.slice(i, i + maxChunkChars));
511
+ }
512
+ if (chunks.length > maxChunks) {
513
+ throw new QuotaExceededError(
514
+ `${driverName} snapshot requires ${chunks.length} chunks but maxChunks is ${maxChunks}.`
515
+ );
516
+ }
517
+ return chunks;
518
+ };
519
+ var ensureCommitCountersSafe = (state) => {
520
+ if (state.commitId >= Number.MAX_SAFE_INTEGER) {
521
+ throw new StorageEngineError(
522
+ "localStorage commitId has reached Number.MAX_SAFE_INTEGER."
523
+ );
524
+ }
525
+ if (state.activeGeneration >= Number.MAX_SAFE_INTEGER) {
526
+ throw new StorageEngineError(
527
+ "localStorage activeGeneration has reached Number.MAX_SAFE_INTEGER."
528
+ );
529
+ }
530
+ };
531
+ var prepareLocalStorageCommit = (state, treeJSON) => {
532
+ const nextCommitId = state.commitId + 1;
533
+ const nextGeneration = state.activeGeneration + 1;
534
+ const chunks = splitTreeJSONIntoChunks(
535
+ treeJSON,
536
+ state.maxChunkChars,
537
+ state.maxChunks,
538
+ "localStorage"
539
+ );
540
+ const newManifest = {
541
+ magic: LS_MAGIC,
542
+ version: LS_VERSION,
543
+ activeGeneration: nextGeneration,
544
+ commitId: nextCommitId,
545
+ chunkCount: chunks.length
546
+ };
547
+ return {
548
+ nextCommitId,
549
+ nextGeneration,
550
+ chunks,
551
+ manifestJson: JSON.stringify(newManifest)
552
+ };
553
+ };
554
+ var writeLocalStorageCommit = (state, preparedCommit) => {
555
+ try {
556
+ cleanupGenerationChunks(state, preparedCommit.nextGeneration, null);
557
+ for (let i = 0; i < preparedCommit.chunks.length; i += 1) {
558
+ state.adapter.setItem(
559
+ chunkKey(
560
+ state.keyPrefix,
561
+ state.databaseKey,
562
+ preparedCommit.nextGeneration,
563
+ i
564
+ ),
565
+ preparedCommit.chunks[i]
566
+ );
567
+ }
568
+ state.adapter.setItem(
569
+ manifestKey(state.keyPrefix, state.databaseKey),
570
+ preparedCommit.manifestJson
571
+ );
572
+ } catch (error) {
573
+ if (isQuotaBrowserError(error) || error instanceof QuotaExceededError) {
574
+ throw new QuotaExceededError(
575
+ "localStorage quota exceeded during commit."
576
+ );
577
+ }
578
+ throw new StorageEngineError("localStorage write failed during commit.");
579
+ }
580
+ };
581
+ var commitLocalStorageSnapshot = (state, treeJSON) => {
582
+ ensureCommitCountersSafe(state);
583
+ const preparedCommit = prepareLocalStorageCommit(state, treeJSON);
584
+ writeLocalStorageCommit(state, preparedCommit);
585
+ const previousGeneration = state.activeGeneration;
586
+ const previousChunkCount = state.activeChunkCount;
587
+ state.activeGeneration = preparedCommit.nextGeneration;
588
+ state.commitId = preparedCommit.nextCommitId;
589
+ state.activeChunkCount = preparedCommit.chunks.length;
590
+ cleanupGenerationChunks(
591
+ state,
592
+ previousGeneration,
593
+ previousChunkCount
594
+ );
595
+ };
596
+
597
+ // src/storage/drivers/localStorage/localStorageBackendController.ts
598
+ var LocalStorageBackendController = class _LocalStorageBackendController extends AsyncDurableAutoCommitController {
599
+ constructor(backend, autoCommit, getSnapshot, onAutoCommitError) {
600
+ super(autoCommit, onAutoCommitError);
601
+ this.backend = backend;
602
+ this.getSnapshot = getSnapshot;
603
+ }
604
+ static create(options) {
605
+ const adapter = detectGlobalLocalStorage();
606
+ if (adapter === null) {
607
+ throw new UnsupportedBackendError(
608
+ "localStorage is not available in the current runtime environment."
609
+ );
610
+ }
611
+ const lsConfig = parseLocalStorageConfig(options.config);
612
+ const autoCommit = parseAutoCommitConfig(options.autoCommit);
613
+ const backend = createLocalStorageBackendState(
614
+ adapter,
615
+ lsConfig.keyPrefix,
616
+ lsConfig.databaseKey,
617
+ lsConfig.maxChunkChars,
618
+ lsConfig.maxChunks
619
+ );
620
+ const loaded = loadLocalStorageSnapshot(backend);
621
+ const controller = new _LocalStorageBackendController(
622
+ backend,
623
+ autoCommit,
624
+ options.getSnapshot,
625
+ options.onAutoCommitError
626
+ );
627
+ return {
628
+ controller,
629
+ initialTreeJSON: loaded.treeJSON,
630
+ initialCurrentSizeBytes: loaded.currentSizeBytes
631
+ };
632
+ }
633
+ executeSingleCommit() {
634
+ const snapshot = this.getSnapshot();
635
+ commitLocalStorageSnapshot(
636
+ this.backend,
637
+ snapshot.treeJSON
638
+ );
639
+ return Promise.resolve();
640
+ }
641
+ };
642
+
643
+ // src/drivers/localStorage.ts
644
+ var localStorageDriver = (options = {}) => {
645
+ return {
646
+ init: (callbacks) => {
647
+ const result = LocalStorageBackendController.create({
648
+ config: options,
649
+ autoCommit: callbacks.autoCommit,
650
+ getSnapshot: callbacks.getSnapshot,
651
+ onAutoCommitError: callbacks.onAutoCommitError
652
+ });
653
+ return {
654
+ controller: result.controller,
655
+ initialTreeJSON: result.initialTreeJSON,
656
+ initialCurrentSizeBytes: result.initialCurrentSizeBytes
657
+ };
658
+ },
659
+ resolveBackendLimitBytes: () => {
660
+ const { maxChunkChars, maxChunks } = parseLocalStorageConfig(options);
661
+ return maxChunkChars * maxChunks;
662
+ }
663
+ };
664
+ };
665
+ // Annotate the CommonJS export names for ESM import in node:
666
+ 0 && (module.exports = {
667
+ localStorageDriver
668
+ });
@@ -0,0 +1,3 @@
1
+ import type { DatastoreDriver, LocalStorageConfig } from '../types.js';
2
+ export type LocalStorageDriverOptions = LocalStorageConfig;
3
+ export declare const localStorageDriver: (options?: LocalStorageDriverOptions) => DatastoreDriver;
@@ -0,0 +1,23 @@
1
+ import { LocalStorageBackendController, } from '../storage/drivers/localStorage/localStorageBackendController.js';
2
+ import { parseLocalStorageConfig, } from '../storage/drivers/localStorage/localStorageConfig.js';
3
+ export const localStorageDriver = (options = {}) => {
4
+ return {
5
+ init: (callbacks) => {
6
+ const result = LocalStorageBackendController.create({
7
+ config: options,
8
+ autoCommit: callbacks.autoCommit,
9
+ getSnapshot: callbacks.getSnapshot,
10
+ onAutoCommitError: callbacks.onAutoCommitError,
11
+ });
12
+ return {
13
+ controller: result.controller,
14
+ initialTreeJSON: result.initialTreeJSON,
15
+ initialCurrentSizeBytes: result.initialCurrentSizeBytes,
16
+ };
17
+ },
18
+ resolveBackendLimitBytes: () => {
19
+ const { maxChunkChars, maxChunks } = parseLocalStorageConfig(options);
20
+ return maxChunkChars * maxChunks;
21
+ },
22
+ };
23
+ };