@memberjunction/version-history 6.2.0-edge.0 → 6.2.0-edge.2
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/README.md +3 -0
- package/dist/DependencyGraphWalker.d.ts +3 -166
- package/dist/DependencyGraphWalker.d.ts.map +1 -1
- package/dist/DependencyGraphWalker.js +3 -503
- package/dist/DependencyGraphWalker.js.map +1 -1
- package/dist/DiffEngine.js +17 -17
- package/dist/LabelManager.js +8 -8
- package/dist/RestoreEngine.d.ts +3 -2
- package/dist/RestoreEngine.d.ts.map +1 -1
- package/dist/RestoreEngine.js +16 -52
- package/dist/RestoreEngine.js.map +1 -1
- package/dist/SnapshotBuilder.d.ts +1 -1
- package/dist/SnapshotBuilder.js +7 -7
- package/dist/VersionHistoryEngine.d.ts +7 -6
- package/dist/VersionHistoryEngine.d.ts.map +1 -1
- package/dist/VersionHistoryEngine.js +18 -18
- package/dist/VersionHistoryEngine.js.map +1 -1
- package/dist/constants.d.ts +5 -48
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +16 -73
- package/dist/constants.js.map +1 -1
- package/dist/types.d.ts +2 -35
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -4
|
@@ -1,506 +1,6 @@
|
|
|
1
|
-
import { CompositeKey, Metadata, RunView, LogError, LogStatus } from '@memberjunction/core';
|
|
2
|
-
import { UUIDsEqual } from '@memberjunction/global';
|
|
3
|
-
import { buildCompositeKeyFromRecord, escapeSqlString } from './constants.js';
|
|
4
|
-
// =============================================================================
|
|
5
|
-
// System FK Skip Patterns
|
|
6
|
-
// =============================================================================
|
|
7
1
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* entity references) rather than business data. Following them would pull in
|
|
11
|
-
* every user record, every entity definition, etc.
|
|
2
|
+
* NOTE: DependencyGraphWalker has moved to @memberjunction/record-graph.
|
|
3
|
+
* Re-exported here for backward compatibility.
|
|
12
4
|
*/
|
|
13
|
-
|
|
14
|
-
/^CreatedByUserID$/i,
|
|
15
|
-
/^UpdatedByUserID$/i,
|
|
16
|
-
/^UserID$/i,
|
|
17
|
-
/^ContextUser(ID)?$/i,
|
|
18
|
-
/^ModifiedBy(UserID)?$/i,
|
|
19
|
-
/^CreatedBy$/i,
|
|
20
|
-
/^UpdatedBy$/i,
|
|
21
|
-
/^Owner(ID|UserID)?$/i,
|
|
22
|
-
/^AssignedTo(ID|UserID)?$/i,
|
|
23
|
-
/^EntityID$/i, // polymorphic entity reference, not a real FK to follow
|
|
24
|
-
];
|
|
25
|
-
// =============================================================================
|
|
26
|
-
// DependencyGraphWalker
|
|
27
|
-
// =============================================================================
|
|
28
|
-
/**
|
|
29
|
-
* Walks entity relationship graphs to discover records for version labeling.
|
|
30
|
-
*
|
|
31
|
-
* ## Two-direction traversal
|
|
32
|
-
*
|
|
33
|
-
* **Reverse**: Uses the entity's `RelatedEntities` (EntityRelationship metadata)
|
|
34
|
-
* to find child records. This is curated by CodeGen and admins — only explicitly
|
|
35
|
-
* registered One-To-Many relationships are walked.
|
|
36
|
-
*
|
|
37
|
-
* **Forward**: Scans the current entity's own FK fields to find records it
|
|
38
|
-
* references (e.g., AI Agent Prompt → AI Prompt via PromptID). Skips
|
|
39
|
-
* system/infrastructure fields (UserID, CreatedBy, etc.).
|
|
40
|
-
*
|
|
41
|
-
* ## Discovery mode — prevents explosion from forward references
|
|
42
|
-
*
|
|
43
|
-
* Records found via **reverse walk** are "owned children" and get full treatment
|
|
44
|
-
* (both reverse + forward walks). Records found via **forward walk** are
|
|
45
|
-
* "referenced records" and only get forward-only treatment (their own FK refs
|
|
46
|
-
* are followed, but their EntityRelationship children are NOT discovered).
|
|
47
|
-
*
|
|
48
|
-
* This prevents the classic explosion pattern:
|
|
49
|
-
* ```
|
|
50
|
-
* Agent → AgentPrompt → Prompt → [reverse to ALL PromptModels] → Model
|
|
51
|
-
* → [reverse to ALL AgentModels across ALL agents] → explosion!
|
|
52
|
-
* ```
|
|
53
|
-
*
|
|
54
|
-
* With discovery mode:
|
|
55
|
-
* ```
|
|
56
|
-
* Agent → AgentPrompt(reverse,full) → Prompt(forward,forward-only)
|
|
57
|
-
* → Model(forward,forward-only) → STOP (no reverse walk on Model)
|
|
58
|
-
* ```
|
|
59
|
-
*
|
|
60
|
-
* ## Ancestor stack — prevents backtracking
|
|
61
|
-
*
|
|
62
|
-
* A set of entity names on the current path from root to the current node is
|
|
63
|
-
* maintained. When evaluating any relationship, if the target entity type is
|
|
64
|
-
* already on the ancestor stack, it is skipped. This is a secondary safety net
|
|
65
|
-
* that prevents cycles even within full-mode walks.
|
|
66
|
-
*
|
|
67
|
-
* ## Filters
|
|
68
|
-
*
|
|
69
|
-
* - Only walks entities with `TrackRecordChanges === true`
|
|
70
|
-
* - Skips system FK fields via regex patterns (UserID, CreatedBy, etc.)
|
|
71
|
-
* - Respects `ExcludeEntities` and `EntityFilter` options
|
|
72
|
-
* - Uses a global `visited` set for record-level cycle detection
|
|
73
|
-
*/
|
|
74
|
-
export class DependencyGraphWalker {
|
|
75
|
-
constructor() {
|
|
76
|
-
/** Cache: entityID → reverse relationships from EntityRelationship metadata */
|
|
77
|
-
this.reverseRelCache = new Map();
|
|
78
|
-
/** Cache: entityID → forward FK references from field metadata */
|
|
79
|
-
this.forwardRefCache = new Map();
|
|
80
|
-
}
|
|
81
|
-
/** Returns the active provider — explicit override if set, otherwise the global default. */
|
|
82
|
-
get ProviderToUse() {
|
|
83
|
-
return this._provider ?? Metadata.Provider;
|
|
84
|
-
}
|
|
85
|
-
// =========================================================================
|
|
86
|
-
// Public API
|
|
87
|
-
// =========================================================================
|
|
88
|
-
/**
|
|
89
|
-
* Walk from a root record through its relationships, building a tree of
|
|
90
|
-
* all records that should be included in a version label.
|
|
91
|
-
*
|
|
92
|
-
* @param entityName - The starting entity name
|
|
93
|
-
* @param recordKey - The starting record's primary key
|
|
94
|
-
* @param options - Controls depth, filtering, etc.
|
|
95
|
-
* @param contextUser - Server-side user context
|
|
96
|
-
* @returns The root DependencyNode with all descendants populated
|
|
97
|
-
*/
|
|
98
|
-
async WalkDependents(entityName, recordKey, options, contextUser) {
|
|
99
|
-
const md = this.ProviderToUse;
|
|
100
|
-
const entityInfo = md.EntityByName(entityName);
|
|
101
|
-
if (!entityInfo) {
|
|
102
|
-
throw new Error(`Entity '${entityName}' not found in metadata`);
|
|
103
|
-
}
|
|
104
|
-
const resolvedOptions = this.resolveDefaults(options);
|
|
105
|
-
const visited = new Set();
|
|
106
|
-
const ancestorStack = new Set();
|
|
107
|
-
const stats = this.createEmptyStats();
|
|
108
|
-
this.reverseRelCache.clear();
|
|
109
|
-
this.forwardRefCache.clear();
|
|
110
|
-
const rootData = await this.loadRecordData(entityInfo, recordKey, contextUser);
|
|
111
|
-
const rootNode = {
|
|
112
|
-
EntityName: entityName,
|
|
113
|
-
EntityInfo: entityInfo,
|
|
114
|
-
RecordKey: recordKey,
|
|
115
|
-
RecordID: recordKey.ToConcatenatedString(),
|
|
116
|
-
RecordData: rootData,
|
|
117
|
-
Relationship: null,
|
|
118
|
-
Children: [],
|
|
119
|
-
Depth: 0,
|
|
120
|
-
};
|
|
121
|
-
// Mark root as visited and push its entity type onto the ancestor stack
|
|
122
|
-
visited.add(this.visitKey(entityInfo.Name, rootNode.RecordID));
|
|
123
|
-
ancestorStack.add(entityInfo.Name);
|
|
124
|
-
this.incrementEntityCount(stats, entityInfo.Name);
|
|
125
|
-
// Root always gets full discovery mode (both reverse + forward)
|
|
126
|
-
await this.walkChildren(rootNode, 'full', resolvedOptions, visited, ancestorStack, stats, contextUser);
|
|
127
|
-
// Pop root (cleanup)
|
|
128
|
-
ancestorStack.delete(entityInfo.Name);
|
|
129
|
-
this.logWalkSummary(entityName, resolvedOptions, stats);
|
|
130
|
-
return rootNode;
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Flatten a dependency tree into a topologically sorted list.
|
|
134
|
-
* Parents appear before their children, ensuring safe restore ordering.
|
|
135
|
-
*/
|
|
136
|
-
FlattenTopological(root) {
|
|
137
|
-
const result = [];
|
|
138
|
-
const queue = [root];
|
|
139
|
-
while (queue.length > 0) {
|
|
140
|
-
const node = queue.shift();
|
|
141
|
-
result.push(node);
|
|
142
|
-
for (const child of node.Children) {
|
|
143
|
-
queue.push(child);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return result;
|
|
147
|
-
}
|
|
148
|
-
// =========================================================================
|
|
149
|
-
// Core Walk Logic
|
|
150
|
-
// =========================================================================
|
|
151
|
-
/**
|
|
152
|
-
* Recursively discover and attach child nodes for a given parent node.
|
|
153
|
-
*
|
|
154
|
-
* The `discoveryMode` controls which directions are walked:
|
|
155
|
-
* - `'full'`: both reverse (EntityRelationship) and forward (FK refs)
|
|
156
|
-
* - `'forward-only'`: only forward FK refs — no reverse children discovered
|
|
157
|
-
*
|
|
158
|
-
* Records found via reverse walk are recursed with `'full'` mode.
|
|
159
|
-
* Records found via forward walk are recursed with `'forward-only'` mode.
|
|
160
|
-
*/
|
|
161
|
-
async walkChildren(parentNode, discoveryMode, options, visited, ancestorStack, stats, contextUser) {
|
|
162
|
-
if (parentNode.Depth >= options.MaxDepth) {
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
// Reverse walk: only when in full mode (root or reverse-discovered nodes)
|
|
166
|
-
if (discoveryMode === 'full') {
|
|
167
|
-
await this.walkReverseRelationships(parentNode, options, visited, ancestorStack, stats, contextUser);
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
stats.ForwardOnlySuppressions++;
|
|
171
|
-
}
|
|
172
|
-
// Forward walk: always performed regardless of discovery mode
|
|
173
|
-
await this.walkForwardReferences(parentNode, options, visited, ancestorStack, stats, contextUser);
|
|
174
|
-
}
|
|
175
|
-
// =========================================================================
|
|
176
|
-
// Reverse Walking (EntityRelationship-driven)
|
|
177
|
-
// =========================================================================
|
|
178
|
-
/**
|
|
179
|
-
* Walk reverse relationships: for each EntityRelationship on the parent
|
|
180
|
-
* entity, load matching child records and recurse into them.
|
|
181
|
-
*
|
|
182
|
-
* Skips any child entity type that is already on the ancestor stack,
|
|
183
|
-
* preventing backtracking (e.g., from AI Prompt back to AI Agent Prompts).
|
|
184
|
-
*
|
|
185
|
-
* Children found via reverse walk are recursed with 'full' discovery mode,
|
|
186
|
-
* since they are "owned children" that may have their own children.
|
|
187
|
-
*/
|
|
188
|
-
async walkReverseRelationships(parentNode, options, visited, ancestorStack, stats, contextUser) {
|
|
189
|
-
const relationships = this.discoverReverseRelationships(parentNode.EntityInfo);
|
|
190
|
-
for (const rel of relationships) {
|
|
191
|
-
// Ancestor check: skip if the child entity type is on our current path
|
|
192
|
-
if (ancestorStack.has(rel.ChildEntityInfo.Name)) {
|
|
193
|
-
stats.AncestorSkips++;
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
196
|
-
if (this.shouldSkipEntity(rel.ChildEntityInfo.Name, options)) {
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
const childRecords = await this.loadChildRecords(parentNode, rel, options, contextUser);
|
|
200
|
-
for (const childData of childRecords) {
|
|
201
|
-
const childNode = this.registerNode(rel.ChildEntityInfo, childData, rel.RelationshipInfo, parentNode, visited, stats);
|
|
202
|
-
if (!childNode)
|
|
203
|
-
continue;
|
|
204
|
-
// Push child entity type, recurse with FULL mode, then pop
|
|
205
|
-
ancestorStack.add(rel.ChildEntityInfo.Name);
|
|
206
|
-
await this.walkChildren(childNode, 'full', options, visited, ancestorStack, stats, contextUser);
|
|
207
|
-
ancestorStack.delete(rel.ChildEntityInfo.Name);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
// =========================================================================
|
|
212
|
-
// Forward Walking (FK field-driven)
|
|
213
|
-
// =========================================================================
|
|
214
|
-
/**
|
|
215
|
-
* Walk forward FK references: for each FK field on the parent entity that
|
|
216
|
-
* points to another tracked entity, load the referenced record and recurse.
|
|
217
|
-
*
|
|
218
|
-
* Skips system/infrastructure FKs (UserID, CreatedBy, etc.) and any target
|
|
219
|
-
* entity type already on the ancestor stack.
|
|
220
|
-
*
|
|
221
|
-
* Targets found via forward walk are recursed with 'forward-only' discovery
|
|
222
|
-
* mode — they won't discover their own reverse children, preventing graph
|
|
223
|
-
* explosion from "hub" entities like AI Models.
|
|
224
|
-
*/
|
|
225
|
-
async walkForwardReferences(parentNode, options, visited, ancestorStack, stats, contextUser) {
|
|
226
|
-
const forwardRefs = this.discoverForwardReferences(parentNode.EntityInfo);
|
|
227
|
-
for (const ref of forwardRefs) {
|
|
228
|
-
// Ancestor check: skip if target entity type is on our current path
|
|
229
|
-
if (ancestorStack.has(ref.TargetEntityInfo.Name)) {
|
|
230
|
-
stats.AncestorSkips++;
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
if (this.shouldSkipEntity(ref.TargetEntityInfo.Name, options)) {
|
|
234
|
-
continue;
|
|
235
|
-
}
|
|
236
|
-
// Read the FK value from the record data
|
|
237
|
-
const fkValue = parentNode.RecordData[ref.FKFieldName];
|
|
238
|
-
if (fkValue == null)
|
|
239
|
-
continue;
|
|
240
|
-
const targetKey = new CompositeKey([{
|
|
241
|
-
FieldName: ref.TargetKeyField,
|
|
242
|
-
Value: fkValue,
|
|
243
|
-
}]);
|
|
244
|
-
// Skip if we've already visited this specific record
|
|
245
|
-
const vKey = this.visitKey(ref.TargetEntityInfo.Name, targetKey.ToConcatenatedString());
|
|
246
|
-
if (visited.has(vKey)) {
|
|
247
|
-
stats.VisitedSkips++;
|
|
248
|
-
continue;
|
|
249
|
-
}
|
|
250
|
-
const targetData = await this.loadRecordData(ref.TargetEntityInfo, targetKey, contextUser);
|
|
251
|
-
if (!targetData || Object.keys(targetData).length === 0)
|
|
252
|
-
continue;
|
|
253
|
-
const targetNode = this.registerNode(ref.TargetEntityInfo, targetData, null, parentNode, visited, stats);
|
|
254
|
-
if (!targetNode)
|
|
255
|
-
continue;
|
|
256
|
-
// Push target entity type, recurse with FORWARD-ONLY mode, then pop
|
|
257
|
-
ancestorStack.add(ref.TargetEntityInfo.Name);
|
|
258
|
-
await this.walkChildren(targetNode, 'forward-only', options, visited, ancestorStack, stats, contextUser);
|
|
259
|
-
ancestorStack.delete(ref.TargetEntityInfo.Name);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
// =========================================================================
|
|
263
|
-
// Relationship Discovery
|
|
264
|
-
// =========================================================================
|
|
265
|
-
/**
|
|
266
|
-
* Discover reverse relationships for an entity using EntityRelationship
|
|
267
|
-
* metadata. Only includes One-To-Many relationships to entities that have
|
|
268
|
-
* TrackRecordChanges enabled.
|
|
269
|
-
*
|
|
270
|
-
* Uses EntityRelationship (already loaded in memory on EntityInfo) rather
|
|
271
|
-
* than scanning all entities' FK fields. This means only relationships
|
|
272
|
-
* that CodeGen or admins have explicitly registered are walked.
|
|
273
|
-
*/
|
|
274
|
-
discoverReverseRelationships(parentEntity) {
|
|
275
|
-
const cached = this.reverseRelCache.get(parentEntity.ID);
|
|
276
|
-
if (cached)
|
|
277
|
-
return cached;
|
|
278
|
-
const md = this.ProviderToUse;
|
|
279
|
-
const relationships = [];
|
|
280
|
-
for (const rel of parentEntity.RelatedEntities) {
|
|
281
|
-
// Only walk One-To-Many (parent has many children)
|
|
282
|
-
if (rel.Type.trim() !== 'One To Many')
|
|
283
|
-
continue;
|
|
284
|
-
const childEntity = md.Entities.find(e => UUIDsEqual(e.ID, rel.RelatedEntityID));
|
|
285
|
-
if (!childEntity)
|
|
286
|
-
continue;
|
|
287
|
-
if (!childEntity.TrackRecordChanges)
|
|
288
|
-
continue;
|
|
289
|
-
const parentKeyField = this.resolveParentKeyFieldFromRelationship(rel, parentEntity);
|
|
290
|
-
relationships.push({
|
|
291
|
-
ChildEntityInfo: childEntity,
|
|
292
|
-
ChildJoinField: rel.RelatedEntityJoinField,
|
|
293
|
-
ParentKeyField: parentKeyField,
|
|
294
|
-
RelationshipInfo: rel,
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
this.reverseRelCache.set(parentEntity.ID, relationships);
|
|
298
|
-
return relationships;
|
|
299
|
-
}
|
|
300
|
-
/**
|
|
301
|
-
* Discover forward FK references on an entity — fields that point TO other
|
|
302
|
-
* entities. Only includes references to entities with TrackRecordChanges
|
|
303
|
-
* enabled, and skips system/infrastructure FK fields.
|
|
304
|
-
*/
|
|
305
|
-
discoverForwardReferences(entity) {
|
|
306
|
-
const cached = this.forwardRefCache.get(entity.ID);
|
|
307
|
-
if (cached)
|
|
308
|
-
return cached;
|
|
309
|
-
const md = this.ProviderToUse;
|
|
310
|
-
const refs = [];
|
|
311
|
-
for (const field of entity.Fields) {
|
|
312
|
-
if (!field.RelatedEntityID)
|
|
313
|
-
continue;
|
|
314
|
-
if (this.isSystemFKField(field.Name))
|
|
315
|
-
continue;
|
|
316
|
-
if (UUIDsEqual(field.RelatedEntityID, entity.ID))
|
|
317
|
-
continue; // skip self-referencing
|
|
318
|
-
const targetEntity = md.Entities.find(e => UUIDsEqual(e.ID, field.RelatedEntityID));
|
|
319
|
-
if (!targetEntity)
|
|
320
|
-
continue;
|
|
321
|
-
if (!targetEntity.TrackRecordChanges)
|
|
322
|
-
continue;
|
|
323
|
-
refs.push({
|
|
324
|
-
TargetEntityInfo: targetEntity,
|
|
325
|
-
FKFieldName: field.Name,
|
|
326
|
-
TargetKeyField: this.resolveParentKeyFieldFromFK(field, targetEntity),
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
this.forwardRefCache.set(entity.ID, refs);
|
|
330
|
-
return refs;
|
|
331
|
-
}
|
|
332
|
-
// =========================================================================
|
|
333
|
-
// Data Loading
|
|
334
|
-
// =========================================================================
|
|
335
|
-
/**
|
|
336
|
-
* Load child records for a reverse relationship.
|
|
337
|
-
* Queries the child entity where the join field matches the parent's key value.
|
|
338
|
-
*/
|
|
339
|
-
async loadChildRecords(parentNode, rel, options, contextUser) {
|
|
340
|
-
const parentKeyValue = parentNode.RecordData[rel.ParentKeyField] ?? null;
|
|
341
|
-
if (parentKeyValue == null)
|
|
342
|
-
return [];
|
|
343
|
-
let extraFilter = `[${rel.ChildJoinField}] = '${escapeSqlString(String(parentKeyValue))}'`;
|
|
344
|
-
if (!options.IncludeDeleted && this.entityHasSoftDelete(rel.ChildEntityInfo)) {
|
|
345
|
-
extraFilter += ` AND __mj_DeletedAt IS NULL`;
|
|
346
|
-
}
|
|
347
|
-
try {
|
|
348
|
-
const rv = new RunView();
|
|
349
|
-
const result = await rv.RunView({
|
|
350
|
-
EntityName: rel.ChildEntityInfo.Name,
|
|
351
|
-
ExtraFilter: extraFilter,
|
|
352
|
-
ResultType: 'simple',
|
|
353
|
-
}, contextUser);
|
|
354
|
-
if (!result.Success) {
|
|
355
|
-
LogError(`DependencyGraphWalker: Failed to load children of ` +
|
|
356
|
-
`${parentNode.EntityName} via ${rel.ChildEntityInfo.Name}.${rel.ChildJoinField}: ` +
|
|
357
|
-
`${result.ErrorMessage}`);
|
|
358
|
-
return [];
|
|
359
|
-
}
|
|
360
|
-
return result.Results;
|
|
361
|
-
}
|
|
362
|
-
catch (e) {
|
|
363
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
364
|
-
LogError(`DependencyGraphWalker: Error loading child records: ${msg}`);
|
|
365
|
-
return [];
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* Load a single record's data by primary key.
|
|
370
|
-
*/
|
|
371
|
-
async loadRecordData(entityInfo, key, contextUser) {
|
|
372
|
-
const rv = new RunView();
|
|
373
|
-
const result = await rv.RunView({
|
|
374
|
-
EntityName: entityInfo.Name,
|
|
375
|
-
ExtraFilter: key.ToWhereClause(),
|
|
376
|
-
ResultType: 'simple',
|
|
377
|
-
}, contextUser);
|
|
378
|
-
if (!result.Success || result.Results.length === 0) {
|
|
379
|
-
LogError(`DependencyGraphWalker: loadRecordData returned empty for entity ` +
|
|
380
|
-
`'${entityInfo.Name}' with key ${key.ToConcatenatedString()}`);
|
|
381
|
-
return {};
|
|
382
|
-
}
|
|
383
|
-
return result.Results[0];
|
|
384
|
-
}
|
|
385
|
-
// =========================================================================
|
|
386
|
-
// Node Registration
|
|
387
|
-
// =========================================================================
|
|
388
|
-
/**
|
|
389
|
-
* Build a DependencyNode from record data, register it in the visited set,
|
|
390
|
-
* and attach it as a child of the parent. Returns null if already visited.
|
|
391
|
-
*/
|
|
392
|
-
registerNode(entityInfo, recordData, relationship, parentNode, visited, stats) {
|
|
393
|
-
const key = buildCompositeKeyFromRecord(entityInfo, recordData);
|
|
394
|
-
const vKey = this.visitKey(entityInfo.Name, key.ToConcatenatedString());
|
|
395
|
-
if (visited.has(vKey)) {
|
|
396
|
-
stats.VisitedSkips++;
|
|
397
|
-
return null;
|
|
398
|
-
}
|
|
399
|
-
visited.add(vKey);
|
|
400
|
-
const node = {
|
|
401
|
-
EntityName: entityInfo.Name,
|
|
402
|
-
EntityInfo: entityInfo,
|
|
403
|
-
RecordKey: key,
|
|
404
|
-
RecordID: key.ToConcatenatedString(),
|
|
405
|
-
RecordData: recordData,
|
|
406
|
-
Relationship: relationship,
|
|
407
|
-
Children: [],
|
|
408
|
-
Depth: parentNode.Depth + 1,
|
|
409
|
-
};
|
|
410
|
-
parentNode.Children.push(node);
|
|
411
|
-
this.incrementEntityCount(stats, entityInfo.Name);
|
|
412
|
-
return node;
|
|
413
|
-
}
|
|
414
|
-
// =========================================================================
|
|
415
|
-
// Logging
|
|
416
|
-
// =========================================================================
|
|
417
|
-
/** Create an empty stats accumulator. */
|
|
418
|
-
createEmptyStats() {
|
|
419
|
-
return {
|
|
420
|
-
EntityCounts: new Map(),
|
|
421
|
-
TotalRecords: 0,
|
|
422
|
-
AncestorSkips: 0,
|
|
423
|
-
VisitedSkips: 0,
|
|
424
|
-
ForwardOnlySuppressions: 0,
|
|
425
|
-
};
|
|
426
|
-
}
|
|
427
|
-
/** Increment the per-entity record counter. */
|
|
428
|
-
incrementEntityCount(stats, entityName) {
|
|
429
|
-
stats.TotalRecords++;
|
|
430
|
-
stats.EntityCounts.set(entityName, (stats.EntityCounts.get(entityName) ?? 0) + 1);
|
|
431
|
-
}
|
|
432
|
-
/**
|
|
433
|
-
* Log a detailed summary of the walk: total records, per-entity counts,
|
|
434
|
-
* skips, and suppression stats.
|
|
435
|
-
*/
|
|
436
|
-
logWalkSummary(rootEntityName, options, stats) {
|
|
437
|
-
const entityBreakdown = Array.from(stats.EntityCounts.entries())
|
|
438
|
-
.sort((a, b) => b[1] - a[1]) // sort by count descending
|
|
439
|
-
.map(([name, count]) => ` ${name}: ${count}`)
|
|
440
|
-
.join('\n');
|
|
441
|
-
LogStatus(`DependencyGraphWalker: Walk complete for '${rootEntityName}'\n` +
|
|
442
|
-
` Total records: ${stats.TotalRecords}\n` +
|
|
443
|
-
` Unique entities: ${stats.EntityCounts.size}\n` +
|
|
444
|
-
` Ancestor skips: ${stats.AncestorSkips}\n` +
|
|
445
|
-
` Visited skips: ${stats.VisitedSkips}\n` +
|
|
446
|
-
` Forward-only suppressions: ${stats.ForwardOnlySuppressions}\n` +
|
|
447
|
-
` Max depth: ${options.MaxDepth}\n` +
|
|
448
|
-
` Per-entity breakdown:\n${entityBreakdown}`);
|
|
449
|
-
}
|
|
450
|
-
// =========================================================================
|
|
451
|
-
// Helpers
|
|
452
|
-
// =========================================================================
|
|
453
|
-
/** Build a unique key for the visited set: "EntityName::recordID" */
|
|
454
|
-
visitKey(entityName, recordId) {
|
|
455
|
-
return `${entityName}::${recordId}`;
|
|
456
|
-
}
|
|
457
|
-
/**
|
|
458
|
-
* Determine the parent key field from an EntityRelationship record.
|
|
459
|
-
* Uses EntityKeyField if specified, otherwise falls back to the first PK.
|
|
460
|
-
*/
|
|
461
|
-
resolveParentKeyFieldFromRelationship(rel, parentEntity) {
|
|
462
|
-
if (rel.EntityKeyField && rel.EntityKeyField.trim().length > 0) {
|
|
463
|
-
return rel.EntityKeyField;
|
|
464
|
-
}
|
|
465
|
-
return parentEntity.FirstPrimaryKey.Name; // first-pk-ok: FK target — a relationship without an explicit EntityKeyField joins on the parent's single key column by design
|
|
466
|
-
}
|
|
467
|
-
/**
|
|
468
|
-
* Determine the target key field from a FK field definition.
|
|
469
|
-
* Uses RelatedEntityFieldName if specified, otherwise falls back to the
|
|
470
|
-
* target entity's first PK.
|
|
471
|
-
*/
|
|
472
|
-
resolveParentKeyFieldFromFK(fkField, targetEntity) {
|
|
473
|
-
if (fkField.RelatedEntityFieldName && fkField.RelatedEntityFieldName.trim().length > 0) {
|
|
474
|
-
return fkField.RelatedEntityFieldName;
|
|
475
|
-
}
|
|
476
|
-
return targetEntity.FirstPrimaryKey.Name; // first-pk-ok: FK target — a foreign key column without RelatedEntityFieldName references the target's single key column by design
|
|
477
|
-
}
|
|
478
|
-
/** Check if a field name matches a system/infrastructure FK pattern. */
|
|
479
|
-
isSystemFKField(fieldName) {
|
|
480
|
-
return SYSTEM_FK_SKIP_PATTERNS.some(pattern => pattern.test(fieldName));
|
|
481
|
-
}
|
|
482
|
-
/** Check if an entity should be skipped based on filter options. */
|
|
483
|
-
shouldSkipEntity(entityName, options) {
|
|
484
|
-
if (options.ExcludeEntities.length > 0 && options.ExcludeEntities.includes(entityName)) {
|
|
485
|
-
return true;
|
|
486
|
-
}
|
|
487
|
-
if (options.EntityFilter.length > 0 && !options.EntityFilter.includes(entityName)) {
|
|
488
|
-
return true;
|
|
489
|
-
}
|
|
490
|
-
return false;
|
|
491
|
-
}
|
|
492
|
-
/** Check if an entity has a soft delete field. */
|
|
493
|
-
entityHasSoftDelete(entityInfo) {
|
|
494
|
-
return entityInfo.Fields.some(f => f.Name === '__mj_DeletedAt');
|
|
495
|
-
}
|
|
496
|
-
/** Apply defaults to walk options. */
|
|
497
|
-
resolveDefaults(options) {
|
|
498
|
-
return {
|
|
499
|
-
MaxDepth: options.MaxDepth ?? 10,
|
|
500
|
-
EntityFilter: options.EntityFilter ?? [],
|
|
501
|
-
ExcludeEntities: options.ExcludeEntities ?? [],
|
|
502
|
-
IncludeDeleted: options.IncludeDeleted ?? false,
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
}
|
|
5
|
+
export { DependencyGraphWalker } from '@memberjunction/record-graph';
|
|
506
6
|
//# sourceMappingURL=DependencyGraphWalker.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DependencyGraphWalker.js","sourceRoot":"","sources":["../src/DependencyGraphWalker.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DependencyGraphWalker.js","sourceRoot":"","sources":["../src/DependencyGraphWalker.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC"}
|
package/dist/DiffEngine.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Metadata, RunView, LogError, } from '@memberjunction/core';
|
|
2
2
|
import { UUIDsEqual } from '@memberjunction/global';
|
|
3
|
-
import { ENTITY_VERSION_LABEL_ITEMS, ENTITY_VERSION_LABELS, ENTITY_RECORD_CHANGES,
|
|
3
|
+
import { ENTITY_VERSION_LABEL_ITEMS, ENTITY_VERSION_LABELS, ENTITY_RECORD_CHANGES, SqlEquals, LoadRecordChangeSnapshot, LoadEntityById, } from './constants.js';
|
|
4
4
|
/**
|
|
5
5
|
* Compares state between two version labels, or between a label and the
|
|
6
6
|
* current live state, producing a structured diff grouped by entity.
|
|
@@ -16,7 +16,7 @@ export class DiffEngine {
|
|
|
16
16
|
async DiffLabels(fromLabelId, toLabelId, contextUser) {
|
|
17
17
|
// Short-circuit: identical labels produce an empty diff
|
|
18
18
|
if (UUIDsEqual(fromLabelId, toLabelId)) {
|
|
19
|
-
const label = await
|
|
19
|
+
const label = await LoadEntityById(ENTITY_VERSION_LABELS, fromLabelId, contextUser);
|
|
20
20
|
const labelName = label ? label.Name : fromLabelId;
|
|
21
21
|
return {
|
|
22
22
|
FromLabelID: fromLabelId,
|
|
@@ -28,8 +28,8 @@ export class DiffEngine {
|
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
const [fromLabel, toLabel] = await Promise.all([
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
LoadEntityById(ENTITY_VERSION_LABELS, fromLabelId, contextUser),
|
|
32
|
+
LoadEntityById(ENTITY_VERSION_LABELS, toLabelId, contextUser),
|
|
33
33
|
]);
|
|
34
34
|
if (!fromLabel)
|
|
35
35
|
throw new Error(`Version label '${fromLabelId}' not found`);
|
|
@@ -59,7 +59,7 @@ export class DiffEngine {
|
|
|
59
59
|
* existing are reported as Deleted.
|
|
60
60
|
*/
|
|
61
61
|
async DiffLabelToCurrentState(labelId, contextUser) {
|
|
62
|
-
const label = await
|
|
62
|
+
const label = await LoadEntityById(ENTITY_VERSION_LABELS, labelId, contextUser);
|
|
63
63
|
if (!label)
|
|
64
64
|
throw new Error(`Version label '${labelId}' not found`);
|
|
65
65
|
const fromIndex = await this.buildSnapshotIndex(labelId, contextUser);
|
|
@@ -87,9 +87,9 @@ export class DiffEngine {
|
|
|
87
87
|
return null;
|
|
88
88
|
const rv = new RunView();
|
|
89
89
|
const filter = [
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
SqlEquals('VersionLabelID', labelId),
|
|
91
|
+
SqlEquals('EntityID', entityInfo.ID),
|
|
92
|
+
SqlEquals('RecordID', recordId),
|
|
93
93
|
].join(' AND ');
|
|
94
94
|
const result = await rv.RunView({
|
|
95
95
|
EntityName: ENTITY_VERSION_LABEL_ITEMS,
|
|
@@ -113,7 +113,7 @@ export class DiffEngine {
|
|
|
113
113
|
const rv = new RunView();
|
|
114
114
|
const result = await rv.RunView({
|
|
115
115
|
EntityName: ENTITY_VERSION_LABEL_ITEMS,
|
|
116
|
-
ExtraFilter:
|
|
116
|
+
ExtraFilter: SqlEquals('VersionLabelID', labelId),
|
|
117
117
|
Fields: ['ID', 'RecordChangeID', 'EntityID', 'RecordID'],
|
|
118
118
|
ResultType: 'simple',
|
|
119
119
|
}, contextUser);
|
|
@@ -165,8 +165,8 @@ export class DiffEngine {
|
|
|
165
165
|
*/
|
|
166
166
|
async findLatestRecordChange(rv, entityId, recordId, contextUser) {
|
|
167
167
|
const filter = [
|
|
168
|
-
|
|
169
|
-
|
|
168
|
+
SqlEquals('EntityID', entityId),
|
|
169
|
+
SqlEquals('RecordID', recordId),
|
|
170
170
|
].join(' AND ');
|
|
171
171
|
const result = await rv.RunView({
|
|
172
172
|
EntityName: ENTITY_RECORD_CHANGES,
|
|
@@ -257,7 +257,7 @@ export class DiffEngine {
|
|
|
257
257
|
* Build a diff result for a record that was added.
|
|
258
258
|
*/
|
|
259
259
|
async buildAddedDiff(entityName, recordId, toChangeId, contextUser) {
|
|
260
|
-
const toSnapshot = await
|
|
260
|
+
const toSnapshot = await LoadRecordChangeSnapshot(toChangeId, contextUser);
|
|
261
261
|
return {
|
|
262
262
|
RecordID: recordId,
|
|
263
263
|
EntityName: entityName,
|
|
@@ -271,7 +271,7 @@ export class DiffEngine {
|
|
|
271
271
|
* Build a diff result for a record that was deleted.
|
|
272
272
|
*/
|
|
273
273
|
async buildDeletedDiff(entityName, recordId, fromChangeId, contextUser) {
|
|
274
|
-
const fromSnapshot = await
|
|
274
|
+
const fromSnapshot = await LoadRecordChangeSnapshot(fromChangeId, contextUser);
|
|
275
275
|
return {
|
|
276
276
|
RecordID: recordId,
|
|
277
277
|
EntityName: entityName,
|
|
@@ -299,8 +299,8 @@ export class DiffEngine {
|
|
|
299
299
|
*/
|
|
300
300
|
async buildModifiedDiff(entityName, recordId, fromChangeId, toChangeId, contextUser) {
|
|
301
301
|
const [fromSnapshot, toSnapshot] = await Promise.all([
|
|
302
|
-
|
|
303
|
-
|
|
302
|
+
LoadRecordChangeSnapshot(fromChangeId, contextUser),
|
|
303
|
+
LoadRecordChangeSnapshot(toChangeId, contextUser),
|
|
304
304
|
]);
|
|
305
305
|
const fieldChanges = this.compareSnapshots(fromSnapshot, toSnapshot);
|
|
306
306
|
const diffType = fieldChanges.length > 0 ? 'Modified' : 'Unchanged';
|
|
@@ -321,7 +321,7 @@ export class DiffEngine {
|
|
|
321
321
|
const rv = new RunView();
|
|
322
322
|
const result = await rv.RunView({
|
|
323
323
|
EntityName: ENTITY_RECORD_CHANGES,
|
|
324
|
-
ExtraFilter:
|
|
324
|
+
ExtraFilter: SqlEquals('ID', recordChangeId),
|
|
325
325
|
Fields: ['ID', 'ChangedAt'],
|
|
326
326
|
MaxRows: 1,
|
|
327
327
|
ResultType: 'simple',
|
|
@@ -329,7 +329,7 @@ export class DiffEngine {
|
|
|
329
329
|
if (!result.Success || result.Results.length === 0)
|
|
330
330
|
return null;
|
|
331
331
|
const changedAt = result.Results[0].ChangedAt;
|
|
332
|
-
const parsed = await
|
|
332
|
+
const parsed = await LoadRecordChangeSnapshot(recordChangeId, contextUser);
|
|
333
333
|
if (!parsed)
|
|
334
334
|
return null;
|
|
335
335
|
return {
|