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