@ifc-lite/collab 0.2.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 (219) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +92 -0
  3. package/dist/awareness/agent.d.ts +36 -0
  4. package/dist/awareness/agent.d.ts.map +1 -0
  5. package/dist/awareness/agent.js +39 -0
  6. package/dist/awareness/agent.js.map +1 -0
  7. package/dist/awareness/color.d.ts +31 -0
  8. package/dist/awareness/color.d.ts.map +1 -0
  9. package/dist/awareness/color.js +61 -0
  10. package/dist/awareness/color.js.map +1 -0
  11. package/dist/awareness/index.d.ts +6 -0
  12. package/dist/awareness/index.d.ts.map +1 -0
  13. package/dist/awareness/index.js +9 -0
  14. package/dist/awareness/index.js.map +1 -0
  15. package/dist/awareness/overlay.d.ts +59 -0
  16. package/dist/awareness/overlay.d.ts.map +1 -0
  17. package/dist/awareness/overlay.js +110 -0
  18. package/dist/awareness/overlay.js.map +1 -0
  19. package/dist/awareness/presence.d.ts +95 -0
  20. package/dist/awareness/presence.d.ts.map +1 -0
  21. package/dist/awareness/presence.js +114 -0
  22. package/dist/awareness/presence.js.map +1 -0
  23. package/dist/awareness/render.d.ts +58 -0
  24. package/dist/awareness/render.d.ts.map +1 -0
  25. package/dist/awareness/render.js +66 -0
  26. package/dist/awareness/render.js.map +1 -0
  27. package/dist/branch/branch-tree.d.ts +40 -0
  28. package/dist/branch/branch-tree.d.ts.map +1 -0
  29. package/dist/branch/branch-tree.js +66 -0
  30. package/dist/branch/branch-tree.js.map +1 -0
  31. package/dist/branch/branch.d.ts +44 -0
  32. package/dist/branch/branch.d.ts.map +1 -0
  33. package/dist/branch/branch.js +109 -0
  34. package/dist/branch/branch.js.map +1 -0
  35. package/dist/branch/history-automerge.d.ts +31 -0
  36. package/dist/branch/history-automerge.d.ts.map +1 -0
  37. package/dist/branch/history-automerge.js +237 -0
  38. package/dist/branch/history-automerge.js.map +1 -0
  39. package/dist/branch/history.d.ts +147 -0
  40. package/dist/branch/history.d.ts.map +1 -0
  41. package/dist/branch/history.js +223 -0
  42. package/dist/branch/history.js.map +1 -0
  43. package/dist/branch/index.d.ts +5 -0
  44. package/dist/branch/index.d.ts.map +1 -0
  45. package/dist/branch/index.js +8 -0
  46. package/dist/branch/index.js.map +1 -0
  47. package/dist/conflicts/detector.d.ts +52 -0
  48. package/dist/conflicts/detector.d.ts.map +1 -0
  49. package/dist/conflicts/detector.js +226 -0
  50. package/dist/conflicts/detector.js.map +1 -0
  51. package/dist/conflicts/index.d.ts +3 -0
  52. package/dist/conflicts/index.d.ts.map +1 -0
  53. package/dist/conflicts/index.js +6 -0
  54. package/dist/conflicts/index.js.map +1 -0
  55. package/dist/conflicts/ui-bridge.d.ts +80 -0
  56. package/dist/conflicts/ui-bridge.d.ts.map +1 -0
  57. package/dist/conflicts/ui-bridge.js +126 -0
  58. package/dist/conflicts/ui-bridge.js.map +1 -0
  59. package/dist/doc/entity.d.ts +84 -0
  60. package/dist/doc/entity.d.ts.map +1 -0
  61. package/dist/doc/entity.js +345 -0
  62. package/dist/doc/entity.js.map +1 -0
  63. package/dist/doc/geometry.d.ts +39 -0
  64. package/dist/doc/geometry.d.ts.map +1 -0
  65. package/dist/doc/geometry.js +99 -0
  66. package/dist/doc/geometry.js.map +1 -0
  67. package/dist/doc/index.d.ts +8 -0
  68. package/dist/doc/index.d.ts.map +1 -0
  69. package/dist/doc/index.js +11 -0
  70. package/dist/doc/index.js.map +1 -0
  71. package/dist/doc/migration-ifc4-to-ifc4x3.d.ts +21 -0
  72. package/dist/doc/migration-ifc4-to-ifc4x3.d.ts.map +1 -0
  73. package/dist/doc/migration-ifc4-to-ifc4x3.js +55 -0
  74. package/dist/doc/migration-ifc4-to-ifc4x3.js.map +1 -0
  75. package/dist/doc/relationship.d.ts +27 -0
  76. package/dist/doc/relationship.d.ts.map +1 -0
  77. package/dist/doc/relationship.js +110 -0
  78. package/dist/doc/relationship.js.map +1 -0
  79. package/dist/doc/schema-version.d.ts +47 -0
  80. package/dist/doc/schema-version.d.ts.map +1 -0
  81. package/dist/doc/schema-version.js +41 -0
  82. package/dist/doc/schema-version.js.map +1 -0
  83. package/dist/doc/schema.d.ts +131 -0
  84. package/dist/doc/schema.d.ts.map +1 -0
  85. package/dist/doc/schema.js +117 -0
  86. package/dist/doc/schema.js.map +1 -0
  87. package/dist/doc/units.d.ts +45 -0
  88. package/dist/doc/units.d.ts.map +1 -0
  89. package/dist/doc/units.js +120 -0
  90. package/dist/doc/units.js.map +1 -0
  91. package/dist/federation/bridge.d.ts +34 -0
  92. package/dist/federation/bridge.d.ts.map +1 -0
  93. package/dist/federation/bridge.js +52 -0
  94. package/dist/federation/bridge.js.map +1 -0
  95. package/dist/federation/index.d.ts +4 -0
  96. package/dist/federation/index.d.ts.map +1 -0
  97. package/dist/federation/index.js +7 -0
  98. package/dist/federation/index.js.map +1 -0
  99. package/dist/federation/resolver.d.ts +58 -0
  100. package/dist/federation/resolver.d.ts.map +1 -0
  101. package/dist/federation/resolver.js +43 -0
  102. package/dist/federation/resolver.js.map +1 -0
  103. package/dist/federation/session.d.ts +79 -0
  104. package/dist/federation/session.d.ts.map +1 -0
  105. package/dist/federation/session.js +126 -0
  106. package/dist/federation/session.js.map +1 -0
  107. package/dist/geometry/blob-store.d.ts +106 -0
  108. package/dist/geometry/blob-store.d.ts.map +1 -0
  109. package/dist/geometry/blob-store.js +266 -0
  110. package/dist/geometry/blob-store.js.map +1 -0
  111. package/dist/geometry/csg.d.ts +63 -0
  112. package/dist/geometry/csg.d.ts.map +1 -0
  113. package/dist/geometry/csg.js +101 -0
  114. package/dist/geometry/csg.js.map +1 -0
  115. package/dist/geometry/determinism.d.ts +45 -0
  116. package/dist/geometry/determinism.d.ts.map +1 -0
  117. package/dist/geometry/determinism.js +92 -0
  118. package/dist/geometry/determinism.js.map +1 -0
  119. package/dist/geometry/gc.d.ts +63 -0
  120. package/dist/geometry/gc.d.ts.map +1 -0
  121. package/dist/geometry/gc.js +109 -0
  122. package/dist/geometry/gc.js.map +1 -0
  123. package/dist/geometry/index.d.ts +6 -0
  124. package/dist/geometry/index.d.ts.map +1 -0
  125. package/dist/geometry/index.js +9 -0
  126. package/dist/geometry/index.js.map +1 -0
  127. package/dist/geometry/parametric.d.ts +92 -0
  128. package/dist/geometry/parametric.d.ts.map +1 -0
  129. package/dist/geometry/parametric.js +200 -0
  130. package/dist/geometry/parametric.js.map +1 -0
  131. package/dist/index.d.ts +27 -0
  132. package/dist/index.d.ts.map +1 -0
  133. package/dist/index.js +46 -0
  134. package/dist/index.js.map +1 -0
  135. package/dist/mutations/bind.d.ts +56 -0
  136. package/dist/mutations/bind.d.ts.map +1 -0
  137. package/dist/mutations/bind.js +68 -0
  138. package/dist/mutations/bind.js.map +1 -0
  139. package/dist/mutations/index.d.ts +2 -0
  140. package/dist/mutations/index.d.ts.map +1 -0
  141. package/dist/mutations/index.js +5 -0
  142. package/dist/mutations/index.js.map +1 -0
  143. package/dist/perf/benchmark.d.ts +25 -0
  144. package/dist/perf/benchmark.d.ts.map +1 -0
  145. package/dist/perf/benchmark.js +97 -0
  146. package/dist/perf/benchmark.js.map +1 -0
  147. package/dist/perf/index.d.ts +3 -0
  148. package/dist/perf/index.d.ts.map +1 -0
  149. package/dist/perf/index.js +6 -0
  150. package/dist/perf/index.js.map +1 -0
  151. package/dist/perf/latency.d.ts +39 -0
  152. package/dist/perf/latency.d.ts.map +1 -0
  153. package/dist/perf/latency.js +79 -0
  154. package/dist/perf/latency.js.map +1 -0
  155. package/dist/privacy.d.ts +45 -0
  156. package/dist/privacy.d.ts.map +1 -0
  157. package/dist/privacy.js +66 -0
  158. package/dist/privacy.js.map +1 -0
  159. package/dist/providers/indexeddb.d.ts +37 -0
  160. package/dist/providers/indexeddb.d.ts.map +1 -0
  161. package/dist/providers/indexeddb.js +45 -0
  162. package/dist/providers/indexeddb.js.map +1 -0
  163. package/dist/providers/webrtc.d.ts +40 -0
  164. package/dist/providers/webrtc.d.ts.map +1 -0
  165. package/dist/providers/webrtc.js +81 -0
  166. package/dist/providers/webrtc.js.map +1 -0
  167. package/dist/providers/websocket.d.ts +45 -0
  168. package/dist/providers/websocket.d.ts.map +1 -0
  169. package/dist/providers/websocket.js +56 -0
  170. package/dist/providers/websocket.js.map +1 -0
  171. package/dist/security/e2e.d.ts +54 -0
  172. package/dist/security/e2e.d.ts.map +1 -0
  173. package/dist/security/e2e.js +147 -0
  174. package/dist/security/e2e.js.map +1 -0
  175. package/dist/security/index.d.ts +2 -0
  176. package/dist/security/index.d.ts.map +1 -0
  177. package/dist/security/index.js +5 -0
  178. package/dist/security/index.js.map +1 -0
  179. package/dist/session.d.ts +79 -0
  180. package/dist/session.d.ts.map +1 -0
  181. package/dist/session.js +112 -0
  182. package/dist/session.js.map +1 -0
  183. package/dist/snapshot/from-ifcx.d.ts +24 -0
  184. package/dist/snapshot/from-ifcx.d.ts.map +1 -0
  185. package/dist/snapshot/from-ifcx.js +93 -0
  186. package/dist/snapshot/from-ifcx.js.map +1 -0
  187. package/dist/snapshot/index.d.ts +6 -0
  188. package/dist/snapshot/index.d.ts.map +1 -0
  189. package/dist/snapshot/index.js +9 -0
  190. package/dist/snapshot/index.js.map +1 -0
  191. package/dist/snapshot/layers.d.ts +56 -0
  192. package/dist/snapshot/layers.d.ts.map +1 -0
  193. package/dist/snapshot/layers.js +82 -0
  194. package/dist/snapshot/layers.js.map +1 -0
  195. package/dist/snapshot/minimal-layer.d.ts +40 -0
  196. package/dist/snapshot/minimal-layer.d.ts.map +1 -0
  197. package/dist/snapshot/minimal-layer.js +123 -0
  198. package/dist/snapshot/minimal-layer.js.map +1 -0
  199. package/dist/snapshot/to-ifcx.d.ts +26 -0
  200. package/dist/snapshot/to-ifcx.d.ts.map +1 -0
  201. package/dist/snapshot/to-ifcx.js +54 -0
  202. package/dist/snapshot/to-ifcx.js.map +1 -0
  203. package/dist/snapshot/worker.d.ts +45 -0
  204. package/dist/snapshot/worker.d.ts.map +1 -0
  205. package/dist/snapshot/worker.js +73 -0
  206. package/dist/snapshot/worker.js.map +1 -0
  207. package/dist/sync/room.d.ts +15 -0
  208. package/dist/sync/room.d.ts.map +1 -0
  209. package/dist/sync/room.js +20 -0
  210. package/dist/sync/room.js.map +1 -0
  211. package/dist/undo.d.ts +25 -0
  212. package/dist/undo.d.ts.map +1 -0
  213. package/dist/undo.js +37 -0
  214. package/dist/undo.js.map +1 -0
  215. package/dist/viewer-bridge.d.ts +27 -0
  216. package/dist/viewer-bridge.d.ts.map +1 -0
  217. package/dist/viewer-bridge.js +82 -0
  218. package/dist/viewer-bridge.js.map +1 -0
  219. package/package.json +83 -0
