@ryanstark24/sfgraph-core 1.1.8 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/dist/analyze/__tests__/find-nodes.test.d.ts +2 -0
  2. package/dist/analyze/__tests__/find-nodes.test.d.ts.map +1 -0
  3. package/dist/analyze/__tests__/find-nodes.test.js +129 -0
  4. package/dist/analyze/__tests__/find-nodes.test.js.map +1 -0
  5. package/dist/analyze/__tests__/findings.test.d.ts +2 -0
  6. package/dist/analyze/__tests__/findings.test.d.ts.map +1 -0
  7. package/dist/analyze/__tests__/findings.test.js +90 -0
  8. package/dist/analyze/__tests__/findings.test.js.map +1 -0
  9. package/dist/analyze/find-nodes.d.ts +38 -0
  10. package/dist/analyze/find-nodes.d.ts.map +1 -0
  11. package/dist/analyze/find-nodes.js +51 -0
  12. package/dist/analyze/find-nodes.js.map +1 -0
  13. package/dist/analyze/findings.d.ts +86 -0
  14. package/dist/analyze/findings.d.ts.map +1 -0
  15. package/dist/analyze/findings.js +143 -0
  16. package/dist/analyze/findings.js.map +1 -0
  17. package/dist/analyze/index.d.ts +2 -0
  18. package/dist/analyze/index.d.ts.map +1 -1
  19. package/dist/analyze/index.js +2 -0
  20. package/dist/analyze/index.js.map +1 -1
  21. package/dist/domain/edge-fact.d.ts +25 -0
  22. package/dist/domain/edge-fact.d.ts.map +1 -1
  23. package/dist/domain/rel-types.d.ts +4 -0
  24. package/dist/domain/rel-types.d.ts.map +1 -1
  25. package/dist/domain/rel-types.js +7 -0
  26. package/dist/domain/rel-types.js.map +1 -1
  27. package/dist/embedding/__tests__/embed.test.d.ts +2 -0
  28. package/dist/embedding/__tests__/embed.test.d.ts.map +1 -0
  29. package/dist/embedding/__tests__/embed.test.js +72 -0
  30. package/dist/embedding/__tests__/embed.test.js.map +1 -0
  31. package/dist/extractors/live-org/__tests__/liveness.test.d.ts +2 -0
  32. package/dist/extractors/live-org/__tests__/liveness.test.d.ts.map +1 -0
  33. package/dist/extractors/live-org/__tests__/liveness.test.js +148 -0
  34. package/dist/extractors/live-org/__tests__/liveness.test.js.map +1 -0
  35. package/dist/extractors/live-org/__tests__/mcd-baseline.test.d.ts +2 -0
  36. package/dist/extractors/live-org/__tests__/mcd-baseline.test.d.ts.map +1 -0
  37. package/dist/extractors/live-org/__tests__/mcd-baseline.test.js +174 -0
  38. package/dist/extractors/live-org/__tests__/mcd-baseline.test.js.map +1 -0
  39. package/dist/extractors/live-org/__tests__/metadata-read-batch.test.d.ts +2 -0
  40. package/dist/extractors/live-org/__tests__/metadata-read-batch.test.d.ts.map +1 -0
  41. package/dist/extractors/live-org/__tests__/metadata-read-batch.test.js +75 -0
  42. package/dist/extractors/live-org/__tests__/metadata-read-batch.test.js.map +1 -0
  43. package/dist/extractors/live-org/__tests__/omnistudio-retrieve.test.d.ts +2 -0
  44. package/dist/extractors/live-org/__tests__/omnistudio-retrieve.test.d.ts.map +1 -0
  45. package/dist/extractors/live-org/__tests__/omnistudio-retrieve.test.js +135 -0
  46. package/dist/extractors/live-org/__tests__/omnistudio-retrieve.test.js.map +1 -0
  47. package/dist/extractors/live-org/__tests__/soql-rebatch.test.d.ts +2 -0
  48. package/dist/extractors/live-org/__tests__/soql-rebatch.test.d.ts.map +1 -0
  49. package/dist/extractors/live-org/__tests__/soql-rebatch.test.js +118 -0
  50. package/dist/extractors/live-org/__tests__/soql-rebatch.test.js.map +1 -0
  51. package/dist/extractors/live-org/__tests__/vlocity-runner.test.js +27 -0
  52. package/dist/extractors/live-org/__tests__/vlocity-runner.test.js.map +1 -1
  53. package/dist/extractors/live-org/bulk-retrieve.d.ts +7 -0
  54. package/dist/extractors/live-org/bulk-retrieve.d.ts.map +1 -1
  55. package/dist/extractors/live-org/bulk-retrieve.js +10 -1
  56. package/dist/extractors/live-org/bulk-retrieve.js.map +1 -1
  57. package/dist/extractors/live-org/extractors/mcd-baseline.d.ts +60 -0
  58. package/dist/extractors/live-org/extractors/mcd-baseline.d.ts.map +1 -0
  59. package/dist/extractors/live-org/extractors/mcd-baseline.js +120 -0
  60. package/dist/extractors/live-org/extractors/mcd-baseline.js.map +1 -0
  61. package/dist/extractors/live-org/extractors/omnistudio-retrieve.d.ts +23 -0
  62. package/dist/extractors/live-org/extractors/omnistudio-retrieve.d.ts.map +1 -0
  63. package/dist/extractors/live-org/extractors/omnistudio-retrieve.js +172 -0
  64. package/dist/extractors/live-org/extractors/omnistudio-retrieve.js.map +1 -0
  65. package/dist/extractors/live-org/rate-limit.d.ts +57 -0
  66. package/dist/extractors/live-org/rate-limit.d.ts.map +1 -1
  67. package/dist/extractors/live-org/rate-limit.js +93 -8
  68. package/dist/extractors/live-org/rate-limit.js.map +1 -1
  69. package/dist/extractors/live-org/vlocity/runner.d.ts +1 -1
  70. package/dist/extractors/live-org/vlocity/runner.d.ts.map +1 -1
  71. package/dist/extractors/live-org/vlocity/runner.js +20 -9
  72. package/dist/extractors/live-org/vlocity/runner.js.map +1 -1
  73. package/dist/index.d.ts +1 -0
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +1 -0
  76. package/dist/index.js.map +1 -1
  77. package/dist/ingest/__tests__/multi-org-orchestrator.test.js +3 -0
  78. package/dist/ingest/__tests__/multi-org-orchestrator.test.js.map +1 -1
  79. package/dist/ingest/live-ingest.d.ts +70 -0
  80. package/dist/ingest/live-ingest.d.ts.map +1 -1
  81. package/dist/ingest/live-ingest.js +108 -1
  82. package/dist/ingest/live-ingest.js.map +1 -1
  83. package/dist/parsers/__tests__/edge-provenance.test.d.ts +2 -0
  84. package/dist/parsers/__tests__/edge-provenance.test.d.ts.map +1 -0
  85. package/dist/parsers/__tests__/edge-provenance.test.js +44 -0
  86. package/dist/parsers/__tests__/edge-provenance.test.js.map +1 -0
  87. package/dist/parsers/__tests__/lwc-bindings.test.js +72 -0
  88. package/dist/parsers/__tests__/lwc-bindings.test.js.map +1 -1
  89. package/dist/parsers/__tests__/mcd-gap-fills.test.d.ts +2 -0
  90. package/dist/parsers/__tests__/mcd-gap-fills.test.d.ts.map +1 -0
  91. package/dist/parsers/__tests__/mcd-gap-fills.test.js +75 -0
  92. package/dist/parsers/__tests__/mcd-gap-fills.test.js.map +1 -0
  93. package/dist/parsers/__tests__/omnistudio-overlap.test.d.ts +2 -0
  94. package/dist/parsers/__tests__/omnistudio-overlap.test.d.ts.map +1 -0
  95. package/dist/parsers/__tests__/omnistudio-overlap.test.js +163 -0
  96. package/dist/parsers/__tests__/omnistudio-overlap.test.js.map +1 -0
  97. package/dist/parsers/__tests__/reflection-walker.test.d.ts +2 -0
  98. package/dist/parsers/__tests__/reflection-walker.test.d.ts.map +1 -0
  99. package/dist/parsers/__tests__/reflection-walker.test.js +162 -0
  100. package/dist/parsers/__tests__/reflection-walker.test.js.map +1 -0
  101. package/dist/parsers/apex/__tests__/is-test-attribute.test.d.ts +2 -0
  102. package/dist/parsers/apex/__tests__/is-test-attribute.test.d.ts.map +1 -0
  103. package/dist/parsers/apex/__tests__/is-test-attribute.test.js +54 -0
  104. package/dist/parsers/apex/__tests__/is-test-attribute.test.js.map +1 -0
  105. package/dist/parsers/apex/__tests__/regex-arg-count.test.d.ts +2 -0
  106. package/dist/parsers/apex/__tests__/regex-arg-count.test.d.ts.map +1 -0
  107. package/dist/parsers/apex/__tests__/regex-arg-count.test.js +82 -0
  108. package/dist/parsers/apex/__tests__/regex-arg-count.test.js.map +1 -0
  109. package/dist/parsers/apex/class.d.ts.map +1 -1
  110. package/dist/parsers/apex/class.js +103 -5
  111. package/dist/parsers/apex/class.js.map +1 -1
  112. package/dist/parsers/common.d.ts +4 -1
  113. package/dist/parsers/common.d.ts.map +1 -1
  114. package/dist/parsers/common.js +15 -2
  115. package/dist/parsers/common.js.map +1 -1
  116. package/dist/parsers/generic/reflection-walker.d.ts +90 -0
  117. package/dist/parsers/generic/reflection-walker.d.ts.map +1 -0
  118. package/dist/parsers/generic/reflection-walker.js +195 -0
  119. package/dist/parsers/generic/reflection-walker.js.map +1 -0
  120. package/dist/parsers/lwc/html-visitor.d.ts.map +1 -1
  121. package/dist/parsers/lwc/html-visitor.js +81 -3
  122. package/dist/parsers/lwc/html-visitor.js.map +1 -1
  123. package/dist/parsers/object/object.d.ts.map +1 -1
  124. package/dist/parsers/object/object.js +34 -0
  125. package/dist/parsers/object/object.js.map +1 -1
  126. package/dist/parsers/omnistudio/overlap-detector.d.ts +50 -0
  127. package/dist/parsers/omnistudio/overlap-detector.d.ts.map +1 -0
  128. package/dist/parsers/omnistudio/overlap-detector.js +129 -0
  129. package/dist/parsers/omnistudio/overlap-detector.js.map +1 -0
  130. package/dist/render/__tests__/sarif.test.d.ts +2 -0
  131. package/dist/render/__tests__/sarif.test.d.ts.map +1 -0
  132. package/dist/render/__tests__/sarif.test.js +129 -0
  133. package/dist/render/__tests__/sarif.test.js.map +1 -0
  134. package/dist/render/sarif.d.ts +94 -0
  135. package/dist/render/sarif.d.ts.map +1 -0
  136. package/dist/render/sarif.js +133 -0
  137. package/dist/render/sarif.js.map +1 -0
  138. package/dist/storage/__tests__/analysis-tables-migration.test.js +1 -1
  139. package/dist/storage/__tests__/migrations.test.js +4 -4
  140. package/dist/storage/__tests__/rename-stability.test.d.ts +2 -0
  141. package/dist/storage/__tests__/rename-stability.test.d.ts.map +1 -0
  142. package/dist/storage/__tests__/rename-stability.test.js +135 -0
  143. package/dist/storage/__tests__/rename-stability.test.js.map +1 -0
  144. package/dist/storage/index.d.ts +1 -0
  145. package/dist/storage/index.d.ts.map +1 -1
  146. package/dist/storage/index.js +1 -0
  147. package/dist/storage/index.js.map +1 -1
  148. package/dist/storage/interfaces.d.ts +3 -0
  149. package/dist/storage/interfaces.d.ts.map +1 -1
  150. package/dist/storage/sqlite/graph-store.d.ts +6 -0
  151. package/dist/storage/sqlite/graph-store.d.ts.map +1 -1
  152. package/dist/storage/sqlite/graph-store.js +8 -0
  153. package/dist/storage/sqlite/graph-store.js.map +1 -1
  154. package/dist/storage/sqlite/migrations.d.ts.map +1 -1
  155. package/dist/storage/sqlite/migrations.js +19 -0
  156. package/dist/storage/sqlite/migrations.js.map +1 -1
  157. package/dist/storage/sqlite/rename-stability.d.ts +94 -0
  158. package/dist/storage/sqlite/rename-stability.d.ts.map +1 -0
  159. package/dist/storage/sqlite/rename-stability.js +96 -0
  160. package/dist/storage/sqlite/rename-stability.js.map +1 -0
  161. package/package.json +4 -1
