@dabble/patches 0.5.22 → 0.7.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 (118) hide show
  1. package/README.md +221 -208
  2. package/dist/BaseDoc-DkP3tUhT.d.ts +206 -0
  3. package/dist/algorithms/client/applyCommittedChanges.d.ts +7 -0
  4. package/dist/algorithms/client/applyCommittedChanges.js +6 -3
  5. package/dist/algorithms/lww/consolidateOps.d.ts +40 -0
  6. package/dist/algorithms/lww/consolidateOps.js +103 -0
  7. package/dist/algorithms/lww/index.d.ts +2 -0
  8. package/dist/algorithms/lww/index.js +1 -0
  9. package/dist/algorithms/lww/mergeServerWithLocal.d.ts +22 -0
  10. package/dist/algorithms/lww/mergeServerWithLocal.js +32 -0
  11. package/dist/algorithms/server/commitChanges.d.ts +32 -8
  12. package/dist/algorithms/server/commitChanges.js +24 -10
  13. package/dist/algorithms/server/createVersion.d.ts +1 -1
  14. package/dist/algorithms/server/createVersion.js +2 -4
  15. package/dist/algorithms/server/getSnapshotAtRevision.d.ts +1 -1
  16. package/dist/algorithms/server/getStateAtRevision.d.ts +1 -1
  17. package/dist/algorithms/server/handleOfflineSessionsAndBatches.d.ts +1 -1
  18. package/dist/algorithms/server/handleOfflineSessionsAndBatches.js +5 -7
  19. package/dist/client/BaseDoc.d.ts +6 -0
  20. package/dist/client/BaseDoc.js +70 -0
  21. package/dist/client/ClientAlgorithm.d.ts +101 -0
  22. package/dist/client/ClientAlgorithm.js +0 -0
  23. package/dist/client/InMemoryStore.d.ts +5 -7
  24. package/dist/client/InMemoryStore.js +6 -35
  25. package/dist/client/IndexedDBStore.d.ts +39 -73
  26. package/dist/client/IndexedDBStore.js +17 -220
  27. package/dist/client/LWWAlgorithm.d.ts +43 -0
  28. package/dist/client/LWWAlgorithm.js +87 -0
  29. package/dist/client/LWWClientStore.d.ts +73 -0
  30. package/dist/client/LWWClientStore.js +0 -0
  31. package/dist/client/LWWDoc.d.ts +56 -0
  32. package/dist/client/LWWDoc.js +84 -0
  33. package/dist/client/LWWInMemoryStore.d.ts +88 -0
  34. package/dist/client/LWWInMemoryStore.js +208 -0
  35. package/dist/client/LWWIndexedDBStore.d.ts +91 -0
  36. package/dist/client/LWWIndexedDBStore.js +275 -0
  37. package/dist/client/OTAlgorithm.d.ts +42 -0
  38. package/dist/client/OTAlgorithm.js +113 -0
  39. package/dist/client/OTClientStore.d.ts +50 -0
  40. package/dist/client/OTClientStore.js +0 -0
  41. package/dist/client/OTDoc.d.ts +6 -0
  42. package/dist/client/OTDoc.js +97 -0
  43. package/dist/client/OTIndexedDBStore.d.ts +84 -0
  44. package/dist/client/OTIndexedDBStore.js +163 -0
  45. package/dist/client/Patches.d.ts +36 -16
  46. package/dist/client/Patches.js +60 -27
  47. package/dist/client/PatchesDoc.d.ts +4 -113
  48. package/dist/client/PatchesDoc.js +3 -153
  49. package/dist/client/PatchesStore.d.ts +8 -105
  50. package/dist/client/factories.d.ts +72 -0
  51. package/dist/client/factories.js +80 -0
  52. package/dist/client/index.d.ts +14 -5
  53. package/dist/client/index.js +9 -0
  54. package/dist/compression/index.d.ts +1 -1
  55. package/dist/data/change.js +4 -3
  56. package/dist/fractionalIndex.d.ts +67 -0
  57. package/dist/fractionalIndex.js +241 -0
  58. package/dist/index.d.ts +13 -4
  59. package/dist/index.js +1 -1
  60. package/dist/json-patch/types.d.ts +2 -0
  61. package/dist/net/PatchesClient.js +15 -15
  62. package/dist/net/PatchesSync.d.ts +24 -12
  63. package/dist/net/PatchesSync.js +56 -64
  64. package/dist/net/index.d.ts +6 -10
  65. package/dist/net/index.js +6 -1
  66. package/dist/net/protocol/JSONRPCClient.d.ts +4 -4
  67. package/dist/net/protocol/JSONRPCClient.js +6 -4
  68. package/dist/net/protocol/JSONRPCServer.d.ts +45 -9
  69. package/dist/net/protocol/JSONRPCServer.js +63 -8
  70. package/dist/net/serverContext.d.ts +38 -0
  71. package/dist/net/serverContext.js +20 -0
  72. package/dist/net/webrtc/WebRTCTransport.js +1 -1
  73. package/dist/net/websocket/AuthorizationProvider.d.ts +3 -3
  74. package/dist/net/websocket/WebSocketServer.d.ts +29 -20
  75. package/dist/net/websocket/WebSocketServer.js +23 -12
  76. package/dist/server/BranchManager.d.ts +50 -0
  77. package/dist/server/BranchManager.js +0 -0
  78. package/dist/server/CompressedStoreBackend.d.ts +8 -6
  79. package/dist/server/CompressedStoreBackend.js +3 -9
  80. package/dist/server/LWWBranchManager.d.ts +82 -0
  81. package/dist/server/LWWBranchManager.js +99 -0
  82. package/dist/server/LWWMemoryStoreBackend.d.ts +78 -0
  83. package/dist/server/LWWMemoryStoreBackend.js +191 -0
  84. package/dist/server/LWWServer.d.ts +130 -0
  85. package/dist/server/LWWServer.js +207 -0
  86. package/dist/server/{PatchesBranchManager.d.ts → OTBranchManager.d.ts} +32 -12
  87. package/dist/server/{PatchesBranchManager.js → OTBranchManager.js} +26 -42
  88. package/dist/server/OTServer.d.ts +108 -0
  89. package/dist/server/OTServer.js +141 -0
  90. package/dist/server/PatchesHistoryManager.d.ts +20 -7
  91. package/dist/server/PatchesHistoryManager.js +26 -3
  92. package/dist/server/PatchesServer.d.ts +70 -81
  93. package/dist/server/PatchesServer.js +0 -176
  94. package/dist/server/branchUtils.d.ts +82 -0
  95. package/dist/server/branchUtils.js +66 -0
  96. package/dist/server/index.d.ts +17 -6
  97. package/dist/server/index.js +33 -4
  98. package/dist/server/tombstone.d.ts +29 -0
  99. package/dist/server/tombstone.js +32 -0
  100. package/dist/server/types.d.ts +129 -27
  101. package/dist/server/utils.d.ts +12 -0
  102. package/dist/server/utils.js +23 -0
  103. package/dist/solid/context.d.ts +5 -4
  104. package/dist/solid/doc-manager.d.ts +3 -3
  105. package/dist/solid/index.d.ts +5 -4
  106. package/dist/solid/primitives.d.ts +2 -3
  107. package/dist/types.d.ts +16 -14
  108. package/dist/vue/composables.d.ts +2 -3
  109. package/dist/vue/doc-manager.d.ts +3 -3
  110. package/dist/vue/index.d.ts +5 -4
  111. package/dist/vue/provider.d.ts +5 -4
  112. package/package.json +1 -1
  113. package/dist/algorithms/client/collapsePendingChanges.d.ts +0 -30
  114. package/dist/algorithms/client/collapsePendingChanges.js +0 -78
  115. package/dist/net/websocket/RPCServer.d.ts +0 -141
  116. package/dist/net/websocket/RPCServer.js +0 -204
  117. package/dist/utils/dates.d.ts +0 -43
  118. package/dist/utils/dates.js +0 -47