@@ -0,0 +1,223 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+ import { snapshotToIfcx } from '../snapshot/to-ifcx.js';
5
+ import { extractMinimalLayer } from '../snapshot/minimal-layer.js';
6
+ import * as Y from 'yjs';
7
+ /**
8
+ * Cheap deep clone for IFCX snapshots. Uses `structuredClone` when
9
+ * available (Node 17+, all modern browsers) and falls back to a
10
+ * JSON round-trip otherwise — IFCX is plain JSON so the round-trip is
11
+ * sufficient and loses nothing.
12
+ */
13
+ function cloneIfcx(file) {
14
+ if (typeof structuredClone === 'function') {
15
+ return structuredClone(file);
16
+ }
17
+ return JSON.parse(JSON.stringify(file));
18
+ }
19
+ /* ------------------------------------------------------------------ */
20
+ /* In-memory implementation */
21
+ /* ------------------------------------------------------------------ */
22
+ export class MemoryHistorySidecar {
23
+ entriesByBranch = new Map();
24
+ branchInfo = new Map();
25
+ counter = 0;
26
+ constructor() {
27
+ this.branchInfo.set('main', {
28
+ name: 'main',
29
+ createdAt: new Date().toISOString(),
30
+ });
31
+ this.entriesByBranch.set('main', []);
32
+ }
33
+ nextEntryId() {
34
+ this.counter += 1;
35
+ return `e${this.counter}-${Date.now().toString(36)}`;
36
+ }
37
+ async record(input) {
38
+ const branch = input.branch ?? 'main';
39
+ if (!this.branchInfo.has(branch)) {
40
+ this.branchInfo.set(branch, { name: branch, createdAt: new Date().toISOString() });
41
+ this.entriesByBranch.set(branch, []);
42
+ }
43
+ // Deep-clone snapshot/diff so callers that reuse buffers or mutate
44
+ // their input objects (common in streaming snapshot pipelines)
45
+ // cannot poison historical entries after they were recorded.
46
+ const entry = {
47
+ entryId: this.nextEntryId(),
48
+ at: new Date().toISOString(),
49
+ branch,
50
+ authorClientId: input.authorClientId,
51
+ label: input.label,
52
+ snapshot: cloneIfcx(input.snapshot),
53
+ diff: input.diff ? cloneIfcx(input.diff) : undefined,
54
+ };
55
+ const arr = this.entriesByBranch.get(branch);
56
+ arr.push(entry);
57
+ return entry;
58
+ }
59
+ async entries(branch) {
60
+ if (branch)
61
+ return [...(this.entriesByBranch.get(branch) ?? [])];
62
+ const all = [];
63
+ for (const arr of this.entriesByBranch.values())
64
+ all.push(...arr);
65
+ return all.sort((a, b) => a.at.localeCompare(b.at));
66
+ }
67
+ async at(at, branch) {
68
+ const target = at instanceof Date ? at.toISOString() : at;
69
+ const list = await this.entries(branch);
70
+ let best = null;
71
+ for (const e of list) {
72
+ if (e.at <= target)
73
+ best = e;
74
+ else
75
+ break;
76
+ }
77
+ return best;
78
+ }
79
+ async diff(fromEntryId, toEntryId) {
80
+ const all = await this.entries();
81
+ const from = all.find((e) => e.entryId === fromEntryId);
82
+ const to = all.find((e) => e.entryId === toEntryId);
83
+ if (!from || !to) {
84
+ return { from: fromEntryId, to: toEntryId, added: [], removed: [], changed: [] };
85
+ }
86
+ const fromPaths = new Map();
87
+ const toPaths = new Map();
88
+ for (const n of from.snapshot.data ?? [])
89
+ fromPaths.set(n.path, n);
90
+ for (const n of to.snapshot.data ?? [])
91
+ toPaths.set(n.path, n);
92
+ const added = [];
93
+ const removed = [];
94
+ const changed = [];
95
+ for (const path of toPaths.keys()) {
96
+ if (!fromPaths.has(path))
97
+ added.push(path);
98
+ else if (JSON.stringify(toPaths.get(path)) !== JSON.stringify(fromPaths.get(path))) {
99
+ changed.push(path);
100
+ }
101
+ }
102
+ for (const path of fromPaths.keys()) {
103
+ if (!toPaths.has(path))
104
+ removed.push(path);
105
+ }
106
+ return {
107
+ from: fromEntryId,
108
+ to: toEntryId,
109
+ added: added.sort(),
110
+ removed: removed.sort(),
111
+ changed: changed.sort(),
112
+ };
113
+ }
114
+ async branches() {
115
+ return Array.from(this.branchInfo.values()).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
116
+ }
117
+ async branch(name, fromEntryId) {
118
+ if (this.branchInfo.has(name)) {
119
+ throw new Error(`@ifc-lite/collab: branch "${name}" already exists`);
120
+ }
121
+ // Honor the documented default: when no explicit fork point is
122
+ // given, take the current head of `main`. Persisting `undefined`
123
+ // dropped fork ancestry for every caller that relied on the API
124
+ // signature's documented behavior.
125
+ let resolvedFork = fromEntryId;
126
+ if (!resolvedFork) {
127
+ const mainEntries = this.entriesByBranch.get('main') ?? [];
128
+ const head = mainEntries[mainEntries.length - 1];
129
+ if (head)
130
+ resolvedFork = head.entryId;
131
+ }
132
+ const info = {
133
+ name,
134
+ forkedFromEntryId: resolvedFork,
135
+ createdAt: new Date().toISOString(),
136
+ };
137
+ this.branchInfo.set(name, info);
138
+ this.entriesByBranch.set(name, []);
139
+ return info;
140
+ }
141
+ async merge(branch, into, mergedSnapshot) {
142
+ // Validate the source branch up front. Previously only `into` was
143
+ // checked, so a typo like merge('experimnet', 'main', …) wrote an
144
+ // irreversible merge entry with no resolvable source.
145
+ const sourceEntries = this.entriesByBranch.get(branch);
146
+ if (!sourceEntries) {
147
+ throw new Error(`@ifc-lite/collab: source branch "${branch}" not found`);
148
+ }
149
+ const targetEntries = this.entriesByBranch.get(into);
150
+ if (!targetEntries) {
151
+ throw new Error(`@ifc-lite/collab: target branch "${into}" not found`);
152
+ }
153
+ // Capture the source branch's CURRENT tip — the commit that was
154
+ // actually merged — into immutable metadata so the merge edge in
155
+ // the branch-tree view doesn't drift if the source branch keeps
156
+ // moving later.
157
+ const sourceTip = sourceEntries[sourceEntries.length - 1];
158
+ const entry = {
159
+ entryId: this.nextEntryId(),
160
+ at: new Date().toISOString(),
161
+ branch: into,
162
+ label: `merge ${branch} → ${into}`,
163
+ snapshot: cloneIfcx(mergedSnapshot),
164
+ mergedFromBranch: branch,
165
+ mergedFromEntryId: sourceTip?.entryId,
166
+ };
167
+ targetEntries.push(entry);
168
+ return entry;
169
+ }
170
+ async clear() {
171
+ this.entriesByBranch.clear();
172
+ this.branchInfo.clear();
173
+ this.branchInfo.set('main', { name: 'main', createdAt: new Date().toISOString() });
174
+ this.entriesByBranch.set('main', []);
175
+ this.counter = 0;
176
+ }
177
+ }
178
+ /**
179
+ * Drive a `HistorySidecar` from a live `CollabSession`. Records a
180
+ * snapshot every `intervalMs`, plus on-demand via `capture(label)`.
181
+ */
182
+ export function attachHistorySidecar(session, sidecar, options = {}) {
183
+ const intervalMs = options.intervalMs ?? 60_000;
184
+ const branch = options.branch ?? 'main';
185
+ const includeDiff = options.includeDiff ?? true;
186
+ let lastBaseline = null;
187
+ const capture = async (label) => {
188
+ const ifcx = snapshotToIfcx(session.doc, options.snapshot);
189
+ const diff = includeDiff && lastBaseline
190
+ ? extractMinimalLayer(session.doc, lastBaseline, { snapshot: options.snapshot })
191
+ : undefined;
192
+ // Capture the candidate baseline BEFORE record() so we don't read
193
+ // the doc twice with a window in between, but only commit it to
194
+ // `lastBaseline` AFTER record() resolves. If persistence rejects,
195
+ // the next capture must still diff against the previously-recorded
196
+ // state — otherwise history can silently skip changes.
197
+ const nextBaseline = Y.encodeStateAsUpdate(session.doc);
198
+ const entry = await sidecar.record({
199
+ branch,
200
+ snapshot: ifcx,
201
+ diff,
202
+ label,
203
+ authorClientId: session.clientId,
204
+ });
205
+ lastBaseline = nextBaseline;
206
+ return entry;
207
+ };
208
+ const timer = setInterval(() => {
209
+ void capture().catch((err) => {
210
+ // eslint-disable-next-line no-console
211
+ console.error('[collab] history capture failed:', err);
212
+ });
213
+ }, intervalMs);
214
+ // Don't keep Node alive solely for this timer.
215
+ timer.unref?.();
216
+ return {
217
+ capture,
218
+ detach() {
219
+ clearInterval(timer);
220
+ },
221
+ };
222
+ }
223
+ //# sourceMappingURL=history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.js","sourceRoot":"","sources":["../../src/branch/history.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export * from './branch.js';
2
+ export * from './history.js';
3
+ export * from './history-automerge.js';
4
+ export * from './branch-tree.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/branch/index.ts"],"names":[],"mappings":"AAIA,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,kBAAkB,CAAC"}
@@ -0,0 +1,8 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+ export * from './branch.js';
5
+ export * from './history.js';
6
+ export * from './history-automerge.js';
7
+ export * from './branch-tree.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/branch/index.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,kBAAkB,CAAC"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Conflict detector.
3
+ *
4
+ * Observes Y.Doc transactions and emits structured events when concurrent
5
+ * edits land on the same target within a tunable window. The viewer (or
6
+ * any UI) listens to these events and renders a conflict badge plus an
7
+ * optional ghost overlay (§9.4 / §9.7).
8
+ *
9
+ * The CRDT itself never blocks or rolls back on conflict — it converges
10
+ * deterministically. Detection here is purely advisory: it tells the user
11
+ * "your peer changed this at the same time."
12
+ *
13
+ * Implementation note: we use `Transaction.changed` (struct-level
14
+ * changes) rather than `YEvent.keys` (head-level changes) because LWW
15
+ * can keep the existing head value when a remote struct loses, in which
16
+ * case `YEvent.keys` is empty even though there was a concurrent write.
17
+ * The struct-level view is what we want for the conflict surface.
18
+ */
19
+ import * as Y from 'yjs';
20
+ export type ConflictKind = 'attribute' | 'pset-property' | 'hierarchy' | 'geometry-blob' | 'geometry-param' | 'relationship-target' | 'concurrent-delete';
21
+ export interface ConflictEvent {
22
+ kind: ConflictKind;
23
+ /** Entity / relationship / geometry path involved. */
24
+ path: string;
25
+ /** Sub-path (e.g. attribute name, `pset.prop`, role). */
26
+ field?: string;
27
+ /** clientIDs that contributed conflicting writes within the window. */
28
+ contributors: number[];
29
+ /** Wall-clock ms when the detector first flagged this conflict. */
30
+ detectedAt: number;
31
+ }
32
+ export type ConflictListener = (event: ConflictEvent) => void;
33
+ export interface ConflictDetectorOptions {
34
+ /** Window in ms inside which two writes count as concurrent (default 750). */
35
+ windowMs?: number;
36
+ }
37
+ export interface ConflictDetector {
38
+ onConflict(listener: ConflictListener): () => void;
39
+ /** All currently-active conflicts (cleared after `windowMs * 4`). */
40
+ active(): ConflictEvent[];
41
+ destroy(): void;
42
+ }
43
+ /**
44
+ * Build a conflict detector for `doc`.
45
+ *
46
+ * Subscribes to `afterTransaction` and inspects `tr.changed`, which
47
+ * reports every key whose underlying struct list changed (independent of
48
+ * the head value). For each entry we resolve where in the tree the
49
+ * change happened and classify it into one of `ConflictKind`s.
50
+ */
51
+ export declare function createConflictDetector(doc: Y.Doc, options?: ConflictDetectorOptions): ConflictDetector;
52
+ //# sourceMappingURL=detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../../src/conflicts/detector.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AAWzB,MAAM,MAAM,YAAY,GACpB,WAAW,GACX,eAAe,GACf,WAAW,GACX,eAAe,GACf,gBAAgB,GAChB,qBAAqB,GACrB,mBAAmB,CAAC;AAExB,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,YAAY,CAAC;IACnB,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;AAE9D,MAAM,WAAW,uBAAuB;IACtC,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,CAAC,QAAQ,EAAE,gBAAgB,GAAG,MAAM,IAAI,CAAC;IACnD,qEAAqE;IACrE,MAAM,IAAI,aAAa,EAAE,CAAC;IAC1B,OAAO,IAAI,IAAI,CAAC;CACjB;AAaD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,CAAC,CAAC,GAAG,EACV,OAAO,GAAE,uBAA4B,GACpC,gBAAgB,CAyElB"}
@@ -0,0 +1,226 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+ import { ENTITY_KEY, GEOMETRY_KEY, RELATIONSHIP_KEY, TOP, } from '../doc/schema.js';
5
+ /**
6
+ * Build a conflict detector for `doc`.
7
+ *
8
+ * Subscribes to `afterTransaction` and inspects `tr.changed`, which
9
+ * reports every key whose underlying struct list changed (independent of
10
+ * the head value). For each entry we resolve where in the tree the
11
+ * change happened and classify it into one of `ConflictKind`s.
12
+ */
13
+ export function createConflictDetector(doc, options = {}) {
14
+ const windowMs = options.windowMs ?? 750;
15
+ const listeners = new Set();
16
+ const conflicts = [];
17
+ /** key = `${kind}|${path}|${field}` → recent writers. */
18
+ const recentWrites = new Map();
19
+ const flag = (info, writers) => {
20
+ const contributors = Array.from(new Set(writers.map((w) => w.client)));
21
+ if (contributors.length < 2)
22
+ return;
23
+ const event = {
24
+ kind: info.kind,
25
+ path: info.path,
26
+ field: info.field,
27
+ contributors,
28
+ detectedAt: Date.now(),
29
+ };
30
+ conflicts.push(event);
31
+ listeners.forEach((l) => l(event));
32
+ };
33
+ const record = (info, client) => {
34
+ if (client < 0)
35
+ return;
36
+ const key = `${info.kind}|${info.path}|${info.field ?? ''}`;
37
+ const now = Date.now();
38
+ const arr = (recentWrites.get(key) ?? []).filter((w) => now - w.at < windowMs);
39
+ arr.push({ client, at: now });
40
+ recentWrites.set(key, arr);
41
+ if (arr.length >= 2)
42
+ flag(info, arr);
43
+ };
44
+ const onAfterTransaction = (tr) => {
45
+ if (tr.changed.size === 0)
46
+ return;
47
+ const client = tr.local ? doc.clientID : guessRemoteClient(tr);
48
+ if (client < 0)
49
+ return;
50
+ for (const [type, keys] of tr.changed.entries()) {
51
+ const top = topLevelKey(type);
52
+ if (top !== TOP.ENTITIES && top !== TOP.RELATIONSHIPS && top !== TOP.GEOMETRY)
53
+ continue;
54
+ const path = pathFromTop(type);
55
+ if (!path)
56
+ continue;
57
+ for (const key of keys) {
58
+ // For top-level Y.Map changes the parent map's `has(key)` tells
59
+ // us whether this was a delete (key absent → yes) or an add.
60
+ // Only deletes count as conflict-inducing at the top level.
61
+ const isDelete = path.length === 0 && key != null && !type.has(key);
62
+ const info = classify(top, path, key, isDelete);
63
+ if (info)
64
+ record(info, client);
65
+ }
66
+ }
67
+ const cutoff = Date.now() - windowMs * 4;
68
+ while (conflicts.length > 0 && conflicts[0].detectedAt < cutoff) {
69
+ conflicts.shift();
70
+ }
71
+ };
72
+ doc.on('afterTransaction', onAfterTransaction);
73
+ return {
74
+ onConflict(listener) {
75
+ listeners.add(listener);
76
+ return () => listeners.delete(listener);
77
+ },
78
+ active: () => [...conflicts],
79
+ destroy() {
80
+ doc.off('afterTransaction', onAfterTransaction);
81
+ listeners.clear();
82
+ conflicts.length = 0;
83
+ recentWrites.clear();
84
+ },
85
+ };
86
+ }
87
+ /**
88
+ * Resolve a changed Y.AbstractType's path from its top-level shared
89
+ * type as a list of keys/indices. Keys at intermediate levels are
90
+ * always strings — we only nest Y.Maps under named keys.
91
+ */
92
+ function pathFromTop(type) {
93
+ const out = [];
94
+ let node = type;
95
+ while (node) {
96
+ const item = node._item;
97
+ if (!item)
98
+ break; // top-level
99
+ const parentSub = item.parentSub;
100
+ if (typeof parentSub !== 'string')
101
+ return null; // array-indexed nesting; unsupported here
102
+ out.unshift(parentSub);
103
+ node = item.parent;
104
+ }
105
+ return out;
106
+ }
107
+ function topLevelKey(type) {
108
+ let node = type;
109
+ while (node) {
110
+ const item = node._item;
111
+ if (!item) {
112
+ const doc = node.doc;
113
+ if (!doc)
114
+ return undefined;
115
+ for (const [name, shared] of doc.share) {
116
+ if (shared === node)
117
+ return name;
118
+ }
119
+ return undefined;
120
+ }
121
+ node = item.parent;
122
+ }
123
+ return undefined;
124
+ }
125
+ /**
126
+ * Map a (top, path[], key) triple to a ConflictKind + path + field.
127
+ * `path` is the list of map keys descending from the top-level shared
128
+ * type to the changed AbstractType. `key` is the changed leaf key (or
129
+ * null for array-shaped changes). `isDelete` only matters at
130
+ * `path.length === 0` (top-level entity churn).
131
+ */
132
+ function classify(top, path, key, isDelete) {
133
+ if (top === TOP.ENTITIES) {
134
+ if (path.length === 0) {
135
+ // Entity create OR delete on the top-level entities map. We only
136
+ // surface deletes — concurrent creates are CRDT-friendly (both
137
+ // entities coexist if they have different paths).
138
+ if (isDelete && key)
139
+ return { kind: 'concurrent-delete', path: key };
140
+ return null;
141
+ }
142
+ const entityPath = path[0];
143
+ if (path.length === 1) {
144
+ // Change on the entity Y.Map itself; sub-key is `key` (e.g. the
145
+ // attributes Y.Map being added/replaced). Not a leaf conflict.
146
+ return null;
147
+ }
148
+ const subMap = path[1];
149
+ if (path.length === 2) {
150
+ switch (subMap) {
151
+ case ENTITY_KEY.ATTRIBUTES:
152
+ return key ? { kind: 'attribute', path: entityPath, field: key } : null;
153
+ case ENTITY_KEY.CHILDREN:
154
+ return key ? { kind: 'hierarchy', path: entityPath, field: key } : null;
155
+ case ENTITY_KEY.PSETS:
156
+ // A new (or replaced) Pset Y.Map at the entity level. Treat
157
+ // the pset name as the field — useful when two peers seed the
158
+ // same Pset concurrently.
159
+ return key ? { kind: 'pset-property', path: entityPath, field: key } : null;
160
+ default:
161
+ return null;
162
+ }
163
+ }
164
+ if (path.length === 3 && subMap === ENTITY_KEY.PSETS) {
165
+ const psetName = path[2];
166
+ return key
167
+ ? { kind: 'pset-property', path: entityPath, field: `${psetName}.${key}` }
168
+ : null;
169
+ }
170
+ return null;
171
+ }
172
+ if (top === TOP.GEOMETRY) {
173
+ if (path.length === 0) {
174
+ return null;
175
+ }
176
+ const geomId = path[0];
177
+ if (path.length === 1) {
178
+ // direct field on a geometry node
179
+ if (key === GEOMETRY_KEY.BLOB_HASH) {
180
+ return { kind: 'geometry-blob', path: geomId };
181
+ }
182
+ return null;
183
+ }
184
+ if (path.length === 2 && path[1] === GEOMETRY_KEY.PARAMS) {
185
+ return key ? { kind: 'geometry-param', path: geomId, field: key } : null;
186
+ }
187
+ return null;
188
+ }
189
+ if (top === TOP.RELATIONSHIPS) {
190
+ if (path.length === 0) {
191
+ return null;
192
+ }
193
+ const relPath = path[0];
194
+ if (path.length === 1 && key === RELATIONSHIP_KEY.TARGETS) {
195
+ return { kind: 'relationship-target', path: relPath };
196
+ }
197
+ // Targets array changes: the Y.Array is at path=[relPath, 'targets']
198
+ if (path.length === 2 && path[1] === RELATIONSHIP_KEY.TARGETS) {
199
+ return { kind: 'relationship-target', path: relPath };
200
+ }
201
+ return null;
202
+ }
203
+ return null;
204
+ }
205
+ /**
206
+ * Best-effort attribution of a remote transaction to a clientID.
207
+ *
208
+ * Yjs doesn't carry "the client that authored this transaction" directly;
209
+ * it carries per-struct clientIDs in the `afterState`/`beforeState`
210
+ * vectors. We pick the client whose clock advanced the furthest. Ties
211
+ * are rare and harmless — the conflict UI surfaces all contributors.
212
+ */
213
+ function guessRemoteClient(tr) {
214
+ let bestClient = -1;
215
+ let bestDelta = 0;
216
+ for (const [client, after] of tr.afterState) {
217
+ const before = tr.beforeState.get(client) ?? 0;
218
+ const delta = after - before;
219
+ if (delta > bestDelta) {
220
+ bestDelta = delta;
221
+ bestClient = client;
222
+ }
223
+ }
224
+ return bestClient;
225
+ }
226
+ //# sourceMappingURL=detector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detector.js","sourceRoot":"","sources":["../../src/conflicts/detector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export * from './detector.js';
2
+ export * from './ui-bridge.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/conflicts/index.ts"],"names":[],"mappings":"AAIA,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1,6 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+ export * from './detector.js';
5
+ export * from './ui-bridge.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/conflicts/index.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * UI bridge for conflict surfacing (spec §9.7).
3
+ *
4
+ * The detector reports raw events at every concurrent write. The viewer
5
+ * (or any UI) needs:
6
+ * - a stable list of currently-active conflicts grouped by entity path
7
+ * - notifications when a conflict appears, ages out, or resolves
8
+ * - a clean "resolve mine" / "accept theirs" handle that emits a
9
+ * follow-up edit on resolution
10
+ *
11
+ * This module sits between the detector and the UI. It does NOT touch
12
+ * the CRDT — resolution is a normal write that the caller dispatches
13
+ * inside its own transaction.
14
+ */
15
+ import type { ConflictDetector, ConflictEvent } from './detector.js';
16
+ export type ConflictBucketKey = string;
17
+ export interface ConflictBucket {
18
+ key: ConflictBucketKey;
19
+ /** Entity / relationship / geometry path involved. */
20
+ path: string;
21
+ /** Optional sub-path (e.g. attribute name). */
22
+ field?: string;
23
+ kind: ConflictEvent['kind'];
24
+ /** All clientIDs that wrote in the conflict window. */
25
+ contributors: Set<number>;
26
+ /** Wall-clock ms of the first event in the bucket. */
27
+ firstSeenAt: number;
28
+ /** Wall-clock ms of the most-recent event. */
29
+ lastSeenAt: number;
30
+ /** Number of detector events folded into this bucket. */
31
+ count: number;
32
+ }
33
+ export type BridgeEventType = 'open' | 'update' | 'close';
34
+ export interface BridgeEvent {
35
+ type: BridgeEventType;
36
+ bucket: ConflictBucket;
37
+ }
38
+ export type BridgeListener = (event: BridgeEvent) => void;
39
+ export interface ConflictUIBridgeOptions {
40
+ /** A bucket closes after this many ms with no new events (default 4_000). */
41
+ closeAfterMs?: number;
42
+ }
43
+ export interface ResolutionContext {
44
+ bucket: ConflictBucket;
45
+ }
46
+ export type ResolutionAction = (ctx: ResolutionContext) => void | Promise<void>;
47
+ export interface ConflictUIBridge {
48
+ /** All currently-open buckets. */
49
+ active(): ConflictBucket[];
50
+ /** Subscribe to bucket lifecycle (open / update / close). */
51
+ on(listener: BridgeListener): () => void;
52
+ /**
53
+ * Mark a bucket resolved (close immediately). Useful when the UI
54
+ * dispatches a "keep mine" edit and wants the badge to disappear.
55
+ */
56
+ resolve(key: ConflictBucketKey): boolean;
57
+ /**
58
+ * Run the registered "keep mine" callback (if any) and close the
59
+ * bucket. Apps register the callback per kind via `onKeepMine`. The
60
+ * callback is responsible for emitting the follow-up CRDT edit that
61
+ * re-asserts the local user's value.
62
+ */
63
+ keepMine(key: ConflictBucketKey): Promise<boolean>;
64
+ /** Same shape as `keepMine`, but for "accept theirs". */
65
+ acceptTheirs(key: ConflictBucketKey): Promise<boolean>;
66
+ /** Register a "keep mine" handler for a given conflict kind. */
67
+ onKeepMine(kind: ConflictBucket['kind'], action: ResolutionAction): () => void;
68
+ /** Register an "accept theirs" handler for a given conflict kind. */
69
+ onAcceptTheirs(kind: ConflictBucket['kind'], action: ResolutionAction): () => void;
70
+ destroy(): void;
71
+ }
72
+ /**
73
+ * Build a UI bridge subscribed to `detector`.
74
+ *
75
+ * Each `(kind, path, field)` triple maps to one bucket. A new event
76
+ * folded into an existing bucket emits `update`; a new triple emits
77
+ * `open`; an idle timeout or explicit `resolve()` emits `close`.
78
+ */
79
+ export declare function createConflictUIBridge(detector: ConflictDetector, opts?: ConflictUIBridgeOptions): ConflictUIBridge;
80
+ //# sourceMappingURL=ui-bridge.d.ts.map