@pixerate/editor 0.23.0 → 0.25.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 (187) hide show
  1. package/README.md +167 -2
  2. package/THIRD_PARTY_NOTICES.md +80 -0
  3. package/dist/agent/index.cjs +10 -8
  4. package/dist/agent/index.cjs.map +1 -1
  5. package/dist/agent/index.d.cts +4 -3
  6. package/dist/agent/index.d.ts +4 -3
  7. package/dist/agent/index.js +4 -2
  8. package/dist/agent/index.js.map +1 -1
  9. package/dist/canvas/index.cjs +4437 -411
  10. package/dist/canvas/index.cjs.map +1 -1
  11. package/dist/canvas/index.d.cts +934 -4
  12. package/dist/canvas/index.d.ts +934 -4
  13. package/dist/canvas/index.js +4309 -283
  14. package/dist/canvas/index.js.map +1 -1
  15. package/dist/chunk-4BVP64JJ.cjs +1406 -0
  16. package/dist/chunk-4BVP64JJ.cjs.map +1 -0
  17. package/dist/chunk-664LQYFJ.js +115 -0
  18. package/dist/chunk-664LQYFJ.js.map +1 -0
  19. package/dist/chunk-7WISYZFO.cjs +740 -0
  20. package/dist/chunk-7WISYZFO.cjs.map +1 -0
  21. package/dist/chunk-AL6JMGWW.cjs +783 -0
  22. package/dist/chunk-AL6JMGWW.cjs.map +1 -0
  23. package/dist/chunk-BVP3TSLH.cjs +69 -0
  24. package/dist/chunk-BVP3TSLH.cjs.map +1 -0
  25. package/dist/{chunk-RIKBJLS2.cjs → chunk-C7P22VIP.cjs} +2001 -279
  26. package/dist/chunk-C7P22VIP.cjs.map +1 -0
  27. package/dist/chunk-D6FH2YAX.cjs +131 -0
  28. package/dist/chunk-D6FH2YAX.cjs.map +1 -0
  29. package/dist/chunk-ECCCYKGL.js +740 -0
  30. package/dist/chunk-ECCCYKGL.js.map +1 -0
  31. package/dist/{chunk-4SUNJZWB.cjs → chunk-FD4LPFAE.cjs} +48 -200
  32. package/dist/chunk-FD4LPFAE.cjs.map +1 -0
  33. package/dist/{chunk-QBJ22YZR.js → chunk-FH6F67DN.js} +1999 -277
  34. package/dist/chunk-FH6F67DN.js.map +1 -0
  35. package/dist/{chunk-OYXSKQFR.js → chunk-FPENXAEJ.js} +35 -187
  36. package/dist/chunk-FPENXAEJ.js.map +1 -0
  37. package/dist/chunk-HLIMTC6N.js +1 -0
  38. package/dist/chunk-HLIMTC6N.js.map +1 -0
  39. package/dist/chunk-HRMEDT2L.js +1406 -0
  40. package/dist/chunk-HRMEDT2L.js.map +1 -0
  41. package/dist/{chunk-QGHGJ56O.js → chunk-IXHSYOVY.js} +2518 -122
  42. package/dist/chunk-IXHSYOVY.js.map +1 -0
  43. package/dist/chunk-MKA3AHLV.cjs +256 -0
  44. package/dist/chunk-MKA3AHLV.cjs.map +1 -0
  45. package/dist/chunk-NAXVVS5I.js +783 -0
  46. package/dist/chunk-NAXVVS5I.js.map +1 -0
  47. package/dist/chunk-O5RYH2PG.js +69 -0
  48. package/dist/chunk-O5RYH2PG.js.map +1 -0
  49. package/dist/chunk-OXSV64JX.js +689 -0
  50. package/dist/chunk-OXSV64JX.js.map +1 -0
  51. package/dist/chunk-P3RQ5UJE.cjs +689 -0
  52. package/dist/chunk-P3RQ5UJE.cjs.map +1 -0
  53. package/dist/chunk-P6ODPZ4W.js +256 -0
  54. package/dist/chunk-P6ODPZ4W.js.map +1 -0
  55. package/dist/chunk-PL4JB3BF.cjs +1 -0
  56. package/dist/chunk-PL4JB3BF.cjs.map +1 -0
  57. package/dist/{chunk-GVSMRPHB.cjs → chunk-PO6A6QQH.cjs} +2607 -211
  58. package/dist/chunk-PO6A6QQH.cjs.map +1 -0
  59. package/dist/{chunk-JJDLPEWL.js → chunk-QGYDWPRM.js} +2 -2
  60. package/dist/chunk-RR7F5GUM.js +5726 -0
  61. package/dist/chunk-RR7F5GUM.js.map +1 -0
  62. package/dist/chunk-TNNJXF46.cjs +115 -0
  63. package/dist/chunk-TNNJXF46.cjs.map +1 -0
  64. package/dist/chunk-U5HZSFCS.js +131 -0
  65. package/dist/chunk-U5HZSFCS.js.map +1 -0
  66. package/dist/{chunk-MJZBNWG7.js → chunk-UAKDG62Z.js} +309 -581
  67. package/dist/chunk-UAKDG62Z.js.map +1 -0
  68. package/dist/{chunk-CL37JGFW.cjs → chunk-V3CHHWOE.cjs} +4 -4
  69. package/dist/{chunk-CL37JGFW.cjs.map → chunk-V3CHHWOE.cjs.map} +1 -1
  70. package/dist/chunk-WSQKHXW7.cjs +5726 -0
  71. package/dist/chunk-WSQKHXW7.cjs.map +1 -0
  72. package/dist/{chunk-AA4ZEVCC.cjs → chunk-X4N4WUKB.cjs} +336 -608
  73. package/dist/chunk-X4N4WUKB.cjs.map +1 -0
  74. package/dist/commands/index.cjs +74 -1
  75. package/dist/commands/index.cjs.map +1 -1
  76. package/dist/commands/index.d.cts +38 -353
  77. package/dist/commands/index.d.ts +38 -353
  78. package/dist/commands/index.js +73 -0
  79. package/dist/commands/index.js.map +1 -1
  80. package/dist/comment-store-BEMgVlAA.d.cts +135 -0
  81. package/dist/comment-store-BEMgVlAA.d.ts +135 -0
  82. package/dist/compare/index.cjs +15 -0
  83. package/dist/compare/index.cjs.map +1 -0
  84. package/dist/compare/index.d.cts +58 -0
  85. package/dist/compare/index.d.ts +58 -0
  86. package/dist/compare/index.js +15 -0
  87. package/dist/compare/index.js.map +1 -0
  88. package/dist/controller-BIJiNoVs.d.cts +1842 -0
  89. package/dist/controller-BIJiNoVs.d.ts +1842 -0
  90. package/dist/documents-CHT1U5Vs.d.ts +86 -0
  91. package/dist/documents-Gp7wb0Me.d.cts +86 -0
  92. package/dist/extensions/index.cjs +100 -5
  93. package/dist/extensions/index.cjs.map +1 -1
  94. package/dist/extensions/index.d.cts +16 -4
  95. package/dist/extensions/index.d.ts +16 -4
  96. package/dist/extensions/index.js +100 -5
  97. package/dist/generation/index.cjs +2317 -0
  98. package/dist/generation/index.cjs.map +1 -0
  99. package/dist/generation/index.d.cts +1015 -0
  100. package/dist/generation/index.d.ts +1015 -0
  101. package/dist/generation/index.js +2317 -0
  102. package/dist/generation/index.js.map +1 -0
  103. package/dist/grammar/index.cjs +48 -3
  104. package/dist/grammar/index.cjs.map +1 -1
  105. package/dist/grammar/index.d.cts +322 -100
  106. package/dist/grammar/index.d.ts +322 -100
  107. package/dist/grammar/index.js +54 -9
  108. package/dist/image-editor/index.cjs +4392 -481
  109. package/dist/image-editor/index.cjs.map +1 -1
  110. package/dist/image-editor/index.d.cts +1249 -97
  111. package/dist/image-editor/index.d.ts +1249 -97
  112. package/dist/image-editor/index.js +4281 -370
  113. package/dist/image-editor/index.js.map +1 -1
  114. package/dist/index-7a6bHTLy.d.ts +1754 -0
  115. package/dist/index-Csd5qI8u.d.cts +1754 -0
  116. package/dist/index.cjs +1682 -99
  117. package/dist/index.cjs.map +1 -1
  118. package/dist/index.d.cts +118 -21
  119. package/dist/index.d.ts +118 -21
  120. package/dist/index.js +1630 -47
  121. package/dist/index.js.map +1 -1
  122. package/dist/mention-text-CDnCFd3c.d.cts +53 -0
  123. package/dist/mention-text-CDnCFd3c.d.ts +53 -0
  124. package/dist/migrate-iTlm3a8h.d.cts +112 -0
  125. package/dist/migrate-iTlm3a8h.d.ts +112 -0
  126. package/dist/model-C1zApQM8.d.cts +219 -0
  127. package/dist/model-C1zApQM8.d.ts +219 -0
  128. package/dist/persistence/index.cjs +676 -0
  129. package/dist/persistence/index.cjs.map +1 -0
  130. package/dist/persistence/index.d.cts +230 -0
  131. package/dist/persistence/index.d.ts +230 -0
  132. package/dist/persistence/index.js +676 -0
  133. package/dist/persistence/index.js.map +1 -0
  134. package/dist/{registry-D5-R90p0.d.cts → registry-BDe-vlv3.d.ts} +2 -40
  135. package/dist/{registry-GJI4gToU.d.ts → registry-CEgwXED1.d.cts} +12 -1
  136. package/dist/{registry-GJI4gToU.d.cts → registry-CEgwXED1.d.ts} +12 -1
  137. package/dist/registry-D32LpIc4.d.cts +354 -0
  138. package/dist/{registry-D5-R90p0.d.ts → registry-DnzpgY27.d.cts} +2 -40
  139. package/dist/registry-evKTP4ls.d.ts +354 -0
  140. package/dist/schema-DYDGdYL9.d.cts +41 -0
  141. package/dist/schema-DYDGdYL9.d.ts +41 -0
  142. package/dist/serializers/index.cjs +12 -3
  143. package/dist/serializers/index.cjs.map +1 -1
  144. package/dist/serializers/index.d.cts +40 -12
  145. package/dist/serializers/index.d.ts +40 -12
  146. package/dist/serializers/index.js +12 -3
  147. package/dist/sourcemap-C8AH3MBQ.d.cts +91 -0
  148. package/dist/sourcemap-DA1Evhrv.d.ts +91 -0
  149. package/dist/spreadsheet/index.cjs +161 -3
  150. package/dist/spreadsheet/index.cjs.map +1 -1
  151. package/dist/spreadsheet/index.d.cts +611 -20
  152. package/dist/spreadsheet/index.d.ts +611 -20
  153. package/dist/spreadsheet/index.js +168 -10
  154. package/dist/storage-BpFfspHm.d.cts +120 -0
  155. package/dist/storage-BpFfspHm.d.ts +120 -0
  156. package/dist/tokens/index.cjs +115 -0
  157. package/dist/tokens/index.cjs.map +1 -0
  158. package/dist/tokens/index.d.cts +212 -0
  159. package/dist/tokens/index.d.ts +212 -0
  160. package/dist/tokens/index.js +115 -0
  161. package/dist/tokens/index.js.map +1 -0
  162. package/dist/{tokens-CyLZmIei.d.cts → tokens-D7yXvUU6.d.cts} +15 -2
  163. package/dist/{tokens-CyLZmIei.d.ts → tokens-D7yXvUU6.d.ts} +15 -2
  164. package/dist/{track-changes-BSF58RK0.d.cts → track-changes-D_VAPELi.d.cts} +110 -144
  165. package/dist/{track-changes-BSF58RK0.d.ts → track-changes-Db_fGEvx.d.ts} +110 -144
  166. package/package.json +21 -1
  167. package/dist/chunk-4SUNJZWB.cjs.map +0 -1
  168. package/dist/chunk-AA4ZEVCC.cjs.map +0 -1
  169. package/dist/chunk-AXV2ZW34.cjs +0 -118
  170. package/dist/chunk-AXV2ZW34.cjs.map +0 -1
  171. package/dist/chunk-B2ZRLN4S.js +0 -118
  172. package/dist/chunk-B2ZRLN4S.js.map +0 -1
  173. package/dist/chunk-GVSMRPHB.cjs.map +0 -1
  174. package/dist/chunk-K3R5NG2D.js +0 -3100
  175. package/dist/chunk-K3R5NG2D.js.map +0 -1
  176. package/dist/chunk-MJZBNWG7.js.map +0 -1
  177. package/dist/chunk-OYXSKQFR.js.map +0 -1
  178. package/dist/chunk-QBJ22YZR.js.map +0 -1
  179. package/dist/chunk-QGHGJ56O.js.map +0 -1
  180. package/dist/chunk-RIKBJLS2.cjs.map +0 -1
  181. package/dist/chunk-XDMXXC4Z.cjs +0 -3100
  182. package/dist/chunk-XDMXXC4Z.cjs.map +0 -1
  183. package/dist/controller-pePCJLT1.d.cts +0 -959
  184. package/dist/controller-pePCJLT1.d.ts +0 -959
  185. package/dist/index-CCr_z9kF.d.cts +0 -899
  186. package/dist/index-Ck6ajwXh.d.ts +0 -899
  187. /package/dist/{chunk-JJDLPEWL.js.map → chunk-QGYDWPRM.js.map} +0 -0
