@pixerate/editor 0.164.0 → 0.165.0

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 (141) hide show
  1. package/dist/agent/index.cjs +54 -39
  2. package/dist/agent/index.js +24 -9
  3. package/dist/animation/index.cjs +6 -5
  4. package/dist/animation/index.js +5 -4
  5. package/dist/animation-editor/index.cjs +65 -64
  6. package/dist/animation-editor/index.js +8 -7
  7. package/dist/bridge/cli.cjs +3 -3
  8. package/dist/bridge/cli.js +2 -2
  9. package/dist/bridge/index.cjs +3 -3
  10. package/dist/bridge/index.js +2 -2
  11. package/dist/canvas/index.cjs +89 -87
  12. package/dist/canvas/index.js +11 -9
  13. package/dist/chart/controller.d.cts +31 -2
  14. package/dist/chart/controller.d.ts +31 -2
  15. package/dist/chart/index.cjs +504 -132
  16. package/dist/chart/index.d.cts +2 -0
  17. package/dist/chart/index.d.ts +2 -0
  18. package/dist/chart/index.js +386 -14
  19. package/dist/chart/layout-families.d.cts +7 -0
  20. package/dist/chart/layout-families.d.ts +7 -0
  21. package/dist/chart/links.d.cts +34 -0
  22. package/dist/chart/links.d.ts +34 -0
  23. package/dist/chart/mermaid.d.cts +9 -6
  24. package/dist/chart/mermaid.d.ts +9 -6
  25. package/dist/chart/schemas.d.cts +3 -0
  26. package/dist/chart/schemas.d.ts +3 -0
  27. package/dist/chart/spec.d.cts +3 -2
  28. package/dist/chart/spec.d.ts +3 -2
  29. package/dist/chart/style.d.cts +33 -0
  30. package/dist/chart/style.d.ts +33 -0
  31. package/dist/chart/types.d.cts +24 -2
  32. package/dist/chart/types.d.ts +24 -2
  33. package/dist/{chunk-TALQONZ5.js → chunk-2J5VLJHC.js} +3 -3
  34. package/dist/{chunk-3NEZJCBY.js → chunk-2ZRN43IN.js} +340 -201
  35. package/dist/{chunk-ZVNAMTFO.cjs → chunk-3FSUJIXN.cjs} +18 -174
  36. package/dist/{chunk-RQCMSEM5.cjs → chunk-4QUUHTGJ.cjs} +2 -2
  37. package/dist/{chunk-CIPIESX4.cjs → chunk-4ZC5QJE2.cjs} +3 -3
  38. package/dist/{chunk-S6X2QRVU.cjs → chunk-5UEFBM2M.cjs} +1 -138
  39. package/dist/{chunk-YSAGBIRW.cjs → chunk-64VWWR5U.cjs} +16 -16
  40. package/dist/{chunk-QYZGBVNC.js → chunk-7272D3K2.js} +10 -8
  41. package/dist/{chunk-462WGGXX.cjs → chunk-AMX23CSN.cjs} +27 -16
  42. package/dist/{chunk-3VLOOO3G.js → chunk-BDIM6NVW.js} +1 -1
  43. package/dist/{chunk-POR54B55.js → chunk-CRFSQWKQ.js} +4 -1
  44. package/dist/{chunk-GZ2FM3ZB.js → chunk-DKNEQX7T.js} +6 -162
  45. package/dist/{chunk-JCXMJUQO.cjs → chunk-EGZM52EO.cjs} +3 -3
  46. package/dist/{chunk-ES3UEVUT.cjs → chunk-EUIDTZNQ.cjs} +112 -110
  47. package/dist/{chunk-H3OEM3Q2.js → chunk-F6R67I3A.js} +1 -1
  48. package/dist/{chunk-5FNGLV2D.js → chunk-FEU2XVQH.js} +15 -3
  49. package/dist/{chunk-SZA42R4N.js → chunk-FRBRLKNQ.js} +154 -16
  50. package/dist/chunk-HIDFL5LQ.js +484 -0
  51. package/dist/{chunk-MKTDELCD.js → chunk-I5MRJW7B.js} +1 -1
  52. package/dist/{chunk-SDHZG5TF.js → chunk-ILP77UD6.js} +160 -2
  53. package/dist/{chunk-GKNEJ6EN.cjs → chunk-IMRXC7OA.cjs} +241 -103
  54. package/dist/chunk-KGRZMHZB.js +204 -0
  55. package/dist/{chunk-SET6KJOP.js → chunk-LPIKXPBM.js} +1 -1
  56. package/dist/{chunk-BQWGN5WH.js → chunk-M2TPMYJE.js} +1 -1
  57. package/dist/{chunk-GTELFJRE.cjs → chunk-M5GLQQWG.cjs} +2 -2
  58. package/dist/{chunk-4VU4CP7R.js → chunk-MHLU6GRJ.js} +1 -138
  59. package/dist/{chunk-D54JHQK2.cjs → chunk-MKXK6E4O.cjs} +51 -51
  60. package/dist/{chunk-DAPQGA55.js → chunk-N7NBGT2Y.js} +2 -2
  61. package/dist/chunk-O6OGNDND.js +164 -0
  62. package/dist/{chunk-22KLZZGH.cjs → chunk-ODFCS7HF.cjs} +8 -8
  63. package/dist/{chunk-PA3IMPPS.cjs → chunk-OVYQAYGH.cjs} +81 -81
  64. package/dist/{chunk-YXOW456B.js → chunk-PPEDZ5WR.js} +1 -1
  65. package/dist/{chunk-YGG7WDWT.cjs → chunk-Q25WAFTD.cjs} +21 -18
  66. package/dist/{chunk-SBQVEBSU.cjs → chunk-QO2T6KA5.cjs} +165 -67
  67. package/dist/{chunk-24M7VD6M.cjs → chunk-S4EIAIEW.cjs} +7 -7
  68. package/dist/{chunk-M4MIZPQI.cjs → chunk-SJ2CPTQC.cjs} +189 -15
  69. package/dist/chunk-TFF2GTGN.cjs +153 -0
  70. package/dist/chunk-TTCJPX4U.cjs +204 -0
  71. package/dist/{chunk-NZD2MZJC.cjs → chunk-TVQW26AV.cjs} +35 -23
  72. package/dist/{chunk-R3P2M7HU.cjs → chunk-UVXTOZ2B.cjs} +346 -207
  73. package/dist/{chunk-WI7QJA7A.cjs → chunk-UXETV2NC.cjs} +161 -3
  74. package/dist/{chunk-Z4P2RBMJ.js → chunk-V5GSY3NC.js} +101 -3
  75. package/dist/{chunk-QY4KUDMY.js → chunk-VRXXAXUT.js} +2 -2
  76. package/dist/chunk-VU6WQSMB.cjs +164 -0
  77. package/dist/{chunk-OXERWBKE.js → chunk-X5W645RI.js} +3 -3
  78. package/dist/{chunk-NHLYK3AX.js → chunk-XBQX76CT.js} +182 -8
  79. package/dist/{chunk-5AARIS3R.cjs → chunk-XCDGM7TD.cjs} +37 -37
  80. package/dist/{chunk-EVXQNQND.js → chunk-XZB3TJ6K.js} +16 -5
  81. package/dist/chunk-YBAPT7NL.js +153 -0
  82. package/dist/chunk-Z2UQYMPS.cjs +484 -0
  83. package/dist/color/index.cjs +5 -3
  84. package/dist/color/index.js +12 -10
  85. package/dist/diagram/index.cjs +55 -243
  86. package/dist/diagram/index.js +12 -200
  87. package/dist/export/index.cjs +7 -6
  88. package/dist/export/index.js +6 -5
  89. package/dist/extensions/index.cjs +12 -9
  90. package/dist/extensions/index.js +11 -8
  91. package/dist/gradient/index.cjs +29 -27
  92. package/dist/gradient/index.js +6 -4
  93. package/dist/image-editor/index.cjs +644 -643
  94. package/dist/image-editor/index.js +7 -6
  95. package/dist/index.cjs +21 -18
  96. package/dist/index.js +14 -11
  97. package/dist/paint-editor/index.cjs +19 -18
  98. package/dist/paint-editor/index.js +7 -6
  99. package/dist/parameters/index.cjs +2 -2
  100. package/dist/parameters/index.js +1 -1
  101. package/dist/persistence/assetWrites.d.cts +22 -0
  102. package/dist/persistence/assetWrites.d.ts +22 -0
  103. package/dist/persistence/assets.d.cts +71 -0
  104. package/dist/persistence/assets.d.ts +71 -0
  105. package/dist/persistence/documentStore.d.cts +17 -1
  106. package/dist/persistence/documentStore.d.ts +17 -1
  107. package/dist/persistence/documentTools.d.cts +5 -1
  108. package/dist/persistence/documentTools.d.ts +5 -1
  109. package/dist/persistence/editorAutosave.d.cts +7 -0
  110. package/dist/persistence/editorAutosave.d.ts +7 -0
  111. package/dist/persistence/index.cjs +141 -21
  112. package/dist/persistence/index.js +130 -10
  113. package/dist/persistence/storage.d.cts +13 -1
  114. package/dist/persistence/storage.d.ts +13 -1
  115. package/dist/spreadsheet/index.cjs +3 -3
  116. package/dist/spreadsheet/index.js +2 -2
  117. package/dist/tokens/index.cjs +11 -3
  118. package/dist/tokens/index.d.cts +1 -0
  119. package/dist/tokens/index.d.ts +1 -0
  120. package/dist/tokens/index.js +10 -2
  121. package/dist/tokens/style.d.cts +36 -0
  122. package/dist/tokens/style.d.ts +36 -0
  123. package/dist/tools/index.cjs +15 -3
  124. package/dist/tools/index.d.cts +2 -0
  125. package/dist/tools/index.d.ts +2 -0
  126. package/dist/tools/index.js +14 -2
  127. package/dist/tools/style-tools.d.cts +19 -0
  128. package/dist/tools/style-tools.d.ts +19 -0
  129. package/dist/tools/style.d.cts +212 -0
  130. package/dist/tools/style.d.ts +212 -0
  131. package/dist/tools/view.d.cts +6 -0
  132. package/dist/tools/view.d.ts +6 -0
  133. package/dist/typography/index.cjs +8 -6
  134. package/dist/typography/index.js +5 -3
  135. package/dist/vector/index.cjs +953 -952
  136. package/dist/vector/index.js +7 -6
  137. package/dist/video-editor/index.cjs +8 -7
  138. package/dist/video-editor/index.js +6 -5
  139. package/package.json +1 -1
  140. package/dist/chunk-ERAGQL2N.cjs +0 -68
  141. package/dist/chunk-KMU6QJB7.js +0 -68
