@nicia-ai/typegraph 0.30.0 → 0.31.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 (181) hide show
  1. package/README.md +39 -0
  2. package/dist/backend/postgres/index.cjs +19 -18
  3. package/dist/backend/postgres/index.d.cts +6 -6
  4. package/dist/backend/postgres/index.d.ts +6 -6
  5. package/dist/backend/postgres/index.js +9 -8
  6. package/dist/backend/postgres/pglite.cjs +13 -12
  7. package/dist/backend/postgres/pglite.cjs.map +1 -1
  8. package/dist/backend/postgres/pglite.d.cts +3 -3
  9. package/dist/backend/postgres/pglite.d.ts +3 -3
  10. package/dist/backend/postgres/pglite.js +9 -8
  11. package/dist/backend/postgres/pglite.js.map +1 -1
  12. package/dist/backend/sqlite/index.cjs +16 -16
  13. package/dist/backend/sqlite/index.d.cts +7 -7
  14. package/dist/backend/sqlite/index.d.ts +7 -7
  15. package/dist/backend/sqlite/index.js +7 -7
  16. package/dist/backend/sqlite/libsql.cjs +12 -12
  17. package/dist/backend/sqlite/libsql.d.cts +3 -3
  18. package/dist/backend/sqlite/libsql.d.ts +3 -3
  19. package/dist/backend/sqlite/libsql.js +8 -8
  20. package/dist/backend/sqlite/local.cjs +13 -13
  21. package/dist/backend/sqlite/local.d.cts +3 -3
  22. package/dist/backend/sqlite/local.d.ts +3 -3
  23. package/dist/backend/sqlite/local.js +8 -8
  24. package/dist/{chunk-AI3RNREM.js → chunk-2HZGWCB4.js} +94 -496
  25. package/dist/chunk-2HZGWCB4.js.map +1 -0
  26. package/dist/chunk-2YMGNYXY.js +40 -0
  27. package/dist/chunk-2YMGNYXY.js.map +1 -0
  28. package/dist/chunk-2ZY4K43M.js +90 -0
  29. package/dist/chunk-2ZY4K43M.js.map +1 -0
  30. package/dist/chunk-3EUKJEQ3.js +77 -0
  31. package/dist/chunk-3EUKJEQ3.js.map +1 -0
  32. package/dist/chunk-3JBJEYWE.cjs +604 -0
  33. package/dist/chunk-3JBJEYWE.cjs.map +1 -0
  34. package/dist/{chunk-IUIJJYPI.js → chunk-4A4GUZTQ.js} +6 -5
  35. package/dist/chunk-4A4GUZTQ.js.map +1 -0
  36. package/dist/{chunk-WEMZM7J7.cjs → chunk-54JVNOAQ.cjs} +5 -41
  37. package/dist/chunk-54JVNOAQ.cjs.map +1 -0
  38. package/dist/{chunk-IE6GN7L3.cjs → chunk-5CN7B4CZ.cjs} +17 -98
  39. package/dist/chunk-5CN7B4CZ.cjs.map +1 -0
  40. package/dist/{chunk-K4N35E7B.cjs → chunk-5DTPBFX5.cjs} +64 -63
  41. package/dist/chunk-5DTPBFX5.cjs.map +1 -0
  42. package/dist/chunk-6G35I3VW.cjs +40 -0
  43. package/dist/chunk-6G35I3VW.cjs.map +1 -0
  44. package/dist/{chunk-4BXHVEFB.cjs → chunk-6RMLO5O4.cjs} +6 -51
  45. package/dist/chunk-6RMLO5O4.cjs.map +1 -0
  46. package/dist/chunk-6SA7UIQJ.cjs +94 -0
  47. package/dist/chunk-6SA7UIQJ.cjs.map +1 -0
  48. package/dist/chunk-6ZR4QC3O.cjs +12 -0
  49. package/dist/{chunk-LUHU4T2F.cjs.map → chunk-6ZR4QC3O.cjs.map} +1 -1
  50. package/dist/chunk-73KASJGO.cjs +52 -0
  51. package/dist/chunk-73KASJGO.cjs.map +1 -0
  52. package/dist/chunk-A3W7QG7K.js +45 -0
  53. package/dist/chunk-A3W7QG7K.js.map +1 -0
  54. package/dist/{chunk-FWBVPATK.cjs → chunk-ASYQDPPU.cjs} +19 -19
  55. package/dist/{chunk-FWBVPATK.cjs.map → chunk-ASYQDPPU.cjs.map} +1 -1
  56. package/dist/{chunk-FVIGJTS7.js → chunk-ATTXBMAX.js} +4 -74
  57. package/dist/chunk-ATTXBMAX.js.map +1 -0
  58. package/dist/chunk-AW4A3P5A.cjs +51 -0
  59. package/dist/chunk-AW4A3P5A.cjs.map +1 -0
  60. package/dist/{chunk-WFCECD6Q.cjs → chunk-CIINW6PS.cjs} +73 -68
  61. package/dist/chunk-CIINW6PS.cjs.map +1 -0
  62. package/dist/{chunk-26HWIIVS.cjs → chunk-FLOI7QOT.cjs} +63 -62
  63. package/dist/chunk-FLOI7QOT.cjs.map +1 -0
  64. package/dist/{chunk-633N7DSE.js → chunk-GKFWM3DS.js} +4 -36
  65. package/dist/chunk-GKFWM3DS.js.map +1 -0
  66. package/dist/chunk-HTGDWIDE.cjs +89 -0
  67. package/dist/chunk-HTGDWIDE.cjs.map +1 -0
  68. package/dist/{chunk-R4JOBEJU.js → chunk-IJORSAC3.js} +4 -40
  69. package/dist/chunk-IJORSAC3.js.map +1 -0
  70. package/dist/{chunk-SGPXKJ4A.cjs → chunk-KTIEB7VH.cjs} +26 -16
  71. package/dist/chunk-KTIEB7VH.cjs.map +1 -0
  72. package/dist/chunk-LJLMDDCW.js +282 -0
  73. package/dist/chunk-LJLMDDCW.js.map +1 -0
  74. package/dist/{chunk-VFX3LC76.cjs → chunk-N2MBPTAF.cjs} +14 -14
  75. package/dist/{chunk-VFX3LC76.cjs.map → chunk-N2MBPTAF.cjs.map} +1 -1
  76. package/dist/{chunk-3IRMLJEK.js → chunk-O4VUR6SI.js} +3 -3
  77. package/dist/{chunk-3IRMLJEK.js.map → chunk-O4VUR6SI.js.map} +1 -1
  78. package/dist/chunk-RZ4DH4E7.cjs +15832 -0
  79. package/dist/chunk-RZ4DH4E7.cjs.map +1 -0
  80. package/dist/chunk-SAJSMR2B.js +35 -0
  81. package/dist/chunk-SAJSMR2B.js.map +1 -0
  82. package/dist/{chunk-4LBO6KIF.cjs → chunk-SFFJ2IKN.cjs} +60 -59
  83. package/dist/chunk-SFFJ2IKN.cjs.map +1 -0
  84. package/dist/{chunk-K5VREXMZ.js → chunk-STP7QVUB.js} +7 -6
  85. package/dist/chunk-STP7QVUB.js.map +1 -0
  86. package/dist/chunk-THX25XW6.js +590 -0
  87. package/dist/chunk-THX25XW6.js.map +1 -0
  88. package/dist/chunk-UIFEEPME.js +15787 -0
  89. package/dist/chunk-UIFEEPME.js.map +1 -0
  90. package/dist/{chunk-2QA5EHEN.js → chunk-UJ4V3YUQ.js} +3 -3
  91. package/dist/{chunk-2QA5EHEN.js.map → chunk-UJ4V3YUQ.js.map} +1 -1
  92. package/dist/{chunk-UESTHN47.cjs → chunk-V4IQYOQ4.cjs} +95 -507
  93. package/dist/chunk-V4IQYOQ4.cjs.map +1 -0
  94. package/dist/chunk-VMEWZU2N.cjs +287 -0
  95. package/dist/chunk-VMEWZU2N.cjs.map +1 -0
  96. package/dist/{chunk-N2OEOGBB.js → chunk-VZX2KSY3.js} +13 -8
  97. package/dist/chunk-VZX2KSY3.js.map +1 -0
  98. package/dist/{chunk-IX7RE7CG.js → chunk-WYTUF3QO.js} +3 -3
  99. package/dist/{chunk-IX7RE7CG.js.map → chunk-WYTUF3QO.js.map} +1 -1
  100. package/dist/{chunk-3JLRHQ67.js → chunk-XKJQQQVR.js} +6 -5
  101. package/dist/chunk-XKJQQQVR.js.map +1 -0
  102. package/dist/{chunk-ENDUY2CD.js → chunk-YCV72E6L.js} +23 -17
  103. package/dist/chunk-YCV72E6L.js.map +1 -0
  104. package/dist/{ddl-EdkIiFVU.d.ts → ddl-BJcPrB4L.d.ts} +3 -3
  105. package/dist/{ddl-BSFlbGQF.d.cts → ddl-CPZ7mB7X.d.cts} +3 -3
  106. package/dist/{extension-types-CwHV-Uk4.d.cts → extension-types-D1k0inWU.d.cts} +1 -1
  107. package/dist/{extension-types-CwHV-Uk4.d.ts → extension-types-D1k0inWU.d.ts} +1 -1
  108. package/dist/graph-extension/index.cjs +17 -17
  109. package/dist/graph-extension/index.d.cts +5 -16
  110. package/dist/graph-extension/index.d.ts +5 -16
  111. package/dist/graph-extension/index.js +2 -2
  112. package/dist/graph-merge/index.cjs +3822 -0
  113. package/dist/graph-merge/index.cjs.map +1 -0
  114. package/dist/graph-merge/index.d.cts +1191 -0
  115. package/dist/graph-merge/index.d.ts +1191 -0
  116. package/dist/graph-merge/index.js +3791 -0
  117. package/dist/graph-merge/index.js.map +1 -0
  118. package/dist/{index-DzeIiMO3.d.ts → index-BEK0VtNB.d.ts} +2 -2
  119. package/dist/{index-Ln_KHTGA.d.cts → index-C8sSTErx.d.cts} +2 -2
  120. package/dist/index.cjs +281 -15913
  121. package/dist/index.cjs.map +1 -1
  122. package/dist/index.d.cts +12 -11
  123. package/dist/index.d.ts +12 -11
  124. package/dist/index.js +23 -15790
  125. package/dist/index.js.map +1 -1
  126. package/dist/indexes/index.cjs +30 -26
  127. package/dist/indexes/index.d.cts +5 -5
  128. package/dist/indexes/index.d.ts +5 -5
  129. package/dist/indexes/index.js +6 -2
  130. package/dist/interchange/index.cjs +49 -592
  131. package/dist/interchange/index.cjs.map +1 -1
  132. package/dist/interchange/index.d.cts +5 -5
  133. package/dist/interchange/index.d.ts +5 -5
  134. package/dist/interchange/index.js +2 -588
  135. package/dist/interchange/index.js.map +1 -1
  136. package/dist/{manager-BbdyNhkv.d.ts → manager-By-WEd8M.d.ts} +3 -3
  137. package/dist/{manager-wTDgjoXG.d.cts → manager-Np7VVSlE.d.cts} +3 -3
  138. package/dist/{materialize-indexes-lkawaDor.d.ts → materialize-indexes-CAGhqPHQ.d.ts} +1 -1
  139. package/dist/{materialize-indexes-oanE3RU0.d.cts → materialize-indexes-DEYkU-wr.d.cts} +1 -1
  140. package/dist/{postgres-CK2ByP1q.d.cts → postgres-4J2tmauh.d.cts} +1 -1
  141. package/dist/{postgres-ymXYC6Rr.d.ts → postgres-BdIb7tS_.d.ts} +1 -1
  142. package/dist/profiler/index.d.cts +7 -7
  143. package/dist/profiler/index.d.ts +7 -7
  144. package/dist/result-DWo6mUVz.d.cts +37 -0
  145. package/dist/result-DWo6mUVz.d.ts +37 -0
  146. package/dist/schema/index.cjs +29 -28
  147. package/dist/schema/index.cjs.map +1 -1
  148. package/dist/schema/index.d.cts +7 -7
  149. package/dist/schema/index.d.ts +7 -7
  150. package/dist/schema/index.js +6 -5
  151. package/dist/schema/index.js.map +1 -1
  152. package/dist/{sqlite-DxH1lrwD.d.cts → sqlite-Gatk_iw_.d.cts} +1 -1
  153. package/dist/{sqlite-R8YGOqVQ.d.ts → sqlite-aM_PAz-O.d.ts} +1 -1
  154. package/dist/{store--z1Tmk7U.d.cts → store-DuCbdqsh.d.cts} +49 -49
  155. package/dist/{store-CNKM334w.d.ts → store-MloRPwb9.d.ts} +49 -49
  156. package/dist/{types-BBiDqJCU.d.ts → types-BKQljXom.d.ts} +16 -2
  157. package/dist/{types-dL-clxO3.d.cts → types-C1CYxWrL.d.cts} +16 -2
  158. package/dist/{types-D5B6Be5c.d.cts → types-DoByHGyU.d.cts} +2 -2
  159. package/dist/{types-CMvxx2kd.d.ts → types-K3vpaJAL.d.ts} +2 -2
  160. package/dist/{where-CAo6wsBZ.d.ts → where-Dt49bvo6.d.ts} +2 -2
  161. package/dist/{where-bJjuOCeV.d.cts → where-DydZjGOb.d.cts} +2 -2
  162. package/package.json +6 -1
  163. package/dist/chunk-26HWIIVS.cjs.map +0 -1
  164. package/dist/chunk-3JLRHQ67.js.map +0 -1
  165. package/dist/chunk-4BXHVEFB.cjs.map +0 -1
  166. package/dist/chunk-4LBO6KIF.cjs.map +0 -1
  167. package/dist/chunk-633N7DSE.js.map +0 -1
  168. package/dist/chunk-AI3RNREM.js.map +0 -1
  169. package/dist/chunk-ENDUY2CD.js.map +0 -1
  170. package/dist/chunk-FVIGJTS7.js.map +0 -1
  171. package/dist/chunk-IE6GN7L3.cjs.map +0 -1
  172. package/dist/chunk-IUIJJYPI.js.map +0 -1
  173. package/dist/chunk-K4N35E7B.cjs.map +0 -1
  174. package/dist/chunk-K5VREXMZ.js.map +0 -1
  175. package/dist/chunk-LUHU4T2F.cjs +0 -12
  176. package/dist/chunk-N2OEOGBB.js.map +0 -1
  177. package/dist/chunk-R4JOBEJU.js.map +0 -1
  178. package/dist/chunk-SGPXKJ4A.cjs.map +0 -1
  179. package/dist/chunk-UESTHN47.cjs.map +0 -1
  180. package/dist/chunk-WEMZM7J7.cjs.map +0 -1
  181. package/dist/chunk-WFCECD6Q.cjs.map +0 -1