@@ -1,176 +0,0 @@
1
- import "../chunk-IZ2YBCUP.js";
2
- import { commitChanges } from "../algorithms/server/commitChanges.js";
3
- import { createVersion } from "../algorithms/server/createVersion.js";
4
- import { getSnapshotAtRevision } from "../algorithms/server/getSnapshotAtRevision.js";
5
- import { getStateAtRevision } from "../algorithms/server/getStateAtRevision.js";
6
- import { applyChanges } from "../algorithms/shared/applyChanges.js";
7
- import { createChange } from "../data/change.js";
8
- import { signal } from "../event-signal.js";
9
- import { createJSONPatch } from "../json-patch/createJSONPatch.js";
10
- import { getISO } from "../utils/dates.js";
11
- import { CompressedStoreBackend } from "./CompressedStoreBackend.js";
12
- class PatchesServer {
13
- sessionTimeoutMillis;
14
- maxStorageBytes;
15
- store;
16
- /** Notifies listeners whenever a batch of changes is *successfully* committed. */
17
- onChangesCommitted = signal();
18
- /** Notifies listeners when a document is deleted. */
19
- onDocDeleted = signal();
20
- constructor(store, options = {}) {
21
- this.sessionTimeoutMillis = (options.sessionTimeoutMinutes ?? 30) * 60 * 1e3;
22
- this.maxStorageBytes = options.maxStorageBytes;
23
- if (options.compressor) {
24
- this.store = new CompressedStoreBackend(store, options.compressor);
25
- } else {
26
- this.store = store;
27
- }
28
- }
29
- /**
30
- * Get the state of a document at a specific revision (or the latest state if no revision is provided).
31
- * @param docId - The ID of the document.
32
- * @param rev - The revision number.
33
- * @returns The state of the document at the specified revision.
34
- */
35
- async getDoc(docId, atRev) {
36
- return getStateAtRevision(this.store, docId, atRev);
37
- }
38
- /**
39
- * Get the state of a document at a specific revision.
40
- * @param docId - The ID of the document.
41
- * @param rev - The revision number.
42
- * @returns The state of the document at the specified revision.
43
- */
44
- async getStateAtRevision(docId, atRev) {
45
- return getStateAtRevision(this.store, docId, atRev);
46
- }
47
- /**
48
- * Get changes that occurred after a specific revision.
49
- * @param docId - The ID of the document.
50
- * @param rev - The revision number.
51
- * @returns The changes that occurred after the specified revision.
52
- */
53
- getChangesSince(docId, rev) {
54
- return this.store.listChanges(docId, { startAfter: rev });
55
- }
56
- /**
57
- * Commits a set of changes to a document, applying operational transformation as needed.
58
- * @param docId - The ID of the document.
59
- * @param changes - The changes to commit.
60
- * @param options - Optional commit settings (e.g., forceCommit for migrations).
61
- * @param originClientId - The ID of the client that initiated the commit (used by transport layer for broadcast filtering).
62
- * @returns A tuple of [committedChanges, transformedChanges] where:
63
- * - committedChanges: Changes that were already committed to the server after the client's base revision
64
- * - transformedChanges: The client's changes after being transformed against concurrent changes
65
- */
66
- async commitChanges(docId, changes, options, originClientId) {
67
- const [committedChanges, transformedChanges] = await commitChanges(
68
- this.store,
69
- docId,
70
- changes,
71
- this.sessionTimeoutMillis,
72
- options,
73
- this.maxStorageBytes
74
- );
75
- if (transformedChanges.length > 0) {
76
- try {
77
- await this.onChangesCommitted.emit(docId, transformedChanges, originClientId);
78
- } catch (error) {
79
- console.error(`Failed to notify clients about committed changes for doc ${docId}:`, error);
80
- }
81
- }
82
- return [committedChanges, transformedChanges];
83
- }
84
- /**
85
- * Make a server-side change to a document.
86
- * @param mutator
87
- * @returns
88
- */
89
- async change(docId, mutator, metadata) {
90
- const { state, rev } = await this.getDoc(docId);
91
- const patch = createJSONPatch(mutator);
92
- if (patch.ops.length === 0) {
93
- return null;
94
- }
95
- const change = createChange(rev, rev + 1, patch.ops, metadata);
96
- patch.apply(state);
97
- await this.commitChanges(docId, [change]);
98
- return change;
99
- }
100
- /**
101
- * Deletes a document.
102
- * @param docId The document ID.
103
- * @param originClientId - The ID of the client that initiated the delete operation.
104
- * @param options - Optional deletion settings (e.g., skipTombstone for testing).
105
- */
106
- async deleteDoc(docId, options, originClientId) {
107
- if (this.store.createTombstone && !options?.skipTombstone) {
108
- const { rev: lastRev } = await this.getDoc(docId);
109
- await this.store.createTombstone({
110
- docId,
111
- deletedAt: getISO(),
112
- lastRev,
113
- deletedByClientId: originClientId
114
- });
115
- }
116
- await this.store.deleteDoc(docId);
117
- await this.onDocDeleted.emit(docId, options, originClientId);
118
- }
119
- /**
120
- * Removes the tombstone for a deleted document, allowing it to be recreated.
121
- * @param docId The document ID.
122
- * @returns True if tombstone was found and removed, false if no tombstone existed.
123
- */
124
- async undeleteDoc(docId) {
125
- if (!this.store.removeTombstone) {
126
- return false;
127
- }
128
- const tombstone = await this.store.getTombstone?.(docId);
129
- if (!tombstone) {
130
- return false;
131
- }
132
- await this.store.removeTombstone(docId);
133
- return true;
134
- }
135
- // === Version Operations ===
136
- /**
137
- * Captures the current state of a document as a new version.
138
- * @param docId The document ID.
139
- * @param metadata Optional metadata for the version.
140
- * @returns The ID of the created version.
141
- */
142
- async captureCurrentVersion(docId, metadata) {
143
- assertVersionMetadata(metadata);
144
- const { state: initialState, changes } = await getSnapshotAtRevision(this.store, docId);
145
- let state = initialState;
146
- state = applyChanges(state, changes);
147
- const version = await createVersion(this.store, docId, state, changes, metadata);
148
- if (!version) {
149
- return null;
150
- }
151
- return version.id;
152
- }
153
- }
154
- const nonModifiableMetadataFields = /* @__PURE__ */ new Set([
155
- "id",
156
- "parentId",
157
- "groupId",
158
- "origin",
159
- "branchName",
160
- "startedAt",
161
- "endedAt",
162
- "rev",
163
- "baseRev"
164
- ]);
165
- function assertVersionMetadata(metadata) {
166
- if (!metadata) return;
167
- for (const key in metadata) {
168
- if (nonModifiableMetadataFields.has(key)) {
169
- throw new Error(`Cannot modify version field ${key}`);
170
- }
171
- }
172
- }
173
- export {
174
- PatchesServer,
175
- assertVersionMetadata
176
- };
@@ -0,0 +1,82 @@
1
+ import { ApiDefinition } from '../net/protocol/JSONRPCServer.js';
2
+ import { EditableBranchMetadata, Branch, BranchStatus } from '../types.js';
3
+ import '../event-signal.js';
4
+ import '../net/websocket/AuthorizationProvider.js';
5
+ import './types.js';
6
+ import '../json-patch/types.js';
7
+ import '../json-patch/JSONPatch.js';
8
+ import '@dabble/delta';
9
+ import '../net/protocol/types.js';
10
+
11
+ /**
12
+ * Standard API definition for branch managers.
13
+ * Used with JSONRPCServer.register() to expose branch methods.
14
+ */
15
+ declare const branchManagerApi: ApiDefinition;
16
+ /**
17
+ * Validates that branch metadata doesn't contain non-modifiable fields.
18
+ * @throws Error if metadata contains protected fields.
19
+ */
20
+ declare function assertBranchMetadata(metadata?: EditableBranchMetadata): void;
21
+ /**
22
+ * Store interface for branch ID generation.
23
+ */
24
+ interface BranchIdGenerator {
25
+ createBranchId?: (docId: string) => Promise<string> | string;
26
+ }
27
+ /**
28
+ * Generates a branch ID, using the store's custom generator if available.
29
+ * @param store - Store that may have a custom createBranchId method.
30
+ * @param docId - The source document ID.
31
+ * @returns A unique branch document ID.
32
+ */
33
+ declare function generateBranchId(store: BranchIdGenerator, docId: string): Promise<string>;
34
+ /**
35
+ * Creates a Branch record with standard fields.
36
+ * @param branchDocId - The branch document ID.
37
+ * @param sourceDocId - The source document being branched from.
38
+ * @param branchedAtRev - The revision at which the branch was created.
39
+ * @param metadata - Optional branch metadata (name, etc.).
40
+ * @returns A new Branch object.
41
+ */
42
+ declare function createBranchRecord(branchDocId: string, sourceDocId: string, branchedAtRev: number, metadata?: EditableBranchMetadata): Branch;
43
+ /**
44
+ * Store interface for branch loading.
45
+ */
46
+ interface BranchLoader {
47
+ loadBranch(docId: string): Promise<Branch | null>;
48
+ }
49
+ /**
50
+ * Validates that a document is not already a branch (prevents branch-of-branch).
51
+ * @param store - Store with loadBranch capability.
52
+ * @param docId - The document ID to check.
53
+ * @throws Error if the document is already a branch.
54
+ */
55
+ declare function assertNotABranch(store: BranchLoader, docId: string): Promise<void>;
56
+ /**
57
+ * Validates that a branch exists and is open for merging.
58
+ * @param branch - The branch to validate (may be null).
59
+ * @param branchId - The branch ID (for error messages).
60
+ * @throws Error if branch not found or not open.
61
+ */
62
+ declare function assertBranchOpenForMerge(branch: Branch | null, branchId: string): asserts branch is Branch;
63
+ /**
64
+ * Wraps a merge commit operation with standard error handling.
65
+ * @param branchId - The branch being merged.
66
+ * @param sourceDocId - The source document being merged into.
67
+ * @param commitFn - The async function that performs the commit.
68
+ * @returns The result of the commit function.
69
+ * @throws Error with standardized message if commit fails.
70
+ */
71
+ declare function wrapMergeCommit<T>(branchId: string, sourceDocId: string, commitFn: () => Promise<T>): Promise<T>;
72
+ /**
73
+ * Standard close branch operation.
74
+ * @param store - Store with updateBranch capability.
75
+ * @param branchId - The branch to close.
76
+ * @param status - The status to set (defaults to 'closed').
77
+ */
78
+ declare function closeBranch(store: {
79
+ updateBranch(branchId: string, updates: Partial<Pick<Branch, 'status' | 'name'>>): Promise<void>;
80
+ }, branchId: string, status?: Exclude<BranchStatus, 'open'>): Promise<void>;
81
+
82
+ export { type BranchIdGenerator, type BranchLoader, assertBranchMetadata, assertBranchOpenForMerge, assertNotABranch, branchManagerApi, closeBranch, createBranchRecord, generateBranchId, wrapMergeCommit };
@@ -0,0 +1,66 @@
1
+ import "../chunk-IZ2YBCUP.js";
2
+ import { createId } from "crypto-id";
3
+ const branchManagerApi = {
4
+ listBranches: "read",
5
+ createBranch: "write",
6
+ updateBranch: "write",
7
+ closeBranch: "write",
8
+ mergeBranch: "write"
9
+ };
10
+ const nonModifiableBranchFields = /* @__PURE__ */ new Set(["id", "docId", "branchedAtRev", "createdAt", "status"]);
11
+ function assertBranchMetadata(metadata) {
12
+ if (!metadata) return;
13
+ for (const key in metadata) {
14
+ if (nonModifiableBranchFields.has(key)) {
15
+ throw new Error(`Cannot modify branch field ${key}`);
16
+ }
17
+ }
18
+ }
19
+ async function generateBranchId(store, docId) {
20
+ return store.createBranchId ? await Promise.resolve(store.createBranchId(docId)) : createId(22);
21
+ }
22
+ function createBranchRecord(branchDocId, sourceDocId, branchedAtRev, metadata) {
23
+ return {
24
+ ...metadata,
25
+ id: branchDocId,
26
+ docId: sourceDocId,
27
+ branchedAtRev,
28
+ createdAt: Date.now(),
29
+ status: "open"
30
+ };
31
+ }
32
+ async function assertNotABranch(store, docId) {
33
+ const maybeBranch = await store.loadBranch(docId);
34
+ if (maybeBranch) {
35
+ throw new Error("Cannot create a branch from another branch.");
36
+ }
37
+ }
38
+ function assertBranchOpenForMerge(branch, branchId) {
39
+ if (!branch) {
40
+ throw new Error(`Branch with ID ${branchId} not found.`);
41
+ }
42
+ if (branch.status !== "open") {
43
+ throw new Error(`Branch ${branchId} is not open (status: ${branch.status}). Cannot merge.`);
44
+ }
45
+ }
46
+ async function wrapMergeCommit(branchId, sourceDocId, commitFn) {
47
+ try {
48
+ return await commitFn();
49
+ } catch (error) {
50
+ console.error(`Failed to merge branch ${branchId} into ${sourceDocId}:`, error);
51
+ throw new Error(`Merge failed: ${error instanceof Error ? error.message : String(error)}`);
52
+ }
53
+ }
54
+ async function closeBranch(store, branchId, status = "closed") {
55
+ await store.updateBranch(branchId, { status });
56
+ }
57
+ export {
58
+ assertBranchMetadata,
59
+ assertBranchOpenForMerge,
60
+ assertNotABranch,
61
+ branchManagerApi,
62
+ closeBranch,
63
+ createBranchRecord,
64
+ generateBranchId,
65
+ wrapMergeCommit
66
+ };
@@ -1,12 +1,23 @@
1
- export { CompressedStoreBackend } from './CompressedStoreBackend.js';
2
- export { PatchesBranchManager } from './PatchesBranchManager.js';
1
+ export { LWWServer, LWWServerOptions } from './LWWServer.js';
2
+ export { OTServer, OTServerOptions } from './OTServer.js';
3
+ export { BranchManager } from './BranchManager.js';
4
+ export { LWWBranchManager } from './LWWBranchManager.js';
5
+ export { OTBranchManager, PatchesBranchManager } from './OTBranchManager.js';
6
+ export { LWWMemoryStoreBackend } from './LWWMemoryStoreBackend.js';
3
7
  export { PatchesHistoryManager } from './PatchesHistoryManager.js';