@@ -20,7 +20,10 @@
20
20
 
21
21
 
22
22
 
23
- var _chunkM4MIZPQIcjs = require('../chunk-M4MIZPQI.cjs');
23
+
24
+
25
+
26
+ var _chunkSJ2CPTQCcjs = require('../chunk-SJ2CPTQC.cjs');
24
27
  require('../chunk-2BSBFBOA.cjs');
25
28
 
26
29
  // src/persistence/recoveryPolicy.ts
@@ -136,7 +139,13 @@ function withStorageFailures(inner, failure) {
136
139
  async remove(key) {
137
140
  checkFailure(failure, "remove", key);
138
141
  return inner.remove(key);
139
- }
142
+ },
143
+ ...inner.keys ? {
144
+ async keys(prefix = "") {
145
+ checkFailure(failure, "keys", prefix);
146
+ return inner.keys(prefix);
147
+ }
148
+ } : {}
140
149
  };
141
150
  }
142
151
  function guarded(kind, isAvailable, failure, impl) {
@@ -160,7 +169,13 @@ function guarded(kind, isAvailable, failure, impl) {
160
169
  async remove(key) {
161
170
  check("remove", key);
162
171
  return impl.remove(key);
163
- }
172
+ },
173
+ ...impl.keys ? {
174
+ async keys(prefix = "") {
175
+ check("keys", prefix);
176
+ return impl.keys(prefix);
177
+ }
178
+ } : {}
164
179
  };
165
180
  }
166
181
  function createMemoryStorage(options = {}) {
@@ -174,6 +189,9 @@ function createMemoryStorage(options = {}) {
174
189
  },
175
190
  async remove(key) {
176
191
  entries.delete(key);
192
+ },
193
+ async keys(prefix) {
194
+ return [...entries.keys()].filter((key) => key.startsWith(prefix));
177
195
  }
178
196
  });
179
197
  return Object.assign(adapter, { entries });
@@ -200,7 +218,18 @@ function createLocalStorageAdapter(prefix = "", options = {}) {
200
218
  },