@@ -0,0 +1,676 @@
1
+ import {
2
+ createLoadReport,
3
+ hasLoadProblems,
4
+ loadDocument,
5
+ loadSections,
6
+ migrateDocument
7
+ } from "../chunk-U5HZSFCS.js";
8
+
9
+ // src/persistence/recoveryPolicy.ts
10
+ function exponentialBackoff(failures, base = 1e3, max = 3e4) {
11
+ const exp = Math.min(Math.max(failures - 1, 0), 16);
12
+ return Math.min(base * 2 ** exp, max);
13
+ }
14
+ function createRecoveryPolicy(options = {}) {
15
+ const interval = options.interval ?? 5e3;
16
+ const retryDelay = options.retryDelay ?? (() => interval);
17
+ const minWake = interval * 0.01;
18
+ let lastRevision = null;
19
+ let lastTime = null;
20
+ let gap = interval;
21
+ let ownsDirtyMeta = false;
22
+ let cleaning = false;
23
+ let snapshots = 0;
24
+ let snapshotsAtClean = 0;
25
+ let failures = 0;
26
+ const fail = (now) => {
27
+ failures++;
28
+ lastRevision = null;
29
+ lastTime = now;
30
+ gap = retryDelay(failures);
31
+ };
32
+ return {
33
+ tick(now, revision, savedRevision, tickOptions) {
34
+ const force = tickOptions?.force === true;
35
+ const since = force || lastTime === null ? Infinity : now - lastTime;
36
+ if (revision !== savedRevision) {
37
+ if (lastRevision === revision) return { type: "nothing" };
38
+ if (since >= gap) {
39
+ lastRevision = revision;
40
+ lastTime = now;
41
+ ownsDirtyMeta = true;
42
+ snapshots++;
43
+ return { type: "writeSnapshot", revision };
44
+ }
45
+ return { type: "wakeAfter", delay: Math.max(gap - since, minWake) };
46
+ }
47
+ if (ownsDirtyMeta && !cleaning) {
48
+ if (lastRevision !== null || since >= gap) {
49
+ cleaning = true;
50
+ snapshotsAtClean = snapshots;
51
+ return { type: "writeClean" };
52
+ }
53
+ return { type: "wakeAfter", delay: Math.max(gap - since, minWake) };
54
+ }
55
+ return { type: "nothing" };
56
+ },
57
+ written(revision, ok, now) {
58
+ if (ok) {
59
+ if (lastRevision === revision) {
60
+ failures = 0;
61
+ gap = interval;
62
+ }
63
+ } else if (lastRevision === revision) {
64
+ fail(now);
65
+ }
66
+ },
67
+ cleaned(ok, now) {
68
+ cleaning = false;
69
+ if (ok) {
70
+ ownsDirtyMeta = snapshots !== snapshotsAtClean;
71
+ failures = 0;
72
+ gap = interval;
73
+ } else {
74
+ fail(now);
75
+ }
76
+ },
77
+ get ownsSnapshot() {
78
+ return ownsDirtyMeta;
79
+ },
80
+ get failures() {
81
+ return failures;
82
+ }
83
+ };
84
+ }
85
+
86
+ // src/persistence/storage.ts
87
+ var StorageUnavailableError = class extends Error {
88
+ constructor(kind) {
89
+ super(`${kind} storage is not available in this environment`);
90
+ this.name = "StorageUnavailableError";
91
+ }
92
+ };
93
+ var InjectedStorageError = class extends Error {
94
+ constructor(operation, key) {
95
+ super(`injected ${operation} failure for "${key}"`);
96
+ this.name = "InjectedStorageError";
97
+ }
98
+ };
99
+ function checkFailure(failure, operation, key) {
100
+ if (!failure) return;
101
+ const result = failure(operation, key);
102
+ if (result instanceof Error) throw result;
103
+ if (result) throw new InjectedStorageError(operation, key);
104
+ }
105
+ function withStorageFailures(inner, failure) {
106
+ return {
107
+ kind: inner.kind,
108
+ get available() {
109
+ return inner.available;
110
+ },
111
+ async get(key) {
112
+ checkFailure(failure, "get", key);
113
+ return inner.get(key);
114
+ },
115
+ async set(key, value) {
116
+ checkFailure(failure, "set", key);
117
+ return inner.set(key, value);
118
+ },
119
+ async remove(key) {
120
+ checkFailure(failure, "remove", key);
121
+ return inner.remove(key);
122
+ }
123
+ };
124
+ }
125
+ function guarded(kind, isAvailable, failure, impl) {
126
+ const check = (operation, key) => {
127
+ if (!isAvailable()) throw new StorageUnavailableError(kind);
128
+ checkFailure(failure, operation, key);
129
+ };
130
+ return {
131
+ kind,
132
+ get available() {
133
+ return isAvailable();
134
+ },
135
+ async get(key) {
136
+ check("get", key);
137
+ return impl.get(key);
138
+ },
139
+ async set(key, value) {
140
+ check("set", key);
141
+ return impl.set(key, value);
142
+ },
143
+ async remove(key) {
144
+ check("remove", key);
145
+ return impl.remove(key);
146
+ }
147
+ };
148
+ }
149
+ function createMemoryStorage(options = {}) {
150
+ const entries = new Map(Object.entries(options.initial ?? {}));
151
+ const adapter = guarded("memory", () => true, options.failure, {
152
+ async get(key) {
153
+ return entries.has(key) ? entries.get(key) : null;
154
+ },
155
+ async set(key, value) {
156
+ entries.set(key, value);
157
+ },
158
+ async remove(key) {
159
+ entries.delete(key);
160
+ }
161
+ });
162
+ return Object.assign(adapter, { entries });
163
+ }
164
+ function detectLocalStorage() {
165
+ try {
166
+ const storage = globalThis.localStorage;
167
+ return storage && typeof storage.getItem === "function" ? storage : void 0;
168
+ } catch {
169
+ return void 0;
170
+ }
171
+ }
172
+ function isLocalStorageAvailable() {
173
+ return detectLocalStorage() !== void 0;
174
+ }
175
+ function createLocalStorageAdapter(prefix = "", options = {}) {
176
+ const storage = options.storage ?? detectLocalStorage();
177
+ return guarded("localStorage", () => storage !== void 0, options.failure, {
178
+ async get(key) {
179
+ return storage.getItem(prefix + key);
180
+ },
181
+ async set(key, value) {
182
+ storage.setItem(prefix + key, value);
183
+ },
184
+ async remove(key) {
185
+ storage.removeItem(prefix + key);
186
+ }
187
+ });
188
+ }
189
+ function detectIndexedDb() {
190
+ try {
191
+ const factory = globalThis.indexedDB;
192
+ return factory && typeof factory.open === "function" ? factory : void 0;
193
+ } catch {
194
+ return void 0;
195
+ }
196
+ }
197
+ function isIndexedDbAvailable() {
198
+ return detectIndexedDb() !== void 0;
199
+ }
200
+ var requestToPromise = (request) => new Promise((resolve, reject) => {
201
+ request.onsuccess = () => resolve(request.result);
202
+ request.onerror = () => reject(request.error ?? new Error("IndexedDB request failed"));
203
+ });
204
+ function createIndexedDbAdapter(name = "pixerate-editor", options = {}) {
205
+ const factory = options.indexedDB ?? detectIndexedDb();
206
+ const storeName = options.store ?? "kv";
207
+ let dbPromise = null;
208
+ const open = () => {
209
+ dbPromise ??= new Promise((resolve, reject) => {
210
+ const request = factory.open(name, 1);
211
+ request.onupgradeneeded = () => {
212
+ const db = request.result;
213
+ if (!db.objectStoreNames.contains(storeName)) db.createObjectStore(storeName);
214
+ };
215
+ request.onsuccess = () => resolve(request.result);
216
+ request.onerror = () => {
217
+ dbPromise = null;
218
+ reject(request.error ?? new Error("IndexedDB open failed"));
219
+ };
220
+ });
221
+ return dbPromise;
222
+ };
223
+ const write = async (fn) => {
224
+ const db = await open();
225
+ await new Promise((resolve, reject) => {
226
+ const tx = db.transaction(storeName, "readwrite");
227
+ tx.oncomplete = () => resolve();
228
+ tx.onerror = () => reject(tx.error ?? new Error("IndexedDB transaction failed"));
229
+ tx.onabort = () => reject(tx.error ?? new Error("IndexedDB transaction aborted"));
230
+ fn(tx.objectStore(storeName));
231
+ });
232
+ };
233
+ return guarded("indexedDB", () => factory !== void 0, options.failure, {
234
+ async get(key) {
235
+ const db = await open();
236
+ const tx = db.transaction(storeName, "readonly");
237
+ const value = await requestToPromise(tx.objectStore(storeName).get(key));
238
+ return typeof value === "string" ? value : null;
239
+ },
240
+ set: (key, value) => write((store) => store.put(value, key)),
241
+ remove: (key) => write((store) => store.delete(key))
242
+ });
243
+ }
244
+ function detectOpfsRoot() {
245
+ try {
246
+ const storage = globalThis.navigator?.storage;
247
+ if (storage && typeof storage.getDirectory === "function") {
248
+ return () => storage.getDirectory();
249
+ }
250
+ } catch {
251
+ }
252
+ return void 0;
253
+ }
254
+ function isOpfsAvailable() {
255
+ return detectOpfsRoot() !== void 0;
256
+ }
257
+ var isNotFound = (error) => !!error && typeof error === "object" && error.name === "NotFoundError";
258
+ function createOpfsAdapter(dir = "pixerate-editor", options = {}) {
259
+ const getRoot = options.root ?? detectOpfsRoot();
260
+ let dirPromise = null;
261
+ const directory = () => {
262
+ dirPromise ??= getRoot().then((root) => root.getDirectoryHandle(dir, { create: true }));
263
+ dirPromise.catch(() => {
264
+ dirPromise = null;
265
+ });
266
+ return dirPromise;
267
+ };
268
+ const fileName = (key) => encodeURIComponent(key) || "_";
269
+ const readFile = async (d, name) => {
270
+ try {
271
+ const handle = await d.getFileHandle(name);
272
+ return await (await handle.getFile()).text();
273
+ } catch (error) {
274
+ if (isNotFound(error)) return null;
275
+ throw error;
276
+ }
277
+ };
278
+ const writeFile = async (d, name, value) => {
279
+ const handle = await d.getFileHandle(name, { create: true });
280
+ if (typeof handle.createWritable !== "function") {
281
+ throw new StorageUnavailableError("opfs (createWritable)");
282
+ }
283
+ const writable = await handle.createWritable();
284
+ await writable.write(value);
285
+ await writable.close();
286
+ return handle;
287
+ };
288
+ const removeFile = async (d, name) => {
289
+ try {
290
+ await d.removeEntry(name);
291
+ } catch (error) {
292
+ if (!isNotFound(error)) throw error;
293
+ }
294
+ };
295
+ return guarded("opfs", () => getRoot !== void 0, options.failure, {
296
+ async get(key) {
297
+ const d = await directory();
298
+ const base = fileName(key);
299
+ const pointer = await readFile(d, `${base}.ptr`);
300
+ if (pointer === "a" || pointer === "b") return readFile(d, `${base}.${pointer}`);
301
+ return readFile(d, base);
302
+ },
303
+ async set(key, value) {
304
+ const d = await directory();
305
+ const base = fileName(key);
306
+ const pointer = await readFile(d, `${base}.ptr`);
307
+ if (!options.disableRename && pointer === null) {
308
+ const tmp = await writeFile(d, `${base}.tmp`, value);
309
+ if (typeof tmp.move === "function") {
310
+ await tmp.move(base);
311
+ return;
312
+ }
313
+ await removeFile(d, `${base}.tmp`);
314
+ }
315
+ const next = pointer === "a" ? "b" : "a";
316
+ await writeFile(d, `${base}.${next}`, value);
317
+ await writeFile(d, `${base}.ptr`, next);
318
+ },
319
+ async remove(key) {
320
+ const d = await directory();
321
+ const base = fileName(key);
322
+ for (const name of [`${base}.ptr`, base, `${base}.a`, `${base}.b`, `${base}.tmp`]) {
323
+ await removeFile(d, name);
324
+ }
325
+ }
326
+ });
327
+ }
328
+
329
+ // src/persistence/autosave.ts
330
+ var recoveryMetaKey = (key) => `${key}:meta`;
331
+ function formatRecoveryMessage(meta, formatTime = (time) => new Date(time).toLocaleString()) {
332
+ return typeof meta.time === "number" && Number.isFinite(meta.time) ? `Restore unsaved changes from ${formatTime(meta.time)}?` : "Restore unsaved changes?";
333
+ }
334
+ function createAutosaveController(options) {
335
+ const {
336
+ storage,
337
+ key,
338
+ getSnapshot,
339
+ revision,
340
+ savedRevision,
341
+ interval = 5e3,
342
+ debounceMs = 1e3,
343
+ retryBaseMs = 1e3,
344
+ retryMaxMs = 3e4
345
+ } = options;
346
+ const serialize = options.serialize ?? ((value) => JSON.stringify(value));
347
+ const deserialize = options.deserialize ?? ((text) => JSON.parse(text));
348
+ const now = options.now ?? (() => Date.now());
349
+ const wallClock = options.wallClock ?? (() => Date.now());
350
+ const setTimer = options.setTimeout ?? ((fn, ms) => globalThis.setTimeout(fn, ms));
351
+ const clearTimer = options.clearTimeout ?? ((handle) => globalThis.clearTimeout(handle));
352
+ const metaKey = recoveryMetaKey(key);
353
+ const policy = createRecoveryPolicy({
354
+ interval,
355
+ retryDelay: (failures) => exponentialBackoff(failures, retryBaseMs, retryMaxMs)
356
+ });
357
+ let hydrated = options.ready === true;
358
+ let destroyed = false;
359
+ let timer = null;
360
+ let timerDue = Infinity;
361
+ let inflight = null;
362
+ let observedRevision = revision();
363
+ let lastChangeAt = null;
364
+ let pendingSince = null;
365
+ let forceNext = false;
366
+ let info = {
367
+ status: "idle",
368
+ error: null,
369
+ failures: 0,
370
+ nextAttemptAt: null,
371
+ lastSavedAt: null,
372
+ ready: hydrated
373
+ };
374
+ const listeners = /* @__PURE__ */ new Set();
375
+ const setInfo = (patch) => {
376
+ const next = { ...info, ...patch, failures: policy.failures, ready: hydrated };
377
+ if (next.status === info.status && next.error === info.error && next.failures === info.failures && next.nextAttemptAt === info.nextAttemptAt && next.lastSavedAt === info.lastSavedAt && next.ready === info.ready) {
378
+ return;
379
+ }
380
+ info = next;
381
+ options.onStatus?.(info);
382
+ listeners.forEach((listener) => {
383
+ try {
384
+ listener(info);
385
+ } catch (error) {
386
+ console.error("[autosave] Error in status listener:", error);
387
+ }
388
+ });
389
+ };
390
+ const cancelTimer = () => {
391
+ if (timer !== null) clearTimer(timer);
392
+ timer = null;
393
+ timerDue = Infinity;
394
+ };
395
+ const schedule = (delay) => {
396
+ if (destroyed) return;
397
+ const due = now() + Math.max(0, delay);
398
+ if (timer !== null && timerDue <= due) return;
399
+ cancelTimer();
400
+ timerDue = due;
401
+ timer = setTimer(() => {
402
+ timer = null;
403
+ timerDue = Infinity;
404
+ tick();
405
+ }, Math.max(0, delay));
406
+ };
407
+ const observe = (t) => {
408
+ const rev = revision();
409
+ if (rev !== observedRevision) {
410
+ observedRevision = rev;
411
+ lastChangeAt = t;
412
+ pendingSince ??= t;
413
+ }
414
+ return rev;
415
+ };
416
+ const docName = () => typeof options.name === "function" ? options.name() : options.name;
417
+ const run = (action, rev) => {
418
+ if (action.type === "writeSnapshot") {
419
+ setInfo({ status: "saving", nextAttemptAt: null });
420
+ inflight = (async () => {
421
+ let ok = false;
422
+ let error = null;
423
+ try {
424
+ const text = serialize(getSnapshot());
425
+ await storage.set(key, text);
426
+ const meta = {
427
+ dirty: true,
428
+ revision: action.revision,
429
+ time: wallClock(),
430
+ name: docName()
431
+ };
432
+ await storage.set(metaKey, JSON.stringify(meta));
433
+ ok = true;
434
+ } catch (e) {
435
+ error = e;
436
+ }
437
+ const t = now();
438
+ policy.written(action.revision, ok, t);
439
+ inflight = null;
440
+ if (ok) {
441
+ if (observedRevision === action.revision) pendingSince = null;
442
+ setInfo({ status: "saved", error: null, lastSavedAt: t });
443
+ } else {
444
+ setInfo({ status: "error", error });
445
+ }
446
+ tick();
447
+ })();
448
+ } else if (action.type === "writeClean") {
449
+ setInfo({ status: "saving", nextAttemptAt: null });
450
+ inflight = (async () => {
451
+ let ok = false;
452
+ let error = null;
453
+ try {
454
+ await storage.set(metaKey, JSON.stringify({ dirty: false }));
455
+ ok = true;
456
+ } catch (e) {
457
+ error = e;
458
+ }
459
+ policy.cleaned(ok, now());
460
+ inflight = null;
461
+ if (ok) {
462
+ if (!policy.ownsSnapshot) await storage.remove(key).catch(() => void 0);
463
+ setInfo({ status: "saved", error: null });
464
+ } else {
465
+ setInfo({ status: "error", error });
466
+ }
467
+ tick();
468
+ })();
469
+ } else if (action.type === "wakeAfter") {
470
+ schedule(action.delay);
471
+ setInfo(
472
+ info.status === "error" ? { nextAttemptAt: now() + action.delay } : { status: rev !== savedRevision() ? "dirty" : info.status, nextAttemptAt: now() + action.delay }
473
+ );
474
+ } else if (rev === savedRevision() && info.status === "dirty") {
475
+ setInfo({ status: policy.ownsSnapshot ? "dirty" : "idle", nextAttemptAt: null });
476
+ } else if (rev !== savedRevision() && info.status !== "saved" && info.status !== "error") {
477
+ setInfo({ status: "dirty" });
478
+ }
479
+ };
480
+ function tick() {
481
+ if (destroyed || inflight) return;
482
+ const t = now();
483
+ const rev = observe(t);
484
+ const saved = savedRevision();
485
+ if (!hydrated) {
486
+ if (rev !== saved) setInfo({ status: "dirty" });
487
+ return;
488
+ }
489
+ const force = forceNext;
490
+ forceNext = false;
491
+ if (rev !== saved && !force && info.status !== "error") {
492
+ const changed = lastChangeAt ?? t;
493
+ const due = Math.min(changed + debounceMs, (pendingSince ?? changed) + interval);
494
+ if (t < due) {
495
+ setInfo({ status: "dirty", nextAttemptAt: due });
496
+ schedule(due - t);
497
+ return;
498
+ }
499
+ }
500
+ run(policy.tick(t, rev, saved, { force }), rev);
501
+ }
502
+ const whenIdle = async () => {
503
+ while (inflight) await inflight;
504
+ };
505
+ return {
506
+ getStatus: () => info,
507
+ subscribe(listener) {
508
+ listeners.add(listener);
509
+ return () => {
510
+ listeners.delete(listener);
511
+ };
512
+ },
513
+ notify() {
514
+ if (destroyed) return;
515
+ const t = now();
516
+ const before = observedRevision;
517
+ const rev = observe(t);
518
+ const dirty = rev !== savedRevision();
519
+ if (dirty && rev !== before && info.status !== "error" && !inflight) {
520
+ setInfo({ status: "dirty" });
521
+ }
522
+ if (!hydrated) return;
523
+ const changed = lastChangeAt ?? t;
524
+ const due = Math.min(changed + debounceMs, (pendingSince ?? changed) + interval);
525
+ schedule(dirty ? due - t : 0);
526
+ },
527
+ ready() {
528
+ if (hydrated || destroyed) return;
529
+ hydrated = true;
530
+ setInfo({});
531
+ schedule(0);
532
+ },
533
+ get isReady() {
534
+ return hydrated;
535
+ },
536
+ async flushNow() {
537
+ if (destroyed || !hydrated) return;
538
+ await whenIdle();
539
+ cancelTimer();
540
+ forceNext = true;
541
+ tick();
542
+ await whenIdle();
543
+ },
544
+ async markClean() {
545
+ if (destroyed) return;
546
+ await whenIdle();
547
+ cancelTimer();
548
+ tick();
549
+ await whenIdle();
550
+ },
551
+ async checkRecovery() {
552
+ if (policy.ownsSnapshot) return null;
553
+ let meta;
554
+ try {
555
+ const metaText = await storage.get(metaKey);
556
+ if (!metaText) return null;
557
+ meta = JSON.parse(metaText);
558
+ } catch {
559
+ return null;
560
+ }
561
+ if (!meta || meta.dirty !== true) return null;
562
+ const text = await storage.get(key).catch(() => null);
563
+ if (text === null) return null;
564
+ try {
565
+ return { snapshot: deserialize(text), text, meta };
566
+ } catch (error) {
567
+ return { snapshot: void 0, text, meta, error };
568
+ }
569
+ },
570
+ async discardRecovery() {
571
+ if (policy.ownsSnapshot) return false;
572
+ await whenIdle();
573
+ await storage.remove(metaKey);
574
+ await storage.remove(key);
575
+ return true;
576
+ },
577
+ tick,
578
+ whenIdle,
579
+ destroy() {
580
+ destroyed = true;
581
+ cancelTimer();
582
+ listeners.clear();
583
+ }
584
+ };
585
+ }
586
+
587
+ // src/persistence/revisions.ts
588
+ function createRevisionTracker(options = {}) {
589
+ let revision = 0;
590
+ let saved = 0;
591
+ const listeners = /* @__PURE__ */ new Set();
592
+ const emit = () => listeners.forEach((listener) => {
593
+ try {
594
+ listener();
595
+ } catch (error) {
596
+ console.error("[autosave] Error in revision listener:", error);
597
+ }
598
+ });
599
+ const syncSaved = () => {
600
+ if (options.dirty && !options.dirty.dirty) saved = revision;
601
+ };
602
+ const unsubscribers = [];
603
+ if (options.history) {
604
+ unsubscribers.push(
605
+ options.history.subscribe(() => {
606
+ revision++;
607
+ emit();
608
+ })
609
+ );
610
+ }
611
+ if (options.dirty) {
612
+ let first = true;
613
+ unsubscribers.push(
614
+ options.dirty.subscribe((dirty) => {
615
+ if (first) {
616
+ first = false;
617
+ if (dirty && revision === saved) revision++;
618
+ return;
619
+ }
620
+ if (dirty && revision === saved) revision++;
621
+ if (!dirty) saved = revision;
622
+ emit();
623
+ })
624
+ );
625
+ }
626
+ return {
627
+ revision: () => revision,
628
+ savedRevision: () => {
629
+ syncSaved();
630
+ return saved;
631
+ },
632
+ bump() {
633
+ revision++;
634
+ emit();
635
+ },
636
+ markSaved() {
637
+ saved = revision;
638
+ emit();
639
+ },
640
+ subscribe(listener) {
641
+ listeners.add(listener);
642
+ return () => {
643
+ listeners.delete(listener);
644
+ };
645
+ },
646
+ destroy() {
647
+ unsubscribers.forEach((fn) => fn());
648
+ unsubscribers.length = 0;
649
+ listeners.clear();
650
+ }
651
+ };
652
+ }
653
+ export {
654
+ InjectedStorageError,
655
+ StorageUnavailableError,
656
+ createAutosaveController,
657
+ createIndexedDbAdapter,
658
+ createLoadReport,
659
+ createLocalStorageAdapter,
660
+ createMemoryStorage,
661
+ createOpfsAdapter,
662
+ createRecoveryPolicy,
663
+ createRevisionTracker,
664
+ exponentialBackoff,
665
+ formatRecoveryMessage,
666
+ hasLoadProblems,
667
+ isIndexedDbAvailable,
668
+ isLocalStorageAvailable,
669
+ isOpfsAvailable,
670
+ loadDocument,
671
+ loadSections,
672
+ migrateDocument,
673
+ recoveryMetaKey,
674
+ withStorageFailures
675
+ };
676
+ //# sourceMappingURL=index.js.map