@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,898 @@
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/syncStorage.ts
21
+ var syncStorage_exports = {};
22
+ __export(syncStorage_exports, {
23
+ syncStorageDriver: () => syncStorageDriver
24
+ });
25
+ module.exports = __toCommonJS(syncStorage_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/config/config.shared.ts
55
+ var BYTE_SIZE_MULTIPLIER = {
56
+ B: 1,
57
+ KB: 1024,
58
+ MB: 1024 * 1024,
59
+ GB: 1024 * 1024 * 1024
60
+ };
61
+ var FREQUENCY_REGEX = /^(\d+)(ms|s|m|h)$/;
62
+ var FREQUENCY_MULTIPLIER = {
63
+ ms: 1,
64
+ s: 1e3,
65
+ m: 60 * 1e3,
66
+ h: 60 * 60 * 1e3
67
+ };
68
+ var parseFrequencyString = (frequency) => {
69
+ const matched = FREQUENCY_REGEX.exec(frequency);
70
+ if (matched === null) {
71
+ throw new ConfigurationError(
72
+ "autoCommit.frequency string must be one of: <positive>ms, <positive>s, <positive>m, <positive>h."
73
+ );
74
+ }
75
+ const amount = Number(matched[1]);
76
+ if (!Number.isSafeInteger(amount) || amount <= 0) {
77
+ throw new ConfigurationError(
78
+ "autoCommit.frequency string amount must be a positive safe integer."
79
+ );
80
+ }
81
+ const unit = matched[2];
82
+ const multiplier = FREQUENCY_MULTIPLIER[unit];
83
+ const intervalMs = amount * multiplier;
84
+ if (!Number.isSafeInteger(intervalMs) || intervalMs <= 0) {
85
+ throw new ConfigurationError(
86
+ "autoCommit.frequency exceeds safe integer range."
87
+ );
88
+ }
89
+ return intervalMs;
90
+ };
91
+ var parseAutoCommitConfig = (autoCommit) => {
92
+ if (autoCommit?.maxPendingBytes !== void 0) {
93
+ if (!Number.isSafeInteger(autoCommit.maxPendingBytes) || autoCommit.maxPendingBytes <= 0) {
94
+ throw new ConfigurationError(
95
+ "autoCommit.maxPendingBytes must be a positive safe integer."
96
+ );
97
+ }
98
+ }
99
+ const maxPendingBytes = autoCommit?.maxPendingBytes ?? null;
100
+ const frequency = autoCommit?.frequency;
101
+ if (frequency === void 0 || frequency === "immediate") {
102
+ return { frequency: "immediate", intervalMs: null, maxPendingBytes };
103
+ }
104
+ if (typeof frequency === "number") {
105
+ if (!Number.isSafeInteger(frequency) || frequency <= 0) {
106
+ throw new ConfigurationError(
107
+ "autoCommit.frequency number must be a positive safe integer."
108
+ );
109
+ }
110
+ return { frequency: "scheduled", intervalMs: frequency, maxPendingBytes };
111
+ }
112
+ const intervalMs = parseFrequencyString(frequency);
113
+ return { frequency: "scheduled", intervalMs, maxPendingBytes };
114
+ };
115
+
116
+ // src/storage/drivers/syncStorage/syncStorageConfig.ts
117
+ var DEFAULT_SYNC_STORAGE_MAX_TOTAL_BYTES = 102400;
118
+ var parseSyncStorageMaxTotalBytesForBackendLimit = (config) => {
119
+ const maxTotalBytes = config?.maxTotalBytes ?? DEFAULT_SYNC_STORAGE_MAX_TOTAL_BYTES;
120
+ if (!Number.isSafeInteger(maxTotalBytes) || maxTotalBytes <= 0) {
121
+ throw new ConfigurationError(
122
+ "syncStorage.maxTotalBytes must be a positive safe integer."
123
+ );
124
+ }
125
+ return maxTotalBytes;
126
+ };
127
+ var parseSyncStorageConfig = (config) => {
128
+ const keyPrefix = config?.keyPrefix ?? "frostpillar";
129
+ const databaseKey = config?.databaseKey ?? "default";
130
+ const maxChunkChars = config?.maxChunkChars ?? 6e3;
131
+ const maxChunks = config?.maxChunks ?? 511;
132
+ const maxItemBytes = config?.maxItemBytes ?? 8192;
133
+ const maxTotalBytes = parseSyncStorageMaxTotalBytesForBackendLimit(config);
134
+ const maxItems = config?.maxItems ?? 512;
135
+ if (!Number.isSafeInteger(maxChunkChars) || maxChunkChars <= 0) {
136
+ throw new ConfigurationError(
137
+ "syncStorage.maxChunkChars must be a positive safe integer."
138
+ );
139
+ }
140
+ if (!Number.isSafeInteger(maxChunks) || maxChunks <= 0) {
141
+ throw new ConfigurationError(
142
+ "syncStorage.maxChunks must be a positive safe integer."
143
+ );
144
+ }
145
+ if (!Number.isSafeInteger(maxItemBytes) || maxItemBytes <= 0) {
146
+ throw new ConfigurationError(
147
+ "syncStorage.maxItemBytes must be a positive safe integer."
148
+ );
149
+ }
150
+ if (!Number.isSafeInteger(maxItems) || maxItems <= 0) {
151
+ throw new ConfigurationError(
152
+ "syncStorage.maxItems must be a positive safe integer."
153
+ );
154
+ }
155
+ if (maxChunks + 1 > maxItems) {
156
+ throw new ConfigurationError(
157
+ "syncStorage.maxChunks + 1 (manifest item) must be <= syncStorage.maxItems."
158
+ );
159
+ }
160
+ return {
161
+ keyPrefix,
162
+ databaseKey,
163
+ maxChunkChars,
164
+ maxChunks,
165
+ maxItemBytes,
166
+ maxTotalBytes,
167
+ maxItems
168
+ };
169
+ };
170
+
171
+ // src/storage/backend/asyncDurableAutoCommitController.ts
172
+ var AsyncDurableAutoCommitController = class {
173
+ constructor(autoCommit, onAutoCommitError) {
174
+ this.autoCommit = autoCommit;
175
+ this.onAutoCommitError = onAutoCommitError;
176
+ this.pendingAutoCommitBytes = 0;
177
+ this.dirtyFromClear = false;
178
+ this.autoCommitTimer = null;
179
+ this.commitInFlight = null;
180
+ this.pendingForegroundCommitRequest = false;
181
+ this.pendingBackgroundCommitRequest = false;
182
+ this.closed = false;
183
+ this.startAutoCommitSchedule();
184
+ }
185
+ handleRecordAppended(encodedBytes) {
186
+ if (this.autoCommit.frequency === "immediate") {
187
+ return this.commitNow();
188
+ }
189
+ this.pendingAutoCommitBytes += encodedBytes;
190
+ if (this.autoCommit.maxPendingBytes !== null && this.pendingAutoCommitBytes >= this.autoCommit.maxPendingBytes) {
191
+ return this.queueCommitRequest("foreground");
192
+ }
193
+ return Promise.resolve();
194
+ }
195
+ handleCleared() {
196
+ this.dirtyFromClear = true;
197
+ if (this.autoCommit.frequency === "immediate") {
198
+ return this.commitNow();
199
+ }
200
+ return this.queueCommitRequest("background");
201
+ }
202
+ commitNow() {
203
+ return this.queueCommitRequest("foreground");
204
+ }
205
+ async close() {
206
+ if (this.closed) {
207
+ return;
208
+ }
209
+ this.closed = true;
210
+ this.stopAutoCommitSchedule();
211
+ await this.waitForCommitSettlement();
212
+ let flushError = null;
213
+ if (this.pendingAutoCommitBytes > 0 || this.dirtyFromClear) {
214
+ try {
215
+ await this.executeSingleCommit();
216
+ this.pendingAutoCommitBytes = 0;
217
+ this.dirtyFromClear = false;
218
+ } catch (error) {
219
+ flushError = toErrorInstance(
220
+ error,
221
+ "Final close-time flush commit failed with a non-Error value."
222
+ );
223
+ }
224
+ }
225
+ let drainError = null;
226
+ try {
227
+ await this.onCloseAfterDrain();
228
+ } catch (error) {
229
+ drainError = toErrorInstance(
230
+ error,
231
+ "onCloseAfterDrain failed with a non-Error value."
232
+ );
233
+ }
234
+ if (flushError !== null && drainError !== null) {
235
+ throw createCloseAggregateError(flushError, drainError);
236
+ }
237
+ if (flushError !== null) {
238
+ throw flushError;
239
+ }
240
+ if (drainError !== null) {
241
+ throw drainError;
242
+ }
243
+ }
244
+ getPendingAutoCommitBytes() {
245
+ return this.pendingAutoCommitBytes;
246
+ }
247
+ onCloseAfterDrain() {
248
+ return Promise.resolve();
249
+ }
250
+ waitForCommitSettlement() {
251
+ if (this.commitInFlight === null) {
252
+ return Promise.resolve();
253
+ }
254
+ return this.commitInFlight.then(() => void 0).catch(() => void 0);
255
+ }
256
+ queueCommitRequest(requestType) {
257
+ if (requestType === "foreground") {
258
+ this.pendingForegroundCommitRequest = true;
259
+ } else {
260
+ this.pendingBackgroundCommitRequest = true;
261
+ }
262
+ if (this.commitInFlight === null) {
263
+ this.commitInFlight = this.runCommitLoop().finally(() => {
264
+ this.commitInFlight = null;
265
+ });
266
+ }
267
+ if (requestType === "background") {
268
+ return Promise.resolve();
269
+ }
270
+ return this.commitInFlight;
271
+ }
272
+ async runCommitLoop() {
273
+ let shouldContinue = true;
274
+ while (shouldContinue) {
275
+ const runForeground = this.pendingForegroundCommitRequest;
276
+ const runBackground = this.pendingBackgroundCommitRequest;
277
+ const runClear = this.dirtyFromClear;
278
+ this.pendingForegroundCommitRequest = false;
279
+ this.pendingBackgroundCommitRequest = false;
280
+ this.dirtyFromClear = false;
281
+ const shouldRunCommit = runForeground || runBackground && (this.pendingAutoCommitBytes > 0 || runClear);
282
+ if (!shouldRunCommit) {
283
+ shouldContinue = false;
284
+ continue;
285
+ }
286
+ try {
287
+ const committedPendingBytes = this.pendingAutoCommitBytes;
288
+ await this.executeSingleCommit();
289
+ this.pendingAutoCommitBytes = Math.max(
290
+ 0,
291
+ this.pendingAutoCommitBytes - committedPendingBytes
292
+ );
293
+ } catch (error) {
294
+ if (runClear) {
295
+ this.dirtyFromClear = true;
296
+ }
297
+ if (runForeground) {
298
+ throw toErrorInstance(
299
+ error,
300
+ "Foreground auto-commit failed with a non-Error value."
301
+ );
302
+ }
303
+ this.onAutoCommitError(error);
304
+ }
305
+ if (!this.pendingForegroundCommitRequest && !this.pendingBackgroundCommitRequest) {
306
+ shouldContinue = false;
307
+ }
308
+ }
309
+ }
310
+ startAutoCommitSchedule() {
311
+ if (this.autoCommit.frequency !== "scheduled" || this.autoCommit.intervalMs === null) {
312
+ return;
313
+ }
314
+ this.autoCommitTimer = setInterval(() => {
315
+ this.handleAutoCommitTick();
316
+ }, this.autoCommit.intervalMs);
317
+ if (typeof this.autoCommitTimer === "object" && this.autoCommitTimer !== null && "unref" in this.autoCommitTimer) {
318
+ this.autoCommitTimer.unref();
319
+ }
320
+ }
321
+ stopAutoCommitSchedule() {
322
+ if (this.autoCommitTimer === null) {
323
+ return;
324
+ }
325
+ clearInterval(this.autoCommitTimer);
326
+ this.autoCommitTimer = null;
327
+ }
328
+ handleAutoCommitTick() {
329
+ if (this.closed) {
330
+ return;
331
+ }
332
+ if (this.pendingAutoCommitBytes <= 0 && !this.dirtyFromClear) {
333
+ return;
334
+ }
335
+ void this.queueCommitRequest("background");
336
+ }
337
+ };
338
+ var readAggregateErrorConstructor = () => {
339
+ const candidate = globalThis.AggregateError;
340
+ if (typeof candidate !== "function") {
341
+ return null;
342
+ }
343
+ return candidate;
344
+ };
345
+ var createCloseAggregateError = (flushError, drainError) => {
346
+ const aggregateErrorConstructor = readAggregateErrorConstructor();
347
+ if (aggregateErrorConstructor !== null) {
348
+ return new aggregateErrorConstructor(
349
+ [flushError, drainError],
350
+ "Close failed: both final flush and drain produced errors."
351
+ );
352
+ }
353
+ const fallbackError = new Error(
354
+ "Close failed: both final flush and drain produced errors."
355
+ );
356
+ fallbackError.errors = [flushError, drainError];
357
+ return fallbackError;
358
+ };
359
+
360
+ // src/validation/typeGuards.ts
361
+ var isRecordObject = (value) => {
362
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
363
+ return false;
364
+ }
365
+ const prototype = Object.getPrototypeOf(value);
366
+ return prototype === Object.prototype || prototype === null;
367
+ };
368
+
369
+ // src/validation/metadata.ts
370
+ var parseNonNegativeSafeInteger = (value, fieldName, backendName) => {
371
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
372
+ throw new StorageEngineError(
373
+ `${backendName} ${fieldName} must be a non-negative safe integer.`
374
+ );
375
+ }
376
+ return value;
377
+ };
378
+
379
+ // src/storage/drivers/syncStorage/syncStorageAdapter.ts
380
+ var readChromeRuntimeError = (runtime) => {
381
+ const runtimeMessage = runtime?.lastError?.message;
382
+ if (runtimeMessage === void 0) {
383
+ return null;
384
+ }
385
+ if (runtimeMessage.trim().length === 0) {
386
+ return new Error("chrome.runtime.lastError is set with an empty message.");
387
+ }
388
+ return new Error(runtimeMessage);
389
+ };
390
+ var callChromeCallbackGet = (syncArea, runtime, keys) => {
391
+ return new Promise((resolve, reject) => {
392
+ try {
393
+ syncArea.get(keys, (items) => {
394
+ const runtimeError = readChromeRuntimeError(runtime);
395
+ if (runtimeError !== null) {
396
+ reject(runtimeError);
397
+ return;
398
+ }
399
+ resolve(items);
400
+ });
401
+ } catch (error) {
402
+ reject(
403
+ toErrorInstance(error, "chrome.storage.sync.get failed with a non-Error value.")
404
+ );
405
+ }
406
+ });
407
+ };
408
+ var callChromeCallbackSet = (syncArea, runtime, items) => {
409
+ return new Promise((resolve, reject) => {
410
+ try {
411
+ syncArea.set(items, () => {
412
+ const runtimeError = readChromeRuntimeError(runtime);
413
+ if (runtimeError !== null) {
414
+ reject(runtimeError);
415
+ return;
416
+ }
417
+ resolve();
418
+ });
419
+ } catch (error) {
420
+ reject(
421
+ toErrorInstance(error, "chrome.storage.sync.set failed with a non-Error value.")
422
+ );
423
+ }
424
+ });
425
+ };
426
+ var callChromeCallbackRemove = (syncArea, runtime, keys) => {
427
+ return new Promise((resolve, reject) => {
428
+ try {
429
+ syncArea.remove(keys, () => {
430
+ const runtimeError = readChromeRuntimeError(runtime);
431
+ if (runtimeError !== null) {
432
+ reject(runtimeError);
433
+ return;
434
+ }
435
+ resolve();
436
+ });
437
+ } catch (error) {
438
+ reject(
439
+ toErrorInstance(
440
+ error,
441
+ "chrome.storage.sync.remove failed with a non-Error value."
442
+ )
443
+ );
444
+ }
445
+ });
446
+ };
447
+ var createBrowserPromiseSyncStorageAdapter = (syncArea) => {
448
+ return {
449
+ getItems: async (keys) => {
450
+ return await syncArea.get(keys);
451
+ },
452
+ setItems: async (items) => {
453
+ await syncArea.set(items);
454
+ },
455
+ removeItems: async (keys) => {
456
+ await syncArea.remove(keys);
457
+ }
458
+ };
459
+ };
460
+ var createChromeCallbackSyncStorageAdapter = (syncArea, runtime) => {
461
+ return {
462
+ getItems: async (keys) => {
463
+ return await callChromeCallbackGet(syncArea, runtime, keys);
464
+ },
465
+ setItems: async (items) => {
466
+ await callChromeCallbackSet(syncArea, runtime, items);
467
+ },
468
+ removeItems: async (keys) => {
469
+ await callChromeCallbackRemove(syncArea, runtime, keys);
470
+ }
471
+ };
472
+ };
473
+ var hasSyncAreaFunctionShape = (value) => {
474
+ if (!isRecordObject(value)) {
475
+ return false;
476
+ }
477
+ return typeof value.get === "function" && typeof value.set === "function" && typeof value.remove === "function";
478
+ };
479
+ var hasBrowserPromiseSyncArea = (value) => {
480
+ return hasSyncAreaFunctionShape(value);
481
+ };
482
+ var hasChromeCallbackSyncArea = (value) => {
483
+ return hasSyncAreaFunctionShape(value);
484
+ };
485
+ var detectGlobalSyncStorage = () => {
486
+ try {
487
+ const globals = globalThis;
488
+ const browserSync = globals.browser?.storage?.sync;
489
+ if (hasBrowserPromiseSyncArea(browserSync)) {
490
+ return createBrowserPromiseSyncStorageAdapter(browserSync);
491
+ }
492
+ const chromeSync = globals.chrome?.storage?.sync;
493
+ if (hasChromeCallbackSyncArea(chromeSync)) {
494
+ const runtime = globals.chrome?.runtime ?? null;
495
+ return createChromeCallbackSyncStorageAdapter(chromeSync, runtime);
496
+ }
497
+ return null;
498
+ } catch {
499
+ return null;
500
+ }
501
+ };
502
+
503
+ // src/storage/drivers/syncStorage/syncStorageChunkMaintenance.ts
504
+ var cleanupGenerationChunks = async (state, generation, knownChunkCount, chunkKeyResolver) => {
505
+ if (knownChunkCount !== null) {
506
+ if (knownChunkCount <= 0) {
507
+ return;
508
+ }
509
+ const knownKeys = [];
510
+ for (let i = 0; i < knownChunkCount; i += 1) {
511
+ knownKeys.push(chunkKeyResolver(generation, i));
512
+ }
513
+ await state.adapter.removeItems(knownKeys);
514
+ return;
515
+ }
516
+ if (state.maxChunks <= 0) {
517
+ return;
518
+ }
519
+ const speculativeKeys = [];
520
+ for (let i = 0; i < state.maxChunks; i += 1) {
521
+ speculativeKeys.push(chunkKeyResolver(generation, i));
522
+ }
523
+ const maybeChunks = await state.adapter.getItems(speculativeKeys);
524
+ const discoveredKeys = speculativeKeys.filter((key) => {
525
+ return Object.prototype.hasOwnProperty.call(maybeChunks, key);
526
+ });
527
+ if (discoveredKeys.length === 0) {
528
+ return;
529
+ }
530
+ await state.adapter.removeItems(discoveredKeys);
531
+ };
532
+
533
+ // src/storage/drivers/syncStorage/syncStorageQuota.ts
534
+ var utf8Encoder = new TextEncoder();
535
+ var computeSyncStorageItemBytes = (key, value) => {
536
+ const valueJson = JSON.stringify(value);
537
+ if (valueJson === void 0) {
538
+ throw new StorageEngineError(
539
+ `syncStorage value for key "${key}" cannot be serialized.`
540
+ );
541
+ }
542
+ return utf8Encoder.encode(key).byteLength + utf8Encoder.encode(valueJson).byteLength;
543
+ };
544
+ var isQuotaBrowserError = (error) => {
545
+ if (error instanceof QuotaExceededError) {
546
+ return true;
547
+ }
548
+ if (!(error instanceof Error)) {
549
+ return false;
550
+ }
551
+ const normalized = `${error.name}:${error.message}`;
552
+ return /quota|max_items|quota_bytes|quota_bytes_per_item/i.test(normalized);
553
+ };
554
+ var validateSyncStorageCommitQuota = (state, generation, chunks, manifest, resolveChunkKey, manifestStorageKey) => {
555
+ const pendingItems = chunks.map(
556
+ (chunkValue, chunkIndex) => {
557
+ return {
558
+ key: resolveChunkKey(generation, chunkIndex),
559
+ value: chunkValue
560
+ };
561
+ }
562
+ );
563
+ pendingItems.push({
564
+ key: manifestStorageKey,
565
+ value: manifest
566
+ });
567
+ if (pendingItems.length > state.maxItems) {
568
+ throw new QuotaExceededError(
569
+ `syncStorage snapshot requires ${pendingItems.length} items but maxItems is ${state.maxItems}.`
570
+ );
571
+ }
572
+ let totalBytes = 0;
573
+ for (const pendingItem of pendingItems) {
574
+ const itemBytes = computeSyncStorageItemBytes(
575
+ pendingItem.key,
576
+ pendingItem.value
577
+ );
578
+ if (itemBytes > state.maxItemBytes) {
579
+ throw new QuotaExceededError(
580
+ `syncStorage item "${pendingItem.key}" requires ${itemBytes} bytes but maxItemBytes is ${state.maxItemBytes}.`
581
+ );
582
+ }
583
+ totalBytes += itemBytes;
584
+ }
585
+ if (totalBytes > state.maxTotalBytes) {
586
+ throw new QuotaExceededError(
587
+ `syncStorage snapshot requires ${totalBytes} bytes but maxTotalBytes is ${state.maxTotalBytes}.`
588
+ );
589
+ }
590
+ };
591
+
592
+ // src/storage/backend/encoding.ts
593
+ var computeUtf8ByteLengthJs = (value) => {
594
+ let bytes = 0;
595
+ for (let i = 0; i < value.length; i++) {
596
+ const code = value.charCodeAt(i);
597
+ if (code <= 127) {
598
+ bytes += 1;
599
+ } else if (code <= 2047) {
600
+ bytes += 2;
601
+ } else if (code >= 55296 && code <= 56319) {
602
+ const next = i + 1 < value.length ? value.charCodeAt(i + 1) : 0;
603
+ if (next >= 56320 && next <= 57343) {
604
+ bytes += 4;
605
+ i++;
606
+ } else {
607
+ bytes += 3;
608
+ }
609
+ } else if (code >= 56320 && code <= 57343) {
610
+ bytes += 3;
611
+ } else {
612
+ bytes += 3;
613
+ }
614
+ }
615
+ return bytes;
616
+ };
617
+ var hasBuffer = typeof Buffer !== "undefined" && typeof Buffer.byteLength === "function";
618
+ var computeUtf8ByteLength = hasBuffer ? (value) => Buffer.byteLength(value, "utf8") : computeUtf8ByteLengthJs;
619
+
620
+ // src/storage/drivers/syncStorage/syncStorageBackend.ts
621
+ var SYNC_STORAGE_MAGIC = "FPSYNC_META";
622
+ var SYNC_STORAGE_VERSION = 2;
623
+ var manifestKey = (keyPrefix, databaseKey) => {
624
+ return `${keyPrefix}:sync:${databaseKey}:manifest`;
625
+ };
626
+ var chunkKey = (keyPrefix, databaseKey, generation, index) => {
627
+ return `${keyPrefix}:sync:${databaseKey}:g:${generation}:chunk:${index}`;
628
+ };
629
+ var createSyncStorageBackendState = (adapter, keyPrefix, databaseKey, maxChunkChars, maxChunks, maxItemBytes, maxTotalBytes, maxItems) => {
630
+ return {
631
+ adapter,
632
+ keyPrefix,
633
+ databaseKey,
634
+ maxChunkChars,
635
+ maxChunks,
636
+ maxItemBytes,
637
+ maxTotalBytes,
638
+ maxItems,
639
+ activeGeneration: 0,
640
+ commitId: 0,
641
+ activeChunkCount: 0
642
+ };
643
+ };
644
+ var parseSyncManifest = (manifestUnknown, maxChunks) => {
645
+ if (!isRecordObject(manifestUnknown)) {
646
+ throw new StorageEngineError("syncStorage manifest must be an object.");
647
+ }
648
+ const manifest = manifestUnknown;
649
+ if (manifest.magic !== SYNC_STORAGE_MAGIC || manifest.version !== SYNC_STORAGE_VERSION) {
650
+ throw new StorageEngineError("syncStorage manifest magic/version mismatch.");
651
+ }
652
+ const chunkCount = parseNonNegativeSafeInteger(
653
+ manifest.chunkCount,
654
+ "manifest.chunkCount",
655
+ "syncStorage"
656
+ );
657
+ if (chunkCount > maxChunks) {
658
+ throw new StorageEngineError(
659
+ `syncStorage snapshot requires ${chunkCount} chunks but maxChunks is ${maxChunks}.`
660
+ );
661
+ }
662
+ return manifest;
663
+ };
664
+ var loadSyncChunksAndDecodeTreeJSON = async (state, activeGeneration, chunkCount) => {
665
+ const chunkKeys = [];
666
+ for (let i = 0; i < chunkCount; i += 1) {
667
+ chunkKeys.push(
668
+ chunkKey(state.keyPrefix, state.databaseKey, activeGeneration, i)
669
+ );
670
+ }
671
+ const chunkValuesByKey = chunkKeys.length === 0 ? {} : await state.adapter.getItems(chunkKeys);
672
+ const chunks = [];
673
+ for (const cKey of chunkKeys) {
674
+ const chunkValue = chunkValuesByKey[cKey];
675
+ if (typeof chunkValue !== "string") {
676
+ throw new StorageEngineError(
677
+ `syncStorage chunk "${cKey}" is missing or not a string.`
678
+ );
679
+ }
680
+ chunks.push(chunkValue);
681
+ }
682
+ const treeJson = chunks.join("");
683
+ let parsedTreeJSON;
684
+ try {
685
+ parsedTreeJSON = JSON.parse(treeJson);
686
+ } catch {
687
+ throw new StorageEngineError("syncStorage chunk data JSON is malformed.");
688
+ }
689
+ if (typeof parsedTreeJSON !== "object" || parsedTreeJSON === null || Array.isArray(parsedTreeJSON)) {
690
+ throw new PageCorruptionError("treeJSON must be a non-null plain object.");
691
+ }
692
+ return {
693
+ treeJSON: parsedTreeJSON,
694
+ rawJsonLength: computeUtf8ByteLength(treeJson)
695
+ };
696
+ };
697
+ var loadSyncStorageSnapshot = async (state) => {
698
+ const mKey = manifestKey(state.keyPrefix, state.databaseKey);
699
+ const manifestMap = await state.adapter.getItems([mKey]);
700
+ const manifestUnknown = manifestMap[mKey];
701
+ if (manifestUnknown === void 0) {
702
+ return { treeJSON: null, currentSizeBytes: 0 };
703
+ }
704
+ const manifest = parseSyncManifest(manifestUnknown, state.maxChunks);
705
+ const activeGeneration = parseNonNegativeSafeInteger(
706
+ manifest.activeGeneration,
707
+ "manifest.activeGeneration",
708
+ "syncStorage"
709
+ );
710
+ const commitId = parseNonNegativeSafeInteger(
711
+ manifest.commitId,
712
+ "manifest.commitId",
713
+ "syncStorage"
714
+ );
715
+ const chunkCount = parseNonNegativeSafeInteger(
716
+ manifest.chunkCount,
717
+ "manifest.chunkCount",
718
+ "syncStorage"
719
+ );
720
+ const { treeJSON, rawJsonLength } = await loadSyncChunksAndDecodeTreeJSON(
721
+ state,
722
+ activeGeneration,
723
+ chunkCount
724
+ );
725
+ const currentSizeBytes = rawJsonLength;
726
+ state.activeGeneration = activeGeneration;
727
+ state.commitId = commitId;
728
+ state.activeChunkCount = chunkCount;
729
+ return { treeJSON, currentSizeBytes };
730
+ };
731
+ var buildSyncChunkKeyResolver = (state) => {
732
+ return (generation, index) => {
733
+ return chunkKey(state.keyPrefix, state.databaseKey, generation, index);
734
+ };
735
+ };
736
+ var buildSyncCommitItems = (state, chunks, newManifest, nextGeneration) => {
737
+ const mKey = manifestKey(state.keyPrefix, state.databaseKey);
738
+ const items = { [mKey]: newManifest };
739
+ for (let i = 0; i < chunks.length; i += 1) {
740
+ const cKey = chunkKey(state.keyPrefix, state.databaseKey, nextGeneration, i);
741
+ items[cKey] = chunks[i];
742
+ }
743
+ return items;
744
+ };
745
+ var splitSyncTreeJSONIntoChunks = (treeJSON, maxChunkChars, maxChunks) => {
746
+ const dataJson = JSON.stringify(treeJSON);
747
+ const chunks = [];
748
+ for (let i = 0; i < dataJson.length; i += maxChunkChars) {
749
+ chunks.push(dataJson.slice(i, i + maxChunkChars));
750
+ }
751
+ if (chunks.length > maxChunks) {
752
+ throw new QuotaExceededError(
753
+ `syncStorage snapshot requires ${chunks.length} chunks but maxChunks is ${maxChunks}.`
754
+ );
755
+ }
756
+ return chunks;
757
+ };
758
+ var ensureSyncCommitCountersSafe = (state) => {
759
+ if (state.commitId >= Number.MAX_SAFE_INTEGER) {
760
+ throw new StorageEngineError("syncStorage commitId has reached Number.MAX_SAFE_INTEGER.");
761
+ }
762
+ if (state.activeGeneration >= Number.MAX_SAFE_INTEGER) {
763
+ throw new StorageEngineError("syncStorage activeGeneration has reached Number.MAX_SAFE_INTEGER.");
764
+ }
765
+ };
766
+ var commitSyncStorageSnapshot = async (state, treeJSON) => {
767
+ ensureSyncCommitCountersSafe(state);
768
+ const nextCommitId = state.commitId + 1;
769
+ const nextGeneration = state.activeGeneration + 1;
770
+ const chunks = splitSyncTreeJSONIntoChunks(
771
+ treeJSON,
772
+ state.maxChunkChars,
773
+ state.maxChunks
774
+ );
775
+ const newManifest = {
776
+ magic: SYNC_STORAGE_MAGIC,
777
+ version: SYNC_STORAGE_VERSION,
778
+ activeGeneration: nextGeneration,
779
+ commitId: nextCommitId,
780
+ chunkCount: chunks.length
781
+ };
782
+ const resolveChunkKey = buildSyncChunkKeyResolver(state);
783
+ const mKey = manifestKey(state.keyPrefix, state.databaseKey);
784
+ validateSyncStorageCommitQuota(
785
+ state,
786
+ nextGeneration,
787
+ chunks,
788
+ newManifest,
789
+ resolveChunkKey,
790
+ mKey
791
+ );
792
+ const newSnapshotItems = buildSyncCommitItems(
793
+ state,
794
+ chunks,
795
+ newManifest,
796
+ nextGeneration
797
+ );
798
+ try {
799
+ await cleanupGenerationChunks(state, nextGeneration, null, resolveChunkKey);
800
+ } catch {
801
+ }
802
+ try {
803
+ await state.adapter.setItems(newSnapshotItems);
804
+ } catch (error) {
805
+ if (isQuotaBrowserError(error)) {
806
+ throw new QuotaExceededError("syncStorage quota exceeded during commit.");
807
+ }
808
+ throw new StorageEngineError(
809
+ "syncStorage write failed during commit.",
810
+ { cause: error }
811
+ );
812
+ }
813
+ const previousGeneration = state.activeGeneration;
814
+ const previousChunkCount = state.activeChunkCount;
815
+ state.activeGeneration = nextGeneration;
816
+ state.commitId = nextCommitId;
817
+ state.activeChunkCount = chunks.length;
818
+ await cleanupGenerationChunks(
819
+ state,
820
+ previousGeneration,
821
+ previousChunkCount,
822
+ resolveChunkKey
823
+ );
824
+ };
825
+
826
+ // src/storage/drivers/syncStorage/syncStorageBackendController.ts
827
+ var SyncStorageBackendController = class _SyncStorageBackendController extends AsyncDurableAutoCommitController {
828
+ constructor(backend, autoCommit, getSnapshot, onAutoCommitError) {
829
+ super(autoCommit, onAutoCommitError);
830
+ this.backend = backend;
831
+ this.getSnapshot = getSnapshot;
832
+ }
833
+ static async create(options) {
834
+ const adapter = detectGlobalSyncStorage();
835
+ if (adapter === null) {
836
+ throw new UnsupportedBackendError(
837
+ "browser sync storage is not available in the current runtime environment."
838
+ );
839
+ }
840
+ const syncConfig = parseSyncStorageConfig(options.config);
841
+ const autoCommit = parseAutoCommitConfig(options.autoCommit);
842
+ const backend = createSyncStorageBackendState(
843
+ adapter,
844
+ syncConfig.keyPrefix,
845
+ syncConfig.databaseKey,
846
+ syncConfig.maxChunkChars,
847
+ syncConfig.maxChunks,
848
+ syncConfig.maxItemBytes,
849
+ syncConfig.maxTotalBytes,
850
+ syncConfig.maxItems
851
+ );
852
+ const loaded = await loadSyncStorageSnapshot(backend);
853
+ const controller = new _SyncStorageBackendController(
854
+ backend,
855
+ autoCommit,
856
+ options.getSnapshot,
857
+ options.onAutoCommitError
858
+ );
859
+ return {
860
+ controller,
861
+ initialTreeJSON: loaded.treeJSON,
862
+ initialCurrentSizeBytes: loaded.currentSizeBytes
863
+ };
864
+ }
865
+ async executeSingleCommit() {
866
+ const snapshot = this.getSnapshot();
867
+ await commitSyncStorageSnapshot(
868
+ this.backend,
869
+ snapshot.treeJSON
870
+ );
871
+ }
872
+ };
873
+
874
+ // src/drivers/syncStorage.ts
875
+ var syncStorageDriver = (options = {}) => {
876
+ return {
877
+ init: async (callbacks) => {
878
+ const result = await SyncStorageBackendController.create({
879
+ config: options,
880
+ autoCommit: callbacks.autoCommit,
881
+ getSnapshot: callbacks.getSnapshot,
882
+ onAutoCommitError: callbacks.onAutoCommitError
883
+ });
884
+ return {
885
+ controller: result.controller,
886
+ initialTreeJSON: result.initialTreeJSON,
887
+ initialCurrentSizeBytes: result.initialCurrentSizeBytes
888
+ };
889
+ },
890
+ resolveBackendLimitBytes: () => {
891
+ return parseSyncStorageMaxTotalBytesForBackendLimit(options);
892
+ }
893
+ };
894
+ };
895
+ // Annotate the CommonJS export names for ESM import in node:
896
+ 0 && (module.exports = {
897
+ syncStorageDriver
898
+ });