@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.
- package/dist/animation-editor/index.cjs +59 -59
- package/dist/animation-editor/index.js +3 -3
- package/dist/canvas/editorDocument.d.cts +53 -0
- package/dist/canvas/editorDocument.d.ts +53 -0
- package/dist/canvas/index.cjs +169 -22
- package/dist/canvas/index.js +148 -1
- package/dist/chart/index.cjs +1 -1
- package/dist/chart/index.js +1 -1
- package/dist/{chunk-MITQGHN3.cjs → chunk-4BMRQSPB.cjs} +15 -1
- package/dist/{chunk-T2HUQBRC.cjs → chunk-DK33HLA2.cjs} +331 -10
- package/dist/{chunk-DWPFGWW5.js → chunk-FKO3CY2C.js} +1 -1
- package/dist/{chunk-N4MCWQZ7.js → chunk-HN57I7KT.js} +1 -1
- package/dist/{chunk-4NZTK7LM.cjs → chunk-K4ANWBF7.cjs} +35 -35
- package/dist/{chunk-JV7DKLMX.js → chunk-KBH6CM3U.js} +326 -5
- package/dist/{chunk-VNF7A2JA.cjs → chunk-MNHP3AQT.cjs} +23 -23
- package/dist/{chunk-FNIWHSLI.js → chunk-NJDEKU5D.js} +1 -1
- package/dist/{chunk-CTHWXW5M.js → chunk-V6LQND7D.js} +14 -0
- package/dist/{chunk-QBZEC2JJ.cjs → chunk-WCC4NXWY.cjs} +86 -86
- package/dist/{chunk-PEQWARCP.cjs → chunk-WZYRYYXN.cjs} +29 -25
- package/dist/{chunk-HHX7ST6Z.js → chunk-YOM76H2V.js} +7 -3
- package/dist/image-editor/index.cjs +783 -783
- package/dist/image-editor/index.js +4 -4
- package/dist/paint/index.cjs +2 -2
- package/dist/paint/index.js +1 -1
- package/dist/paint/wet/gpu.d.cts +2 -0
- package/dist/paint/wet/gpu.d.ts +2 -0
- package/dist/paint-editor/index.cjs +128 -128
- package/dist/paint-editor/index.js +3 -3
- package/dist/persistence/autosave.d.cts +15 -1
- package/dist/persistence/autosave.d.ts +15 -1
- package/dist/persistence/editorAutosave.d.cts +12 -1
- package/dist/persistence/editorAutosave.d.ts +12 -1
- package/dist/persistence/index.cjs +6 -2
- package/dist/persistence/index.d.cts +1 -0
- package/dist/persistence/index.d.ts +1 -0
- package/dist/persistence/index.js +5 -1
- package/dist/persistence/remoteSync.d.cts +152 -0
- package/dist/persistence/remoteSync.d.ts +152 -0
- package/dist/vector/index.cjs +997 -997
- package/dist/vector/index.js +4 -4
- package/package.json +1 -1
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
resolveExternalAssets,
|
|
43
43
|
serializeLayers,
|
|
44
44
|
serializeLayersExternal
|
|
45
|
-
} from "../chunk-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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>;
|