@pixerate/editor 0.278.0 → 0.279.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/animation-editor/index.cjs +59 -59
  2. package/dist/animation-editor/index.js +3 -3
  3. package/dist/canvas/editorDocument.d.cts +53 -0
  4. package/dist/canvas/editorDocument.d.ts +53 -0
  5. package/dist/canvas/index.cjs +169 -22
  6. package/dist/canvas/index.js +148 -1
  7. package/dist/chart/index.cjs +1 -1
  8. package/dist/chart/index.js +1 -1
  9. package/dist/{chunk-MITQGHN3.cjs → chunk-4BMRQSPB.cjs} +15 -1
  10. package/dist/{chunk-T2HUQBRC.cjs → chunk-DK33HLA2.cjs} +331 -10
  11. package/dist/{chunk-DWPFGWW5.js → chunk-FKO3CY2C.js} +1 -1
  12. package/dist/{chunk-N4MCWQZ7.js → chunk-HN57I7KT.js} +1 -1
  13. package/dist/{chunk-4NZTK7LM.cjs → chunk-K4ANWBF7.cjs} +35 -35
  14. package/dist/{chunk-JV7DKLMX.js → chunk-KBH6CM3U.js} +326 -5
  15. package/dist/{chunk-VNF7A2JA.cjs → chunk-MNHP3AQT.cjs} +23 -23
  16. package/dist/{chunk-FNIWHSLI.js → chunk-NJDEKU5D.js} +1 -1
  17. package/dist/{chunk-CTHWXW5M.js → chunk-V6LQND7D.js} +14 -0
  18. package/dist/{chunk-QBZEC2JJ.cjs → chunk-WCC4NXWY.cjs} +86 -86
  19. package/dist/{chunk-PEQWARCP.cjs → chunk-WZYRYYXN.cjs} +29 -25
  20. package/dist/{chunk-HHX7ST6Z.js → chunk-YOM76H2V.js} +7 -3
  21. package/dist/image-editor/index.cjs +783 -783
  22. package/dist/image-editor/index.js +4 -4
  23. package/dist/paint/index.cjs +2 -2
  24. package/dist/paint/index.js +1 -1
  25. package/dist/paint/wet/gpu.d.cts +2 -0
  26. package/dist/paint/wet/gpu.d.ts +2 -0
  27. package/dist/paint-editor/index.cjs +128 -128
  28. package/dist/paint-editor/index.js +3 -3
  29. package/dist/persistence/autosave.d.cts +15 -1
  30. package/dist/persistence/autosave.d.ts +15 -1
  31. package/dist/persistence/editorAutosave.d.cts +12 -1
  32. package/dist/persistence/editorAutosave.d.ts +12 -1
  33. package/dist/persistence/index.cjs +6 -2
  34. package/dist/persistence/index.d.cts +1 -0
  35. package/dist/persistence/index.d.ts +1 -0
  36. package/dist/persistence/index.js +5 -1
  37. package/dist/persistence/remoteSync.d.cts +152 -0
  38. package/dist/persistence/remoteSync.d.ts +152 -0
  39. package/dist/vector/index.cjs +997 -997
  40. package/dist/vector/index.js +4 -4
  41. package/package.json +1 -1
@@ -42,7 +42,7 @@ import {
42
42
  resolveExternalAssets,
43
43
  serializeLayers,
44
44
  serializeLayersExternal
45
- } from "../chunk-FNIWHSLI.js";
45
+ } from "../chunk-NJDEKU5D.js";
46
46
  import "../chunk-ADMRNWR7.js";
47
47
  import "../chunk-WJ4WQJCC.js";
48
48
  import {
@@ -125,7 +125,7 @@ import {
125
125
  wetPaperInput,
126
126
  wetPigmentOf,
127
127
  withStrokePath
128
- } from "../chunk-HHX7ST6Z.js";
128
+ } from "../chunk-YOM76H2V.js";
129
129
  import {
130
130
  BRUSH_ID,
131
131
  BRUSH_PRESETS,
@@ -250,7 +250,7 @@ import {
250
250
  normalizeWetParams,
251
251
  pigmentOf,
252
252
  wetSupport
253
- } from "../chunk-CTHWXW5M.js";
253
+ } from "../chunk-V6LQND7D.js";
254
254
  import "../chunk-J23JOHPR.js";
