@nicia-ai/typegraph 0.31.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 (125) hide show
  1. package/dist/backend/postgres/index.cjs +16 -16
  2. package/dist/backend/postgres/index.d.cts +5 -5
  3. package/dist/backend/postgres/index.d.ts +5 -5
  4. package/dist/backend/postgres/index.js +6 -6
  5. package/dist/backend/postgres/pglite.cjs +9 -9
  6. package/dist/backend/postgres/pglite.d.cts +2 -2
  7. package/dist/backend/postgres/pglite.d.ts +2 -2
  8. package/dist/backend/postgres/pglite.js +6 -6
  9. package/dist/backend/sqlite/index.cjs +14 -14
  10. package/dist/backend/sqlite/index.d.cts +6 -6
  11. package/dist/backend/sqlite/index.d.ts +6 -6
  12. package/dist/backend/sqlite/index.js +5 -5
  13. package/dist/backend/sqlite/libsql.cjs +10 -10
  14. package/dist/backend/sqlite/libsql.d.cts +2 -2
  15. package/dist/backend/sqlite/libsql.d.ts +2 -2
  16. package/dist/backend/sqlite/libsql.js +6 -6
  17. package/dist/backend/sqlite/local.cjs +10 -10
  18. package/dist/backend/sqlite/local.d.cts +2 -2
  19. package/dist/backend/sqlite/local.d.ts +2 -2
  20. package/dist/backend/sqlite/local.js +6 -6
  21. package/dist/{chunk-XKJQQQVR.js → chunk-2BVHPOIG.js} +3 -3
  22. package/dist/{chunk-XKJQQQVR.js.map → chunk-2BVHPOIG.js.map} +1 -1
  23. package/dist/{chunk-RZ4DH4E7.cjs → chunk-2LAT5BIK.cjs} +662 -217
  24. package/dist/chunk-2LAT5BIK.cjs.map +1 -0
  25. package/dist/{chunk-CIINW6PS.cjs → chunk-3HQDMOBK.cjs} +64 -64
  26. package/dist/{chunk-CIINW6PS.cjs.map → chunk-3HQDMOBK.cjs.map} +1 -1
  27. package/dist/{chunk-6SA7UIQJ.cjs → chunk-3QRBXYT5.cjs} +6 -6
  28. package/dist/{chunk-6SA7UIQJ.cjs.map → chunk-3QRBXYT5.cjs.map} +1 -1
  29. package/dist/{chunk-4A4GUZTQ.js → chunk-7WW3WL6E.js} +5 -5
  30. package/dist/{chunk-4A4GUZTQ.js.map → chunk-7WW3WL6E.js.map} +1 -1
  31. package/dist/{chunk-2HZGWCB4.js → chunk-C4C4Q7A6.js} +3 -3
  32. package/dist/chunk-C4C4Q7A6.js.map +1 -0
  33. package/dist/{chunk-SFFJ2IKN.cjs → chunk-CAMMC3E4.cjs} +58 -58
  34. package/dist/{chunk-SFFJ2IKN.cjs.map → chunk-CAMMC3E4.cjs.map} +1 -1
  35. package/dist/{chunk-O4VUR6SI.js → chunk-CEF2QOTO.js} +3 -3
  36. package/dist/{chunk-O4VUR6SI.js.map → chunk-CEF2QOTO.js.map} +1 -1
  37. package/dist/{chunk-V4IQYOQ4.cjs → chunk-CFNT3ITG.cjs} +3 -3
  38. package/dist/chunk-CFNT3ITG.cjs.map +1 -0
  39. package/dist/{chunk-LJLMDDCW.js → chunk-CWFPJ3RC.js} +3 -3
  40. package/dist/{chunk-LJLMDDCW.js.map → chunk-CWFPJ3RC.js.map} +1 -1
  41. package/dist/{chunk-7JGLPAFV.cjs → chunk-EEYV6UOE.cjs} +16 -16
  42. package/dist/chunk-EEYV6UOE.cjs.map +1 -0
  43. package/dist/{chunk-ASYQDPPU.cjs → chunk-FDN43KXV.cjs} +19 -19
  44. package/dist/{chunk-ASYQDPPU.cjs.map → chunk-FDN43KXV.cjs.map} +1 -1
  45. package/dist/{chunk-THX25XW6.js → chunk-HFBRAOF7.js} +27 -7
  46. package/dist/chunk-HFBRAOF7.js.map +1 -0
  47. package/dist/{chunk-54JVNOAQ.cjs → chunk-JKRKIEVA.cjs} +5 -5
  48. package/dist/{chunk-54JVNOAQ.cjs.map → chunk-JKRKIEVA.cjs.map} +1 -1
  49. package/dist/{chunk-5DTPBFX5.cjs → chunk-LAZGHOZW.cjs} +10 -10
  50. package/dist/{chunk-5DTPBFX5.cjs.map → chunk-LAZGHOZW.cjs.map} +1 -1
  51. package/dist/{chunk-N2MBPTAF.cjs → chunk-LM2IC4ZJ.cjs} +14 -14
  52. package/dist/{chunk-N2MBPTAF.cjs.map → chunk-LM2IC4ZJ.cjs.map} +1 -1
  53. package/dist/{chunk-STP7QVUB.js → chunk-NDM5PXEH.js} +5 -5
  54. package/dist/{chunk-STP7QVUB.js.map → chunk-NDM5PXEH.js.map} +1 -1
  55. package/dist/{chunk-2ZY4K43M.js → chunk-PIHXFF73.js} +3 -3
  56. package/dist/{chunk-2ZY4K43M.js.map → chunk-PIHXFF73.js.map} +1 -1
  57. package/dist/{chunk-ATTXBMAX.js → chunk-PRMKPFN5.js} +3 -3
  58. package/dist/{chunk-ATTXBMAX.js.map → chunk-PRMKPFN5.js.map} +1 -1
  59. package/dist/{chunk-3JBJEYWE.cjs → chunk-SB65GJNY.cjs} +32 -12
  60. package/dist/chunk-SB65GJNY.cjs.map +1 -0
  61. package/dist/{chunk-VZX2KSY3.js → chunk-TUA3MEJJ.js} +5 -5
  62. package/dist/{chunk-VZX2KSY3.js.map → chunk-TUA3MEJJ.js.map} +1 -1
  63. package/dist/{chunk-MAQJYNJR.js → chunk-W4ZX2GCP.js} +14 -16
  64. package/dist/chunk-W4ZX2GCP.js.map +1 -0
  65. package/dist/{chunk-UIFEEPME.js → chunk-WNORITK6.js} +593 -149
  66. package/dist/chunk-WNORITK6.js.map +1 -0
  67. package/dist/{chunk-VMEWZU2N.cjs → chunk-WWVV44DY.cjs} +11 -11
  68. package/dist/{chunk-VMEWZU2N.cjs.map → chunk-WWVV44DY.cjs.map} +1 -1
  69. package/dist/{chunk-UJ4V3YUQ.js → chunk-XKNAK27R.js} +3 -3
  70. package/dist/{chunk-UJ4V3YUQ.js.map → chunk-XKNAK27R.js.map} +1 -1
  71. package/dist/{chunk-GKFWM3DS.js → chunk-XQFAJVYW.js} +3 -3
  72. package/dist/{chunk-GKFWM3DS.js.map → chunk-XQFAJVYW.js.map} +1 -1
  73. package/dist/{chunk-FLOI7QOT.cjs → chunk-YL4F3CMD.cjs} +59 -59
  74. package/dist/{chunk-FLOI7QOT.cjs.map → chunk-YL4F3CMD.cjs.map} +1 -1
  75. package/dist/{chunk-5CN7B4CZ.cjs → chunk-YW7GHMF4.cjs} +17 -17
  76. package/dist/{chunk-5CN7B4CZ.cjs.map → chunk-YW7GHMF4.cjs.map} +1 -1
  77. package/dist/{ddl-BJcPrB4L.d.ts → ddl-C8e8zDAE.d.ts} +3 -3
  78. package/dist/{ddl-CPZ7mB7X.d.cts → ddl-CwyG4HoQ.d.cts} +3 -3
  79. package/dist/graph-merge/index.cjs +193 -49
  80. package/dist/graph-merge/index.cjs.map +1 -1
  81. package/dist/graph-merge/index.d.cts +3 -3
  82. package/dist/graph-merge/index.d.ts +3 -3
  83. package/dist/graph-merge/index.js +180 -36
  84. package/dist/graph-merge/index.js.map +1 -1
  85. package/dist/index.cjs +89 -85
  86. package/dist/index.d.cts +7 -7
  87. package/dist/index.d.ts +7 -7
  88. package/dist/index.js +12 -12
  89. package/dist/indexes/index.cjs +10 -10
  90. package/dist/indexes/index.d.cts +4 -4
  91. package/dist/indexes/index.d.ts +4 -4
  92. package/dist/indexes/index.js +3 -3
  93. package/dist/interchange/index.cjs +15 -15
  94. package/dist/interchange/index.d.cts +7 -7
  95. package/dist/interchange/index.d.ts +7 -7
  96. package/dist/interchange/index.js +2 -2
  97. package/dist/{manager-By-WEd8M.d.ts → manager-8htmuGr-.d.ts} +1 -1
  98. package/dist/{manager-Np7VVSlE.d.cts → manager-aVgxbGt-.d.cts} +1 -1
  99. package/dist/{postgres-BdIb7tS_.d.ts → postgres-Dhom7iiX.d.ts} +1 -1
  100. package/dist/{postgres-4J2tmauh.d.cts → postgres-jgXmD45C.d.cts} +1 -1
  101. package/dist/profiler/index.d.cts +5 -5
  102. package/dist/profiler/index.d.ts +5 -5
  103. package/dist/schema/index.cjs +26 -26
  104. package/dist/schema/index.d.cts +4 -4
  105. package/dist/schema/index.d.ts +4 -4
  106. package/dist/schema/index.js +3 -3
  107. package/dist/{sqlite-aM_PAz-O.d.ts → sqlite-BfLsCEqO.d.ts} +1 -1
  108. package/dist/{sqlite-Gatk_iw_.d.cts → sqlite-DcQ_BCUW.d.cts} +1 -1
  109. package/dist/{store-DuCbdqsh.d.cts → store-view-lTcb_cnp.d.cts} +639 -311
  110. package/dist/{store-MloRPwb9.d.ts → store-view-pBOUrQte.d.ts} +639 -311
  111. package/dist/{types-DoByHGyU.d.cts → types-5RQwzWXx.d.cts} +1 -1
  112. package/dist/{types-K3vpaJAL.d.ts → types-DGDiXAYc.d.ts} +1 -1
  113. package/dist/{types-BKQljXom.d.ts → types-Dk8r09KY.d.ts} +1 -1
  114. package/dist/{types-C1CYxWrL.d.cts → types-Dvmn-eal.d.cts} +1 -1
  115. package/dist/{where-Dt49bvo6.d.ts → where-CHZFELX0.d.ts} +1 -1
  116. package/dist/{where-DydZjGOb.d.cts → where-Cx_glEEB.d.cts} +1 -1
  117. package/package.json +2 -2
  118. package/dist/chunk-2HZGWCB4.js.map +0 -1
  119. package/dist/chunk-3JBJEYWE.cjs.map +0 -1
  120. package/dist/chunk-7JGLPAFV.cjs.map +0 -1
  121. package/dist/chunk-MAQJYNJR.js.map +0 -1
  122. package/dist/chunk-RZ4DH4E7.cjs.map +0 -1
  123. package/dist/chunk-THX25XW6.js.map +0 -1
  124. package/dist/chunk-UIFEEPME.js.map +0 -1
  125. package/dist/chunk-V4IQYOQ4.cjs.map +0 -1