4
- export { PatchesServer, PatchesServerOptions } from './PatchesServer.js';
5
- export { BranchingStoreBackend, PatchesStoreBackend } from './types.js';
8
+ export { BranchIdGenerator, BranchLoader, assertBranchMetadata, assertBranchOpenForMerge, assertNotABranch, branchManagerApi, createBranchRecord, generateBranchId, wrapMergeCommit } from './branchUtils.js';
9
+ export { CompressedStoreBackend } from './CompressedStoreBackend.js';
10
+ export { createTombstoneIfSupported, isTombstoneStore, removeTombstoneIfExists } from './tombstone.js';
11
+ export { assertVersionMetadata } from './utils.js';
6
12
  export { CommitChangesOptions, DeleteDocOptions } from '../types.js';
13
+ export { PatchesServer } from './PatchesServer.js';
14
+ export { BranchingStoreBackend, LWWStoreBackend, LWWVersioningStoreBackend, ListFieldsOptions, OTStoreBackend, PatchesStoreBackend, ServerStoreBackend, TombstoneStoreBackend, VersioningStoreBackend } from './types.js';
15
+ import '../event-signal.js';
16
+ import '../net/protocol/JSONRPCServer.js';
17
+ import '../net/websocket/AuthorizationProvider.js';
18
+ import '../net/protocol/types.js';
19
+ import '../json-patch/types.js';
7
20
  import '../compression/index.js';