255
255
  import {
256
256
  CoverageMap,
@@ -21,6 +21,8 @@ export interface RecoveryMeta {
21
21
  time?: number;
22
22
  /** Document name, for the "Restore unsaved changes from …?" prompt. */
23
23
  name?: string;
24
+ /** The server / host version the edits in the snapshot were made on (`baseVersion` option). */
25
+ baseVersion?: number;
24
26
  }
25
27
  export interface AutosaveRecovery<T = unknown> {
26
28
  /** The parsed snapshot (`undefined` when `deserialize` threw; see `error`). */
@@ -65,6 +67,18 @@ export interface AutosaveControllerOptions<T> {
65
67
  clearTimeout?: (handle: any) => void;
66
68
  /** Start hydrated, i.e. as if `ready()` was already called (default `false`). */
67
69
  ready?: boolean;
70
+ /**
71
+ * The server / host version of the document that is loaded now. It is stored in each
72
+ * snapshot's meta, and `checkRecovery` retires (deletes and does not return) a snapshot whose
73
+ * edits were made on an older version, so it never overwrites newer server state. A snapshot
74
+ * without a stored version counts as version 0.
75
+ */
76
+ baseVersion?: number | (() => number | undefined);
77
+ }
78
+ /** Options for `AutosaveController.checkRecovery`. */
79
+ export interface CheckRecoveryOptions {
80
+ /** Retire snapshots made on a version older than this (default the `baseVersion` option). */
81
+ minimumBaseVersion?: number;
68
82
  }
69
83
  export interface AutosaveController<T = unknown> {
70
84
  /** Current status. */
@@ -91,7 +105,7 @@ export interface AutosaveController<T = unknown> {
91
105
  * A dirty snapshot left by an earlier session, or `null`. Returns `null` once
92
106
  * this session has written its own snapshot.
93
107
  */
94
- checkRecovery(): Promise<AutosaveRecovery<T> | null>;
108
+ checkRecovery(options?: CheckRecoveryOptions): Promise<AutosaveRecovery<T> | null>;
95
109
  /** Deletes an earlier session's snapshot. No-op once this session wrote its own. */
96
110
  discardRecovery(): Promise<boolean>;
97
111
  /** Runs one scheduling step now (normally driven by `notify` and timers). */
@@ -21,6 +21,8 @@ export interface RecoveryMeta {
21
21
  time?: number;
22
22
  /** Document name, for the "Restore unsaved changes from …?" prompt. */
23
23
  name?: string;
24
+ /** The server / host version the edits in the snapshot were made on (`baseVersion` option). */
25
+ baseVersion?: number;
24
26
  }
25
27
  export interface AutosaveRecovery<T = unknown> {
26
28
  /** The parsed snapshot (`undefined` when `deserialize` threw; see `error`). */
@@ -65,6 +67,18 @@ export interface AutosaveControllerOptions<T> {
65
67
  clearTimeout?: (handle: any) => void;
66
68
  /** Start hydrated, i.e. as if `ready()` was already called (default `false`). */
67
69
  ready?: boolean;
70
+ /**
71
+ * The server / host version of the document that is loaded now. It is stored in each
72
+ * snapshot's meta, and `checkRecovery` retires (deletes and does not return) a snapshot whose
73
+ * edits were made on an older version, so it never overwrites newer server state. A snapshot
74
+ * without a stored version counts as version 0.
75
+ */
76
+ baseVersion?: number | (() => number | undefined);
77
+ }
78
+ /** Options for `AutosaveController.checkRecovery`. */
79
+ export interface CheckRecoveryOptions {
80
+ /** Retire snapshots made on a version older than this (default the `baseVersion` option). */
81
+ minimumBaseVersion?: number;
68
82
  }
69
83
  export interface AutosaveController<T = unknown> {
70
84
  /** Current status. */
@@ -91,7 +105,7 @@ export interface AutosaveController<T = unknown> {
91
105
  * A dirty snapshot left by an earlier session, or `null`. Returns `null` once
92
106
  * this session has written its own snapshot.
93
107
  */
94
- checkRecovery(): Promise<AutosaveRecovery<T> | null>;
108
+ checkRecovery(options?: CheckRecoveryOptions): Promise<AutosaveRecovery<T> | null>;
95
109
  /** Deletes an earlier session's snapshot. No-op once this session wrote its own. */
96
110
  discardRecovery(): Promise<boolean>;
97
111
  /** Runs one scheduling step now (normally driven by `notify` and timers). */
@@ -11,6 +11,12 @@ export interface EditorAutosaveOptions extends Omit<AutosaveControllerOptions<Ed
11
11
  * saved state needs no snapshot. Without it, every change counts until `markClean()`.
12
12
  */
13
13
  dirty?: DirtyFlagSource;
14
+ /**
15
+ * The server / host version of the loaded document, stored with each snapshot.
16
+ * `checkRecovery` / `recoverIfNewer` retire snapshots made on an older version instead of
17
+ * offering them (see `AutosaveControllerOptions.baseVersion`).
18
+ */
19
+ baseVersion?: number | (() => number | undefined);
14
20
  /**
15
21
  * For documents with `assets`: after `markClean` or `discard` retire a snapshot, delete the
16
22
  * stored assets nothing refers to any more (`collectEditorAssetGarbage`, best effort, keeping
@@ -21,8 +27,13 @@ export interface EditorAutosaveOptions extends Omit<AutosaveControllerOptions<Ed
21
27
  export interface EditorAutosave {
22
28
  readonly controller: AutosaveController<EditorDocumentFile>;
23
29
  readonly revisions: RevisionTracker;
24
- /** An earlier session's unsaved snapshot of this document, or `null`. */
30
+ /** An earlier session's unsaved snapshot of this document, or `null` (snapshots made on a version older than `baseVersion` are retired). */
25
31
  checkRecovery(): Promise<AutosaveRecovery<EditorDocumentFile> | null>;
32
+ /**
33
+ * Checks for an earlier session's unsaved snapshot, restoring it into `doc` if it is valid and
34
+ * at least as new as `baseVersion`, or retiring it and starting autosave cleanly if stale/invalid.
35
+ */
36
+ recoverIfNewer(minimumBaseVersion?: number): Promise<LoadReport | null>;
26
37
  /** Loads a recovered snapshot into the document and starts autosaving. */
27
38
  restore(recovery: AutosaveRecovery<EditorDocumentFile>): LoadReport;
28
39
  /** Deletes the earlier session's snapshot and starts autosaving. */
@@ -11,6 +11,12 @@ export interface EditorAutosaveOptions extends Omit<AutosaveControllerOptions<Ed
11
11
  * saved state needs no snapshot. Without it, every change counts until `markClean()`.
12
12
  */
13
13
  dirty?: DirtyFlagSource;
14
+ /**
15
+ * The server / host version of the loaded document, stored with each snapshot.
16
+ * `checkRecovery` / `recoverIfNewer` retire snapshots made on an older version instead of
17
+ * offering them (see `AutosaveControllerOptions.baseVersion`).
18
+ */
19
+ baseVersion?: number | (() => number | undefined);
14
20
  /**
15
21
  * For documents with `assets`: after `markClean` or `discard` retire a snapshot, delete the
16
22
  * stored assets nothing refers to any more (`collectEditorAssetGarbage`, best effort, keeping
@@ -21,8 +27,13 @@ export interface EditorAutosaveOptions extends Omit<AutosaveControllerOptions<Ed
21
27
  export interface EditorAutosave {
22
28
  readonly controller: AutosaveController<EditorDocumentFile>;
23
29
  readonly revisions: RevisionTracker;
24
- /** An earlier session's unsaved snapshot of this document, or `null`. */
30
+ /** An earlier session's unsaved snapshot of this document, or `null` (snapshots made on a version older than `baseVersion` are retired). */
25
31
  checkRecovery(): Promise<AutosaveRecovery<EditorDocumentFile> | null>;
32
+ /**
33
+ * Checks for an earlier session's unsaved snapshot, restoring it into `doc` if it is valid and
34
+ * at least as new as `baseVersion`, or retiring it and starting autosave cleanly if stale/invalid.
35
+ */
36
+ recoverIfNewer(minimumBaseVersion?: number): Promise<LoadReport | null>;
26
37
  /** Loads a recovered snapshot into the document and starts autosaving. */
27
38
  restore(recovery: AutosaveRecovery<EditorDocumentFile>): LoadReport;
28
39
  /** Deletes the earlier session's snapshot and starts autosaving. */
@@ -19,7 +19,9 @@
19
19
 
20
20
 
21
21
 
22
- var _chunkT2HUQBRCcjs = require('../chunk-T2HUQBRC.cjs');
22
+
23
+
24
+ var _chunkDK33HLA2cjs = require('../chunk-DK33HLA2.cjs');
23
25
 
24
26
 
25
27
 
@@ -92,4 +94,6 @@ require('../chunk-2BSBFBOA.cjs');
92
94
 
93
95
 
94
96
 
95
- exports.EDITOR_ASSET_PREFIX = _chunkSJ2CPTQCcjs.EDITOR_ASSET_PREFIX; exports.EDITOR_DOCUMENT_FORMAT = _chunkSJ2CPTQCcjs.EDITOR_DOCUMENT_FORMAT; exports.EditorDocumentLoadError = _chunkSJ2CPTQCcjs.EditorDocumentLoadError; exports.InjectedStorageError = _chunkT2HUQBRCcjs.InjectedStorageError; exports.StorageUnavailableError = _chunkT2HUQBRCcjs.StorageUnavailableError; exports.collectAssetRefs = _chunkSJ2CPTQCcjs.collectAssetRefs; exports.collectEditorAssetGarbage = _chunkSJ2CPTQCcjs.collectEditorAssetGarbage; exports.collectRetiredEditorAssets = _chunkSJ2CPTQCcjs.collectRetiredEditorAssets; exports.createAutosaveController = _chunkT2HUQBRCcjs.createAutosaveController; exports.createDocumentToolProvider = _chunkT2HUQBRCcjs.createDocumentToolProvider; exports.createEditorAssetRegistry = _chunkSJ2CPTQCcjs.createEditorAssetRegistry; exports.createEditorAutosave = _chunkT2HUQBRCcjs.createEditorAutosave; exports.createEditorDocumentStore = _chunkT2HUQBRCcjs.createEditorDocumentStore; exports.createIndexedDbAdapter = _chunkT2HUQBRCcjs.createIndexedDbAdapter; exports.createLoadReport = _chunkSJ2CPTQCcjs.createLoadReport; exports.createLocalStorageAdapter = _chunkT2HUQBRCcjs.createLocalStorageAdapter; exports.createMemoryStorage = _chunkT2HUQBRCcjs.createMemoryStorage; exports.createOpfsAdapter = _chunkT2HUQBRCcjs.createOpfsAdapter; exports.createRecoveryPolicy = _chunkT2HUQBRCcjs.createRecoveryPolicy; exports.createRevisionTracker = _chunkT2HUQBRCcjs.createRevisionTracker; exports.defineEditorDocument = _chunkSJ2CPTQCcjs.defineEditorDocument; exports.exponentialBackoff = _chunkT2HUQBRCcjs.exponentialBackoff; exports.externalizeAssets = _chunkSJ2CPTQCcjs.externalizeAssets; exports.formatRecoveryMessage = _chunkT2HUQBRCcjs.formatRecoveryMessage; exports.hasLoadProblems = _chunkSJ2CPTQCcjs.hasLoadProblems; exports.internalizeAssets = _chunkSJ2CPTQCcjs.internalizeAssets; exports.isEditorDocumentFile = _chunkSJ2CPTQCcjs.isEditorDocumentFile; exports.isIndexedDbAvailable = _chunkT2HUQBRCcjs.isIndexedDbAvailable; exports.isInlineAsset = _chunkSJ2CPTQCcjs.isInlineAsset; exports.isLocalStorageAvailable = _chunkT2HUQBRCcjs.isLocalStorageAvailable; exports.isOpfsAvailable = _chunkT2HUQBRCcjs.isOpfsAvailable; exports.loadDocument = _chunkSJ2CPTQCcjs.loadDocument; exports.loadSections = _chunkSJ2CPTQCcjs.loadSections; exports.migrateDocument = _chunkSJ2CPTQCcjs.migrateDocument; exports.parseEditorDocument = _chunkSJ2CPTQCcjs.parseEditorDocument; exports.persistEditorAssets = _chunkSJ2CPTQCcjs.persistEditorAssets; exports.preloadEditorAssets = _chunkSJ2CPTQCcjs.preloadEditorAssets; exports.recoveryMetaKey = _chunkT2HUQBRCcjs.recoveryMetaKey; exports.serializeEditorDocument = _chunkSJ2CPTQCcjs.serializeEditorDocument; exports.slugifyDocumentName = _chunkT2HUQBRCcjs.slugifyDocumentName; exports.toEditorDocumentFile = _chunkSJ2CPTQCcjs.toEditorDocumentFile; exports.tryCollectEditorAssetGarbage = _chunkSJ2CPTQCcjs.tryCollectEditorAssetGarbage; exports.withEditorAssets = _chunkSJ2CPTQCcjs.withEditorAssets; exports.withStorageFailures = _chunkT2HUQBRCcjs.withStorageFailures;
97
+
98
+
99
+ exports.EDITOR_ASSET_PREFIX = _chunkSJ2CPTQCcjs.EDITOR_ASSET_PREFIX; exports.EDITOR_DOCUMENT_FORMAT = _chunkSJ2CPTQCcjs.EDITOR_DOCUMENT_FORMAT; exports.EditorDocumentLoadError = _chunkSJ2CPTQCcjs.EditorDocumentLoadError; exports.EditorRemoteConflictError = _chunkDK33HLA2cjs.EditorRemoteConflictError; exports.InjectedStorageError = _chunkDK33HLA2cjs.InjectedStorageError; exports.StorageUnavailableError = _chunkDK33HLA2cjs.StorageUnavailableError; exports.collectAssetRefs = _chunkSJ2CPTQCcjs.collectAssetRefs; exports.collectEditorAssetGarbage = _chunkSJ2CPTQCcjs.collectEditorAssetGarbage; exports.collectRetiredEditorAssets = _chunkSJ2CPTQCcjs.collectRetiredEditorAssets; exports.createAutosaveController = _chunkDK33HLA2cjs.createAutosaveController; exports.createDocumentToolProvider = _chunkDK33HLA2cjs.createDocumentToolProvider; exports.createEditorAssetRegistry = _chunkSJ2CPTQCcjs.createEditorAssetRegistry; exports.createEditorAutosave = _chunkDK33HLA2cjs.createEditorAutosave; exports.createEditorDocumentStore = _chunkDK33HLA2cjs.createEditorDocumentStore; exports.createEditorRemoteSync = _chunkDK33HLA2cjs.createEditorRemoteSync; exports.createIndexedDbAdapter = _chunkDK33HLA2cjs.createIndexedDbAdapter; exports.createLoadReport = _chunkSJ2CPTQCcjs.createLoadReport; exports.createLocalStorageAdapter = _chunkDK33HLA2cjs.createLocalStorageAdapter; exports.createMemoryStorage = _chunkDK33HLA2cjs.createMemoryStorage; exports.createOpfsAdapter = _chunkDK33HLA2cjs.createOpfsAdapter; exports.createRecoveryPolicy = _chunkDK33HLA2cjs.createRecoveryPolicy; exports.createRevisionTracker = _chunkDK33HLA2cjs.createRevisionTracker; exports.defineEditorDocument = _chunkSJ2CPTQCcjs.defineEditorDocument; exports.exponentialBackoff = _chunkDK33HLA2cjs.exponentialBackoff; exports.externalizeAssets = _chunkSJ2CPTQCcjs.externalizeAssets; exports.formatRecoveryMessage = _chunkDK33HLA2cjs.formatRecoveryMessage; exports.hasLoadProblems = _chunkSJ2CPTQCcjs.hasLoadProblems; exports.internalizeAssets = _chunkSJ2CPTQCcjs.internalizeAssets; exports.isEditorDocumentFile = _chunkSJ2CPTQCcjs.isEditorDocumentFile; exports.isIndexedDbAvailable = _chunkDK33HLA2cjs.isIndexedDbAvailable; exports.isInlineAsset = _chunkSJ2CPTQCcjs.isInlineAsset; exports.isLocalStorageAvailable = _chunkDK33HLA2cjs.isLocalStorageAvailable; exports.isOpfsAvailable = _chunkDK33HLA2cjs.isOpfsAvailable; exports.loadDocument = _chunkSJ2CPTQCcjs.loadDocument; exports.loadSections = _chunkSJ2CPTQCcjs.loadSections; exports.migrateDocument = _chunkSJ2CPTQCcjs.migrateDocument; exports.parseEditorDocument = _chunkSJ2CPTQCcjs.parseEditorDocument; exports.persistEditorAssets = _chunkSJ2CPTQCcjs.persistEditorAssets; exports.preloadEditorAssets = _chunkSJ2CPTQCcjs.preloadEditorAssets; exports.recoveryMetaKey = _chunkDK33HLA2cjs.recoveryMetaKey; exports.serializeEditorDocument = _chunkSJ2CPTQCcjs.serializeEditorDocument; exports.slugifyDocumentName = _chunkDK33HLA2cjs.slugifyDocumentName; exports.toEditorDocumentFile = _chunkSJ2CPTQCcjs.toEditorDocumentFile; exports.tryCollectEditorAssetGarbage = _chunkSJ2CPTQCcjs.tryCollectEditorAssetGarbage; exports.withEditorAssets = _chunkSJ2CPTQCcjs.withEditorAssets; exports.withStorageFailures = _chunkDK33HLA2cjs.withStorageFailures;
@@ -5,6 +5,7 @@ export * from "./migrate.cjs";
5
5
  export * from "./revisions.cjs";
6
6
  export * from "./editorDocument.cjs";
7
7
  export * from "./editorAutosave.cjs";
8
+ export * from "./remoteSync.cjs";
8
9
  export * from "./documentStore.cjs";
9
10
  export * from "./documentTools.cjs";
10
11
  export * from "./assets.cjs";
@@ -5,6 +5,7 @@ export * from "./migrate.js";
5
5
  export * from "./revisions.js";
6
6
  export * from "./editorDocument.js";
7
7
  export * from "./editorAutosave.js";
8
+ export * from "./remoteSync.js";
8
9
  export * from "./documentStore.js";
9
10
  export * from "./documentTools.js";
10
11
  export * from "./assets.js";
@@ -1,10 +1,12 @@
1
1
  import {
2
+ EditorRemoteConflictError,
2
3
  InjectedStorageError,
3
4
  StorageUnavailableError,
4
5
  createAutosaveController,
5
6
  createDocumentToolProvider,
6
7
  createEditorAutosave,
7
8
  createEditorDocumentStore,
9
+ createEditorRemoteSync,
8
10
  createIndexedDbAdapter,
9
11
  createLocalStorageAdapter,
10
12
  createMemoryStorage,
@@ -19,7 +21,7 @@ import {
19
21
  recoveryMetaKey,
20
22
  slugifyDocumentName,
21
23
  withStorageFailures
22
- } from "../chunk-JV7DKLMX.js";
24
+ } from "../chunk-KBH6CM3U.js";
23
25
  import {
24
26
  EDITOR_ASSET_PREFIX,
25
27
  EDITOR_DOCUMENT_FORMAT,
@@ -51,6 +53,7 @@ export {
51
53
  EDITOR_ASSET_PREFIX,
52
54
  EDITOR_DOCUMENT_FORMAT,
53
55
  EditorDocumentLoadError,
56
+ EditorRemoteConflictError,
54
57
  InjectedStorageError,
55
58
  StorageUnavailableError,
56
59
  collectAssetRefs,
@@ -61,6 +64,7 @@ export {
61
64
  createEditorAssetRegistry,
62
65
  createEditorAutosave,
63
66
  createEditorDocumentStore,
67
+ createEditorRemoteSync,
64
68
  createIndexedDbAdapter,
65
69
  createLoadReport,
66
70
  createLocalStorageAdapter,
@@ -0,0 +1,152 @@
1
+ import type { EditorDocument } from "./editorDocument.cjs";
2
+ import type { EditorAutosave } from "./editorAutosave.cjs";
3
+ export type EditorRemoteSyncState = "idle" | "pending" | "saving" | "saved" | "error";
4
+ export interface EditorRemoteSyncStatus {
5
+ status: EditorRemoteSyncState;
6
+ version: number;
7
+ savedVersion: number;
8
+ dirty: boolean;
9
+ lastSavedAt: number | null;
10
+ error?: unknown;
11
+ }
12
+ export interface EditorRemoteSaveContext<TDocument = unknown, TDiff = unknown> {
13
+ document: TDocument;
14
+ /** The version to write. */
15
+ version: number;
16
+ /**
17
+ * The server version this save builds on (the last one confirmed). For a compare-and-set
18
+ * write, update only where the stored version still equals it, and return `{ conflict: true }`
19
+ * when nothing was updated.
20
+ */
21
+ baseVersion: number;
22
+ /** Changes since the last saved snapshot (with the `diff` option). */
23
+ diff?: TDiff;
24
+ }
25
+ export interface EditorRemoteSaveResult {
26
+ version?: number;
27
+ /**
28
+ * The server holds a newer version than `baseVersion`, so nothing was written. The controller
29
+ * calls `onConflict` (or reports an `EditorRemoteConflictError` without it) and keeps the edits.
30
+ */
31
+ conflict?: boolean;
32
+ }
33
+ export interface EditorRemoteConflictContext<TDocument = unknown> {
34
+ /** What this session tried to save. */
35
+ document: TDocument;
36
+ /** The last snapshot the server confirmed: the common base for a three-way merge. */
37
+ base: TDocument;
38
+ /** The server version reported with the conflict, when `save` returned one. */
39
+ serverVersion?: number;
40
+ /**
41
+ * Runs `fn` (e.g. loading the merged document into the editor) without it counting as a local
42
+ * change, so it is not broadcast as one; the retry save picks the result up.
43
+ */
44
+ apply<TResult>(fn: () => TResult): TResult;
45
+ }
46
+ export interface EditorRemoteConflictResolution<TDocument = unknown> {
47
+ /** The server's version the merge was made on; the retry save builds on it. */
48
+ serverVersion: number;
49
+ /** The server's document, used as the base for the retry's `diff` (default: the old base). */
50
+ serverDocument?: TDocument;
51
+ }
52
+ /** Reported through `onError` / `status().error` when a save conflicts and is not resolved. */
53
+ export declare class EditorRemoteConflictError extends Error {
54
+ readonly serverVersion?: number | undefined;
55
+ constructor(serverVersion?: number | undefined);
56
+ }
57
+ export interface EditorRemoteLocalChangeEvent<TDocument = unknown, TDiff = unknown> {
58
+ document: TDocument;
59
+ baseline: TDocument;
60
+ diff?: TDiff;
61
+ version: number;
62
+ }
63
+ export interface EditorRemoteSyncOptions<TDocument = unknown, TDiff = unknown> {
64
+ /**
65
+ * Persists the latest document snapshot and version to the remote backend.
66
+ * May return `{ version }` or a numeric `version` if the server advanced the version higher.
67
+ */
68
+ save: (context: EditorRemoteSaveContext<TDocument, TDiff>) => Promise<EditorRemoteSaveResult | number | void>;
69
+ /** Initial known server version of the document (default `0`). */
70
+ initialVersion?: number;
71
+ /** Debounce delay in milliseconds after a local edit before calling `save` (default `500`). */
72
+ debounceMs?: number;
73
+ /**
74
+ * Optional diff calculator. `onLocalChange` receives the diff from the previous local snapshot
75
+ * (the one edit); `save` receives the diff from the last saved snapshot (every edit it covers).
76
+ */
77
+ diff?: (current: TDocument, baseline: TDocument) => TDiff;
78
+ /**
79
+ * Optional predicate returning `true` when `diff` represents no meaningful change.
80
+ * When omitted, compares `JSON.stringify(current) === JSON.stringify(baseline)`.
81
+ */
82
+ isEmptyDiff?: (diff: TDiff, current: TDocument, baseline: TDocument) => boolean;
83
+ /**
84
+ * Invoked immediately whenever a local document change is detected (before `save` debounce),
85
+ * e.g. for broadcasting incremental diffs to realtime collaborators.
86
+ */
87
+ onLocalChange?: (event: EditorRemoteLocalChangeEvent<TDocument, TDiff>) => void;
88
+ /** Invoked when a remote `save` completes. */
89
+ onSaved?: (event: {
90
+ document: TDocument;
91
+ version: number;
92
+ }) => void;
93
+ /** Invoked when a remote `save` throws. */
94
+ onError?: (error: unknown) => void;
95
+ /**
96
+ * Resolves a conflicting save (`save` returned `{ conflict: true }`): fetch the server's
97
+ * document, merge this session's edits into it (`base` → `document`), load the result with
98
+ * `apply`, and return the server's version. The controller then saves again on top of it.
99
+ */
100
+ onConflict?: (context: EditorRemoteConflictContext<TDocument>) => Promise<EditorRemoteConflictResolution<TDocument>> | EditorRemoteConflictResolution<TDocument>;
101
+ /** Conflicts resolved in a row before the controller gives up with an error (default `3`). */
102
+ maxConflictRetries?: number;
103
+ /**
104
+ * Optional crash-recovery autosave instance. When a remote save succeeds and no newer
105
+ * local edits occurred while it was in flight, `autosave.markClean()` is called automatically.
106
+ */
107
+ autosave?: Pick<EditorAutosave, "markClean">;
108
+ /** Wall clock (default `Date.now`). */
109
+ now?: () => number;
110
+ }
111
+ export interface EditorRemoteSync<TDocument = unknown, TDiff = unknown> {
112
+ /** Current local version number. */
113
+ version(): number;
114
+ /** Last version confirmed saved by the remote server. */
115
+ savedVersion(): number;
116
+ /** Whether local changes are waiting to be saved or currently saving. */
117
+ dirty(): boolean;
118
+ /** Current sync status snapshot. */
119
+ status(): EditorRemoteSyncStatus;
120
+ /** Last committed local baseline snapshot. */
121
+ baseline(): TDocument;
122
+ /** Checks the document for changes against `baseline()` (called automatically via `doc.subscribe`). */
123
+ notify(): boolean;
124
+ /**
125
+ * Runs `fn` while suppressing local change detection (so incoming collaborator diffs or
126
+ * server loads do not echo back a save), then updates `baseline` and advances `version` /
127
+ * `savedVersion` to `Math.max(version, remoteVersion)`.
128
+ */
129
+ applyRemote<TResult>(fn: () => TResult, remoteVersion?: number): TResult;
130
+ /** Resets the baseline to `doc.getDocument()` and updates `version` / `savedVersion`. */
131
+ resetBaseline(remoteVersion?: number): void;
132
+ /** Cancels any debounce timer and immediately saves pending changes. */
133
+ flush(): Promise<boolean>;
134
+ /** Resolves when any pending or in-flight save has settled. */
135
+ whenIdle(): Promise<void>;
136
+ /** Subscribes to status changes. */
137
+ subscribe(listener: (status: EditorRemoteSyncStatus) => void): () => void;
138
+ /** Cancels timers and unsubscribes from `doc`, without discarding in-flight work. */
139
+ destroy(): void;
140
+ }
141
+ /**
142
+ * Reactive, version-safe remote persistence controller for an `EditorDocument`.
143
+ *
144
+ * - Subscribes to `doc` so every edit (history steps, undo/redo, node drags/resizes, inline
145
+ * node data updates, and companion `extras` changes) automatically diffs against the last
146
+ * baseline, emits `onLocalChange` for realtime collaboration, and schedules a debounced save.
147
+ * - Serializes remote `save` calls so two saves never overlap on the wire; edits made while a
148
+ * save is in flight are coalesced into a single follow-up save.
149
+ * - Advances versions monotonically (`Math.max(localVersion, remoteVersion) + 1`), preventing
150
+ * version-mismatch wedging when collaborators or server triggers advance the remote version.
151
+ */
152
+ export declare function createEditorRemoteSync<TDocument = unknown, TDiff = unknown>(doc: EditorDocument<TDocument>, options: EditorRemoteSyncOptions<TDocument, TDiff>): EditorRemoteSync<TDocument, TDiff>;
@@ -0,0 +1,152 @@
1
+ import type { EditorDocument } from "./editorDocument.js";
2
+ import type { EditorAutosave } from "./editorAutosave.js";
3
+ export type EditorRemoteSyncState = "idle" | "pending" | "saving" | "saved" | "error";
4
+ export interface EditorRemoteSyncStatus {
5
+ status: EditorRemoteSyncState;
6
+ version: number;
7
+ savedVersion: number;
8
+ dirty: boolean;
9
+ lastSavedAt: number | null;
10
+ error?: unknown;
11
+ }
12
+ export interface EditorRemoteSaveContext<TDocument = unknown, TDiff = unknown> {
13
+ document: TDocument;
14
+ /** The version to write. */
15
+ version: number;
16
+ /**
17
+ * The server version this save builds on (the last one confirmed). For a compare-and-set
18
+ * write, update only where the stored version still equals it, and return `{ conflict: true }`
19
+ * when nothing was updated.
20
+ */
21
+ baseVersion: number;
22
+ /** Changes since the last saved snapshot (with the `diff` option). */
23
+ diff?: TDiff;
24
+ }
25
+ export interface EditorRemoteSaveResult {
26
+ version?: number;
27
+ /**
28
+ * The server holds a newer version than `baseVersion`, so nothing was written. The controller
29
+ * calls `onConflict` (or reports an `EditorRemoteConflictError` without it) and keeps the edits.
30
+ */
31
+ conflict?: boolean;
32
+ }
33
+ export interface EditorRemoteConflictContext<TDocument = unknown> {
34
+ /** What this session tried to save. */
35
+ document: TDocument;
36
+ /** The last snapshot the server confirmed: the common base for a three-way merge. */
37
+ base: TDocument;
38
+ /** The server version reported with the conflict, when `save` returned one. */
39
+ serverVersion?: number;
40
+ /**
41
+ * Runs `fn` (e.g. loading the merged document into the editor) without it counting as a local
42
+ * change, so it is not broadcast as one; the retry save picks the result up.
43
+ */
44
+ apply<TResult>(fn: () => TResult): TResult;
45
+ }
46
+ export interface EditorRemoteConflictResolution<TDocument = unknown> {
47
+ /** The server's version the merge was made on; the retry save builds on it. */
48
+ serverVersion: number;
49
+ /** The server's document, used as the base for the retry's `diff` (default: the old base). */
50
+ serverDocument?: TDocument;
51
+ }
52
+ /** Reported through `onError` / `status().error` when a save conflicts and is not resolved. */
53
+ export declare class EditorRemoteConflictError extends Error {
54
+ readonly serverVersion?: number | undefined;
55
+ constructor(serverVersion?: number | undefined);
56
+ }
57
+ export interface EditorRemoteLocalChangeEvent<TDocument = unknown, TDiff = unknown> {
58
+ document: TDocument;
59
+ baseline: TDocument;
60
+ diff?: TDiff;
61
+ version: number;
62
+ }
63
+ export interface EditorRemoteSyncOptions<TDocument = unknown, TDiff = unknown> {
64
+ /**
65
+ * Persists the latest document snapshot and version to the remote backend.
66
+ * May return `{ version }` or a numeric `version` if the server advanced the version higher.
67
+ */
68
+ save: (context: EditorRemoteSaveContext<TDocument, TDiff>) => Promise<EditorRemoteSaveResult | number | void>;
69
+ /** Initial known server version of the document (default `0`). */
70
+ initialVersion?: number;
71
+ /** Debounce delay in milliseconds after a local edit before calling `save` (default `500`). */
72
+ debounceMs?: number;
73
+ /**
74
+ * Optional diff calculator. `onLocalChange` receives the diff from the previous local snapshot
75
+ * (the one edit); `save` receives the diff from the last saved snapshot (every edit it covers).
76
+ */
77
+ diff?: (current: TDocument, baseline: TDocument) => TDiff;
78
+ /**
79
+ * Optional predicate returning `true` when `diff` represents no meaningful change.
80
+ * When omitted, compares `JSON.stringify(current) === JSON.stringify(baseline)`.
81
+ */
82
+ isEmptyDiff?: (diff: TDiff, current: TDocument, baseline: TDocument) => boolean;
83
+ /**
84
+ * Invoked immediately whenever a local document change is detected (before `save` debounce),
85
+ * e.g. for broadcasting incremental diffs to realtime collaborators.
86
+ */
87
+ onLocalChange?: (event: EditorRemoteLocalChangeEvent<TDocument, TDiff>) => void;
88
+ /** Invoked when a remote `save` completes. */
89
+ onSaved?: (event: {
90
+ document: TDocument;
91
+ version: number;
92
+ }) => void;
93
+ /** Invoked when a remote `save` throws. */
94
+ onError?: (error: unknown) => void;
95
+ /**
96
+ * Resolves a conflicting save (`save` returned `{ conflict: true }`): fetch the server's
97
+ * document, merge this session's edits into it (`base` → `document`), load the result with
98
+ * `apply`, and return the server's version. The controller then saves again on top of it.
99
+ */
100
+ onConflict?: (context: EditorRemoteConflictContext<TDocument>) => Promise<EditorRemoteConflictResolution<TDocument>> | EditorRemoteConflictResolution<TDocument>;
101
+ /** Conflicts resolved in a row before the controller gives up with an error (default `3`). */
102
+ maxConflictRetries?: number;
103
+ /**
104
+ * Optional crash-recovery autosave instance. When a remote save succeeds and no newer
105
+ * local edits occurred while it was in flight, `autosave.markClean()` is called automatically.
106
+ */
107
+ autosave?: Pick<EditorAutosave, "markClean">;
108
+ /** Wall clock (default `Date.now`). */
109
+ now?: () => number;
110
+ }
111
+ export interface EditorRemoteSync<TDocument = unknown, TDiff = unknown> {
112
+ /** Current local version number. */
113
+ version(): number;
114
+ /** Last version confirmed saved by the remote server. */
115
+ savedVersion(): number;
116
+ /** Whether local changes are waiting to be saved or currently saving. */
117
+ dirty(): boolean;
118
+ /** Current sync status snapshot. */
119
+ status(): EditorRemoteSyncStatus;
120
+ /** Last committed local baseline snapshot. */
121
+ baseline(): TDocument;
122
+ /** Checks the document for changes against `baseline()` (called automatically via `doc.subscribe`). */
123
+ notify(): boolean;
124
+ /**
125
+ * Runs `fn` while suppressing local change detection (so incoming collaborator diffs or
126
+ * server loads do not echo back a save), then updates `baseline` and advances `version` /
127
+ * `savedVersion` to `Math.max(version, remoteVersion)`.
128
+ */
129
+ applyRemote<TResult>(fn: () => TResult, remoteVersion?: number): TResult;
130
+ /** Resets the baseline to `doc.getDocument()` and updates `version` / `savedVersion`. */
131
+ resetBaseline(remoteVersion?: number): void;
132
+ /** Cancels any debounce timer and immediately saves pending changes. */
133
+ flush(): Promise<boolean>;
134
+ /** Resolves when any pending or in-flight save has settled. */
135
+ whenIdle(): Promise<void>;
136
+ /** Subscribes to status changes. */
137
+ subscribe(listener: (status: EditorRemoteSyncStatus) => void): () => void;
138
+ /** Cancels timers and unsubscribes from `doc`, without discarding in-flight work. */
139
+ destroy(): void;
140
+ }
141
+ /**
142
+ * Reactive, version-safe remote persistence controller for an `EditorDocument`.
143
+ *
144
+ * - Subscribes to `doc` so every edit (history steps, undo/redo, node drags/resizes, inline
145
+ * node data updates, and companion `extras` changes) automatically diffs against the last
146
+ * baseline, emits `onLocalChange` for realtime collaboration, and schedules a debounced save.
147
+ * - Serializes remote `save` calls so two saves never overlap on the wire; edits made while a
148
+ * save is in flight are coalesced into a single follow-up save.
149
+ * - Advances versions monotonically (`Math.max(localVersion, remoteVersion) + 1`), preventing
150
+ * version-mismatch wedging when collaborators or server triggers advance the remote version.
151
+ */
152
+ export declare function createEditorRemoteSync<TDocument = unknown, TDiff = unknown>(doc: EditorDocument<TDocument>, options: EditorRemoteSyncOptions<TDocument, TDiff>): EditorRemoteSync<TDocument, TDiff>;