@memberjunction/metadata-sync 2.83.0 → 2.85.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.
- package/dist/lib/record-dependency-analyzer.d.ts +77 -0
- package/dist/lib/record-dependency-analyzer.js +398 -0
- package/dist/lib/record-dependency-analyzer.js.map +1 -0
- package/dist/lib/sync-engine.d.ts +4 -77
- package/dist/lib/sync-engine.js +41 -171
- package/dist/lib/sync-engine.js.map +1 -1
- package/dist/services/PushService.d.ts +2 -17
- package/dist/services/PushService.js +100 -443
- package/dist/services/PushService.js.map +1 -1
- package/package.json +7 -7
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { RecordData } from './sync-engine';
|
|
2
|
+
/**
|
|
3
|
+
* Represents a flattened record with its context and dependencies
|
|
4
|
+
*/
|
|
5
|
+
export interface FlattenedRecord {
|
|
6
|
+
record: RecordData;
|
|
7
|
+
entityName: string;
|
|
8
|
+
parentContext?: {
|
|
9
|
+
entityName: string;
|
|
10
|
+
record: RecordData;
|
|
11
|
+
recordIndex: number;
|
|
12
|
+
};
|
|
13
|
+
depth: number;
|
|
14
|
+
path: string;
|
|
15
|
+
dependencies: Set<string>;
|
|
16
|
+
id: string;
|
|
17
|
+
originalIndex: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Result of dependency analysis
|
|
21
|
+
*/
|
|
22
|
+
export interface DependencyAnalysisResult {
|
|
23
|
+
sortedRecords: FlattenedRecord[];
|
|
24
|
+
circularDependencies: string[][];
|
|
25
|
+
dependencyGraph: Map<string, Set<string>>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Analyzes and sorts records based on their dependencies
|
|
29
|
+
*/
|
|
30
|
+
export declare class RecordDependencyAnalyzer {
|
|
31
|
+
private metadata;
|
|
32
|
+
private flattenedRecords;
|
|
33
|
+
private recordIdMap;
|
|
34
|
+
private entityInfoCache;
|
|
35
|
+
private recordCounter;
|
|
36
|
+
constructor();
|
|
37
|
+
/**
|
|
38
|
+
* Main entry point: analyzes all records in a file and returns them in dependency order
|
|
39
|
+
*/
|
|
40
|
+
analyzeFileRecords(records: RecordData[], entityName: string): Promise<DependencyAnalysisResult>;
|
|
41
|
+
/**
|
|
42
|
+
* Flattens all records including nested relatedEntities
|
|
43
|
+
*/
|
|
44
|
+
private flattenRecords;
|
|
45
|
+
/**
|
|
46
|
+
* Analyzes dependencies between all flattened records
|
|
47
|
+
*/
|
|
48
|
+
private analyzeDependencies;
|
|
49
|
+
/**
|
|
50
|
+
* Analyzes foreign key dependencies based on EntityInfo
|
|
51
|
+
*/
|
|
52
|
+
private analyzeForeignKeyDependencies;
|
|
53
|
+
/**
|
|
54
|
+
* Finds a record that matches a @lookup reference
|
|
55
|
+
*/
|
|
56
|
+
private findLookupDependency;
|
|
57
|
+
/**
|
|
58
|
+
* Finds the root record for a given record
|
|
59
|
+
*/
|
|
60
|
+
private findRootDependency;
|
|
61
|
+
/**
|
|
62
|
+
* Finds a record by its primary key value
|
|
63
|
+
*/
|
|
64
|
+
private findRecordByPrimaryKey;
|
|
65
|
+
/**
|
|
66
|
+
* Gets EntityInfo from cache or metadata
|
|
67
|
+
*/
|
|
68
|
+
private getEntityInfo;
|
|
69
|
+
/**
|
|
70
|
+
* Detects circular dependencies in the dependency graph
|
|
71
|
+
*/
|
|
72
|
+
private detectCircularDependencies;
|
|
73
|
+
/**
|
|
74
|
+
* Performs topological sort on the dependency graph
|
|
75
|
+
*/
|
|
76
|
+
private topologicalSort;
|
|
77
|
+
}
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RecordDependencyAnalyzer = void 0;
|
|
4
|
+
const core_1 = require("@memberjunction/core");
|
|
5
|
+
/**
|
|
6
|
+
* Analyzes and sorts records based on their dependencies
|
|
7
|
+
*/
|
|
8
|
+
class RecordDependencyAnalyzer {
|
|
9
|
+
metadata;
|
|
10
|
+
flattenedRecords = [];
|
|
11
|
+
recordIdMap = new Map();
|
|
12
|
+
entityInfoCache = new Map();
|
|
13
|
+
recordCounter = 0;
|
|
14
|
+
constructor() {
|
|
15
|
+
this.metadata = new core_1.Metadata();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Main entry point: analyzes all records in a file and returns them in dependency order
|
|
19
|
+
*/
|
|
20
|
+
async analyzeFileRecords(records, entityName) {
|
|
21
|
+
// Reset state
|
|
22
|
+
this.flattenedRecords = [];
|
|
23
|
+
this.recordIdMap.clear();
|
|
24
|
+
this.recordCounter = 0;
|
|
25
|
+
// Step 1: Flatten all records (including nested relatedEntities)
|
|
26
|
+
this.flattenRecords(records, entityName);
|
|
27
|
+
// Step 2: Analyze dependencies between all flattened records
|
|
28
|
+
this.analyzeDependencies();
|
|
29
|
+
// Step 3: Detect circular dependencies
|
|
30
|
+
const circularDeps = this.detectCircularDependencies();
|
|
31
|
+
// Step 4: Perform topological sort
|
|
32
|
+
const sortedRecords = this.topologicalSort();
|
|
33
|
+
// Step 5: Build dependency graph for debugging
|
|
34
|
+
const dependencyGraph = new Map();
|
|
35
|
+
for (const record of this.flattenedRecords) {
|
|
36
|
+
dependencyGraph.set(record.id, record.dependencies);
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
sortedRecords,
|
|
40
|
+
circularDependencies: circularDeps,
|
|
41
|
+
dependencyGraph
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Flattens all records including nested relatedEntities
|
|
46
|
+
*/
|
|
47
|
+
flattenRecords(records, entityName, parentContext, depth = 0, pathPrefix = '', parentRecordId) {
|
|
48
|
+
for (let i = 0; i < records.length; i++) {
|
|
49
|
+
const record = records[i];
|
|
50
|
+
const recordId = `${entityName}_${this.recordCounter++}`;
|
|
51
|
+
const path = pathPrefix ? `${pathPrefix}/${entityName}[${i}]` : `${entityName}[${i}]`;
|
|
52
|
+
const flattenedRecord = {
|
|
53
|
+
record,
|
|
54
|
+
entityName,
|
|
55
|
+
parentContext,
|
|
56
|
+
depth,
|
|
57
|
+
path,
|
|
58
|
+
dependencies: new Set(),
|
|
59
|
+
id: recordId,
|
|
60
|
+
originalIndex: i
|
|
61
|
+
};
|
|
62
|
+
// If this has a parent, add dependency on the parent
|
|
63
|
+
if (parentRecordId) {
|
|
64
|
+
flattenedRecord.dependencies.add(parentRecordId);
|
|
65
|
+
}
|
|
66
|
+
this.flattenedRecords.push(flattenedRecord);
|
|
67
|
+
this.recordIdMap.set(recordId, flattenedRecord);
|
|
68
|
+
// Recursively flatten related entities
|
|
69
|
+
if (record.relatedEntities) {
|
|
70
|
+
for (const [relatedEntityName, relatedRecords] of Object.entries(record.relatedEntities)) {
|
|
71
|
+
this.flattenRecords(relatedRecords, relatedEntityName, {
|
|
72
|
+
entityName,
|
|
73
|
+
record,
|
|
74
|
+
recordIndex: i
|
|
75
|
+
}, depth + 1, path, recordId // Pass current record ID as parent for children
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Analyzes dependencies between all flattened records
|
|
83
|
+
*/
|
|
84
|
+
analyzeDependencies() {
|
|
85
|
+
for (const record of this.flattenedRecords) {
|
|
86
|
+
// Get entity info for foreign key relationships
|
|
87
|
+
const entityInfo = this.getEntityInfo(record.entityName);
|
|
88
|
+
if (!entityInfo)
|
|
89
|
+
continue;
|
|
90
|
+
// Analyze field dependencies
|
|
91
|
+
if (record.record.fields) {
|
|
92
|
+
for (const [fieldName, fieldValue] of Object.entries(record.record.fields)) {
|
|
93
|
+
if (typeof fieldValue === 'string') {
|
|
94
|
+
// Handle @lookup references
|
|
95
|
+
if (fieldValue.startsWith('@lookup:')) {
|
|
96
|
+
const dependency = this.findLookupDependency(fieldValue, record);
|
|
97
|
+
if (dependency) {
|
|
98
|
+
record.dependencies.add(dependency);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Handle @root references - these create dependencies on the root record
|
|
102
|
+
else if (fieldValue.startsWith('@root:')) {
|
|
103
|
+
const rootDependency = this.findRootDependency(record);
|
|
104
|
+
if (rootDependency) {
|
|
105
|
+
record.dependencies.add(rootDependency);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// @parent references don't create explicit dependencies in our flattened structure
|
|
109
|
+
// because parent is guaranteed to be processed before children due to the way
|
|
110
|
+
// we flatten records (parent always comes before its children)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Check foreign key dependencies
|
|
115
|
+
this.analyzeForeignKeyDependencies(record, entityInfo);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Analyzes foreign key dependencies based on EntityInfo
|
|
120
|
+
*/
|
|
121
|
+
analyzeForeignKeyDependencies(record, entityInfo) {
|
|
122
|
+
// Check all foreign key fields
|
|
123
|
+
for (const field of entityInfo.ForeignKeys) {
|
|
124
|
+
const fieldValue = record.record.fields?.[field.Name];
|
|
125
|
+
if (fieldValue && typeof fieldValue === 'string' && !fieldValue.startsWith('@')) {
|
|
126
|
+
// This is a direct foreign key value, find the referenced record
|
|
127
|
+
const relatedEntityInfo = this.getEntityInfo(field.RelatedEntity);
|
|
128
|
+
if (relatedEntityInfo) {
|
|
129
|
+
const dependency = this.findRecordByPrimaryKey(field.RelatedEntity, fieldValue, relatedEntityInfo);
|
|
130
|
+
if (dependency) {
|
|
131
|
+
record.dependencies.add(dependency);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Finds a record that matches a @lookup reference
|
|
139
|
+
*/
|
|
140
|
+
findLookupDependency(lookupValue, currentRecord) {
|
|
141
|
+
// Parse lookup format: @lookup:EntityName.Field=Value or @lookup:Field=Value
|
|
142
|
+
const lookupStr = lookupValue.substring(8); // Remove '@lookup:'
|
|
143
|
+
// Handle the ?create syntax by removing it
|
|
144
|
+
const cleanLookup = lookupStr.split('?')[0];
|
|
145
|
+
// Parse entity name if present
|
|
146
|
+
let targetEntity;
|
|
147
|
+
let criteria;
|
|
148
|
+
if (cleanLookup.includes('.')) {
|
|
149
|
+
const parts = cleanLookup.split('.');
|
|
150
|
+
targetEntity = parts[0];
|
|
151
|
+
criteria = parts.slice(1).join('.');
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
// Same entity if not specified
|
|
155
|
+
targetEntity = currentRecord.entityName;
|
|
156
|
+
criteria = cleanLookup;
|
|
157
|
+
}
|
|
158
|
+
// Parse criteria (can be multiple with &)
|
|
159
|
+
const criteriaMap = new Map();
|
|
160
|
+
for (const pair of criteria.split('&')) {
|
|
161
|
+
const [field, value] = pair.split('=');
|
|
162
|
+
if (field && value) {
|
|
163
|
+
let resolvedValue = value.trim();
|
|
164
|
+
// Special handling for nested @lookup references in lookup criteria
|
|
165
|
+
// This creates a dependency on the looked-up record
|
|
166
|
+
if (resolvedValue.startsWith('@lookup:')) {
|
|
167
|
+
const nestedDependency = this.findLookupDependency(resolvedValue, currentRecord);
|
|
168
|
+
if (nestedDependency) {
|
|
169
|
+
// Add this as a dependency of the current record
|
|
170
|
+
currentRecord.dependencies.add(nestedDependency);
|
|
171
|
+
// Continue processing - we can't resolve the actual value here
|
|
172
|
+
// but we've recorded the dependency
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// Special handling for @root references in lookup criteria
|
|
176
|
+
else if (resolvedValue.startsWith('@root:')) {
|
|
177
|
+
// Add dependency on root record
|
|
178
|
+
const rootDep = this.findRootDependency(currentRecord);
|
|
179
|
+
if (rootDep) {
|
|
180
|
+
currentRecord.dependencies.add(rootDep);
|
|
181
|
+
}
|
|
182
|
+
// Note: We can't resolve the actual value here, but we've recorded the dependency
|
|
183
|
+
}
|
|
184
|
+
// Special handling for @parent references in lookup criteria
|
|
185
|
+
// If the value is @parent:field, we need to resolve it from the parent context
|
|
186
|
+
else if (resolvedValue.startsWith('@parent:') && currentRecord.parentContext) {
|
|
187
|
+
const parentField = resolvedValue.substring(8);
|
|
188
|
+
// Try to resolve from parent context
|
|
189
|
+
const parentValue = currentRecord.parentContext.record.fields?.[parentField] ||
|
|
190
|
+
currentRecord.parentContext.record.primaryKey?.[parentField];
|
|
191
|
+
if (parentValue && typeof parentValue === 'string') {
|
|
192
|
+
// Check if parent value is also a @parent reference (nested parent refs)
|
|
193
|
+
if (parentValue.startsWith('@parent:')) {
|
|
194
|
+
// Find the parent record to get its parent context
|
|
195
|
+
const parentRecord = this.flattenedRecords.find(r => r.record === currentRecord.parentContext.record &&
|
|
196
|
+
r.entityName === currentRecord.parentContext.entityName);
|
|
197
|
+
if (parentRecord && parentRecord.parentContext) {
|
|
198
|
+
const grandParentField = parentValue.substring(8);
|
|
199
|
+
const grandParentValue = parentRecord.parentContext.record.fields?.[grandParentField] ||
|
|
200
|
+
parentRecord.parentContext.record.primaryKey?.[grandParentField];
|
|
201
|
+
if (grandParentValue && typeof grandParentValue === 'string' && !grandParentValue.startsWith('@')) {
|
|
202
|
+
resolvedValue = grandParentValue;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
else if (!parentValue.startsWith('@')) {
|
|
207
|
+
resolvedValue = parentValue;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
criteriaMap.set(field.trim(), resolvedValue);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// Find matching record in our flattened list
|
|
215
|
+
for (const candidate of this.flattenedRecords) {
|
|
216
|
+
if (candidate.entityName !== targetEntity)
|
|
217
|
+
continue;
|
|
218
|
+
if (candidate.id === currentRecord.id)
|
|
219
|
+
continue; // Skip self
|
|
220
|
+
// Check if all criteria match
|
|
221
|
+
let allMatch = true;
|
|
222
|
+
for (const [field, value] of criteriaMap) {
|
|
223
|
+
let candidateValue = candidate.record.fields?.[field] ||
|
|
224
|
+
candidate.record.primaryKey?.[field];
|
|
225
|
+
let lookupValue = value;
|
|
226
|
+
// Handle special case where candidate has @parent:ID and lookup has resolved value
|
|
227
|
+
if (candidateValue === '@parent:ID' && candidate.parentContext && !lookupValue.startsWith('@')) {
|
|
228
|
+
// Get the parent record's ID
|
|
229
|
+
const parentRecord = candidate.parentContext.record;
|
|
230
|
+
candidateValue = parentRecord.primaryKey?.ID || parentRecord.fields?.ID;
|
|
231
|
+
// If parent ID is not yet set (new record), we can't match yet
|
|
232
|
+
if (!candidateValue) {
|
|
233
|
+
allMatch = false;
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// Also check if @parent:<field> syntax
|
|
238
|
+
if (typeof candidateValue === 'string' && candidateValue.startsWith('@parent:') && candidate.parentContext) {
|
|
239
|
+
const parentField = candidateValue.substring(8);
|
|
240
|
+
const parentRecord = candidate.parentContext.record;
|
|
241
|
+
candidateValue = parentRecord.fields?.[parentField] || parentRecord.primaryKey?.[parentField];
|
|
242
|
+
}
|
|
243
|
+
if (candidateValue !== lookupValue) {
|
|
244
|
+
allMatch = false;
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (allMatch) {
|
|
249
|
+
return candidate.id;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Finds the root record for a given record
|
|
256
|
+
*/
|
|
257
|
+
findRootDependency(record) {
|
|
258
|
+
// If this record has no parent, it IS the root, no dependency
|
|
259
|
+
if (!record.parentContext) {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
// Walk up the parent chain to find the root
|
|
263
|
+
let current = record;
|
|
264
|
+
while (current.parentContext) {
|
|
265
|
+
// Try to find the parent record in our flattened list
|
|
266
|
+
const parentRecord = this.flattenedRecords.find(r => r.record === current.parentContext.record &&
|
|
267
|
+
r.entityName === current.parentContext.entityName);
|
|
268
|
+
if (!parentRecord) {
|
|
269
|
+
// Parent not found, something is wrong
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
// If this parent has no parent, it's the root
|
|
273
|
+
if (!parentRecord.parentContext) {
|
|
274
|
+
return parentRecord.id;
|
|
275
|
+
}
|
|
276
|
+
current = parentRecord;
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Finds a record by its primary key value
|
|
282
|
+
*/
|
|
283
|
+
findRecordByPrimaryKey(entityName, primaryKeyValue, entityInfo) {
|
|
284
|
+
// Get primary key field name
|
|
285
|
+
const primaryKeyField = entityInfo.PrimaryKeys[0]?.Name;
|
|
286
|
+
if (!primaryKeyField)
|
|
287
|
+
return null;
|
|
288
|
+
for (const candidate of this.flattenedRecords) {
|
|
289
|
+
if (candidate.entityName !== entityName)
|
|
290
|
+
continue;
|
|
291
|
+
const candidateValue = candidate.record.primaryKey?.[primaryKeyField] ||
|
|
292
|
+
candidate.record.fields?.[primaryKeyField];
|
|
293
|
+
if (candidateValue === primaryKeyValue) {
|
|
294
|
+
return candidate.id;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Gets EntityInfo from cache or metadata
|
|
301
|
+
*/
|
|
302
|
+
getEntityInfo(entityName) {
|
|
303
|
+
if (!this.entityInfoCache.has(entityName)) {
|
|
304
|
+
const info = this.metadata.EntityByName(entityName);
|
|
305
|
+
if (info) {
|
|
306
|
+
this.entityInfoCache.set(entityName, info);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return this.entityInfoCache.get(entityName) || null;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Detects circular dependencies in the dependency graph
|
|
313
|
+
*/
|
|
314
|
+
detectCircularDependencies() {
|
|
315
|
+
const cycles = [];
|
|
316
|
+
const visited = new Set();
|
|
317
|
+
const recursionStack = new Set();
|
|
318
|
+
const detectCycle = (recordId, path) => {
|
|
319
|
+
visited.add(recordId);
|
|
320
|
+
recursionStack.add(recordId);
|
|
321
|
+
path.push(recordId);
|
|
322
|
+
const record = this.recordIdMap.get(recordId);
|
|
323
|
+
if (record) {
|
|
324
|
+
for (const depId of record.dependencies) {
|
|
325
|
+
if (!visited.has(depId)) {
|
|
326
|
+
if (detectCycle(depId, [...path])) {
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
else if (recursionStack.has(depId)) {
|
|
331
|
+
// Found a cycle
|
|
332
|
+
const cycleStart = path.indexOf(depId);
|
|
333
|
+
const cycle = path.slice(cycleStart);
|
|
334
|
+
cycle.push(depId); // Complete the cycle
|
|
335
|
+
cycles.push(cycle);
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
recursionStack.delete(recordId);
|
|
341
|
+
return false;
|
|
342
|
+
};
|
|
343
|
+
// Check all records for cycles
|
|
344
|
+
for (const record of this.flattenedRecords) {
|
|
345
|
+
if (!visited.has(record.id)) {
|
|
346
|
+
detectCycle(record.id, []);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return cycles;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Performs topological sort on the dependency graph
|
|
353
|
+
*/
|
|
354
|
+
topologicalSort() {
|
|
355
|
+
const result = [];
|
|
356
|
+
const visited = new Set();
|
|
357
|
+
const tempStack = new Set();
|
|
358
|
+
const visit = (recordId) => {
|
|
359
|
+
if (tempStack.has(recordId)) {
|
|
360
|
+
// Circular dependency - we've already detected these
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
if (visited.has(recordId)) {
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
tempStack.add(recordId);
|
|
367
|
+
const record = this.recordIdMap.get(recordId);
|
|
368
|
+
if (record) {
|
|
369
|
+
// Visit dependencies first
|
|
370
|
+
for (const depId of record.dependencies) {
|
|
371
|
+
visit(depId);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
tempStack.delete(recordId);
|
|
375
|
+
visited.add(recordId);
|
|
376
|
+
if (record) {
|
|
377
|
+
result.push(record);
|
|
378
|
+
}
|
|
379
|
+
return true;
|
|
380
|
+
};
|
|
381
|
+
// Process all records, starting with those that have no dependencies
|
|
382
|
+
// First, process records with no dependencies
|
|
383
|
+
for (const record of this.flattenedRecords) {
|
|
384
|
+
if (record.dependencies.size === 0 && !visited.has(record.id)) {
|
|
385
|
+
visit(record.id);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// Then process any remaining records (handles disconnected components)
|
|
389
|
+
for (const record of this.flattenedRecords) {
|
|
390
|
+
if (!visited.has(record.id)) {
|
|
391
|
+
visit(record.id);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return result;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
exports.RecordDependencyAnalyzer = RecordDependencyAnalyzer;
|
|
398
|
+
//# sourceMappingURL=record-dependency-analyzer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record-dependency-analyzer.js","sourceRoot":"","sources":["../../src/lib/record-dependency-analyzer.ts"],"names":[],"mappings":"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","sourcesContent":["import { Metadata, EntityInfo } from '@memberjunction/core';\nimport { RecordData } from './sync-engine';\n\n/**\n * Represents a flattened record with its context and dependencies\n */\nexport interface FlattenedRecord {\n record: RecordData;\n entityName: string;\n parentContext?: {\n entityName: string;\n record: RecordData;\n recordIndex: number;\n };\n depth: number;\n path: string; // Path to this record for debugging\n dependencies: Set<string>; // Set of record IDs this record depends on\n id: string; // Unique identifier for this record in the flattened list\n originalIndex: number; // Original index in the source array\n}\n\n/**\n * Result of dependency analysis\n */\nexport interface DependencyAnalysisResult {\n sortedRecords: FlattenedRecord[];\n circularDependencies: string[][];\n dependencyGraph: Map<string, Set<string>>;\n}\n\n/**\n * Analyzes and sorts records based on their dependencies\n */\nexport class RecordDependencyAnalyzer {\n private metadata: Metadata;\n private flattenedRecords: FlattenedRecord[] = [];\n private recordIdMap: Map<string, FlattenedRecord> = new Map();\n private entityInfoCache: Map<string, EntityInfo> = new Map();\n private recordCounter: number = 0;\n\n constructor() {\n this.metadata = new Metadata();\n }\n\n /**\n * Main entry point: analyzes all records in a file and returns them in dependency order\n */\n public async analyzeFileRecords(\n records: RecordData[],\n entityName: string\n ): Promise<DependencyAnalysisResult> {\n // Reset state\n this.flattenedRecords = [];\n this.recordIdMap.clear();\n this.recordCounter = 0;\n\n // Step 1: Flatten all records (including nested relatedEntities)\n this.flattenRecords(records, entityName);\n\n // Step 2: Analyze dependencies between all flattened records\n this.analyzeDependencies();\n\n // Step 3: Detect circular dependencies\n const circularDeps = this.detectCircularDependencies();\n\n // Step 4: Perform topological sort\n const sortedRecords = this.topologicalSort();\n\n // Step 5: Build dependency graph for debugging\n const dependencyGraph = new Map<string, Set<string>>();\n for (const record of this.flattenedRecords) {\n dependencyGraph.set(record.id, record.dependencies);\n }\n\n return {\n sortedRecords,\n circularDependencies: circularDeps,\n dependencyGraph\n };\n }\n\n /**\n * Flattens all records including nested relatedEntities\n */\n private flattenRecords(\n records: RecordData[],\n entityName: string,\n parentContext?: FlattenedRecord['parentContext'],\n depth: number = 0,\n pathPrefix: string = '',\n parentRecordId?: string\n ): void {\n for (let i = 0; i < records.length; i++) {\n const record = records[i];\n const recordId = `${entityName}_${this.recordCounter++}`;\n const path = pathPrefix ? `${pathPrefix}/${entityName}[${i}]` : `${entityName}[${i}]`;\n\n const flattenedRecord: FlattenedRecord = {\n record,\n entityName,\n parentContext,\n depth,\n path,\n dependencies: new Set(),\n id: recordId,\n originalIndex: i\n };\n\n // If this has a parent, add dependency on the parent\n if (parentRecordId) {\n flattenedRecord.dependencies.add(parentRecordId);\n }\n\n this.flattenedRecords.push(flattenedRecord);\n this.recordIdMap.set(recordId, flattenedRecord);\n\n // Recursively flatten related entities\n if (record.relatedEntities) {\n for (const [relatedEntityName, relatedRecords] of Object.entries(record.relatedEntities)) {\n this.flattenRecords(\n relatedRecords,\n relatedEntityName,\n {\n entityName,\n record,\n recordIndex: i\n },\n depth + 1,\n path,\n recordId // Pass current record ID as parent for children\n );\n }\n }\n }\n }\n\n /**\n * Analyzes dependencies between all flattened records\n */\n private analyzeDependencies(): void {\n for (const record of this.flattenedRecords) {\n // Get entity info for foreign key relationships\n const entityInfo = this.getEntityInfo(record.entityName);\n if (!entityInfo) continue;\n\n // Analyze field dependencies\n if (record.record.fields) {\n for (const [fieldName, fieldValue] of Object.entries(record.record.fields)) {\n if (typeof fieldValue === 'string') {\n // Handle @lookup references\n if (fieldValue.startsWith('@lookup:')) {\n const dependency = this.findLookupDependency(fieldValue, record);\n if (dependency) {\n record.dependencies.add(dependency);\n }\n }\n // Handle @root references - these create dependencies on the root record\n else if (fieldValue.startsWith('@root:')) {\n const rootDependency = this.findRootDependency(record);\n if (rootDependency) {\n record.dependencies.add(rootDependency);\n }\n }\n // @parent references don't create explicit dependencies in our flattened structure\n // because parent is guaranteed to be processed before children due to the way\n // we flatten records (parent always comes before its children)\n }\n }\n }\n\n // Check foreign key dependencies\n this.analyzeForeignKeyDependencies(record, entityInfo);\n }\n }\n\n /**\n * Analyzes foreign key dependencies based on EntityInfo\n */\n private analyzeForeignKeyDependencies(record: FlattenedRecord, entityInfo: EntityInfo): void {\n // Check all foreign key fields\n for (const field of entityInfo.ForeignKeys) {\n const fieldValue = record.record.fields?.[field.Name];\n if (fieldValue && typeof fieldValue === 'string' && !fieldValue.startsWith('@')) {\n // This is a direct foreign key value, find the referenced record\n const relatedEntityInfo = this.getEntityInfo(field.RelatedEntity);\n if (relatedEntityInfo) {\n const dependency = this.findRecordByPrimaryKey(\n field.RelatedEntity,\n fieldValue,\n relatedEntityInfo\n );\n if (dependency) {\n record.dependencies.add(dependency);\n }\n }\n }\n }\n }\n\n /**\n * Finds a record that matches a @lookup reference\n */\n private findLookupDependency(lookupValue: string, currentRecord: FlattenedRecord): string | null {\n // Parse lookup format: @lookup:EntityName.Field=Value or @lookup:Field=Value\n const lookupStr = lookupValue.substring(8); // Remove '@lookup:'\n \n // Handle the ?create syntax by removing it\n const cleanLookup = lookupStr.split('?')[0];\n \n // Parse entity name if present\n let targetEntity: string;\n let criteria: string;\n \n if (cleanLookup.includes('.')) {\n const parts = cleanLookup.split('.');\n targetEntity = parts[0];\n criteria = parts.slice(1).join('.');\n } else {\n // Same entity if not specified\n targetEntity = currentRecord.entityName;\n criteria = cleanLookup;\n }\n\n // Parse criteria (can be multiple with &)\n const criteriaMap = new Map<string, string>();\n for (const pair of criteria.split('&')) {\n const [field, value] = pair.split('=');\n if (field && value) {\n let resolvedValue = value.trim();\n \n // Special handling for nested @lookup references in lookup criteria\n // This creates a dependency on the looked-up record\n if (resolvedValue.startsWith('@lookup:')) {\n const nestedDependency = this.findLookupDependency(resolvedValue, currentRecord);\n if (nestedDependency) {\n // Add this as a dependency of the current record\n currentRecord.dependencies.add(nestedDependency);\n // Continue processing - we can't resolve the actual value here\n // but we've recorded the dependency\n }\n }\n // Special handling for @root references in lookup criteria\n else if (resolvedValue.startsWith('@root:')) {\n // Add dependency on root record\n const rootDep = this.findRootDependency(currentRecord);\n if (rootDep) {\n currentRecord.dependencies.add(rootDep);\n }\n // Note: We can't resolve the actual value here, but we've recorded the dependency\n }\n // Special handling for @parent references in lookup criteria\n // If the value is @parent:field, we need to resolve it from the parent context\n else if (resolvedValue.startsWith('@parent:') && currentRecord.parentContext) {\n const parentField = resolvedValue.substring(8);\n \n // Try to resolve from parent context\n const parentValue = currentRecord.parentContext.record.fields?.[parentField] ||\n currentRecord.parentContext.record.primaryKey?.[parentField];\n \n if (parentValue && typeof parentValue === 'string') {\n // Check if parent value is also a @parent reference (nested parent refs)\n if (parentValue.startsWith('@parent:')) {\n // Find the parent record to get its parent context\n const parentRecord = this.flattenedRecords.find(r => \n r.record === currentRecord.parentContext!.record && \n r.entityName === currentRecord.parentContext!.entityName\n );\n \n if (parentRecord && parentRecord.parentContext) {\n const grandParentField = parentValue.substring(8);\n const grandParentValue = parentRecord.parentContext.record.fields?.[grandParentField] ||\n parentRecord.parentContext.record.primaryKey?.[grandParentField];\n if (grandParentValue && typeof grandParentValue === 'string' && !grandParentValue.startsWith('@')) {\n resolvedValue = grandParentValue;\n }\n }\n } else if (!parentValue.startsWith('@')) {\n resolvedValue = parentValue;\n }\n }\n }\n \n criteriaMap.set(field.trim(), resolvedValue);\n }\n }\n\n // Find matching record in our flattened list\n for (const candidate of this.flattenedRecords) {\n if (candidate.entityName !== targetEntity) continue;\n if (candidate.id === currentRecord.id) continue; // Skip self\n\n // Check if all criteria match\n let allMatch = true;\n for (const [field, value] of criteriaMap) {\n let candidateValue = candidate.record.fields?.[field] || \n candidate.record.primaryKey?.[field];\n let lookupValue = value;\n \n // Handle special case where candidate has @parent:ID and lookup has resolved value\n if (candidateValue === '@parent:ID' && candidate.parentContext && !lookupValue.startsWith('@')) {\n // Get the parent record's ID\n const parentRecord = candidate.parentContext.record;\n candidateValue = parentRecord.primaryKey?.ID || parentRecord.fields?.ID;\n \n // If parent ID is not yet set (new record), we can't match yet\n if (!candidateValue) {\n allMatch = false;\n break;\n }\n }\n \n // Also check if @parent:<field> syntax\n if (typeof candidateValue === 'string' && candidateValue.startsWith('@parent:') && candidate.parentContext) {\n const parentField = candidateValue.substring(8);\n const parentRecord = candidate.parentContext.record;\n candidateValue = parentRecord.fields?.[parentField] || parentRecord.primaryKey?.[parentField];\n }\n \n if (candidateValue !== lookupValue) {\n allMatch = false;\n break;\n }\n }\n\n if (allMatch) {\n return candidate.id;\n }\n }\n\n return null;\n }\n\n /**\n * Finds the root record for a given record\n */\n private findRootDependency(record: FlattenedRecord): string | null {\n // If this record has no parent, it IS the root, no dependency\n if (!record.parentContext) {\n return null;\n }\n \n // Walk up the parent chain to find the root\n let current = record;\n while (current.parentContext) {\n // Try to find the parent record in our flattened list\n const parentRecord = this.flattenedRecords.find(r => \n r.record === current.parentContext!.record && \n r.entityName === current.parentContext!.entityName\n );\n \n if (!parentRecord) {\n // Parent not found, something is wrong\n return null;\n }\n \n // If this parent has no parent, it's the root\n if (!parentRecord.parentContext) {\n return parentRecord.id;\n }\n \n current = parentRecord;\n }\n \n return null;\n }\n\n /**\n * Finds a record by its primary key value\n */\n private findRecordByPrimaryKey(\n entityName: string,\n primaryKeyValue: string,\n entityInfo: EntityInfo\n ): string | null {\n // Get primary key field name\n const primaryKeyField = entityInfo.PrimaryKeys[0]?.Name;\n if (!primaryKeyField) return null;\n\n for (const candidate of this.flattenedRecords) {\n if (candidate.entityName !== entityName) continue;\n\n const candidateValue = candidate.record.primaryKey?.[primaryKeyField] ||\n candidate.record.fields?.[primaryKeyField];\n if (candidateValue === primaryKeyValue) {\n return candidate.id;\n }\n }\n\n return null;\n }\n\n /**\n * Gets EntityInfo from cache or metadata\n */\n private getEntityInfo(entityName: string): EntityInfo | null {\n if (!this.entityInfoCache.has(entityName)) {\n const info = this.metadata.EntityByName(entityName);\n if (info) {\n this.entityInfoCache.set(entityName, info);\n }\n }\n return this.entityInfoCache.get(entityName) || null;\n }\n\n /**\n * Detects circular dependencies in the dependency graph\n */\n private detectCircularDependencies(): string[][] {\n const cycles: string[][] = [];\n const visited = new Set<string>();\n const recursionStack = new Set<string>();\n\n const detectCycle = (recordId: string, path: string[]): boolean => {\n visited.add(recordId);\n recursionStack.add(recordId);\n path.push(recordId);\n\n const record = this.recordIdMap.get(recordId);\n if (record) {\n for (const depId of record.dependencies) {\n if (!visited.has(depId)) {\n if (detectCycle(depId, [...path])) {\n return true;\n }\n } else if (recursionStack.has(depId)) {\n // Found a cycle\n const cycleStart = path.indexOf(depId);\n const cycle = path.slice(cycleStart);\n cycle.push(depId); // Complete the cycle\n cycles.push(cycle);\n return true;\n }\n }\n }\n\n recursionStack.delete(recordId);\n return false;\n };\n\n // Check all records for cycles\n for (const record of this.flattenedRecords) {\n if (!visited.has(record.id)) {\n detectCycle(record.id, []);\n }\n }\n\n return cycles;\n }\n\n /**\n * Performs topological sort on the dependency graph\n */\n private topologicalSort(): FlattenedRecord[] {\n const result: FlattenedRecord[] = [];\n const visited = new Set<string>();\n const tempStack = new Set<string>();\n\n const visit = (recordId: string): boolean => {\n if (tempStack.has(recordId)) {\n // Circular dependency - we've already detected these\n return false;\n }\n\n if (visited.has(recordId)) {\n return true;\n }\n\n tempStack.add(recordId);\n\n const record = this.recordIdMap.get(recordId);\n if (record) {\n // Visit dependencies first\n for (const depId of record.dependencies) {\n visit(depId);\n }\n }\n\n tempStack.delete(recordId);\n visited.add(recordId);\n \n if (record) {\n result.push(record);\n }\n\n return true;\n };\n\n // Process all records, starting with those that have no dependencies\n // First, process records with no dependencies\n for (const record of this.flattenedRecords) {\n if (record.dependencies.size === 0 && !visited.has(record.id)) {\n visit(record.id);\n }\n }\n\n // Then process any remaining records (handles disconnected components)\n for (const record of this.flattenedRecords) {\n if (!visited.has(record.id)) {\n visit(record.id);\n }\n }\n\n return result;\n }\n}"]}
|
|
@@ -34,7 +34,6 @@ export interface RecordData {
|
|
|
34
34
|
* @example
|
|
35
35
|
* ```typescript
|
|
36
36
|
* const syncEngine = new SyncEngine(systemUser);
|
|
37
|
-
* await syncEngine.initialize();
|
|
38
37
|
*
|
|
39
38
|
* // Process a field value with special references
|
|
40
39
|
* const value = await syncEngine.processFieldValue('@lookup:Users.Email=admin@example.com', '/path/to/base');
|
|
@@ -52,7 +51,7 @@ export declare class SyncEngine {
|
|
|
52
51
|
* Initializes the sync engine by refreshing metadata cache
|
|
53
52
|
* @returns Promise that resolves when initialization is complete
|
|
54
53
|
*/
|
|
55
|
-
initialize(
|
|
54
|
+
initialize(): Promise<void>;
|
|
56
55
|
/**
|
|
57
56
|
* Process special references in field values and handle complex objects
|
|
58
57
|
*
|
|
@@ -88,7 +87,7 @@ export declare class SyncEngine {
|
|
|
88
87
|
* // Returns: '{\n "items": [\n {\n "id": 1\n },\n {\n "id": 2\n }\n ]\n}'
|
|
89
88
|
* ```
|
|
90
89
|
*/
|
|
91
|
-
processFieldValue(value: any, baseDir: string, parentRecord?: BaseEntity | null, rootRecord?: BaseEntity | null, depth?: number): Promise<any>;
|
|
90
|
+
processFieldValue(value: any, baseDir: string, parentRecord?: BaseEntity | null, rootRecord?: BaseEntity | null, depth?: number, batchContext?: Map<string, BaseEntity>): Promise<any>;
|
|
92
91
|
/**
|
|
93
92
|
* Resolve a lookup reference to an ID, optionally creating the record if it doesn't exist
|
|
94
93
|
*
|
|
@@ -115,7 +114,7 @@ export declare class SyncEngine {
|
|
|
115
114
|
resolveLookup(entityName: string, lookupFields: Array<{
|
|
116
115
|
fieldName: string;
|
|
117
116
|
fieldValue: string;
|
|
118
|
-
}>, autoCreate?: boolean, createFields?: Record<string, any>): Promise<string>;
|
|
117
|
+
}>, autoCreate?: boolean, createFields?: Record<string, any>, batchContext?: Map<string, BaseEntity>): Promise<string>;
|
|
119
118
|
/**
|
|
120
119
|
* Build cascading defaults for a file path and process field values
|
|
121
120
|
*
|
|
@@ -244,49 +243,6 @@ export declare class SyncEngine {
|
|
|
244
243
|
* ```
|
|
245
244
|
*/
|
|
246
245
|
loadEntity(entityName: string, primaryKey: Record<string, any>): Promise<BaseEntity | null>;
|
|
247
|
-
/**
|
|
248
|
-
* Process JSON object with template references
|
|
249
|
-
*
|
|
250
|
-
* Recursively processes JSON data structures to resolve `@template` references.
|
|
251
|
-
* Templates can be defined at any level and support:
|
|
252
|
-
* - Single template references: `"@template:path/to/template.json"`
|
|
253
|
-
* - Object with @template field: `{ "@template": "file.json", "override": "value" }`
|
|
254
|
-
* - Array of templates for merging: `{ "@template": ["base.json", "overrides.json"] }`
|
|
255
|
-
* - Nested template references within templates
|
|
256
|
-
*
|
|
257
|
-
* @param data - JSON data structure to process
|
|
258
|
-
* @param baseDir - Base directory for resolving relative template paths
|
|
259
|
-
* @returns Promise resolving to the processed data with all templates resolved
|
|
260
|
-
* @throws Error if template file is not found or contains invalid JSON
|
|
261
|
-
*
|
|
262
|
-
* @example
|
|
263
|
-
* ```typescript
|
|
264
|
-
* // Input data with template reference
|
|
265
|
-
* const data = {
|
|
266
|
-
* "@template": "defaults/ai-prompt.json",
|
|
267
|
-
* "Name": "Custom Prompt",
|
|
268
|
-
* "Prompt": "Override the template prompt"
|
|
269
|
-
* };
|
|
270
|
-
*
|
|
271
|
-
* // Resolves template and merges with overrides
|
|
272
|
-
* const result = await syncEngine.processTemplates(data, '/path/to/dir');
|
|
273
|
-
* ```
|
|
274
|
-
*/
|
|
275
|
-
processTemplates(data: any, baseDir: string): Promise<any>;
|
|
276
|
-
/**
|
|
277
|
-
* Load and process a template file
|
|
278
|
-
*
|
|
279
|
-
* Loads a JSON template file from the filesystem and recursively processes any
|
|
280
|
-
* nested template references within it. Template paths are resolved relative to
|
|
281
|
-
* the template file's directory, enabling template composition.
|
|
282
|
-
*
|
|
283
|
-
* @param templatePath - Path to the template file (relative or absolute)
|
|
284
|
-
* @param baseDir - Base directory for resolving relative paths
|
|
285
|
-
* @returns Promise resolving to the processed template content
|
|
286
|
-
* @throws Error if template file not found or contains invalid JSON
|
|
287
|
-
* @private
|
|
288
|
-
*/
|
|
289
|
-
private loadAndProcessTemplate;
|
|
290
246
|
/**
|
|
291
247
|
* Process file content with {@include} references
|
|
292
248
|
*
|
|
@@ -297,8 +253,8 @@ export declare class SyncEngine {
|
|
|
297
253
|
* - Circular reference detection to prevent infinite loops
|
|
298
254
|
* - Seamless content substitution maintaining surrounding text
|
|
299
255
|
*
|
|
300
|
-
* @param filePath - Path to the file being processed
|
|
301
256
|
* @param content - The file content to process
|
|
257
|
+
* @param filePath - Path to the file being processed
|
|
302
258
|
* @param visitedPaths - Set of already visited file paths for circular reference detection
|
|
303
259
|
* @returns Promise resolving to the content with all includes resolved
|
|
304
260
|
* @throws Error if circular reference detected or included file not found
|
|
@@ -314,33 +270,4 @@ export declare class SyncEngine {
|
|
|
314
270
|
* ```
|
|
315
271
|
*/
|
|
316
272
|
private processFileContentWithIncludes;
|
|
317
|
-
/**
|
|
318
|
-
* Deep merge two objects with target taking precedence
|
|
319
|
-
*
|
|
320
|
-
* Recursively merges two objects, with values from the target object overriding
|
|
321
|
-
* values from the source object. Arrays and primitive values are not merged but
|
|
322
|
-
* replaced entirely by the target value. Undefined values in target are skipped.
|
|
323
|
-
*
|
|
324
|
-
* @param source - Base object to merge from
|
|
325
|
-
* @param target - Object with values that override source
|
|
326
|
-
* @returns New object with merged values
|
|
327
|
-
* @private
|
|
328
|
-
*
|
|
329
|
-
* @example
|
|
330
|
-
* ```typescript
|
|
331
|
-
* const source = {
|
|
332
|
-
* a: 1,
|
|
333
|
-
* b: { x: 10, y: 20 },
|
|
334
|
-
* c: [1, 2, 3]
|
|
335
|
-
* };
|
|
336
|
-
* const target = {
|
|
337
|
-
* a: 2,
|
|
338
|
-
* b: { y: 30, z: 40 },
|
|
339
|
-
* d: 'new'
|
|
340
|
-
* };
|
|
341
|
-
* const result = deepMerge(source, target);
|
|
342
|
-
* // Result: { a: 2, b: { x: 10, y: 30, z: 40 }, c: [1, 2, 3], d: 'new' }
|
|
343
|
-
* ```
|
|
344
|
-
*/
|
|
345
|
-
private deepMerge;
|
|
346
273
|
}
|