@dabble/patches 0.8.8 → 0.8.10

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 (56) hide show
  1. package/dist/algorithms/ot/shared/changeBatching.js +1 -1
  2. package/dist/client/BranchClientStore.d.ts +71 -0
  3. package/dist/client/BranchClientStore.js +0 -0
  4. package/dist/client/ClientAlgorithm.d.ts +12 -0
  5. package/dist/client/IndexedDBStore.d.ts +13 -2
  6. package/dist/client/IndexedDBStore.js +125 -1
  7. package/dist/client/LWWInMemoryStore.js +5 -3
  8. package/dist/client/LWWIndexedDBStore.d.ts +1 -0
  9. package/dist/client/LWWIndexedDBStore.js +14 -5
  10. package/dist/client/OTAlgorithm.d.ts +3 -0
  11. package/dist/client/OTAlgorithm.js +4 -0
  12. package/dist/client/OTClientStore.d.ts +11 -0
  13. package/dist/client/OTIndexedDBStore.d.ts +9 -0
  14. package/dist/client/OTIndexedDBStore.js +15 -3
  15. package/dist/client/Patches.d.ts +6 -0
  16. package/dist/client/Patches.js +11 -0
  17. package/dist/client/PatchesBranchClient.d.ts +73 -12
  18. package/dist/client/PatchesBranchClient.js +143 -14
  19. package/dist/client/index.d.ts +3 -1
  20. package/dist/compression/index.d.ts +15 -7
  21. package/dist/compression/index.js +13 -2
  22. package/dist/index.d.ts +4 -2
  23. package/dist/net/PatchesClient.d.ts +13 -8
  24. package/dist/net/PatchesClient.js +17 -10
  25. package/dist/net/PatchesSync.d.ts +36 -3
  26. package/dist/net/PatchesSync.js +72 -0
  27. package/dist/net/index.d.ts +2 -1
  28. package/dist/net/protocol/types.d.ts +5 -4
  29. package/dist/net/rest/PatchesREST.d.ts +8 -5
  30. package/dist/net/rest/PatchesREST.js +30 -20
  31. package/dist/net/rest/index.d.ts +1 -1
  32. package/dist/net/rest/index.js +1 -2
  33. package/dist/net/rest/utils.d.ts +1 -9
  34. package/dist/net/rest/utils.js +0 -4
  35. package/dist/server/BranchManager.d.ts +11 -10
  36. package/dist/server/LWWBranchManager.d.ts +10 -9
  37. package/dist/server/LWWBranchManager.js +48 -31
  38. package/dist/server/LWWMemoryStoreBackend.d.ts +5 -2
  39. package/dist/server/LWWMemoryStoreBackend.js +21 -3
  40. package/dist/server/OTBranchManager.d.ts +14 -12
  41. package/dist/server/OTBranchManager.js +58 -66
  42. package/dist/server/branchUtils.d.ts +6 -15
  43. package/dist/server/branchUtils.js +16 -13
  44. package/dist/server/index.d.ts +1 -1
  45. package/dist/server/index.js +2 -2
  46. package/dist/server/types.d.ts +8 -2
  47. package/dist/solid/context.d.ts +1 -0
  48. package/dist/solid/index.d.ts +2 -1
  49. package/dist/solid/primitives.d.ts +30 -155
  50. package/dist/solid/primitives.js +53 -219
  51. package/dist/types.d.ts +35 -6
  52. package/dist/vue/composables.d.ts +29 -170
  53. package/dist/vue/composables.js +59 -200
  54. package/dist/vue/index.d.ts +2 -1
  55. package/dist/vue/provider.d.ts +1 -0
  56. package/package.json +1 -1
@@ -5,7 +5,7 @@ import { createChange } from "../data/change.js";
5
5
  import { createVersionMetadata } from "../data/version.js";