8
21
  import '../algorithms/shared/lz.js';
9
- import '../json-patch/types.js';
10
- import '../event-signal.js';
11
22
  import '../json-patch/JSONPatch.js';
12
23
  import '@dabble/delta';
@@ -1,11 +1,40 @@
1
1
  import "../chunk-IZ2YBCUP.js";
2
- import { CompressedStoreBackend } from "./CompressedStoreBackend.js";
3
- import { PatchesBranchManager } from "./PatchesBranchManager.js";
2
+ import { LWWServer } from "./LWWServer.js";
3
+ import { OTServer } from "./OTServer.js";
4
+ import { LWWBranchManager } from "./LWWBranchManager.js";
5
+ import { OTBranchManager, PatchesBranchManager } from "./OTBranchManager.js";
6
+ import { LWWMemoryStoreBackend } from "./LWWMemoryStoreBackend.js";
4
7
  import { PatchesHistoryManager } from "./PatchesHistoryManager.js";
5
- import { PatchesServer } from "./PatchesServer.js";
8
+ import {
9
+ assertBranchMetadata,
10
+ assertBranchOpenForMerge,
11
+ assertNotABranch,
12
+ branchManagerApi,
13
+ createBranchRecord,
14
+ generateBranchId,
15
+ wrapMergeCommit
16
+ } from "./branchUtils.js";
17
+ import { CompressedStoreBackend } from "./CompressedStoreBackend.js";
18
+ import { createTombstoneIfSupported, isTombstoneStore, removeTombstoneIfExists } from "./tombstone.js";
19
+ import { assertVersionMetadata } from "./utils.js";
6
20
  export {
7
21
  CompressedStoreBackend,
22
+ LWWBranchManager,
23
+ LWWMemoryStoreBackend,
24
+ LWWServer,
25
+ OTBranchManager,
26
+ OTServer,
8
27
  PatchesBranchManager,
9
28
  PatchesHistoryManager,
10
- PatchesServer
29
+ assertBranchMetadata,
30
+ assertBranchOpenForMerge,
31
+ assertNotABranch,
32
+ assertVersionMetadata,
33
+ branchManagerApi,
34
+ createBranchRecord,
35
+ createTombstoneIfSupported,
36
+ generateBranchId,
37
+ isTombstoneStore,
38
+ removeTombstoneIfExists,
39
+ wrapMergeCommit
11
40
  };
