@yesprasad/fluent-graph 0.1.0 → 0.2.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.
@@ -4,35 +4,93 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.displayLineageMap = displayLineageMap;
7
+ const path_1 = __importDefault(require("path"));
7
8
  const chalk_1 = __importDefault(require("chalk"));
8
9
  const cli_table3_1 = __importDefault(require("cli-table3"));
9
- function displayLineageMap(graph) {
10
+ function displayLineageMap(graph, registry) {
10
11
  console.log(chalk_1.default.yellow.bold('\nšŸ“Š LINEAGE MAP'));
11
12
  console.log(chalk_1.default.gray('═'.repeat(80)));
12
- // ===== SECTION 1: DATA SCHEMA =====
13
- const dataTables = graph.nodes.filter(node => node.table === 'sys_db_object' && node.action !== 'DELETE');
14
- if (dataTables.length > 0) {
15
- console.log(chalk_1.default.cyan.bold('\nšŸ“¦ DATA SCHEMA'));
16
- const table = new cli_table3_1.default({
17
- head: [
18
- chalk_1.default.cyan('Table Name'),
19
- chalk_1.default.cyan('Status'),
20
- chalk_1.default.cyan('Source File')
21
- ],
22
- wordWrap: true,
23
- style: { head: [], border: ['gray'] }
24
- });
25
- dataTables.forEach(tableNode => {
26
- const statusLabel = tableNode.action === 'DELETE' ? chalk_1.default.red('DELETE') : chalk_1.default.green('ACTIVE');
27
- table.push([
28
- chalk_1.default.white(tableNode.name),
29
- statusLabel,
30
- chalk_1.default.gray(tableNode.source.file)
31
- ]);
32
- });
33
- console.log(table.toString());
13
+ // ===== DYNAMIC PLUGIN GROUPS =====
14
+ const pluginGroups = new Map();
15
+ for (const node of graph.nodes) {
16
+ if (node.action === 'DELETE')
17
+ continue;
18
+ const groupName = registry.getDisplayGroupName(node.table);
19
+ if (!pluginGroups.has(groupName)) {
20
+ pluginGroups.set(groupName, []);
21
+ }
22
+ pluginGroups.get(groupName).push(node);
23
+ }
24
+ for (const [groupName, nodes] of pluginGroups) {
25
+ // Only show primary (non-child) nodes in the section
26
+ const primaryNodes = nodes.filter(n => !registry.isChild(n.table) && n.source.file);
27
+ if (primaryNodes.length === 0)
28
+ continue;
29
+ console.log(chalk_1.default.cyan.bold(`\n${groupName}`));
30
+ const isBusinessRuleGroup = primaryNodes.some(n => registry.getPluginName(n.table) === 'BusinessRulePlugin');
31
+ if (isBusinessRuleGroup) {
32
+ const table = new cli_table3_1.default({
33
+ head: [
34
+ chalk_1.default.cyan('Name'),
35
+ chalk_1.default.cyan('When'),
36
+ chalk_1.default.cyan('Table'),
37
+ chalk_1.default.cyan('On'),
38
+ chalk_1.default.cyan('Order'),
39
+ chalk_1.default.cyan('Status'),
40
+ chalk_1.default.cyan('File'),
41
+ ],
42
+ colWidths: [32, 8, 28, 16, 7, 8, 28],
43
+ wordWrap: true,
44
+ style: { head: [], border: ['gray'] },
45
+ });
46
+ primaryNodes.forEach(node => {
47
+ const statusLabel = node.action === 'DELETE' ? chalk_1.default.red('DELETE') : chalk_1.default.green('ACTIVE');
48
+ const when = node.attributes.when || '';
49
+ const depEdge = graph.edges.find(e => e.from === node.id && e.type === 'dependency' && e.targetTable);
50
+ const targetTable = depEdge?.targetTable || '';
51
+ const ops = [];
52
+ if (node.attributes.action_insert)
53
+ ops.push('Insert');
54
+ if (node.attributes.action_update)
55
+ ops.push('Update');
56
+ if (node.attributes.action_delete)
57
+ ops.push('Delete');
58
+ if (node.attributes.action_query)
59
+ ops.push('Query');
60
+ table.push([
61
+ chalk_1.default.white(node.name),
62
+ chalk_1.default.yellow(when),
63
+ chalk_1.default.magenta(targetTable),
64
+ ops.length ? ops.join(', ') : chalk_1.default.gray('—'),
65
+ String(node.attributes.order ?? ''),
66
+ statusLabel,
67
+ chalk_1.default.gray(path_1.default.basename(node.source.file)),
68
+ ]);
69
+ });
70
+ console.log(table.toString());
71
+ }
72
+ else {
73
+ const table = new cli_table3_1.default({
74
+ head: [
75
+ chalk_1.default.cyan('Name'),
76
+ chalk_1.default.cyan('Status'),
77
+ chalk_1.default.cyan('Source File')
78
+ ],
79
+ wordWrap: true,
80
+ style: { head: [], border: ['gray'] }
81
+ });
82
+ primaryNodes.forEach(node => {
83
+ const statusLabel = node.action === 'DELETE' ? chalk_1.default.red('DELETE') : chalk_1.default.green('ACTIVE');
84
+ table.push([
85
+ chalk_1.default.white(node.name),
86
+ statusLabel,
87
+ chalk_1.default.gray(node.source.file)
88
+ ]);
89
+ });
90
+ console.log(table.toString());
91
+ }
34
92
  }
35
- // ===== SECTION 2: SCHEMA RELATIONSHIPS =====
93
+ // ===== SCHEMA RELATIONSHIPS =====
36
94
  const schemaEdges = graph.edges.filter(edge => edge.type === 'schema_relationship');
37
95
  if (schemaEdges.length > 0) {
38
96
  console.log(chalk_1.default.cyan.bold('\nšŸ”— SCHEMA RELATIONSHIPS (Reference Fields)'));
@@ -56,111 +114,18 @@ function displayLineageMap(graph) {
56
114
  });
57
115
  console.log(table.toString());
58
116
  }
59
- // ===== SECTION 3: LOGIC ATTACHMENTS =====
60
- const logicArtifacts = graph.nodes.filter(node => ['sys_script_client', 'sys_ui_action', 'sys_script', 'sys_script_include', 'sys_ui_policy'].includes(node.table) &&
61
- node.action !== 'DELETE' &&
62
- node.source.file);
63
- if (logicArtifacts.length > 0) {
64
- console.log(chalk_1.default.cyan.bold('\nāš™ļø LOGIC ATTACHMENTS (Scripts & Actions)'));
65
- const table = new cli_table3_1.default({
66
- head: [
67
- chalk_1.default.cyan('Artifact Name'),
68
- chalk_1.default.cyan('Target Table'),
69
- chalk_1.default.cyan('Type'),
70
- chalk_1.default.cyan('Artifact Type')
71
- ],
72
- wordWrap: true,
73
- style: { head: [], border: ['gray'] }
74
- });
75
- logicArtifacts.forEach(logicNode => {
76
- const dependencyEdge = graph.edges.find(edge => edge.from === logicNode.id && edge.type === 'dependency');
77
- const targetTable = dependencyEdge
78
- ? (dependencyEdge.to.startsWith('platform:')
79
- ? dependencyEdge.to.split(':')[1]
80
- : graph.nodes.find(node => node.id === dependencyEdge.to)?.name || dependencyEdge.to)
81
- : '[global]';
82
- const typeLabel = logicNode.attributes?.type ? `[${logicNode.attributes.type}]` : '[action]';
83
- const artifactType = (() => {
84
- switch (logicNode.table) {
85
- case 'sys_script_client': return 'client script';
86
- case 'sys_script': return 'script';
87
- case 'sys_ui_action': return 'ui action';
88
- case 'sys_script_include': return 'script include';
89
- case 'sys_ui_policy': return 'ui policy';
90
- default: return logicNode.table;
91
- }
92
- })();
93
- table.push([
94
- chalk_1.default.white(logicNode.name),
95
- chalk_1.default.gray(targetTable),
96
- chalk_1.default.gray(typeLabel),
97
- artifactType
98
- ]);
99
- });
100
- console.log(table.toString());
101
- }
102
- // ===== SECTION 4: SECURITY & ACCESS (ACLs) =====
103
- const aclNodes = graph.nodes.filter(node => node.table === 'sys_security_acl' && node.action !== 'DELETE');
104
- if (aclNodes.length > 0) {
105
- console.log(chalk_1.default.cyan.bold('\nšŸ”’ SECURITY & ACCESS (ACLs)'));
106
- const table = new cli_table3_1.default({
107
- head: [
108
- chalk_1.default.cyan('ACL'),
109
- chalk_1.default.cyan('Level'),
110
- chalk_1.default.cyan('Operation'),
111
- chalk_1.default.cyan('Roles'),
112
- chalk_1.default.cyan('Target Table')
113
- ],
114
- wordWrap: true,
115
- style: { head: [], border: ['gray'] }
116
- });
117
- aclNodes.forEach(aclNode => {
118
- const aclEdge = graph.edges.find(edge => edge.from === aclNode.id && edge.type === 'dependency');
119
- const targetTable = aclEdge?.targetTable || (aclEdge?.to.startsWith('platform:') ? aclEdge.to.split(':')[1] : 'unknown');
120
- const [nameTable, nameField] = aclNode.name.includes('.') ? aclNode.name.split('.') : [aclNode.name, '*'];
121
- const isFieldLevel = nameField !== '*' && nameField !== undefined;
122
- const level = isFieldLevel ? 'Field-level' : 'Table-level';
123
- const operation = (aclNode.attributes?.operation || 'read').toUpperCase();
124
- const roleEdges = graph.edges.filter(edge => edge.from === aclNode.id && edge.type === 'composition');
125
- const roleNames = roleEdges
126
- .map(edge => {
127
- const roleNode = graph.nodes.find(node => node.id === edge.to);
128
- return roleNode?.attributes?.name || roleNode?.name || 'unknown';
129
- })
130
- .filter(Boolean)
131
- .sort();
132
- const rolesDisplay = roleNames.length > 0 ? roleNames.join(', ') : chalk_1.default.gray('No roles specified');
133
- const aclLabel = `${operation} on ${nameTable}${isFieldLevel ? `.${nameField}` : ''}`;
134
- table.push([
135
- chalk_1.default.yellow(aclLabel),
136
- chalk_1.default.gray(level),
137
- chalk_1.default.gray(operation),
138
- rolesDisplay,
139
- chalk_1.default.magenta(targetTable)
140
- ]);
141
- });
142
- console.log(table.toString());
143
- }
144
- // ===== SECTION 5: PROJECT INSIGHTS =====
117
+ // ===== PROJECT INSIGHTS =====
145
118
  console.log(chalk_1.default.cyan.bold('\n🧠 PROJECT INSIGHTS'));
146
119
  const platformDeps = graph.edges.filter(edge => edge.to.startsWith('platform:')).length;
147
120
  const internalDeps = graph.edges.filter(edge => !edge.to.startsWith('platform:')).length;
148
- const userArtifactTables = [
149
- 'sys_db_object',
150
- 'sys_script_client',
151
- 'sys_script',
152
- 'sys_business_rule',
153
- 'sys_ui_action',
154
- 'sys_security_acl',
155
- 'sys_script_include'
156
- ];
157
121
  const userArtifacts = graph.nodes.filter(node => node.action !== 'DELETE' &&
158
122
  node.source.file &&
159
123
  !node.id.startsWith('platform:') &&
160
- userArtifactTables.includes(node.table));
124
+ !registry.isChild(node.table));
161
125
  console.log(` • Total User Artifacts: ${chalk_1.default.cyan(userArtifacts.length)}`);
162
126
  const countsByType = userArtifacts.reduce((acc, node) => {
163
- acc[node.table] = (acc[node.table] || 0) + 1;
127
+ const groupName = registry.getDisplayGroupName(node.table);
128
+ acc[groupName] = (acc[groupName] || 0) + 1;
164
129
  return acc;
165
130
  }, {});
166
131
  console.log(` • User Artifacts Breakdown: ${JSON.stringify(countsByType, null, 2)}`);
@@ -1,11 +1,13 @@
1
1
  import type { ArtifactEdge, ArtifactNode } from '../types/graphs';
2
+ import type { ArtifactTypeRegistry } from '../sdk/registry';
2
3
  /**
3
4
  * UNIVERSAL EDGE EXTRACTOR
4
5
  *
5
6
  * Strategy:
6
- * 1. For every record, scan ALL properties
7
- * 2. If a property looks like a reference (RecordId or string table name), create an edge
8
- * 3. No hardcoded artifact types
7
+ * 1. Property-based schema relationship detection (internal_type=reference)
8
+ * 2. Logic attachment via table/collection property
9
+ * 3. ACL name parsing — detected via registry plugin name
10
+ * 4. Universal RecordId scan across all properties
9
11
  */
10
- export declare function extractReferenceEdges(records: any, allNodes: ArtifactNode[]): ArtifactEdge[];
12
+ export declare function extractReferenceEdges(records: any, allNodes: ArtifactNode[], registry: ArtifactTypeRegistry): ArtifactEdge[];
11
13
  export declare function extractCompositionEdges(records: any): ArtifactEdge[];
@@ -6,31 +6,30 @@ exports.extractCompositionEdges = extractCompositionEdges;
6
6
  * UNIVERSAL EDGE EXTRACTOR
7
7
  *
8
8
  * Strategy:
9
- * 1. For every record, scan ALL properties
10
- * 2. If a property looks like a reference (RecordId or string table name), create an edge
11
- * 3. No hardcoded artifact types
9
+ * 1. Property-based schema relationship detection (internal_type=reference)
10
+ * 2. Logic attachment via table/collection property
11
+ * 3. ACL name parsing — detected via registry plugin name
12
+ * 4. Universal RecordId scan across all properties
12
13
  */
13
- function extractReferenceEdges(records, allNodes) {
14
+ function extractReferenceEdges(records, allNodes, registry) {
14
15
  const edges = [];
15
16
  for (const record of records.query()) {
16
17
  const fromId = record.getId()?.getValue()?.toString();
17
18
  const props = record.setProperties || {};
18
19
  const recordTable = record.getTable()?.toString();
19
20
  // === STRATEGY 1: SCHEMA RELATIONSHIPS ===
20
- // Extract reference fields from sys_dictionary records
21
- if (recordTable === 'sys_dictionary') {
22
- const internalType = props.internal_type?.getValue?.()?.toString();
23
- const refTable = props.reference?.getValue?.()?.toString();
24
- const elementName = props.element?.getValue?.()?.toString();
25
- if (internalType === 'reference' && refTable) {
26
- const targetNode = allNodes.find(n => n.name === refTable);
27
- edges.push({
28
- from: fromId,
29
- to: targetNode?.id || `platform:${refTable}`,
30
- via: elementName || 'reference',
31
- type: 'schema_relationship'
32
- });
33
- }
21
+ // Detect by properties: any record with internal_type=reference and a reference property
22
+ const internalType = props.internal_type?.getValue?.()?.toString();
23
+ const refTable = props.reference?.getValue?.()?.toString();
24
+ const elementName = props.element?.getValue?.()?.toString();
25
+ if (internalType === 'reference' && refTable) {
26
+ const targetNode = allNodes.find(n => n.name === refTable);
27
+ edges.push({
28
+ from: fromId,
29
+ to: targetNode?.id || `platform:${refTable}`,
30
+ via: elementName || 'reference',
31
+ type: 'schema_relationship'
32
+ });
34
33
  }
35
34
  // === STRATEGY 2: LOGIC ATTACHMENTS ===
36
35
  // Any artifact with 'table' or 'collection' property
@@ -43,12 +42,12 @@ function extractReferenceEdges(records, allNodes) {
43
42
  to: targetNode?.id || `platform:${tableRef}`,
44
43
  via: props.table ? 'table' : 'collection',
45
44
  type: 'dependency',
46
- targetTable: tableRef // Added: Set targetTable for consistency (fixes fallback in display for platform tables)
45
+ targetTable: tableRef
47
46
  });
48
47
  }
49
48
  // === STRATEGY 3: ACL NAME PARSING ===
50
- // sys_security_acl uses 'name' field like 'task.description'
51
- if (recordTable === 'sys_security_acl') {
49
+ // Detect ACL records by plugin name (registry-derived) rather than hardcoded table name
50
+ if (registry.getPluginName(recordTable) === 'AclPlugin') {
52
51
  const aclName = props.name?.getValue?.()?.toString();
53
52
  if (aclName && aclName.includes('.')) {
54
53
  const [targetTable] = aclName.split('.');
@@ -63,15 +62,17 @@ function extractReferenceEdges(records, allNodes) {
63
62
  }
64
63
  }
65
64
  // === STRATEGY 4: UNIVERSAL REFERENCE SCAN ===
66
- // Scan ALL properties for RecordId types (sys_id references)
65
+ // Scan ALL properties for RecordId and Record types (sys_id references).
66
+ // RecordId: getValue() = sys_id, getTable() = table name.
67
+ // Record: getId().getValue() = sys_id; table resolved via allNodes lookup.
68
+ // Used by SDK for cross-artifact references like flow_designer_flow on CatalogItem.
67
69
  for (const [key, val] of Object.entries(props)) {
68
- // Added: Handle arrays of RecordId (e.g., roles: [role1, role2]) to create multiple edges
69
70
  if (Array.isArray(val)) {
70
71
  val.forEach((item) => {
71
72
  if (item && item.constructor?.name === 'RecordId') {
72
73
  const targetId = item.getValue?.()?.toString();
73
74
  const targetTable = item.getTable?.()?.toString();
74
- if (targetId && targetTable !== 'sys_db_object') {
75
+ if (targetId && registry.getPluginName(targetTable) !== 'TablePlugin') {
75
76
  edges.push({
76
77
  from: fromId,
77
78
  to: targetId,
@@ -86,7 +87,7 @@ function extractReferenceEdges(records, allNodes) {
86
87
  else if (val && val.constructor?.name === 'RecordId') {
87
88
  const targetId = val.getValue?.()?.toString();
88
89
  const targetTable = val.getTable?.()?.toString();
89
- if (targetId && targetTable !== 'sys_db_object') {
90
+ if (targetId && registry.getPluginName(targetTable) !== 'TablePlugin') {
90
91
  edges.push({
91
92
  from: fromId,
92
93
  to: targetId,
@@ -96,6 +97,21 @@ function extractReferenceEdges(records, allNodes) {
96
97
  });
97
98
  }
98
99
  }
100
+ else if (val && val.constructor?.name === 'Record') {
101
+ const targetId = val.getId?.()?.getValue?.()?.toString();
102
+ if (targetId) {
103
+ const targetNode = allNodes.find(n => n.id === targetId);
104
+ if (targetNode && registry.getPluginName(targetNode.table) !== 'TablePlugin') {
105
+ edges.push({
106
+ from: fromId,
107
+ to: targetId,
108
+ via: key,
109
+ type: 'dependency',
110
+ targetTable: targetNode.table
111
+ });
112
+ }
113
+ }
114
+ }
99
115
  }
100
116
  }
101
117
  return edges;
@@ -1,2 +1,3 @@
1
1
  import type { LineageGraph } from '../types/graphs';
2
- export declare function extractLineageGraph(project: any): Promise<LineageGraph>;
2
+ import type { ArtifactTypeRegistry } from '../sdk/registry';
3
+ export declare function extractLineageGraph(project: any, registry: ArtifactTypeRegistry): Promise<LineageGraph>;
@@ -4,14 +4,14 @@ exports.extractLineageGraph = extractLineageGraph;
4
4
  const nodes_1 = require("./nodes");
5
5
  const edges_1 = require("./edges");
6
6
  const metadata_1 = require("./metadata");
7
- async function extractLineageGraph(project) {
7
+ async function extractLineageGraph(project, registry) {
8
8
  // Get raw records from the ServiceNow SDK Project
9
9
  const records = await project.getRecords();
10
10
  const rootDir = project.getRootDir();
11
11
  // 1. Extract Nodes (Includes dynamic attributes like order, when, active)
12
12
  const nodes = (0, nodes_1.extractNodes)(records, rootDir);
13
13
  // 2. Extract Edges (Requires node list for implicit string-to-table resolution)
14
- const referenceEdges = (0, edges_1.extractReferenceEdges)(records, nodes);
14
+ const referenceEdges = (0, edges_1.extractReferenceEdges)(records, nodes, registry);
15
15
  const compositionEdges = (0, edges_1.extractCompositionEdges)(records);
16
16
  const edges = [...referenceEdges, ...compositionEdges];
17
17
  // 3. Generate Summary Metadata
@@ -1,6 +1,7 @@
1
+ import { ArtifactTypeRegistry } from '../sdk/registry';
1
2
  import type { LineageGraph } from '../types/graphs';
2
3
  /**
3
4
  * The Orchestrator: Converts raw SDK records into a formal Directed Graph.
4
5
  * It treats every artifact as a node and every relationship as an iterable edge.
5
6
  */
6
- export declare function extractLineageGraph(project: any): Promise<LineageGraph>;
7
+ export declare function extractLineageGraph(project: any, registry?: ArtifactTypeRegistry): Promise<LineageGraph>;
@@ -4,13 +4,15 @@ exports.extractLineageGraph = extractLineageGraph;
4
4
  const nodes_1 = require("../extractor/nodes");
5
5
  const edges_1 = require("../extractor/edges");
6
6
  const metadata_1 = require("../extractor/metadata");
7
+ const registry_1 = require("../sdk/registry");
7
8
  /**
8
9
  * The Orchestrator: Converts raw SDK records into a formal Directed Graph.
9
10
  * It treats every artifact as a node and every relationship as an iterable edge.
10
11
  */
11
- async function extractLineageGraph(project) {
12
+ async function extractLineageGraph(project, registry) {
12
13
  const records = project.getRecords();
13
14
  const projectRoot = project.getRootDir();
15
+ const reg = registry ?? new registry_1.ArtifactTypeRegistry();
14
16
  // 1. PHASE ONE: Vertex Extraction (Nodes)
15
17
  // We only extract the "winning" records to avoid conflict noise.
16
18
  // This creates the list of local artifacts.
@@ -20,7 +22,7 @@ async function extractLineageGraph(project) {
20
22
  // This allows the edge extractor to differentiate between:
21
23
  // a) A reference to a local table (e.g. x_1566039_seventh_account)
22
24
  // b) A reference to a platform table (e.g. platform:incident)
23
- const refEdges = (0, edges_1.extractReferenceEdges)(records, nodes);
25
+ const refEdges = (0, edges_1.extractReferenceEdges)(records, nodes, reg);
24
26
  const compEdges = (0, edges_1.extractCompositionEdges)(records);
25
27
  const allEdges = [...refEdges, ...compEdges];
26
28
  // 3. PHASE THREE: Metadata & Context
@@ -1,5 +1,7 @@
1
+ import { ArtifactTypeRegistry } from './registry';
1
2
  export interface WorkspaceSDK {
2
3
  buildCore: any;
3
4
  sdkapi: any;
5
+ registry: ArtifactTypeRegistry;
4
6
  }
5
7
  export declare function loadWorkspaceSDK(sdkPath: string): WorkspaceSDK;
@@ -6,10 +6,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.loadWorkspaceSDK = loadWorkspaceSDK;
7
7
  const module_1 = require("module");
8
8
  const path_1 = __importDefault(require("path"));
9
+ const registry_1 = require("./registry");
9
10
  function loadWorkspaceSDK(sdkPath) {
10
11
  const requireFromWorkspace = (0, module_1.createRequire)(path_1.default.join(sdkPath, 'package.json'));
12
+ const registry = new registry_1.ArtifactTypeRegistry();
13
+ registry.load(sdkPath);
11
14
  return {
12
15
  buildCore: requireFromWorkspace('@servicenow/sdk-build-core'),
13
16
  sdkapi: requireFromWorkspace('@servicenow/sdk-api'),
17
+ registry,
14
18
  };
15
19
  }
@@ -0,0 +1,30 @@
1
+ interface TableInfo {
2
+ table: string;
3
+ pluginName: string;
4
+ isChild: boolean;
5
+ parentTable?: string;
6
+ parentVia?: string;
7
+ children: string[];
8
+ }
9
+ export declare class ArtifactTypeRegistry {
10
+ private tables;
11
+ load(sdkPath: string): void;
12
+ private register;
13
+ /**
14
+ * Parse the top-level relationships map:
15
+ * { parentTable: { childTable: { via, descendant, relationships: { grandchild: ... } } } }
16
+ */
17
+ private parseRelationships;
18
+ private processChildren;
19
+ get(table: string): TableInfo | undefined;
20
+ getPluginName(table: string): string;
21
+ getDisplayGroupName(table: string): string;
22
+ isChild(table: string): boolean;
23
+ getParentTable(table: string): string | undefined;
24
+ getChildren(table: string): string[];
25
+ getTablesByPlugin(pluginName: string): string[];
26
+ getAllPluginNames(): string[];
27
+ getAllTables(): string[];
28
+ isKnown(table: string): boolean;
29
+ }
30
+ export {};
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ArtifactTypeRegistry = void 0;
7
+ const module_1 = require("module");
8
+ const path_1 = __importDefault(require("path"));
9
+ class ArtifactTypeRegistry {
10
+ constructor() {
11
+ this.tables = new Map();
12
+ }
13
+ load(sdkPath) {
14
+ try {
15
+ const requireFromWorkspace = (0, module_1.createRequire)(path_1.default.join(sdkPath, 'package.json'));
16
+ const buildPlugins = requireFromWorkspace('@servicenow/sdk-build-plugins');
17
+ for (const [, value] of Object.entries(buildPlugins)) {
18
+ const plugin = value;
19
+ if (!plugin?.config?.name)
20
+ continue;
21
+ const pluginName = plugin.config.name;
22
+ const tables = Object.keys(plugin.config.records || {}).filter(k => k !== '*');
23
+ if (tables.length === 0)
24
+ continue;
25
+ for (const table of tables) {
26
+ this.register(table, pluginName);
27
+ }
28
+ try {
29
+ const rels = plugin.getRelationships();
30
+ if (rels && typeof rels === 'object') {
31
+ this.parseRelationships(rels, pluginName);
32
+ }
33
+ }
34
+ catch {
35
+ // safe to skip — needs compiler context
36
+ }
37
+ }
38
+ }
39
+ catch {
40
+ // sdk-build-plugins unavailable; registry stays empty, fallback behavior applies
41
+ }
42
+ }
43
+ register(table, pluginName) {
44
+ if (!this.tables.has(table)) {
45
+ this.tables.set(table, { table, pluginName, isChild: false, children: [] });
46
+ }
47
+ }
48
+ /**
49
+ * Parse the top-level relationships map:
50
+ * { parentTable: { childTable: { via, descendant, relationships: { grandchild: ... } } } }
51
+ */
52
+ parseRelationships(rels, pluginName) {
53
+ for (const [parentTable, childDefs] of Object.entries(rels)) {
54
+ this.register(parentTable, pluginName);
55
+ if (childDefs && typeof childDefs === 'object') {
56
+ this.processChildren(childDefs, pluginName, parentTable);
57
+ }
58
+ }
59
+ }
60
+ processChildren(childDefs, pluginName, parentTable) {
61
+ for (const [key, def] of Object.entries(childDefs)) {
62
+ // Skip meta-keys and self-referential entries
63
+ if (key === 'via' || key === 'descendant' || key === 'relationships')
64
+ continue;
65
+ if (key === parentTable)
66
+ continue;
67
+ const childTable = key;
68
+ this.register(childTable, pluginName);
69
+ const entry = this.tables.get(childTable);
70
+ if (!entry.isChild) {
71
+ entry.isChild = true;
72
+ entry.parentTable = parentTable;
73
+ if (def && typeof def === 'object' && typeof def.via === 'string') {
74
+ entry.parentVia = def.via;
75
+ }
76
+ }
77
+ // Add to parent's children list
78
+ const parentEntry = this.tables.get(parentTable);
79
+ if (parentEntry && !parentEntry.children.includes(childTable)) {
80
+ parentEntry.children.push(childTable);
81
+ }
82
+ // Recurse into nested relationships
83
+ if (def && typeof def === 'object' && def.relationships && typeof def.relationships === 'object') {
84
+ this.processChildren(def.relationships, pluginName, childTable);
85
+ }
86
+ }
87
+ }
88
+ get(table) {
89
+ return this.tables.get(table);
90
+ }
91
+ getPluginName(table) {
92
+ return this.tables.get(table)?.pluginName ?? 'Record';
93
+ }
94
+ getDisplayGroupName(table) {
95
+ const entry = this.tables.get(table);
96
+ if (!entry)
97
+ return 'Other';
98
+ return entry.pluginName
99
+ .replace(/Plugin$/, '')
100
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
101
+ .replace(/^./, c => c.toUpperCase())
102
+ .trim();
103
+ }
104
+ isChild(table) {
105
+ return this.tables.get(table)?.isChild ?? false;
106
+ }
107
+ getParentTable(table) {
108
+ return this.tables.get(table)?.parentTable;
109
+ }
110
+ getChildren(table) {
111
+ return this.tables.get(table)?.children ?? [];
112
+ }
113
+ getTablesByPlugin(pluginName) {
114
+ return [...this.tables.values()]
115
+ .filter(t => t.pluginName === pluginName)
116
+ .map(t => t.table);
117
+ }
118
+ getAllPluginNames() {
119
+ return [...new Set([...this.tables.values()].map(t => t.pluginName))].sort();
120
+ }
121
+ getAllTables() {
122
+ return [...this.tables.keys()];
123
+ }
124
+ isKnown(table) {
125
+ return this.tables.has(table);
126
+ }
127
+ }
128
+ exports.ArtifactTypeRegistry = ArtifactTypeRegistry;