6
6
  import {
7
7
  assertBranchMetadata,
8
- assertBranchOpenForMerge,
8
+ assertBranchExists,
9
9
  assertNotABranch,
10
10
  branchManagerApi,
11
11
  createBranchRecord,
@@ -22,49 +22,55 @@ class OTBranchManager {
22
22
  /**
23
23
  * Lists all open branches for a document.
24
24
  * @param docId - The ID of the document.
25
+ * @param options - Optional filtering options (e.g. `since` for incremental sync).
25
26
  * @returns The branches.
26
27
  */
27
- async listBranches(docId) {
28
- return await this.store.listBranches(docId);
28
+ async listBranches(docId, options) {
29
+ return await this.store.listBranches(docId, options);
29
30
  }
30
31
  /**
31
32
  * Creates a new branch for a document.
32
33
  * @param docId - The ID of the document to branch from.
33
34
  * @param rev - The revision of the document to branch from.
34
- * @param metadata - Additional optional metadata to store with the branch.
35
- * @param initialChanges - Optional pre-built initialization changes. If provided, stored
36
- * directly. If omitted, a root-replace change is generated from the source state at `rev`
37
- * and split via breakChanges when maxPayloadBytes is set.
38
35
  * @returns The ID of the new branch document.
39
36
  */
40
- async createBranch(docId, rev, metadata, initialChanges) {
37
+ async createBranch(docId, rev, metadata) {
38
+ const branchDocId = metadata?.id ?? await generateBranchId(this.store, docId);
39
+ if (metadata?.id) {
40
+ const existing = await this.store.loadBranch(branchDocId);
41
+ if (existing) {
42
+ if (existing.docId !== docId) {
43
+ throw new Error(`Branch ${branchDocId} already exists for a different document`);
44
+ }
45
+ return branchDocId;
46
+ }
47
+ }
41
48
  await assertNotABranch(this.store, docId);
42
- const branchDocId = await generateBranchId(this.store, docId);
43
49
  const now = Date.now();
44
- let initChanges;
45
- if (initialChanges?.length) {
46
- initChanges = initialChanges;
50
+ let contentStartRev;
51
+ if (metadata?.contentStartRev) {
52
+ contentStartRev = metadata.contentStartRev;
47
53
  } else {
48
54
  const { state: stateAtRev } = await getStateAtRevision(this.store, docId, rev);
49
55
  const rootReplace = createChange(0, 1, [{ op: "replace", path: "", value: stateAtRev }], {
50
56
  createdAt: now,
51
57
  committedAt: now
52
58
  });
53
- initChanges = this.maxPayloadBytes ? breakChanges([rootReplace], this.maxPayloadBytes) : [rootReplace];
59
+ const initChanges = this.maxPayloadBytes ? breakChanges([rootReplace], this.maxPayloadBytes) : [rootReplace];
60
+ contentStartRev = initChanges[initChanges.length - 1].rev + 1;
61
+ await this.store.saveChanges(branchDocId, initChanges);
62
+ const initialVersionMetadata = createVersionMetadata({
63
+ origin: "main",
64
+ startedAt: now,
65
+ endedAt: now,
66
+ endRev: rev,
67
+ startRev: rev,
68
+ name: metadata?.name,
69
+ groupId: branchDocId,
70
+ branchName: metadata?.name
71
+ });
72
+ await this.store.createVersion(branchDocId, initialVersionMetadata, initChanges);
54
73
  }
55
- const contentStartRev = initChanges[initChanges.length - 1].rev + 1;
56
- await this.store.saveChanges(branchDocId, initChanges);
57
- const initialVersionMetadata = createVersionMetadata({
58
- origin: "main",
59
- startedAt: now,
60
- endedAt: now,
61
- endRev: rev,
62
- startRev: rev,
63
- name: metadata?.name,
64
- groupId: branchDocId,
65
- branchName: metadata?.name
66
- });
67
- await this.store.createVersion(branchDocId, initialVersionMetadata, initChanges);
68
74
  const branch = createBranchRecord(branchDocId, docId, rev, contentStartRev, metadata);
69
75
  await this.store.createBranch(branch);
70
76
  return branchDocId;
@@ -76,79 +82,65 @@ class OTBranchManager {
76
82
  */
77
83
  async updateBranch(branchId, metadata) {
78
84
  assertBranchMetadata(metadata);
79
- await this.store.updateBranch(branchId, metadata);
85
+ await this.store.updateBranch(branchId, { ...metadata, modifiedAt: Date.now() });
80
86
  }
81
87
  /**
82
- * Closes a branch, marking it as merged or deleted.
83
- * @param branchId - The ID of the branch to close.
84
- * @param status - The status to set for the branch.
88
+ * Deletes a branch, replacing the record with a tombstone.
85
89
  */
86
- async closeBranch(branchId, status) {
87
- await this.store.updateBranch(branchId, { status: status ?? "closed" });
90
+ async deleteBranch(branchId) {
91
+ await this.store.deleteBranch(branchId);
88
92
  }
89
93
  /**
90
94
  * Merges changes from a branch back into its source document.
95
+ *
96
+ * Supports multiple merges — the branch stays open and `lastMergedRev` tracks
97
+ * which branch revision was last merged. Subsequent merges only pick up new changes.
98
+ *
99
+ * All merge changes use `batchId: branchId` so that `commitChanges` never transforms
100
+ * branch changes against each other (they share the same causal context).
101
+ *
91
102
  * @param branchId - The ID of the branch document to merge.
92
103
  * @returns The server commit change(s) applied to the source document.
93
104
  * @throws Error if branch not found, already closed/merged, or merge fails.
94
105
  */
95
106
  async mergeBranch(branchId) {
96
107
  const branch = await this.store.loadBranch(branchId);
97
- assertBranchOpenForMerge(branch, branchId);
108
+ assertBranchExists(branch, branchId);
98
109
  const sourceDocId = branch.docId;
99
110
  const branchStartRevOnSource = branch.branchedAtRev;
100
- const contentStartRev = branch.contentStartRev ?? 2;
101
- const branchChanges = await this.store.listChanges(branchId, { startAfter: contentStartRev - 1 });
111
+ const startAfter = branch.lastMergedRev ?? (branch.contentStartRev ?? 2) - 1;
112
+ const branchChanges = await this.store.listChanges(branchId, { startAfter });
102
113
  if (branchChanges.length === 0) {
103
- console.log(`Branch ${branchId} has no changes to merge.`);
104
- await this.closeBranch(branchId, "merged");
105
114
  return [];
106
115
  }
107
- const sourceChanges = await this.store.listChanges(sourceDocId, {
108
- startAfter: branchStartRevOnSource
109
- });
110
- const canFastForward = sourceChanges.length === 0;
116
+ const lastBranchRev = branchChanges[branchChanges.length - 1].rev;
111
117
  const branchVersions = await this.store.listVersions(branchId, { origin: "main" });
112
- const versionOrigin = canFastForward ? "main" : "branch";
113
118
  let lastVersionId;
114
119
  for (const v of branchVersions) {
115
120
  const newVersionMetadata = createVersionMetadata({
116
121
  ...v,
117
- origin: versionOrigin,
122
+ origin: "branch",
118
123
  startRev: branchStartRevOnSource,
119
124
  groupId: branchId,
120
125
  branchName: branch.name,
121
- // Keep branchName for traceability
122
126
  parentId: lastVersionId
123
127
  });
124
128
  const changes = await this.store.loadVersionChanges?.(branchId, v.id);
125
129
  await this.store.createVersion(sourceDocId, newVersionMetadata, changes);
126
130
  lastVersionId = newVersionMetadata.id;
127
131
  }
132
+ const changesToCommit = branchChanges.map((c, i) => ({
133
+ ...c,
134
+ baseRev: branchStartRevOnSource,
135
+ rev: branchStartRevOnSource + i + 1,
136
+ batchId: branchId
137
+ }));
128
138
  const committedMergeChanges = await wrapMergeCommit(branchId, sourceDocId, async () => {
129
- if (canFastForward) {
130
- const adjustedChanges = branchChanges.map((c) => ({
131
- ...c,
132
- baseRev: branchStartRevOnSource,
133
- rev: void 0
134
- // Let commitChanges assign sequential revs
135
- }));
136
- return (await this.patchesServer.commitChanges(sourceDocId, adjustedChanges)).changes;
137
- } else {
138
- const rev = branchStartRevOnSource + branchChanges.length;
139
- const flattenedChange = createChange(
140
- branchStartRevOnSource,
141
- rev,
142
- branchChanges.flatMap((c) => c.ops)
143
- );
144
- let changesToCommit = [flattenedChange];
145
- if (this.maxPayloadBytes) {
146
- changesToCommit = breakChanges(changesToCommit, this.maxPayloadBytes);
147
- }
148
- return (await this.patchesServer.commitChanges(sourceDocId, changesToCommit)).changes;
149
- }
139
+ return (await this.patchesServer.commitChanges(sourceDocId, changesToCommit)).changes;
150
140
  });
151
- await this.closeBranch(branchId, "merged");
141
+ const currentBranch = await this.store.loadBranch(branchId);
142
+ const effectiveLastMergedRev = Math.max(lastBranchRev, currentBranch?.lastMergedRev ?? 0);
143
+ await this.store.updateBranch(branchId, { lastMergedRev: effectiveLastMergedRev, modifiedAt: Date.now() });
152
144
  return committedMergeChanges;
153
145
  }
154
146
  }
@@ -1,5 +1,5 @@
1
1
  import { ApiDefinition } from '../net/protocol/JSONRPCServer.js';
2
- import { EditableBranchMetadata, Branch, BranchStatus } from '../types.js';
2
+ import { EditableBranchMetadata, Branch, CreateBranchMetadata } from '../types.js';
3
3
  import 'easy-signal';
4
4
  import '../net/websocket/AuthorizationProvider.js';
5
5
  import './types.js';
@@ -40,7 +40,7 @@ declare function generateBranchId(store: BranchIdGenerator, docId: string): Prom
40
40
  * @param metadata - Optional branch metadata (name, etc.).
41
41
  * @returns A new Branch object.
42
42
  */
43
- declare function createBranchRecord(branchDocId: string, sourceDocId: string, branchedAtRev: number, contentStartRev: number, metadata?: EditableBranchMetadata): Branch;
43
+ declare function createBranchRecord(branchDocId: string, sourceDocId: string, branchedAtRev: number, contentStartRev: number, metadata?: CreateBranchMetadata | EditableBranchMetadata): Branch;
44
44
  /**
45
45
  * Store interface for branch loading.
46
46
  */
@@ -55,12 +55,12 @@ interface BranchLoader {
55
55
  */
56
56
  declare function assertNotABranch(store: BranchLoader, docId: string): Promise<void>;
57
57
  /**
58
- * Validates that a branch exists and is open for merging.
58
+ * Validates that a branch exists.
59
59
  * @param branch - The branch to validate (may be null).
60
60
  * @param branchId - The branch ID (for error messages).
61
- * @throws Error if branch not found or not open.
61
+ * @throws Error if branch not found.
62
62
  */
63
- declare function assertBranchOpenForMerge(branch: Branch | null, branchId: string): asserts branch is Branch;
63
+ declare function assertBranchExists(branch: Branch | null, branchId: string): asserts branch is Branch;
64
64
  /**
65
65
  * Wraps a merge commit operation with standard error handling.
66
66
  * @param branchId - The branch being merged.
@@ -70,14 +70,5 @@ declare function assertBranchOpenForMerge(branch: Branch | null, branchId: strin
70
70
  * @throws Error with standardized message if commit fails.
71
71
  */
72
72
  declare function wrapMergeCommit<T>(branchId: string, sourceDocId: string, commitFn: () => Promise<T>): Promise<T>;
73
- /**
74
- * Standard close branch operation.
75
- * @param store - Store with updateBranch capability.
76
- * @param branchId - The branch to close.
77
- * @param status - The status to set (defaults to 'closed').
78
- */
79
- declare function closeBranch(store: {
80
- updateBranch(branchId: string, updates: Partial<Pick<Branch, 'status' | 'name'>>): Promise<void>;
81
- }, branchId: string, status?: Exclude<BranchStatus, 'open'> | null): Promise<void>;
82
73
 
83
- export { type BranchIdGenerator, type BranchLoader, assertBranchMetadata, assertBranchOpenForMerge, assertNotABranch, branchManagerApi, closeBranch, createBranchRecord, generateBranchId, wrapMergeCommit };
74
+ export { type BranchIdGenerator, type BranchLoader, assertBranchExists, assertBranchMetadata, assertNotABranch, branchManagerApi, createBranchRecord, generateBranchId, wrapMergeCommit };
@@ -4,10 +4,19 @@ const branchManagerApi = {
4
4
  listBranches: "read",
5
5
  createBranch: "write",
6
6
  updateBranch: "write",
7
- closeBranch: "write",
7
+ deleteBranch: "write",
8
8
  mergeBranch: "write"
9
9
  };
10
- const nonModifiableBranchFields = /* @__PURE__ */ new Set(["id", "docId", "branchedAtRev", "createdAt", "status", "contentStartRev"]);
10
+ const nonModifiableBranchFields = /* @__PURE__ */ new Set([
11
+ "id",
12
+ "docId",
13
+ "branchedAtRev",
14
+ "createdAt",
15
+ "modifiedAt",
16
+ "contentStartRev",
17
+ "pendingOp",
18
+ "deleted"
19
+ ]);
11
20
  function assertBranchMetadata(metadata) {
12
21
  if (!metadata) return;
13
22
  for (const key in metadata) {
@@ -20,14 +29,15 @@ async function generateBranchId(store, docId) {
20
29
  return store.createBranchId ? await Promise.resolve(store.createBranchId(docId)) : createId(22);
21
30
  }
22
31
  function createBranchRecord(branchDocId, sourceDocId, branchedAtRev, contentStartRev, metadata) {
32
+ const now = Date.now();
23
33
  return {
24
34
  ...metadata,
25
35
  id: branchDocId,
26
36
  docId: sourceDocId,
27
37
  branchedAtRev,
28
38
  contentStartRev,
29
- createdAt: Date.now(),
30
- status: "open"
39
+ createdAt: now,
40
+ modifiedAt: now
31
41
  };
32
42
  }
33
43
  async function assertNotABranch(store, docId) {
@@ -36,13 +46,10 @@ async function assertNotABranch(store, docId) {
36
46
  throw new Error("Cannot create a branch from another branch.");
37
47
  }
38
48
  }
39
- function assertBranchOpenForMerge(branch, branchId) {
49
+ function assertBranchExists(branch, branchId) {
40
50
  if (!branch) {
41
51
  throw new Error(`Branch with ID ${branchId} not found.`);
42
52
  }
43
- if (branch.status !== "open") {
44
- throw new Error(`Branch ${branchId} is not open (status: ${branch.status}). Cannot merge.`);
45
- }
46
53
  }
47
54
  async function wrapMergeCommit(branchId, sourceDocId, commitFn) {
48
55
  try {
@@ -52,15 +59,11 @@ async function wrapMergeCommit(branchId, sourceDocId, commitFn) {
52
59
  throw new Error(`Merge failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
53
60
  }
54
61
  }
55
- async function closeBranch(store, branchId, status) {
56
- await store.updateBranch(branchId, { status: status ?? "closed" });
57
- }
58
62
  export {
63
+ assertBranchExists,
59
64
  assertBranchMetadata,
60
- assertBranchOpenForMerge,
61
65
  assertNotABranch,
62
66
  branchManagerApi,
63
- closeBranch,
64
67
  createBranchRecord,
65
68
  generateBranchId,
66
69
  wrapMergeCommit
@@ -9,7 +9,7 @@ export { buildVersionState, getBaseStateBeforeVersion } from '../algorithms/ot/s
9
9
  export { concatStreams, jsonReadable, parseVersionState, readStreamAsString } from './jsonReadable.js';
10
10
  export { blockable, blockableResponse, blocking, releaseConcurrency, singleInvocation } from '../utils/concurrency.js';
11
11
  export { RevConflictError } from './RevConflictError.js';
12
- export { BranchIdGenerator, BranchLoader, assertBranchMetadata, assertBranchOpenForMerge, assertNotABranch, branchManagerApi, createBranchRecord, generateBranchId, wrapMergeCommit } from './branchUtils.js';
12
+ export { BranchIdGenerator, BranchLoader, assertBranchExists, assertBranchMetadata, assertNotABranch, branchManagerApi, createBranchRecord, generateBranchId, wrapMergeCommit } from './branchUtils.js';
13
13
  export { CompressedStoreBackend } from './CompressedStoreBackend.js';
14
14
  export { createTombstoneIfSupported, isTombstoneStore, removeTombstoneIfExists } from './tombstone.js';
15
15
  export { assertVersionMetadata } from './utils.js';
@@ -11,7 +11,7 @@ import { blockable, blockableResponse, blocking, releaseConcurrency, singleInvoc
11
11
  import { RevConflictError } from "./RevConflictError.js";
12
12
  import {
13
13
  assertBranchMetadata,
14
- assertBranchOpenForMerge,
14
+ assertBranchExists,
15
15
  assertNotABranch,
16
16
  branchManagerApi,
17
17
  createBranchRecord,
@@ -30,8 +30,8 @@ export {
30
30
  OTServer,
31
31
  PatchesHistoryManager,
32
32
  RevConflictError,
33
+ assertBranchExists,
33
34
  assertBranchMetadata,
34
- assertBranchOpenForMerge,
35
35
  assertNotABranch,
36
36
  assertVersionMetadata,
37
37
  blockable,
@@ -1,5 +1,5 @@
1
1
  import { JSONPatchOp } from '../json-patch/types.js';
2
- import { DocumentTombstone, VersionMetadata, Change, ListVersionsOptions, EditableVersionMetadata, ListChangesOptions, Branch } from '../types.js';
2
+ import { DocumentTombstone, VersionMetadata, Change, ListVersionsOptions, EditableVersionMetadata, ListChangesOptions, ListBranchesOptions, Branch } from '../types.js';
3
3
  import '../json-patch/JSONPatch.js';
4
4
  import '@dabble/delta';
5
5
 
@@ -150,13 +150,19 @@ interface BranchingStoreBackend {
150
150
  */
151
151
  createBranchId?(docId: string): Promise<string> | string;
152
152
  /** Lists metadata records for branches originating from a document. */
153
- listBranches(docId: string): Promise<Branch[]>;
153
+ listBranches(docId: string, options?: ListBranchesOptions): Promise<Branch[]>;
154
154
  /** Loads the metadata record for a specific branch ID. */
155
155
  loadBranch(branchId: string): Promise<Branch | null>;
156
156
  /** Creates or updates the metadata record for a branch. */
157
157
  createBranch(branch: Branch): Promise<void>;
158
158
  /** Updates mutable fields of an existing branch record (excludes immutable identity fields). */
159
159
  updateBranch(branchId: string, updates: Partial<Omit<Branch, 'id' | 'docId' | 'branchedAtRev' | 'createdAt' | 'contentStartRev'>>): Promise<void>;
160
+ /**
161
+ * Replaces a branch record with a tombstone containing only `id`, `docId`, `modifiedAt`,
162
+ * and `deleted: true`. Tombstones are returned by `listBranches` when `since` is provided
163
+ * so that clients can clean up their local cache.
164
+ */
165
+ deleteBranch(branchId: string): Promise<void>;
160
166
  }
161
167
 
162
168
  export type { BranchingStoreBackend, LWWStoreBackend, ListFieldsOptions, OTStoreBackend, ServerStoreBackend, SnapshotResult, TombstoneStoreBackend, VersioningStoreBackend };
@@ -10,6 +10,7 @@ import '../client/ClientAlgorithm.js';
10
10
  import '../BaseDoc-BT18xPxU.js';
11
11
  import '../client/PatchesStore.js';
12
12
  import '../algorithms/ot/shared/changeBatching.js';
13
+ import '../client/BranchClientStore.js';
13
14
  import '../net/PatchesConnection.js';
14
15
  import '../net/protocol/types.js';
15
16
  import '../net/protocol/JSONRPCClient.js';
@@ -1,5 +1,5 @@
1
1
  export { PatchesContextValue, PatchesProvider, PatchesProviderProps, usePatchesContext } from './context.js';
2
- export { MaybeAccessor, PatchesDocProviderProps, UsePatchesDocLazyOptions, UsePatchesDocLazyReturn, UsePatchesDocOptions, UsePatchesDocReturn, UsePatchesSyncReturn, createPatchesDoc, usePatchesDoc, usePatchesSync } from './primitives.js';
2
+ export { MaybeAccessor, PatchesDocProviderProps, UsePatchesDocOptions, UsePatchesDocReturn, UsePatchesSyncReturn, createPatchesDoc, usePatchesDoc, usePatchesSync } from './primitives.js';
3
3
  export { CreateManagedDocsOptions, CreateManagedDocsReturn, createManagedDocs } from './managed-docs.js';
4
4
  export { fillPath } from '../shared/utils.js';
5
5
  export { DocManager, getDocManager } from '../shared/doc-manager.js';
@@ -15,6 +15,7 @@ import '../BaseDoc-BT18xPxU.js';
15
15
  import '../client/PatchesStore.js';
16
16
  import '../net/PatchesSync.js';
17
17
  import '../algorithms/ot/shared/changeBatching.js';
18
+ import '../client/BranchClientStore.js';
18
19
  import '../net/PatchesConnection.js';
19
20
  import '../net/protocol/types.js';
20
21
  import '../net/protocol/JSONRPCClient.js';
@@ -1,172 +1,78 @@
1
1
  import { Accessor } from 'solid-js';
2
2
  import { OpenDocOptions } from '../client/Patches.js';
3
3
  import { P as PatchesDoc } from '../BaseDoc-BT18xPxU.js';
4
- import { JSONPatch } from '../json-patch/JSONPatch.js';
5
4
  import { ChangeMutator } from '../types.js';
6
5
  import 'easy-signal';
7
6
  import '../json-patch/types.js';
8
7
  import '../client/ClientAlgorithm.js';
9
8
  import '../client/PatchesStore.js';
9
+ import '../json-patch/JSONPatch.js';
10
10
  import '@dabble/delta';
11
11
 
12
12
  /**
13
- * Options for usePatchesDoc primitive (eager mode with docId).
13
+ * Options for usePatchesDoc primitive.
14
14
  */
15
15
  interface UsePatchesDocOptions extends OpenDocOptions {
16
16
  /**
17
- * Controls document lifecycle management on cleanup.
18
- *
19
- * - `false` (default): Explicit mode. Assumes doc is already open. Throws if not.
20
- * - `true`: Opens doc on mount with ref counting, closes on cleanup (doc stays tracked).
21
- * - `'untrack'`: Opens doc on mount, closes AND untracks on cleanup (removes from sync).
22
- *
23
- * @default false
17
+ * When true, the document is removed from sync tracking on close.
18
+ * By default documents stay tracked after closing.
24
19
  */
25
- autoClose?: boolean | 'untrack';
20
+ untrack?: boolean;
26
21
  }
27
22
  /**
28
- * Options for usePatchesDoc primitive (lazy mode without docId).
29
- */
30
- interface UsePatchesDocLazyOptions {
31
- /**
32
- * Inject doc.id into state under this key on every state update.
33
- * Useful when the document ID is derived from the path but needed in the data.
34
- */
35
- idProp?: string;
36
- }
37
- /**
38
- * Return type for usePatchesDoc primitive (eager mode).
23
+ * Return type for usePatchesDoc primitive.
39
24
  */
40
25
  interface UsePatchesDocReturn<T extends object> {
41
- /**
42
- * Accessor for the document state.
43
- * Updated whenever the document changes (local or remote).
44
- */
26
+ /** Accessor for the document state. */
45
27
  data: Accessor<T | undefined>;
46
- /**
47
- * Whether the document is currently loading/syncing.
48
- * - `true` during initial load or updates
49
- * - `false` when fully synced
50
- */
28
+ /** Whether the document is currently loading. */
51
29
  loading: Accessor<boolean>;
52
- /**
53
- * Error that occurred during sync, if any.
54
- */
30
+ /** Error that occurred during sync, if any. */
55
31
  error: Accessor<Error | undefined>;
56
- /**
57
- * The committed revision number.
58
- * Increments each time the server confirms changes.
59
- */
32
+ /** The committed revision number. */
60
33
  rev: Accessor<number>;
61
- /**
62
- * Whether there are pending local changes not yet committed by server.
63
- */
34
+ /** Whether there are pending local changes not yet committed by server. */
64
35
  hasPending: Accessor<boolean>;
65
- /**
66
- * Make changes to the document.
67
- *
68
- * @example
69
- * ```typescript
70
- * change((patch, root) => {
71
- * patch.replace(root.title!, 'New Title')
72
- * })
73
- * ```
74
- */
36
+ /** Make changes to the document. No-ops if the document is not loaded. */
75
37
  change: (mutator: ChangeMutator<T>) => void;
76
- /**
77
- * The underlying PatchesDoc instance.
78
- * Useful for advanced operations.
79
- */
38
+ /** Close the document and reset state. Useful for explicit cleanup. */
39
+ close: () => Promise<void>;
40
+ /** The underlying PatchesDoc instance. */
80
41
  doc: Accessor<PatchesDoc<T> | undefined>;
81
42
  }
82
43
  /**
83
- * Return type for usePatchesDoc primitive (lazy mode).
84
- * Extends the eager return type with lifecycle management methods.
44
+ * Type for document ID can be a static string or accessor function.
85
45
  */
86
- interface UsePatchesDocLazyReturn<T extends object> extends UsePatchesDocReturn<T> {
87
- /**
88
- * Current document path. `null` when no document is loaded.
89
- */
90
- path: Accessor<string | null>;
91
- /**
92
- * Open a document by path. Closes any previously loaded document first.
93
- *
94
- * @param docPath - The document path to open
95
- * @param options - Optional algorithm and metadata overrides
96
- */
97
- load: (docPath: string, options?: OpenDocOptions) => Promise<void>;
98
- /**
99
- * Close the current document, unsubscribe, and reset all state.
100
- * Calls `patches.closeDoc()` but does not untrack — tracking is managed separately.
101
- */
102
- close: () => Promise<void>;
103
- /**
104
- * Create a new document: open it, set initial state, then close it.
105
- * A one-shot operation that doesn't bind the document to this handle.
106
- *
107
- * @param docPath - The document path to create
108
- * @param initialState - Initial state object or JSONPatch to apply
109
- * @param options - Optional algorithm and metadata overrides
110
- */
111
- create: (docPath: string, initialState: T | JSONPatch, options?: OpenDocOptions) => Promise<void>;
112
- }
46
+ type MaybeAccessor<T> = T | Accessor<T>;
113
47
  /**
114
48
  * Solid primitive for reactive Patches document state.
115
49
  *
116
- * ## Eager Mode (with docId)
117
- *
118
- * Provides reactive access to an already-open Patches document.
50
+ * Opens the document automatically and closes it on cleanup (or when the
51
+ * accessor value changes). Accepts a static string or an accessor. When the
52
+ * value is falsy, no document is loaded.
119
53
  *
120
54
  * @example
121
55
  * ```tsx
122
- * // Explicit lifecycle — you control open/close
123
- * const { data, loading, change } = usePatchesDoc(() => props.docId)
56
+ * // Static
57
+ * const { data, change } = usePatchesDoc('doc-123')
124
58
  *
125
- * // Auto lifecycle opens on mount, closes on cleanup
126
- * const { data, loading, change } = usePatchesDoc(() => props.docId, { autoClose: true })
127
- * ```
128
- *
129
- * ## Lazy Mode (without docId)
130
- *
131
- * Returns a deferred handle with `load()`, `close()`, and `create()` methods.
132
- * Does NOT register `onCleanup` — caller manages lifecycle.
133
- *
134
- * @example
135
- * ```tsx
136
- * const { data, load, close, change, create } = usePatchesDoc<Project>()
137
- *
138
- * // Later, when the user navigates:
139
- * await load('projects/abc/content')
140
- *
141
- * // When leaving:
142
- * await close()
59
+ * // Reactiveswaps automatically
60
+ * const [projectId, setProjectId] = createSignal<string | null>('abc')
61
+ * const { data, change } = usePatchesDoc(() => projectId() && `projects/${projectId()}`)
143
62
  * ```
144
63
  */
145
- declare function usePatchesDoc<T extends object>(docId: MaybeAccessor<string>, options?: UsePatchesDocOptions): UsePatchesDocReturn<T>;
146
- declare function usePatchesDoc<T extends object>(options?: UsePatchesDocLazyOptions): UsePatchesDocLazyReturn<T>;
64
+ declare function usePatchesDoc<T extends object>(docId: MaybeAccessor<string | null | undefined | false>, options?: UsePatchesDocOptions): UsePatchesDocReturn<T>;
147
65
  /**
148
66
  * Return type for usePatchesSync primitive.
149
67
  */
150
68
  interface UsePatchesSyncReturn {
151
- /**
152
- * Whether the WebSocket connection is established.
153
- */
154
69
  connected: Accessor<boolean>;
155
- /**
156
- * Whether documents are currently syncing with the server.
157
- */
158
70
  syncing: Accessor<boolean>;
159
- /**
160
- * Whether the client believes it has network connectivity.
161
- */
162
71
  online: Accessor<boolean>;
163
72
  }
164
73
  /**
165
74
  * Solid primitive for reactive Patches sync state.
166
75
  *
167
- * Provides reactive access to PatchesSync connection and sync status.
168
- * Useful for showing "Offline" banners, global loading indicators, etc.
169
- *
170
76
  * @example
171
77
  * ```tsx
172
78
  * const { connected, syncing, online } = usePatchesSync();
@@ -177,68 +83,37 @@ interface UsePatchesSyncReturn {
177
83
  * </Show>
178
84
  * );
179
85
  * ```
180
- *
181
- * @returns Reactive sync state
182
- * @throws Error if Patches context not provided
183
- * @throws Error if PatchesSync was not provided to context
184
86
  */
185
87
  declare function usePatchesSync(): UsePatchesSyncReturn;
186
- /**
187
- * Type for document ID - can be static string or accessor function.
188
- */
189
- type MaybeAccessor<T> = T | Accessor<T>;
190
88
  /**
191
89
  * Props for the Provider component returned by createPatchesDoc.
192
90
  */
193
91
  interface PatchesDocProviderProps extends OpenDocOptions {
194
92
  docId: MaybeAccessor<string>;
195
- autoClose?: boolean | 'untrack';
93
+ untrack?: boolean;
196
94
  children: any;
197
95
  }
198
96
  /**
199
97
  * Creates a named document context that can be provided to child components.
200
98
  *
201
- * This enables child components to access the document using the returned `useDoc` hook
202
- * without needing to pass the docId down through props. Supports both static and
203
- * reactive docIds.
204
- *
205
- * ## Use Cases
206
- *
207
- * **Static document (user settings):**
99
+ * @example
208
100
  * ```tsx
209
101
  * const { Provider, useDoc } = createPatchesDoc<User>('user');
210
102
  *
211
103
  * <Provider docId="user-123">
212
104
  * <UserProfile />
213
105
  * </Provider>
214
- * ```
215
106
  *
216
- * **Reactive document (multi-tab):**
217
- * ```tsx
218
- * const { Provider, useDoc } = createPatchesDoc<Whiteboard>('whiteboard');
107
+ * // Reactive
219
108
  * const [activeTabId, setActiveTabId] = createSignal('design-1');
220
- *
221
109
  * <Provider docId={activeTabId}>
222
110
  * <WhiteboardCanvas />
223
111
  * </Provider>
224
112
  * ```
225
- *
226
- * **With autoClose:**
227
- * ```tsx
228
- * const { Provider, useDoc } = createPatchesDoc<Doc>('document');
229
- * const [currentDocId, setCurrentDocId] = createSignal('doc-1');
230
- *
231
- * <Provider docId={currentDocId} autoClose>
232
- * <DocumentEditor />
233
- * </Provider>
234
- * ```
235
- *
236
- * @param name - Unique identifier for this document context (e.g., 'whiteboard', 'user')
237
- * @returns Object with Provider component and useDoc hook
238
113
  */
239
114
  declare function createPatchesDoc<T extends object>(name: string): {
240
115
  Provider: (props: PatchesDocProviderProps) => any;
241
116
  useDoc: () => UsePatchesDocReturn<T>;
242
117
  };
243
118
 
244
- export { type MaybeAccessor, type PatchesDocProviderProps, type UsePatchesDocLazyOptions, type UsePatchesDocLazyReturn, type UsePatchesDocOptions, type UsePatchesDocReturn, type UsePatchesSyncReturn, createPatchesDoc, usePatchesDoc, usePatchesSync };
119
+ export { type MaybeAccessor, type PatchesDocProviderProps, type UsePatchesDocOptions, type UsePatchesDocReturn, type UsePatchesSyncReturn, createPatchesDoc, usePatchesDoc, usePatchesSync };