@colbymchenry/codegraph-darwin-arm64 1.3.0 → 1.3.1

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 (53) hide show
  1. package/lib/dist/db/index.d.ts +17 -5
  2. package/lib/dist/db/index.d.ts.map +1 -1
  3. package/lib/dist/db/index.js +68 -12
  4. package/lib/dist/db/index.js.map +1 -1
  5. package/lib/dist/db/queries.d.ts +25 -1
  6. package/lib/dist/db/queries.d.ts.map +1 -1
  7. package/lib/dist/db/queries.js +39 -0
  8. package/lib/dist/db/queries.js.map +1 -1
  9. package/lib/dist/extraction/index.d.ts.map +1 -1
  10. package/lib/dist/extraction/index.js +84 -31
  11. package/lib/dist/extraction/index.js.map +1 -1
  12. package/lib/dist/index.d.ts.map +1 -1
  13. package/lib/dist/index.js +21 -3
  14. package/lib/dist/index.js.map +1 -1
  15. package/lib/dist/mcp/daemon.d.ts.map +1 -1
  16. package/lib/dist/mcp/daemon.js +23 -1
  17. package/lib/dist/mcp/daemon.js.map +1 -1
  18. package/lib/dist/mcp/proxy.d.ts.map +1 -1
  19. package/lib/dist/mcp/proxy.js +8 -0
  20. package/lib/dist/mcp/proxy.js.map +1 -1
  21. package/lib/dist/mcp/query-pool.d.ts +14 -0
  22. package/lib/dist/mcp/query-pool.d.ts.map +1 -1
  23. package/lib/dist/mcp/query-pool.js +18 -0
  24. package/lib/dist/mcp/query-pool.js.map +1 -1
  25. package/lib/dist/mcp/session.d.ts.map +1 -1
  26. package/lib/dist/mcp/session.js +6 -0
  27. package/lib/dist/mcp/session.js.map +1 -1
  28. package/lib/dist/mcp/tools.d.ts.map +1 -1
  29. package/lib/dist/mcp/tools.js +14 -9
  30. package/lib/dist/mcp/tools.js.map +1 -1
  31. package/lib/dist/mcp/transport.d.ts.map +1 -1
  32. package/lib/dist/mcp/transport.js +18 -0
  33. package/lib/dist/mcp/transport.js.map +1 -1
  34. package/lib/dist/resolution/c-fnptr-synthesizer.d.ts +2 -1
  35. package/lib/dist/resolution/c-fnptr-synthesizer.d.ts.map +1 -1
  36. package/lib/dist/resolution/c-fnptr-synthesizer.js +170 -146
  37. package/lib/dist/resolution/c-fnptr-synthesizer.js.map +1 -1
  38. package/lib/dist/resolution/callback-synthesizer.d.ts.map +1 -1
  39. package/lib/dist/resolution/callback-synthesizer.js +322 -93
  40. package/lib/dist/resolution/callback-synthesizer.js.map +1 -1
  41. package/lib/dist/resolution/goframe-synthesizer.d.ts +2 -1
  42. package/lib/dist/resolution/goframe-synthesizer.d.ts.map +1 -1
  43. package/lib/dist/resolution/goframe-synthesizer.js +8 -3
  44. package/lib/dist/resolution/goframe-synthesizer.js.map +1 -1
  45. package/lib/dist/resolution/index.d.ts +10 -0
  46. package/lib/dist/resolution/index.d.ts.map +1 -1
  47. package/lib/dist/resolution/index.js +55 -15
  48. package/lib/dist/resolution/index.js.map +1 -1
  49. package/lib/dist/resolution/types.d.ts +9 -0
  50. package/lib/dist/resolution/types.d.ts.map +1 -1
  51. package/lib/node_modules/.package-lock.json +1 -1
  52. package/lib/package.json +1 -1
  53. package/package.json +1 -1
@@ -276,10 +276,13 @@ async function closureCollectionEdges(queries, ctx, onYield) {
276
276
  }
277
277
  /** Phase 2: string-keyed EventEmitter channels (on('e', fn) ↔ emit('e')). */