201
219
  async remove(key) {
202
220
  storage.removeItem(prefix + key);
203
- }
221
+ },
222
+ // Listing needs `length` and `key()` (real Web Storage has both).
223
+ ...!storage || typeof storage.key === "function" && typeof storage.length === "number" ? {
224
+ async keys(start) {
225
+ const out = [];
226
+ for (let i = 0; i < storage.length; i++) {
227
+ const key = storage.key(i);
228
+ if (key !== null && key.startsWith(prefix + start)) out.push(key.slice(prefix.length));
229
+ }
230
+ return out;
231
+ }
232
+ } : {}
204
233
  });
205
234
  }
206
235
  function detectIndexedDb() {
@@ -255,7 +284,13 @@ function createIndexedDbAdapter(name = "pixerate-editor", options = {}) {
255
284
  return typeof value === "string" ? value : null;
256
285
  },
257
286
  set: (key, value) => write((store) => store.put(value, key)),
258
- remove: (key) => write((store) => store.delete(key))
287
+ remove: (key) => write((store) => store.delete(key)),
288
+ async keys(prefix) {
289
+ const db = await open();
290
+ const tx = db.transaction(storeName, "readonly");
291
+ const all = await requestToPromise(tx.objectStore(storeName).getAllKeys());
292
+ return all.filter((key) => typeof key === "string" && key.startsWith(prefix));
293
+ }
259
294
  });
260
295
  }
261
296
  function detectOpfsRoot() {
@@ -339,6 +374,30 @@ function createOpfsAdapter(dir = "pixerate-editor", options = {}) {
339
374
  for (const name of [`${base}.ptr`, base, `${base}.a`, `${base}.b`, `${base}.tmp`]) {
340
375
  await removeFile(d, name);
341
376
  }
377
+ },
378
+ async keys(prefix) {
379
+ const d = await directory();
380
+ if (typeof d.keys !== "function") throw new StorageUnavailableError("opfs (directory listing)");
381
+ const names = /* @__PURE__ */ new Set();
382
+ for await (const name of d.keys()) names.add(name);
383
+ const keys = /* @__PURE__ */ new Set();
384
+ for (const name of names) {
385
+ if (name.endsWith(".tmp")) continue;
386
+ if (name.endsWith(".ptr")) keys.add(name.slice(0, -4));
387
+ else if (/\.[ab]$/.test(name) && names.has(`${name.slice(0, -2)}.ptr`)) continue;
388
+ else keys.add(name);
389
+ }
390
+ const out = [];
391
+ for (const name of keys) {
392
+ let key;
393
+ try {
394
+ key = decodeURIComponent(name);
395
+ } catch (e5) {
396
+ continue;
397
+ }
398
+ if (key.startsWith(prefix)) out.push(key);
399
+ }
400
+ return out;
342
401
  }
343
402
  });
344
403
  }
@@ -572,7 +631,7 @@ function createAutosaveController(options) {
572
631
  const metaText = await storage.get(metaKey);
573
632
  if (!metaText) return null;
574
633
  meta = JSON.parse(metaText);
575
- } catch (e5) {
634
+ } catch (e6) {
576
635
  return null;
577
636
  }
578
637
  if (!meta || meta.dirty !== true) return null;