@@ -0,0 +1,3791 @@
1
+ import { createStoreWithSchema, generateId, computeUniqueKey } from '../chunk-UIFEEPME.js';
2
+ import '../chunk-3EUKJEQ3.js';
3
+ import { exportGraph, importGraph } from '../chunk-THX25XW6.js';
4
+ import { defineNode, computeSchemaHash, serializeSchema } from '../chunk-XKJQQQVR.js';
5
+ import { getNodeKinds, getEdgeKinds, defineGraph } from '../chunk-MAQJYNJR.js';
6
+ import '../chunk-2ZY4K43M.js';
7
+ import '../chunk-IJORSAC3.js';
8
+ import '../chunk-MXE2U3EX.js';
9
+ import '../chunk-454I6GDF.js';
10
+ import { ok, err, isErr, computeTransitiveClosure, isReachable } from '../chunk-YCV72E6L.js';
11
+ export { isErr, isOk, unwrap } from '../chunk-YCV72E6L.js';
12
+ import '../chunk-SAJSMR2B.js';
13
+ import { sortedReplacer } from '../chunk-M6OC455F.js';
14
+ import '../chunk-2HZGWCB4.js';
15
+ import { sha256Hex } from '../chunk-2YMGNYXY.js';
16
+ import { TypeGraphError } from '../chunk-TQOYM4LN.js';
17
+ import '../chunk-K5P5OZXS.js';
18
+ import { z } from 'zod';
19
+
20
+ // src/graph-merge/canonical-props.ts
21
+ function canonicalizeProps(props) {
22
+ return JSON.stringify(props, sortedReplacer);
23
+ }
24
+ function canonicalValueKey(value) {
25
+ return JSON.stringify(value, sortedReplacer);
26
+ }
27
+ function parseRowProps(props) {
28
+ let parsed;
29
+ try {
30
+ parsed = JSON.parse(props);
31
+ } catch {
32
+ return {};
33
+ }
34
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
35
+ return {};
36
+ }
37
+ return parsed;
38
+ }
39
+
40
+ // src/graph-merge/node-key.ts
41
+ var SEPARATOR = String.fromCharCode(0);
42
+ function mergeKey(kind, id) {
43
+ if (kind.includes(SEPARATOR)) {
44
+ throw new Error(
45
+ `Node kind ${JSON.stringify(kind)} contains a NUL byte, which collides the (kind, id) merge-identity separator.`
46
+ );
47
+ }
48
+ return `${kind}${SEPARATOR}${id}`;
49
+ }
50
+ function compareStrings(left, right) {
51
+ return left < right ? -1 : left > right ? 1 : 0;
52
+ }
53
+ function mergeKeyOf(node) {
54
+ return mergeKey(node.kind, node.id);
55
+ }
56
+ function kindOf(key) {
57
+ const separator = key.indexOf(SEPARATOR);
58
+ return separator === -1 ? key : key.slice(0, separator);
59
+ }
60
+ function idOf(key) {
61
+ const separator = key.indexOf(SEPARATOR);
62
+ return separator === -1 ? key : key.slice(separator + 1);
63
+ }
64
+ function compareMergeKeys(left, right) {
65
+ const leftId = idOf(left);
66
+ const rightId = idOf(right);
67
+ if (leftId !== rightId) {
68
+ return leftId < rightId ? -1 : 1;
69
+ }
70
+ const leftKind = kindOf(left);
71
+ const rightKind = kindOf(right);
72
+ return leftKind < rightKind ? -1 : leftKind > rightKind ? 1 : 0;
73
+ }
74
+
75
+ // src/graph-merge/state-diff.ts
76
+ var ENUMERATION_PAGE_SIZE = 1e3;
77
+ async function enumerateAllNodes(backend, graphId, kind) {
78
+ const collected = [];
79
+ let after = void 0;
80
+ for (; ; ) {
81
+ const page = await backend.findNodesByKind({
82
+ graphId,
83
+ kind,
84
+ excludeDeleted: false,
85
+ orderBy: "id",
86
+ limit: ENUMERATION_PAGE_SIZE,
87
+ ...after === void 0 ? {} : { after }
88
+ });
89
+ for (const row of page) {
90
+ collected.push(row);
91
+ }
92
+ if (page.length < ENUMERATION_PAGE_SIZE) {
93
+ break;
94
+ }
95
+ after = page.at(-1).id;
96
+ }
97
+ return collected;
98
+ }
99
+ async function enumerateAllEdges(backend, graphId, kind) {
100
+ const collected = [];
101
+ let after = void 0;
102
+ for (; ; ) {
103
+ const page = await backend.findEdgesByKind({
104
+ graphId,
105
+ kind,
106
+ excludeDeleted: false,
107
+ orderBy: "id",
108
+ limit: ENUMERATION_PAGE_SIZE,
109
+ ...after === void 0 ? {} : { after }
110
+ });
111
+ for (const row of page) {
112
+ collected.push(row);
113
+ }
114
+ if (page.length < ENUMERATION_PAGE_SIZE) {
115
+ break;
116
+ }
117
+ after = page.at(-1).id;
118
+ }
119
+ return collected;
120
+ }
121
+ function isLive(row) {
122
+ return row.deleted_at === void 0;
123
+ }
124
+ function indexById(rows) {
125
+ const index = /* @__PURE__ */ new Map();
126
+ for (const row of rows) {
127
+ index.set(row.id, row);
128
+ }
129
+ return index;
130
+ }
131
+ function diffNodeKind(kind, baseRows, forkRows) {
132
+ const baseIndex = indexById(baseRows);
133
+ const forkIndex = indexById(forkRows);
134
+ const created = [];
135
+ const modified = [];
136
+ const deleted = [];
137
+ for (const forkRow of forkRows) {
138
+ if (!isLive(forkRow)) {
139
+ continue;
140
+ }
141
+ const baseRow = baseIndex.get(forkRow.id);
142
+ const forkProps = parseRowProps(forkRow.props);
143
+ if (baseRow === void 0 || !isLive(baseRow)) {
144
+ created.push({
145
+ id: forkRow.id,
146
+ kind,
147
+ props: forkProps,
148
+ row: forkRow
149
+ });
150
+ continue;
151
+ }
152
+ const baseProps = parseRowProps(baseRow.props);
153
+ if (canonicalizeProps(baseProps) !== canonicalizeProps(forkProps)) {
154
+ modified.push({
155
+ id: forkRow.id,
156
+ kind,
157
+ baseProps,
158
+ forkProps,
159
+ row: forkRow
160
+ });
161
+ }
162
+ }
163
+ for (const baseRow of baseRows) {
164
+ if (!isLive(baseRow)) {
165
+ continue;
166
+ }
167
+ const forkRow = forkIndex.get(baseRow.id);
168
+ if (forkRow === void 0 || !isLive(forkRow)) {
169
+ deleted.push({ id: baseRow.id, kind });
170
+ }
171
+ }
172
+ return { new: created, modified, deleted };
173
+ }
174
+ function diffEdgeKind(kind, baseRows, forkRows) {
175
+ const baseIndex = indexById(baseRows);
176
+ const forkIndex = indexById(forkRows);
177
+ const created = [];
178
+ const modified = [];
179
+ const deleted = [];
180
+ for (const forkRow of forkRows) {
181
+ if (!isLive(forkRow)) {
182
+ continue;
183
+ }
184
+ const baseRow = baseIndex.get(forkRow.id);
185
+ const forkProps = parseRowProps(forkRow.props);
186
+ if (baseRow === void 0 || !isLive(baseRow)) {
187
+ created.push({
188
+ id: forkRow.id,
189
+ kind,
190
+ fromId: forkRow.from_id,
191
+ toId: forkRow.to_id,
192
+ fromKind: forkRow.from_kind,
193
+ toKind: forkRow.to_kind,
194
+ props: forkProps,
195
+ row: forkRow
196
+ });
197
+ continue;
198
+ }
199
+ const baseProps = parseRowProps(baseRow.props);
200
+ if (canonicalizeProps(baseProps) !== canonicalizeProps(forkProps)) {
201
+ modified.push({
202
+ id: forkRow.id,
203
+ kind,
204
+ fromId: forkRow.from_id,
205
+ toId: forkRow.to_id,
206
+ fromKind: forkRow.from_kind,
207
+ toKind: forkRow.to_kind,
208
+ baseProps,
209
+ forkProps,
210
+ row: forkRow
211
+ });
212
+ }
213
+ }
214
+ for (const baseRow of baseRows) {
215
+ if (!isLive(baseRow)) {
216
+ continue;
217
+ }
218
+ const forkRow = forkIndex.get(baseRow.id);
219
+ if (forkRow === void 0 || !isLive(forkRow)) {
220
+ deleted.push({ id: baseRow.id, kind });
221
+ }
222
+ }
223
+ return { new: created, modified, deleted };
224
+ }
225
+ function byId(left, right) {
226
+ return compareStrings(left.id, right.id);
227
+ }
228
+ async function diffAgainstBase(baseStore, forkStore) {
229
+ const graph = baseStore.graph;
230
+ const nodeKinds = getNodeKinds(graph);
231
+ const edgeKinds = getEdgeKinds(graph);
232
+ const newNodes = [];
233
+ const modifiedNodes = [];
234
+ const deletedNodes = [];
235
+ for (const kind of nodeKinds) {
236
+ const baseRows = await enumerateAllNodes(
237
+ baseStore.backend,
238
+ baseStore.graphId,
239
+ kind
240
+ );
241
+ const forkRows = await enumerateAllNodes(
242
+ forkStore.backend,
243
+ forkStore.graphId,
244
+ kind
245
+ );
246
+ const delta = diffNodeKind(kind, baseRows, forkRows);
247
+ for (const entry of delta.new) {
248
+ newNodes.push(entry);
249
+ }
250
+ for (const entry of delta.modified) {
251
+ modifiedNodes.push(entry);
252
+ }
253
+ for (const entry of delta.deleted) {
254
+ deletedNodes.push(entry);
255
+ }
256
+ }
257
+ const newEdges = [];
258
+ const modifiedEdges = [];
259
+ const deletedEdges = [];
260
+ for (const kind of edgeKinds) {
261
+ const baseRows = await enumerateAllEdges(
262
+ baseStore.backend,
263
+ baseStore.graphId,
264
+ kind
265
+ );
266
+ const forkRows = await enumerateAllEdges(
267
+ forkStore.backend,
268
+ forkStore.graphId,
269
+ kind
270
+ );
271
+ const delta = diffEdgeKind(kind, baseRows, forkRows);
272
+ for (const entry of delta.new) {
273
+ newEdges.push(entry);
274
+ }
275
+ for (const entry of delta.modified) {
276
+ modifiedEdges.push(entry);
277
+ }
278
+ for (const entry of delta.deleted) {
279
+ deletedEdges.push(entry);
280
+ }
281
+ }
282
+ return {
283
+ nodes: {
284
+ new: newNodes.sort((left, right) => byId(left, right)),
285
+ modified: modifiedNodes.sort((left, right) => byId(left, right)),
286
+ deleted: deletedNodes.sort((left, right) => byId(left, right))
287
+ },
288
+ edges: {
289
+ new: newEdges.sort((left, right) => byId(left, right)),
290
+ modified: modifiedEdges.sort((left, right) => byId(left, right)),
291
+ deleted: deletedEdges.sort((left, right) => byId(left, right))
292
+ }
293
+ };
294
+ }
295
+
296
+ // src/graph-merge/types.ts
297
+ function asBranchId(value) {
298
+ return value;
299
+ }
300
+ function asBaseVersion(value) {
301
+ return value;
302
+ }
303
+
304
+ // src/graph-merge/base-version.ts
305
+ var TOKEN_SEPARATOR = "\0";
306
+ var FALLBACK_SCHEMA_VERSION = 1;
307
+ async function readActiveSchemaVersion(backend, graphId) {
308
+ const active = await backend.getActiveSchema(graphId);
309
+ return active?.version ?? FALLBACK_SCHEMA_VERSION;
310
+ }
311
+ async function computeSchemaComponent(store) {
312
+ const version = await readActiveSchemaVersion(store.backend, store.graphId);
313
+ return computeSchemaHash(serializeSchema(store.graph, version));
314
+ }
315
+ function byDigestEntry(left, right) {
316
+ const byKind = compareStrings(left.kind, right.kind);
317
+ return byKind === 0 ? compareStrings(left.id, right.id) : byKind;
318
+ }
319
+ var CONTENT_FINGERPRINT_BYTES = 16;
320
+ async function computeContentFingerprint(store) {
321
+ const backend = store.backend;
322
+ const graphId = store.graphId;
323
+ const graph = store.graph;
324
+ const nodeKinds = getNodeKinds(graph);
325
+ const edgeKinds = getEdgeKinds(graph);
326
+ const nodeDigest = [];
327
+ for (const kind of nodeKinds) {
328
+ const rows = await enumerateAllNodes(backend, graphId, kind);
329
+ for (const row of rows) {
330
+ if (row.deleted_at === void 0) {
331
+ nodeDigest.push({
332
+ id: row.id,
333
+ kind: row.kind,
334
+ updatedAt: row.updated_at,
335
+ validFrom: row.valid_from,
336
+ validTo: row.valid_to,
337
+ props: canonicalizeProps(parseRowProps(row.props))
338
+ });
339
+ }
340
+ }
341
+ }
342
+ const edgeDigest = [];
343
+ for (const kind of edgeKinds) {
344
+ const rows = await enumerateAllEdges(backend, graphId, kind);
345
+ for (const row of rows) {
346
+ if (row.deleted_at === void 0) {
347
+ edgeDigest.push({
348
+ id: row.id,
349
+ kind: row.kind,
350
+ fromId: row.from_id,
351
+ toId: row.to_id,
352
+ updatedAt: row.updated_at,
353
+ validFrom: row.valid_from,
354
+ validTo: row.valid_to,
355
+ props: canonicalizeProps(parseRowProps(row.props))
356
+ });
357
+ }
358
+ }
359
+ }
360
+ return sha256Hex(
361
+ canonicalizeProps({
362
+ nodes: nodeDigest.sort((left, right) => byDigestEntry(left, right)),
363
+ edges: edgeDigest.sort((left, right) => byDigestEntry(left, right))
364
+ }),
365
+ CONTENT_FINGERPRINT_BYTES
366
+ );
367
+ }
368
+ async function computeBaseVersion(store) {
369
+ const [schemaComponent, contentComponent] = await Promise.all([
370
+ computeSchemaComponent(store),
371
+ computeContentFingerprint(store)
372
+ ]);
373
+ return asBaseVersion(
374
+ `${schemaComponent}${TOKEN_SEPARATOR}${contentComponent}`
375
+ );
376
+ }
377
+
378
+ // src/graph-merge/errors.ts
379
+ var MERGE_ERROR_CODES = {
380
+ merge: "GRAPH_MERGE_ERROR",
381
+ branch: "GRAPH_MERGE_BRANCH_ERROR",
382
+ similarityUnavailable: "GRAPH_MERGE_SIMILARITY_UNAVAILABLE",
383
+ conflict: "GRAPH_MERGE_CONFLICT",
384
+ baseVersionMismatch: "GRAPH_MERGE_BASE_VERSION_MISMATCH"
385
+ };
386
+ function toTypeGraphErrorOptions(category, options) {
387
+ return {
388
+ category,
389
+ ...options.details === void 0 ? {} : { details: options.details },
390
+ ...options.suggestion === void 0 ? {} : { suggestion: options.suggestion },
391
+ ...options.cause === void 0 ? {} : { cause: options.cause }
392
+ };
393
+ }
394
+ var MergeError = class extends TypeGraphError {
395
+ constructor(message, options = {}) {
396
+ super(
397
+ message,
398
+ MERGE_ERROR_CODES.merge,
399
+ toTypeGraphErrorOptions("system", options)
400
+ );
401
+ this.name = "MergeError";
402
+ }
403
+ };
404
+ var BranchError = class extends TypeGraphError {
405
+ constructor(message, options = {}) {
406
+ super(
407
+ message,
408
+ MERGE_ERROR_CODES.branch,
409
+ toTypeGraphErrorOptions("system", options)
410
+ );
411
+ this.name = "BranchError";
412
+ }
413
+ };
414
+ var SimilarityUnavailableError = class extends MergeError {
415
+ code = MERGE_ERROR_CODES.similarityUnavailable;
416
+ constructor(message, options = {}) {
417
+ super(message, {
418
+ ...options,
419
+ suggestion: options.suggestion ?? "Pass MergeOptions.embedder (a local model), or use a fulltext/custom similarity strategy."
420
+ });
421
+ this.name = "SimilarityUnavailableError";
422
+ }
423
+ };
424
+ var MergeConflictError = class extends MergeError {
425
+ code = MERGE_ERROR_CODES.conflict;
426
+ constructor(message, options = {}) {
427
+ super(message, options);
428
+ this.name = "MergeConflictError";
429
+ }
430
+ };
431
+ var BaseVersionMismatchError = class extends MergeError {
432
+ code = MERGE_ERROR_CODES.baseVersionMismatch;
433
+ constructor(message, options = {}) {
434
+ super(message, {
435
+ ...options,
436
+ suggestion: options.suggestion ?? "Re-branch from the current target so the branch base matches before merging."
437
+ });
438
+ this.name = "BaseVersionMismatchError";
439
+ }
440
+ };
441
+
442
+ // src/graph-merge/working-copy.ts
443
+ var CLONE_IMPORT_BATCH_SIZE = 1e3;
444
+ function cloneWorkingCopyStrategy(makeBackend) {
445
+ return {
446
+ create: async (baseStore) => {
447
+ const data = await exportGraph(baseStore, {
448
+ includeMeta: true,
449
+ includeDeleted: false
450
+ });
451
+ const backend = await makeBackend();
452
+ try {
453
+ const [freshStore] = await createStoreWithSchema(
454
+ baseStore.graph,
455
+ backend
456
+ );
457
+ const result = await importGraph(freshStore, data, {
458
+ onConflict: "error",
459
+ onUnknownProperty: "error",
460
+ validateReferences: true,
461
+ batchSize: CLONE_IMPORT_BATCH_SIZE
462
+ });
463
+ if (!result.success) {
464
+ throw new BranchError(
465
+ "Clone import failed: the working copy could not be seeded from the base store. The backend returned by makeBackend() must be empty.",
466
+ { details: { errors: result.errors } }
467
+ );
468
+ }
469
+ return freshStore;
470
+ } catch (error) {
471
+ try {
472
+ await backend.close();
473
+ } catch {
474
+ }
475
+ throw error;
476
+ }
477
+ }
478
+ };
479
+ }
480
+
481
+ // src/graph-merge/branch.ts
482
+ async function branch(baseStore, makeBackend, options, strategy) {
483
+ try {
484
+ const base = await computeBaseVersion(baseStore);
485
+ const id = options?.id ?? asBranchId(generateId());
486
+ const workingCopyStrategy = strategy ?? cloneWorkingCopyStrategy(makeBackend);
487
+ const store = await workingCopyStrategy.create(baseStore);
488
+ return ok({ id, base, store });
489
+ } catch (error) {
490
+ return err(
491
+ new BranchError("Failed to create working-copy branch of base store", {
492
+ cause: error
493
+ })
494
+ );
495
+ }
496
+ }
497
+
498
+ // src/graph-merge/blocking.ts
499
+ var UNBLOCKED_BUCKET_KEY = "unblocked";
500
+ var KEY_PART_SEPARATOR = "\0";
501
+ var BLOCK_KEY_PREFIX = "b";
502
+ var UNIQUE_KEY_PREFIX = "u";
503
+ function isUniqueBucketKey(key) {
504
+ return key.startsWith(`${UNIQUE_KEY_PREFIX}${KEY_PART_SEPARATOR}`);
505
+ }
506
+ function constraintSignature(node, constraint) {
507
+ const props = node;
508
+ for (const field of constraint.fields) {
509
+ const value = props[field];
510
+ if (value === void 0 || value === null) {
511
+ return void 0;
512
+ }
513
+ }
514
+ return `${constraint.name}${KEY_PART_SEPARATOR}${computeUniqueKey(
515
+ props,
516
+ constraint.fields,
517
+ constraint.collation
518
+ )}`;
519
+ }
520
+ function bucketKeysFor(node, block, constraints) {
521
+ const keys = [];
522
+ const blockKey = block?.(node);
523
+ if (blockKey !== void 0) {
524
+ keys.push(`${BLOCK_KEY_PREFIX}${KEY_PART_SEPARATOR}${blockKey}`);
525
+ }
526
+ for (const constraint of constraints) {
527
+ const signature = constraintSignature(node, constraint);
528
+ if (signature !== void 0) {
529
+ keys.push(`${UNIQUE_KEY_PREFIX}${KEY_PART_SEPARATOR}${signature}`);
530
+ }
531
+ }
532
+ return keys.length === 0 ? [UNBLOCKED_BUCKET_KEY] : keys;
533
+ }
534
+ function blockNodes(newNodes, resolveConfig, uniqueConstraints = []) {
535
+ const buckets = /* @__PURE__ */ new Map();
536
+ for (const node of newNodes) {
537
+ for (const key of bucketKeysFor(
538
+ node,
539
+ resolveConfig.block,
540
+ uniqueConstraints
541
+ )) {
542
+ const bucket = buckets.get(key);
543
+ if (bucket === void 0) {
544
+ buckets.set(key, [node]);
545
+ } else {
546
+ bucket.push(node);
547
+ }
548
+ }
549
+ }
550
+ const sortedKeys = [...buckets.keys()].sort(
551
+ (left, right) => compareStrings(left, right)
552
+ );
553
+ const ordered = /* @__PURE__ */ new Map();
554
+ for (const key of sortedKeys) {
555
+ const members = [...buckets.get(key)].sort(
556
+ (left, right) => compareStrings(left.id, right.id)
557
+ );
558
+ ordered.set(key, members);
559
+ }
560
+ return ordered;
561
+ }
562
+
563
+ // src/graph-merge/conflict-policy.ts
564
+ function buildBranchRank(branchOrder, allBranchIds) {
565
+ const rank = /* @__PURE__ */ new Map();
566
+ let next = 0;
567
+ for (const branchId of branchOrder) {
568
+ if (!rank.has(branchId)) {
569
+ rank.set(branchId, next);
570
+ next += 1;
571
+ }
572
+ }
573
+ const remaining = allBranchIds.filter((branchId) => !rank.has(branchId)).sort((left, right) => compareStrings(left, right));
574
+ for (const branchId of remaining) {
575
+ if (!rank.has(branchId)) {
576
+ rank.set(branchId, next);
577
+ next += 1;
578
+ }
579
+ }
580
+ return rank;
581
+ }
582
+ function pickByPriority(values, branchRank) {
583
+ let chosen = values[0];
584
+ let chosenRank = branchRank.get(chosen.branchId) ?? Number.MAX_SAFE_INTEGER;
585
+ for (const candidate of values.slice(1)) {
586
+ const candidateRank = branchRank.get(candidate.branchId) ?? Number.MAX_SAFE_INTEGER;
587
+ if (candidateRank < chosenRank) {
588
+ chosen = candidate;
589
+ chosenRank = candidateRank;
590
+ } else if (candidateRank === chosenRank && canonicalValueKey(candidate.value) < canonicalValueKey(chosen.value)) {
591
+ chosen = candidate;
592
+ }
593
+ }
594
+ return chosen.value;
595
+ }
596
+ function pickByWeight(values, weights, branchRank) {
597
+ let chosen = values[0];
598
+ let chosenWeight = weights.get(chosen.branchId) ?? 0;
599
+ let chosenRank = branchRank.get(chosen.branchId) ?? Number.MAX_SAFE_INTEGER;
600
+ for (const candidate of values.slice(1)) {
601
+ const candidateWeight = weights.get(candidate.branchId) ?? 0;
602
+ const candidateRank = branchRank.get(candidate.branchId) ?? Number.MAX_SAFE_INTEGER;
603
+ if (candidateWeight > chosenWeight) {
604
+ chosen = candidate;
605
+ chosenWeight = candidateWeight;
606
+ chosenRank = candidateRank;
607
+ continue;
608
+ }
609
+ if (candidateWeight === chosenWeight) {
610
+ if (candidateRank < chosenRank) {
611
+ chosen = candidate;
612
+ chosenRank = candidateRank;
613
+ } else if (candidateRank === chosenRank && canonicalValueKey(candidate.value) < canonicalValueKey(chosen.value)) {
614
+ chosen = candidate;
615
+ }
616
+ }
617
+ }
618
+ return chosen.value;
619
+ }
620
+ function allValuesEqual(values) {
621
+ if (values.length <= 1) {
622
+ return true;
623
+ }
624
+ const first = canonicalValueKey(values[0].value);
625
+ return values.every(
626
+ (candidate) => canonicalValueKey(candidate.value) === first
627
+ );
628
+ }
629
+ function collectConflictingValues(property, contributions) {
630
+ const seen = /* @__PURE__ */ new Set();
631
+ const values = [];
632
+ for (const { branchId, props } of contributions) {
633
+ if (!(property in props)) {
634
+ continue;
635
+ }
636
+ const value = props[property];
637
+ const dedupeKey2 = `${branchId}\0${canonicalValueKey(value)}`;
638
+ if (seen.has(dedupeKey2)) {
639
+ continue;
640
+ }
641
+ seen.add(dedupeKey2);
642
+ values.push({ branchId, value });
643
+ }
644
+ return [...values].sort((left, right) => {
645
+ const byBranch = compareStrings(left.branchId, right.branchId);
646
+ return byBranch === 0 ? compareStrings(
647
+ canonicalValueKey(left.value),
648
+ canonicalValueKey(right.value)
649
+ ) : byBranch;
650
+ });
651
+ }
652
+ function resolveConflictValue(input, context, branchRank, makeConflict) {
653
+ if (allValuesEqual(input.values)) {
654
+ return {
655
+ value: input.values[0]?.value ?? input.canonicalValue,
656
+ conflicted: false
657
+ };
658
+ }
659
+ const { policy } = context;
660
+ if (policy === "flag") {
661
+ return { value: input.canonicalValue, conflicted: true };
662
+ }
663
+ if (policy === "lastWriteWins") {
664
+ return {
665
+ value: pickByPriority(input.values, branchRank),
666
+ conflicted: true
667
+ };
668
+ }
669
+ if (policy === "provenanceWeighted") {
670
+ const weights = context.weights ?? /* @__PURE__ */ new Map();
671
+ return {
672
+ value: pickByWeight(input.values, weights, branchRank),
673
+ conflicted: true
674
+ };
675
+ }
676
+ const provisional = pickByPriority(input.values, branchRank);
677
+ const resolved = policy(makeConflict(provisional));
678
+ return { value: resolved, conflicted: true };
679
+ }
680
+ function resolvePropertyUnion(input, context, branchRank) {
681
+ const makeRecord = (resolution) => ({
682
+ entityId: input.entityId,
683
+ kind: input.kind,
684
+ property: input.property,
685
+ values: input.reportedValues,
686
+ resolution
687
+ });
688
+ const resolved = resolveConflictValue(
689
+ {
690
+ property: input.property,
691
+ values: input.values,
692
+ canonicalValue: input.canonicalValue
693
+ },
694
+ context,
695
+ branchRank,
696
+ (resolution) => makeRecord(resolution)
697
+ );
698
+ return {
699
+ value: resolved.value,
700
+ conflict: resolved.conflicted ? makeRecord(resolved.value) : void 0
701
+ };
702
+ }
703
+
704
+ // src/graph-merge/canonicalize.ts
705
+ var BASE_PROVENANCE_BRANCH = asBranchId("__committed_base__");
706
+ function pickClusterSurvivor(members, cluster, canonicalOverride, preferKind) {
707
+ const byKey = (left, right) => compareMergeKeys(mergeKeyOf(left), mergeKeyOf(right));
708
+ const baseMembers = members.filter((member) => member.origin === "base").sort(byKey);
709
+ if (baseMembers.length > 0) {
710
+ return baseMembers[0];
711
+ }
712
+ let canonicalId;
713
+ if (canonicalOverride !== void 0) {
714
+ const chosen = canonicalOverride({ members: cluster.members.map(idOf) });
715
+ if (members.some((member) => member.id === chosen)) {
716
+ canonicalId = chosen;
717
+ }
718
+ }
719
+ if (canonicalId === void 0) {
720
+ const minIdentity = [...cluster.members].sort(
721
+ (left, right) => compareMergeKeys(left, right)
722
+ )[0];
723
+ canonicalId = minIdentity === void 0 ? members[0].id : idOf(minIdentity);
724
+ }
725
+ const membersAtId = members.filter((member) => member.id === canonicalId);
726
+ if (membersAtId.length === 0) {
727
+ return [...members].sort(byKey)[0];
728
+ }
729
+ const kinds = [...new Set(membersAtId.map((member) => member.kind))];
730
+ const chosenKind = preferKind !== void 0 && kinds.length > 1 ? preferKind(kinds) : void 0;
731
+ return (chosenKind === void 0 ? void 0 : membersAtId.find((member) => member.kind === chosenKind)) ?? [...membersAtId].sort(byKey)[0];
732
+ }
733
+ function findPreferredMember(members, property, preferredBranchId) {
734
+ if (preferredBranchId === void 0) {
735
+ return void 0;
736
+ }
737
+ return members.find(
738
+ (member) => member.branchId === preferredBranchId && property in member.props
739
+ );
740
+ }
741
+ function memberPropertyValue(member, property) {
742
+ if (!(property in member.props)) {
743
+ return void 0;
744
+ }
745
+ return member.props[property];
746
+ }
747
+ function pickCanonicalPropertyValue(canonicalMember, members, property, values, preferredBranchId) {
748
+ const preferredMember = findPreferredMember(
749
+ members,
750
+ property,
751
+ preferredBranchId
752
+ );
753
+ const preferredValue = preferredMember === void 0 ? void 0 : memberPropertyValue(preferredMember, property);
754
+ if (preferredValue !== void 0) {
755
+ return preferredValue;
756
+ }
757
+ const canonicalValue = memberPropertyValue(canonicalMember, property);
758
+ return canonicalValue === void 0 ? values[0].value : canonicalValue;
759
+ }
760
+ function unionProperties(canonicalMember, members, context, baseContext, branchRank, preferredBranchId) {
761
+ const canonicalId = canonicalMember.id;
762
+ const kind = canonicalMember.kind;
763
+ const hasBaseMember = members.some((member) => member.origin === "base");
764
+ const propertyNames = /* @__PURE__ */ new Set();
765
+ for (const member of members) {
766
+ for (const name of Object.keys(member.props)) {
767
+ propertyNames.add(name);
768
+ }
769
+ }
770
+ const props = {};
771
+ const conflicts = [];
772
+ for (const property of [...propertyNames].sort(
773
+ (left, right) => compareStrings(left, right)
774
+ )) {
775
+ const values = collectConflictingValues(property, members);
776
+ if (values.length === 0) {
777
+ continue;
778
+ }
779
+ const canonicalValue = pickCanonicalPropertyValue(
780
+ canonicalMember,
781
+ members,
782
+ property,
783
+ values,
784
+ preferredBranchId
785
+ );
786
+ const baseInvolved = hasBaseMember && members.some(
787
+ (member) => member.origin === "base" && property in member.props
788
+ );
789
+ const reportedValues = values.filter(
790
+ (value2) => value2.branchId !== BASE_PROVENANCE_BRANCH && value2.branchId !== preferredBranchId
791
+ );
792
+ const { value, conflict } = resolvePropertyUnion(
793
+ {
794
+ entityId: canonicalId,
795
+ kind,
796
+ property,
797
+ values,
798
+ reportedValues,
799
+ canonicalValue
800
+ },
801
+ baseInvolved ? baseContext : context,
802
+ branchRank
803
+ );
804
+ props[property] = value;
805
+ if (conflict !== void 0) {
806
+ conflicts.push(conflict);
807
+ }
808
+ }
809
+ return { props, conflicts };
810
+ }
811
+ function canonicalizeCluster(cluster, members, policy, branchRank, weights, canonicalOverride, basePolicy = "flag", preferKind, preferredBranchId) {
812
+ const survivor = pickClusterSurvivor(
813
+ members,
814
+ cluster,
815
+ canonicalOverride,
816
+ preferKind
817
+ );
818
+ const canonicalId = survivor.id;
819
+ const kind = survivor.kind;
820
+ const context = {
821
+ policy,
822
+ ...weights === void 0 ? {} : { weights }
823
+ };
824
+ const baseContext = {
825
+ policy: basePolicy,
826
+ ...weights === void 0 ? {} : { weights }
827
+ };
828
+ const { props, conflicts } = unionProperties(
829
+ survivor,
830
+ members,
831
+ context,
832
+ baseContext,
833
+ branchRank,
834
+ preferredBranchId
835
+ );
836
+ const memberIds = [
837
+ ...new Set(cluster.members.map((member) => idOf(member)))
838
+ ].sort((left, right) => compareStrings(left, right));
839
+ const branchOrigins = [
840
+ ...new Set(
841
+ members.map((member) => member.branchId).filter(
842
+ (branchId) => branchId !== BASE_PROVENANCE_BRANCH && branchId !== preferredBranchId
843
+ )
844
+ )
845
+ ].sort((left, right) => compareStrings(left, right));
846
+ const resolution = {
847
+ canonicalId,
848
+ memberIds,
849
+ kind,
850
+ branchOrigins
851
+ };
852
+ return { canonicalId, kind, props, resolution, conflicts };
853
+ }
854
+
855
+ // src/graph-merge/union-find.ts
856
+ var UnionFind = class {
857
+ constructor(compare) {
858
+ this.compare = compare;
859
+ }
860
+ parent = /* @__PURE__ */ new Map();
861
+ /** Adds `value` as its own singleton set if it is not already present. */
862
+ add(value) {
863
+ if (!this.parent.has(value)) {
864
+ this.parent.set(value, value);
865
+ }
866
+ }
867
+ /** Returns the representative of `value`'s set, compressing the path to it. */
868
+ find(value) {
869
+ this.add(value);
870
+ let root = value;
871
+ while (this.parent.get(root) !== root) {
872
+ root = this.parent.get(root);
873
+ }
874
+ let cursor = value;
875
+ while (cursor !== root) {
876
+ const next = this.parent.get(cursor);
877
+ this.parent.set(cursor, root);
878
+ cursor = next;
879
+ }
880
+ return root;
881
+ }
882
+ /** Merges the sets of `left` and `right`; the `compare`-minimal root wins. */
883
+ union(left, right) {
884
+ const leftRoot = this.find(left);
885
+ const rightRoot = this.find(right);
886
+ if (leftRoot === rightRoot) {
887
+ return;
888
+ }
889
+ if (this.compare(leftRoot, rightRoot) <= 0) {
890
+ this.parent.set(rightRoot, leftRoot);
891
+ } else {
892
+ this.parent.set(leftRoot, rightRoot);
893
+ }
894
+ }
895
+ /** Every value seen by {@link add}/{@link find}/{@link union}, insertion order. */
896
+ members() {
897
+ return [...this.parent.keys()];
898
+ }
899
+ };
900
+
901
+ // src/graph-merge/closures.ts
902
+ var SUB_CLASS_OF_META_EDGE = "subClassOf";
903
+ var EQUIVALENT_TO_META_EDGE = "equivalentTo";
904
+ function buildSubClassClosure(ontology) {
905
+ const unionFind = new UnionFind(compareStrings);
906
+ for (const relation of ontology) {
907
+ unionFind.add(relation.from);
908
+ unionFind.add(relation.to);
909
+ if (relation.metaEdge === EQUIVALENT_TO_META_EDGE) {
910
+ unionFind.union(relation.from, relation.to);
911
+ }
912
+ }
913
+ const canonicalOf = /* @__PURE__ */ new Map();
914
+ for (const value of unionFind.members()) {
915
+ canonicalOf.set(value, unionFind.find(value));
916
+ }
917
+ const relationKeys = /* @__PURE__ */ new Set();
918
+ const relations = [];
919
+ for (const relation of ontology) {
920
+ if (relation.metaEdge !== SUB_CLASS_OF_META_EDGE) {
921
+ continue;
922
+ }
923
+ const child = canonicalOf.get(relation.from) ?? relation.from;
924
+ const parent = canonicalOf.get(relation.to) ?? relation.to;
925
+ if (child === parent) {
926
+ continue;
927
+ }
928
+ const key = `${child}\0${parent}`;
929
+ if (relationKeys.has(key)) {
930
+ continue;
931
+ }
932
+ relationKeys.add(key);
933
+ relations.push([child, parent]);
934
+ }
935
+ return {
936
+ closure: computeTransitiveClosure(relations),
937
+ canonicalOf
938
+ };
939
+ }
940
+ function canonicalType(closure, type) {
941
+ return closure.canonicalOf.get(type) ?? type;
942
+ }
943
+ function isReachable2(closure, from, to) {
944
+ const fromRep = canonicalType(closure, from);
945
+ const toRep = canonicalType(closure, to);
946
+ if (fromRep === toRep) {
947
+ return from !== to;
948
+ }
949
+ return isReachable(closure.closure, fromRep, toRep);
950
+ }
951
+
952
+ // src/graph-merge/similarity.ts
953
+ var MIN_SCORE = 0;
954
+ var MAX_SCORE = 1;
955
+ var NO_EVIDENCE_SCORE = -1;
956
+ var TRIGRAM_LENGTH = 3;
957
+ var TRIGRAM_PAD = " ";
958
+ function clampScore(value) {
959
+ if (Number.isNaN(value)) {
960
+ return MIN_SCORE;
961
+ }
962
+ if (value < MIN_SCORE) {
963
+ return MIN_SCORE;
964
+ }
965
+ if (value > MAX_SCORE) {
966
+ return MAX_SCORE;
967
+ }
968
+ return value;
969
+ }
970
+ function readField(node, field) {
971
+ return node[field];
972
+ }
973
+ function fieldText(node, fields) {
974
+ const parts = [];
975
+ for (const field of fields) {
976
+ const value = readField(node, field);
977
+ if (value === void 0 || value === null) {
978
+ continue;
979
+ }
980
+ parts.push(stringifyFieldValue(value));
981
+ }
982
+ return parts.join(TRIGRAM_PAD).toLowerCase();
983
+ }
984
+ function stringifyFieldValue(value) {
985
+ if (typeof value === "string") {
986
+ return value;
987
+ }
988
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
989
+ return String(value);
990
+ }
991
+ return canonicalValueKey(value);
992
+ }
993
+ function trigramMultiset(text) {
994
+ const counts = /* @__PURE__ */ new Map();
995
+ if (text.length === 0) {
996
+ return counts;
997
+ }
998
+ const padded = `${TRIGRAM_PAD}${text}${TRIGRAM_PAD}`;
999
+ for (let index = 0; index + TRIGRAM_LENGTH <= padded.length; index += 1) {
1000
+ const gram = padded.slice(index, index + TRIGRAM_LENGTH);
1001
+ counts.set(gram, (counts.get(gram) ?? 0) + 1);
1002
+ }
1003
+ return counts;
1004
+ }
1005
+ function multisetSize(counts) {
1006
+ let total = 0;
1007
+ for (const count of counts.values()) {
1008
+ total += count;
1009
+ }
1010
+ return total;
1011
+ }
1012
+ function multisetIntersectionSize(left, right) {
1013
+ let shared = 0;
1014
+ for (const [gram, leftCount] of left) {
1015
+ const rightCount = right.get(gram);
1016
+ if (rightCount !== void 0) {
1017
+ shared += Math.min(leftCount, rightCount);
1018
+ }
1019
+ }
1020
+ return shared;
1021
+ }
1022
+ function diceFromMultisets(left, right) {
1023
+ const leftSize = multisetSize(left);
1024
+ const rightSize = multisetSize(right);
1025
+ if (leftSize === 0 && rightSize === 0) {
1026
+ return MAX_SCORE;
1027
+ }
1028
+ if (leftSize === 0 || rightSize === 0) {
1029
+ return MIN_SCORE;
1030
+ }
1031
+ const shared = multisetIntersectionSize(left, right);
1032
+ return 2 * shared / (leftSize + rightSize);
1033
+ }
1034
+ function embeddingFields(strategy) {
1035
+ switch (strategy.kind) {
1036
+ case "vector": {
1037
+ return [strategy.field];
1038
+ }
1039
+ case "hybrid": {
1040
+ return strategy.fields;
1041
+ }
1042
+ case "fulltext":
1043
+ case "custom": {
1044
+ return void 0;
1045
+ }
1046
+ default: {
1047
+ const exhaustive = strategy;
1048
+ throw new Error(
1049
+ `Unhandled similarity strategy: ${JSON.stringify(exhaustive)}`
1050
+ );
1051
+ }
1052
+ }
1053
+ }
1054
+ function cosineSimilarity(left, right) {
1055
+ if (left.length !== right.length || left.length === 0) {
1056
+ return MIN_SCORE;
1057
+ }
1058
+ let dot = 0;
1059
+ let leftMagnitude = 0;
1060
+ let rightMagnitude = 0;
1061
+ for (let index = 0; index < left.length; index += 1) {
1062
+ const leftComponent = left[index];
1063
+ const rightComponent = right[index];
1064
+ dot += leftComponent * rightComponent;
1065
+ leftMagnitude += leftComponent * leftComponent;
1066
+ rightMagnitude += rightComponent * rightComponent;
1067
+ }
1068
+ if (leftMagnitude === 0 || rightMagnitude === 0) {
1069
+ return MIN_SCORE;
1070
+ }
1071
+ return dot / (Math.sqrt(leftMagnitude) * Math.sqrt(rightMagnitude));
1072
+ }
1073
+ function cosineFromLookup(embeddings, leftText, rightText) {
1074
+ const leftVector = embeddings.get(leftText);
1075
+ const rightVector = embeddings.get(rightText);
1076
+ if (leftVector === void 0 || rightVector === void 0) {
1077
+ return MIN_SCORE;
1078
+ }
1079
+ return cosineSimilarity(leftVector, rightVector);
1080
+ }
1081
+ var DEFAULT_HYBRID_WEIGHT = 0.5;
1082
+ function nonNegativeFinite(value) {
1083
+ return Number.isFinite(value) ? Math.max(0, value) : 0;
1084
+ }
1085
+ function normalizeHybridWeights(weights) {
1086
+ const vector = nonNegativeFinite(weights?.vector ?? DEFAULT_HYBRID_WEIGHT);
1087
+ const fulltext = nonNegativeFinite(
1088
+ weights?.fulltext ?? DEFAULT_HYBRID_WEIGHT
1089
+ );
1090
+ const total = vector + fulltext;
1091
+ if (total === 0) {
1092
+ return { vector: DEFAULT_HYBRID_WEIGHT, fulltext: DEFAULT_HYBRID_WEIGHT };
1093
+ }
1094
+ return { vector: vector / total, fulltext: fulltext / total };
1095
+ }
1096
+ function embedderUnavailable(kind, ctx) {
1097
+ return new SimilarityUnavailableError(
1098
+ `Similarity strategy "${kind}" requires a configured embedder (MergeOptions.embedder); none was provided.`,
1099
+ { details: { strategy: kind, dialect: ctx.backend.dialect } }
1100
+ );
1101
+ }
1102
+ function scoringFields(strategy) {
1103
+ switch (strategy.kind) {
1104
+ case "fulltext":
1105
+ case "hybrid": {
1106
+ return strategy.fields;
1107
+ }
1108
+ case "vector": {
1109
+ return [strategy.field];
1110
+ }
1111
+ case "custom": {
1112
+ return [];
1113
+ }
1114
+ default: {
1115
+ const exhaustive = strategy;
1116
+ throw new Error(
1117
+ `Unhandled similarity strategy: ${JSON.stringify(exhaustive)}`
1118
+ );
1119
+ }
1120
+ }
1121
+ }
1122
+ var NO_GRAMS = /* @__PURE__ */ new Map();
1123
+ function prepareNode(node, strategy) {
1124
+ const text = fieldText(node, scoringFields(strategy));
1125
+ const grams = strategy.kind === "fulltext" || strategy.kind === "hybrid" ? trigramMultiset(text) : NO_GRAMS;
1126
+ return { node, text, grams };
1127
+ }
1128
+ function scorePrepared(left, right, strategy, ctx) {
1129
+ switch (strategy.kind) {
1130
+ case "custom": {
1131
+ const raw = strategy.score(left.node, right.node);
1132
+ return ok(clampScore(raw));
1133
+ }
1134
+ case "fulltext": {
1135
+ if (left.text.length === 0 || right.text.length === 0) {
1136
+ return ok(NO_EVIDENCE_SCORE);
1137
+ }
1138
+ return ok(clampScore(diceFromMultisets(left.grams, right.grams)));
1139
+ }
1140
+ case "vector": {
1141
+ if (ctx.embeddings === void 0) {
1142
+ return err(embedderUnavailable("vector", ctx));
1143
+ }
1144
+ if (left.text.length === 0 || right.text.length === 0) {
1145
+ return ok(NO_EVIDENCE_SCORE);
1146
+ }
1147
+ const cosine = cosineFromLookup(ctx.embeddings, left.text, right.text);
1148
+ return ok(clampScore(cosine));
1149
+ }
1150
+ case "hybrid": {
1151
+ if (ctx.embeddings === void 0) {
1152
+ return err(embedderUnavailable("hybrid", ctx));
1153
+ }
1154
+ if (left.text.length === 0 || right.text.length === 0) {
1155
+ return ok(NO_EVIDENCE_SCORE);
1156
+ }
1157
+ const vectorScore = clampScore(
1158
+ cosineFromLookup(ctx.embeddings, left.text, right.text)
1159
+ );
1160
+ const fulltextScore = diceFromMultisets(left.grams, right.grams);
1161
+ const weights = normalizeHybridWeights(strategy.weights);
1162
+ return ok(
1163
+ clampScore(
1164
+ weights.vector * vectorScore + weights.fulltext * fulltextScore
1165
+ )
1166
+ );
1167
+ }
1168
+ default: {
1169
+ const exhaustive = strategy;
1170
+ throw new Error(
1171
+ `Unhandled similarity strategy: ${JSON.stringify(exhaustive)}`
1172
+ );
1173
+ }
1174
+ }
1175
+ }
1176
+ function createPairScorer(strategy, ctx) {
1177
+ const prepared = /* @__PURE__ */ new Map();
1178
+ const prepare = (key, node) => {
1179
+ const cached = prepared.get(key);
1180
+ if (cached !== void 0) {
1181
+ return cached;
1182
+ }
1183
+ const fresh = prepareNode(node, strategy);
1184
+ prepared.set(key, fresh);
1185
+ return fresh;
1186
+ };
1187
+ return (a, left, b, right) => scorePrepared(prepare(a, left), prepare(b, right), strategy, ctx);
1188
+ }
1189
+
1190
+ // src/graph-merge/scoring.ts
1191
+ var FORCED_MATCH_SCORE = 1;
1192
+ function compareCandidateEdges(left, right) {
1193
+ const byA = compareMergeKeys(left.a, right.a);
1194
+ return byA === 0 ? compareMergeKeys(left.b, right.b) : byA;
1195
+ }
1196
+ function endpointKey(a, b) {
1197
+ return JSON.stringify([a, b]);
1198
+ }
1199
+ function scoreCandidates(input, resolveConfig, ctx, ceilingPolicy, maxComparisonsPerKind) {
1200
+ const { similarity, threshold } = resolveConfig;
1201
+ const seen = /* @__PURE__ */ new Set();
1202
+ const forced = [];
1203
+ for (const edge of input.forcedEdges) {
1204
+ const key = endpointKey(edge.a, edge.b);
1205
+ if (!seen.has(key)) {
1206
+ seen.add(key);
1207
+ forced.push(edge);
1208
+ }
1209
+ }
1210
+ const fuzzy = [];
1211
+ for (const pair of input.pairs) {
1212
+ const key = endpointKey(pair.a, pair.b);
1213
+ if (!seen.has(key)) {
1214
+ seen.add(key);
1215
+ fuzzy.push(pair);
1216
+ }
1217
+ }
1218
+ if (maxComparisonsPerKind !== void 0 && fuzzy.length > maxComparisonsPerKind) {
1219
+ if (ceilingPolicy === "error") {
1220
+ return err(
1221
+ new MergeError(
1222
+ `Comparison ceiling exceeded: ${fuzzy.length} candidate pairs > maxComparisonsPerKind ${maxComparisonsPerKind}.`,
1223
+ {
1224
+ details: {
1225
+ comparisons: fuzzy.length,
1226
+ limit: maxComparisonsPerKind
1227
+ },
1228
+ suggestion: `Tighten the kind's block() to shrink buckets, or set onComparisonCeiling: "mergeByIdOnly".`
1229
+ }
1230
+ )
1231
+ );
1232
+ }
1233
+ return ok({
1234
+ edges: [...forced].sort(
1235
+ (left, right) => compareCandidateEdges(left, right)
1236
+ ),
1237
+ warnings: [
1238
+ {
1239
+ kind: "comparisonCeiling",
1240
+ comparisons: fuzzy.length,
1241
+ limit: maxComparisonsPerKind,
1242
+ message: `Skipped similarity for this kind: ${fuzzy.length} candidate pairs exceeded maxComparisonsPerKind ${maxComparisonsPerKind}; nodes will merge by id and exact unique match only.`
1243
+ }
1244
+ ]
1245
+ });
1246
+ }
1247
+ const scorer = createPairScorer(similarity, ctx);
1248
+ const edges = [...forced];
1249
+ for (const { left, right, a, b } of fuzzy) {
1250
+ const scored = scorer(a, left, b, right);
1251
+ if (isErr(scored)) {
1252
+ return err(scored.error);
1253
+ }
1254
+ if (scored.data >= threshold) {
1255
+ edges.push({ a, b, score: scored.data });
1256
+ }
1257
+ }
1258
+ return ok({
1259
+ edges: edges.sort((left, right) => compareCandidateEdges(left, right)),
1260
+ warnings: []
1261
+ });
1262
+ }
1263
+
1264
+ // src/graph-merge/clustering.ts
1265
+ function buildComponents(edges, nodeIds, edgesSorted = false) {
1266
+ const forest = new UnionFind(compareMergeKeys);
1267
+ const seed = new Set(nodeIds);
1268
+ for (const id of nodeIds) {
1269
+ forest.add(id);
1270
+ }
1271
+ const orderedEdges = edgesSorted ? edges : [...edges].sort((left, right) => compareCandidateEdges(left, right));
1272
+ for (const edge of orderedEdges) {
1273
+ if (!seed.has(edge.a) || !seed.has(edge.b)) {
1274
+ continue;
1275
+ }
1276
+ forest.union(edge.a, edge.b);
1277
+ }
1278
+ const byRoot = /* @__PURE__ */ new Map();
1279
+ for (const id of forest.members()) {
1280
+ const root = forest.find(id);
1281
+ const bucket = byRoot.get(root);
1282
+ if (bucket === void 0) {
1283
+ byRoot.set(root, [id]);
1284
+ } else {
1285
+ bucket.push(id);
1286
+ }
1287
+ }
1288
+ const clusters = [];
1289
+ for (const members of byRoot.values()) {
1290
+ clusters.push({
1291
+ members: [...members].sort(
1292
+ (left, right) => compareMergeKeys(left, right)
1293
+ )
1294
+ });
1295
+ }
1296
+ clusters.sort(
1297
+ (left, right) => compareMergeKeys(left.members[0], right.members[0])
1298
+ );
1299
+ return clusters;
1300
+ }
1301
+ function adjacencyOf(members, edges) {
1302
+ const memberSet = new Set(members);
1303
+ const adjacency = /* @__PURE__ */ new Map();
1304
+ for (const id of members) {
1305
+ adjacency.set(id, /* @__PURE__ */ new Set());
1306
+ }
1307
+ for (const edge of edges) {
1308
+ if (memberSet.has(edge.a) && memberSet.has(edge.b)) {
1309
+ adjacency.get(edge.a).add(edge.b);
1310
+ adjacency.get(edge.b).add(edge.a);
1311
+ }
1312
+ }
1313
+ return adjacency;
1314
+ }
1315
+ function exceedsDiameter(members, edges, maxDiameter) {
1316
+ if (members.length <= 1) {
1317
+ return false;
1318
+ }
1319
+ const adjacency = adjacencyOf(members, edges);
1320
+ for (const source of members) {
1321
+ const distance = /* @__PURE__ */ new Map([[source, 0]]);
1322
+ const queue = [source];
1323
+ let head = 0;
1324
+ while (head < queue.length) {
1325
+ const current = queue[head];
1326
+ head += 1;
1327
+ const currentDistance = distance.get(current);
1328
+ for (const neighbor of adjacency.get(current)) {
1329
+ if (!distance.has(neighbor)) {
1330
+ const neighborDistance = currentDistance + 1;
1331
+ if (neighborDistance > maxDiameter) {
1332
+ return true;
1333
+ }
1334
+ distance.set(neighbor, neighborDistance);
1335
+ queue.push(neighbor);
1336
+ }
1337
+ }
1338
+ }
1339
+ if (distance.size < members.length) {
1340
+ return true;
1341
+ }
1342
+ }
1343
+ return false;
1344
+ }
1345
+ function internalEdges(members, edges) {
1346
+ const memberSet = new Set(members);
1347
+ return edges.filter((edge) => memberSet.has(edge.a) && memberSet.has(edge.b)).sort((left, right) => compareCandidateEdges(left, right));
1348
+ }
1349
+ function weakestEdge(edges) {
1350
+ return edges.reduce((current, candidate) => {
1351
+ if (candidate.score !== current.score) {
1352
+ return candidate.score < current.score ? candidate : current;
1353
+ }
1354
+ return compareCandidateEdges(candidate, current) < 0 ? candidate : current;
1355
+ });
1356
+ }
1357
+ function splitUntil(members, edges, isOffending) {
1358
+ let surviving = internalEdges(members, edges);
1359
+ for (; ; ) {
1360
+ const components = buildComponents(surviving, members, true);
1361
+ const offending = components.filter(
1362
+ (component) => isOffending(component.members, surviving)
1363
+ );
1364
+ if (offending.length === 0) {
1365
+ return components;
1366
+ }
1367
+ let splittable;
1368
+ for (const component of offending) {
1369
+ const componentEdges = internalEdges(component.members, surviving);
1370
+ if (componentEdges.length > 0) {
1371
+ splittable = componentEdges;
1372
+ break;
1373
+ }
1374
+ }
1375
+ if (splittable === void 0) {
1376
+ const offendingSet = new Set(offending);
1377
+ return components.flatMap(
1378
+ (component) => offendingSet.has(component) ? component.members.map((id) => ({ members: [id] })) : [component]
1379
+ );
1380
+ }
1381
+ const weakest = weakestEdge(splittable);
1382
+ surviving = surviving.filter((edge) => edge !== weakest);
1383
+ }
1384
+ }
1385
+ function splitByDropWeakest(members, edges, maxDiameter) {
1386
+ return splitUntil(
1387
+ members,
1388
+ edges,
1389
+ (componentMembers, surviving) => exceedsDiameter(componentMembers, surviving, maxDiameter)
1390
+ );
1391
+ }
1392
+ function connectedComponents(candidateEdges, nodeIds) {
1393
+ return buildComponents(candidateEdges, nodeIds);
1394
+ }
1395
+ function enforceDiameter(clusters, candidateEdges, clusterMaxDiameter) {
1396
+ const guarded = [];
1397
+ for (const component of clusters) {
1398
+ if (exceedsDiameter(component.members, candidateEdges, clusterMaxDiameter)) {
1399
+ guarded.push(
1400
+ ...splitByDropWeakest(
1401
+ component.members,
1402
+ candidateEdges,
1403
+ clusterMaxDiameter
1404
+ )
1405
+ );
1406
+ } else {
1407
+ guarded.push(component);
1408
+ }
1409
+ }
1410
+ guarded.sort(
1411
+ (left, right) => compareMergeKeys(left.members[0], right.members[0])
1412
+ );
1413
+ return guarded;
1414
+ }
1415
+ function countBaseMembers(members, baseIds) {
1416
+ let count = 0;
1417
+ for (const id of members) {
1418
+ if (baseIds.has(id)) {
1419
+ count += 1;
1420
+ }
1421
+ }
1422
+ return count;
1423
+ }
1424
+ function splitByBaseMultiplicity(members, edges, baseIds) {
1425
+ return splitUntil(
1426
+ members,
1427
+ edges,
1428
+ (componentMembers) => countBaseMembers(componentMembers, baseIds) >= 2
1429
+ );
1430
+ }
1431
+ function enforceBaseGuard(clusters, candidateEdges, baseIds) {
1432
+ if (baseIds.size === 0) {
1433
+ return { clusters, events: [] };
1434
+ }
1435
+ const result = [];
1436
+ const events = [];
1437
+ for (const cluster of clusters) {
1438
+ const clusterBaseIds = cluster.members.filter((id) => baseIds.has(id));
1439
+ if (clusterBaseIds.length < 2) {
1440
+ result.push(cluster);
1441
+ continue;
1442
+ }
1443
+ events.push({
1444
+ baseIds: [...clusterBaseIds].sort(
1445
+ (left, right) => compareMergeKeys(left, right)
1446
+ ),
1447
+ memberIds: [...cluster.members].sort(
1448
+ (left, right) => compareMergeKeys(left, right)
1449
+ )
1450
+ });
1451
+ result.push(
1452
+ ...splitByBaseMultiplicity(cluster.members, candidateEdges, baseIds)
1453
+ );
1454
+ }
1455
+ result.sort(
1456
+ (left, right) => compareMergeKeys(left.members[0], right.members[0])
1457
+ );
1458
+ return { clusters: result, events };
1459
+ }
1460
+
1461
+ // src/graph-merge/delete-modify.ts
1462
+ var DELETED_NODE_DROP_REASON = "delete-modify:deleteWins";
1463
+ function pickHighestPriorityBranch(branchIds, branchRank) {
1464
+ let chosen = branchIds[0];
1465
+ let chosenRank = branchRank.get(chosen) ?? Number.MAX_SAFE_INTEGER;
1466
+ for (const candidate of branchIds.slice(1)) {
1467
+ const candidateRank = branchRank.get(candidate) ?? Number.MAX_SAFE_INTEGER;
1468
+ if (candidateRank < chosenRank || candidateRank === chosenRank && compareStrings(candidate, chosen) < 0) {
1469
+ chosen = candidate;
1470
+ chosenRank = candidateRank;
1471
+ }
1472
+ }
1473
+ return chosen;
1474
+ }
1475
+ function effectiveDeleteModifyPolicy(policy, deletions, modifications, preferredBranchId) {
1476
+ if (preferredBranchId !== void 0 && deletions.some((deletion) => deletion.branchId === preferredBranchId)) {
1477
+ return "deleteWins";
1478
+ }
1479
+ if (policy === "deleteWins" && preferredBranchId !== void 0 && modifications.some(
1480
+ (modification) => modification.branchId === preferredBranchId
1481
+ )) {
1482
+ return "modifyWins";
1483
+ }
1484
+ return policy;
1485
+ }
1486
+ function groupByEntity(items, entityOf) {
1487
+ const grouped = /* @__PURE__ */ new Map();
1488
+ for (const item of items) {
1489
+ const entity = entityOf(item);
1490
+ const key = mergeKey(entity.kind, entity.id);
1491
+ const bucket = grouped.get(key);
1492
+ if (bucket === void 0) {
1493
+ grouped.set(key, [item]);
1494
+ } else {
1495
+ bucket.push(item);
1496
+ }
1497
+ }
1498
+ return grouped;
1499
+ }
1500
+ function resolveDeleteModifyOver(modified, deleted, modifiedEntity, deletedEntity, droppedKind, policy, branchRank, preferredBranchId) {
1501
+ const modifiedByKey = groupByEntity(modified, modifiedEntity);
1502
+ const deletedByKey = groupByEntity(deleted, deletedEntity);
1503
+ const survivingModifications = [];
1504
+ const deletions = [];
1505
+ const conflicts = [];
1506
+ const dropped = [];
1507
+ const conflictedKeys = /* @__PURE__ */ new Set();
1508
+ for (const key of deletedByKey.keys()) {
1509
+ if (modifiedByKey.has(key)) {
1510
+ conflictedKeys.add(key);
1511
+ }
1512
+ }
1513
+ for (const [key, modifications] of modifiedByKey) {
1514
+ if (!conflictedKeys.has(key)) {
1515
+ for (const modification of modifications) {
1516
+ survivingModifications.push(modification);
1517
+ }
1518
+ continue;
1519
+ }
1520
+ const deletionsForKey = deletedByKey.get(key);
1521
+ const { id, kind } = modifiedEntity(modifications[0]);
1522
+ const deletedBy = pickHighestPriorityBranch(
1523
+ deletionsForKey.map((deletion) => deletion.branchId),
1524
+ branchRank
1525
+ );
1526
+ const modifiedBy = pickHighestPriorityBranch(
1527
+ modifications.map((modification) => modification.branchId),
1528
+ branchRank
1529
+ );
1530
+ const effectivePolicy = effectiveDeleteModifyPolicy(
1531
+ policy,
1532
+ deletionsForKey,
1533
+ modifications,
1534
+ preferredBranchId
1535
+ );
1536
+ conflicts.push({
1537
+ entityId: id,
1538
+ kind,
1539
+ deletedBy,
1540
+ modifiedBy,
1541
+ resolution: effectivePolicy
1542
+ });
1543
+ if (effectivePolicy === "deleteWins") {
1544
+ deletions.push({ id, kind });
1545
+ dropped.push({ kind: droppedKind, id, reason: DELETED_NODE_DROP_REASON });
1546
+ continue;
1547
+ }
1548
+ for (const modification of modifications) {
1549
+ survivingModifications.push(modification);
1550
+ }
1551
+ }
1552
+ for (const [key, deletionsForKey] of deletedByKey) {
1553
+ if (conflictedKeys.has(key)) {
1554
+ continue;
1555
+ }
1556
+ const { id, kind } = deletedEntity(deletionsForKey[0]);
1557
+ deletions.push({ id, kind });
1558
+ }
1559
+ return {
1560
+ survivingModifications: [...survivingModifications].sort((left, right) => {
1561
+ const byId2 = compareStrings(
1562
+ modifiedEntity(left).id,
1563
+ modifiedEntity(right).id
1564
+ );
1565
+ return byId2 === 0 ? compareStrings(left.branchId, right.branchId) : byId2;
1566
+ }),
1567
+ deletions: [...deletions].sort(
1568
+ (left, right) => compareStrings(left.id, right.id)
1569
+ ),
1570
+ conflicts: [...conflicts].sort(
1571
+ (left, right) => compareStrings(left.entityId, right.entityId)
1572
+ ),
1573
+ dropped: [...dropped].sort(
1574
+ (left, right) => compareStrings(left.id, right.id)
1575
+ )
1576
+ };
1577
+ }
1578
+ function reconcileOver(survivingModifications, entityOf, rebuild, policy, branchRank, weights, preferredBranchId) {
1579
+ const context = {
1580
+ policy,
1581
+ ...weights === void 0 ? {} : { weights }
1582
+ };
1583
+ const grouped = groupByEntity(survivingModifications, entityOf);
1584
+ const out = [];
1585
+ const conflicts = [];
1586
+ for (const modifications of grouped.values()) {
1587
+ if (modifications.length === 1) {
1588
+ out.push(modifications[0]);
1589
+ continue;
1590
+ }
1591
+ const first = entityOf(modifications[0]);
1592
+ const { props, conflicts: propertyConflicts } = threeWayMergeProps(
1593
+ first.id,
1594
+ first.kind,
1595
+ first.baseProps,
1596
+ modifications.map((modification) => ({
1597
+ branchId: modification.branchId,
1598
+ forkProps: entityOf(modification).forkProps
1599
+ })),
1600
+ context,
1601
+ branchRank,
1602
+ preferredBranchId
1603
+ );
1604
+ const chosenBranch = pickHighestPriorityBranch(
1605
+ modifications.map((modification) => modification.branchId),
1606
+ branchRank
1607
+ );
1608
+ const representative = modifications.find(
1609
+ (modification) => modification.branchId === chosenBranch
1610
+ ) ?? modifications[0];
1611
+ out.push(rebuild(representative, chosenBranch, props));
1612
+ for (const conflict of propertyConflicts) {
1613
+ conflicts.push(conflict);
1614
+ }
1615
+ }
1616
+ return {
1617
+ survivingModifications: [...out].sort((left, right) => {
1618
+ const byId2 = compareStrings(entityOf(left).id, entityOf(right).id);
1619
+ return byId2 === 0 ? compareStrings(left.branchId, right.branchId) : byId2;
1620
+ }),
1621
+ conflicts: conflicts.sort(
1622
+ (left, right) => compareStrings(
1623
+ `${left.entityId}|${left.property}`,
1624
+ `${right.entityId}|${right.property}`
1625
+ )
1626
+ )
1627
+ };
1628
+ }
1629
+ function resolveDeleteModify(staging, policy, branchRank, preferredBranchId) {
1630
+ const resolved = resolveDeleteModifyOver(
1631
+ staging.modifiedNodes,
1632
+ staging.deletedNodes,
1633
+ (item) => item.node,
1634
+ (item) => item.node,
1635
+ "node",
1636
+ policy,
1637
+ branchRank,
1638
+ preferredBranchId
1639
+ );
1640
+ return {
1641
+ survivingModifications: resolved.survivingModifications,
1642
+ nodeDeletions: resolved.deletions,
1643
+ conflicts: resolved.conflicts,
1644
+ dropped: resolved.dropped
1645
+ };
1646
+ }
1647
+ function threeWayMergeProps(entityId, kind, baseProps, contributions, context, branchRank, preferredBranchId) {
1648
+ const merged = {};
1649
+ const conflicts = [];
1650
+ const propertyNames = new Set(Object.keys(baseProps));
1651
+ for (const contribution of contributions) {
1652
+ for (const name of Object.keys(contribution.forkProps)) {
1653
+ propertyNames.add(name);
1654
+ }
1655
+ }
1656
+ for (const property of [...propertyNames].sort(compareStrings)) {
1657
+ const baseHas = property in baseProps;
1658
+ const baseKey = baseHas ? canonicalValueKey(baseProps[property]) : void 0;
1659
+ const changed = contributions.filter((contribution) => {
1660
+ if (!(property in contribution.forkProps)) {
1661
+ return false;
1662
+ }
1663
+ const value2 = contribution.forkProps[property];
1664
+ return !baseHas || canonicalValueKey(value2) !== baseKey;
1665
+ }).map((contribution) => ({
1666
+ branchId: contribution.branchId,
1667
+ props: contribution.forkProps
1668
+ }));
1669
+ const values = collectConflictingValues(property, changed);
1670
+ if (values.length === 0) {
1671
+ const deletedByFork = baseHas && contributions.some(
1672
+ (contribution) => !(property in contribution.forkProps)
1673
+ );
1674
+ if (baseHas && !deletedByFork) {
1675
+ merged[property] = baseProps[property];
1676
+ }
1677
+ continue;
1678
+ }
1679
+ const distinctValues = new Set(
1680
+ values.map((candidate) => canonicalValueKey(candidate.value))
1681
+ );
1682
+ if (distinctValues.size === 1) {
1683
+ merged[property] = values[0].value;
1684
+ continue;
1685
+ }
1686
+ const preferredValue = values.find(
1687
+ (value2) => value2.branchId === preferredBranchId
1688
+ )?.value;
1689
+ const canonicalValue = preferredValue ?? (baseHas ? baseProps[property] : values[0].value);
1690
+ const reportedValues = preferredBranchId === void 0 ? values : values.filter((value2) => value2.branchId !== preferredBranchId);
1691
+ const { value, conflict } = resolvePropertyUnion(
1692
+ { entityId, kind, property, values, reportedValues, canonicalValue },
1693
+ context,
1694
+ branchRank
1695
+ );
1696
+ merged[property] = value;
1697
+ if (conflict !== void 0) {
1698
+ conflicts.push(conflict);
1699
+ }
1700
+ }
1701
+ return { props: merged, conflicts };
1702
+ }
1703
+ function reconcileModifications(survivingModifications, policy, branchRank, weights, preferredBranchId) {
1704
+ return reconcileOver(
1705
+ survivingModifications,
1706
+ (item) => item.node,
1707
+ (representative, chosenBranch, mergedProps) => ({
1708
+ branchId: chosenBranch,
1709
+ node: { ...representative.node, forkProps: mergedProps }
1710
+ }),
1711
+ policy,
1712
+ branchRank,
1713
+ weights,
1714
+ preferredBranchId
1715
+ );
1716
+ }
1717
+ function resolveEdgeDeleteModify(staging, policy, branchRank, preferredBranchId) {
1718
+ const resolved = resolveDeleteModifyOver(
1719
+ staging.modifiedEdges,
1720
+ staging.deletedEdges,
1721
+ (item) => item.edge,
1722
+ (item) => item.edge,
1723
+ "edge",
1724
+ policy,
1725
+ branchRank,
1726
+ preferredBranchId
1727
+ );
1728
+ return {
1729
+ survivingModifications: resolved.survivingModifications,
1730
+ edgeDeletions: resolved.deletions,
1731
+ conflicts: resolved.conflicts,
1732
+ dropped: resolved.dropped
1733
+ };
1734
+ }
1735
+ function reconcileEdgeModifications(survivingModifications, policy, branchRank, weights, preferredBranchId) {
1736
+ return reconcileOver(
1737
+ survivingModifications,
1738
+ (item) => item.edge,
1739
+ (representative, chosenBranch, mergedProps) => ({
1740
+ branchId: chosenBranch,
1741
+ edge: { ...representative.edge, forkProps: mergedProps }
1742
+ }),
1743
+ policy,
1744
+ branchRank,
1745
+ weights,
1746
+ preferredBranchId
1747
+ );
1748
+ }
1749
+
1750
+ // src/graph-merge/edge-repoint.ts
1751
+ var ENDPOINT_DELETED_DROP_REASON = "edge:endpoint-deleted";
1752
+ function buildCanonicalMap(clusters, canonicalOf) {
1753
+ const map = /* @__PURE__ */ new Map();
1754
+ for (const cluster of clusters) {
1755
+ const canonical = canonicalOf(cluster);
1756
+ for (const member of cluster.members) {
1757
+ map.set(member, canonical);
1758
+ }
1759
+ }
1760
+ return map;
1761
+ }
1762
+ function repoint(key, canonicalOf) {
1763
+ return canonicalOf.get(key) ?? key;
1764
+ }
1765
+ function dedupeKey(fromKey, type, toKey, props) {
1766
+ return JSON.stringify([fromKey, type, toKey, canonicalizeProps(props)]);
1767
+ }
1768
+ function groupKey(fromKey, type, toKey) {
1769
+ return JSON.stringify([fromKey, type, toKey]);
1770
+ }
1771
+ function pickSurvivor(edges, preferredBranchId) {
1772
+ const preferred = preferredBranchId === void 0 ? [] : edges.filter((edge) => edge.staged.branchId === preferredBranchId);
1773
+ const candidates = preferred.length > 0 ? preferred : edges;
1774
+ let survivor = candidates[0];
1775
+ for (const candidate of candidates.slice(1)) {
1776
+ if (candidate.staged.id < survivor.staged.id) {
1777
+ survivor = candidate;
1778
+ }
1779
+ }
1780
+ return survivor;
1781
+ }
1782
+ function unionEdgeProps(survivorId, kind, survivor, edges, context, branchRank, preferredBranchId) {
1783
+ const propertyNames = /* @__PURE__ */ new Set();
1784
+ for (const edge of edges) {
1785
+ for (const name of Object.keys(edge.staged.props)) {
1786
+ propertyNames.add(name);
1787
+ }
1788
+ }
1789
+ const props = {};
1790
+ const conflicts = [];
1791
+ const contributions = edges.map((edge) => edge.staged);
1792
+ for (const property of [...propertyNames].sort(
1793
+ (left, right) => compareStrings(left, right)
1794
+ )) {
1795
+ const values = collectConflictingValues(property, contributions);
1796
+ if (values.length === 0) {
1797
+ continue;
1798
+ }
1799
+ const survivorValue = property in survivor.staged.props ? survivor.staged.props[property] : values[0].value;
1800
+ const canonicalValue = values.find((value2) => value2.branchId === preferredBranchId)?.value ?? survivorValue;
1801
+ const reportedValues = preferredBranchId === void 0 ? values : values.filter((value2) => value2.branchId !== preferredBranchId);
1802
+ const { value, conflict } = resolvePropertyUnion(
1803
+ {
1804
+ entityId: survivorId,
1805
+ kind,
1806
+ property,
1807
+ values,
1808
+ reportedValues,
1809
+ canonicalValue
1810
+ },
1811
+ context,
1812
+ branchRank
1813
+ );
1814
+ props[property] = value;
1815
+ if (conflict !== void 0) {
1816
+ conflicts.push(conflict);
1817
+ }
1818
+ }
1819
+ return { props, conflicts };
1820
+ }
1821
+ function repointEdges(stagedEdges, canonicalOf, deletedNodeIds, policy, branchRank, weights, preferredBranchId) {
1822
+ const dropped = [];
1823
+ const liveByDedupeKey = /* @__PURE__ */ new Map();
1824
+ const dedupeKeyByGroup = /* @__PURE__ */ new Map();
1825
+ for (const staged of stagedEdges) {
1826
+ const fromKey = repoint(
1827
+ mergeKey(staged.fromKind, staged.fromId),
1828
+ canonicalOf
1829
+ );
1830
+ const toKey = repoint(mergeKey(staged.toKind, staged.toId), canonicalOf);
1831
+ if (deletedNodeIds.has(fromKey) || deletedNodeIds.has(toKey)) {
1832
+ dropped.push({
1833
+ kind: "edge",
1834
+ id: staged.id,
1835
+ reason: ENDPOINT_DELETED_DROP_REASON
1836
+ });
1837
+ continue;
1838
+ }
1839
+ const repointed = { staged, fromKey, toKey };
1840
+ const key = dedupeKey(fromKey, staged.kind, toKey, staged.props);
1841
+ const bucket = liveByDedupeKey.get(key);
1842
+ if (bucket === void 0) {
1843
+ liveByDedupeKey.set(key, [repointed]);
1844
+ } else {
1845
+ bucket.push(repointed);
1846
+ }
1847
+ const group = groupKey(fromKey, staged.kind, toKey);
1848
+ const keysForGroup = dedupeKeyByGroup.get(group);
1849
+ if (keysForGroup === void 0) {
1850
+ dedupeKeyByGroup.set(group, /* @__PURE__ */ new Set([key]));
1851
+ } else {
1852
+ keysForGroup.add(key);
1853
+ }
1854
+ }
1855
+ const context = {
1856
+ policy,
1857
+ ...weights === void 0 ? {} : { weights }
1858
+ };
1859
+ const merged = [];
1860
+ const conflicts = [];
1861
+ const sortedGroups = [...dedupeKeyByGroup.keys()].sort(
1862
+ (left, right) => compareStrings(left, right)
1863
+ );
1864
+ for (const group of sortedGroups) {
1865
+ const dedupeKeys = [...dedupeKeyByGroup.get(group)];
1866
+ const groupEdges = [];
1867
+ for (const key of dedupeKeys) {
1868
+ for (const edge of liveByDedupeKey.get(key)) {
1869
+ groupEdges.push(edge);
1870
+ }
1871
+ }
1872
+ const survivor = pickSurvivor(groupEdges, preferredBranchId);
1873
+ const survivorId = survivor.staged.id;
1874
+ const mergedIds = [...groupEdges].map((edge) => edge.staged.id).sort((left, right) => compareStrings(left, right));
1875
+ const fromId = idOf(survivor.fromKey);
1876
+ const fromKind = kindOf(survivor.fromKey);
1877
+ const toId = idOf(survivor.toKey);
1878
+ const toKind = kindOf(survivor.toKey);
1879
+ if (dedupeKeys.length === 1) {
1880
+ merged.push({
1881
+ id: survivorId,
1882
+ kind: survivor.staged.kind,
1883
+ fromId,
1884
+ toId,
1885
+ fromKind,
1886
+ toKind,
1887
+ props: survivor.staged.props,
1888
+ mergedIds
1889
+ });
1890
+ continue;
1891
+ }
1892
+ const { props, conflicts: groupConflicts } = unionEdgeProps(
1893
+ survivorId,
1894
+ survivor.staged.kind,
1895
+ survivor,
1896
+ groupEdges,
1897
+ context,
1898
+ branchRank,
1899
+ preferredBranchId
1900
+ );
1901
+ for (const conflict of groupConflicts) {
1902
+ conflicts.push(conflict);
1903
+ }
1904
+ merged.push({
1905
+ id: survivorId,
1906
+ kind: survivor.staged.kind,
1907
+ fromId,
1908
+ toId,
1909
+ fromKind,
1910
+ toKind,
1911
+ props,
1912
+ mergedIds
1913
+ });
1914
+ }
1915
+ const sortedEdges = merged.map((edge) => ({
1916
+ edge,
1917
+ sortKey: dedupeKey(
1918
+ mergeKey(edge.fromKind, edge.fromId),
1919
+ edge.kind,
1920
+ mergeKey(edge.toKind, edge.toId),
1921
+ edge.props
1922
+ )
1923
+ })).sort((left, right) => compareStrings(left.sortKey, right.sortKey)).map(({ edge }) => edge);
1924
+ return {
1925
+ edges: sortedEdges,
1926
+ dropped: dropped.sort((left, right) => compareStrings(left.id, right.id)),
1927
+ conflicts: conflicts.sort(
1928
+ (left, right) => compareStrings(
1929
+ `${left.entityId}|${left.property}`,
1930
+ `${right.entityId}|${right.property}`
1931
+ )
1932
+ )
1933
+ };
1934
+ }
1935
+ var MERGE_OPTION_DEFAULTS = {
1936
+ reconcileTypes: "off",
1937
+ onPropertyConflict: "flag",
1938
+ onBasePropertyConflict: "flag",
1939
+ onDeleteModifyConflict: "flag",
1940
+ onComparisonCeiling: "error",
1941
+ provenance: true,
1942
+ persistProvenance: false
1943
+ };
1944
+ var propertyConflictPolicySchema = z.enum([
1945
+ "flag",
1946
+ "lastWriteWins",
1947
+ "provenanceWeighted"
1948
+ ]);
1949
+ var resolveConfigScalarSchema = z.object({
1950
+ threshold: z.number().min(0, { message: "threshold must be >= 0" }).max(1, { message: "threshold must be <= 1" })
1951
+ });
1952
+ var mergeOptionsScalarSchema = z.object({
1953
+ reconcileTypes: z.enum(["ontology", "off"]).default(MERGE_OPTION_DEFAULTS.reconcileTypes),
1954
+ onDeleteModifyConflict: z.enum(["deleteWins", "modifyWins", "flag"]).default(MERGE_OPTION_DEFAULTS.onDeleteModifyConflict),
1955
+ onComparisonCeiling: z.enum(["error", "mergeByIdOnly"]).default(MERGE_OPTION_DEFAULTS.onComparisonCeiling),
1956
+ provenance: z.boolean().default(MERGE_OPTION_DEFAULTS.provenance),
1957
+ persistProvenance: z.boolean().default(MERGE_OPTION_DEFAULTS.persistProvenance),
1958
+ maxComparisonsPerKind: z.number().int({ message: "maxComparisonsPerKind must be an integer" }).min(0, { message: "maxComparisonsPerKind must be >= 0" }).optional(),
1959
+ clusterMaxDiameter: z.number().positive({ message: "clusterMaxDiameter must be positive" }).optional()
1960
+ });
1961
+ function validatePropertyConflictPolicy(policy, label) {
1962
+ if (typeof policy === "string" && !propertyConflictPolicySchema.safeParse(policy).success) {
1963
+ throw new Error(
1964
+ `Invalid ${label} "${policy}": expected "flag", "lastWriteWins", "provenanceWeighted", or a function.`
1965
+ );
1966
+ }
1967
+ return policy;
1968
+ }
1969
+ function validateResolveMap(resolve) {
1970
+ if (resolve === void 0) {
1971
+ return {};
1972
+ }
1973
+ const configs = resolve;
1974
+ const validated = {};
1975
+ for (const [kind, config] of Object.entries(configs)) {
1976
+ const parsed = resolveConfigScalarSchema.safeParse({
1977
+ threshold: config.threshold
1978
+ });
1979
+ if (!parsed.success) {
1980
+ throw new Error(
1981
+ `Invalid resolve config for kind "${kind}": ${parsed.error.message}`
1982
+ );
1983
+ }
1984
+ if (config.keyless !== void 0 && (!Number.isInteger(config.keyless.window) || config.keyless.window < 1)) {
1985
+ throw new Error(
1986
+ `Invalid resolve config for kind "${kind}": keyless.window must be a positive integer, got ${config.keyless.window}.`
1987
+ );
1988
+ }
1989
+ const strategy = config.similarity;
1990
+ if (strategy.kind === "hybrid") {
1991
+ for (const component of ["vector", "fulltext"]) {
1992
+ const weight = strategy.weights?.[component];
1993
+ if (weight !== void 0 && (!Number.isFinite(weight) || weight < 0)) {
1994
+ throw new Error(
1995
+ `Invalid resolve config for kind "${kind}": similarity.weights.${component} must be a finite number >= 0, got ${String(weight)}.`
1996
+ );
1997
+ }
1998
+ }
1999
+ }
2000
+ validated[kind] = config;
2001
+ }
2002
+ return validated;
2003
+ }
2004
+ function validateProvenanceWeights(weights) {
2005
+ for (const [branchId, weight] of weights) {
2006
+ if (!Number.isFinite(weight) || weight < 0) {
2007
+ throw new Error(
2008
+ `Invalid provenanceWeights for branch "${branchId}": weight must be a finite number >= 0, got ${String(weight)}.`
2009
+ );
2010
+ }
2011
+ }
2012
+ return weights;
2013
+ }
2014
+ function normalizeMergeOptions(options = {}) {
2015
+ const scalar = mergeOptionsScalarSchema.parse({
2016
+ reconcileTypes: options.reconcileTypes,
2017
+ onDeleteModifyConflict: options.onDeleteModifyConflict,
2018
+ onComparisonCeiling: options.onComparisonCeiling,
2019
+ provenance: options.provenance,
2020
+ persistProvenance: options.persistProvenance,
2021
+ ...options.maxComparisonsPerKind === void 0 ? {} : { maxComparisonsPerKind: options.maxComparisonsPerKind },
2022
+ ...options.clusterMaxDiameter === void 0 ? {} : { clusterMaxDiameter: options.clusterMaxDiameter }
2023
+ });
2024
+ const onPropertyConflict = validatePropertyConflictPolicy(
2025
+ options.onPropertyConflict ?? MERGE_OPTION_DEFAULTS.onPropertyConflict,
2026
+ "onPropertyConflict"
2027
+ );
2028
+ const onBasePropertyConflict = validatePropertyConflictPolicy(
2029
+ options.onBasePropertyConflict ?? MERGE_OPTION_DEFAULTS.onBasePropertyConflict,
2030
+ "onBasePropertyConflict"
2031
+ );
2032
+ const provenanceWeights = options.provenanceWeights === void 0 ? void 0 : validateProvenanceWeights(options.provenanceWeights);
2033
+ const usesProvenanceWeighting = onPropertyConflict === "provenanceWeighted" || onBasePropertyConflict === "provenanceWeighted";
2034
+ if (usesProvenanceWeighting && (provenanceWeights === void 0 || provenanceWeights.size === 0)) {
2035
+ throw new Error(
2036
+ 'A "provenanceWeighted" property-conflict policy requires a non-empty provenanceWeights map.'
2037
+ );
2038
+ }
2039
+ return {
2040
+ resolve: validateResolveMap(options.resolve),
2041
+ reconcileTypes: scalar.reconcileTypes,
2042
+ onPropertyConflict,
2043
+ onBasePropertyConflict,
2044
+ onDeleteModifyConflict: scalar.onDeleteModifyConflict,
2045
+ onComparisonCeiling: scalar.onComparisonCeiling,
2046
+ provenance: scalar.provenance,
2047
+ persistProvenance: scalar.persistProvenance,
2048
+ ...options.canonical === void 0 ? {} : { canonical: options.canonical },
2049
+ ...options.embedder === void 0 ? {} : { embedder: options.embedder },
2050
+ ...options.target === void 0 ? {} : { target: options.target },
2051
+ ...scalar.maxComparisonsPerKind === void 0 ? {} : { maxComparisonsPerKind: scalar.maxComparisonsPerKind },
2052
+ ...scalar.clusterMaxDiameter === void 0 ? {} : { clusterMaxDiameter: scalar.clusterMaxDiameter },
2053
+ ...options.branchOrder === void 0 ? {} : { branchOrder: options.branchOrder },
2054
+ ...provenanceWeights === void 0 ? {} : { provenanceWeights }
2055
+ };
2056
+ }
2057
+ var Provenance = defineNode("Provenance", {
2058
+ schema: z.object({
2059
+ targetGraphId: z.string(),
2060
+ role: z.enum(["node", "edge"]),
2061
+ canonicalId: z.string(),
2062
+ canonicalKind: z.string(),
2063
+ branchId: z.string(),
2064
+ sourceId: z.string()
2065
+ })
2066
+ });
2067
+ function provenanceGraphId(targetGraphId) {
2068
+ return `${targetGraphId}::merge-provenance`;
2069
+ }
2070
+ function buildProvenanceGraph(targetGraphId) {
2071
+ return defineGraph({
2072
+ id: provenanceGraphId(targetGraphId),
2073
+ nodes: { Provenance: { type: Provenance } },
2074
+ edges: {}
2075
+ });
2076
+ }
2077
+ async function openProvenanceStore(backend, targetGraphId) {
2078
+ const [store] = await createStoreWithSchema(
2079
+ buildProvenanceGraph(targetGraphId),
2080
+ backend
2081
+ );
2082
+ return store;
2083
+ }
2084
+ var ID_SEPARATOR = "\0";
2085
+ var ID_DIGEST_BYTES = 16;
2086
+ async function provenanceNodeId(targetGraphId, record) {
2087
+ const tuple = [
2088
+ targetGraphId,
2089
+ record.role,
2090
+ // canonicalKind is part of node identity: two contributions to different-kind
2091
+ // canonicals that share a bare id (e.g. base Patient:x and Encounter:x) must NOT
2092
+ // hash to the same sidecar row and clobber each other (the (kind,id) identity model).
2093
+ record.canonicalKind,
2094
+ record.canonicalId,
2095
+ record.branchId,
2096
+ record.sourceId
2097
+ ].join(ID_SEPARATOR);
2098
+ const digest = await sha256Hex(tuple, ID_DIGEST_BYTES);
2099
+ return `prov_${digest}`;
2100
+ }
2101
+ async function persistProvenanceRecords(store, targetGraphId, records) {
2102
+ if (records.length === 0) {
2103
+ return 0;
2104
+ }
2105
+ const items = await Promise.all(
2106
+ records.map(async (record) => ({
2107
+ id: await provenanceNodeId(targetGraphId, record),
2108
+ props: {
2109
+ targetGraphId,
2110
+ role: record.role,
2111
+ canonicalId: record.canonicalId,
2112
+ canonicalKind: record.canonicalKind,
2113
+ branchId: record.branchId,
2114
+ sourceId: record.sourceId
2115
+ }
2116
+ }))
2117
+ );
2118
+ await store.nodes.Provenance.bulkUpsertById(items);
2119
+ return items.length;
2120
+ }
2121
+ async function readProvenance(store, query = {}) {
2122
+ const all = await store.nodes.Provenance.find();
2123
+ return all.filter(
2124
+ (node) => (query.branchId === void 0 || node.branchId === query.branchId) && (query.canonicalId === void 0 || node.canonicalId === query.canonicalId) && (query.role === void 0 || node.role === query.role)
2125
+ ).sort((left, right) => compareStrings(left.id, right.id));
2126
+ }
2127
+
2128
+ // src/graph-merge/sources.ts
2129
+ function orderEndpoints(left, right) {
2130
+ const leftKey = mergeKeyOf(left);
2131
+ const rightKey = mergeKeyOf(right);
2132
+ return compareMergeKeys(leftKey, rightKey) <= 0 ? { a: leftKey, b: rightKey, left, right } : { a: rightKey, b: leftKey, left: right, right: left };
2133
+ }
2134
+ function sortMembersById(members) {
2135
+ return [...members].sort((left, right) => compareStrings(left.id, right.id));
2136
+ }
2137
+ function sortedBucketKeys(blocks) {
2138
+ return [...blocks.keys()].sort((left, right) => compareStrings(left, right));
2139
+ }
2140
+ var NODE_SYSTEM_FIELDS = /* @__PURE__ */ new Set(["id", "kind", "meta"]);
2141
+ function nodeProps(node) {
2142
+ const props = {};
2143
+ for (const [key, value] of Object.entries(
2144
+ node
2145
+ )) {
2146
+ if (!NODE_SYSTEM_FIELDS.has(key)) {
2147
+ props[key] = value;
2148
+ }
2149
+ }
2150
+ return props;
2151
+ }
2152
+ function keylessConfigFor(resolveConfig) {
2153
+ if (resolveConfig.keyless === void 0) {
2154
+ return void 0;
2155
+ }
2156
+ const strategy = resolveConfig.similarity;
2157
+ const sortFields = strategy.kind === "fulltext" || strategy.kind === "hybrid" ? strategy.fields : strategy.kind === "vector" ? [strategy.field] : [];
2158
+ return { window: resolveConfig.keyless.window, sortFields };
2159
+ }
2160
+ function windowedPairs(members, config) {
2161
+ const keyed = members.map((node) => ({
2162
+ node,
2163
+ key: fieldText(node, config.sortFields)
2164
+ }));
2165
+ keyed.sort(
2166
+ (left, right) => left.key === right.key ? compareStrings(left.node.id, right.node.id) : compareStrings(left.key, right.key)
2167
+ );
2168
+ const pairs = [];
2169
+ for (let index = 0; index < keyed.length; index += 1) {
2170
+ const last = Math.min(index + config.window, keyed.length - 1);
2171
+ for (let index_ = index + 1; index_ <= last; index_ += 1) {
2172
+ pairs.push(orderEndpoints(keyed[index].node, keyed[index_].node));
2173
+ }
2174
+ }
2175
+ return pairs;
2176
+ }
2177
+ function pairsFromBlocks(blocks, keyless) {
2178
+ const pairs = [];
2179
+ for (const bucketKey of sortedBucketKeys(blocks)) {
2180
+ if (isUniqueBucketKey(bucketKey)) {
2181
+ continue;
2182
+ }
2183
+ const members = sortMembersById(blocks.get(bucketKey) ?? []);
2184
+ if (keyless !== void 0 && bucketKey === UNBLOCKED_BUCKET_KEY) {
2185
+ pairs.push(...windowedPairs(members, keyless));
2186
+ continue;
2187
+ }
2188
+ for (let index = 0; index < members.length; index += 1) {
2189
+ for (let index_ = index + 1; index_ < members.length; index_ += 1) {
2190
+ pairs.push(orderEndpoints(members[index], members[index_]));
2191
+ }
2192
+ }
2193
+ }
2194
+ return pairs;
2195
+ }
2196
+ function forcedEdgesFromBlocks(blocks) {
2197
+ const edges = [];
2198
+ for (const bucketKey of sortedBucketKeys(blocks)) {
2199
+ if (!isUniqueBucketKey(bucketKey)) {
2200
+ continue;
2201
+ }
2202
+ const members = sortMembersById(blocks.get(bucketKey) ?? []);
2203
+ for (let index = 0; index < members.length; index += 1) {
2204
+ for (let index_ = index + 1; index_ < members.length; index_ += 1) {
2205
+ const { a, b } = orderEndpoints(members[index], members[index_]);
2206
+ edges.push({ a, b, score: FORCED_MATCH_SCORE });
2207
+ }
2208
+ }
2209
+ }
2210
+ return edges;
2211
+ }
2212
+ function ontologyRetypeEdges(stagedIdentities, isRetypeCompatible) {
2213
+ const identitiesById = /* @__PURE__ */ new Map();
2214
+ for (const identity of stagedIdentities) {
2215
+ const id = idOf(identity);
2216
+ const bucket = identitiesById.get(id);
2217
+ if (bucket === void 0) {
2218
+ identitiesById.set(id, [identity]);
2219
+ } else {
2220
+ bucket.push(identity);
2221
+ }
2222
+ }
2223
+ const edges = [];
2224
+ for (const id of [...identitiesById.keys()].sort(compareStrings)) {
2225
+ const identities = [...new Set(identitiesById.get(id))].sort(
2226
+ compareMergeKeys
2227
+ );
2228
+ if (identities.length < 2) {
2229
+ continue;
2230
+ }
2231
+ const group = unionByCompatibility(identities, isRetypeCompatible);
2232
+ for (const members of groupsOf(identities, group)) {
2233
+ if (members.length < 2 || !isRetypeCompatible(members.map((identity) => kindOf(identity)))) {
2234
+ continue;
2235
+ }
2236
+ for (let index = 1; index < members.length; index += 1) {
2237
+ edges.push({
2238
+ a: members[0],
2239
+ b: members[index],
2240
+ score: FORCED_MATCH_SCORE
2241
+ });
2242
+ }
2243
+ }
2244
+ }
2245
+ return edges;
2246
+ }
2247
+ function unionByCompatibility(identities, isRetypeCompatible) {
2248
+ const forest = new UnionFind(compareMergeKeys);
2249
+ for (const identity of identities) {
2250
+ forest.add(identity);
2251
+ }
2252
+ for (let index = 0; index < identities.length; index += 1) {
2253
+ for (let index_ = index + 1; index_ < identities.length; index_ += 1) {
2254
+ if (isRetypeCompatible([
2255
+ kindOf(identities[index]),
2256
+ kindOf(identities[index_])
2257
+ ])) {
2258
+ forest.union(identities[index], identities[index_]);
2259
+ }
2260
+ }
2261
+ }
2262
+ return new Map(
2263
+ identities.map((identity) => [identity, forest.find(identity)])
2264
+ );
2265
+ }
2266
+ function groupsOf(identities, representativeOf) {
2267
+ const byRoot = /* @__PURE__ */ new Map();
2268
+ for (const identity of identities) {
2269
+ const root = representativeOf.get(identity);
2270
+ const bucket = byRoot.get(root);
2271
+ if (bucket === void 0) {
2272
+ byRoot.set(root, [identity]);
2273
+ } else {
2274
+ bucket.push(identity);
2275
+ }
2276
+ }
2277
+ return [...byRoot.values()].map((group) => [...group].sort(compareMergeKeys));
2278
+ }
2279
+ var exactKeySource = {
2280
+ id: "exactKey",
2281
+ generate(scope) {
2282
+ return Promise.resolve({
2283
+ pairs: pairsFromBlocks(scope.blocks, scope.keyless),
2284
+ forcedEdges: [],
2285
+ baseMembers: []
2286
+ });
2287
+ }
2288
+ };
2289
+ var uniqueSource = {
2290
+ id: "unique",
2291
+ generate(scope) {
2292
+ return Promise.resolve({
2293
+ pairs: [],
2294
+ forcedEdges: forcedEdgesFromBlocks(scope.blocks),
2295
+ baseMembers: []
2296
+ });
2297
+ }
2298
+ };
2299
+ var baseUniqueSource = {
2300
+ id: "baseUnique",
2301
+ async generate(scope) {
2302
+ const { kind, nodes, uniqueConstraints, store } = scope;
2303
+ if (nodes === void 0 || uniqueConstraints === void 0 || store === void 0) {
2304
+ throw new MergeError(
2305
+ "baseUniqueSource requires nodes, uniqueConstraints, and store in the source scope.",
2306
+ { details: { kind, source: "baseUnique" } }
2307
+ );
2308
+ }
2309
+ const collection = store.nodes[kind];
2310
+ if (collection === void 0 || nodes.length === 0) {
2311
+ return { pairs: [], forcedEdges: [], baseMembers: [] };
2312
+ }
2313
+ const forcedEdges = [];
2314
+ const baseMembers = /* @__PURE__ */ new Map();
2315
+ const constraints = [...uniqueConstraints].sort(
2316
+ (left, right) => compareStrings(left.name, right.name)
2317
+ );
2318
+ const items = nodes.map((node) => ({ props: nodeProps(node) }));
2319
+ const matchesByConstraint = await Promise.all(
2320
+ constraints.map(async (constraint) => {
2321
+ try {
2322
+ return await collection.bulkFindByConstraint(constraint.name, items);
2323
+ } catch (error) {
2324
+ throw new MergeError(
2325
+ `baseUniqueSource: new-vs-base lookup for constraint "${constraint.name}" on kind "${kind}" failed \u2014 a staged node's props could not be validated against the kind's schema for constraint-key computation.`,
2326
+ { details: { kind, constraint: constraint.name }, cause: error }
2327
+ );
2328
+ }
2329
+ })
2330
+ );
2331
+ for (const matches of matchesByConstraint) {
2332
+ for (const [index, node] of nodes.entries()) {
2333
+ const base = matches[index];
2334
+ if (base === void 0) {
2335
+ continue;
2336
+ }
2337
+ const stagedNode = node;
2338
+ const stagedKey = mergeKey(stagedNode.kind, stagedNode.id);
2339
+ const baseKey = mergeKey(base.kind, base.id);
2340
+ if (!baseMembers.has(baseKey)) {
2341
+ baseMembers.set(baseKey, {
2342
+ origin: "base",
2343
+ id: base.id,
2344
+ kind: base.kind,
2345
+ props: nodeProps(base)
2346
+ });
2347
+ }
2348
+ if (stagedKey === baseKey) {
2349
+ continue;
2350
+ }
2351
+ const { a, b } = compareMergeKeys(stagedKey, baseKey) <= 0 ? { a: stagedKey, b: baseKey } : { a: baseKey, b: stagedKey };
2352
+ forcedEdges.push({ a, b, score: FORCED_MATCH_SCORE });
2353
+ }
2354
+ }
2355
+ return {
2356
+ pairs: [],
2357
+ forcedEdges,
2358
+ baseMembers: [...baseMembers.values()].sort(
2359
+ (left, right) => compareStrings(left.id, right.id)
2360
+ )
2361
+ };
2362
+ }
2363
+ };
2364
+ var baseKeySource = {
2365
+ id: "baseKey",
2366
+ async generate(scope) {
2367
+ const { kind, nodes, blockIndex, store } = scope;
2368
+ if (blockIndex === void 0) {
2369
+ return { pairs: [], forcedEdges: [], baseMembers: [] };
2370
+ }
2371
+ if (nodes === void 0 || store === void 0) {
2372
+ throw new MergeError(
2373
+ "baseKeySource requires nodes and store in the source scope.",
2374
+ { details: { kind, source: "baseKey" } }
2375
+ );
2376
+ }
2377
+ const collection = store.nodes[kind];
2378
+ if (collection === void 0 || nodes.length === 0) {
2379
+ return { pairs: [], forcedEdges: [], baseMembers: [] };
2380
+ }
2381
+ const items = nodes.map((node) => ({ props: nodeProps(node) }));
2382
+ let matchesByNode;
2383
+ try {
2384
+ matchesByNode = await collection.bulkFindByIndex(blockIndex, items);
2385
+ } catch (error) {
2386
+ throw new MergeError(
2387
+ `baseKeySource: new-vs-base lookup on index "${blockIndex}" for kind "${kind}" failed \u2014 the index is undeclared or a staged node's props are incompatible with its indexed fields.`,
2388
+ { details: { kind, index: blockIndex }, cause: error }
2389
+ );
2390
+ }
2391
+ const pairs = [];
2392
+ const baseMembers = /* @__PURE__ */ new Map();
2393
+ for (const [index, node] of nodes.entries()) {
2394
+ const stagedNode = node;
2395
+ const stagedKey = mergeKey(stagedNode.kind, stagedNode.id);
2396
+ for (const base of matchesByNode[index] ?? []) {
2397
+ const baseKey = mergeKey(base.kind, base.id);
2398
+ if (!baseMembers.has(baseKey)) {
2399
+ baseMembers.set(baseKey, {
2400
+ origin: "base",
2401
+ id: base.id,
2402
+ kind: base.kind,
2403
+ props: nodeProps(base)
2404
+ });
2405
+ }
2406
+ if (stagedKey === baseKey) {
2407
+ continue;
2408
+ }
2409
+ pairs.push(orderEndpoints(stagedNode, base));
2410
+ }
2411
+ }
2412
+ return {
2413
+ pairs,
2414
+ forcedEdges: [],
2415
+ baseMembers: [...baseMembers.values()].sort(
2416
+ (left, right) => compareStrings(left.id, right.id)
2417
+ )
2418
+ };
2419
+ }
2420
+ };
2421
+ var CANDIDATE_SOURCES = [
2422
+ exactKeySource,
2423
+ uniqueSource
2424
+ ];
2425
+
2426
+ // src/graph-merge/staging.ts
2427
+ function compareByIdThenBranch(left, right) {
2428
+ const byId2 = compareStrings(left.id, right.id);
2429
+ return byId2 === 0 ? compareStrings(left.branchId, right.branchId) : byId2;
2430
+ }
2431
+ function compareByKindIdBranch(left, right) {
2432
+ const byKind = compareStrings(left.kind, right.kind);
2433
+ if (byKind !== 0) {
2434
+ return byKind;
2435
+ }
2436
+ const byId2 = compareStrings(left.id, right.id);
2437
+ return byId2 === 0 ? compareStrings(left.branchId, right.branchId) : byId2;
2438
+ }
2439
+ function groupByKind(items) {
2440
+ const buckets = /* @__PURE__ */ new Map();
2441
+ for (const item of items) {
2442
+ const bucket = buckets.get(item.kind);
2443
+ if (bucket === void 0) {
2444
+ buckets.set(item.kind, [item]);
2445
+ } else {
2446
+ bucket.push(item);
2447
+ }
2448
+ }
2449
+ const ordered = /* @__PURE__ */ new Map();
2450
+ for (const kind of [...buckets.keys()].sort(
2451
+ (left, right) => compareStrings(left, right)
2452
+ )) {
2453
+ ordered.set(
2454
+ kind,
2455
+ [...buckets.get(kind)].sort(
2456
+ (left, right) => compareByIdThenBranch(left, right)
2457
+ )
2458
+ );
2459
+ }
2460
+ return ordered;
2461
+ }
2462
+ async function stageBranches(baseStore, branches) {
2463
+ const newNodes = [];
2464
+ const modifiedNodes = [];
2465
+ const deletedNodes = [];
2466
+ const newEdges = [];
2467
+ const modifiedEdges = [];
2468
+ const deletedEdges = [];
2469
+ for (const branch2 of branches) {
2470
+ const diff = await diffAgainstBase(baseStore, branch2.store);
2471
+ const branchId = branch2.id;
2472
+ for (const node of diff.nodes.new) {
2473
+ newNodes.push({ branchId, node, kind: node.kind, id: node.id });
2474
+ }
2475
+ for (const node of diff.nodes.modified) {
2476
+ modifiedNodes.push({ branchId, node, kind: node.kind, id: node.id });
2477
+ }
2478
+ for (const node of diff.nodes.deleted) {
2479
+ deletedNodes.push({ branchId, node, kind: node.kind, id: node.id });
2480
+ }
2481
+ for (const edge of diff.edges.new) {
2482
+ newEdges.push({ branchId, edge, kind: edge.kind, id: edge.id });
2483
+ }
2484
+ for (const edge of diff.edges.modified) {
2485
+ modifiedEdges.push({ branchId, edge, kind: edge.kind, id: edge.id });
2486
+ }
2487
+ for (const edge of diff.edges.deleted) {
2488
+ deletedEdges.push({ branchId, edge, kind: edge.kind, id: edge.id });
2489
+ }
2490
+ }
2491
+ return {
2492
+ newNodesByKind: groupByKind(newNodes),
2493
+ modifiedNodes: [...modifiedNodes].sort(
2494
+ (left, right) => compareByKindIdBranch(left, right)
2495
+ ),
2496
+ deletedNodes: [...deletedNodes].sort(
2497
+ (left, right) => compareByKindIdBranch(left, right)
2498
+ ),
2499
+ newEdgesByKind: groupByKind(newEdges),
2500
+ modifiedEdges: [...modifiedEdges].sort(
2501
+ (left, right) => compareByKindIdBranch(left, right)
2502
+ ),
2503
+ deletedEdges: [...deletedEdges].sort(
2504
+ (left, right) => compareByKindIdBranch(left, right)
2505
+ )
2506
+ };
2507
+ }
2508
+
2509
+ // src/graph-merge/type-reconcile.ts
2510
+ var INCOMPATIBLE_TYPES_FLAG_REASON = "type-reconcile:incompatible-kinds";
2511
+ function distinctKinds(kinds) {
2512
+ return [...new Set(kinds)].sort((left, right) => compareStrings(left, right));
2513
+ }
2514
+ function areEquivalent(closure, left, right) {
2515
+ return left !== right && isReachable2(closure, left, right) && isReachable2(closure, right, left);
2516
+ }
2517
+ function isAtOrBelow(closure, candidate, other) {
2518
+ if (candidate === other) {
2519
+ return true;
2520
+ }
2521
+ return isReachable2(closure, candidate, other) || areEquivalent(closure, candidate, other);
2522
+ }
2523
+ function mostSpecificCommonKind(closure, kinds) {
2524
+ const qualifiers = kinds.filter(
2525
+ (candidate) => kinds.every((other) => isAtOrBelow(closure, candidate, other))
2526
+ );
2527
+ if (qualifiers.length === 0) {
2528
+ return void 0;
2529
+ }
2530
+ return [...qualifiers].sort((left, right) => compareStrings(left, right))[0];
2531
+ }
2532
+ function reconcileTypes(clusters, closure, mode) {
2533
+ if (mode === "off") {
2534
+ return {
2535
+ reconciliations: [],
2536
+ retypeMap: /* @__PURE__ */ new Map(),
2537
+ dropped: []
2538
+ };
2539
+ }
2540
+ const reconciliations = [];
2541
+ const retypeMap = /* @__PURE__ */ new Map();
2542
+ const dropped = [];
2543
+ for (const cluster of clusters) {
2544
+ const kinds = distinctKinds(cluster.memberKinds);
2545
+ if (kinds.length <= 1) {
2546
+ continue;
2547
+ }
2548
+ const entityId = idOf(cluster.canonicalId);
2549
+ const toType = mostSpecificCommonKind(closure, kinds);
2550
+ if (toType === void 0) {
2551
+ dropped.push({
2552
+ kind: "node",
2553
+ id: entityId,
2554
+ reason: INCOMPATIBLE_TYPES_FLAG_REASON
2555
+ });
2556
+ continue;
2557
+ }
2558
+ reconciliations.push({
2559
+ entityId,
2560
+ fromTypes: kinds,
2561
+ toType
2562
+ });
2563
+ retypeMap.set(cluster.canonicalId, toType);
2564
+ }
2565
+ return {
2566
+ reconciliations: reconciliations.sort(
2567
+ (left, right) => compareStrings(left.entityId, right.entityId)
2568
+ ),
2569
+ retypeMap,
2570
+ dropped: dropped.sort((left, right) => compareStrings(left.id, right.id))
2571
+ };
2572
+ }
2573
+
2574
+ // src/graph-merge/merge.ts
2575
+ var COMMITTED_TARGET_BRANCH = asBranchId("__committed_target__");
2576
+ function asNode(staged) {
2577
+ return {
2578
+ kind: staged.node.kind,
2579
+ id: staged.node.id,
2580
+ ...staged.node.props
2581
+ };
2582
+ }
2583
+ function newNodesByKind(staging) {
2584
+ const ordered = /* @__PURE__ */ new Map();
2585
+ for (const [kind, items] of staging.newNodesByKind) {
2586
+ ordered.set(
2587
+ kind,
2588
+ [...items].sort((left, right) => {
2589
+ const byId2 = compareStrings(left.node.id, right.node.id);
2590
+ return byId2 === 0 ? compareStrings(left.branchId, right.branchId) : byId2;
2591
+ })
2592
+ );
2593
+ }
2594
+ return ordered;
2595
+ }
2596
+ function uniqueConstraintsFor(introspectionKinds, kind) {
2597
+ return introspectionKinds.get(kind) ?? [];
2598
+ }
2599
+ async function precomputeEmbeddings(byKind, resolve, embedder) {
2600
+ const texts = /* @__PURE__ */ new Set();
2601
+ for (const [kind, items] of byKind) {
2602
+ const config = resolve[kind];
2603
+ if (config === void 0) {
2604
+ continue;
2605
+ }
2606
+ const fields = embeddingFields(config.similarity);
2607
+ if (fields === void 0) {
2608
+ continue;
2609
+ }
2610
+ for (const staged of items) {
2611
+ const text = fieldText(asNode(staged), fields);
2612
+ if (text.length > 0) {
2613
+ texts.add(text);
2614
+ }
2615
+ }
2616
+ }
2617
+ const lookup = /* @__PURE__ */ new Map();
2618
+ if (texts.size === 0) {
2619
+ return lookup;
2620
+ }
2621
+ const orderedTexts = [...texts].sort(
2622
+ (left, right) => compareStrings(left, right)
2623
+ );
2624
+ const vectors = await embedder(orderedTexts);
2625
+ if (vectors.length !== orderedTexts.length) {
2626
+ throw new MergeError(
2627
+ `Embedder returned ${vectors.length} vectors for ${orderedTexts.length} texts; expected exactly one per text.`,
2628
+ {
2629
+ details: { texts: orderedTexts.length, vectors: vectors.length },
2630
+ suggestion: "Ensure MergeOptions.embedder returns one vector per input text, in order."
2631
+ }
2632
+ );
2633
+ }
2634
+ for (const [index, text] of orderedTexts.entries()) {
2635
+ lookup.set(text, vectors[index]);
2636
+ }
2637
+ return lookup;
2638
+ }
2639
+ async function embedMissingPairTexts(base, pairs, strategy, embedder) {
2640
+ const existing = base ?? /* @__PURE__ */ new Map();
2641
+ const fields = embeddingFields(strategy);
2642
+ if (fields === void 0) {
2643
+ return existing;
2644
+ }
2645
+ const missing = /* @__PURE__ */ new Set();
2646
+ for (const pair of pairs) {
2647
+ for (const node of [pair.left, pair.right]) {
2648
+ const text = fieldText(node, fields);
2649
+ if (text.length > 0 && !existing.has(text)) {
2650
+ missing.add(text);
2651
+ }
2652
+ }
2653
+ }
2654
+ if (missing.size === 0) {
2655
+ return existing;
2656
+ }
2657
+ const orderedTexts = [...missing].sort(
2658
+ (left, right) => compareStrings(left, right)
2659
+ );
2660
+ const vectors = await embedder(orderedTexts);
2661
+ if (vectors.length !== orderedTexts.length) {
2662
+ throw new MergeError(
2663
+ `Embedder returned ${vectors.length} vectors for ${orderedTexts.length} texts; expected exactly one per text.`,
2664
+ {
2665
+ details: { texts: orderedTexts.length, vectors: vectors.length },
2666
+ suggestion: "Ensure MergeOptions.embedder returns one vector per input text, in order."
2667
+ }
2668
+ );
2669
+ }
2670
+ const augmented = new Map(existing);
2671
+ for (const [index, text] of orderedTexts.entries()) {
2672
+ augmented.set(text, vectors[index]);
2673
+ }
2674
+ return augmented;
2675
+ }
2676
+ async function generateAllCandidates(target, staging, options, introspectionKinds, ctx, useBaseSources, embedder) {
2677
+ const allEdges = [];
2678
+ const warnings = [];
2679
+ const baseMembers = [];
2680
+ const byKind = newNodesByKind(staging);
2681
+ const sources = useBaseSources ? [...CANDIDATE_SOURCES, baseUniqueSource, baseKeySource] : CANDIDATE_SOURCES;
2682
+ const baseStore = target;
2683
+ const perKind = await Promise.all(
2684
+ [...byKind].map(
2685
+ async ([kind, items]) => {
2686
+ const resolveConfig = options.resolve[kind];
2687
+ if (resolveConfig === void 0) {
2688
+ return ok({ edges: [], warnings: [], baseMembers: [] });
2689
+ }
2690
+ const nodes = items.map((staged) => asNode(staged));
2691
+ const uniqueConstraints = uniqueConstraintsFor(
2692
+ introspectionKinds,
2693
+ kind
2694
+ );
2695
+ const blocks = blockNodes(nodes, resolveConfig, uniqueConstraints);
2696
+ const keylessConfig = keylessConfigFor(resolveConfig);
2697
+ const scope = {
2698
+ kind,
2699
+ blocks,
2700
+ nodes,
2701
+ uniqueConstraints,
2702
+ store: baseStore,
2703
+ ...resolveConfig.blockIndex === void 0 ? {} : { blockIndex: resolveConfig.blockIndex },
2704
+ ...keylessConfig === void 0 ? {} : { keyless: keylessConfig }
2705
+ };
2706
+ const pairs = [];
2707
+ const forcedEdges = [];
2708
+ const kindBaseMembers = [];
2709
+ for (const source of sources) {
2710
+ const produced = await source.generate(scope);
2711
+ pairs.push(...produced.pairs);
2712
+ forcedEdges.push(...produced.forcedEdges);
2713
+ kindBaseMembers.push(...produced.baseMembers);
2714
+ }
2715
+ const kindCtx = embedder === void 0 ? ctx : {
2716
+ ...ctx,
2717
+ embeddings: await embedMissingPairTexts(
2718
+ ctx.embeddings,
2719
+ pairs,
2720
+ resolveConfig.similarity,
2721
+ embedder
2722
+ )
2723
+ };
2724
+ const scored = scoreCandidates(
2725
+ { pairs, forcedEdges },
2726
+ resolveConfig,
2727
+ kindCtx,
2728
+ options.onComparisonCeiling,
2729
+ options.maxComparisonsPerKind
2730
+ );
2731
+ if (isErr(scored)) {
2732
+ return err(scored.error);
2733
+ }
2734
+ return ok({
2735
+ edges: scored.data.edges,
2736
+ warnings: scored.data.warnings.map(
2737
+ (warning) => `[${kind}] ${warning.message}`
2738
+ ),
2739
+ baseMembers: kindBaseMembers
2740
+ });
2741
+ }
2742
+ )
2743
+ );
2744
+ for (const result of perKind) {
2745
+ if (isErr(result)) {
2746
+ return err(result.error);
2747
+ }
2748
+ allEdges.push(...result.data.edges);
2749
+ warnings.push(...result.data.warnings);
2750
+ baseMembers.push(...result.data.baseMembers);
2751
+ }
2752
+ return ok({
2753
+ // The ONE shared `(a, b)` edge comparator (id-first `(kind, id)` order), so this
2754
+ // stage emits edges in exactly the order clustering consumes them.
2755
+ edges: allEdges.sort((left, right) => compareCandidateEdges(left, right)),
2756
+ warnings,
2757
+ // Total order over the composite `(kind, id)` identity (two same-id/different-kind
2758
+ // base members would tie under a bare-id compare).
2759
+ baseMembers: baseMembers.sort(
2760
+ (left, right) => compareMergeKeys(mergeKeyOf(left), mergeKeyOf(right))
2761
+ )
2762
+ });
2763
+ }
2764
+ function clusterMembersFor(cluster, newNodesById, baseMembersById) {
2765
+ const members = [];
2766
+ for (const key of cluster.members) {
2767
+ for (const staged of newNodesById.get(key) ?? []) {
2768
+ members.push({
2769
+ origin: "staged",
2770
+ id: staged.node.id,
2771
+ kind: staged.node.kind,
2772
+ branchId: staged.branchId,
2773
+ props: staged.node.props
2774
+ });
2775
+ }
2776
+ const base = baseMembersById.get(key);
2777
+ if (base !== void 0) {
2778
+ members.push({
2779
+ origin: "base",
2780
+ id: base.id,
2781
+ kind: base.kind,
2782
+ branchId: BASE_PROVENANCE_BRANCH,
2783
+ props: base.props
2784
+ });
2785
+ }
2786
+ }
2787
+ return members;
2788
+ }
2789
+ function indexNewNodesById(staging) {
2790
+ const index = /* @__PURE__ */ new Map();
2791
+ for (const items of staging.newNodesByKind.values()) {
2792
+ for (const staged of items) {
2793
+ const key = mergeKeyOf(staged.node);
2794
+ const bucket = index.get(key);
2795
+ if (bucket === void 0) {
2796
+ index.set(key, [staged]);
2797
+ } else {
2798
+ bucket.push(staged);
2799
+ }
2800
+ }
2801
+ }
2802
+ return index;
2803
+ }
2804
+ function buildStagedEdges(staging, modifiedEdges) {
2805
+ const staged = [];
2806
+ for (const items of staging.newEdgesByKind.values()) {
2807
+ for (const item of items) {
2808
+ staged.push(toStagedEdge(item.branchId, item));
2809
+ }
2810
+ }
2811
+ for (const item of modifiedEdges) {
2812
+ staged.push({
2813
+ id: item.edge.id,
2814
+ kind: item.edge.kind,
2815
+ fromId: item.edge.fromId,
2816
+ toId: item.edge.toId,
2817
+ fromKind: item.edge.fromKind,
2818
+ toKind: item.edge.toKind,
2819
+ props: item.edge.forkProps,
2820
+ branchId: item.branchId
2821
+ });
2822
+ }
2823
+ return staged;
2824
+ }
2825
+ function toStagedEdge(branchId, item) {
2826
+ return {
2827
+ id: item.edge.id,
2828
+ kind: item.edge.kind,
2829
+ fromId: item.edge.fromId,
2830
+ toId: item.edge.toId,
2831
+ fromKind: item.edge.fromKind,
2832
+ toKind: item.edge.toKind,
2833
+ props: item.edge.props,
2834
+ branchId
2835
+ };
2836
+ }
2837
+ function buildProvenanceIndex(records) {
2838
+ const byBranch = /* @__PURE__ */ new Map();
2839
+ for (const record of records) {
2840
+ let entry = byBranch.get(record.branchId);
2841
+ if (entry === void 0) {
2842
+ entry = { nodeIds: /* @__PURE__ */ new Set(), edgeIds: /* @__PURE__ */ new Set() };
2843
+ byBranch.set(record.branchId, entry);
2844
+ }
2845
+ if (record.role === "node") {
2846
+ entry.nodeIds.add(record.canonicalId);
2847
+ } else {
2848
+ entry.edgeIds.add(record.canonicalId);
2849
+ }
2850
+ }
2851
+ const frozen = /* @__PURE__ */ new Map();
2852
+ for (const [branchId, sets] of byBranch) {
2853
+ frozen.set(branchId, {
2854
+ nodeIds: [...sets.nodeIds].sort(
2855
+ (left, right) => compareStrings(left, right)
2856
+ ),
2857
+ edgeIds: [...sets.edgeIds].sort((left, right) => compareStrings(left, right)).map((id) => id)
2858
+ });
2859
+ }
2860
+ return {
2861
+ byBranch: (branchId) => frozen.get(branchId) ?? { nodeIds: [], edgeIds: [] }
2862
+ };
2863
+ }
2864
+ function pushProvenance(records, role, branchId, canonicalId, canonicalKind, sourceId) {
2865
+ records.push({ role, canonicalId, canonicalKind, branchId, sourceId });
2866
+ }
2867
+ var EMPTY_WEIGHTS = /* @__PURE__ */ new Map();
2868
+ function planMerge(staging, candidateEdges, candidateWarnings, baseMembers, options, branchRank, subClassClosure, preferredBranchId) {
2869
+ const provenanceRecords = [];
2870
+ const weights = options.provenanceWeights ?? EMPTY_WEIGHTS;
2871
+ const recordProvenance = (role, branchId, canonicalId, canonicalKind, sourceId) => {
2872
+ if (branchId === preferredBranchId) {
2873
+ return;
2874
+ }
2875
+ pushProvenance(
2876
+ provenanceRecords,
2877
+ role,
2878
+ branchId,
2879
+ canonicalId,
2880
+ canonicalKind,
2881
+ sourceId
2882
+ );
2883
+ };
2884
+ const newNodesById = indexNewNodesById(staging);
2885
+ const acceptedEndpointKeys = /* @__PURE__ */ new Set();
2886
+ for (const edge of candidateEdges) {
2887
+ acceptedEndpointKeys.add(edge.a);
2888
+ acceptedEndpointKeys.add(edge.b);
2889
+ }
2890
+ const baseMembersById = /* @__PURE__ */ new Map();
2891
+ for (const member of baseMembers) {
2892
+ const key = mergeKeyOf(member);
2893
+ if (acceptedEndpointKeys.has(key) || newNodesById.has(key)) {
2894
+ baseMembersById.set(key, member);
2895
+ }
2896
+ }
2897
+ const newNodeIds = [...newNodesById.keys(), ...baseMembersById.keys()];
2898
+ const isOntology = options.reconcileTypes === "ontology";
2899
+ const preferKind = isOntology ? (kinds) => mostSpecificCommonKind(subClassClosure, kinds) : void 0;
2900
+ const clusterEdges = isOntology ? [
2901
+ ...candidateEdges,
2902
+ ...ontologyRetypeEdges(
2903
+ newNodesById.keys(),
2904
+ (kinds) => mostSpecificCommonKind(subClassClosure, kinds) !== void 0
2905
+ )
2906
+ ] : candidateEdges;
2907
+ const baseIds = new Set(baseMembersById.keys());
2908
+ const guard = enforceBaseGuard(
2909
+ connectedComponents(clusterEdges, newNodeIds),
2910
+ clusterEdges,
2911
+ baseIds
2912
+ );
2913
+ const clusters = options.clusterMaxDiameter === void 0 ? guard.clusters : enforceDiameter(guard.clusters, clusterEdges, options.clusterMaxDiameter);
2914
+ const baseAmbiguities = guard.events.map((event) => ({
2915
+ baseIds: event.baseIds.map((key) => ({ kind: kindOf(key), id: idOf(key) })),
2916
+ memberIds: event.memberIds.map((key) => ({
2917
+ kind: kindOf(key),
2918
+ id: idOf(key)
2919
+ }))
2920
+ }));
2921
+ const canonicalEntities = [];
2922
+ const resolutions = [];
2923
+ const propertyConflicts = [];
2924
+ const reconcileInputs = [];
2925
+ for (const cluster of clusters) {
2926
+ const members = clusterMembersFor(cluster, newNodesById, baseMembersById);
2927
+ if (members.length === 0) {
2928
+ continue;
2929
+ }
2930
+ const entity = canonicalizeCluster(
2931
+ cluster,
2932
+ members,
2933
+ options.onPropertyConflict,
2934
+ branchRank,
2935
+ weights,
2936
+ options.canonical,
2937
+ options.onBasePropertyConflict,
2938
+ preferKind,
2939
+ preferredBranchId
2940
+ );
2941
+ canonicalEntities.push(entity);
2942
+ reconcileInputs.push({
2943
+ canonicalId: mergeKey(entity.kind, entity.canonicalId),
2944
+ memberKinds: members.map((member) => member.kind)
2945
+ });
2946
+ const distinctMergedIds = new Set(
2947
+ cluster.members.map((member) => idOf(member))
2948
+ );
2949
+ if (distinctMergedIds.size > 1) {
2950
+ resolutions.push(entity.resolution);
2951
+ }
2952
+ for (const conflict of entity.conflicts) {
2953
+ propertyConflicts.push(conflict);
2954
+ }
2955
+ for (const member of members) {
2956
+ recordProvenance(
2957
+ "node",
2958
+ member.branchId,
2959
+ entity.canonicalId,
2960
+ entity.kind,
2961
+ member.id
2962
+ );
2963
+ }
2964
+ }
2965
+ const deleteModify = resolveDeleteModify(
2966
+ staging,
2967
+ options.onDeleteModifyConflict,
2968
+ branchRank,
2969
+ preferredBranchId
2970
+ );
2971
+ const nodeDeletions = /* @__PURE__ */ new Map();
2972
+ for (const deletion of deleteModify.nodeDeletions) {
2973
+ nodeDeletions.set(mergeKey(deletion.kind, deletion.id), deletion.kind);
2974
+ }
2975
+ for (const modification of deleteModify.survivingModifications) {
2976
+ recordProvenance(
2977
+ "node",
2978
+ modification.branchId,
2979
+ modification.node.id,
2980
+ modification.node.kind,
2981
+ modification.node.id
2982
+ );
2983
+ }
2984
+ const reconciledModifications = reconcileModifications(
2985
+ deleteModify.survivingModifications,
2986
+ options.onPropertyConflict,
2987
+ branchRank,
2988
+ weights,
2989
+ preferredBranchId
2990
+ );
2991
+ for (const conflict of reconciledModifications.conflicts) {
2992
+ propertyConflicts.push(conflict);
2993
+ }
2994
+ const edgeDeleteModify = resolveEdgeDeleteModify(
2995
+ staging,
2996
+ options.onDeleteModifyConflict,
2997
+ branchRank,
2998
+ preferredBranchId
2999
+ );
3000
+ const edgeDeletions = /* @__PURE__ */ new Map();
3001
+ for (const deletion of edgeDeleteModify.edgeDeletions) {
3002
+ edgeDeletions.set(mergeKey(deletion.kind, deletion.id), deletion.kind);
3003
+ }
3004
+ for (const modification of edgeDeleteModify.survivingModifications) {
3005
+ recordProvenance(
3006
+ "edge",
3007
+ modification.branchId,
3008
+ modification.edge.id,
3009
+ modification.edge.kind,
3010
+ modification.edge.id
3011
+ );
3012
+ }
3013
+ const reconciledEdgeModifications = reconcileEdgeModifications(
3014
+ edgeDeleteModify.survivingModifications,
3015
+ options.onPropertyConflict,
3016
+ branchRank,
3017
+ weights,
3018
+ preferredBranchId
3019
+ );
3020
+ for (const conflict of reconciledEdgeModifications.conflicts) {
3021
+ propertyConflicts.push(conflict);
3022
+ }
3023
+ const reconciliation = reconcileTypes(
3024
+ reconcileInputs,
3025
+ subClassClosure,
3026
+ options.reconcileTypes
3027
+ );
3028
+ const entityByIdentity = /* @__PURE__ */ new Map();
3029
+ for (const entity of canonicalEntities) {
3030
+ entityByIdentity.set(mergeKey(entity.kind, entity.canonicalId), entity);
3031
+ }
3032
+ const canonicalOf = buildCanonicalMap(
3033
+ clusters,
3034
+ (cluster) => pickClusterCanonical(cluster, entityByIdentity)
3035
+ );
3036
+ const stagedEdges = buildStagedEdges(
3037
+ staging,
3038
+ reconciledEdgeModifications.survivingModifications
3039
+ );
3040
+ const stagedEdgeBranches = /* @__PURE__ */ new Map();
3041
+ for (const staged of stagedEdges) {
3042
+ const branches = stagedEdgeBranches.get(staged.id);
3043
+ if (branches === void 0) {
3044
+ stagedEdgeBranches.set(staged.id, /* @__PURE__ */ new Set([staged.branchId]));
3045
+ } else {
3046
+ branches.add(staged.branchId);
3047
+ }
3048
+ }
3049
+ const deletedNodeIdSet = new Set(nodeDeletions.keys());
3050
+ const repoint2 = repointEdges(
3051
+ stagedEdges,
3052
+ canonicalOf,
3053
+ deletedNodeIdSet,
3054
+ options.onPropertyConflict,
3055
+ branchRank,
3056
+ weights,
3057
+ preferredBranchId
3058
+ );
3059
+ for (const merged of repoint2.edges) {
3060
+ for (const sourceId of merged.mergedIds) {
3061
+ for (const branchId of stagedEdgeBranches.get(sourceId) ?? []) {
3062
+ recordProvenance("edge", branchId, merged.id, merged.kind, sourceId);
3063
+ }
3064
+ }
3065
+ }
3066
+ const dropped = [
3067
+ ...deleteModify.dropped,
3068
+ ...edgeDeleteModify.dropped,
3069
+ ...repoint2.dropped,
3070
+ ...reconciliation.dropped
3071
+ ].sort(
3072
+ (left, right) => compareStrings(`${left.kind}|${left.id}`, `${right.kind}|${right.id}`)
3073
+ );
3074
+ return {
3075
+ canonicalEntities,
3076
+ survivingModifications: reconciledModifications.survivingModifications,
3077
+ nodeDeletions,
3078
+ edgeDeletions,
3079
+ mergedEdges: repoint2.edges,
3080
+ inheritedEdgeBaseProps: new Map(
3081
+ reconciledEdgeModifications.survivingModifications.map((modification) => [
3082
+ modification.edge.id,
3083
+ modification.edge.baseProps
3084
+ ])
3085
+ ),
3086
+ retypeMap: reconciliation.retypeMap,
3087
+ // Order report arrays by the composite (kind, id) identity — never a bare id
3088
+ // or a `|`-joined string. A bare id ties two different-kind entities that
3089
+ // share an id, and a `|` separator collides on caller-supplied ids/property
3090
+ // names that contain `|`; either makes the comparator non-total, so the
3091
+ // returned order would depend on stable-sort + insertion order and break the
3092
+ // order-independence the whole subsystem guarantees.
3093
+ resolutions: resolutions.sort(
3094
+ (left, right) => compareMergeKeys(
3095
+ mergeKey(left.kind, left.canonicalId),
3096
+ mergeKey(right.kind, right.canonicalId)
3097
+ )
3098
+ ),
3099
+ propertyConflicts: [...propertyConflicts, ...repoint2.conflicts].sort(
3100
+ (left, right) => {
3101
+ const byEntity = compareMergeKeys(
3102
+ mergeKey(left.kind, left.entityId),
3103
+ mergeKey(right.kind, right.entityId)
3104
+ );
3105
+ return byEntity === 0 ? compareStrings(left.property, right.property) : byEntity;
3106
+ }
3107
+ ),
3108
+ deleteModifyConflicts: [
3109
+ ...deleteModify.conflicts,
3110
+ ...edgeDeleteModify.conflicts
3111
+ ].sort(
3112
+ (left, right) => compareMergeKeys(
3113
+ mergeKey(left.kind, left.entityId),
3114
+ mergeKey(right.kind, right.entityId)
3115
+ )
3116
+ ),
3117
+ typeReconciliations: reconciliation.reconciliations,
3118
+ dropped,
3119
+ baseAmbiguities: baseAmbiguities.sort(
3120
+ (left, right) => compareMergeKeys(
3121
+ mergeKey(left.baseIds[0].kind, left.baseIds[0].id),
3122
+ mergeKey(right.baseIds[0].kind, right.baseIds[0].id)
3123
+ )
3124
+ ),
3125
+ provenanceRecords,
3126
+ warnings: candidateWarnings
3127
+ };
3128
+ }
3129
+ function pickClusterCanonical(cluster, entityByIdentity) {
3130
+ for (const member of cluster.members) {
3131
+ if (entityByIdentity.has(member)) {
3132
+ return member;
3133
+ }
3134
+ }
3135
+ throw new MergeError(
3136
+ "Internal invariant: cluster has no canonical entity in pickClusterCanonical.",
3137
+ { details: { members: cluster.members.map((member) => idOf(member)) } }
3138
+ );
3139
+ }
3140
+ function commitModificationProps(baseProps, forkProps) {
3141
+ const props = { ...forkProps };
3142
+ for (const key of Object.keys(baseProps)) {
3143
+ if (!(key in forkProps)) {
3144
+ props[key] = void 0;
3145
+ }
3146
+ }
3147
+ return props;
3148
+ }
3149
+ async function applyMergePlan(plan, nodesApi, edgesApi) {
3150
+ const committedNodeIds = /* @__PURE__ */ new Set();
3151
+ const canonicalIdentities = /* @__PURE__ */ new Set();
3152
+ for (const entity of plan.canonicalEntities) {
3153
+ canonicalIdentities.add(mergeKey(entity.kind, entity.canonicalId));
3154
+ }
3155
+ const survivingModProps = /* @__PURE__ */ new Map();
3156
+ for (const modification of plan.survivingModifications) {
3157
+ survivingModProps.set(mergeKeyOf(modification.node), {
3158
+ baseProps: modification.node.baseProps,
3159
+ forkProps: modification.node.forkProps
3160
+ });
3161
+ }
3162
+ for (const modification of plan.survivingModifications) {
3163
+ const identity = mergeKeyOf(modification.node);
3164
+ if (plan.nodeDeletions.has(identity) || canonicalIdentities.has(identity)) {
3165
+ continue;
3166
+ }
3167
+ const collection = nodeCollection(nodesApi, modification.node.kind);
3168
+ await collection.upsertByIdFromRecord(
3169
+ modification.node.id,
3170
+ commitModificationProps(
3171
+ modification.node.baseProps,
3172
+ modification.node.forkProps
3173
+ )
3174
+ );
3175
+ committedNodeIds.add(identity);
3176
+ }
3177
+ for (const entity of plan.canonicalEntities) {
3178
+ const identity = mergeKey(entity.kind, entity.canonicalId);
3179
+ if (plan.nodeDeletions.has(identity)) {
3180
+ continue;
3181
+ }
3182
+ const kind = plan.retypeMap.get(identity) ?? entity.kind;
3183
+ const modification = survivingModProps.get(identity);
3184
+ const props = modification === void 0 ? entity.props : {
3185
+ ...entity.props,
3186
+ ...commitModificationProps(
3187
+ modification.baseProps,
3188
+ modification.forkProps
3189
+ )
3190
+ };
3191
+ const collection = nodeCollection(nodesApi, kind);
3192
+ await collection.upsertByIdFromRecord(entity.canonicalId, props);
3193
+ committedNodeIds.add(mergeKey(kind, entity.canonicalId));
3194
+ }
3195
+ for (const [identity, kind] of plan.nodeDeletions) {
3196
+ const collection = nodeCollection(nodesApi, kind);
3197
+ await collection.delete(idOf(identity));
3198
+ }
3199
+ for (const [identity, kind] of plan.edgeDeletions) {
3200
+ await edgeCollection(edgesApi, kind).delete(idOf(identity));
3201
+ }
3202
+ const edgesByKind = /* @__PURE__ */ new Map();
3203
+ for (const edge of plan.mergedEdges) {
3204
+ const fromKind = plan.retypeMap.get(mergeKey(edge.fromKind, edge.fromId));
3205
+ const toKind = plan.retypeMap.get(mergeKey(edge.toKind, edge.toId));
3206
+ const edgeBaseProps = plan.inheritedEdgeBaseProps.get(edge.id);
3207
+ const props = edgeBaseProps === void 0 ? edge.props : commitModificationProps(edgeBaseProps, edge.props);
3208
+ const item = {
3209
+ id: edge.id,
3210
+ from: { kind: fromKind ?? edge.fromKind, id: edge.fromId },
3211
+ to: { kind: toKind ?? edge.toKind, id: edge.toId },
3212
+ props
3213
+ };
3214
+ const existing = edgesByKind.get(edge.kind);
3215
+ if (existing === void 0) {
3216
+ edgesByKind.set(edge.kind, [item]);
3217
+ } else {
3218
+ existing.push(item);
3219
+ }
3220
+ }
3221
+ let committedEdges = 0;
3222
+ for (const [kind, items] of edgesByKind) {
3223
+ await edgeCollection(edgesApi, kind).bulkUpsertById(items);
3224
+ committedEdges += items.length;
3225
+ }
3226
+ return { nodes: committedNodeIds.size, edges: committedEdges };
3227
+ }
3228
+ async function commitPlan(target, plan) {
3229
+ if (!target.backend.capabilities.transactions) {
3230
+ throw new MergeError(
3231
+ "merge() requires a transaction-capable target backend. The merged plan (canonical upserts, soft-deletes, edge upserts) must commit atomically; a non-transactional fallback would leave a partially-merged graph on a mid-commit failure. (mergeIncremental() enforces the same requirement.)",
3232
+ { details: { capability: "transactions" } }
3233
+ );
3234
+ }
3235
+ return target.transaction(
3236
+ (tx) => applyMergePlan(
3237
+ plan,
3238
+ tx.nodes,
3239
+ tx.edges
3240
+ )
3241
+ );
3242
+ }
3243
+ function nodeCollection(nodes, kind) {
3244
+ const collection = nodes[kind];
3245
+ if (collection === void 0) {
3246
+ throw new MergeError(`No node collection for kind "${kind}".`, {
3247
+ details: { kind }
3248
+ });
3249
+ }
3250
+ return collection;
3251
+ }
3252
+ function edgeCollection(edges, kind) {
3253
+ const collection = edges[kind];
3254
+ if (collection === void 0) {
3255
+ throw new MergeError(`No edge collection for kind "${kind}".`, {
3256
+ details: { kind }
3257
+ });
3258
+ }
3259
+ return collection;
3260
+ }
3261
+ async function validateBaseVersions(target, branches) {
3262
+ const targetVersion = await computeBaseVersion(target);
3263
+ for (const branch2 of branches) {
3264
+ if (branch2.base !== targetVersion) {
3265
+ return err(
3266
+ new BaseVersionMismatchError(
3267
+ `Branch "${branch2.id}" forked from base@V "${branch2.base}", which does not match the merge target's current base@V "${targetVersion}".`,
3268
+ {
3269
+ details: {
3270
+ branchId: branch2.id,
3271
+ branchBase: branch2.base,
3272
+ targetBase: targetVersion
3273
+ }
3274
+ }
3275
+ )
3276
+ );
3277
+ }
3278
+ }
3279
+ return ok();
3280
+ }
3281
+ function tryNormalize(optionsInput) {
3282
+ try {
3283
+ return ok(normalizeMergeOptions(optionsInput));
3284
+ } catch (error) {
3285
+ return err(new MergeError("Invalid merge options.", { cause: error }));
3286
+ }
3287
+ }
3288
+ async function resolveMerge(store, target, branches, options, useBaseSources, incremental) {
3289
+ let targetBranchSeen = false;
3290
+ for (const branch2 of branches) {
3291
+ if (branch2.id === BASE_PROVENANCE_BRANCH) {
3292
+ return err(
3293
+ new MergeError(
3294
+ `Branch id "${BASE_PROVENANCE_BRANCH}" is reserved for committed-base provenance and cannot be used as a branch id.`,
3295
+ { details: { branchId: branch2.id } }
3296
+ )
3297
+ );
3298
+ }
3299
+ if (branch2.id === COMMITTED_TARGET_BRANCH) {
3300
+ if (incremental?.targetBranchId !== COMMITTED_TARGET_BRANCH || targetBranchSeen) {
3301
+ return err(
3302
+ new MergeError(
3303
+ `Branch id "${COMMITTED_TARGET_BRANCH}" is reserved for the committed incremental target and cannot be used as a branch id.`,
3304
+ { details: { branchId: branch2.id } }
3305
+ )
3306
+ );
3307
+ }
3308
+ targetBranchSeen = true;
3309
+ }
3310
+ }
3311
+ try {
3312
+ const staging = await stageBranches(store, branches);
3313
+ const preferredBranchId = incremental?.targetBranchId;
3314
+ const branchIds = branches.map((branch2) => branch2.id);
3315
+ const branchOrder = preferredBranchId === void 0 ? options.branchOrder ?? [] : [
3316
+ preferredBranchId,
3317
+ ...(options.branchOrder ?? []).filter(
3318
+ (branchId) => branchId !== preferredBranchId
3319
+ )
3320
+ ];
3321
+ const branchRank = buildBranchRank(branchOrder, branchIds);
3322
+ const introspection = store.introspect();
3323
+ const introspectionKinds = /* @__PURE__ */ new Map();
3324
+ for (const kind of introspection.kinds) {
3325
+ introspectionKinds.set(kind.name, kind.unique);
3326
+ }
3327
+ const subClassClosure = buildSubClassClosure(introspection.ontology);
3328
+ const embeddings = options.embedder === void 0 ? void 0 : await precomputeEmbeddings(
3329
+ newNodesByKind(staging),
3330
+ options.resolve,
3331
+ options.embedder
3332
+ );
3333
+ const ctx = {
3334
+ backend: store.backend,
3335
+ ...embeddings === void 0 ? {} : { embeddings }
3336
+ };
3337
+ const candidates = await generateAllCandidates(
3338
+ target,
3339
+ staging,
3340
+ options,
3341
+ introspectionKinds,
3342
+ ctx,
3343
+ useBaseSources,
3344
+ options.embedder
3345
+ );
3346
+ if (isErr(candidates)) {
3347
+ return err(candidates.error);
3348
+ }
3349
+ const plan = planMerge(
3350
+ staging,
3351
+ candidates.data.edges,
3352
+ candidates.data.warnings,
3353
+ candidates.data.baseMembers,
3354
+ options,
3355
+ branchRank,
3356
+ subClassClosure,
3357
+ preferredBranchId
3358
+ );
3359
+ const merged = incremental === void 0 ? await commitPlan(target, plan) : await commitIncrementalPlan(target, plan);
3360
+ const provenance = options.provenance ? buildProvenanceIndex(plan.provenanceRecords) : { byBranch: () => ({ nodeIds: [], edgeIds: [] }) };
3361
+ const warnings = [...plan.warnings];
3362
+ let provenancePersisted;
3363
+ if (options.persistProvenance) {
3364
+ try {
3365
+ const provenanceStore = await openProvenanceStore(
3366
+ target.backend,
3367
+ target.graphId
3368
+ );
3369
+ const count = await persistProvenanceRecords(
3370
+ provenanceStore,
3371
+ target.graphId,
3372
+ plan.provenanceRecords
3373
+ );
3374
+ provenancePersisted = {
3375
+ graphId: provenanceGraphId(target.graphId),
3376
+ count
3377
+ };
3378
+ } catch (error) {
3379
+ warnings.push(
3380
+ "provenance persistence failed (graph committed; provenance not persisted): " + (error instanceof Error ? error.message : String(error))
3381
+ );
3382
+ }
3383
+ }
3384
+ const report = {
3385
+ merged,
3386
+ resolutions: plan.resolutions,
3387
+ conflicts: plan.propertyConflicts,
3388
+ deleteModifyConflicts: plan.deleteModifyConflicts,
3389
+ typeReconciliations: plan.typeReconciliations,
3390
+ dropped: plan.dropped,
3391
+ baseAmbiguities: plan.baseAmbiguities,
3392
+ provenance,
3393
+ warnings,
3394
+ ...provenancePersisted === void 0 ? {} : { provenancePersisted }
3395
+ };
3396
+ return ok(report);
3397
+ } catch (error) {
3398
+ if (error instanceof MergeError) {
3399
+ return err(error);
3400
+ }
3401
+ return err(
3402
+ new MergeError(
3403
+ `Merge failed while staging or committing: ${describeCause(error)}`,
3404
+ { cause: error }
3405
+ )
3406
+ );
3407
+ }
3408
+ }
3409
+ async function merge(store, branches, optionsInput = {}) {
3410
+ const normalized = tryNormalize(optionsInput);
3411
+ if (isErr(normalized)) {
3412
+ return err(normalized.error);
3413
+ }
3414
+ const options = normalized.data;
3415
+ const target = options.target ?? store;
3416
+ const precondition = await validateBaseVersions(target, branches);
3417
+ if (isErr(precondition)) {
3418
+ return err(precondition.error);
3419
+ }
3420
+ return resolveMerge(store, target, branches, options, false);
3421
+ }
3422
+ async function validateForkPointVersions(forkPoint, branches) {
3423
+ const forkVersion = await computeBaseVersion(forkPoint);
3424
+ for (const branch2 of branches) {
3425
+ if (branch2.base !== forkVersion) {
3426
+ return err(
3427
+ new BaseVersionMismatchError(
3428
+ `Branch "${branch2.id}" forked from base@V "${branch2.base}", which does not match the fork-point's base@V "${forkVersion}". mergeIncremental() requires every branch to have forked from the supplied forkPoint.`,
3429
+ {
3430
+ details: {
3431
+ branchId: branch2.id,
3432
+ branchBase: branch2.base,
3433
+ forkPointBase: forkVersion
3434
+ }
3435
+ }
3436
+ )
3437
+ );
3438
+ }
3439
+ }
3440
+ return ok(forkVersion);
3441
+ }
3442
+ function nodeSchemaFor(target, kind) {
3443
+ const registry = target.graph.nodes;
3444
+ return registry[kind]?.type?.schema;
3445
+ }
3446
+ function edgeSchemaFor(target, kind) {
3447
+ const registry = target.graph.edges;
3448
+ return registry[kind]?.type?.schema;
3449
+ }
3450
+ function analyzeRevalidatingWrite(schema, current, planned) {
3451
+ const merged = { ...current, ...planned };
3452
+ if (schema === void 0) {
3453
+ const storageWouldChange = canonicalizeProps(merged) !== canonicalizeProps(current);
3454
+ return {
3455
+ status: "valid",
3456
+ storageWouldChange,
3457
+ schemaWouldChange: storageWouldChange,
3458
+ stripsCurrentProps: false
3459
+ };
3460
+ }
3461
+ const currentParsed = schema.safeParse(current);
3462
+ const mergedParsed = schema.safeParse(merged);
3463
+ if (!currentParsed.success || !mergedParsed.success) {
3464
+ return { status: "invalid" };
3465
+ }
3466
+ const normalizedCurrent = currentParsed.data;
3467
+ const normalizedMerged = mergedParsed.data;
3468
+ return {
3469
+ status: "valid",
3470
+ storageWouldChange: canonicalizeProps(normalizedMerged) !== canonicalizeProps(current),
3471
+ schemaWouldChange: canonicalizeProps(normalizedMerged) !== canonicalizeProps(normalizedCurrent),
3472
+ stripsCurrentProps: canonicalizeProps(normalizedCurrent) !== canonicalizeProps(current)
3473
+ };
3474
+ }
3475
+ function nodeProps2(node) {
3476
+ const props = {};
3477
+ for (const [key, value] of Object.entries(node)) {
3478
+ if (key === "id" || key === "kind" || key === "meta") continue;
3479
+ props[key] = value;
3480
+ }
3481
+ return props;
3482
+ }
3483
+ function edgeProps(edge) {
3484
+ const props = {};
3485
+ for (const [key, value] of Object.entries(edge)) {
3486
+ if (key === "id" || key === "kind" || key === "fromKind" || key === "fromId" || key === "toKind" || key === "toId" || key === "meta") {
3487
+ continue;
3488
+ }
3489
+ props[key] = value;
3490
+ }
3491
+ return props;
3492
+ }
3493
+ var INCLUDE_TOMBSTONES = { temporalMode: "includeTombstones" };
3494
+ function finalEdgeFrom(edge, plan) {
3495
+ return {
3496
+ kind: plan.retypeMap.get(mergeKey(edge.fromKind, edge.fromId)) ?? edge.fromKind,
3497
+ id: edge.fromId
3498
+ };
3499
+ }
3500
+ function finalEdgeTo(edge, plan) {
3501
+ return {
3502
+ kind: plan.retypeMap.get(mergeKey(edge.toKind, edge.toId)) ?? edge.toKind,
3503
+ id: edge.toId
3504
+ };
3505
+ }
3506
+ function assertValidRevalidatingWrite(analysis, message, details) {
3507
+ if (analysis.status === "invalid") {
3508
+ throw new MergeError(message, { details });
3509
+ }
3510
+ }
3511
+ function describeCause(cause) {
3512
+ return cause instanceof Error ? cause.message : String(cause);
3513
+ }
3514
+ function plannedNodeWrites(plan) {
3515
+ const canonicalIdentities = /* @__PURE__ */ new Set();
3516
+ for (const entity of plan.canonicalEntities) {
3517
+ canonicalIdentities.add(mergeKey(entity.kind, entity.canonicalId));
3518
+ }
3519
+ const survivingModProps = /* @__PURE__ */ new Map();
3520
+ for (const modification of plan.survivingModifications) {
3521
+ survivingModProps.set(
3522
+ mergeKeyOf(modification.node),
3523
+ modification.node.forkProps
3524
+ );
3525
+ }
3526
+ const writes = [];
3527
+ for (const modification of plan.survivingModifications) {
3528
+ const identity = mergeKeyOf(modification.node);
3529
+ if (plan.nodeDeletions.has(identity) || canonicalIdentities.has(identity)) {
3530
+ continue;
3531
+ }
3532
+ writes.push({
3533
+ identity,
3534
+ kind: modification.node.kind,
3535
+ id: modification.node.id,
3536
+ props: modification.node.forkProps
3537
+ });
3538
+ }
3539
+ for (const entity of plan.canonicalEntities) {
3540
+ const sourceIdentity = mergeKey(entity.kind, entity.canonicalId);
3541
+ if (plan.nodeDeletions.has(sourceIdentity)) {
3542
+ continue;
3543
+ }
3544
+ const kind = plan.retypeMap.get(sourceIdentity) ?? entity.kind;
3545
+ const forkProps = survivingModProps.get(sourceIdentity);
3546
+ writes.push({
3547
+ identity: mergeKey(kind, entity.canonicalId),
3548
+ kind,
3549
+ id: entity.canonicalId,
3550
+ props: forkProps === void 0 ? entity.props : { ...entity.props, ...forkProps }
3551
+ });
3552
+ }
3553
+ return writes;
3554
+ }
3555
+ function edgeWriteSignature(edge, plan) {
3556
+ const from = finalEdgeFrom(edge, plan);
3557
+ const to = finalEdgeTo(edge, plan);
3558
+ return JSON.stringify([
3559
+ edge.kind,
3560
+ from.kind,
3561
+ from.id,
3562
+ to.kind,
3563
+ to.id,
3564
+ canonicalizeProps(edge.props)
3565
+ ]);
3566
+ }
3567
+ async function validateIncrementalNodeWrites(target, nodesApi, plan) {
3568
+ const writes = plannedNodeWrites(plan);
3569
+ const nodeIdsByKind = /* @__PURE__ */ new Map();
3570
+ for (const write of writes) {
3571
+ const bucket = nodeIdsByKind.get(write.kind) ?? [];
3572
+ bucket.push(write.id);
3573
+ nodeIdsByKind.set(write.kind, bucket);
3574
+ }
3575
+ const currentByIdentity = /* @__PURE__ */ new Map();
3576
+ for (const [kind, ids] of nodeIdsByKind) {
3577
+ const rows = await nodeCollection(nodesApi, kind).getByIds(
3578
+ ids,
3579
+ INCLUDE_TOMBSTONES
3580
+ );
3581
+ for (const [index, id] of ids.entries()) {
3582
+ currentByIdentity.set(mergeKey(kind, id), rows[index]);
3583
+ }
3584
+ }
3585
+ for (const write of writes) {
3586
+ const current = currentByIdentity.get(write.identity);
3587
+ if (current === void 0) {
3588
+ continue;
3589
+ }
3590
+ if (current.meta.deletedAt !== void 0) {
3591
+ throw new MergeError(
3592
+ `mergeIncremental() would resurrect soft-deleted committed node "${write.id}" (kind "${write.kind}").`,
3593
+ { details: { id: write.id, kind: write.kind } }
3594
+ );
3595
+ }
3596
+ const analysis = analyzeRevalidatingWrite(
3597
+ nodeSchemaFor(target, write.kind),
3598
+ nodeProps2(current),
3599
+ write.props
3600
+ );
3601
+ assertValidRevalidatingWrite(
3602
+ analysis,
3603
+ `mergeIncremental() found an existing committed node "${write.id}" (kind "${write.kind}") whose current props do not validate against the active schema.`,
3604
+ { id: write.id, kind: write.kind }
3605
+ );
3606
+ if (analysis.stripsCurrentProps && analysis.schemaWouldChange) {
3607
+ throw new MergeError(
3608
+ `mergeIncremental() would update committed node "${write.id}" (kind "${write.kind}") but the write would strip existing props outside the active schema (lossy base update).`,
3609
+ { details: { id: write.id, kind: write.kind } }
3610
+ );
3611
+ }
3612
+ }
3613
+ }
3614
+ async function validateIncrementalEdgeWrites(target, edgesApi, plan) {
3615
+ const signatureById = /* @__PURE__ */ new Map();
3616
+ for (const edge of plan.mergedEdges) {
3617
+ const signature = edgeWriteSignature(edge, plan);
3618
+ const existing = signatureById.get(edge.id);
3619
+ if (existing !== void 0 && existing !== signature) {
3620
+ throw new MergeError(
3621
+ `mergeIncremental() would overwrite committed edge "${edge.id}" with multiple planned endpoint/prop shapes (edge.id collision).`,
3622
+ { details: { id: edge.id } }
3623
+ );
3624
+ }
3625
+ signatureById.set(edge.id, signature);
3626
+ }
3627
+ const plannedKindById = /* @__PURE__ */ new Map();
3628
+ const idsByKind = /* @__PURE__ */ new Map();
3629
+ for (const edge of plan.mergedEdges) {
3630
+ if (plannedKindById.has(edge.id)) {
3631
+ continue;
3632
+ }
3633
+ plannedKindById.set(edge.id, edge.kind);
3634
+ const bucket = idsByKind.get(edge.kind) ?? [];
3635
+ bucket.push(edge.id);
3636
+ idsByKind.set(edge.kind, bucket);
3637
+ }
3638
+ const existingById = /* @__PURE__ */ new Map();
3639
+ for (const [kind, ids] of idsByKind) {
3640
+ const rows = await edgeCollection(edgesApi, kind).getByIds(
3641
+ ids,
3642
+ INCLUDE_TOMBSTONES
3643
+ );
3644
+ for (const [index, id] of ids.entries()) {
3645
+ const row = rows[index];
3646
+ if (row !== void 0) {
3647
+ existingById.set(id, row);
3648
+ }
3649
+ }
3650
+ }
3651
+ const notFound = [...plannedKindById.keys()].filter((id) => !existingById.has(id)).sort((left, right) => compareStrings(left, right));
3652
+ if (notFound.length > 0) {
3653
+ const perKind = await Promise.all(
3654
+ Object.keys(target.graph.edges).map(
3655
+ (kind) => edgeCollection(edgesApi, kind).getByIds(notFound, INCLUDE_TOMBSTONES)
3656
+ )
3657
+ );
3658
+ const crossKindById = /* @__PURE__ */ new Map();
3659
+ for (const rows of perKind) {
3660
+ for (const [index, id] of notFound.entries()) {
3661
+ const row = rows[index];
3662
+ if (row !== void 0 && !crossKindById.has(id)) {
3663
+ crossKindById.set(id, row);
3664
+ }
3665
+ }
3666
+ }
3667
+ for (const id of notFound) {
3668
+ const row = crossKindById.get(id);
3669
+ if (row === void 0) {
3670
+ continue;
3671
+ }
3672
+ if (row.meta.deletedAt !== void 0) {
3673
+ throw new MergeError(
3674
+ `mergeIncremental() would resurrect soft-deleted committed edge "${id}" (kind "${row.kind}").`,
3675
+ { details: { id, committedKind: row.kind } }
3676
+ );
3677
+ }
3678
+ throw new MergeError(
3679
+ `mergeIncremental() would overwrite committed edge "${id}" (kind "${row.kind}") with a different-kind edge "${plannedKindById.get(id)}" of the same id.`,
3680
+ {
3681
+ details: {
3682
+ id,
3683
+ committedKind: row.kind,
3684
+ plannedKind: plannedKindById.get(id)
3685
+ }
3686
+ }
3687
+ );
3688
+ }
3689
+ }
3690
+ for (const edge of plan.mergedEdges) {
3691
+ const current = existingById.get(edge.id);
3692
+ if (current === void 0) {
3693
+ continue;
3694
+ }
3695
+ if (current.meta.deletedAt !== void 0) {
3696
+ throw new MergeError(
3697
+ `mergeIncremental() would resurrect soft-deleted committed edge "${edge.id}" (kind "${current.kind}").`,
3698
+ { details: { id: edge.id, committedKind: current.kind } }
3699
+ );
3700
+ }
3701
+ const from = finalEdgeFrom(edge, plan);
3702
+ const to = finalEdgeTo(edge, plan);
3703
+ if (current.fromId !== from.id || current.fromKind !== from.kind || current.toId !== to.id || current.toKind !== to.kind) {
3704
+ throw new MergeError(
3705
+ `mergeIncremental() would overwrite committed edge "${edge.id}" (kind "${edge.kind}") with different endpoints.`,
3706
+ { details: { id: edge.id, kind: edge.kind } }
3707
+ );
3708
+ }
3709
+ const analysis = analyzeRevalidatingWrite(
3710
+ edgeSchemaFor(target, edge.kind),
3711
+ edgeProps(current),
3712
+ edge.props
3713
+ );
3714
+ assertValidRevalidatingWrite(
3715
+ analysis,
3716
+ `mergeIncremental() found an existing committed edge "${edge.id}" (kind "${edge.kind}") whose current props do not validate against the active schema.`,
3717
+ { id: edge.id, kind: edge.kind }
3718
+ );
3719
+ if (analysis.stripsCurrentProps && analysis.schemaWouldChange) {
3720
+ throw new MergeError(
3721
+ `mergeIncremental() would update committed edge "${edge.id}" (kind "${edge.kind}") but the write would strip existing props outside the active schema.`,
3722
+ { details: { id: edge.id, kind: edge.kind } }
3723
+ );
3724
+ }
3725
+ }
3726
+ }
3727
+ async function commitIncrementalPlan(target, plan) {
3728
+ if (!target.backend.capabilities.transactions) {
3729
+ throw new MergeError(
3730
+ "mergeIncremental() requires a transaction-capable target backend. Incremental writes must preflight existing rows and commit atomically; non-transactional fallback would allow partial graph writes.",
3731
+ { details: { capability: "transactions" } }
3732
+ );
3733
+ }
3734
+ return target.transaction(async (tx) => {
3735
+ const nodesApi = tx.nodes;
3736
+ const edgesApi = tx.edges;
3737
+ await validateIncrementalNodeWrites(target, nodesApi, plan);
3738
+ await validateIncrementalEdgeWrites(target, edgesApi, plan);
3739
+ return applyMergePlan(plan, nodesApi, edgesApi);
3740
+ });
3741
+ }
3742
+ async function mergeIncremental(args) {
3743
+ const { forkPoint, target, branches } = args;
3744
+ const normalized = tryNormalize(args.options ?? {});
3745
+ if (isErr(normalized)) {
3746
+ return err(normalized.error);
3747
+ }
3748
+ const options = normalized.data;
3749
+ if (options.onBasePropertyConflict !== "flag") {
3750
+ return err(
3751
+ new MergeError(
3752
+ 'mergeIncremental() requires onBasePropertyConflict: "flag" (keep-base); a non-keep-base policy could overwrite a newer committed base value with a stale branch value.',
3753
+ { details: {} }
3754
+ )
3755
+ );
3756
+ }
3757
+ const forkPrecondition = await validateForkPointVersions(forkPoint, branches);
3758
+ if (isErr(forkPrecondition)) {
3759
+ return err(forkPrecondition.error);
3760
+ }
3761
+ const forkVersion = forkPrecondition.data;
3762
+ const [forkSchema, targetSchema] = await Promise.all([
3763
+ computeSchemaComponent(forkPoint),
3764
+ computeSchemaComponent(target)
3765
+ ]);
3766
+ if (forkSchema !== targetSchema) {
3767
+ return err(
3768
+ new MergeError(
3769
+ "mergeIncremental() requires target and forkPoint to share a schema; schema drift is not supported (the target content may differ \u2014 only the schema must match).",
3770
+ { details: {} }
3771
+ )
3772
+ );
3773
+ }
3774
+ const targetBranch = {
3775
+ id: COMMITTED_TARGET_BRANCH,
3776
+ base: forkVersion,
3777
+ store: target
3778
+ };
3779
+ return resolveMerge(
3780
+ forkPoint,
3781
+ target,
3782
+ [targetBranch, ...branches],
3783
+ options,
3784
+ true,
3785
+ { targetBranchId: COMMITTED_TARGET_BRANCH }
3786
+ );
3787
+ }
3788
+
3789
+ export { BaseVersionMismatchError, BranchError, MERGE_ERROR_CODES, MERGE_OPTION_DEFAULTS, MergeConflictError, MergeError, SimilarityUnavailableError, asBaseVersion, asBranchId, branch, cloneWorkingCopyStrategy, computeBaseVersion, merge, mergeIncremental, normalizeMergeOptions, openProvenanceStore, persistProvenanceRecords, provenanceGraphId, readProvenance };
3790
+ //# sourceMappingURL=index.js.map
3791
+ //# sourceMappingURL=index.js.map