@@ -0,0 +1,75 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { CustomObjectParser } from "../object/index.js";
3
+ import { makeTestCtx } from "./_harness.js";
4
+ const PARSER = new CustomObjectParser();
5
+ const ACCOUNT_OBJECT_HEADER = `<?xml version="1.0"?>
6
+ <CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
7
+ <fullName>Account</fullName>
8
+ <label>Account</label>`;
9
+ describe("W2-04: MCD gap-fill edges via the object parser", () => {
10
+ it("emits USES_GLOBAL_VALUE_SET when a picklist field references a GlobalValueSet", async () => {
11
+ const xml = `${ACCOUNT_OBJECT_HEADER}
12
+ <fields>
13
+ <fullName>Industry__c</fullName>
14
+ <type>Picklist</type>
15
+ <valueSet>
16
+ <valueSetName>IndustryGlobalValues</valueSetName>
17
+ </valueSet>
18
+ </fields>
19
+ </CustomObject>`;
20
+ const result = await PARSER.parse({ apiName: "Account", objectXml: xml }, makeTestCtx());
21
+ const gvs = result.edges.find((e) => e.relType === "USES_GLOBAL_VALUE_SET");
22
+ expect(gvs).toBeDefined();
23
+ expect(String(gvs?.srcQualifiedName)).toBe("CustomField:Account.Industry__c");
24
+ expect(String(gvs?.dstQualifiedName)).toBe("GlobalValueSet:IndustryGlobalValues");
25
+ expect(gvs?.attributes.fieldType).toBe("Picklist");
26
+ });
27
+ it("emits DEPENDS_ON_FIELD when a dependent picklist references its controlling field", async () => {
28
+ const xml = `${ACCOUNT_OBJECT_HEADER}
29
+ <fields>
30
+ <fullName>SubIndustry__c</fullName>
31
+ <type>Picklist</type>
32
+ <valueSet>
33
+ <controllingField>Industry__c</controllingField>
34
+ <valueSettings>
35
+ <controllingFieldValue>Tech</controllingFieldValue>
36
+ <valueName>SaaS</valueName>
37
+ </valueSettings>
38
+ </valueSet>
39
+ </fields>
40
+ </CustomObject>`;
41
+ const result = await PARSER.parse({ apiName: "Account", objectXml: xml }, makeTestCtx());
42
+ const dep = result.edges.find((e) => e.relType === "DEPENDS_ON_FIELD");
43
+ expect(dep).toBeDefined();
44
+ expect(String(dep?.srcQualifiedName)).toBe("CustomField:Account.SubIndustry__c");
45
+ expect(String(dep?.dstQualifiedName)).toBe("CustomField:Account.Industry__c");
46
+ expect(dep?.attributes.reason).toBe("dependent-picklist");
47
+ });
48
+ it("emits neither edge when the field has no valueSet structure", async () => {
49
+ const xml = `${ACCOUNT_OBJECT_HEADER}
50
+ <fields>
51
+ <fullName>Phone</fullName>
52
+ <type>Phone</type>
53
+ </fields>
54
+ </CustomObject>`;
55
+ const result = await PARSER.parse({ apiName: "Account", objectXml: xml }, makeTestCtx());
56
+ expect(result.edges.find((e) => e.relType === "USES_GLOBAL_VALUE_SET")).toBeUndefined();
57
+ expect(result.edges.find((e) => e.relType === "DEPENDS_ON_FIELD")).toBeUndefined();
58
+ });
59
+ it("handles BOTH gap-fills on the same field (rare but valid)", async () => {
60
+ const xml = `${ACCOUNT_OBJECT_HEADER}
61
+ <fields>
62
+ <fullName>Region__c</fullName>
63
+ <type>Picklist</type>
64
+ <valueSet>
65
+ <valueSetName>RegionGVS</valueSetName>
66
+ <controllingField>Country__c</controllingField>
67
+ </valueSet>
68
+ </fields>
69
+ </CustomObject>`;
70
+ const result = await PARSER.parse({ apiName: "Account", objectXml: xml }, makeTestCtx());
71
+ expect(result.edges.some((e) => e.relType === "USES_GLOBAL_VALUE_SET")).toBe(true);
72
+ expect(result.edges.some((e) => e.relType === "DEPENDS_ON_FIELD")).toBe(true);
73
+ });
74
+ });
75
+ //# sourceMappingURL=mcd-gap-fills.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcd-gap-fills.test.js","sourceRoot":"","sources":["../../../src/parsers/__tests__/mcd-gap-fills.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=omnistudio-overlap.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"omnistudio-overlap.test.d.ts","sourceRoot":"","sources":["../../../src/parsers/__tests__/omnistudio-overlap.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,163 @@
1
+ import { asOrgId, asQualifiedName, asSha256 } from "@ryanstark24/sfgraph-shared";
2
+ import { describe, expect, it } from "vitest";
3
+ import { REL_TYPES } from "../../domain/index.js";
4
+ import { SqliteGraphStore } from "../../storage/sqlite/graph-store.js";
5
+ import { resolveCrossFlavor } from "../cross-flavor-resolver.js";
6
+ import { detectOmnistudioOverlap } from "../omnistudio/overlap-detector.js";
7
+ import { makeTestCtx } from "./_harness.js";
8
+ const ORG = asOrgId("org_test");
9
+ function setupStore() {
10
+ const store = new SqliteGraphStore({ dbPath: ":memory:" });
11
+ return store.init().then(() => {
12
+ store.upsertOrg({
13
+ id: ORG,
14
+ alias: "test",
15
+ instanceUrl: "https://example.test",
16
+ apiVersion: "59.0",
17
+ createdAt: Date.now(),
18
+ });
19
+ return store;
20
+ });
21
+ }
22
+ function node(label, qname) {
23
+ const ts = Date.now();
24
+ return {
25
+ orgId: ORG,
26
+ qualifiedName: asQualifiedName(qname),
27
+ label,
28
+ attributes: {},
29
+ sourceHash: asSha256(qname),
30
+ firstSeenAt: ts,
31
+ lastSeenAt: ts,
32
+ lastModifiedAt: ts,
33
+ };
34
+ }
35
+ function edge(src, rel, dst) {
36
+ const ts = Date.now();
37
+ return {
38
+ orgId: ORG,
39
+ srcQualifiedName: asQualifiedName(src),
40
+ dstQualifiedName: asQualifiedName(dst),
41
+ relType: rel,
42
+ attributes: {},
43
+ firstSeenAt: ts,
44
+ lastSeenAt: ts,
45
+ };
46
+ }
47
+ describe("W2-01: OmniStudio overlap detector", () => {
48
+ it("annotates a matched CANONICAL_OF pair when both sides have identical signatures", async () => {
49
+ const store = await setupStore();
50
+ // Pair: DataRaptor:GetAccount ↔ OmniDataTransform:GetAccount, both with
51
+ // identical outbound calls (one read-field, one calls-method) so the
52
+ // signature multiset matches exactly.
53
+ store.mergeNodes([
54
+ node("DataRaptor", "DataRaptor:GetAccount"),
55
+ node("OmniDataTransform", "OmniDataTransform:GetAccount"),
56
+ node("CustomField", "CustomField:Account.Name"),
57
+ node("ApexMethod", "ApexMethod:Util.format(1)"),
58
+ ]);
59
+ store.mergeEdges([
60
+ edge("DataRaptor:GetAccount", REL_TYPES.READS_FIELD, "CustomField:Account.Name"),
61
+ edge("DataRaptor:GetAccount", REL_TYPES.CALLS, "ApexMethod:Util.format(1)"),
62
+ edge("OmniDataTransform:GetAccount", REL_TYPES.READS_FIELD, "CustomField:Account.Name"),
63
+ edge("OmniDataTransform:GetAccount", REL_TYPES.CALLS, "ApexMethod:Util.format(1)"),
64
+ ]);
65
+ const ctx = makeTestCtx();
66
+ resolveCrossFlavor(store, { orgId: ORG, ctx, namespace: null });
67
+ const result = detectOmnistudioOverlap(store, { orgId: ORG, ctx });
68
+ expect(result.matched).toBe(1);
69
+ expect(result.diverged).toBe(0);
70
+ expect(result.empty).toBe(0);
71
+ // Two CANONICAL_OF edges (one per direction) get annotated.
72
+ expect(result.annotated).toBe(2);
73
+ const canon = store
74
+ .listEdgesFrom(ORG, asQualifiedName("DataRaptor:GetAccount"), REL_TYPES.CANONICAL_OF)
75
+ .find((e) => String(e.dstQualifiedName) === "OmniDataTransform:GetAccount");
76
+ expect(canon?.attributes.signaturesMatch).toBe(true);
77
+ expect(canon?.attributes.divergencePoints).toEqual([]);
78
+ await store.close();
79
+ });
80
+ it("flags a diverged pair when the two sides have different outbound signatures", async () => {
81
+ const store = await setupStore();
82
+ // OmniDataTransform has one extra READS_FIELD edge — the signature
83
+ // multisets differ on that key.
84
+ store.mergeNodes([
85
+ node("DataRaptor", "DataRaptor:GetAccount"),
86
+ node("OmniDataTransform", "OmniDataTransform:GetAccount"),
87
+ node("CustomField", "CustomField:Account.Name"),
88
+ node("CustomField", "CustomField:Account.Phone"),
89
+ ]);
90
+ store.mergeEdges([
91
+ edge("DataRaptor:GetAccount", REL_TYPES.READS_FIELD, "CustomField:Account.Name"),
92
+ edge("OmniDataTransform:GetAccount", REL_TYPES.READS_FIELD, "CustomField:Account.Name"),
93
+ edge("OmniDataTransform:GetAccount", REL_TYPES.READS_FIELD, "CustomField:Account.Phone"),
94
+ ]);
95
+ const ctx = makeTestCtx();
96
+ resolveCrossFlavor(store, { orgId: ORG, ctx, namespace: null });
97
+ const result = detectOmnistudioOverlap(store, { orgId: ORG, ctx });
98
+ expect(result.matched).toBe(0);
99
+ expect(result.diverged).toBe(1);
100
+ const canon = store
101
+ .listEdgesFrom(ORG, asQualifiedName("DataRaptor:GetAccount"), REL_TYPES.CANONICAL_OF)
102
+ .find((e) => String(e.dstQualifiedName) === "OmniDataTransform:GetAccount");
103
+ expect(canon?.attributes.signaturesMatch).toBe(false);
104
+ const points = canon?.attributes.divergencePoints;
105
+ expect(points.length).toBe(1);
106
+ expect(points[0]).toMatch(/READS_FIELD/);
107
+ // Difference: DataRaptor=1, OmniDataTransform=2 (alphabetical order
108
+ // puts DataRaptor:Foo before OmniDataTransform:Foo in pairKey but the
109
+ // diff is symmetric, both 1-vs-2 or 2-vs-1 are valid expressions of
110
+ // the same divergence — accept either ordering).
111
+ expect(points[0]).toMatch(/(1 vs 2|2 vs 1)/);
112
+ await store.close();
113
+ });
114
+ it("ignores CANONICAL_OF self-edges when computing signatures (no spurious matching)", async () => {
115
+ const store = await setupStore();
116
+ // A pair whose ONLY outbound edges are the CANONICAL_OF links the
117
+ // resolver emits. Without the filter, both sides' signature multisets
118
+ // would each contain one CANONICAL_OF entry — vacuously "matched". The
119
+ // detector must exclude these to surface the pair as `empty` instead.
120
+ store.mergeNodes([
121
+ node("DataRaptor", "DataRaptor:Bare"),
122
+ node("OmniDataTransform", "OmniDataTransform:Bare"),
123
+ ]);
124
+ const ctx = makeTestCtx();
125
+ resolveCrossFlavor(store, { orgId: ORG, ctx, namespace: null });
126
+ const result = detectOmnistudioOverlap(store, { orgId: ORG, ctx });
127
+ expect(result.empty).toBe(1);
128
+ expect(result.matched).toBe(0);
129
+ expect(result.diverged).toBe(0);
130
+ await store.close();
131
+ });
132
+ it("dryRun mode computes results without writing annotations", async () => {
133
+ const store = await setupStore();
134
+ store.mergeNodes([
135
+ node("DataRaptor", "DataRaptor:X"),
136
+ node("OmniDataTransform", "OmniDataTransform:X"),
137
+ node("CustomField", "CustomField:Account.Name"),
138
+ ]);
139
+ store.mergeEdges([
140
+ edge("DataRaptor:X", REL_TYPES.READS_FIELD, "CustomField:Account.Name"),
141
+ edge("OmniDataTransform:X", REL_TYPES.READS_FIELD, "CustomField:Account.Name"),
142
+ ]);
143
+ const ctx = makeTestCtx();
144
+ resolveCrossFlavor(store, { orgId: ORG, ctx, namespace: null });
145
+ const result = detectOmnistudioOverlap(store, { orgId: ORG, ctx, dryRun: true });
146
+ expect(result.matched).toBe(1);
147
+ // Annotation NOT present in store
148
+ const canon = store
149
+ .listEdgesFrom(ORG, asQualifiedName("DataRaptor:X"), REL_TYPES.CANONICAL_OF)
150
+ .find((e) => String(e.dstQualifiedName) === "OmniDataTransform:X");
151
+ expect(canon?.attributes.signaturesMatch).toBeUndefined();
152
+ await store.close();
153
+ });
154
+ it("returns zero counts when no CANONICAL_OF edges exist (no canonical pairs in the org)", async () => {
155
+ const store = await setupStore();
156
+ store.mergeNodes([node("DataRaptor", "DataRaptor:Lonely")]);
157
+ const ctx = makeTestCtx();
158
+ const result = detectOmnistudioOverlap(store, { orgId: ORG, ctx });
159
+ expect(result).toEqual({ matched: 0, diverged: 0, empty: 0, annotated: 0 });
160
+ await store.close();
161
+ });
162
+ });
163
+ //# sourceMappingURL=omnistudio-overlap.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"omnistudio-overlap.test.js","sourceRoot":"","sources":["../../../src/parsers/__tests__/omnistudio-overlap.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=reflection-walker.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reflection-walker.test.d.ts","sourceRoot":"","sources":["../../../src/parsers/__tests__/reflection-walker.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,162 @@
1
+ import { asOrgId, asQualifiedName, asSha256 } from "@ryanstark24/sfgraph-shared";
2
+ import { describe, expect, it } from "vitest";
3
+ import { SqliteGraphStore } from "../../storage/sqlite/graph-store.js";
4
+ import { walkBlobsForReferences } from "../generic/reflection-walker.js";
5
+ import { makeTestCtx } from "./_harness.js";
6
+ const ORG = asOrgId("org_reflection");
7
+ async function setupStore() {
8
+ const store = new SqliteGraphStore({ dbPath: ":memory:" });
9
+ await store.init();
10
+ store.upsertOrg({
11
+ id: ORG,
12
+ alias: "test",
13
+ instanceUrl: "https://example.test",
14
+ apiVersion: "59.0",
15
+ createdAt: Date.now(),
16
+ });
17
+ return store;
18
+ }
19
+ function node(label, qname, attributes = {}) {
20
+ const ts = Date.now();
21
+ return {
22
+ orgId: ORG,
23
+ qualifiedName: asQualifiedName(qname),
24
+ label,
25
+ attributes,
26
+ sourceHash: asSha256(qname),
27
+ firstSeenAt: ts,
28
+ lastSeenAt: ts,
29
+ lastModifiedAt: ts,
30
+ };
31
+ }
32
+ describe("W6-01: reflection-based generic walker", () => {
33
+ it("emits a REFERENCES edge when a nested blob string matches an existing qname's bare name", async () => {
34
+ const store = await setupStore();
35
+ store.mergeNodes([
36
+ node("OmniProcess", "OmniProcess:GetAccount", {
37
+ propertySet: {
38
+ dataRaptorBundleName: "AccountDR",
39
+ someUnrelatedField: "hello world",
40
+ },
41
+ }),
42
+ node("DataRaptor", "DataRaptor:AccountDR"),
43
+ ]);
44
+ const result = walkBlobsForReferences(store, { orgId: ORG, ctx: makeTestCtx() });
45
+ expect(result.scanned).toBeGreaterThan(0);
46
+ expect(result.edgesEmitted).toBe(1);
47
+ const edge = store
48
+ .listEdgesFrom(ORG, asQualifiedName("OmniProcess:GetAccount"), "REFERENCES")
49
+ .find((e) => String(e.dstQualifiedName) === "DataRaptor:AccountDR");
50
+ expect(edge).toBeDefined();
51
+ expect(edge?.attributes.source).toBe("reflection");
52
+ expect(edge?.attributes.confidence).toBe("pattern-match");
53
+ expect(edge?.attributes.viaKey).toBe("dataRaptorBundleName");
54
+ await store.close();
55
+ });
56
+ it("flags ambiguous matches when one bare name matches multiple labels", async () => {
57
+ const store = await setupStore();
58
+ store.mergeNodes([
59
+ node("OmniProcess", "OmniProcess:Driver", { config: { name: "SharedName" } }),
60
+ node("ApexClass", "ApexClass:SharedName"),
61
+ node("Flow", "Flow:SharedName"),
62
+ ]);
63
+ const result = walkBlobsForReferences(store, { orgId: ORG, ctx: makeTestCtx() });
64
+ const edges = store
65
+ .listEdgesFrom(ORG, asQualifiedName("OmniProcess:Driver"), "REFERENCES")
66
+ .filter((e) => e.attributes.ambiguous === true);
67
+ expect(edges.length).toBe(2);
68
+ expect(edges.every((e) => e.attributes.source === "reflection")).toBe(true);
69
+ expect(result.ambiguousMatches).toBeGreaterThan(0);
70
+ await store.close();
71
+ });
72
+ it("skips self-references when a node's blob contains its own bare name", async () => {
73
+ const store = await setupStore();
74
+ store.mergeNodes([
75
+ node("OmniProcess", "OmniProcess:GetAccount", {
76
+ propertySet: { name: "GetAccount" },
77
+ }),
78
+ ]);
79
+ const result = walkBlobsForReferences(store, { orgId: ORG, ctx: makeTestCtx() });
80
+ expect(result.edgesEmitted).toBe(0);
81
+ await store.close();
82
+ });
83
+ it("ignores reserved words even when an identically-named node exists", async () => {
84
+ const store = await setupStore();
85
+ store.mergeNodes([
86
+ node("OmniProcess", "OmniProcess:Driver", {
87
+ config: { someFlag: "true", anotherFlag: "string" },
88
+ }),
89
+ // Pathological nodes whose bare names collide with reserved words.
90
+ node("CustomField", "CustomField:Object.true"),
91
+ node("ApexClass", "ApexClass:string"),
92
+ ]);
93
+ const result = walkBlobsForReferences(store, { orgId: ORG, ctx: makeTestCtx() });
94
+ expect(result.edgesEmitted).toBe(0);
95
+ await store.close();
96
+ });
97
+ it("ignores strings shorter than minStringLength (default 4)", async () => {
98
+ const store = await setupStore();
99
+ store.mergeNodes([
100
+ node("OmniProcess", "OmniProcess:Driver", { config: { id: "Foo" } }),
101
+ node("ApexClass", "ApexClass:Foo"),
102
+ ]);
103
+ const result = walkBlobsForReferences(store, { orgId: ORG, ctx: makeTestCtx() });
104
+ expect(result.edgesEmitted).toBe(0); // "Foo" is length 3
105
+ await store.close();
106
+ });
107
+ it("respects maxEdgesPerSource cap and increments truncatedSources", async () => {
108
+ const store = await setupStore();
109
+ // Source node whose blob references 5 distinct existing dsts.
110
+ const blob = {};
111
+ for (let i = 0; i < 5; i += 1)
112
+ blob[`ref${i}`] = `Target${i}_Class`;
113
+ store.mergeNodes([
114
+ node("OmniProcess", "OmniProcess:Driver", { config: blob }),
115
+ ...[0, 1, 2, 3, 4].map((i) => node("ApexClass", `ApexClass:Target${i}_Class`)),
116
+ ]);
117
+ const result = walkBlobsForReferences(store, {
118
+ orgId: ORG,
119
+ ctx: makeTestCtx(),
120
+ maxEdgesPerSource: 2,
121
+ });
122
+ expect(result.edgesEmitted).toBe(2);
123
+ expect(result.truncatedSources).toBe(1);
124
+ await store.close();
125
+ });
126
+ it("scopeToLabels restricts the source-and-index node set", async () => {
127
+ const store = await setupStore();
128
+ store.mergeNodes([
129
+ node("OmniProcess", "OmniProcess:Driver", { config: { foo: "AccountDR" } }),
130
+ node("DataRaptor", "DataRaptor:AccountDR"),
131
+ // ApexClass should be IGNORED entirely when scopeToLabels excludes it.
132
+ node("ApexClass", "ApexClass:AccountDR_Class", { body: { ref: "AccountDR" } }),
133
+ ]);
134
+ const result = walkBlobsForReferences(store, {
135
+ orgId: ORG,
136
+ ctx: makeTestCtx(),
137
+ scopeToLabels: ["OmniProcess", "DataRaptor"],
138
+ });
139
+ // OmniProcess:Driver → DataRaptor:AccountDR is in-scope and emits.
140
+ // ApexClass:AccountDR_Class is out-of-scope (not scanned as source AND
141
+ // not in the bare-name index), so no edge from it.
142
+ expect(result.edgesEmitted).toBe(1);
143
+ await store.close();
144
+ });
145
+ it("skips multi-word / sentence strings (Salesforce identifiers don't contain whitespace)", async () => {
146
+ const store = await setupStore();
147
+ store.mergeNodes([
148
+ node("OmniProcess", "OmniProcess:Driver", {
149
+ propertySet: {
150
+ description: "AccountDR is the helper that does the thing",
151
+ },
152
+ }),
153
+ node("DataRaptor", "DataRaptor:AccountDR"),
154
+ ]);
155
+ const result = walkBlobsForReferences(store, { orgId: ORG, ctx: makeTestCtx() });
156
+ // The string contains whitespace, so the whole value is rejected — we
157
+ // do NOT tokenise and match per-word, by design.
158
+ expect(result.edgesEmitted).toBe(0);
159
+ await store.close();
160
+ });
161
+ });
162
+ //# sourceMappingURL=reflection-walker.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reflection-walker.test.js","sourceRoot":"","sources":["../../../src/parsers/__tests__/reflection-walker.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=is-test-attribute.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is-test-attribute.test.d.ts","sourceRoot":"","sources":["../../../../src/parsers/apex/__tests__/is-test-attribute.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,54 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { ApexClassParser } from "../index.js";
3
+ import { makeTestCtx } from "../../__tests__/_harness.js";
4
+ async function parse(body) {
5
+ return new ApexClassParser().parse({ className: "Sample", body, metaXml: "<ApexClass/>" }, makeTestCtx());
6
+ }
7
+ describe("W1-05: IS_TEST attribute on Apex class / method nodes", () => {
8
+ it("class with @isTest annotation: class node has isTest=true; static methods inherit", async () => {
9
+ const result = await parse(`
10
+ @isTest
11
+ public class SampleTest {
12
+ @isTest static void testOne() {}
13
+ static void helper() {}
14
+ }
15
+ `);
16
+ const cls = result.nodes.find((n) => String(n.qualifiedName) === "ApexClass:SampleTest");
17
+ expect(cls?.attributes.isTest).toBe(true);
18
+ // Both methods inherit isTest from class @isTest + static
19
+ const testOne = result.nodes.find((n) => String(n.qualifiedName).startsWith("ApexMethod:SampleTest.testOne") || String(n.qualifiedName).startsWith("ApexMethod:SampleTest.testOne") || String(n.qualifiedName).includes("SampleTest.testOne"));
20
+ expect(testOne?.label).toBe("TestMethod");
21
+ expect(testOne?.attributes.isTest).toBe(true);
22
+ const helper = result.nodes.find((n) => String(n.qualifiedName).includes("SampleTest.helper"));
23
+ expect(helper?.label).toBe("TestMethod"); // static method in @isTest class
24
+ expect(helper?.attributes.isTest).toBe(true);
25
+ });
26
+ it("plain class with @isTest only on a single method: only that method is a test", async () => {
27
+ const result = await parse(`
28
+ public class Mixed {
29
+ @isTest static void onlyThisOne() {}
30
+ public void normalMethod() {}
31
+ }
32
+ `);
33
+ const cls = result.nodes.find((n) => String(n.qualifiedName) === "ApexClass:Mixed");
34
+ expect(cls?.attributes.isTest).toBe(false);
35
+ const test = result.nodes.find((n) => String(n.qualifiedName).includes("Mixed.onlyThisOne"));
36
+ expect(test?.label).toBe("TestMethod");
37
+ expect(test?.attributes.isTest).toBe(true);
38
+ const normal = result.nodes.find((n) => String(n.qualifiedName).includes("Mixed.normalMethod"));
39
+ expect(normal?.label).toBe("ApexMethod");
40
+ expect(normal?.attributes.isTest).toBe(false);
41
+ });
42
+ it("@TestSetup methods are flagged as tests", async () => {
43
+ const result = await parse(`
44
+ @isTest
45
+ public class WithSetup {
46
+ @TestSetup static void seed() {}
47
+ }
48
+ `);
49
+ const setup = result.nodes.find((n) => String(n.qualifiedName).includes("WithSetup.seed"));
50
+ expect(setup?.label).toBe("TestMethod");
51
+ expect(setup?.attributes.isTest).toBe(true);
52
+ });
53
+ });
54
+ //# sourceMappingURL=is-test-attribute.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is-test-attribute.test.js","sourceRoot":"","sources":["../../../../src/parsers/apex/__tests__/is-test-attribute.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=regex-arg-count.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"regex-arg-count.test.d.ts","sourceRoot":"","sources":["../../../../src/parsers/apex/__tests__/regex-arg-count.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,82 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { ApexClassParser } from "../index.js";
3
+ import { makeTestCtx } from "../../__tests__/_harness.js";
4
+ async function callsFromBody(body) {
5
+ const result = await new ApexClassParser().parse({ className: "Caller", body, metaXml: "<ApexClass/>" }, makeTestCtx());
6
+ return result.edges
7
+ .filter((e) => e.relType === "CALLS")
8
+ .map((e) => ({
9
+ dst: String(e.dstQualifiedName),
10
+ arity: e.attributes.arity,
11
+ resolvedBy: e.attributes.resolvedBy,
12
+ unresolvedArity: e.attributes.unresolvedArity,
13
+ }));
14
+ }
15
+ describe("W1-04: regex-mode arg counting in Apex CALLS emission", () => {
16
+ it("counts a simple two-arg call", async () => {
17
+ const body = `
18
+ public class Caller {
19
+ public void run() {
20
+ Util.doWork(account, 42);
21
+ }
22
+ }
23
+ `;
24
+ const calls = await callsFromBody(body);
25
+ const c = calls.find((e) => e.dst === "ApexMethod:Util.doWork(2)");
26
+ expect(c).toBeDefined();
27
+ expect(c?.arity).toBe(2);
28
+ expect(c?.resolvedBy).toBe("regex-arg-count");
29
+ });
30
+ it("counts zero args correctly (no commas, no content)", async () => {
31
+ const body = `
32
+ public class Caller {
33
+ public void run() {
34
+ Util.init();
35
+ }
36
+ }
37
+ `;
38
+ const calls = await callsFromBody(body);
39
+ const c = calls.find((e) => e.dst === "ApexMethod:Util.init(0)");
40
+ expect(c).toBeDefined();
41
+ expect(c?.arity).toBe(0);
42
+ });
43
+ // NOTE: String literals are stripped (replaced with a single space) by
44
+ // stripCommentsAndStrings *before* the regex pass runs, so the arity
45
+ // counter never sees commas-inside-strings. A call whose only arg is
46
+ // a string literal becomes `Util.log( )` after stripping and counts
47
+ // as arity 0 — a known regex-mode precision limit. Set
48
+ // SFGRAPH_APEX_PARSER=ast for precise arity on those calls.
49
+ it("does not double-count commas inside nested calls", async () => {
50
+ const body = `
51
+ public class Caller {
52
+ public void run() {
53
+ Util.combine(Inner.make(a, b), Inner.make(c, d));
54
+ }
55
+ }
56
+ `;
57
+ const calls = await callsFromBody(body);
58
+ const c = calls.find((e) => e.dst === "ApexMethod:Util.combine(2)");
59
+ expect(c).toBeDefined();
60
+ expect(c?.arity).toBe(2);
61
+ });
62
+ it("does not double-count commas inside block comments", async () => {
63
+ const body = `
64
+ public class Caller {
65
+ public void run() {
66
+ Util.run(a /* note, with, commas */, b);
67
+ }
68
+ }
69
+ `;
70
+ const calls = await callsFromBody(body);
71
+ const c = calls.find((e) => e.dst === "ApexMethod:Util.run(2)");
72
+ expect(c).toBeDefined();
73
+ expect(c?.arity).toBe(2);
74
+ });
75
+ // NOTE: truncated/unbalanced source never reaches the regex pass —
76
+ // apex-parser's ThrowingErrorListener emits a ParseError node first and
77
+ // returns empty edges (class.ts:292-303). countCallArgs returning null
78
+ // is the defensive guard for *valid* sources that happen to confuse
79
+ // the lexer-light counter (e.g. unusual operator overloads in the
80
+ // future); we can't exercise the fallback path from this test surface.
81
+ });
82
+ //# sourceMappingURL=regex-arg-count.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"regex-arg-count.test.js","sourceRoot":"","sources":["../../../../src/parsers/apex/__tests__/regex-arg-count.test.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"class.d.ts","sourceRoot":"","sources":["../../../src/parsers/apex/class.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAGxE,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAqKD,qBAAa,eAAgB,YAAW,MAAM,CAAC,cAAc,CAAC;IAC5D,QAAQ,CAAC,QAAQ,cAAgC;IACjD,QAAQ,CAAC,IAAI,eAAe;IAEtB,KAAK,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC;CA0T5E"}
1
+ {"version":3,"file":"class.d.ts","sourceRoot":"","sources":["../../../src/parsers/apex/class.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAGxE,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAgPD,qBAAa,eAAgB,YAAW,MAAM,CAAC,cAAc,CAAC;IAC5D,QAAQ,CAAC,QAAQ,cAAgC;IACjD,QAAQ,CAAC,IAAI,eAAe;IAEtB,KAAK,CAAC,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC;CA8U5E"}