@@ -670,15 +729,16 @@ function createRevisionTracker(options = {}) {
670
729
 
671
730
  // src/persistence/editorAutosave.ts
672
731
  function createEditorAutosave(doc, options) {
673
- const { dirty, key, ...rest } = options;
732
+ const { dirty, key, collectAssetGarbage, ...rest } = options;
733
+ const collect = () => _chunkSJ2CPTQCcjs.collectRetiredEditorAssets.call(void 0, rest.storage, doc, collectAssetGarbage);
674
734
  const revisions = createRevisionTracker({ history: doc, dirty });
675
735
  const controller = createAutosaveController({
676
736
  ...rest,
677
737
  // Image and paint documents hold asset references: their bytes are saved and read with them.
678
- storage: doc.assets ? _chunkM4MIZPQIcjs.withEditorAssets.call(void 0, rest.storage, doc.assets) : rest.storage,
738
+ storage: doc.assets ? _chunkSJ2CPTQCcjs.withEditorAssets.call(void 0, rest.storage, doc.assets) : rest.storage,
679
739
  key: _nullishCoalesce(key, () => ( doc.kind)),
680
- getSnapshot: () => _chunkM4MIZPQIcjs.toEditorDocumentFile.call(void 0, doc),
681
- deserialize: (text) => _chunkM4MIZPQIcjs.parseEditorDocument.call(void 0, text, { kind: doc.kind }),
740
+ getSnapshot: () => _chunkSJ2CPTQCcjs.toEditorDocumentFile.call(void 0, doc),
741
+ deserialize: (text) => _chunkSJ2CPTQCcjs.parseEditorDocument.call(void 0, text, { kind: doc.kind }),
682
742
  revision: revisions.revision,
683
743
  savedRevision: revisions.savedRevision
684
744
  });
@@ -696,10 +756,12 @@ function createEditorAutosave(doc, options) {
696
756
  async discard() {
697
757
  await controller.discardRecovery();
698
758
  controller.ready();
759
+ await collect();
699
760
  },
700
761
  async markClean() {
701
762
  revisions.markSaved();
702
763
  await controller.markClean();
764
+ await collect();
703
765
  },
704
766
  destroy() {
705
767
  unsubscribe();
@@ -720,6 +782,7 @@ function createEditorDocumentStore(options) {
720
782
  const now = _nullishCoalesce(options.now, () => ( Date.now));
721
783
  const indexKey = `${ns}:index`;
722
784
  const fileKey = (key) => `${ns}:file:${key}`;
785
+ const gc = _nullishCoalesce(options.collectAssetGarbage, () => ( true));
723
786
  let index = null;
724
787
  let queue = Promise.resolve();
725
788
  const run = (task) => {
@@ -737,7 +800,7 @@ function createEditorDocumentStore(options) {
737
800
  if (data && Array.isArray(data.items)) {
738
801
  items = data.items.filter((item) => item && typeof item.key === "string" && typeof item.kind === "string");
739
802
  }
740
- } catch (e6) {
803
+ } catch (e7) {
741
804
  items = [];
742
805
  }
743
806
  }
@@ -751,7 +814,7 @@ function createEditorDocumentStore(options) {
751
814
  const newestFirst = (a, b) => a.savedAt < b.savedAt ? 1 : a.savedAt > b.savedAt ? -1 : 0;
752
815
  const readFile = async (key) => {
753
816
  const raw = await storage.get(fileKey(key));
754
- return raw === null ? null : _chunkM4MIZPQIcjs.parseEditorDocument.call(void 0, raw);
817
+ return raw === null ? null : _chunkSJ2CPTQCcjs.parseEditorDocument.call(void 0, raw);
755
818
  };
756
819
  return {
757
820
  save: (doc, name) => run(async () => {
@@ -763,9 +826,8 @@ function createEditorDocumentStore(options) {
763
826
  formatVersion: doc.formatVersion,
764
827
  savedAt: new Date(now()).toISOString()
765
828
  };
766
- const text = JSON.stringify(_chunkM4MIZPQIcjs.toEditorDocumentFile.call(void 0, doc, trimmed));
767
- if (doc.assets) await _chunkM4MIZPQIcjs.persistEditorAssets.call(void 0, storage, doc.assets, text);
768
- await storage.set(fileKey(entry.key), text);
829
+ const text = JSON.stringify(_chunkSJ2CPTQCcjs.toEditorDocumentFile.call(void 0, doc, trimmed));
830
+ await (doc.assets ? _chunkSJ2CPTQCcjs.withEditorAssets.call(void 0, storage, doc.assets) : storage).set(fileKey(entry.key), text);
769
831
  await saveIndex([...(await loadIndex()).filter((item) => item.key !== entry.key), entry]);
770
832
  return entry;
771
833
  }),
@@ -776,13 +838,13 @@ function createEditorDocumentStore(options) {
776
838
  const file = entry ? await readFile(key) : null;
777
839
  if (!entry || !file) {
778
840
  const keys = (await loadIndex()).filter((item) => item.kind === doc.kind).sort(newestFirst).map((item) => item.key);
779
- throw new (0, _chunkM4MIZPQIcjs.EditorDocumentLoadError)(
841
+ throw new (0, _chunkSJ2CPTQCcjs.EditorDocumentLoadError)(
780
842
  `There is no saved document "${key}". ${keys.length ? `Saved ${doc.kind} documents: ${keys.join(", ")}.` : `No ${doc.kind} documents are saved yet.`}`
781
843
  );
782
844
  }
783
845
  if (doc.assets) {
784
- const missing = await _chunkM4MIZPQIcjs.preloadEditorAssets.call(void 0, storage, doc.assets, file);
785
- if (missing.length) throw new (0, _chunkM4MIZPQIcjs.EditorDocumentLoadError)(`The saved document "${key}" refers to ${missing.length} asset${missing.length === 1 ? "" : "s"} that storage no longer has, so it cannot be opened.`);
846
+ const missing = await _chunkSJ2CPTQCcjs.preloadEditorAssets.call(void 0, storage, doc.assets, file);
847
+ if (missing.length) throw new (0, _chunkSJ2CPTQCcjs.EditorDocumentLoadError)(`The saved document "${key}" refers to ${missing.length} asset${missing.length === 1 ? "" : "s"} that storage no longer has, so it cannot be opened.`);
786
848
  }
787
849
  const report = doc.loadDocument(file, { history: _nullishCoalesce(openOptions.history, () => ( "reset")) });
788
850
  return { ...entry, report };
@@ -792,8 +854,10 @@ function createEditorDocumentStore(options) {
792
854
  if (!items.some((item) => item.key === key)) return false;
793
855
  await saveIndex(items.filter((item) => item.key !== key));
794
856
  await storage.remove(fileKey(key));
857
+ if (gc) await _chunkSJ2CPTQCcjs.tryCollectEditorAssetGarbage.call(void 0, storage, gc === true ? {} : gc);
795
858
  return true;
796
- })
859
+ }),
860
+ ...storage.keys ? { collectAssetGarbage: (gcOptions) => run(() => _chunkSJ2CPTQCcjs.collectEditorAssetGarbage.call(void 0, storage, gcOptions)) } : {}
797
861
  };
798
862
  }
799
863
 
@@ -939,13 +1003,66 @@ function createDocumentToolProvider(doc, options = {}) {
939
1003
  }
940
1004
  }
941
1005
  ];
1006
+ const LIST_LIMIT = 100;
1007
+ const assetTools = (s) => [
1008
+ {
1009
+ name: `find_unreferenced_${kind}_assets`,
1010
+ title: `Find unreferenced ${noun} assets`,
1011
+ description: `Finds the stored image data (layer pixels, masks and other assets of every image and paint document in the host's storage) that no saved document or autosave snapshot refers to any more, without deleting anything. The open ${noun}'s own assets always count as used. Returns { assets, referenced, kept, unreferenced, unreferencedChars, refs, more }: unreferenced is how many could be deleted, unreferencedChars the characters of data they hold, refs up to ${LIST_LIMIT} of their references for delete_unreferenced_${kind}_assets, and more how many were left out of refs.`,
1012
+ inputSchema: object({}),
1013
+ annotations: { title: `Find unreferenced ${noun} assets`, ...READ },
1014
+ handler: async () => {
1015
+ const report = await s.collectAssetGarbage({ dryRun: true, keep: [doc.getDocument()] });
1016
+ return {
1017
+ assets: report.assets,
1018
+ referenced: report.referenced,
1019
+ kept: report.kept,
1020
+ unreferenced: report.unreferenced.length,
1021
+ unreferencedChars: report.unreferencedChars,
1022
+ refs: report.unreferenced.slice(0, LIST_LIMIT),
1023
+ more: Math.max(0, report.unreferenced.length - LIST_LIMIT)
1024
+ };
1025
+ }
1026
+ },
1027
+ {
1028
+ name: `delete_unreferenced_${kind}_assets`,
1029
+ title: `Delete unreferenced ${noun} assets`,
1030
+ description: `Deletes stored image data that no saved document or autosave snapshot refers to, to free browser storage. Saved documents and the open ${noun} are never affected, and a write in progress is waited for. Call find_unreferenced_${kind}_assets first to see what would go, and do it when the user asks to free space or clean up storage. It cannot be undone, but nothing refers to the deleted data. Returns { removed, freedChars, kept, remaining }: how many assets were deleted, the characters of data freed, how many unreferenced ones were kept because a write was using them, and how many assets are left.`,
1031
+ inputSchema: object({
1032
+ refs: {
1033
+ type: "array",
1034
+ items: { type: "string", pattern: "^pixerate-asset:" },
1035
+ minItems: 1,
1036
+ uniqueItems: true,
1037
+ description: `Delete only these references from find_unreferenced_${kind}_assets (refs). Default: every unreferenced asset. If any is no longer unreferenced, nothing is deleted and the error names it.`
1038
+ }
1039
+ }),
1040
+ annotations: { title: `Delete unreferenced ${noun} assets`, ...OVERWRITE },
1041
+ handler: async ({ refs }) => {
1042
+ writable();
1043
+ const report = await s.collectAssetGarbage({ keep: [doc.getDocument()], ...refs ? { only: refs } : {} });
1044
+ return {
1045
+ removed: report.removed.length,
1046
+ freedChars: report.freedChars,
1047
+ kept: report.kept,
1048
+ remaining: report.assets - report.removed.length
1049
+ };
1050
+ }
1051
+ }
1052
+ ];
942
1053
  return {
943
1054
  id: _nullishCoalesce(options.id, () => ( `${kind}-document`)),
944
1055
  ...options.namespace !== void 0 ? { namespace: options.namespace } : {},
945
1056
  title: _nullishCoalesce(options.title, () => ( `${noun.charAt(0).toUpperCase()}${noun.slice(1)} document`)),
946
1057
  description: `Get, load${options.history === false ? "" : ", undo, redo"}${store ? ", save and open" : ""} the whole ${noun}.`,
947
1058
  ...options.skill ? { skill: options.skill } : {},
948
- tools: [get, load, ...options.history === false ? [] : [undo, redo], ...store ? storeTools(store) : []]
1059
+ tools: [
1060
+ get,
1061
+ load,
1062
+ ...options.history === false ? [] : [undo, redo],
1063
+ ...store ? storeTools(store) : [],
1064
+ ..._optionalChain([store, 'optionalAccess', _11 => _11.collectAssetGarbage]) && doc.assets ? assetTools(store) : []
1065
+ ]
949
1066
  };
950
1067
  }
951
1068
 
@@ -990,4 +1107,7 @@ function createDocumentToolProvider(doc, options = {}) {
990
1107
 
991
1108
 
992
1109
 
993
- exports.EDITOR_ASSET_PREFIX = _chunkM4MIZPQIcjs.EDITOR_ASSET_PREFIX; exports.EDITOR_DOCUMENT_FORMAT = _chunkM4MIZPQIcjs.EDITOR_DOCUMENT_FORMAT; exports.EditorDocumentLoadError = _chunkM4MIZPQIcjs.EditorDocumentLoadError; exports.InjectedStorageError = InjectedStorageError; exports.StorageUnavailableError = StorageUnavailableError; exports.collectAssetRefs = _chunkM4MIZPQIcjs.collectAssetRefs; exports.createAutosaveController = createAutosaveController; exports.createDocumentToolProvider = createDocumentToolProvider; exports.createEditorAssetRegistry = _chunkM4MIZPQIcjs.createEditorAssetRegistry; exports.createEditorAutosave = createEditorAutosave; exports.createEditorDocumentStore = createEditorDocumentStore; exports.createIndexedDbAdapter = createIndexedDbAdapter; exports.createLoadReport = _chunkM4MIZPQIcjs.createLoadReport; exports.createLocalStorageAdapter = createLocalStorageAdapter; exports.createMemoryStorage = createMemoryStorage; exports.createOpfsAdapter = createOpfsAdapter; exports.createRecoveryPolicy = createRecoveryPolicy; exports.createRevisionTracker = createRevisionTracker; exports.defineEditorDocument = _chunkM4MIZPQIcjs.defineEditorDocument; exports.exponentialBackoff = exponentialBackoff; exports.externalizeAssets = _chunkM4MIZPQIcjs.externalizeAssets; exports.formatRecoveryMessage = formatRecoveryMessage; exports.hasLoadProblems = _chunkM4MIZPQIcjs.hasLoadProblems; exports.internalizeAssets = _chunkM4MIZPQIcjs.internalizeAssets; exports.isEditorDocumentFile = _chunkM4MIZPQIcjs.isEditorDocumentFile; exports.isIndexedDbAvailable = isIndexedDbAvailable; exports.isInlineAsset = _chunkM4MIZPQIcjs.isInlineAsset; exports.isLocalStorageAvailable = isLocalStorageAvailable; exports.isOpfsAvailable = isOpfsAvailable; exports.loadDocument = _chunkM4MIZPQIcjs.loadDocument; exports.loadSections = _chunkM4MIZPQIcjs.loadSections; exports.migrateDocument = _chunkM4MIZPQIcjs.migrateDocument; exports.parseEditorDocument = _chunkM4MIZPQIcjs.parseEditorDocument; exports.persistEditorAssets = _chunkM4MIZPQIcjs.persistEditorAssets; exports.preloadEditorAssets = _chunkM4MIZPQIcjs.preloadEditorAssets; exports.recoveryMetaKey = recoveryMetaKey; exports.serializeEditorDocument = _chunkM4MIZPQIcjs.serializeEditorDocument; exports.slugifyDocumentName = slugifyDocumentName; exports.toEditorDocumentFile = _chunkM4MIZPQIcjs.toEditorDocumentFile; exports.withEditorAssets = _chunkM4MIZPQIcjs.withEditorAssets; exports.withStorageFailures = withStorageFailures;
1110
+
1111
+
1112
+
1113
+ exports.EDITOR_ASSET_PREFIX = _chunkSJ2CPTQCcjs.EDITOR_ASSET_PREFIX; exports.EDITOR_DOCUMENT_FORMAT = _chunkSJ2CPTQCcjs.EDITOR_DOCUMENT_FORMAT; exports.EditorDocumentLoadError = _chunkSJ2CPTQCcjs.EditorDocumentLoadError; exports.InjectedStorageError = InjectedStorageError; exports.StorageUnavailableError = StorageUnavailableError; exports.collectAssetRefs = _chunkSJ2CPTQCcjs.collectAssetRefs; exports.collectEditorAssetGarbage = _chunkSJ2CPTQCcjs.collectEditorAssetGarbage; exports.collectRetiredEditorAssets = _chunkSJ2CPTQCcjs.collectRetiredEditorAssets; exports.createAutosaveController = createAutosaveController; exports.createDocumentToolProvider = createDocumentToolProvider; exports.createEditorAssetRegistry = _chunkSJ2CPTQCcjs.createEditorAssetRegistry; exports.createEditorAutosave = createEditorAutosave; exports.createEditorDocumentStore = createEditorDocumentStore; exports.createIndexedDbAdapter = createIndexedDbAdapter; exports.createLoadReport = _chunkSJ2CPTQCcjs.createLoadReport; exports.createLocalStorageAdapter = createLocalStorageAdapter; exports.createMemoryStorage = createMemoryStorage; exports.createOpfsAdapter = createOpfsAdapter; exports.createRecoveryPolicy = createRecoveryPolicy; exports.createRevisionTracker = createRevisionTracker; exports.defineEditorDocument = _chunkSJ2CPTQCcjs.defineEditorDocument; exports.exponentialBackoff = exponentialBackoff; exports.externalizeAssets = _chunkSJ2CPTQCcjs.externalizeAssets; exports.formatRecoveryMessage = formatRecoveryMessage; exports.hasLoadProblems = _chunkSJ2CPTQCcjs.hasLoadProblems; exports.internalizeAssets = _chunkSJ2CPTQCcjs.internalizeAssets; exports.isEditorDocumentFile = _chunkSJ2CPTQCcjs.isEditorDocumentFile; exports.isIndexedDbAvailable = isIndexedDbAvailable; exports.isInlineAsset = _chunkSJ2CPTQCcjs.isInlineAsset; exports.isLocalStorageAvailable = isLocalStorageAvailable; exports.isOpfsAvailable = isOpfsAvailable; exports.loadDocument = _chunkSJ2CPTQCcjs.loadDocument; exports.loadSections = _chunkSJ2CPTQCcjs.loadSections; exports.migrateDocument = _chunkSJ2CPTQCcjs.migrateDocument; exports.parseEditorDocument = _chunkSJ2CPTQCcjs.parseEditorDocument; exports.persistEditorAssets = _chunkSJ2CPTQCcjs.persistEditorAssets; exports.preloadEditorAssets = _chunkSJ2CPTQCcjs.preloadEditorAssets; exports.recoveryMetaKey = recoveryMetaKey; exports.serializeEditorDocument = _chunkSJ2CPTQCcjs.serializeEditorDocument; exports.slugifyDocumentName = slugifyDocumentName; exports.toEditorDocumentFile = _chunkSJ2CPTQCcjs.toEditorDocumentFile; exports.tryCollectEditorAssetGarbage = _chunkSJ2CPTQCcjs.tryCollectEditorAssetGarbage; exports.withEditorAssets = _chunkSJ2CPTQCcjs.withEditorAssets; exports.withStorageFailures = withStorageFailures;
@@ -3,6 +3,8 @@ import {
3
3
  EDITOR_DOCUMENT_FORMAT,
4
4
  EditorDocumentLoadError,
5
5
  collectAssetRefs,
6
+ collectEditorAssetGarbage,
7
+ collectRetiredEditorAssets,
6
8
  createEditorAssetRegistry,
7
9
  createLoadReport,
8
10
  defineEditorDocument,
@@ -19,8 +21,9 @@ import {
19
21
  preloadEditorAssets,
20
22
  serializeEditorDocument,
21
23
  toEditorDocumentFile,
24
+ tryCollectEditorAssetGarbage,
22
25
  withEditorAssets
23
- } from "../chunk-NHLYK3AX.js";
26
+ } from "../chunk-XBQX76CT.js";
24
27
  import "../chunk-HF5QZS7Z.js";
25
28
 
26
29
  // src/persistence/recoveryPolicy.ts
@@ -136,7 +139,13 @@ function withStorageFailures(inner, failure) {
136
139
  async remove(key) {
137
140
  checkFailure(failure, "remove", key);
138
141
  return inner.remove(key);
139
- }
142
+ },
143
+ ...inner.keys ? {
144
+ async keys(prefix = "") {
145
+ checkFailure(failure, "keys", prefix);
146
+ return inner.keys(prefix);
147
+ }
148
+ } : {}
140
149
  };
141
150
  }