@@ -1,10 +1,10 @@
1
- import { G as GraphDef, t as NodeKinds, u as GetNodeType } from '../manager-Np7VVSlE.cjs';
2
- import { S as Store, N as Node, U as UniqueIntrospection } from '../store-DuCbdqsh.cjs';
1
+ import { G as GraphDef, t as NodeKinds, u as GetNodeType } from '../manager-aVgxbGt-.cjs';
2
+ import { S as Store, N as Node, U as UniqueIntrospection } from '../store-view-lTcb_cnp.cjs';
3
3
  import { d as NodeId, N as NodeType, e as EdgeId, J as JsonValue, f as KindAnnotations, g as EdgeType, D as DeleteBehavior, T as TemporalMode, h as GraphExtensionVersion, i as ExtensionNodeDef, j as ExtensionEdgeDef, k as ExtensionOntologyRelation, l as ExtensionIndex } from '../extension-types-D1k0inWU.cjs';
4
4
  import { T as TypeGraphError } from '../index-C8sSTErx.cjs';
5
5
  import { R as Result } from '../result-DWo6mUVz.cjs';
6
6
  export { i as isErr, a as isOk, u as unwrap } from '../result-DWo6mUVz.cjs';
7
- import { G as GraphBackend, M as MetaEdge, I as IndexDeclaration } from '../types-C1CYxWrL.cjs';
7
+ import { G as GraphBackend, M as MetaEdge, I as IndexDeclaration } from '../types-Dvmn-eal.cjs';
8
8
  import { z } from 'zod';