@@ -0,0 +1,29 @@
1
+ import { ServerStoreBackend, TombstoneStoreBackend } from './types.js';
2
+ import '../json-patch/types.js';
3
+ import '../types.js';
4
+ import '../json-patch/JSONPatch.js';
5
+ import '@dabble/delta';
6
+
7
+ /**
8
+ * Type guard to check if a store implements TombstoneStoreBackend.
9
+ */
10
+ declare function isTombstoneStore(store: ServerStoreBackend): store is ServerStoreBackend & TombstoneStoreBackend;
11
+ /**
12
+ * Creates a tombstone for a document if the store supports it.
13
+ * @param store - The store backend
14
+ * @param docId - The document ID
15
+ * @param lastRev - The last revision before deletion
16
+ * @param clientId - The client that initiated deletion
17
+ * @param skipTombstone - If true, skip creating the tombstone
18
+ * @returns true if tombstone was created, false if skipped or store doesn't support it
19
+ */
20
+ declare function createTombstoneIfSupported(store: ServerStoreBackend, docId: string, lastRev: number, clientId?: string, skipTombstone?: boolean): Promise<boolean>;
21
+ /**
22
+ * Removes a tombstone for a document if it exists.
23
+ * @param store - The store backend
24
+ * @param docId - The document ID
25
+ * @returns true if tombstone was found and removed, false otherwise
26
+ */
27
+ declare function removeTombstoneIfExists(store: ServerStoreBackend, docId: string): Promise<boolean>;
28
+
29
+ export { createTombstoneIfSupported, isTombstoneStore, removeTombstoneIfExists };
@@ -0,0 +1,32 @@
1
+ import "../chunk-IZ2YBCUP.js";
2
+ function isTombstoneStore(store) {
3
+ return "createTombstone" in store && "getTombstone" in store && "removeTombstone" in store;
4
+ }
5
+ async function createTombstoneIfSupported(store, docId, lastRev, clientId, skipTombstone) {
6
+ if (!isTombstoneStore(store) || skipTombstone) {
7
+ return false;
8
+ }
9
+ await store.createTombstone({
10
+ docId,
11
+ deletedAt: Date.now(),
12
+ lastRev,
13
+ deletedByClientId: clientId
14
+ });
15
+ return true;
16
+ }
17
+ async function removeTombstoneIfExists(store, docId) {
18
+ if (!isTombstoneStore(store)) {
19
+ return false;
20
+ }
21
+ const tombstone = await store.getTombstone(docId);
22
+ if (!tombstone) {
23
+ return false;
24
+ }
25
+ await store.removeTombstone(docId);
26
+ return true;
27
+ }
28
+ export {
29
+ createTombstoneIfSupported,
30
+ isTombstoneStore,
31
+ removeTombstoneIfExists
32
+ };
@@ -1,41 +1,137 @@
1
- import { Change, ListChangesOptions, PatchesState, VersionMetadata, EditableVersionMetadata, ListVersionsOptions, DocumentTombstone, Branch } from '../types.js';
1
+ import { JSONPatchOp } from '../json-patch/types.js';
2
+ import { VersionMetadata, Change, ListVersionsOptions, EditableVersionMetadata, ListChangesOptions, DocumentTombstone, Branch } from '../types.js';
2
3
  import '../json-patch/JSONPatch.js';