142
151
  function guarded(kind, isAvailable, failure, impl) {
@@ -160,7 +169,13 @@ function guarded(kind, isAvailable, failure, impl) {
160
169
  async remove(key) {
161
170
  check("remove", key);
162
171
  return impl.remove(key);
163
- }
172
+ },
173
+ ...impl.keys ? {
174
+ async keys(prefix = "") {
175
+ check("keys", prefix);
176
+ return impl.keys(prefix);
177
+ }
178
+ } : {}
164
179
  };
165
180
  }
166
181
  function createMemoryStorage(options = {}) {
@@ -174,6 +189,9 @@ function createMemoryStorage(options = {}) {
174
189
  },
175
190
  async remove(key) {
176
191
  entries.delete(key);
192
+ },
193
+ async keys(prefix) {
194
+ return [...entries.keys()].filter((key) => key.startsWith(prefix));
177
195
  }
178
196
  });
179
197
  return Object.assign(adapter, { entries });
@@ -200,7 +218,18 @@ function createLocalStorageAdapter(prefix = "", options = {}) {
200
218
  },
201
219
  async remove(key) {
202
220
  storage.removeItem(prefix + key);
203
- }
221
+ },
222
+ // Listing needs `length` and `key()` (real Web Storage has both).
223
+ ...!storage || typeof storage.key === "function" && typeof storage.length === "number" ? {
224
+ async keys(start) {
225
+ const out = [];
226
+ for (let i = 0; i < storage.length; i++) {
227
+ const key = storage.key(i);
228
+ if (key !== null && key.startsWith(prefix + start)) out.push(key.slice(prefix.length));
229
+ }
230
+ return out;
231
+ }
232
+ } : {}
204
233
  });