9
9
  import 'drizzle-orm';
10
10
  import '../materialize-indexes-DEYkU-wr.cjs';
@@ -1,10 +1,10 @@
1
- import { G as GraphDef, t as NodeKinds, u as GetNodeType } from '../manager-By-WEd8M.js';
2
- import { S as Store, N as Node, U as UniqueIntrospection } from '../store-MloRPwb9.js';
1
+ import { G as GraphDef, t as NodeKinds, u as GetNodeType } from '../manager-8htmuGr-.js';
2
+ import { S as Store, N as Node, U as UniqueIntrospection } from '../store-view-pBOUrQte.js';
3
3
  import { d as NodeId, N as NodeType, e as EdgeId, J as JsonValue, f as KindAnnotations, g as EdgeType, D as DeleteBehavior, T as TemporalMode, h as GraphExtensionVersion, i as ExtensionNodeDef, j as ExtensionEdgeDef, k as ExtensionOntologyRelation, l as ExtensionIndex } from '../extension-types-D1k0inWU.js';
4
4
  import { T as TypeGraphError } from '../index-BEK0VtNB.js';
5
5
  import { R as Result } from '../result-DWo6mUVz.js';
6
6
  export { i as isErr, a as isOk, u as unwrap } from '../result-DWo6mUVz.js';