3
4
  import '@dabble/delta';
4
- import '../json-patch/types.js';
5
5
 
6
6
  /**
7
- * Interface for a backend storage system for patch synchronization.
8
- * Defines methods needed by PatchesServer, PatchesHistoryManager, etc.
7
+ * Base interface for all server store backends.
8
+ * Provides the minimal deletion capability that all servers need.
9
9
  */
10
- interface PatchesStoreBackend {
11
- /** Saves a batch of committed server changes. */
12
- saveChanges(docId: string, changes: Change[]): Promise<void>;
13
- /** Lists committed server changes based on revision numbers. */
14
- listChanges(docId: string, options: ListChangesOptions): Promise<Change[]>;
15
- /** Loads the last version state for a document. Optional method for performance. */
16
- loadLastVersionState?: (docId: string) => Promise<PatchesState | undefined>;
17
- /** Saves the last version state for a document. Optional method for performance. */
18
- saveLastVersionState?: (docId: string, rev: number, state: any) => Promise<void>;
10
+ interface ServerStoreBackend {
11
+ /** Deletes a document and all its associated data. */
12
+ deleteDoc(docId: string): Promise<void>;
13
+ }
14
+ /**
15
+ * Interface for version storage, shared between OT and LWW.
16
+ * OT requires this, LWW can optionally implement it for user-visible versioning.
17
+ */
18
+ interface VersioningStoreBackend {
19
19
  /**
20
- * Saves version metadata, its state snapshot, and the original changes that constitute it.
20
+ * Saves version metadata, its state snapshot, and optionally the original changes.
21
21
  * State and changes are stored separately from the core metadata.
22
+ * @param changes - Optional for LWW (which doesn't store changes), required for OT.
22
23
  */
23
- createVersion(docId: string, metadata: VersionMetadata, state: any, changes: Change[]): Promise<void>;
24
- /** Update a version's metadata. */
25
- updateVersion(docId: string, versionId: string, metadata: EditableVersionMetadata): Promise<void>;
26
- /**
27
- * Appends changes to an existing version, updating its state snapshot, endedAt, and endRev.
28
- * Used when a session spans multiple batch submissions.
29
- */
30
- appendVersionChanges(docId: string, versionId: string, changes: Change[], newEndedAt: string, newEndRev: number, newState: any): Promise<void>;
24
+ createVersion(docId: string, metadata: VersionMetadata, state: any, changes?: Change[]): Promise<void>;
31
25
  /** Lists version metadata based on filtering/sorting options. */
32
26
  listVersions(docId: string, options: ListVersionsOptions): Promise<VersionMetadata[]>;
33
27
  /** Loads the state snapshot for a specific version ID. */
34
28
  loadVersionState(docId: string, versionId: string): Promise<any | undefined>;
29
+ /** Update a version's metadata. */
30
+ updateVersion(docId: string, versionId: string, metadata: EditableVersionMetadata): Promise<void>;
31
+ }
32
+ /**
33
+ * Interface for OT (Operational Transformation) storage backend.
34
+ * Extends ServerStoreBackend and VersioningStoreBackend because OT requires versioning
35
+ * for session tracking and state snapshots.
36
+ */
37
+ interface OTStoreBackend extends ServerStoreBackend, VersioningStoreBackend {
38
+ /** Saves a batch of committed server changes. */
39
+ saveChanges(docId: string, changes: Change[]): Promise<void>;
40
+ /** Lists committed server changes based on revision numbers. */
41
+ listChanges(docId: string, options: ListChangesOptions): Promise<Change[]>;
35
42
  /** Loads the original Change objects associated with a specific version ID. */
36
43
  loadVersionChanges(docId: string, versionId: string): Promise<Change[]>;
37
- /** Deletes a document. */
38
- deleteDoc(docId: string): Promise<void>;
44
+ /**
45
+ * Appends changes to an existing version, updating its state snapshot, endedAt, and endRev.
46
+ * Used when a session spans multiple batch submissions.
47
+ */
48
+ appendVersionChanges(docId: string, versionId: string, changes: Change[], newEndedAt: number, newEndRev: number, newState: any): Promise<void>;
49
+ }
50
+ /**
51
+ * Options for listing fields. Use either sinceRev OR paths, not both.
52
+ */
53
+ type ListFieldsOptions = {
54
+ sinceRev: number;
55
+ } | {
56
+ paths: string[];
57
+ };
58
+ /**
59
+ * Interface for LWW (Last-Write-Wins) storage backend.
60
+ * LWW stores fields (not changes) and reconstructs state from fields.
61
+ */
62
+ interface LWWStoreBackend extends ServerStoreBackend {
63
+ /**
64
+ * Get the current revision number without reconstructing state.
65
+ * More efficient than getSnapshot() when only the revision is needed.
66
+ * @param docId - The document ID.
67
+ * @returns The current revision number, or 0 if document doesn't exist.
68
+ */
69
+ getCurrentRev(docId: string): Promise<number>;
70
+ /**
71
+ * Get the latest snapshot of document state.
72
+ * @param docId - The document ID.
73
+ * @returns The snapshot state and revision, or null if no snapshot exists.
74
+ */
75
+ getSnapshot(docId: string): Promise<{
76
+ state: any;
77
+ rev: number;
78
+ } | null>;
79
+ /**
80
+ * Save a snapshot of document state (overwrites previous snapshot).
81
+ * @param docId - The document ID.
82
+ * @param state - The document state.
83
+ * @param rev - The revision number.
84
+ */
85
+ saveSnapshot(docId: string, state: any, rev: number): Promise<void>;
86
+ /**
87
+ * List field metadata, optionally filtered by paths or revision.
88
+ *
89
+ * Options are mutually exclusive:
90
+ * - `{ sinceRev: number }` - Get fields changed since a revision
91
+ * - `{ paths: string[] }` - Get fields at specific paths
92
+ * - No options - Get all fields
93
+ *
94
+ * @param docId - The document ID.
95
+ * @param options - Optional filter options.
96
+ * @returns Array of field metadata matching the criteria.
97
+ */
98
+ listOps(docId: string, options?: ListFieldsOptions): Promise<JSONPatchOp[]>;
99
+ /**
100
+ * Save field metadata and atomically increment the revision.
101
+ *
102
+ * Implementation requirements:
103
+ * - Atomically increment the document revision
104
+ * - Set the rev on all saved fields to the new revision
105
+ * - Delete children atomically when saving a parent (e.g., saving /obj deletes /obj/name)
106
+ * - Delete paths in pathsToDelete atomically with saving ops
107
+ *
108
+ * @param docId - The document ID.
109
+ * @param ops - Array of ops to save.
110
+ * @param pathsToDelete - Optional paths to delete atomically.
111
+ * @returns The new revision number.
112
+ */
113
+ saveOps(docId: string, ops: JSONPatchOp[], pathsToDelete?: string[]): Promise<number>;
114
+ }
115
+ /**
116
+ * Extended storage interface for LWWServer with versioning support.
117
+ * Versioning is optional - not all LWW docs need it.
118
+ */
119
+ interface LWWVersioningStoreBackend extends LWWStoreBackend {
120
+ /**
121
+ * Creates a new version snapshot.
122
+ * @param docId - The document ID.
123
+ * @param versionId - The version ID.
124
+ * @param state - The document state snapshot.
125
+ * @param rev - The revision number.
126
+ * @param metadata - Optional version metadata.
127
+ */
128
+ createVersion(docId: string, versionId: string, state: any, rev: number, metadata?: EditableVersionMetadata): Promise<void>;
129
+ }
130
+ /**
131
+ * Interface for tombstone storage, providing soft-delete capabilities.
132
+ * Optional add-on for servers that need to track deleted documents.
133
+ */
134
+ interface TombstoneStoreBackend {
39
135
  /** Creates a tombstone for a deleted document. Called before deleteDoc() to preserve deletion metadata. */
40
136
  createTombstone(tombstone: DocumentTombstone): Promise<void>;
41
137
  /** Retrieves a tombstone for a document if it exists. Returns undefined if the document was never deleted or tombstone has expired. */
@@ -44,9 +140,10 @@ interface PatchesStoreBackend {
44
140
  removeTombstone(docId: string): Promise<void>;
45
141
  }
46
142
  /**
47
- * Extends PatchesStoreBackend with methods specifically for managing branches.
143
+ * Interface for branch storage. Standalone interface that can be composed
144
+ * with OTStoreBackend or LWWStoreBackend as needed.
48
145
  */
49
- interface BranchingStoreBackend extends PatchesStoreBackend {
146
+ interface BranchingStoreBackend {
50
147
  /**
51
148
  * Generates a unique ID for a new branch document.
52
149
  * If not provided, a random 22-character ID is generated using createId().
@@ -67,5 +164,10 @@ interface BranchingStoreBackend extends PatchesStoreBackend {
67
164
  */
68
165
  closeBranch(branchId: string): Promise<void>;
69
166
  }
167
+ /**
168
+ * @deprecated Use OTStoreBackend instead.
169
+ * Alias for backwards compatibility.
170
+ */
171
+ type PatchesStoreBackend = OTStoreBackend;
70
172
 
71
- export type { BranchingStoreBackend, PatchesStoreBackend };
173
+ export type { BranchingStoreBackend, LWWStoreBackend, LWWVersioningStoreBackend, ListFieldsOptions, OTStoreBackend, PatchesStoreBackend, ServerStoreBackend, TombstoneStoreBackend, VersioningStoreBackend };
@@ -0,0 +1,12 @@
1
+ import { EditableVersionMetadata } from '../types.js';
2
+ import '../json-patch/JSONPatch.js';
3
+ import '@dabble/delta';
4
+ import '../json-patch/types.js';
5
+
6
+ /**
7
+ * Validates that version metadata does not contain non-modifiable fields.
8
+ * @throws Error if metadata contains any non-modifiable fields.
9
+ */
10
+ declare function assertVersionMetadata(metadata?: EditableVersionMetadata): void;
11
+
12
+ export { assertVersionMetadata };