205
234
  }
206
235
  function detectIndexedDb() {
@@ -255,7 +284,13 @@ function createIndexedDbAdapter(name = "pixerate-editor", options = {}) {
255
284
  return typeof value === "string" ? value : null;
256
285
  },
257
286
  set: (key, value) => write((store) => store.put(value, key)),
258
- remove: (key) => write((store) => store.delete(key))
287
+ remove: (key) => write((store) => store.delete(key)),
288
+ async keys(prefix) {
289
+ const db = await open();
290
+ const tx = db.transaction(storeName, "readonly");
291
+ const all = await requestToPromise(tx.objectStore(storeName).getAllKeys());
292
+ return all.filter((key) => typeof key === "string" && key.startsWith(prefix));
293
+ }
259
294
  });
260
295
  }
261
296
  function detectOpfsRoot() {
@@ -339,6 +374,30 @@ function createOpfsAdapter(dir = "pixerate-editor", options = {}) {
339
374
  for (const name of [`${base}.ptr`, base, `${base}.a`, `${base}.b`, `${base}.tmp`]) {
340
375
  await removeFile(d, name);
341
376
  }
377
+ },
378
+ async keys(prefix) {
379
+ const d = await directory();
380
+ if (typeof d.keys !== "function") throw new StorageUnavailableError("opfs (directory listing)");
381
+ const names = /* @__PURE__ */ new Set();
382
+ for await (const name of d.keys()) names.add(name);
383
+ const keys = /* @__PURE__ */ new Set();
384
+ for (const name of names) {
385
+ if (name.endsWith(".tmp")) continue;
386
+ if (name.endsWith(".ptr")) keys.add(name.slice(0, -4));
387
+ else if (/\.[ab]$/.test(name) && names.has(`${name.slice(0, -2)}.ptr`)) continue;
388
+ else keys.add(name);
389
+ }
390
+ const out = [];
391
+ for (const name of keys) {
392
+ let key;
393
+ try {
394
+ key = decodeURIComponent(name);
395
+ } catch {
396
+ continue;
397
+ }
398
+ if (key.startsWith(prefix)) out.push(key);
399
+ }
400
+ return out;
342
401
  }
343
402
  });