7
- import { G as GraphBackend, M as MetaEdge, I as IndexDeclaration } from '../types-BKQljXom.js';
7
+ import { G as GraphBackend, M as MetaEdge, I as IndexDeclaration } from '../types-Dk8r09KY.js';
8
8
  import { z } from 'zod';
9
9
  import 'drizzle-orm';
10
10
  import '../materialize-indexes-CAGhqPHQ.js';
@@ -1,9 +1,9 @@
1
- import { createStoreWithSchema, generateId, computeUniqueKey } from '../chunk-UIFEEPME.js';
1
+ import { createStoreWithSchema, generateId, computeUniqueKey } from '../chunk-WNORITK6.js';
2
2
  import '../chunk-3EUKJEQ3.js';
3
- import { exportGraph, importGraph } from '../chunk-THX25XW6.js';
4
- import { defineNode, computeSchemaHash, serializeSchema } from '../chunk-XKJQQQVR.js';
5
- import { getNodeKinds, getEdgeKinds, defineGraph } from '../chunk-MAQJYNJR.js';
6
- import '../chunk-2ZY4K43M.js';
3
+ import { exportGraph, importGraph } from '../chunk-HFBRAOF7.js';
4
+ import { defineNode, computeSchemaHash, serializeSchema } from '../chunk-2BVHPOIG.js';
5
+ import { getNodeKinds, getEdgeKinds, defineGraph } from '../chunk-W4ZX2GCP.js';
6
+ import '../chunk-PIHXFF73.js';
7
7
  import '../chunk-IJORSAC3.js';