278
278
  async function eventEmitterEdges(ctx, onYield) {
279
+ let scannedFiles = 0;
279
280
  const emitsByEvent = new Map(); // event → dispatcher node ids
280
281
  const handlersByEvent = new Map(); // event → handler id → registration site (file:line)
281
282
  let scanned = 0;
282
283
  for (const file of ctx.getAllFiles()) {
284
+ if ((++scannedFiles & 15) === 0)
285
+ await onYield();
283
286
  if ((++scanned & 255) === 0)
284
287
  await onYield(); // #1091: yield mid-scan on huge graphs
285
288
  const content = ctx.readFile(file);
@@ -353,10 +356,13 @@ async function eventEmitterEdges(ctx, onYield) {
353
356
  * `this.setState`). Over-approximation (all setState methods reach render) is
354
357
  * accepted — it's reachability-correct, like the callback channels.
355
358
  */
356
- function reactRenderEdges(queries, ctx) {
359
+ async function reactRenderEdges(queries, ctx, onYield) {
360
+ let scanned255 = 0;
357
361
  const edges = [];
358
362
  const seen = new Set();
359
- for (const cls of queries.getNodesByKind('class')) {
363
+ for (const cls of queries.iterateNodesByKind('class')) {
364
+ if ((++scanned255 & 63) === 0)
365
+ await onYield();
360
366
  const children = queries.getOutgoingEdges(cls.id, ['contains'])
361
367
  .map((e) => queries.getNodeById(e.target))
362
368
  .filter((n) => !!n && n.kind === 'method');
@@ -396,10 +402,13 @@ function reactRenderEdges(queries, ctx) {
396
402
  * body calls `setState(` → `build`. The setState gate + `.dart` file keep this to
397
403
  * Flutter State classes. Over-approximation accepted (reachability-correct).
398
404
  */
399
- function flutterBuildEdges(queries, ctx) {
405
+ async function flutterBuildEdges(queries, ctx, onYield) {
406
+ let scanned255 = 0;
400
407
  const edges = [];
401
408
  const seen = new Set();
402
- for (const cls of queries.getNodesByKind('class')) {
409
+ for (const cls of queries.iterateNodesByKind('class')) {
410
+ if ((++scanned255 & 63) === 0)
411
+ await onYield();
403
412
  const children = queries.getOutgoingEdges(cls.id, ['contains'])
404
413
  .map((e) => queries.getNodeById(e.target))
405
414
  .filter((n) => !!n && n.kind === 'method');
@@ -458,10 +467,13 @@ const ARKUI_ARRAY_MUTATORS = 'push|pop|shift|unshift|splice|sort|reverse|fill';
458
467
  * with no reactive properties, gets nothing (this is the precision line the
459
468
  * all-sibling-methods design would erase).
460
469
  */
461
- function arkuiStateBuildEdges(queries, ctx) {
470
+ async function arkuiStateBuildEdges(queries, ctx, onYield) {
471
+ let scanned255 = 0;
462
472
  const edges = [];
463
473
  const seen = new Set();
464
- for (const struct of queries.getNodesByKind('struct')) {
474
+ for (const struct of queries.iterateNodesByKind('struct')) {
475
+ if ((++scanned255 & 63) === 0)
476
+ await onYield();
465
477
  if (struct.language !== 'arkts')
466
478
  continue;
467
479
  const children = queries.getOutgoingEdges(struct.id, ['contains'])
@@ -526,7 +538,8 @@ const ARKUI_EMITTER_FANOUT_CAP = 8;
526
538
  * handling — their bodies' calls already attribute to the registering method,
527
539
  * so targeting that method keeps the chain connected.
528
540
  */
529
- function arkuiEmitterEdges(ctx) {
541
+ async function arkuiEmitterEdges(ctx, onYield) {
542
+ let scannedFiles = 0;
530
543
  // bucket key -> emit sites / handler sites
531
544
  const emits = new Map();
532
545
  const handlers = new Map();
@@ -542,6 +555,8 @@ function arkuiEmitterEdges(ctx) {
542
555
  return '';
543
556
  };
544
557
  for (const file of ctx.getAllFiles()) {
558
+ if ((++scannedFiles & 15) === 0)
559
+ await onYield();
545
560
  if (!file.endsWith('.ets'))
546
561
  continue;
547
562
  const content = ctx.readFile(file);
@@ -634,7 +649,7 @@ const ARKUI_ROUTER_RE = /\brouter\s*\.\s*(?:pushUrl|replaceUrl)\s*\(\s*\{[^)]{0,
634
649
  * anything still ambiguous is dropped rather than guessed. Only `@Entry`
635
650
  * structs qualify as targets — the decorator is what makes a file a page.
636
651
  */
637
- function arkuiRouterEdges(ctx) {
652
+ async function arkuiRouterEdges(ctx, onYield) {
638
653
  const edges = [];
639
654
  const seen = new Set();
640
655
  const allFiles = ctx.getAllFiles();
@@ -649,7 +664,10 @@ function arkuiRouterEdges(ctx) {
649
664
  }
650
665
  return '';
651
666
  };
667
+ let scannedFiles = 0;
652
668
  for (const file of allFiles) {
669
+ if ((++scannedFiles & 15) === 0)
670
+ await onYield();
653
671
  if (!file.endsWith('.ets'))
654
672
  continue;
655
673
  const content = ctx.readFile(file);
@@ -704,14 +722,17 @@ function arkuiRouterEdges(ctx) {
704
722
  * implementation(s). Over-approximation accepted (reachability-correct); capped
705
723
  * per class and gated to C++ to avoid touching other languages' dispatch.
706
724
  */
707
- function cppOverrideEdges(queries) {
725
+ async function cppOverrideEdges(queries, onYield) {
726
+ let scanned255 = 0;
708
727
  const edges = [];
709
728
  const seen = new Set();
710
729
  const methodsOf = (classId) => queries
711
730
  .getOutgoingEdges(classId, ['contains'])
712
731
  .map((e) => queries.getNodeById(e.target))
713
732
  .filter((n) => !!n && n.kind === 'method');
714
- for (const cls of queries.getNodesByKind('class')) {
733
+ for (const cls of queries.iterateNodesByKind('class')) {
734
+ if ((++scanned255 & 63) === 0)
735
+ await onYield();
715
736
  const subMethods = methodsOf(cls.id).filter((n) => n.language === 'cpp');
716
737
  if (subMethods.length === 0)
717
738
  continue;
@@ -779,7 +800,8 @@ const IFACE_OVERRIDE_LANGS = new Set([
779
800
  * with the other dispatch synthesizers; capped per interface. Empty interfaces
780
801
  * (`any`) are skipped so they don't match every struct.
781
802
  */
782
- function goImplementsEdges(queries) {
803
+ async function goImplementsEdges(queries, onYield) {
804
+ let scanned255 = 0;
783
805
  const edges = [];
784
806
  const seen = new Set();
785
807
  const methodNameSet = (id) => new Set(queries
@@ -787,11 +809,24 @@ function goImplementsEdges(queries) {
787
809
  .map((e) => queries.getNodeById(e.target))
788
810
  .filter((n) => !!n && n.kind === 'method')
789
811
  .map((n) => n.name));
790
- const goStructs = queries.getNodesByKind('struct').filter((s) => s.language === 'go');
812
+ // Materializes GO structs only (the pass is language-gated by the caller),
813
+ // never the whole struct kind — that array is O(nodes) on struct-heavy
814
+ // repos like the Linux kernel (#1212).
815
+ const goStructs = [];
816
+ for (const s of queries.iterateNodesByKind('struct')) {
817
+ if ((++scanned255 & 63) === 0)
818
+ await onYield();
819
+ if (s.language === 'go')
820
+ goStructs.push(s);
821
+ }
791
822
  const structMethods = new Map();
792
823
  for (const s of goStructs)
793
824
  structMethods.set(s.id, methodNameSet(s.id));
794
- for (const iface of queries.getNodesByKind('interface')) {
825
+ for (const iface of queries.iterateNodesByKind('interface')) {
826
+ if ((++scanned255 & 63) === 0)
827
+ await onYield();
828
+ if ((++scanned255 & 63) === 0)
829
+ await onYield();
795
830
  if (iface.language !== 'go')
796
831
  continue;
797
832
  const want = methodNameSet(iface.id);
@@ -851,7 +886,8 @@ function goImplementsEdges(queries) {
851
886
  * matching the same-file edges extraction already emits). Skips methods that
852
887
  * already have a type parent (the same-file case). (#583, cross-file half)
853
888
  */
854
- function goCrossFileMethodContainsEdges(queries) {
889
+ async function goCrossFileMethodContainsEdges(queries, onYield) {
890
+ let scanned255 = 0;
855
891
  const edges = [];
856
892
  const seen = new Set();
857
893
  const TYPE_KINDS = new Set(['struct', 'class', 'interface', 'enum', 'type_alias']);
@@ -859,7 +895,11 @@ function goCrossFileMethodContainsEdges(queries) {
859
895
  const i = p.replace(/\\/g, '/').lastIndexOf('/');
860
896
  return i >= 0 ? p.slice(0, i) : '';
861
897
  };
862
- for (const method of queries.getNodesByKind('method')) {
898
+ for (const method of queries.iterateNodesByKind('method')) {
899
+ if ((++scanned255 & 63) === 0)
900
+ await onYield();
901
+ if ((++scanned255 & 63) === 0)
902
+ await onYield();
863
903
  if (method.language !== 'go')
864
904
  continue;
865
905
  // The receiver type is encoded in the method's qualifiedName as `Recv::name`
@@ -929,13 +969,20 @@ const KMP_TYPE_KINDS = new Set(['class', 'interface', 'struct', 'enum', 'type_al
929
969
  function kmpKindsCompatible(a, b) {
930
970
  return a === b || (KMP_TYPE_KINDS.has(a) && KMP_TYPE_KINDS.has(b));
931
971
  }
932
- function kotlinExpectActualEdges(queries) {
972
+ async function kotlinExpectActualEdges(queries, onYield) {
973
+ let scanned255 = 0;
933
974
  const edges = [];
934
975
  const seen = new Set();
935
- const actuals = queries
936
- .getAllNodes()
937
- .filter((n) => n.language === 'kotlin' && !!n.decorators?.includes('actual'));
938
- for (const act of actuals) {
976
+ // SQL-side language+decorator pre-filter, streamed. The old
977
+ // `getAllNodes().filter(...)` hydrated the ENTIRE node table into one array
978
+ // just to find kotlin `actual` declarations — on a 2M-node graph that alone
979
+ // exceeded Node's default heap and killed the index (#1212). The LIKE
980
+ // pre-filter can over-match (substring), so the exact decorator check stays.
981
+ for (const act of queries.iterateNodesByLanguageWithDecorator('kotlin', 'actual')) {
982
+ if ((++scanned255 & 63) === 0)
983
+ await onYield();
984
+ if (!act.decorators?.includes('actual'))
985
+ continue;
939
986
  let added = 0;
940
987
  for (const cand of queries.getNodesByQualifiedNameExact(act.qualifiedName)) {
941
988
  if (added >= MAX_CALLBACKS_PER_CHANNEL)
@@ -969,7 +1016,8 @@ function kotlinExpectActualEdges(queries) {
969
1016
  }
970
1017
  return edges;
971
1018
  }
972
- function interfaceOverrideEdges(queries) {
1019
+ async function interfaceOverrideEdges(queries, onYield) {
1020
+ let scanned255 = 0;
973
1021
  const edges = [];
974
1022
  const seen = new Set();
975
1023
  const methodsOf = (classId) => queries
@@ -981,7 +1029,9 @@ function interfaceOverrideEdges(queries) {
981
1029
  // types that conform to protocols. Iterate both.
982
1030
  const concreteKinds = ['class', 'struct'];
983
1031
  for (const kind of concreteKinds) {
984
- for (const cls of queries.getNodesByKind(kind)) {
1032
+ for (const cls of queries.iterateNodesByKind(kind)) {
1033
+ if ((++scanned255 & 63) === 0)
1034
+ await onYield();
985
1035
  const implMethods = methodsOf(cls.id).filter((n) => IFACE_OVERRIDE_LANGS.has(n.language));
986
1036
  if (implMethods.length === 0)
987
1037
  continue;
@@ -1055,7 +1105,8 @@ function interfaceOverrideEdges(queries) {
1055
1105
  * Provenance: `heuristic`, `synthesizedBy: 'go-grpc-stub-impl'`. The
1056
1106
  * stub's source line is the wiring site shown in the trace trail.
1057
1107
  */
1058
- function goGrpcStubImplEdges(queries) {
1108
+ async function goGrpcStubImplEdges(queries, onYield) {
1109
+ let scanned255 = 0;
1059
1110
  const edges = [];
1060
1111
  const seen = new Set();
1061
1112
  const STUB_RE = /^Unimplemented.*Server$/;
@@ -1067,7 +1118,9 @@ function goGrpcStubImplEdges(queries) {
1067
1118
  const methodNamesByStruct = new Map();
1068
1119
  const methodNodesByStruct = new Map();
1069
1120
  const goStructs = [];
1070
- for (const s of queries.getNodesByKind('struct')) {
1121
+ for (const s of queries.iterateNodesByKind('struct')) {
1122
+ if ((++scanned255 & 63) === 0)
1123
+ await onYield();
1071
1124
  if (s.language !== 'go')
1072
1125
  continue;
1073
1126
  goStructs.push(s);
@@ -1151,11 +1204,14 @@ function goGrpcStubImplEdges(queries) {
1151
1204
  * (or nothing) and are dropped.
1152
1205
  */
1153
1206
  async function reactJsxChildEdges(ctx, onYield) {
1207
+ let scannedFiles = 0;
1154
1208
  const edges = [];
1155
1209
  const seen = new Set();
1156
1210
  const PARENT_KINDS = new Set(['method', 'function', 'component']);
1157
1211
  let scanned = 0;
1158
1212
  for (const file of ctx.getAllFiles()) {
1213
+ if ((++scannedFiles & 15) === 0)
1214
+ await onYield();
1159
1215
  if ((++scanned & 255) === 0)
1160
1216
  await onYield(); // #1091: yield mid-scan on huge graphs
1161
1217
  const content = ctx.readFile(file);
@@ -1205,7 +1261,9 @@ async function reactJsxChildEdges(ctx, onYield) {
1205
1261
  * component, handler→function/method) keeps precision; inline arrows / `$emit`
1206
1262
  * skipped.
1207
1263
  */
1208
- function vueTemplateEdges(ctx) {
1264
+ async function vueTemplateEdges(ctx, onYield) {
1265
+ let scannedFiles = 0;
1266
+ let scanned255 = 0;
1209
1267
  const edges = [];
1210
1268
  const seen = new Set();
1211
1269
  const COMPONENT_KINDS = new Set(['component', 'function', 'class']);
@@ -1219,12 +1277,16 @@ function vueTemplateEdges(ctx) {
1219
1277
  // misses it (flat components match by basename and don't need this). Map each
1220
1278
  // nested component's Nuxt name → node so those template usages resolve.
1221
1279
  const nuxtComponents = new Map();
1222
- for (const c of ctx.getNodesByKind('component')) {
1280
+ for (const c of (ctx.iterateNodesByKind?.('component') ?? ctx.getNodesByKind('component'))) {
1281
+ if ((++scanned255 & 63) === 0)
1282
+ await onYield();
1223
1283
  const nn = nuxtComponentName(c.filePath);
1224
1284
  if (nn && !nuxtComponents.has(nn))
1225
1285
  nuxtComponents.set(nn, c);
1226
1286
  }
1227
1287
  for (const file of ctx.getAllFiles()) {
1288
+ if ((++scannedFiles & 15) === 0)
1289
+ await onYield();
1228
1290
  if (!file.endsWith('.vue'))
1229
1291
  continue;
1230
1292
  const content = ctx.readFile(file);
@@ -1358,13 +1420,16 @@ const RN_JVM_EMIT_RE = /\.emit\s*\(\s*"([^"]+)"\s*,/g;
1358
1420
  // statement and stops at a block boundary, so the wrapper DEFINITION (whose `(`
1359
1421
  // is followed by `… ) {`) never matches. Multi-line tolerant. (java/kotlin/swift)
1360
1422
  const RN_NATIVE_SENDEVENT_RE = /\bsendEvent\s*\([^;{}]*?"([^"]+)"/g;
1361
- function rnEventEdges(ctx) {
1423
+ async function rnEventEdges(ctx, onYield) {
1424
+ let scannedFiles = 0;
1362
1425
  // Native dispatchers (source = the native method whose body sends the
1363
1426
  // event) and JS handlers (target = the function/method registered as
1364
1427
  // the listener) keyed by event name.
1365
1428
  const nativeDispatchersByEvent = new Map();
1366
1429
  const jsHandlersByEvent = new Map();
1367
1430
  for (const file of ctx.getAllFiles()) {
1431
+ if ((++scannedFiles & 15) === 0)
1432
+ await onYield();
1368
1433
  const content = ctx.readFile(file);
1369
1434
  if (!content)
1370
1435
  continue;
@@ -1552,11 +1617,14 @@ const FABRIC_NATIVE_SUFFIXES = ['', 'View', 'ViewManager', 'ComponentView', 'Man
1552
1617
  * caller. The Expo method nodes are id-prefixed `expo-module:` and qualified
1553
1618
  * `<file>::<module>.<method>` by the framework extractor.
1554
1619
  */
1555
- function expoCrossPlatformEdges(queries) {
1620
+ async function expoCrossPlatformEdges(queries, onYield) {
1621
+ let scanned255 = 0;
1556
1622
  const edges = [];
1557
1623
  const seen = new Set();
1558
1624
  const byKey = new Map();
1559
- for (const m of queries.getNodesByKind('method')) {
1625
+ for (const m of queries.iterateNodesByKind('method')) {
1626
+ if ((++scanned255 & 63) === 0)
1627
+ await onYield();
1560
1628
  if (!m.id.startsWith('expo-module:'))
1561
1629
  continue;
1562
1630
  const key = m.qualifiedName.split('::').pop(); // `<module>.<method>`
@@ -1605,7 +1673,8 @@ function expoCrossPlatformEdges(queries) {
1605
1673
  * `getFreeDiskStorage`) — that's the JS-visible name, and how the iOS selector
1606
1674
  * lines up with the bare Android method name.
1607
1675
  */
1608
- function rnCrossPlatformEdges(queries) {
1676
+ async function rnCrossPlatformEdges(queries, onYield) {
1677
+ let scanned255 = 0;
1609
1678
  const edges = [];
1610
1679
  const seen = new Set();
1611
1680
  const NATIVE = new Set(['java', 'kotlin', 'objc', 'cpp']);
@@ -1627,6 +1696,8 @@ function rnCrossPlatformEdges(queries) {
1627
1696
  // below only runs for genuine cross-platform candidates.
1628
1697
  const byName = new Map();
1629
1698
  for (const m of queries.iterateNodesByKind('method')) {
1699
+ if ((++scanned255 & 63) === 0)
1700
+ await onYield();
1630
1701
  if (!NATIVE.has(m.language))
1631
1702
  continue;
1632
1703
  const key = norm(m.name);
@@ -1677,17 +1748,27 @@ function rnCrossPlatformEdges(queries) {
1677
1748
  }
1678
1749
  return edges;
1679
1750
  }
1680
- function fabricNativeImplEdges(ctx) {
1751
+ async function fabricNativeImplEdges(ctx, onYield) {
1752
+ let scanned255 = 0;
1681
1753
  const edges = [];
1682
1754
  const seen = new Set();
1683
1755
  // The Fabric extractor IDs are prefixed `fabric-component:` so we can
1684
- // filter to just those without iterating all `component` nodes.
1685
- const components = ctx.getNodesByKind('component').filter((n) => n.id.startsWith('fabric-component:'));
1756
+ // filter to just those while streaming never materializing the whole
1757
+ // `component` kind (#1212).
1758
+ const components = [];
1759
+ for (const n of (ctx.iterateNodesByKind?.('component') ?? ctx.getNodesByKind('component'))) {
1760
+ if ((++scanned255 & 63) === 0)
1761
+ await onYield();
1762
+ if (n.id.startsWith('fabric-component:'))
1763
+ components.push(n);
1764
+ }
1686
1765
  if (components.length === 0)
1687
1766
  return edges;
1688
1767
  // Pre-index native classes by name for O(1) lookup.
1689
1768
  const nativeClassesByName = new Map();
1690
- for (const n of ctx.getNodesByKind('class')) {
1769
+ for (const n of (ctx.iterateNodesByKind?.('class') ?? ctx.getNodesByKind('class'))) {
1770
+ if ((++scanned255 & 63) === 0)
1771
+ await onYield();
1691
1772
  if (n.language !== 'objc' && n.language !== 'kotlin' && n.language !== 'java' && n.language !== 'cpp')
1692
1773
  continue;
1693
1774
  const arr = nativeClassesByName.get(n.name);
@@ -1740,12 +1821,15 @@ function fabricNativeImplEdges(ctx) {
1740
1821
  * same simple name) are dropped. We need-not bridge by package because Java
1741
1822
  * mapper interfaces are typically uniquely named within a project.
1742
1823
  */
1743
- function mybatisJavaXmlEdges(queries) {
1824
+ async function mybatisJavaXmlEdges(queries, onYield) {
1825
+ let scanned255 = 0;
1744
1826
  const edges = [];
1745
1827
  const seen = new Set();
1746
1828
  // Index Java methods by `<ClassName>::<methodName>` for O(1) lookup.
1747
1829
  const javaIndex = new Map();
1748
1830
  for (const m of queries.iterateNodesByKind('method')) {
1831
+ if ((++scanned255 & 63) === 0)
1832
+ await onYield();
1749
1833
  if (m.language !== 'java' && m.language !== 'kotlin')
1750
1834
  continue;
1751
1835
  const parts = m.qualifiedName.split('::');
@@ -1761,6 +1845,8 @@ function mybatisJavaXmlEdges(queries) {
1761
1845
  javaIndex.set(key, [m]);
1762
1846
  }
1763
1847
  for (const xml of queries.iterateNodesByKind('method')) {
1848
+ if ((++scanned255 & 63) === 0)
1849
+ await onYield();
1764
1850
  if (xml.language !== 'xml')
1765
1851
  continue;
1766
1852
  // Qualified name: `<namespace>::<id>`. Extract the simple class name.
@@ -1874,10 +1960,14 @@ function goHandlerIdent(expr) {
1874
1960
  const m = cleaned.match(/(?:\.|^)([A-Za-z_]\w*)$/);
1875
1961
  return m ? m[1] : null;
1876
1962
  }
1877
- function ginMiddlewareChainEdges(queries, ctx) {
1963
+ async function ginMiddlewareChainEdges(queries, ctx, onYield) {
1964
+ let scanned255 = 0;
1965
+ let scannedFiles = 0;
1878
1966
  // 1. Find the chain dispatcher(s): a Go method that invokes a `handlers` slice by index.
1879
1967
  const dispatchers = [];
1880
1968
  for (const n of queries.iterateNodesByKind('method')) {
1969
+ if ((++scanned255 & 63) === 0)
1970
+ await onYield();
1881
1971
  if (n.language !== 'go')
1882
1972
  continue;
1883
1973
  const content = ctx.readFile(n.filePath);
@@ -1892,6 +1982,8 @@ function ginMiddlewareChainEdges(queries, ctx) {
1892
1982
  // closures are dropped by goHandlerIdent; the rest are HandlerFuncs.
1893
1983
  const registered = new Map(); // name → registeredAt (file:line)
1894
1984
  for (const file of ctx.getAllFiles()) {
1985
+ if ((++scannedFiles & 15) === 0)
1986
+ await onYield();
1895
1987
  if (!file.endsWith('.go'))
1896
1988
  continue;
1897
1989
  const content = ctx.readFile(file);
@@ -1947,10 +2039,13 @@ function ginMiddlewareChainEdges(queries, ctx) {
1947
2039
  * clause. Link the unit → its form so a `.dfm`/`.fmx` used only as a form
1948
2040
  * definition isn't orphaned, and editing the form surfaces its code-behind unit.
1949
2041
  */
1950
- function pascalFormEdges(ctx) {
2042
+ async function pascalFormEdges(ctx, onYield) {
2043
+ let scannedFiles = 0;
1951
2044
  const edges = [];
1952
2045
  const allFiles = new Set(ctx.getAllFiles());
1953
2046
  for (const file of allFiles) {
2047
+ if ((++scannedFiles & 255) === 0)
2048
+ await onYield();
1954
2049
  if (!/\.(dfm|fmx)$/i.test(file))
1955
2050
  continue;
1956
2051
  const pasFile = file.replace(/\.(dfm|fmx)$/i, '.pas');
@@ -1986,12 +2081,15 @@ function pascalFormEdges(ctx) {
1986
2081
  * a loader's data shows the page it feeds) and the page's dependencies include
1987
2082
  * its loader.
1988
2083
  */
1989
- function svelteKitLoadEdges(ctx) {
2084
+ async function svelteKitLoadEdges(ctx, onYield) {
2085
+ let scannedFiles = 0;
1990
2086
  const edges = [];
1991
2087
  const allFiles = new Set(ctx.getAllFiles());
1992
2088
  const HOOKS = new Set(['load', 'actions']);
1993
2089
  const HOOK_KINDS = new Set(['function', 'method', 'constant', 'variable']);
1994
2090
  for (const file of allFiles) {
2091
+ if ((++scannedFiles & 255) === 0)
2092
+ await onYield();
1995
2093
  const m = file.match(/(.*\/)(\+(?:page|layout))\.svelte$/);
1996
2094
  if (!m)
1997
2095
  continue;
@@ -2040,10 +2138,13 @@ function svelteKitLoadEdges(ctx) {
2040
2138
  const THUNK_DECL_RE = /create(?:Async)?Thunk/;
2041
2139
  const THUNK_DISPATCH_RE = /\bdispatch\s*\(\s*([A-Za-z_]\w*)\s*[(),]/g;
2042
2140
  const THUNK_FANOUT_CAP = 24;
2043
- function reduxThunkEdges(queries, ctx) {
2141
+ async function reduxThunkEdges(queries, ctx, onYield) {
2142
+ let scanned255 = 0;
2044
2143
  const edges = [];
2045
2144
  const seen = new Set();
2046
2145
  for (const node of queries.iterateNodesByKind('constant')) {
2146
+ if ((++scanned255 & 63) === 0)
2147
+ await onYield();
2047
2148
  // Cheap gate: the initializer (captured in `signature`) must be a create(Async)Thunk call —
2048
2149
  // avoids reading every constant's body on a large repo.
2049
2150
  if (!node.signature || !THUNK_DECL_RE.test(node.signature))
@@ -2175,10 +2276,13 @@ function resolveRegistryHandler(ctx, name, chained) {
2175
2276
  return cands.find((n) => n.kind === 'method') ?? null;
2176
2277
  }
2177
2278
  async function objectRegistryEdges(ctx, onYield) {
2279
+ let scannedFiles = 0;
2178
2280
  const edges = [];
2179
2281
  const seen = new Set();
2180
2282
  let scanned = 0;
2181
2283
  for (const file of ctx.getAllFiles()) {
2284
+ if ((++scannedFiles & 15) === 0)
2285
+ await onYield();
2182
2286
  if ((++scanned & 255) === 0)
2183
2287
  await onYield(); // #1091: yield mid-scan on huge graphs
2184
2288
  if (!REGISTRY_JS_EXT.test(file))
@@ -2290,10 +2394,13 @@ function rtkEndpointNameFromHook(hook) {
2290
2394
  return null;
2291
2395
  return mid.charAt(0).toLowerCase() + mid.slice(1);
2292
2396
  }
2293
- function rtkQueryEdges(queries, ctx) {
2397
+ async function rtkQueryEdges(queries, ctx, onYield) {
2398
+ let scanned255 = 0;
2294
2399
  const edges = [];
2295
2400
  const seen = new Set();
2296
2401
  for (const hook of queries.iterateNodesByKind('function')) {
2402
+ if ((++scanned255 & 63) === 0)
2403
+ await onYield();
2297
2404
  // Only our extracted generated-hook bindings (sentinel) — not a real hook fn.
2298
2405
  if (hook.signature !== RTK_GENERATED_HOOK_SIGNATURE)
2299
2406
  continue;
@@ -2337,10 +2444,13 @@ const PINIA_FACTORY_RE = /\b(?:export\s+)?const\s+(\w+)\s*=\s*defineStore\s*\(/g
2337
2444
  const PINIA_BIND_RE = /\bconst\s+(\w+)\s*=\s*(?:await\s+)?(\w+)\s*\(/g;
2338
2445
  const PINIA_CALL_RE = /(\w+)\s*\.\s*(\w+)\s*\(/g;
2339
2446
  const PINIA_FANOUT_CAP = 80;
2340
- function piniaStoreEdges(ctx) {
2447
+ async function piniaStoreEdges(ctx, onYield) {
2448
+ let scannedFiles = 0;
2341
2449
  // 1. Map each `const useXStore = defineStore(...)` factory → its store file.
2342
2450
  const factoryFile = new Map();
2343
2451
  for (const file of ctx.getAllFiles()) {
2452
+ if ((++scannedFiles & 15) === 0)
2453
+ await onYield();
2344
2454
  if (!PINIA_CONSUMER_EXT.test(file))
2345
2455
  continue;
2346
2456
  const content = ctx.readFile(file);
@@ -2356,6 +2466,8 @@ function piniaStoreEdges(ctx) {
2356
2466
  const edges = [];
2357
2467
  const seen = new Set();
2358
2468
  for (const file of ctx.getAllFiles()) {
2469
+ if ((++scannedFiles & 15) === 0)
2470
+ await onYield();
2359
2471
  if (!PINIA_CONSUMER_EXT.test(file))
2360
2472
  continue;
2361
2473
  const content = ctx.readFile(file);
@@ -2428,7 +2540,8 @@ const VUEX_FANOUT_CAP = 120;
2428
2540
  function pathHasSegment(filePath, seg) {
2429
2541
  return new RegExp('[\\\\/]' + seg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '[\\\\/.]').test(filePath);
2430
2542
  }
2431
- function vuexDispatchEdges(ctx) {
2543
+ async function vuexDispatchEdges(ctx, onYield) {
2544
+ let scannedFiles = 0;
2432
2545
  const storeFileCache = new Map();
2433
2546
  const isStoreFile = (file) => {
2434
2547
  let v = storeFileCache.get(file);
@@ -2466,6 +2579,8 @@ function vuexDispatchEdges(ctx) {
2466
2579
  const edges = [];
2467
2580
  const seen = new Set();
2468
2581
  for (const file of ctx.getAllFiles()) {
2582
+ if ((++scannedFiles & 15) === 0)
2583
+ await onYield();
2469
2584
  if (!PINIA_CONSUMER_EXT.test(file))
2470
2585
  continue;
2471
2586
  const content = ctx.readFile(file);
@@ -2525,7 +2640,8 @@ const CELERY_TASK_DECORATOR_RE = /@\s*(?:[A-Za-z_][\w.]*\.)?(?:shared_task|task)
2525
2640
  const CELERY_PY_EXT = /\.py$/;
2526
2641
  const CELERY_FANOUT_CAP = 80;
2527
2642
  const CELERY_DECORATOR_LOOKBACK = 12; // max lines above a `def` to scan for its decorators
2528
- function celeryDispatchEdges(ctx) {
2643
+ async function celeryDispatchEdges(ctx, onYield) {
2644
+ let scannedFiles = 0;
2529
2645
  // Memoize the decorator check per task-candidate node: it reads the file and scans a few
2530
2646
  // lines above the def. Only called on names that are actually `.delay`/`.apply_async`
2531
2647
  // receivers, so the candidate set stays small.
@@ -2569,6 +2685,8 @@ function celeryDispatchEdges(ctx) {
2569
2685
  const edges = [];
2570
2686
  const seen = new Set();
2571
2687
  for (const file of ctx.getAllFiles()) {
2688
+ if ((++scannedFiles & 15) === 0)
2689
+ await onYield();
2572
2690
  if (!CELERY_PY_EXT.test(file))
2573
2691
  continue;
2574
2692
  const content = ctx.readFile(file);
@@ -2647,15 +2765,24 @@ function springFirstParamType(sig) {
2647
2765
  const type = toks[toks.length - 2].replace(/<.*$/, ''); // drop generic args
2648
2766
  return /^[A-Z][A-Za-z0-9_]*$/.test(type) ? type : null;
2649
2767
  }
2650
- function springEventEdges(ctx) {
2768
+ async function springEventEdges(ctx, onYield) {
2769
+ let scannedFiles = 0;
2651
2770
  // Pass 1 — event-type → listener methods, scanning only event-relevant files.
2771
+ // This is the ONLY full read sweep: publisher files are recorded here so
2772
+ // pass 2 re-reads just those instead of every .java file again (#1212 —
2773
+ // the double full-repo read was one of the tail's longest unyielded spans).
2652
2774
  const listeners = new Map();
2775
+ const publisherFiles = [];
2653
2776
  for (const file of ctx.getAllFiles()) {
2777
+ if ((++scannedFiles & 15) === 0)
2778
+ await onYield();
2654
2779
  if (!SPRING_JAVA_EXT.test(file))
2655
2780
  continue;
2656
2781
  const content = ctx.readFile(file);
2657
2782
  if (!content)
2658
2783
  continue;
2784
+ if (content.includes('.publishEvent('))
2785
+ publisherFiles.push(file);
2659
2786
  const hasAnno = content.includes('@EventListener') || content.includes('@TransactionalEventListener');
2660
2787
  const hasAppListener = SPRING_APP_LISTENER_RE.test(content);
2661
2788
  if (!hasAnno && !hasAppListener)
@@ -2695,12 +2822,13 @@ function springEventEdges(ctx) {
2695
2822
  }
2696
2823
  if (!listeners.size)
2697
2824
  return [];
2698
- // Pass 2 — link each publishEvent(new XEvent(...)) site → every listener of XEvent.
2825
+ // Pass 2 — link each publishEvent(new XEvent(...)) site → every listener of
2826
+ // XEvent. Only the publisher files recorded in pass 1 are (re-)read.
2699
2827
  const edges = [];
2700
2828
  const seen = new Set();
2701
- for (const file of ctx.getAllFiles()) {
2702
- if (!SPRING_JAVA_EXT.test(file))
2703
- continue;
2829
+ for (const file of publisherFiles) {
2830
+ if ((++scannedFiles & 15) === 0)
2831
+ await onYield();
2704
2832
  const content = ctx.readFile(file);
2705
2833
  if (!content || !content.includes('.publishEvent('))
2706
2834
  continue;
@@ -2785,10 +2913,13 @@ function resolveMediatrArgType(arg, lines, methodStart, dispatchLine) {
2785
2913
  }
2786
2914
  return declType;
2787
2915
  }
2788
- function mediatrDispatchEdges(ctx) {
2916
+ async function mediatrDispatchEdges(ctx, onYield) {
2917
+ let scannedFiles = 0;
2789
2918
  // Pass 1 — request/notification type → the Handle method of each handler class.
2790
2919
  const handlers = new Map();
2791
2920
  for (const file of ctx.getAllFiles()) {
2921
+ if ((++scannedFiles & 15) === 0)
2922
+ await onYield();
2792
2923
  if (!MEDIATR_CS_EXT.test(file))
2793
2924
  continue;
2794
2925
  const content = ctx.readFile(file);
@@ -2822,6 +2953,8 @@ function mediatrDispatchEdges(ctx) {
2822
2953
  const edges = [];
2823
2954
  const seen = new Set();
2824
2955
  for (const file of ctx.getAllFiles()) {
2956
+ if ((++scannedFiles & 15) === 0)
2957
+ await onYield();
2825
2958
  if (!MEDIATR_CS_EXT.test(file))
2826
2959
  continue;
2827
2960
  const content = ctx.readFile(file);
@@ -2885,7 +3018,8 @@ const SIDEKIQ_DISPATCH_RE = /([A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*)\s*\.\
2885
3018
  const SIDEKIQ_WORKER_RE = /\binclude\s+Sidekiq::(?:Job|Worker)\b/;
2886
3019
  const SIDEKIQ_RB_EXT = /\.rb$/;
2887
3020
  const SIDEKIQ_FANOUT_CAP = 80;
2888
- function sidekiqDispatchEdges(ctx) {
3021
+ async function sidekiqDispatchEdges(ctx, onYield) {
3022
+ let scannedFiles = 0;
2889
3023
  // class node id → its instance `perform` method (null if the class isn't a Sidekiq worker),
2890
3024
  // memoized. Reads the class body for the mixin; only consulted for actual dispatch receivers.
2891
3025
  const performCache = new Map();
@@ -2922,6 +3056,8 @@ function sidekiqDispatchEdges(ctx) {
2922
3056
  const edges = [];
2923
3057
  const seen = new Set();
2924
3058
  for (const file of ctx.getAllFiles()) {
3059
+ if ((++scannedFiles & 15) === 0)
3060
+ await onYield();
2925
3061
  if (!SIDEKIQ_RB_EXT.test(file))
2926
3062
  continue;
2927
3063
  const content = ctx.readFile(file);
@@ -3105,12 +3241,15 @@ function nixLeadingPlainSegments(name) {
3105
3241
  return segs;
3106
3242
  }
3107
3243
  async function nixOptionPathEdges(queries, onYield) {
3244
+ let scanned255 = 0;
3108
3245
  // One streaming pass over nix bindings (variables + the odd function-valued
3109
3246
  // option); memory stays O(bindings-kept), not O(all nodes) (#610).
3110
3247
  const byFile = new Map();
3111
3248
  let scanned = 0;
3112
3249
  for (const kind of ['variable', 'function']) {
3113
3250
  for (const node of queries.iterateNodesByKind(kind)) {
3251
+ if ((++scanned255 & 63) === 0)
3252
+ await onYield();
3114
3253
  if ((++scanned & 0x3fff) === 0 && onYield)
3115
3254
  await onYield();
3116
3255
  if (node.language !== 'nix')
@@ -3223,9 +3362,18 @@ async function nixOptionPathEdges(queries, onYield) {
3223
3362
  }
3224
3363
  return edges;
3225
3364
  }
3226
- function erlangBehaviourDispatchEdges(queries, ctx) {
3227
- // Cheap language gate: no Erlang modules → no cost beyond one kind query.
3228
- const erlangModules = queries.getNodesByKind('namespace').filter((n) => n.language === 'erlang');
3365
+ async function erlangBehaviourDispatchEdges(queries, ctx, onYield) {
3366
+ let scannedFiles = 0;
3367
+ let scanned255 = 0;
3368
+ // Cheap language gate: no Erlang modules → no cost beyond one streamed
3369
+ // kind scan (never a materialized array of every namespace — #1212).
3370
+ const erlangModules = [];
3371
+ for (const n of queries.iterateNodesByKind('namespace')) {
3372
+ if ((++scanned255 & 63) === 0)
3373
+ await onYield();
3374
+ if (n.language === 'erlang')
3375
+ erlangModules.push(n);
3376
+ }
3229
3377
  if (erlangModules.length === 0)
3230
3378
  return [];
3231
3379
  // Pass 1 — scan every Erlang file with `-callback` decls: behaviour module →
@@ -3239,6 +3387,8 @@ function erlangBehaviourDispatchEdges(queries, ctx) {
3239
3387
  const declaringBehaviours = new Map(); // `fn/arity` → behaviour namespaces
3240
3388
  const callbackNames = new Set();
3241
3389
  for (const file of ctx.getAllFiles()) {
3390
+ if ((++scannedFiles & 15) === 0)
3391
+ await onYield();
3242
3392
  if (!ERLANG_EXT.test(file))
3243
3393
  continue;
3244
3394
  const behaviour = moduleByFile.get(file);
@@ -3297,6 +3447,8 @@ function erlangBehaviourDispatchEdges(queries, ctx) {
3297
3447
  const edges = [];
3298
3448
  const seen = new Set();
3299
3449
  for (const file of ctx.getAllFiles()) {
3450
+ if ((++scannedFiles & 15) === 0)
3451
+ await onYield();
3300
3452
  if (!ERLANG_EXT.test(file))
3301
3453
  continue;
3302
3454
  const content = ctx.readFile(file);
@@ -3395,7 +3547,8 @@ function phpArrayBody(src, openIdx) {
3395
3547
  }
3396
3548
  return null;
3397
3549
  }
3398
- function laravelEventEdges(ctx) {
3550
+ async function laravelEventEdges(ctx, onYield) {
3551
+ let scannedFiles = 0;
3399
3552
  // event short name → its listener `handle` methods (deduped by node id).
3400
3553
  const listeners = new Map();
3401
3554
  const add = (event, handle) => {
@@ -3412,6 +3565,8 @@ function laravelEventEdges(ctx) {
3412
3565
  && n.startLine >= cls.startLine && n.startLine <= (cls.endLine ?? cls.startLine)) ?? null;
3413
3566
  // Pass 1 — build the event→handle map from both registration mechanisms.
3414
3567
  for (const file of ctx.getAllFiles()) {
3568
+ if ((++scannedFiles & 15) === 0)
3569
+ await onYield();
3415
3570
  if (!LARAVEL_PHP_EXT.test(file))
3416
3571
  continue;
3417
3572
  const content = ctx.readFile(file);
@@ -3457,6 +3612,8 @@ function laravelEventEdges(ctx) {
3457
3612
  const edges = [];
3458
3613
  const seen = new Set();
3459
3614
  for (const file of ctx.getAllFiles()) {
3615
+ if ((++scannedFiles & 15) === 0)
3616
+ await onYield();
3460
3617
  if (!LARAVEL_PHP_EXT.test(file))
3461
3618
  continue;
3462
3619
  const content = ctx.readFile(file);
@@ -3516,95 +3673,160 @@ async function synthesizeCallbackEdges(queries, ctx) {
3516
3673
  // that itself hangs (a real wedge) never reaches the next yield, so the
3517
3674
  // watchdog still catches that. See ./cooperative-yield.
3518
3675
  const yieldToLoop = (0, cooperative_yield_1.createYielder)();
3676
+ // Per-pass wall-clock timing to stderr, opt-in via CODEGRAPH_SYNTH_TIMINGS
3677
+ // (=1: passes over 250ms; =all: every pass). This is the diagnostic that
3678
+ // located both the #1091/#1122 watchdog stalls and the #1212 OOM — keep it.
3679
+ const markT = { t: Date.now() };
3680
+ const __mark = (label) => {
3681
+ const now = Date.now();
3682
+ const dt = now - markT.t;
3683
+ markT.t = now;
3684
+ if (process.env.CODEGRAPH_SYNTH_TIMINGS && (dt > 250 || process.env.CODEGRAPH_SYNTH_TIMINGS === 'all')) {
3685
+ console.error(`[synth-timing] ${label}: ${dt}ms`);
3686
+ }
3687
+ };
3688
+ // Language gating: one indexed DISTINCT over the files table lets a pass
3689
+ // whose own filters reference a specific language/extension be skipped
3690
+ // outright when the project has no such files — its result is provably
3691
+ // empty, so skipping is behavior-identical and the cost drops to zero
3692
+ // (the Kotlin pass was the OOM culprit on the pure-C Linux kernel, #1212).
3693
+ // Passes without an explicit language filter always run.
3694
+ const langs = queries.getDistinctFileLanguages();
3695
+ const has = (...ls) => ls.some((l) => langs.has(l));
3696
+ const JS_FAMILY = ['typescript', 'javascript', 'tsx', 'jsx'];
3697
+ const NONE = [];
3519
3698
  // Cross-file Go method→type `contains` edges must be synthesized AND persisted
3520
3699
  // FIRST: a method declared in a different file from its receiver type is
3521
3700
  // otherwise orphaned from the struct, and goImplementsEdges (next) derives a
3522
3701
  // struct's method set from its `contains` edges — so without this it would
3523
3702
  // under-count the interfaces a cross-file struct satisfies. (#583)
3524
- const goMethodContains = goCrossFileMethodContainsEdges(queries);
3525
- if (goMethodContains.length > 0)
3526
- queries.insertEdges(goMethodContains);
3703
+ const goMethodContains = has('go') ? await goCrossFileMethodContainsEdges(queries, yieldToLoop) : NONE;
3704
+ for (let i = 0; i < goMethodContains.length; i += 2000) {
3705
+ queries.insertEdges(goMethodContains.slice(i, i + 2000));
3706
+ await yieldToLoop();
3707
+ }
3527
3708
  await yieldToLoop();
3709
+ __mark('goMethodContains');
3528
3710
  // Go implicit `implements` edges must be synthesized AND persisted next: the
3529
3711
  // interface-dispatch bridge below reads `implements` edges from the DB, and
3530
3712
  // Go has none statically. (Other languages already have static implements
3531
3713
  // edges from extraction, so they don't need this pre-pass.)
3532
- const goImpl = goImplementsEdges(queries);
3533
- if (goImpl.length > 0)
3534
- queries.insertEdges(goImpl);
3714
+ const goImpl = has('go') ? await goImplementsEdges(queries, yieldToLoop) : NONE;
3715
+ for (let i = 0; i < goImpl.length; i += 2000) {
3716
+ queries.insertEdges(goImpl.slice(i, i + 2000));
3717
+ await yieldToLoop();
3718
+ }
3535
3719
  await yieldToLoop();
3720
+ __mark('goImplements');
3536
3721
  const fieldEdges = await fieldChannelEdges(queries, ctx, yieldToLoop);
3537
3722
  await yieldToLoop();
3723
+ __mark('fieldEdges');
3538
3724
  const closureCollEdges = await closureCollectionEdges(queries, ctx, yieldToLoop);
3539
3725
  await yieldToLoop();
3726
+ __mark('closureCollEdges');
3540
3727
  const emitterEdges = await eventEmitterEdges(ctx, yieldToLoop);
3541
3728
  await yieldToLoop();
3542
- const renderEdges = reactRenderEdges(queries, ctx);
3729
+ __mark('emitterEdges');
3730
+ const renderEdges = await reactRenderEdges(queries, ctx, yieldToLoop);
3543
3731
  await yieldToLoop();
3732
+ __mark('renderEdges');
3544
3733
  const jsxEdges = await reactJsxChildEdges(ctx, yieldToLoop);
3545
3734
  await yieldToLoop();
3546
- const vueEdges = vueTemplateEdges(ctx);
3735
+ __mark('jsxEdges');
3736
+ const vueEdges = has('vue') ? await vueTemplateEdges(ctx, yieldToLoop) : NONE;
3547
3737
  await yieldToLoop();
3548
- const svelteKitEdges = svelteKitLoadEdges(ctx);
3738
+ __mark('vueEdges');
3739
+ const svelteKitEdges = has('svelte') ? await svelteKitLoadEdges(ctx, yieldToLoop) : NONE;
3549
3740
  await yieldToLoop();
3550
- const pascalEdges = pascalFormEdges(ctx);
3741
+ __mark('svelteKitEdges');
3742
+ const pascalEdges = await pascalFormEdges(ctx, yieldToLoop);
3551
3743
  await yieldToLoop();
3552
- const flutterEdges = flutterBuildEdges(queries, ctx);
3744
+ __mark('pascalEdges');
3745
+ const flutterEdges = has('dart') ? await flutterBuildEdges(queries, ctx, yieldToLoop) : NONE;
3553
3746
  await yieldToLoop();
3554
- const arkuiStateEdges = arkuiStateBuildEdges(queries, ctx);
3747
+ __mark('flutterEdges');
3748
+ const arkuiStateEdges = has('arkts') ? await arkuiStateBuildEdges(queries, ctx, yieldToLoop) : NONE;
3555
3749
  await yieldToLoop();
3556
- const arkuiEmitter = arkuiEmitterEdges(ctx);
3750
+ __mark('arkuiStateEdges');
3751
+ const arkuiEmitter = has('arkts') ? await arkuiEmitterEdges(ctx, yieldToLoop) : NONE;
3557
3752
  await yieldToLoop();
3558
- const arkuiRoutes = arkuiRouterEdges(ctx);
3753
+ __mark('arkuiEmitter');
3754
+ const arkuiRoutes = has('arkts') ? await arkuiRouterEdges(ctx, yieldToLoop) : NONE;
3559
3755
  await yieldToLoop();
3560
- const cppEdges = cppOverrideEdges(queries);
3756
+ __mark('arkuiRoutes');
3757
+ const cppEdges = has('cpp') ? await cppOverrideEdges(queries, yieldToLoop) : NONE;
3561
3758
  await yieldToLoop();
3562
- const ifaceEdges = interfaceOverrideEdges(queries);
3759
+ __mark('cppEdges');
3760
+ const ifaceEdges = has('java', 'kotlin', 'csharp', 'swift', 'scala', 'go', 'rust', 'arkts', ...JS_FAMILY)
3761
+ ? await interfaceOverrideEdges(queries, yieldToLoop) : NONE;
3563
3762
  await yieldToLoop();
3564
- const kotlinExpectActual = kotlinExpectActualEdges(queries);
3763
+ __mark('ifaceEdges');
3764
+ const kotlinExpectActual = has('kotlin') ? await kotlinExpectActualEdges(queries, yieldToLoop) : NONE;
3565
3765
  await yieldToLoop();
3566
- const goGrpcEdges = goGrpcStubImplEdges(queries);
3766
+ __mark('kotlinExpectActual');
3767
+ const goGrpcEdges = has('go') ? await goGrpcStubImplEdges(queries, yieldToLoop) : NONE;
3567
3768
  await yieldToLoop();
3568
- const rnEventEdgesList = rnEventEdges(ctx);
3769
+ __mark('goGrpcEdges');
3770
+ const rnEventEdgesList = has(...JS_FAMILY) ? await rnEventEdges(ctx, yieldToLoop) : NONE;
3569
3771
  await yieldToLoop();
3570
- const fabricNativeEdges = fabricNativeImplEdges(ctx);
3772
+ __mark('rnEventEdgesList');
3773
+ const fabricNativeEdges = await fabricNativeImplEdges(ctx, yieldToLoop);
3571
3774
  await yieldToLoop();
3572
- const expoXPlatEdges = expoCrossPlatformEdges(queries);
3775
+ __mark('fabricNativeEdges');
3776
+ const expoXPlatEdges = await expoCrossPlatformEdges(queries, yieldToLoop);
3573
3777
  await yieldToLoop();
3574
- const rnXPlatEdges = rnCrossPlatformEdges(queries);
3778
+ __mark('expoXPlatEdges');
3779
+ const rnXPlatEdges = await rnCrossPlatformEdges(queries, yieldToLoop);
3575
3780
  await yieldToLoop();
3576
- const mybatisEdges = mybatisJavaXmlEdges(queries);
3781
+ __mark('rnXPlatEdges');
3782
+ const mybatisEdges = has('java', 'kotlin') && has('xml') ? await mybatisJavaXmlEdges(queries, yieldToLoop) : NONE;
3577
3783
  await yieldToLoop();
3578
- const ginEdges = ginMiddlewareChainEdges(queries, ctx);
3784
+ __mark('mybatisEdges');
3785
+ const ginEdges = has('go') ? await ginMiddlewareChainEdges(queries, ctx, yieldToLoop) : NONE;
3579
3786
  await yieldToLoop();
3580
- const thunkEdges = reduxThunkEdges(queries, ctx);
3787
+ __mark('ginEdges');
3788
+ const thunkEdges = has(...JS_FAMILY) ? await reduxThunkEdges(queries, ctx, yieldToLoop) : NONE;
3581
3789
  await yieldToLoop();
3790
+ __mark('thunkEdges');
3582
3791
  const registryEdges = await objectRegistryEdges(ctx, yieldToLoop);
3583
3792
  await yieldToLoop();
3584
- const rtkEdges = rtkQueryEdges(queries, ctx);
3793
+ __mark('registryEdges');
3794
+ const rtkEdges = has(...JS_FAMILY) ? await rtkQueryEdges(queries, ctx, yieldToLoop) : NONE;
3585
3795
  await yieldToLoop();
3586
- const piniaEdges = piniaStoreEdges(ctx);
3796
+ __mark('rtkEdges');
3797
+ const piniaEdges = has('vue', ...JS_FAMILY) ? await piniaStoreEdges(ctx, yieldToLoop) : NONE;
3587
3798
  await yieldToLoop();
3588
- const vuexEdges = vuexDispatchEdges(ctx);
3799
+ __mark('piniaEdges');
3800
+ const vuexEdges = has('vue', ...JS_FAMILY) ? await vuexDispatchEdges(ctx, yieldToLoop) : NONE;
3589
3801
  await yieldToLoop();
3590
- const celeryEdges = celeryDispatchEdges(ctx);
3802
+ __mark('vuexEdges');
3803
+ const celeryEdges = has('python') ? await celeryDispatchEdges(ctx, yieldToLoop) : NONE;
3591
3804
  await yieldToLoop();
3592
- const springEdges = springEventEdges(ctx);
3805
+ __mark('celeryEdges');
3806
+ const springEdges = has('java') ? await springEventEdges(ctx, yieldToLoop) : NONE;
3593
3807
  await yieldToLoop();
3594
- const mediatrEdges = mediatrDispatchEdges(ctx);
3808
+ __mark('springEdges');
3809
+ const mediatrEdges = has('csharp') ? await mediatrDispatchEdges(ctx, yieldToLoop) : NONE;
3595
3810
  await yieldToLoop();
3596
- const sidekiqEdges = sidekiqDispatchEdges(ctx);
3811
+ __mark('mediatrEdges');
3812
+ const sidekiqEdges = has('ruby') ? await sidekiqDispatchEdges(ctx, yieldToLoop) : NONE;
3597
3813
  await yieldToLoop();
3598
- const erlangBehaviourEdges = erlangBehaviourDispatchEdges(queries, ctx);
3814
+ __mark('sidekiqEdges');
3815
+ const erlangBehaviourEdges = has('erlang') ? await erlangBehaviourDispatchEdges(queries, ctx, yieldToLoop) : NONE;
3599
3816
  await yieldToLoop();
3600
- const laravelEdges = laravelEventEdges(ctx);
3817
+ __mark('erlangBehaviourEdges');
3818
+ const laravelEdges = has('php') ? await laravelEventEdges(ctx, yieldToLoop) : NONE;
3601
3819
  await yieldToLoop();
3602
- const cFnPtrEdges = (0, c_fnptr_synthesizer_1.cFnPointerDispatchEdges)(queries, ctx);
3820
+ __mark('laravelEdges');
3821
+ const cFnPtrEdges = has('c', 'cpp') ? await (0, c_fnptr_synthesizer_1.cFnPointerDispatchEdges)(queries, ctx, yieldToLoop) : NONE;
3603
3822
  await yieldToLoop();
3604
- const goframeEdges = (0, goframe_synthesizer_1.goframeRouteEdges)(ctx);
3823
+ __mark('cFnPtrEdges');
3824
+ const goframeEdges = has('go') ? await (0, goframe_synthesizer_1.goframeRouteEdges)(ctx, yieldToLoop) : NONE;
3605
3825
  await yieldToLoop();
3606
- const nixOptionEdges = await nixOptionPathEdges(queries, yieldToLoop);
3826
+ __mark('goframeEdges');
3827
+ const nixOptionEdges = has('nix') ? await nixOptionPathEdges(queries, yieldToLoop) : NONE;
3607
3828
  await yieldToLoop();
3829
+ __mark('nixOptionEdges');
3608
3830
  const merged = [];
3609
3831
  const seen = new Set();
3610
3832
  for (const e of [
@@ -3651,8 +3873,15 @@ async function synthesizeCallbackEdges(queries, ctx) {
3651
3873
  seen.add(key);
3652
3874
  merged.push(e);
3653
3875
  }
3654
- if (merged.length > 0)
3655
- queries.insertEdges(merged);
3876
+ __mark('dedupe-merge');
3877
+ // Chunked insert with yields: on the Linux kernel the merged synthesized
3878
+ // edge set is ~275k rows, and one transaction for all of them was a 20s
3879
+ // unyielded main-thread span (#1212 follow-up) — the last one in the tail.
3880
+ for (let i = 0; i < merged.length; i += 2000) {
3881
+ queries.insertEdges(merged.slice(i, i + 2000));
3882
+ await yieldToLoop();
3883
+ }
3884
+ __mark('insertMergedEdges');
3656
3885
  return merged.length + goImpl.length + goMethodContains.length;
3657
3886
  }
3658
3887
  //# sourceMappingURL=callback-synthesizer.js.map