344
403
  }
@@ -670,7 +729,8 @@ function createRevisionTracker(options = {}) {
670
729
 
671
730
  // src/persistence/editorAutosave.ts
672
731
  function createEditorAutosave(doc, options) {
673
- const { dirty, key, ...rest } = options;
732
+ const { dirty, key, collectAssetGarbage, ...rest } = options;
733
+ const collect = () => collectRetiredEditorAssets(rest.storage, doc, collectAssetGarbage);
674
734
  const revisions = createRevisionTracker({ history: doc, dirty });
675
735
  const controller = createAutosaveController({
676
736
  ...rest,
@@ -696,10 +756,12 @@ function createEditorAutosave(doc, options) {
696
756
  async discard() {
697
757
  await controller.discardRecovery();
698
758
  controller.ready();
759
+ await collect();
699
760
  },
700
761
  async markClean() {
701
762
  revisions.markSaved();
702
763
  await controller.markClean();
764
+ await collect();
703
765
  },
704
766
  destroy() {
705
767
  unsubscribe();
@@ -720,6 +782,7 @@ function createEditorDocumentStore(options) {
720
782
  const now = options.now ?? Date.now;
721
783
  const indexKey = `${ns}:index`;
722
784
  const fileKey = (key) => `${ns}:file:${key}`;
785
+ const gc = options.collectAssetGarbage ?? true;
723
786
  let index = null;
724
787
  let queue = Promise.resolve();
725
788
  const run = (task) => {
@@ -764,8 +827,7 @@ function createEditorDocumentStore(options) {
764
827
  savedAt: new Date(now()).toISOString()
765
828
  };
766
829
  const text = JSON.stringify(toEditorDocumentFile(doc, trimmed));
767
- if (doc.assets) await persistEditorAssets(storage, doc.assets, text);
768
- await storage.set(fileKey(entry.key), text);
830
+ await (doc.assets ? withEditorAssets(storage, doc.assets) : storage).set(fileKey(entry.key), text);
769
831
  await saveIndex([...(await loadIndex()).filter((item) => item.key !== entry.key), entry]);
770
832
  return entry;
771
833
  }),
@@ -792,8 +854,10 @@ function createEditorDocumentStore(options) {
792
854
  if (!items.some((item) => item.key === key)) return false;
793
855
  await saveIndex(items.filter((item) => item.key !== key));
794
856
  await storage.remove(fileKey(key));
857
+ if (gc) await tryCollectEditorAssetGarbage(storage, gc === true ? {} : gc);
795
858
  return true;
796
- })
859
+ }),
860
+ ...storage.keys ? { collectAssetGarbage: (gcOptions) => run(() => collectEditorAssetGarbage(storage, gcOptions)) } : {}
797
861
  };
798
862
  }
799
863
 
@@ -939,13 +1003,66 @@ function createDocumentToolProvider(doc, options = {}) {
939
1003
  }
940
1004
  }
941
1005
  ];
1006
+ const LIST_LIMIT = 100;
1007
+ const assetTools = (s) => [
1008
+ {
1009
+ name: `find_unreferenced_${kind}_assets`,
1010
+ title: `Find unreferenced ${noun} assets`,
1011
+ description: `Finds the stored image data (layer pixels, masks and other assets of every image and paint document in the host's storage) that no saved document or autosave snapshot refers to any more, without deleting anything. The open ${noun}'s own assets always count as used. Returns { assets, referenced, kept, unreferenced, unreferencedChars, refs, more }: unreferenced is how many could be deleted, unreferencedChars the characters of data they hold, refs up to ${LIST_LIMIT} of their references for delete_unreferenced_${kind}_assets, and more how many were left out of refs.`,
1012
+ inputSchema: object({}),
1013
+ annotations: { title: `Find unreferenced ${noun} assets`, ...READ },
1014
+ handler: async () => {
1015
+ const report = await s.collectAssetGarbage({ dryRun: true, keep: [doc.getDocument()] });
1016
+ return {
1017
+ assets: report.assets,
1018
+ referenced: report.referenced,
1019
+ kept: report.kept,
1020
+ unreferenced: report.unreferenced.length,
1021
+ unreferencedChars: report.unreferencedChars,
1022
+ refs: report.unreferenced.slice(0, LIST_LIMIT),
1023
+ more: Math.max(0, report.unreferenced.length - LIST_LIMIT)
1024
+ };
1025
+ }
1026
+ },
1027
+ {
1028
+ name: `delete_unreferenced_${kind}_assets`,
1029
+ title: `Delete unreferenced ${noun} assets`,
1030
+ description: `Deletes stored image data that no saved document or autosave snapshot refers to, to free browser storage. Saved documents and the open ${noun} are never affected, and a write in progress is waited for. Call find_unreferenced_${kind}_assets first to see what would go, and do it when the user asks to free space or clean up storage. It cannot be undone, but nothing refers to the deleted data. Returns { removed, freedChars, kept, remaining }: how many assets were deleted, the characters of data freed, how many unreferenced ones were kept because a write was using them, and how many assets are left.`,
1031
+ inputSchema: object({
1032
+ refs: {
1033
+ type: "array",
1034
+ items: { type: "string", pattern: "^pixerate-asset:" },
1035
+ minItems: 1,
1036
+ uniqueItems: true,
1037
+ description: `Delete only these references from find_unreferenced_${kind}_assets (refs). Default: every unreferenced asset. If any is no longer unreferenced, nothing is deleted and the error names it.`
1038
+ }
1039
+ }),
1040
+ annotations: { title: `Delete unreferenced ${noun} assets`, ...OVERWRITE },
1041
+ handler: async ({ refs }) => {
1042
+ writable();
1043
+ const report = await s.collectAssetGarbage({ keep: [doc.getDocument()], ...refs ? { only: refs } : {} });
1044
+ return {
1045
+ removed: report.removed.length,
1046
+ freedChars: report.freedChars,
1047
+ kept: report.kept,
1048
+ remaining: report.assets - report.removed.length
1049
+ };
1050
+ }
1051
+ }
1052
+ ];
942
1053
  return {
943
1054
  id: options.id ?? `${kind}-document`,
944
1055
  ...options.namespace !== void 0 ? { namespace: options.namespace } : {},
945
1056
  title: options.title ?? `${noun.charAt(0).toUpperCase()}${noun.slice(1)} document`,
946
1057
  description: `Get, load${options.history === false ? "" : ", undo, redo"}${store ? ", save and open" : ""} the whole ${noun}.`,
947
1058
  ...options.skill ? { skill: options.skill } : {},
948
- tools: [get, load, ...options.history === false ? [] : [undo, redo], ...store ? storeTools(store) : []]
1059
+ tools: [
1060
+ get,
1061
+ load,
1062
+ ...options.history === false ? [] : [undo, redo],
1063
+ ...store ? storeTools(store) : [],
1064
+ ...store?.collectAssetGarbage && doc.assets ? assetTools(store) : []
1065
+ ]
949
1066
  };