8
8
  import '../chunk-MXE2U3EX.js';
9
9
  import '../chunk-454I6GDF.js';
@@ -11,7 +11,7 @@ import { ok, err, isErr, computeTransitiveClosure, isReachable } from '../chunk-
11
11
  export { isErr, isOk, unwrap } from '../chunk-YCV72E6L.js';
12
12
  import '../chunk-SAJSMR2B.js';
13
13
  import { sortedReplacer } from '../chunk-M6OC455F.js';
14
- import '../chunk-2HZGWCB4.js';
14
+ import '../chunk-C4C4Q7A6.js';
15
15
  import { sha256Hex } from '../chunk-2YMGNYXY.js';
16
16
  import { TypeGraphError } from '../chunk-TQOYM4LN.js';
17
17
  import '../chunk-K5P5OZXS.js';
@@ -317,10 +317,7 @@ function byDigestEntry(left, right) {
317
317
  return byKind === 0 ? compareStrings(left.id, right.id) : byKind;
318
318
  }
319
319
  var CONTENT_FINGERPRINT_BYTES = 16;
320
- async function computeContentFingerprint(store) {
321
- const backend = store.backend;
322
- const graphId = store.graphId;
323
- const graph = store.graph;
320
+ async function computeContentComponent(backend, graphId, graph) {
324
321
  const nodeKinds = getNodeKinds(graph);
325
322
  const edgeKinds = getEdgeKinds(graph);
326
323
  const nodeDigest = [];
@@ -368,12 +365,16 @@ async function computeContentFingerprint(store) {
368
365
  async function computeBaseVersion(store) {
369
366
  const [schemaComponent, contentComponent] = await Promise.all([
370
367
  computeSchemaComponent(store),
371
- computeContentFingerprint(store)
368
+ computeContentComponent(store.backend, store.graphId, store.graph)
372
369
  ]);
373
370
  return asBaseVersion(
374
371
  `${schemaComponent}${TOKEN_SEPARATOR}${contentComponent}`
375
372
  );
376
373
  }
374
+ function contentComponentOf(version) {
375
+ const separatorIndex = version.indexOf(TOKEN_SEPARATOR);
376
+ return separatorIndex === -1 ? version : version.slice(separatorIndex + 1);
377
+ }
377
378
 
378
379
  // src/graph-merge/errors.ts
379
380
  var MERGE_ERROR_CODES = {
@@ -2506,6 +2507,48 @@ async function stageBranches(baseStore, branches) {
2506
2507
  };
2507
2508
  }
2508
2509
 
2510
+ // src/graph-merge/tx-retry.ts
2511
+ var RETRYABLE_SQLSTATES = /* @__PURE__ */ new Set(["40001", "40P01"]);
2512
+ var RETRYABLE_MESSAGE_PATTERN = /could not serialize access|deadlock detected/i;
2513
+ var MAX_COMMIT_ATTEMPTS = 3;
2514
+ function isRetryableTxConflict(error) {
2515
+ const seen = /* @__PURE__ */ new Set();
2516
+ let current = error;
2517
+ while (current !== void 0 && current !== null && typeof current === "object" && !seen.has(current)) {
2518
+ seen.add(current);
2519
+ const candidate = current;
2520
+ if (typeof candidate.code === "string" && RETRYABLE_SQLSTATES.has(candidate.code)) {
2521
+ return true;
2522
+ }
2523
+ if (typeof candidate.message === "string" && RETRYABLE_MESSAGE_PATTERN.test(candidate.message)) {
2524
+ return true;
2525
+ }
2526
+ current = candidate.cause;
2527
+ }
2528
+ return false;
2529
+ }
2530
+ async function withTxConflictRetry(commit) {
2531
+ let lastConflict;
2532
+ for (let attempt = 1; attempt <= MAX_COMMIT_ATTEMPTS; attempt += 1) {
2533
+ try {
2534
+ return await commit();
2535
+ } catch (error) {
2536
+ if (!isRetryableTxConflict(error)) {
2537
+ throw error;
2538
+ }
2539
+ lastConflict = error;
2540
+ }
2541
+ }
2542
+ throw new MergeError(
2543
+ `Merge commit aborted by transaction conflicts (serialization failure or deadlock) on ${MAX_COMMIT_ATTEMPTS} consecutive attempts; giving up.`,
2544
+ {
2545
+ cause: lastConflict,
2546
+ details: { attempts: MAX_COMMIT_ATTEMPTS },
2547
+ suggestion: "Reduce concurrent writes to the merge target, or serialize merges against it."
2548
+ }
2549
+ );
2550
+ }
2551
+
2509
2552
  // src/graph-merge/type-reconcile.ts
2510
2553
  var INCOMPATIBLE_TYPES_FLAG_REASON = "type-reconcile:incompatible-kinds";
2511
2554
  function distinctKinds(kinds) {
@@ -3225,20 +3268,48 @@ async function applyMergePlan(plan, nodesApi, edgesApi) {
3225
3268
  }
3226
3269
  return { nodes: committedNodeIds.size, edges: committedEdges };
3227
3270
  }
3228
- async function commitPlan(target, plan) {
3271
+ async function commitPlan(target, plan, expectedBaseVersion) {
3229
3272
  if (!target.backend.capabilities.transactions) {
3230
3273
  throw new MergeError(
3231
3274
  "merge() requires a transaction-capable target backend. The merged plan (canonical upserts, soft-deletes, edge upserts) must commit atomically; a non-transactional fallback would leave a partially-merged graph on a mid-commit failure. (mergeIncremental() enforces the same requirement.)",
3232
3275
  { details: { capability: "transactions" } }
3233
3276
  );
3234
3277
  }
3235
- return target.transaction(
3236
- (tx) => applyMergePlan(
3237
- plan,
3238
- tx.nodes,
3239
- tx.edges
3240
- )
3278
+ return withTxConflictRetry(
3279
+ () => target.transaction(async (tx) => {
3280
+ if (expectedBaseVersion !== void 0) {
3281
+ await assertTargetUnchanged(tx.backend, target, expectedBaseVersion);
3282
+ }
3283
+ return applyMergePlan(
3284
+ plan,
3285
+ tx.nodes,
3286
+ tx.edges
3287
+ );
3288
+ }, MERGE_COMMIT_TX_OPTIONS)
3289
+ );
3290
+ }
3291
+ var MERGE_COMMIT_TX_OPTIONS = {
3292
+ isolationLevel: "serializable"
3293
+ };
3294
+ async function assertTargetUnchanged(txBackend, target, expectedBaseVersion) {
3295
+ const liveContent = await computeContentComponent(
3296
+ txBackend,
3297
+ target.graphId,
3298
+ target.graph
3241
3299
  );
3300
+ const expectedContent = contentComponentOf(expectedBaseVersion);
3301
+ if (liveContent !== expectedContent) {
3302
+ throw new BaseVersionMismatchError(
3303
+ "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.",
3304
+ {
3305
+ details: {
3306
+ expectedContentFingerprint: expectedContent,
3307
+ liveContentFingerprint: liveContent
3308
+ },
3309
+ suggestion: "Re-run the merge (and re-branch if the divergence is real), or serialize writers against merges on this target."
3310
+ }
3311
+ );
3312
+ }
3242
3313
  }
3243
3314
  function nodeCollection(nodes, kind) {
3244
3315
  const collection = nodes[kind];
@@ -3276,7 +3347,7 @@ async function validateBaseVersions(target, branches) {
3276
3347
  );
3277
3348
  }
3278
3349
  }
3279
- return ok();
3350
+ return ok(targetVersion);
3280
3351
  }
3281
3352
  function tryNormalize(optionsInput) {
3282
3353
  try {
@@ -3285,7 +3356,7 @@ function tryNormalize(optionsInput) {
3285
3356
  return err(new MergeError("Invalid merge options.", { cause: error }));
3286
3357
  }
3287
3358
  }
3288
- async function resolveMerge(store, target, branches, options, useBaseSources, incremental) {
3359
+ async function resolveMerge(store, target, branches, options, useBaseSources, incremental, expectedBaseVersion) {
3289
3360
  let targetBranchSeen = false;
3290
3361
  for (const branch2 of branches) {
3291
3362
  if (branch2.id === BASE_PROVENANCE_BRANCH) {
@@ -3356,7 +3427,16 @@ async function resolveMerge(store, target, branches, options, useBaseSources, in
3356
3427
  subClassClosure,
3357
3428
  preferredBranchId
3358
3429
  );
3359
- const merged = incremental === void 0 ? await commitPlan(target, plan) : await commitIncrementalPlan(target, plan);
3430
+ const merged = incremental === void 0 ? await commitPlan(target, plan, expectedBaseVersion) : await commitIncrementalPlan(target, plan, {
3431
+ stagedNewByKind: newNodesByKind(staging),
3432
+ options,
3433
+ introspectionKinds,
3434
+ // The committed (kind, id) keys the base sources matched at PLAN time;
3435
+ // anything NEW the in-tx re-probe surfaces is a window write.
3436
+ plannedBaseMatchKeys: new Set(
3437
+ candidates.data.baseMembers.map((member) => mergeKeyOf(member))
3438
+ )
3439
+ });
3360
3440
  const provenance = options.provenance ? buildProvenanceIndex(plan.provenanceRecords) : { byBranch: () => ({ nodeIds: [], edgeIds: [] }) };
3361
3441
  const warnings = [...plan.warnings];
3362
3442
  let provenancePersisted;
@@ -3417,7 +3497,15 @@ async function merge(store, branches, optionsInput = {}) {
3417
3497
  if (isErr(precondition)) {
3418
3498
  return err(precondition.error);
3419
3499
  }
3420
- return resolveMerge(store, target, branches, options, false);
3500
+ return resolveMerge(
3501
+ store,
3502
+ target,
3503
+ branches,
3504
+ options,
3505
+ false,
3506
+ void 0,
3507
+ precondition.data
3508
+ );
3421
3509
  }
3422
3510
  async function validateForkPointVersions(forkPoint, branches) {
3423
3511
  const forkVersion = await computeBaseVersion(forkPoint);
@@ -3650,13 +3738,12 @@ async function validateIncrementalEdgeWrites(target, edgesApi, plan) {
3650
3738
  }
3651
3739
  const notFound = [...plannedKindById.keys()].filter((id) => !existingById.has(id)).sort((left, right) => compareStrings(left, right));
3652
3740
  if (notFound.length > 0) {
3653
- const perKind = await Promise.all(
3654
- Object.keys(target.graph.edges).map(
3655
- (kind) => edgeCollection(edgesApi, kind).getByIds(notFound, INCLUDE_TOMBSTONES)
3656
- )
3657
- );
3658
3741
  const crossKindById = /* @__PURE__ */ new Map();
3659
- for (const rows of perKind) {
3742
+ for (const kind of Object.keys(target.graph.edges)) {
3743
+ const rows = await edgeCollection(edgesApi, kind).getByIds(
3744
+ notFound,
3745
+ INCLUDE_TOMBSTONES
3746
+ );
3660
3747
  for (const [index, id] of notFound.entries()) {
3661
3748
  const row = rows[index];
3662
3749
  if (row !== void 0 && !crossKindById.has(id)) {
@@ -3724,20 +3811,77 @@ async function validateIncrementalEdgeWrites(target, edgesApi, plan) {
3724
3811
  }
3725
3812
  }
3726
3813
  }
3727
- async function commitIncrementalPlan(target, plan) {
3814
+ async function collectBaseMatchKeys(lookupStore, guard) {
3815
+ const matched = /* @__PURE__ */ new Set();
3816
+ for (const [kind, stagedNodes] of guard.stagedNewByKind) {
3817
+ const resolveConfig = guard.options.resolve[kind];
3818
+ if (resolveConfig === void 0) {
3819
+ continue;
3820
+ }
3821
+ const collection = lookupStore.nodes[kind];
3822
+ if (collection === void 0 || stagedNodes.length === 0) {
3823
+ continue;
3824
+ }
3825
+ const items = stagedNodes.map((staged) => ({ props: staged.node.props }));
3826
+ for (const constraint of uniqueConstraintsFor(
3827
+ guard.introspectionKinds,
3828
+ kind
3829
+ )) {
3830
+ const matches = await collection.bulkFindByConstraint(
3831
+ constraint.name,
3832
+ items
3833
+ );
3834
+ for (const base of matches) {
3835
+ if (base !== void 0) {
3836
+ matched.add(mergeKeyOf(base));
3837
+ }
3838
+ }
3839
+ }
3840
+ if (resolveConfig.blockIndex !== void 0) {
3841
+ const matchesByItem = await collection.bulkFindByIndex(
3842
+ resolveConfig.blockIndex,
3843
+ items
3844
+ );
3845
+ for (const perItem of matchesByItem) {
3846
+ for (const base of perItem) {
3847
+ matched.add(mergeKeyOf(base));
3848
+ }
3849
+ }
3850
+ }
3851
+ }
3852
+ return matched;
3853
+ }
3854
+ async function assertBaseResolutionStable(lookupStore, guard) {
3855
+ const liveKeys = await collectBaseMatchKeys(lookupStore, guard);
3856
+ const appeared = [...liveKeys].filter((key) => !guard.plannedBaseMatchKeys.has(key)).sort((left, right) => compareMergeKeys(left, right));
3857
+ if (appeared.length === 0) {
3858
+ return;
3859
+ }
3860
+ throw new BaseVersionMismatchError(
3861
+ `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.`,
3862
+ {
3863
+ details: { appeared },
3864
+ suggestion: "Re-run mergeIncremental(); the re-planned merge resolves the branch additions against the now-committed rows."
3865
+ }
3866
+ );
3867
+ }
3868
+ async function commitIncrementalPlan(target, plan, guard) {
3728
3869
  if (!target.backend.capabilities.transactions) {
3729
3870
  throw new MergeError(
3730
3871
  "mergeIncremental() requires a transaction-capable target backend. Incremental writes must preflight existing rows and commit atomically; non-transactional fallback would allow partial graph writes.",
3731
3872
  { details: { capability: "transactions" } }
3732
3873
  );
3733
3874
  }
3734
- return target.transaction(async (tx) => {
3735
- const nodesApi = tx.nodes;
3736
- const edgesApi = tx.edges;
3737
- await validateIncrementalNodeWrites(target, nodesApi, plan);
3738
- await validateIncrementalEdgeWrites(target, edgesApi, plan);
3739
- return applyMergePlan(plan, nodesApi, edgesApi);
3740
- });
3875
+ return withTxConflictRetry(
3876
+ () => target.transaction(async (tx) => {
3877
+ const nodesApi = tx.nodes;
3878
+ const edgesApi = tx.edges;
3879
+ await assertBaseResolutionStable(tx, guard);
3880
+ await validateIncrementalNodeWrites(target, nodesApi, plan);
3881
+ await validateIncrementalEdgeWrites(target, edgesApi, plan);
3882
+ return applyMergePlan(plan, nodesApi, edgesApi);
3883
+ }, MERGE_COMMIT_TX_OPTIONS)
3884
+ );
3741
3885
  }
3742
3886
  async function mergeIncremental(args) {
3743
3887
  const { forkPoint, target, branches } = args;