@pixerate/editor 0.22.0 → 0.24.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 (188) hide show
  1. package/README.md +175 -2
  2. package/THIRD_PARTY_NOTICES.md +139 -0
  3. package/dist/agent/index.cjs +87 -111
  4. package/dist/agent/index.cjs.map +1 -1
  5. package/dist/agent/index.d.cts +70 -211
  6. package/dist/agent/index.d.ts +70 -211
  7. package/dist/agent/index.js +78 -102
  8. package/dist/agent/index.js.map +1 -1
  9. package/dist/arrangeShortcuts-B8O3f8-P.d.cts +184 -0
  10. package/dist/arrangeShortcuts-sLlD2rSX.d.ts +184 -0
  11. package/dist/canvas/index.cjs +2075 -86
  12. package/dist/canvas/index.cjs.map +1 -1
  13. package/dist/canvas/index.d.cts +608 -186
  14. package/dist/canvas/index.d.ts +608 -186
  15. package/dist/canvas/index.js +2060 -71
  16. package/dist/canvas/index.js.map +1 -1
  17. package/dist/chunk-26BVFKHU.cjs +453 -0
  18. package/dist/chunk-26BVFKHU.cjs.map +1 -0
  19. package/dist/chunk-2H7ZHPEJ.js +762 -0
  20. package/dist/chunk-2H7ZHPEJ.js.map +1 -0
  21. package/dist/{chunk-DENSCKFC.js → chunk-3CN2WD3X.js} +1730 -189
  22. package/dist/chunk-3CN2WD3X.js.map +1 -0
  23. package/dist/{chunk-JJDLPEWL.js → chunk-3LV6RRPH.js} +2 -2
  24. package/dist/{chunk-MJZBNWG7.js → chunk-5S3Z4PPF.js} +78 -557
  25. package/dist/chunk-5S3Z4PPF.js.map +1 -0
  26. package/dist/chunk-664LQYFJ.js +115 -0
  27. package/dist/chunk-664LQYFJ.js.map +1 -0
  28. package/dist/chunk-7DWMJCAT.js +453 -0
  29. package/dist/chunk-7DWMJCAT.js.map +1 -0
  30. package/dist/chunk-ARRTP33I.js +101 -0
  31. package/dist/chunk-ARRTP33I.js.map +1 -0
  32. package/dist/chunk-CPOKFNBG.cjs +101 -0
  33. package/dist/chunk-CPOKFNBG.cjs.map +1 -0
  34. package/dist/{chunk-AA4ZEVCC.cjs → chunk-D27YFC46.cjs} +101 -580
  35. package/dist/chunk-D27YFC46.cjs.map +1 -0
  36. package/dist/chunk-D6FH2YAX.cjs +131 -0
  37. package/dist/chunk-D6FH2YAX.cjs.map +1 -0
  38. package/dist/{chunk-4SUNJZWB.cjs → chunk-FD4LPFAE.cjs} +48 -200
  39. package/dist/chunk-FD4LPFAE.cjs.map +1 -0
  40. package/dist/chunk-FODNLX5N.js +749 -0
  41. package/dist/chunk-FODNLX5N.js.map +1 -0
  42. package/dist/{chunk-OYXSKQFR.js → chunk-FPENXAEJ.js} +35 -187
  43. package/dist/chunk-FPENXAEJ.js.map +1 -0
  44. package/dist/chunk-G2I4QJOT.cjs +762 -0
  45. package/dist/chunk-G2I4QJOT.cjs.map +1 -0
  46. package/dist/chunk-HLIMTC6N.js +1 -0
  47. package/dist/chunk-HLIMTC6N.js.map +1 -0
  48. package/dist/{chunk-QGHGJ56O.js → chunk-ITBSKI26.js} +1483 -19
  49. package/dist/chunk-ITBSKI26.js.map +1 -0
  50. package/dist/{chunk-6M3BCTBD.cjs → chunk-MA2F5KHF.cjs} +470 -242
  51. package/dist/chunk-MA2F5KHF.cjs.map +1 -0
  52. package/dist/{chunk-SAZRENEZ.cjs → chunk-MCSJFWAO.cjs} +1733 -192
  53. package/dist/chunk-MCSJFWAO.cjs.map +1 -0
  54. package/dist/chunk-MKA3AHLV.cjs +256 -0
  55. package/dist/chunk-MKA3AHLV.cjs.map +1 -0
  56. package/dist/chunk-OLZJXL4R.cjs +749 -0
  57. package/dist/chunk-OLZJXL4R.cjs.map +1 -0
  58. package/dist/chunk-OXSV64JX.js +689 -0
  59. package/dist/chunk-OXSV64JX.js.map +1 -0
  60. package/dist/chunk-P3RQ5UJE.cjs +689 -0
  61. package/dist/chunk-P3RQ5UJE.cjs.map +1 -0
  62. package/dist/chunk-P6ODPZ4W.js +256 -0
  63. package/dist/chunk-P6ODPZ4W.js.map +1 -0
  64. package/dist/chunk-PGNZKFQS.js +685 -0
  65. package/dist/chunk-PGNZKFQS.js.map +1 -0
  66. package/dist/chunk-PL4JB3BF.cjs +1 -0
  67. package/dist/chunk-PL4JB3BF.cjs.map +1 -0
  68. package/dist/{chunk-GVSMRPHB.cjs → chunk-SBCZ6RN5.cjs} +1568 -104
  69. package/dist/chunk-SBCZ6RN5.cjs.map +1 -0
  70. package/dist/{chunk-CL37JGFW.cjs → chunk-SML64VJG.cjs} +4 -4
  71. package/dist/{chunk-CL37JGFW.cjs.map → chunk-SML64VJG.cjs.map} +1 -1
  72. package/dist/chunk-TNNJXF46.cjs +115 -0
  73. package/dist/chunk-TNNJXF46.cjs.map +1 -0
  74. package/dist/chunk-U5HZSFCS.js +131 -0
  75. package/dist/chunk-U5HZSFCS.js.map +1 -0
  76. package/dist/{chunk-RU52I7QO.js → chunk-U5KWZU4X.js} +388 -160
  77. package/dist/chunk-U5KWZU4X.js.map +1 -0
  78. package/dist/chunk-XNORY7M6.cjs +685 -0
  79. package/dist/chunk-XNORY7M6.cjs.map +1 -0
  80. package/dist/commands/index.cjs +860 -0
  81. package/dist/commands/index.cjs.map +1 -0
  82. package/dist/commands/index.d.cts +244 -0
  83. package/dist/commands/index.d.ts +244 -0
  84. package/dist/commands/index.js +860 -0
  85. package/dist/commands/index.js.map +1 -0
  86. package/dist/comment-store-BEMgVlAA.d.cts +135 -0
  87. package/dist/comment-store-BEMgVlAA.d.ts +135 -0
  88. package/dist/compare/index.cjs +15 -0
  89. package/dist/compare/index.cjs.map +1 -0
  90. package/dist/compare/index.d.cts +58 -0
  91. package/dist/compare/index.d.ts +58 -0
  92. package/dist/compare/index.js +15 -0
  93. package/dist/compare/index.js.map +1 -0
  94. package/dist/{controller-B7GIybc4.d.cts → controller-BhLLAoi8.d.cts} +119 -5
  95. package/dist/{controller-B7GIybc4.d.ts → controller-BhLLAoi8.d.ts} +119 -5
  96. package/dist/documents-BQjzJNe2.d.ts +76 -0
  97. package/dist/documents-KRTPhvst.d.cts +76 -0
  98. package/dist/extensions/index.cjs +53 -5
  99. package/dist/extensions/index.cjs.map +1 -1
  100. package/dist/extensions/index.d.cts +7 -3
  101. package/dist/extensions/index.d.ts +7 -3
  102. package/dist/extensions/index.js +52 -4
  103. package/dist/generation/index.cjs +1373 -0
  104. package/dist/generation/index.cjs.map +1 -0
  105. package/dist/generation/index.d.cts +554 -0
  106. package/dist/generation/index.d.ts +554 -0
  107. package/dist/generation/index.js +1373 -0
  108. package/dist/generation/index.js.map +1 -0
  109. package/dist/grammar/index.cjs +48 -3
  110. package/dist/grammar/index.cjs.map +1 -1
  111. package/dist/grammar/index.d.cts +322 -100
  112. package/dist/grammar/index.d.ts +322 -100
  113. package/dist/grammar/index.js +54 -9
  114. package/dist/history/index.cjs +12 -2
  115. package/dist/history/index.cjs.map +1 -1
  116. package/dist/history/index.d.cts +211 -7
  117. package/dist/history/index.d.ts +211 -7
  118. package/dist/history/index.js +13 -3
  119. package/dist/image-editor/index.cjs +2244 -38
  120. package/dist/image-editor/index.cjs.map +1 -1
  121. package/dist/image-editor/index.d.cts +742 -4
  122. package/dist/image-editor/index.d.ts +742 -4
  123. package/dist/image-editor/index.js +2222 -16
  124. package/dist/image-editor/index.js.map +1 -1
  125. package/dist/{index-CCr_z9kF.d.cts → index-BhJBSm2d.d.cts} +370 -4
  126. package/dist/{index-Ck6ajwXh.d.ts → index-fJrlkJCd.d.ts} +370 -4
  127. package/dist/index.cjs +193 -98
  128. package/dist/index.cjs.map +1 -1
  129. package/dist/index.d.cts +17 -48
  130. package/dist/index.d.ts +17 -48
  131. package/dist/index.js +198 -103
  132. package/dist/index.js.map +1 -1
  133. package/dist/migrate-iTlm3a8h.d.cts +112 -0
  134. package/dist/migrate-iTlm3a8h.d.ts +112 -0
  135. package/dist/persistence/index.cjs +676 -0
  136. package/dist/persistence/index.cjs.map +1 -0
  137. package/dist/persistence/index.d.cts +347 -0
  138. package/dist/persistence/index.d.ts +347 -0
  139. package/dist/persistence/index.js +676 -0
  140. package/dist/persistence/index.js.map +1 -0
  141. package/dist/registry-BDe-vlv3.d.ts +171 -0
  142. package/dist/registry-D32LpIc4.d.cts +354 -0
  143. package/dist/registry-DnzpgY27.d.cts +171 -0
  144. package/dist/registry-evKTP4ls.d.ts +354 -0
  145. package/dist/schema-DYDGdYL9.d.cts +41 -0
  146. package/dist/schema-DYDGdYL9.d.ts +41 -0
  147. package/dist/serializers/index.cjs +4 -3
  148. package/dist/serializers/index.cjs.map +1 -1
  149. package/dist/serializers/index.d.cts +2 -1
  150. package/dist/serializers/index.d.ts +2 -1
  151. package/dist/serializers/index.js +3 -2
  152. package/dist/sourcemap-C8AH3MBQ.d.cts +91 -0
  153. package/dist/sourcemap-DA1Evhrv.d.ts +91 -0
  154. package/dist/spreadsheet/index.cjs +53 -3
  155. package/dist/spreadsheet/index.cjs.map +1 -1
  156. package/dist/spreadsheet/index.d.cts +247 -18
  157. package/dist/spreadsheet/index.d.ts +247 -18
  158. package/dist/spreadsheet/index.js +58 -8
  159. package/dist/{tokens-CyLZmIei.d.cts → tokens-D7yXvUU6.d.cts} +15 -2
  160. package/dist/{tokens-CyLZmIei.d.ts → tokens-D7yXvUU6.d.ts} +15 -2
  161. package/dist/{track-changes-BSF58RK0.d.cts → track-changes-BK5sVf4_.d.cts} +18 -136
  162. package/dist/{track-changes-BSF58RK0.d.ts → track-changes-CDjCrwKe.d.ts} +18 -136
  163. package/dist/{types-CJ4U-HHI.d.ts → types-DX4Mh4SL.d.cts} +1 -1
  164. package/dist/{types-CJ4U-HHI.d.cts → types-DX4Mh4SL.d.ts} +1 -1
  165. package/dist/types-GT5a5YAX.d.cts +86 -0
  166. package/dist/types-GT5a5YAX.d.ts +86 -0
  167. package/package.json +23 -2
  168. package/dist/chunk-2TWGLC5K.cjs +0 -262
  169. package/dist/chunk-2TWGLC5K.cjs.map +0 -1
  170. package/dist/chunk-4SUNJZWB.cjs.map +0 -1
  171. package/dist/chunk-6M3BCTBD.cjs.map +0 -1
  172. package/dist/chunk-AA4ZEVCC.cjs.map +0 -1
  173. package/dist/chunk-AXV2ZW34.cjs +0 -118
  174. package/dist/chunk-AXV2ZW34.cjs.map +0 -1
  175. package/dist/chunk-B2ZRLN4S.js +0 -118
  176. package/dist/chunk-B2ZRLN4S.js.map +0 -1
  177. package/dist/chunk-DENSCKFC.js.map +0 -1
  178. package/dist/chunk-GVSMRPHB.cjs.map +0 -1
  179. package/dist/chunk-KAADNAPF.js +0 -262
  180. package/dist/chunk-KAADNAPF.js.map +0 -1
  181. package/dist/chunk-MJZBNWG7.js.map +0 -1
  182. package/dist/chunk-OYXSKQFR.js.map +0 -1
  183. package/dist/chunk-QGHGJ56O.js.map +0 -1
  184. package/dist/chunk-RU52I7QO.js.map +0 -1
  185. package/dist/chunk-SAZRENEZ.cjs.map +0 -1
  186. package/dist/types-BZgcDsC5.d.cts +0 -42
  187. package/dist/types-BZgcDsC5.d.ts +0 -42
  188. /package/dist/{chunk-JJDLPEWL.js.map → chunk-3LV6RRPH.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/persistence/index.cjs","../../src/persistence/recoveryPolicy.ts","../../src/persistence/storage.ts","../../src/persistence/autosave.ts","../../src/persistence/revisions.ts"],"names":[],"mappings":"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AA;AACU,YAAA;AACD,YAAA;AAElB,UAAA;AACA,UAAA;AACA,UAAA;AACF,QAAA;AACE,UAAA;AACF,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACE,UAAA;AACA,UAAA;AACF,QAAA;AACE,UAAA;AACF,QAAA;AACA,QAAA;AACC,MAAA;AACL,IAAA;AACE,MAAA;AACA,MAAA;AACE,QAAA;AACA,QAAA;AACA,QAAA;AACE,UAAA;AACA,UAAA;AACF,QAAA;AACE,UAAA;AACF,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AAEE,UAAA;AACA,UAAA;AACF,QAAA;AACE,UAAA;AACF,QAAA;AACA,QAAA;AACC,MAAA;AACL,IAAA;AACE,MAAA;AACA,MAAA;AACE,QAAA;AAGF,MAAA;AACF,IAAA;AACE,MAAA;AACF,IAAA;AACE,MAAA;AACF,IAAA;AACF,EAAA;AAES,EAAA;AACH,IAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AACD,IAAA;AACC,MAAA;AACJ,MAAA;AACF,IAAA;AACM,IAAA;AACN,IAAA;AACI,IAAA;AACF,MAAA;AACA,MAAA;AACI,MAAA;AACF,QAAA;AACA,QAAA;AACA,QAAA;AACF,MAAA;AACF,IAAA;AACI,IAAA;AACN,EAAA;AAEM,EAAA;AACG,IAAA;AACT,EAAA;AAEO,EAAA;AACL,IAAA;AACA,IAAA;AACE,MAAA;AACA,MAAA;AACE,QAAA;AACF,MAAA;AACF,IAAA;AACA,IAAA;AACM,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACI,MAAA;AACF,QAAA;AACF,MAAA;AACK,MAAA;AAEL,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACA,IAAA;AACM,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACI,IAAA;AACF,MAAA;AACF,IAAA;AACM,IAAA;AACA,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACK,MAAA;AACL,MAAA;AACF,IAAA;AACM,IAAA;AACA,MAAA;AACJ,MAAA;AACA,MAAA;AACK,MAAA;AACL,MAAA;AACF,IAAA;AACM,IAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,QAAA;AACA,QAAA;AACA,QAAA;AACF,MAAA;AACE,QAAA;AACF,MAAA;AACK,MAAA;AACL,MAAA;AACI,MAAA;AACA,MAAA;AACF,QAAA;AACF,MAAA;AACE,QAAA;AACF,MAAA;AACF,IAAA;AACM,IAAA;AACA,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AACF;AHkKW;AACA;AIjiBK;AACV,EAAA;AACA,EAAA;AACE,EAAA;AACA,EAAA;AAEE,IAAA;AACF,MAAA;AACF,IAAA;AACE,MAAA;AACF,IAAA;AACD,EAAA;AACG,EAAA;AAEA,IAAA;AACN,EAAA;AAEM,EAAA;AACF,EAAA;AACF,IAAA;AACE,MAAA;AACE,QAAA;AACA,QAAA;AACD,MAAA;AACH,IAAA;AACF,EAAA;AACI,EAAA;AACE,IAAA;AACJ,IAAA;AACE,MAAA;AACE,QAAA;AACE,UAAA;AAEA,UAAA;AACA,UAAA;AACF,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACH,IAAA;AACF,EAAA;AAEO,EAAA;AACL,IAAA;AACA,IAAA;AACE,MAAA;AACA,MAAA;AACF,IAAA;AACO,IAAA;AACL,MAAA;AACK,MAAA;AACP,IAAA;AACA,IAAA;AACE,MAAA;AACK,MAAA;AACP,IAAA;AACA,IAAA;AACE,MAAA;AACA,MAAA;AACE,QAAA;AACF,MAAA;AACF,IAAA;AACA,IAAA;AACE,MAAA;AACA,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AACF;AJ8hBW;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/editor/editor/packages/core/dist/persistence/index.cjs","sourcesContent":[null,"// Portions ported from filmcraft-main/apps/filmcraft-web/src/recovery_policy.rs (MIT OR Apache-2.0,\n// © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\n/**\n * What the caller should do now (see `RecoveryPolicy.tick`).\n *\n * - `writeSnapshot`: write the snapshot and a dirty meta for `revision`, then report with `written`.\n * - `writeClean`: write a clean meta (retire the snapshot), then report with `cleaned`.\n * - `wakeAfter`: nothing now; tick again after `delay` (same unit as `now`).\n */\nexport type RecoveryAction =\n | { type: \"nothing\" }\n | { type: \"writeSnapshot\"; revision: number }\n | { type: \"writeClean\" }\n | { type: \"wakeAfter\"; delay: number };\n\nexport interface RecoveryPolicyOptions {\n /**\n * Minimum time between snapshots of a changing document, in the unit of `now`\n * (default 5000, i.e. milliseconds).\n */\n interval?: number;\n /**\n * Wait before retrying after the `failures`-th consecutive failed write\n * (default: `interval`, as in FilmCraft). Use `exponentialBackoff` for doubling delays.\n */\n retryDelay?: (failures: number) => number;\n}\n\nexport interface RecoveryTickOptions {\n /** Ignore the interval and any retry wait (e.g. on `pagehide`). */\n force?: boolean;\n}\n\nexport interface RecoveryPolicy {\n /** Called whenever something may have changed, with the revisions and the current time. */\n tick(\n now: number,\n revision: number,\n savedRevision: number,\n options?: RecoveryTickOptions,\n ): RecoveryAction;\n /** The snapshot write for `revision` finished. */\n written(revision: number, ok: boolean, now: number): void;\n /** The clean-meta write finished. */\n cleaned(ok: boolean, now: number): void;\n /** Whether this session wrote a dirty snapshot that is not retired yet. */\n readonly ownsSnapshot: boolean;\n /** Consecutive failed writes (reset by a successful one). */\n readonly failures: number;\n}\n\n/**\n * Exponential backoff: `base`, `2·base`, `4·base`, … capped at `max`.\n * `failures` is 1 for the first failure.\n */\nexport function exponentialBackoff(\n failures: number,\n base = 1000,\n max = 30000,\n): number {\n const exp = Math.min(Math.max(failures - 1, 0), 16);\n return Math.min(base * 2 ** exp, max);\n}\n\n/**\n * Pure state machine deciding when a crash-recovery snapshot is written or\n * retired. Deterministic: time is always passed in.\n *\n * - A dirty document is snapshotted at most every `interval`, once per revision.\n * A write that fails is retried after `retryDelay`, without waiting for another edit.\n * - The snapshot is marked clean only if this session wrote it. A snapshot left by an\n * earlier session (that was not restored) stays dirty, so it can still be recovered.\n * - An edit made while the clean write is in flight keeps the snapshot owned.\n */\nexport function createRecoveryPolicy(\n options: RecoveryPolicyOptions = {},\n): RecoveryPolicy {\n const interval = options.interval ?? 5000;\n const retryDelay = options.retryDelay ?? (() => interval);\n const minWake = interval * 0.01;\n\n /** Revision whose snapshot was last written or is being written. */\n let lastRevision: number | null = null;\n let lastTime: number | null = null;\n /** Required gap after `lastTime`: the interval, or the retry delay after a failure. */\n let gap = interval;\n /** This session wrote a dirty meta that is not yet retired. */\n let ownsDirtyMeta = false;\n /** A clean-meta write is in flight. */\n let cleaning = false;\n /** Snapshot writes started so far, and how many had started when the clean write did. */\n let snapshots = 0;\n let snapshotsAtClean = 0;\n let failures = 0;\n\n const fail = (now: number) => {\n failures++;\n lastRevision = null;\n lastTime = now;\n gap = retryDelay(failures);\n };\n\n return {\n tick(now, revision, savedRevision, tickOptions) {\n const force = tickOptions?.force === true;\n const since = force || lastTime === null ? Infinity : now - lastTime;\n if (revision !== savedRevision) {\n if (lastRevision === revision) return { type: \"nothing\" };\n if (since >= gap) {\n lastRevision = revision;\n lastTime = now;\n ownsDirtyMeta = true;\n snapshots++;\n return { type: \"writeSnapshot\", revision };\n }\n return { type: \"wakeAfter\", delay: Math.max(gap - since, minWake) };\n }\n if (ownsDirtyMeta && !cleaning) {\n if (lastRevision !== null || since >= gap) {\n cleaning = true;\n snapshotsAtClean = snapshots;\n return { type: \"writeClean\" };\n }\n return { type: \"wakeAfter\", delay: Math.max(gap - since, minWake) };\n }\n return { type: \"nothing\" };\n },\n written(revision, ok, now) {\n if (ok) {\n if (lastRevision === revision) {\n failures = 0;\n gap = interval;\n }\n } else if (lastRevision === revision) {\n // retry after the delay, even if nothing else changes\n fail(now);\n }\n },\n cleaned(ok, now) {\n cleaning = false;\n if (ok) {\n ownsDirtyMeta = snapshots !== snapshotsAtClean;\n failures = 0;\n gap = interval;\n } else {\n fail(now);\n }\n },\n get ownsSnapshot() {\n return ownsDirtyMeta;\n },\n get failures() {\n return failures;\n },\n };\n}\n","/**\n * Key/value storage adapters for autosave and crash recovery. Every adapter is\n * async, stores strings, feature-detects its backend (an unavailable backend\n * rejects every call with `StorageUnavailableError` instead of throwing at\n * construction, so SSR and old browsers are safe) and accepts an injectable\n * failure mode for tests.\n */\n\nexport type StorageOperation = \"get\" | \"set\" | \"remove\";\n\n/**\n * Injected failure: return `true` (or an `Error`) to make that operation fail.\n * Example: `failure: (op) => op === \"set\"` simulates a full disk.\n */\nexport type StorageFailureMode = (\n operation: StorageOperation,\n key: string,\n) => boolean | Error | undefined | void;\n\nexport interface StorageAdapter {\n /** Short backend name, e.g. `\"memory\"`, `\"localStorage\"`, `\"indexedDB\"`, `\"opfs\"`. */\n readonly kind: string;\n /** Whether the backend was found. When `false`, every call rejects. */\n readonly available: boolean;\n get(key: string): Promise<string | null>;\n set(key: string, value: string): Promise<void>;\n remove(key: string): Promise<void>;\n}\n\nexport interface StorageAdapterOptions {\n failure?: StorageFailureMode;\n}\n\nexport class StorageUnavailableError extends Error {\n constructor(kind: string) {\n super(`${kind} storage is not available in this environment`);\n this.name = \"StorageUnavailableError\";\n }\n}\n\nexport class InjectedStorageError extends Error {\n constructor(operation: StorageOperation, key: string) {\n super(`injected ${operation} failure for \"${key}\"`);\n this.name = \"InjectedStorageError\";\n }\n}\n\nfunction checkFailure(\n failure: StorageFailureMode | undefined,\n operation: StorageOperation,\n key: string,\n): void {\n if (!failure) return;\n const result = failure(operation, key);\n if (result instanceof Error) throw result;\n if (result) throw new InjectedStorageError(operation, key);\n}\n\n/** Wraps an adapter so `failure` can make any of its operations fail. */\nexport function withStorageFailures(\n inner: StorageAdapter,\n failure: StorageFailureMode,\n): StorageAdapter {\n return {\n kind: inner.kind,\n get available() {\n return inner.available;\n },\n async get(key) {\n checkFailure(failure, \"get\", key);\n return inner.get(key);\n },\n async set(key, value) {\n checkFailure(failure, \"set\", key);\n return inner.set(key, value);\n },\n async remove(key) {\n checkFailure(failure, \"remove\", key);\n return inner.remove(key);\n },\n };\n}\n\nfunction guarded(\n kind: string,\n isAvailable: () => boolean,\n failure: StorageFailureMode | undefined,\n impl: Omit<StorageAdapter, \"kind\" | \"available\">,\n): StorageAdapter {\n const check = (operation: StorageOperation, key: string) => {\n if (!isAvailable()) throw new StorageUnavailableError(kind);\n checkFailure(failure, operation, key);\n };\n return {\n kind,\n get available() {\n return isAvailable();\n },\n async get(key) {\n check(\"get\", key);\n return impl.get(key);\n },\n async set(key, value) {\n check(\"set\", key);\n return impl.set(key, value);\n },\n async remove(key) {\n check(\"remove\", key);\n return impl.remove(key);\n },\n };\n}\n\n// ---------------------------------------------------------------- memory\n\nexport interface MemoryStorage extends StorageAdapter {\n /** The underlying map (inspect it in tests). */\n readonly entries: Map<string, string>;\n}\n\n/** In-memory storage (tests, SSR, or a session-only fallback). */\nexport function createMemoryStorage(\n options: StorageAdapterOptions & { initial?: Record<string, string> } = {},\n): MemoryStorage {\n const entries = new Map<string, string>(Object.entries(options.initial ?? {}));\n const adapter = guarded(\"memory\", () => true, options.failure, {\n async get(key) {\n return entries.has(key) ? (entries.get(key) as string) : null;\n },\n async set(key, value) {\n entries.set(key, value);\n },\n async remove(key) {\n entries.delete(key);\n },\n });\n return Object.assign(adapter, { entries });\n}\n\n// ---------------------------------------------------------------- localStorage\n\n/** Minimal `Storage` surface used by the localStorage adapter. */\nexport interface WebStorageLike {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\nexport interface LocalStorageAdapterOptions extends StorageAdapterOptions {\n /** Storage to use (default `globalThis.localStorage`, e.g. pass `sessionStorage`). */\n storage?: WebStorageLike;\n}\n\nfunction detectLocalStorage(): WebStorageLike | undefined {\n try {\n const storage = (globalThis as { localStorage?: WebStorageLike }).localStorage;\n return storage && typeof storage.getItem === \"function\" ? storage : undefined;\n } catch {\n // Accessing localStorage throws when site data is blocked.\n return undefined;\n }\n}\n\n/** Whether `localStorage` can be used (it throws when site data is blocked). */\nexport function isLocalStorageAvailable(): boolean {\n return detectLocalStorage() !== undefined;\n}\n\n/**\n * `localStorage` adapter; every key is prefixed with `prefix`. Writes are\n * synchronous under the hood, so they complete even inside `pagehide`.\n * A full quota rejects the `set` (`QuotaExceededError`).\n */\nexport function createLocalStorageAdapter(\n prefix = \"\",\n options: LocalStorageAdapterOptions = {},\n): StorageAdapter {\n const storage = options.storage ?? detectLocalStorage();\n return guarded(\"localStorage\", () => storage !== undefined, options.failure, {\n async get(key) {\n return storage!.getItem(prefix + key);\n },\n async set(key, value) {\n storage!.setItem(prefix + key, value);\n },\n async remove(key) {\n storage!.removeItem(prefix + key);\n },\n });\n}\n\n// ---------------------------------------------------------------- IndexedDB\n\n/** Minimal `IDBFactory` surface (the real `indexedDB`, or a fake in tests). */\nexport interface IdbFactoryLike {\n open(name: string, version?: number): any;\n}\n\nexport interface IndexedDbAdapterOptions extends StorageAdapterOptions {\n /** Object store name (default `\"kv\"`). */\n store?: string;\n /** Factory to use (default `globalThis.indexedDB`). */\n indexedDB?: IdbFactoryLike;\n}\n\nfunction detectIndexedDb(): IdbFactoryLike | undefined {\n try {\n const factory = (globalThis as { indexedDB?: IdbFactoryLike }).indexedDB;\n return factory && typeof factory.open === \"function\" ? factory : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** Whether `indexedDB` exists in this environment. */\nexport function isIndexedDbAvailable(): boolean {\n return detectIndexedDb() !== undefined;\n}\n\nconst requestToPromise = <T>(request: any): Promise<T> =>\n new Promise((resolve, reject) => {\n request.onsuccess = () => resolve(request.result as T);\n request.onerror = () => reject(request.error ?? new Error(\"IndexedDB request failed\"));\n });\n\n/**\n * IndexedDB adapter: one database `name` with a single key/value object store.\n * A `set` resolves when its transaction completes (not merely when the request\n * succeeds), so a resolved write is durable.\n */\nexport function createIndexedDbAdapter(\n name = \"pixerate-editor\",\n options: IndexedDbAdapterOptions = {},\n): StorageAdapter {\n const factory = options.indexedDB ?? detectIndexedDb();\n const storeName = options.store ?? \"kv\";\n let dbPromise: Promise<any> | null = null;\n\n const open = (): Promise<any> => {\n dbPromise ??= new Promise((resolve, reject) => {\n const request = factory!.open(name, 1);\n request.onupgradeneeded = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(storeName)) db.createObjectStore(storeName);\n };\n request.onsuccess = () => resolve(request.result);\n request.onerror = () => {\n dbPromise = null;\n reject(request.error ?? new Error(\"IndexedDB open failed\"));\n };\n });\n return dbPromise;\n };\n\n const write = async (fn: (store: any) => void): Promise<void> => {\n const db = await open();\n await new Promise<void>((resolve, reject) => {\n const tx = db.transaction(storeName, \"readwrite\");\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error ?? new Error(\"IndexedDB transaction failed\"));\n tx.onabort = () => reject(tx.error ?? new Error(\"IndexedDB transaction aborted\"));\n fn(tx.objectStore(storeName));\n });\n };\n\n return guarded(\"indexedDB\", () => factory !== undefined, options.failure, {\n async get(key) {\n const db = await open();\n const tx = db.transaction(storeName, \"readonly\");\n const value = await requestToPromise<unknown>(tx.objectStore(storeName).get(key));\n return typeof value === \"string\" ? value : null;\n },\n set: (key, value) => write((store) => store.put(value, key)),\n remove: (key) => write((store) => store.delete(key)),\n });\n}\n\n// ---------------------------------------------------------------- OPFS\n\n/** Minimal `FileSystemDirectoryHandle` surface used by the OPFS adapter. */\nexport interface OpfsDirectoryLike {\n getDirectoryHandle(name: string, options?: { create?: boolean }): Promise<OpfsDirectoryLike>;\n getFileHandle(name: string, options?: { create?: boolean }): Promise<OpfsFileLike>;\n removeEntry(name: string): Promise<void>;\n}\n\nexport interface OpfsFileLike {\n getFile(): Promise<{ text(): Promise<string> }>;\n createWritable?(): Promise<{ write(data: string): Promise<void>; close(): Promise<void> }>;\n /** Rename in place (Chromium, Safari). Used for temp-file + rename writes. */\n move?(name: string): Promise<void>;\n}\n\nexport interface OpfsAdapterOptions extends StorageAdapterOptions {\n /** Root directory (default `navigator.storage.getDirectory()`). */\n root?: () => Promise<OpfsDirectoryLike>;\n /** Force the write-then-pointer-swap strategy even when `move` exists. */\n disableRename?: boolean;\n}\n\nfunction detectOpfsRoot(): (() => Promise<OpfsDirectoryLike>) | undefined {\n try {\n const storage = (globalThis as any).navigator?.storage;\n if (storage && typeof storage.getDirectory === \"function\") {\n return () => storage.getDirectory();\n }\n } catch {\n // ignore\n }\n return undefined;\n}\n\n/** Whether the Origin Private File System exists (`navigator.storage.getDirectory`). */\nexport function isOpfsAvailable(): boolean {\n return detectOpfsRoot() !== undefined;\n}\n\nconst isNotFound = (error: unknown) =>\n !!error && typeof error === \"object\" && (error as { name?: string }).name === \"NotFoundError\";\n\n/**\n * Origin Private File System adapter. Values are files in `dir` (created on\n * demand), named after the URI-encoded key.\n *\n * Writes never leave a half-written value behind: where file handles support\n * `move`, the value is written to `<file>.tmp` and renamed over `<file>`;\n * otherwise it alternates between two slots (`<file>.a` / `<file>.b`) and then\n * swaps a pointer file (`<file>.ptr`), so a crash mid-write keeps the previous\n * value. Main-thread writes need `createWritable` (missing in some Safari\n * versions); without it, `set` rejects with `StorageUnavailableError`.\n */\nexport function createOpfsAdapter(\n dir = \"pixerate-editor\",\n options: OpfsAdapterOptions = {},\n): StorageAdapter {\n const getRoot = options.root ?? detectOpfsRoot();\n let dirPromise: Promise<OpfsDirectoryLike> | null = null;\n const directory = () => {\n dirPromise ??= getRoot!().then((root) => root.getDirectoryHandle(dir, { create: true }));\n dirPromise.catch(() => {\n dirPromise = null;\n });\n return dirPromise;\n };\n const fileName = (key: string) => encodeURIComponent(key) || \"_\";\n\n const readFile = async (d: OpfsDirectoryLike, name: string): Promise<string | null> => {\n try {\n const handle = await d.getFileHandle(name);\n return await (await handle.getFile()).text();\n } catch (error) {\n if (isNotFound(error)) return null;\n throw error;\n }\n };\n\n const writeFile = async (d: OpfsDirectoryLike, name: string, value: string) => {\n const handle = await d.getFileHandle(name, { create: true });\n if (typeof handle.createWritable !== \"function\") {\n throw new StorageUnavailableError(\"opfs (createWritable)\");\n }\n const writable = await handle.createWritable();\n await writable.write(value);\n await writable.close();\n return handle;\n };\n\n const removeFile = async (d: OpfsDirectoryLike, name: string) => {\n try {\n await d.removeEntry(name);\n } catch (error) {\n if (!isNotFound(error)) throw error;\n }\n };\n\n return guarded(\"opfs\", () => getRoot !== undefined, options.failure, {\n async get(key) {\n const d = await directory();\n const base = fileName(key);\n const pointer = await readFile(d, `${base}.ptr`);\n if (pointer === \"a\" || pointer === \"b\") return readFile(d, `${base}.${pointer}`);\n return readFile(d, base);\n },\n async set(key, value) {\n const d = await directory();\n const base = fileName(key);\n const pointer = await readFile(d, `${base}.ptr`);\n if (!options.disableRename && pointer === null) {\n const tmp = await writeFile(d, `${base}.tmp`, value);\n if (typeof tmp.move === \"function\") {\n await tmp.move(base);\n return;\n }\n await removeFile(d, `${base}.tmp`);\n }\n // Write-then-pointer swap: the pointer only moves once the new slot is complete.\n const next = pointer === \"a\" ? \"b\" : \"a\";\n await writeFile(d, `${base}.${next}`, value);\n await writeFile(d, `${base}.ptr`, next);\n },\n async remove(key) {\n const d = await directory();\n const base = fileName(key);\n for (const name of [`${base}.ptr`, base, `${base}.a`, `${base}.b`, `${base}.tmp`]) {\n await removeFile(d, name);\n }\n },\n });\n}\n","// Portions ported from filmcraft-main/apps/filmcraft-web/src/recovery.rs and\n// craft-libs-main/crates/settings/src/store.rs (MIT OR Apache-2.0, © ArtCraft Team and\n// contributors). See THIRD_PARTY_NOTICES.md.\n\nimport {\n createRecoveryPolicy,\n exponentialBackoff,\n type RecoveryAction,\n type RecoveryPolicy,\n} from \"./recoveryPolicy\";\nimport type { StorageAdapter } from \"./storage\";\n\nexport type AutosaveStatus = \"idle\" | \"dirty\" | \"saving\" | \"saved\" | \"error\";\n\nexport interface AutosaveStatusInfo {\n status: AutosaveStatus;\n /** Last write error (while `status` is `\"error\"`). */\n error: unknown;\n /** Consecutive failed writes. */\n failures: number;\n /** When the next attempt is due (`now()` units), while waiting. */\n nextAttemptAt: number | null;\n /** When the last snapshot was written (`now()` units). */\n lastSavedAt: number | null;\n /** Whether `ready()` was called. */\n ready: boolean;\n}\n\n/** Stored next to the snapshot (`<key>:meta`). */\nexport interface RecoveryMeta {\n dirty: boolean;\n revision?: number;\n /** `Date.now()` of the write. */\n time?: number;\n /** Document name, for the \"Restore unsaved changes from …?\" prompt. */\n name?: string;\n}\n\nexport interface AutosaveRecovery<T = unknown> {\n /** The parsed snapshot (`undefined` when `deserialize` threw; see `error`). */\n snapshot: T | undefined;\n /** The stored text. */\n text: string;\n meta: RecoveryMeta & { dirty: true };\n error?: unknown;\n}\n\nexport interface AutosaveControllerOptions<T> {\n storage: StorageAdapter;\n /** Snapshot key; the meta lives at `<key>:meta`. */\n key: string;\n /** Reads the current document. */\n getSnapshot: () => T;\n /** Default `JSON.stringify`. */\n serialize?: (snapshot: T) => string;\n /** Default `JSON.parse` (used by `checkRecovery`). */\n deserialize?: (text: string) => T;\n /** Current document revision; must change on every edit. */\n revision: () => number;\n /** Revision of the last explicit save (equal to `revision()` when clean). */\n savedRevision: () => number;\n /** Document name stored in the meta. */\n name?: string | (() => string | undefined);\n onStatus?: (info: AutosaveStatusInfo) => void;\n /** Minimum time between snapshots while edits keep coming (default 5000 ms). */\n interval?: number;\n /** Quiet time after the last edit before a snapshot is written (default 1000 ms). */\n debounceMs?: number;\n /** First retry delay after a failed write; doubles per failure (default 1000 ms). */\n retryBaseMs?: number;\n /** Retry delay cap (default 30000 ms). */\n retryMaxMs?: number;\n /** Clock for scheduling (default `Date.now`). */\n now?: () => number;\n /** Wall clock stored in the meta (default `Date.now`). */\n wallClock?: () => number;\n setTimeout?: (fn: () => void, ms: number) => unknown;\n clearTimeout?: (handle: any) => void;\n /** Start hydrated, i.e. as if `ready()` was already called (default `false`). */\n ready?: boolean;\n}\n\nexport interface AutosaveController<T = unknown> {\n /** Current status. */\n getStatus(): AutosaveStatusInfo;\n /** Listener is called on every status change (not immediately). */\n subscribe(listener: (info: AutosaveStatusInfo) => void): () => void;\n /** Call after every change (edit, undo, save). Cheap; the write is scheduled. */\n notify(): void;\n /** Lifts the hydration gate: no snapshot is written before this. */\n ready(): void;\n readonly isReady: boolean;\n /**\n * Writes a snapshot of unsaved changes right away, skipping the debounce, the\n * interval and retry waits. Use on `pagehide` / `visibilitychange`. Resolves\n * once the write settled.\n */\n flushNow(): Promise<void>;\n /**\n * Call after the document was saved (`savedRevision()` now equals\n * `revision()`): retires the snapshot only if this session wrote it.\n */\n markClean(): Promise<void>;\n /**\n * A dirty snapshot left by an earlier session, or `null`. Returns `null` once\n * this session has written its own snapshot.\n */\n checkRecovery(): Promise<AutosaveRecovery<T> | null>;\n /** Deletes an earlier session's snapshot. No-op once this session wrote its own. */\n discardRecovery(): Promise<boolean>;\n /** Runs one scheduling step now (normally driven by `notify` and timers). */\n tick(): void;\n /** Resolves when no write is in flight. */\n whenIdle(): Promise<void>;\n /** Stops all timers. Pending changes are not written; call `flushNow` first. */\n destroy(): void;\n}\n\nexport const recoveryMetaKey = (key: string) => `${key}:meta`;\n\n/**\n * Text for a recovery prompt: \"Restore unsaved changes from <time>?\" (the\n * React and Svelte `RecoveryPrompt` components use it).\n */\nexport function formatRecoveryMessage(\n meta: Pick<RecoveryMeta, \"time\">,\n formatTime: (time: number) => string = (time) => new Date(time).toLocaleString(),\n): string {\n return typeof meta.time === \"number\" && Number.isFinite(meta.time)\n ? `Restore unsaved changes from ${formatTime(meta.time)}?`\n : \"Restore unsaved changes?\";\n}\n\n/**\n * Debounced autosave of crash-recovery snapshots.\n *\n * - Hydration gate: nothing is written until `ready()`, so loading a document\n * (or deciding about a recovered one) never overwrites the previous session's snapshot.\n * - A snapshot is written `debounceMs` after the last edit, and at least every\n * `interval` while edits keep coming; never twice for one revision.\n * - Failed writes are retried with exponential backoff (`retryBaseMs` doubling up to\n * `retryMaxMs`) without waiting for another edit.\n * - The snapshot is written before its dirty meta, so the meta never points at a\n * missing snapshot. Once saved, the meta is marked clean only if this session\n * wrote the snapshot (`createRecoveryPolicy`).\n */\nexport function createAutosaveController<T>(\n options: AutosaveControllerOptions<T>,\n): AutosaveController<T> {\n const {\n storage,\n key,\n getSnapshot,\n revision,\n savedRevision,\n interval = 5000,\n debounceMs = 1000,\n retryBaseMs = 1000,\n retryMaxMs = 30000,\n } = options;\n const serialize = options.serialize ?? ((value: T) => JSON.stringify(value));\n const deserialize = options.deserialize ?? ((text: string) => JSON.parse(text) as T);\n const now = options.now ?? (() => Date.now());\n const wallClock = options.wallClock ?? (() => Date.now());\n const setTimer =\n options.setTimeout ?? ((fn: () => void, ms: number) => globalThis.setTimeout(fn, ms));\n const clearTimer = options.clearTimeout ?? ((handle: any) => globalThis.clearTimeout(handle));\n const metaKey = recoveryMetaKey(key);\n\n const policy: RecoveryPolicy = createRecoveryPolicy({\n interval,\n retryDelay: (failures) => exponentialBackoff(failures, retryBaseMs, retryMaxMs),\n });\n\n let hydrated = options.ready === true;\n let destroyed = false;\n let timer: unknown = null;\n let timerDue = Infinity;\n let inflight: Promise<void> | null = null;\n let observedRevision = revision();\n let lastChangeAt: number | null = null;\n let pendingSince: number | null = null;\n let forceNext = false;\n let info: AutosaveStatusInfo = {\n status: \"idle\",\n error: null,\n failures: 0,\n nextAttemptAt: null,\n lastSavedAt: null,\n ready: hydrated,\n };\n const listeners = new Set<(info: AutosaveStatusInfo) => void>();\n\n const setInfo = (patch: Partial<AutosaveStatusInfo>) => {\n const next = { ...info, ...patch, failures: policy.failures, ready: hydrated };\n if (\n next.status === info.status &&\n next.error === info.error &&\n next.failures === info.failures &&\n next.nextAttemptAt === info.nextAttemptAt &&\n next.lastSavedAt === info.lastSavedAt &&\n next.ready === info.ready\n ) {\n return;\n }\n info = next;\n options.onStatus?.(info);\n listeners.forEach((listener) => {\n try {\n listener(info);\n } catch (error) {\n console.error(\"[autosave] Error in status listener:\", error);\n }\n });\n };\n\n const cancelTimer = () => {\n if (timer !== null) clearTimer(timer);\n timer = null;\n timerDue = Infinity;\n };\n\n const schedule = (delay: number) => {\n if (destroyed) return;\n const due = now() + Math.max(0, delay);\n if (timer !== null && timerDue <= due) return;\n cancelTimer();\n timerDue = due;\n timer = setTimer(() => {\n timer = null;\n timerDue = Infinity;\n tick();\n }, Math.max(0, delay));\n };\n\n const observe = (t: number) => {\n const rev = revision();\n if (rev !== observedRevision) {\n observedRevision = rev;\n lastChangeAt = t;\n pendingSince ??= t;\n }\n return rev;\n };\n\n const docName = () =>\n typeof options.name === \"function\" ? options.name() : options.name;\n\n const run = (action: RecoveryAction, rev: number) => {\n if (action.type === \"writeSnapshot\") {\n setInfo({ status: \"saving\", nextAttemptAt: null });\n inflight = (async () => {\n let ok = false;\n let error: unknown = null;\n try {\n const text = serialize(getSnapshot());\n await storage.set(key, text);\n const meta: RecoveryMeta = {\n dirty: true,\n revision: action.revision,\n time: wallClock(),\n name: docName(),\n };\n await storage.set(metaKey, JSON.stringify(meta));\n ok = true;\n } catch (e) {\n error = e;\n }\n const t = now();\n policy.written(action.revision, ok, t);\n inflight = null;\n if (ok) {\n if (observedRevision === action.revision) pendingSince = null;\n setInfo({ status: \"saved\", error: null, lastSavedAt: t });\n } else {\n setInfo({ status: \"error\", error });\n }\n tick();\n })();\n } else if (action.type === \"writeClean\") {\n setInfo({ status: \"saving\", nextAttemptAt: null });\n inflight = (async () => {\n let ok = false;\n let error: unknown = null;\n try {\n await storage.set(metaKey, JSON.stringify({ dirty: false } satisfies RecoveryMeta));\n ok = true;\n } catch (e) {\n error = e;\n }\n policy.cleaned(ok, now());\n inflight = null;\n if (ok) {\n // Best effort: the clean meta already retired the snapshot.\n if (!policy.ownsSnapshot) await storage.remove(key).catch(() => undefined);\n setInfo({ status: \"saved\", error: null });\n } else {\n setInfo({ status: \"error\", error });\n }\n tick();\n })();\n } else if (action.type === \"wakeAfter\") {\n schedule(action.delay);\n setInfo(\n info.status === \"error\"\n ? { nextAttemptAt: now() + action.delay }\n : { status: rev !== savedRevision() ? \"dirty\" : info.status, nextAttemptAt: now() + action.delay },\n );\n } else if (rev === savedRevision() && info.status === \"dirty\") {\n setInfo({ status: policy.ownsSnapshot ? \"dirty\" : \"idle\", nextAttemptAt: null });\n } else if (rev !== savedRevision() && info.status !== \"saved\" && info.status !== \"error\") {\n setInfo({ status: \"dirty\" });\n }\n };\n\n function tick(): void {\n if (destroyed || inflight) return;\n const t = now();\n const rev = observe(t);\n const saved = savedRevision();\n if (!hydrated) {\n if (rev !== saved) setInfo({ status: \"dirty\" });\n return;\n }\n const force = forceNext;\n forceNext = false;\n if (rev !== saved && !force && info.status !== \"error\") {\n const changed = lastChangeAt ?? t;\n const due = Math.min(changed + debounceMs, (pendingSince ?? changed) + interval);\n if (t < due) {\n setInfo({ status: \"dirty\", nextAttemptAt: due });\n schedule(due - t);\n return;\n }\n }\n run(policy.tick(t, rev, saved, { force }), rev);\n }\n\n const whenIdle = async () => {\n while (inflight) await inflight;\n };\n\n return {\n getStatus: () => info,\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n notify() {\n if (destroyed) return;\n const t = now();\n const before = observedRevision;\n const rev = observe(t);\n const dirty = rev !== savedRevision();\n if (dirty && rev !== before && info.status !== \"error\" && !inflight) {\n setInfo({ status: \"dirty\" });\n }\n if (!hydrated) return;\n // Never synchronous: listeners that update `savedRevision` may run after us.\n const changed = lastChangeAt ?? t;\n const due = Math.min(changed + debounceMs, (pendingSince ?? changed) + interval);\n schedule(dirty ? due - t : 0);\n },\n ready() {\n if (hydrated || destroyed) return;\n hydrated = true;\n setInfo({});\n schedule(0);\n },\n get isReady() {\n return hydrated;\n },\n async flushNow() {\n if (destroyed || !hydrated) return;\n await whenIdle();\n cancelTimer();\n forceNext = true;\n tick();\n await whenIdle();\n },\n async markClean() {\n if (destroyed) return;\n await whenIdle();\n cancelTimer();\n tick();\n await whenIdle();\n },\n async checkRecovery() {\n if (policy.ownsSnapshot) return null;\n let meta: RecoveryMeta;\n try {\n const metaText = await storage.get(metaKey);\n if (!metaText) return null;\n meta = JSON.parse(metaText) as RecoveryMeta;\n } catch {\n return null;\n }\n if (!meta || meta.dirty !== true) return null;\n const text = await storage.get(key).catch(() => null);\n if (text === null) return null;\n try {\n return { snapshot: deserialize(text), text, meta: meta as RecoveryMeta & { dirty: true } };\n } catch (error) {\n return { snapshot: undefined, text, meta: meta as RecoveryMeta & { dirty: true }, error };\n }\n },\n async discardRecovery() {\n if (policy.ownsSnapshot) return false;\n await whenIdle();\n await storage.remove(metaKey);\n await storage.remove(key);\n return true;\n },\n tick,\n whenIdle,\n destroy() {\n destroyed = true;\n cancelTimer();\n listeners.clear();\n },\n };\n}\n","/** Anything that notifies on every document change (`HistoryManager` satisfies it). */\nexport interface RevisionChangeSource {\n subscribe(listener: (...args: any[]) => void): () => void;\n}\n\n/** Anything with a dirty flag (`DirtyTracker` and `HistoryDirtyBinding` satisfy it). */\nexport interface DirtyFlagSource {\n readonly dirty: boolean;\n subscribe(listener: (dirty: boolean) => void): () => void;\n}\n\nexport interface RevisionTrackerOptions {\n /** Change notifications (e.g. a `HistoryManager`). Every notification is a new revision. */\n history?: RevisionChangeSource;\n /**\n * Saved-vs-current flag (e.g. `bindDirtyToHistory(...)` or a `DirtyTracker`).\n * Whenever it reads clean, the current revision counts as saved, so undoing\n * back to the saved state needs no snapshot. Without it, call `markSaved()`.\n */\n dirty?: DirtyFlagSource;\n}\n\nexport interface RevisionTracker {\n /** Bumped on every change. */\n revision(): number;\n /** Revision of the last save; equals `revision()` while clean. */\n savedRevision(): number;\n /** Records a change made outside `history`. */\n bump(): void;\n /** Marks the current revision saved. */\n markSaved(): void;\n /** Called after every change, save or dirty flip. */\n subscribe(listener: () => void): () => void;\n destroy(): void;\n}\n\n/**\n * Derives the `revision` / `savedRevision` pair an autosave controller needs\n * from a history and a dirty flag (`bindDirtyToHistory` or `DirtyTracker`).\n * Structural types only, so this entry stays dependency-free.\n */\nexport function createRevisionTracker(options: RevisionTrackerOptions = {}): RevisionTracker {\n let revision = 0;\n let saved = 0;\n const listeners = new Set<() => void>();\n const emit = () =>\n listeners.forEach((listener) => {\n try {\n listener();\n } catch (error) {\n console.error(\"[autosave] Error in revision listener:\", error);\n }\n });\n const syncSaved = () => {\n // Read lazily: the dirty binding may update after our history listener ran.\n if (options.dirty && !options.dirty.dirty) saved = revision;\n };\n\n const unsubscribers: (() => void)[] = [];\n if (options.history) {\n unsubscribers.push(\n options.history.subscribe(() => {\n revision++;\n emit();\n }),\n );\n }\n if (options.dirty) {\n let first = true;\n unsubscribers.push(\n options.dirty.subscribe((dirty) => {\n if (first) {\n first = false;\n // DirtyTracker calls back immediately with the current value.\n if (dirty && revision === saved) revision++;\n return;\n }\n if (dirty && revision === saved) revision++;\n if (!dirty) saved = revision;\n emit();\n }),\n );\n }\n\n return {\n revision: () => revision,\n savedRevision: () => {\n syncSaved();\n return saved;\n },\n bump() {\n revision++;\n emit();\n },\n markSaved() {\n saved = revision;\n emit();\n },\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n destroy() {\n unsubscribers.forEach((fn) => fn());\n unsubscribers.length = 0;\n listeners.clear();\n },\n };\n}\n"]}
@@ -0,0 +1,347 @@
1
+ export { J as JsonObject, L as LoadDocumentOptions, a as LoadDocumentResult, b as LoadReport, c as LoadSectionsOptions, d as LoadSectionsResult, M as MigrateOptions, e as MigrateResult, f as MigrationStep, R as ReadOnlyReason, S as SectionProblem, g as SectionSpec, h as SectionSpecs, i as createLoadReport, j as hasLoadProblems, l as loadDocument, k as loadSections, m as migrateDocument } from '../migrate-iTlm3a8h.cjs';
2
+
3
+ /**
4
+ * What the caller should do now (see `RecoveryPolicy.tick`).
5
+ *
6
+ * - `writeSnapshot`: write the snapshot and a dirty meta for `revision`, then report with `written`.
7
+ * - `writeClean`: write a clean meta (retire the snapshot), then report with `cleaned`.
8
+ * - `wakeAfter`: nothing now; tick again after `delay` (same unit as `now`).
9
+ */
10
+ type RecoveryAction = {
11
+ type: "nothing";
12
+ } | {
13
+ type: "writeSnapshot";
14
+ revision: number;
15
+ } | {
16
+ type: "writeClean";
17
+ } | {
18
+ type: "wakeAfter";
19
+ delay: number;
20
+ };
21
+ interface RecoveryPolicyOptions {
22
+ /**
23
+ * Minimum time between snapshots of a changing document, in the unit of `now`
24
+ * (default 5000, i.e. milliseconds).
25
+ */
26
+ interval?: number;
27
+ /**
28
+ * Wait before retrying after the `failures`-th consecutive failed write
29
+ * (default: `interval`, as in FilmCraft). Use `exponentialBackoff` for doubling delays.
30
+ */
31
+ retryDelay?: (failures: number) => number;
32
+ }
33
+ interface RecoveryTickOptions {
34
+ /** Ignore the interval and any retry wait (e.g. on `pagehide`). */
35
+ force?: boolean;
36
+ }
37
+ interface RecoveryPolicy {
38
+ /** Called whenever something may have changed, with the revisions and the current time. */
39
+ tick(now: number, revision: number, savedRevision: number, options?: RecoveryTickOptions): RecoveryAction;
40
+ /** The snapshot write for `revision` finished. */
41
+ written(revision: number, ok: boolean, now: number): void;
42
+ /** The clean-meta write finished. */
43
+ cleaned(ok: boolean, now: number): void;
44
+ /** Whether this session wrote a dirty snapshot that is not retired yet. */
45
+ readonly ownsSnapshot: boolean;
46
+ /** Consecutive failed writes (reset by a successful one). */
47
+ readonly failures: number;
48
+ }
49
+ /**
50
+ * Exponential backoff: `base`, `2·base`, `4·base`, … capped at `max`.
51
+ * `failures` is 1 for the first failure.
52
+ */
53
+ declare function exponentialBackoff(failures: number, base?: number, max?: number): number;
54
+ /**
55
+ * Pure state machine deciding when a crash-recovery snapshot is written or
56
+ * retired. Deterministic: time is always passed in.
57
+ *
58
+ * - A dirty document is snapshotted at most every `interval`, once per revision.
59
+ * A write that fails is retried after `retryDelay`, without waiting for another edit.
60
+ * - The snapshot is marked clean only if this session wrote it. A snapshot left by an
61
+ * earlier session (that was not restored) stays dirty, so it can still be recovered.
62
+ * - An edit made while the clean write is in flight keeps the snapshot owned.
63
+ */
64
+ declare function createRecoveryPolicy(options?: RecoveryPolicyOptions): RecoveryPolicy;
65
+
66
+ /**
67
+ * Key/value storage adapters for autosave and crash recovery. Every adapter is
68
+ * async, stores strings, feature-detects its backend (an unavailable backend
69
+ * rejects every call with `StorageUnavailableError` instead of throwing at
70
+ * construction, so SSR and old browsers are safe) and accepts an injectable
71
+ * failure mode for tests.
72
+ */
73
+ type StorageOperation = "get" | "set" | "remove";
74
+ /**
75
+ * Injected failure: return `true` (or an `Error`) to make that operation fail.
76
+ * Example: `failure: (op) => op === "set"` simulates a full disk.
77
+ */
78
+ type StorageFailureMode = (operation: StorageOperation, key: string) => boolean | Error | undefined | void;
79
+ interface StorageAdapter {
80
+ /** Short backend name, e.g. `"memory"`, `"localStorage"`, `"indexedDB"`, `"opfs"`. */
81
+ readonly kind: string;
82
+ /** Whether the backend was found. When `false`, every call rejects. */
83
+ readonly available: boolean;
84
+ get(key: string): Promise<string | null>;
85
+ set(key: string, value: string): Promise<void>;
86
+ remove(key: string): Promise<void>;
87
+ }
88
+ interface StorageAdapterOptions {
89
+ failure?: StorageFailureMode;
90
+ }
91
+ declare class StorageUnavailableError extends Error {
92
+ constructor(kind: string);
93
+ }
94
+ declare class InjectedStorageError extends Error {
95
+ constructor(operation: StorageOperation, key: string);
96
+ }
97
+ /** Wraps an adapter so `failure` can make any of its operations fail. */
98
+ declare function withStorageFailures(inner: StorageAdapter, failure: StorageFailureMode): StorageAdapter;
99
+ interface MemoryStorage extends StorageAdapter {
100
+ /** The underlying map (inspect it in tests). */
101
+ readonly entries: Map<string, string>;
102
+ }
103
+ /** In-memory storage (tests, SSR, or a session-only fallback). */
104
+ declare function createMemoryStorage(options?: StorageAdapterOptions & {
105
+ initial?: Record<string, string>;
106
+ }): MemoryStorage;
107
+ /** Minimal `Storage` surface used by the localStorage adapter. */
108
+ interface WebStorageLike {
109
+ getItem(key: string): string | null;
110
+ setItem(key: string, value: string): void;
111
+ removeItem(key: string): void;
112
+ }
113
+ interface LocalStorageAdapterOptions extends StorageAdapterOptions {
114
+ /** Storage to use (default `globalThis.localStorage`, e.g. pass `sessionStorage`). */
115
+ storage?: WebStorageLike;
116
+ }
117
+ /** Whether `localStorage` can be used (it throws when site data is blocked). */
118
+ declare function isLocalStorageAvailable(): boolean;
119
+ /**
120
+ * `localStorage` adapter; every key is prefixed with `prefix`. Writes are
121
+ * synchronous under the hood, so they complete even inside `pagehide`.
122
+ * A full quota rejects the `set` (`QuotaExceededError`).
123
+ */
124
+ declare function createLocalStorageAdapter(prefix?: string, options?: LocalStorageAdapterOptions): StorageAdapter;
125
+ /** Minimal `IDBFactory` surface (the real `indexedDB`, or a fake in tests). */
126
+ interface IdbFactoryLike {
127
+ open(name: string, version?: number): any;
128
+ }
129
+ interface IndexedDbAdapterOptions extends StorageAdapterOptions {
130
+ /** Object store name (default `"kv"`). */
131
+ store?: string;
132
+ /** Factory to use (default `globalThis.indexedDB`). */
133
+ indexedDB?: IdbFactoryLike;
134
+ }
135
+ /** Whether `indexedDB` exists in this environment. */
136
+ declare function isIndexedDbAvailable(): boolean;
137
+ /**
138
+ * IndexedDB adapter: one database `name` with a single key/value object store.
139
+ * A `set` resolves when its transaction completes (not merely when the request
140
+ * succeeds), so a resolved write is durable.
141
+ */
142
+ declare function createIndexedDbAdapter(name?: string, options?: IndexedDbAdapterOptions): StorageAdapter;
143
+ /** Minimal `FileSystemDirectoryHandle` surface used by the OPFS adapter. */
144
+ interface OpfsDirectoryLike {
145
+ getDirectoryHandle(name: string, options?: {
146
+ create?: boolean;
147
+ }): Promise<OpfsDirectoryLike>;
148
+ getFileHandle(name: string, options?: {
149
+ create?: boolean;
150
+ }): Promise<OpfsFileLike>;
151
+ removeEntry(name: string): Promise<void>;
152
+ }
153
+ interface OpfsFileLike {
154
+ getFile(): Promise<{
155
+ text(): Promise<string>;
156
+ }>;
157
+ createWritable?(): Promise<{
158
+ write(data: string): Promise<void>;
159
+ close(): Promise<void>;
160
+ }>;
161
+ /** Rename in place (Chromium, Safari). Used for temp-file + rename writes. */
162
+ move?(name: string): Promise<void>;
163
+ }
164
+ interface OpfsAdapterOptions extends StorageAdapterOptions {
165
+ /** Root directory (default `navigator.storage.getDirectory()`). */
166
+ root?: () => Promise<OpfsDirectoryLike>;
167
+ /** Force the write-then-pointer-swap strategy even when `move` exists. */
168
+ disableRename?: boolean;
169
+ }
170
+ /** Whether the Origin Private File System exists (`navigator.storage.getDirectory`). */
171
+ declare function isOpfsAvailable(): boolean;
172
+ /**
173
+ * Origin Private File System adapter. Values are files in `dir` (created on
174
+ * demand), named after the URI-encoded key.
175
+ *
176
+ * Writes never leave a half-written value behind: where file handles support
177
+ * `move`, the value is written to `<file>.tmp` and renamed over `<file>`;
178
+ * otherwise it alternates between two slots (`<file>.a` / `<file>.b`) and then
179
+ * swaps a pointer file (`<file>.ptr`), so a crash mid-write keeps the previous
180
+ * value. Main-thread writes need `createWritable` (missing in some Safari
181
+ * versions); without it, `set` rejects with `StorageUnavailableError`.
182
+ */
183
+ declare function createOpfsAdapter(dir?: string, options?: OpfsAdapterOptions): StorageAdapter;
184
+
185
+ type AutosaveStatus = "idle" | "dirty" | "saving" | "saved" | "error";
186
+ interface AutosaveStatusInfo {
187
+ status: AutosaveStatus;
188
+ /** Last write error (while `status` is `"error"`). */
189
+ error: unknown;
190
+ /** Consecutive failed writes. */
191
+ failures: number;
192
+ /** When the next attempt is due (`now()` units), while waiting. */
193
+ nextAttemptAt: number | null;
194
+ /** When the last snapshot was written (`now()` units). */
195
+ lastSavedAt: number | null;
196
+ /** Whether `ready()` was called. */
197
+ ready: boolean;
198
+ }
199
+ /** Stored next to the snapshot (`<key>:meta`). */
200
+ interface RecoveryMeta {
201
+ dirty: boolean;
202
+ revision?: number;
203
+ /** `Date.now()` of the write. */
204
+ time?: number;
205
+ /** Document name, for the "Restore unsaved changes from …?" prompt. */
206
+ name?: string;
207
+ }
208
+ interface AutosaveRecovery<T = unknown> {
209
+ /** The parsed snapshot (`undefined` when `deserialize` threw; see `error`). */
210
+ snapshot: T | undefined;
211
+ /** The stored text. */
212
+ text: string;
213
+ meta: RecoveryMeta & {
214
+ dirty: true;
215
+ };
216
+ error?: unknown;
217
+ }
218
+ interface AutosaveControllerOptions<T> {
219
+ storage: StorageAdapter;
220
+ /** Snapshot key; the meta lives at `<key>:meta`. */
221
+ key: string;
222
+ /** Reads the current document. */
223
+ getSnapshot: () => T;
224
+ /** Default `JSON.stringify`. */
225
+ serialize?: (snapshot: T) => string;
226
+ /** Default `JSON.parse` (used by `checkRecovery`). */
227
+ deserialize?: (text: string) => T;
228
+ /** Current document revision; must change on every edit. */
229
+ revision: () => number;
230
+ /** Revision of the last explicit save (equal to `revision()` when clean). */
231
+ savedRevision: () => number;
232
+ /** Document name stored in the meta. */
233
+ name?: string | (() => string | undefined);
234
+ onStatus?: (info: AutosaveStatusInfo) => void;
235
+ /** Minimum time between snapshots while edits keep coming (default 5000 ms). */
236
+ interval?: number;
237
+ /** Quiet time after the last edit before a snapshot is written (default 1000 ms). */
238
+ debounceMs?: number;
239
+ /** First retry delay after a failed write; doubles per failure (default 1000 ms). */
240
+ retryBaseMs?: number;
241
+ /** Retry delay cap (default 30000 ms). */
242
+ retryMaxMs?: number;
243
+ /** Clock for scheduling (default `Date.now`). */
244
+ now?: () => number;
245
+ /** Wall clock stored in the meta (default `Date.now`). */
246
+ wallClock?: () => number;
247
+ setTimeout?: (fn: () => void, ms: number) => unknown;
248
+ clearTimeout?: (handle: any) => void;
249
+ /** Start hydrated, i.e. as if `ready()` was already called (default `false`). */
250
+ ready?: boolean;
251
+ }
252
+ interface AutosaveController<T = unknown> {
253
+ /** Current status. */
254
+ getStatus(): AutosaveStatusInfo;
255
+ /** Listener is called on every status change (not immediately). */
256
+ subscribe(listener: (info: AutosaveStatusInfo) => void): () => void;
257
+ /** Call after every change (edit, undo, save). Cheap; the write is scheduled. */
258
+ notify(): void;
259
+ /** Lifts the hydration gate: no snapshot is written before this. */
260
+ ready(): void;
261
+ readonly isReady: boolean;
262
+ /**
263
+ * Writes a snapshot of unsaved changes right away, skipping the debounce, the
264
+ * interval and retry waits. Use on `pagehide` / `visibilitychange`. Resolves
265
+ * once the write settled.
266
+ */
267
+ flushNow(): Promise<void>;
268
+ /**
269
+ * Call after the document was saved (`savedRevision()` now equals
270
+ * `revision()`): retires the snapshot only if this session wrote it.
271
+ */
272
+ markClean(): Promise<void>;
273
+ /**
274
+ * A dirty snapshot left by an earlier session, or `null`. Returns `null` once
275
+ * this session has written its own snapshot.
276
+ */
277
+ checkRecovery(): Promise<AutosaveRecovery<T> | null>;
278
+ /** Deletes an earlier session's snapshot. No-op once this session wrote its own. */
279
+ discardRecovery(): Promise<boolean>;
280
+ /** Runs one scheduling step now (normally driven by `notify` and timers). */
281
+ tick(): void;
282
+ /** Resolves when no write is in flight. */
283
+ whenIdle(): Promise<void>;
284
+ /** Stops all timers. Pending changes are not written; call `flushNow` first. */
285
+ destroy(): void;
286
+ }
287
+ declare const recoveryMetaKey: (key: string) => string;
288
+ /**
289
+ * Text for a recovery prompt: "Restore unsaved changes from <time>?" (the
290
+ * React and Svelte `RecoveryPrompt` components use it).
291
+ */
292
+ declare function formatRecoveryMessage(meta: Pick<RecoveryMeta, "time">, formatTime?: (time: number) => string): string;
293
+ /**
294
+ * Debounced autosave of crash-recovery snapshots.
295
+ *
296
+ * - Hydration gate: nothing is written until `ready()`, so loading a document
297
+ * (or deciding about a recovered one) never overwrites the previous session's snapshot.
298
+ * - A snapshot is written `debounceMs` after the last edit, and at least every
299
+ * `interval` while edits keep coming; never twice for one revision.
300
+ * - Failed writes are retried with exponential backoff (`retryBaseMs` doubling up to
301
+ * `retryMaxMs`) without waiting for another edit.
302
+ * - The snapshot is written before its dirty meta, so the meta never points at a
303
+ * missing snapshot. Once saved, the meta is marked clean only if this session
304
+ * wrote the snapshot (`createRecoveryPolicy`).
305
+ */
306
+ declare function createAutosaveController<T>(options: AutosaveControllerOptions<T>): AutosaveController<T>;
307
+
308
+ /** Anything that notifies on every document change (`HistoryManager` satisfies it). */
309
+ interface RevisionChangeSource {
310
+ subscribe(listener: (...args: any[]) => void): () => void;
311
+ }
312
+ /** Anything with a dirty flag (`DirtyTracker` and `HistoryDirtyBinding` satisfy it). */
313
+ interface DirtyFlagSource {
314
+ readonly dirty: boolean;
315
+ subscribe(listener: (dirty: boolean) => void): () => void;
316
+ }
317
+ interface RevisionTrackerOptions {
318
+ /** Change notifications (e.g. a `HistoryManager`). Every notification is a new revision. */
319
+ history?: RevisionChangeSource;
320
+ /**
321
+ * Saved-vs-current flag (e.g. `bindDirtyToHistory(...)` or a `DirtyTracker`).
322
+ * Whenever it reads clean, the current revision counts as saved, so undoing
323
+ * back to the saved state needs no snapshot. Without it, call `markSaved()`.
324
+ */
325
+ dirty?: DirtyFlagSource;
326
+ }
327
+ interface RevisionTracker {
328
+ /** Bumped on every change. */
329
+ revision(): number;
330
+ /** Revision of the last save; equals `revision()` while clean. */
331
+ savedRevision(): number;
332
+ /** Records a change made outside `history`. */
333
+ bump(): void;
334
+ /** Marks the current revision saved. */
335
+ markSaved(): void;
336
+ /** Called after every change, save or dirty flip. */
337
+ subscribe(listener: () => void): () => void;
338
+ destroy(): void;
339
+ }
340
+ /**
341
+ * Derives the `revision` / `savedRevision` pair an autosave controller needs
342
+ * from a history and a dirty flag (`bindDirtyToHistory` or `DirtyTracker`).
343
+ * Structural types only, so this entry stays dependency-free.
344
+ */
345
+ declare function createRevisionTracker(options?: RevisionTrackerOptions): RevisionTracker;
346
+
347
+ export { type AutosaveController, type AutosaveControllerOptions, type AutosaveRecovery, type AutosaveStatus, type AutosaveStatusInfo, type DirtyFlagSource, type IdbFactoryLike, type IndexedDbAdapterOptions, InjectedStorageError, type LocalStorageAdapterOptions, type MemoryStorage, type OpfsAdapterOptions, type OpfsDirectoryLike, type OpfsFileLike, type RecoveryAction, type RecoveryMeta, type RecoveryPolicy, type RecoveryPolicyOptions, type RecoveryTickOptions, type RevisionChangeSource, type RevisionTracker, type RevisionTrackerOptions, type StorageAdapter, type StorageAdapterOptions, type StorageFailureMode, type StorageOperation, StorageUnavailableError, type WebStorageLike, createAutosaveController, createIndexedDbAdapter, createLocalStorageAdapter, createMemoryStorage, createOpfsAdapter, createRecoveryPolicy, createRevisionTracker, exponentialBackoff, formatRecoveryMessage, isIndexedDbAvailable, isLocalStorageAvailable, isOpfsAvailable, recoveryMetaKey, withStorageFailures };