950
1067
  }
951
1068
  export {
@@ -955,6 +1072,8 @@ export {
955
1072
  InjectedStorageError,
956
1073
  StorageUnavailableError,
957
1074
  collectAssetRefs,
1075
+ collectEditorAssetGarbage,
1076
+ collectRetiredEditorAssets,
958
1077
  createAutosaveController,
959
1078
  createDocumentToolProvider,
960
1079
  createEditorAssetRegistry,
@@ -988,6 +1107,7 @@ export {
988
1107
  serializeEditorDocument,
989
1108
  slugifyDocumentName,
990
1109
  toEditorDocumentFile,
1110
+ tryCollectEditorAssetGarbage,
991
1111
  withEditorAssets,
992
1112
  withStorageFailures
993
1113
  };
@@ -5,7 +5,7 @@
5
5
  * construction, so SSR and old browsers are safe) and accepts an injectable
6
6
  * failure mode for tests.
7
7
  */
8
- export type StorageOperation = "get" | "set" | "remove";
8
+ export type StorageOperation = "get" | "set" | "remove" | "keys";
9
9
  /**
10
10
  * Injected failure: return `true` (or an `Error`) to make that operation fail.
11
11
  * Example: `failure: (op) => op === "set"` simulates a full disk.
@@ -19,6 +19,12 @@ export interface StorageAdapter {
19
19
  get(key: string): Promise<string | null>;
20
20
  set(key: string, value: string): Promise<void>;
21
21
  remove(key: string): Promise<void>;
22
+ /**
23
+ * Every stored key that starts with `prefix` (default: every key), in no particular order.
24
+ * Optional: custom adapters may leave it out, but asset garbage collection
25
+ * (`collectEditorAssetGarbage`) needs it. All built-in adapters have it.
26
+ */
27
+ keys?(prefix?: string): Promise<string[]>;
22
28
  }
23
29
  export interface StorageAdapterOptions {
24
30
  failure?: StorageFailureMode;
@@ -44,6 +50,10 @@ export interface WebStorageLike {
44
50
  getItem(key: string): string | null;
45
51
  setItem(key: string, value: string): void;
46
52
  removeItem(key: string): void;
53
+ /** Number of stored items (with `key`, lets the adapter list its keys). */
54
+ readonly length?: number;
55
+ /** The key at `index` (with `length`, lets the adapter list its keys). */
56
+ key?(index: number): string | null;
47
57
  }
48
58
  export interface LocalStorageAdapterOptions extends StorageAdapterOptions {
49
59
  /** Storage to use (default `globalThis.localStorage`, e.g. pass `sessionStorage`). */
@@ -84,6 +94,8 @@ export interface OpfsDirectoryLike {
84
94
  create?: boolean;
85
95
  }): Promise<OpfsFileLike>;
86
96
  removeEntry(name: string): Promise<void>;
97
+ /** Entry names (real directory handles have it); lets the adapter list its keys. */
98
+ keys?(): AsyncIterable<string>;
87
99
  }
88
100
  export interface OpfsFileLike {
89
101
  getFile(): Promise<{
@@ -5,7 +5,7 @@
5
5
  * construction, so SSR and old browsers are safe) and accepts an injectable
6
6
  * failure mode for tests.
7
7
  */
8
- export type StorageOperation = "get" | "set" | "remove";
8
+ export type StorageOperation = "get" | "set" | "remove" | "keys";
9
9
  /**
10
10
  * Injected failure: return `true` (or an `Error`) to make that operation fail.
11
11
  * Example: `failure: (op) => op === "set"` simulates a full disk.
@@ -19,6 +19,12 @@ export interface StorageAdapter {
19
19
  get(key: string): Promise<string | null>;
20
20
  set(key: string, value: string): Promise<void>;
21
21
  remove(key: string): Promise<void>;
22
+ /**
23
+ * Every stored key that starts with `prefix` (default: every key), in no particular order.
24
+ * Optional: custom adapters may leave it out, but asset garbage collection
25
+ * (`collectEditorAssetGarbage`) needs it. All built-in adapters have it.
26
+ */
27
+ keys?(prefix?: string): Promise<string[]>;
22
28
  }
23
29
  export interface StorageAdapterOptions {
24
30
  failure?: StorageFailureMode;
@@ -44,6 +50,10 @@ export interface WebStorageLike {
44
50
  getItem(key: string): string | null;
45
51
  setItem(key: string, value: string): void;
46
52
  removeItem(key: string): void;
53
+ /** Number of stored items (with `key`, lets the adapter list its keys). */
54
+ readonly length?: number;
55
+ /** The key at `index` (with `length`, lets the adapter list its keys). */
56
+ key?(index: number): string | null;
47
57
  }
48
58
  export interface LocalStorageAdapterOptions extends StorageAdapterOptions {
49
59
  /** Storage to use (default `globalThis.localStorage`, e.g. pass `sessionStorage`). */
@@ -84,6 +94,8 @@ export interface OpfsDirectoryLike {
84
94
  create?: boolean;
85
95
  }): Promise<OpfsFileLike>;
86
96
  removeEntry(name: string): Promise<void>;
97
+ /** Entry names (real directory handles have it); lets the adapter list its keys. */
98
+ keys?(): AsyncIterable<string>;
87
99
  }
88
100
  export interface OpfsFileLike {
89
101
  getFile(): Promise<{