@memberjunction/version-history 0.0.1 → 4.0.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/README.md +218 -34
- package/dist/DependencyGraphWalker.d.ts +165 -0
- package/dist/DependencyGraphWalker.d.ts.map +1 -0
- package/dist/DependencyGraphWalker.js +501 -0
- package/dist/DependencyGraphWalker.js.map +1 -0
- package/dist/DiffEngine.d.ts +98 -0
- package/dist/DiffEngine.d.ts.map +1 -0
- package/dist/DiffEngine.js +409 -0
- package/dist/DiffEngine.js.map +1 -0
- package/dist/LabelManager.d.ts +41 -0
- package/dist/LabelManager.d.ts.map +1 -0
- package/dist/LabelManager.js +150 -0
- package/dist/LabelManager.js.map +1 -0
- package/dist/RestoreEngine.d.ts +97 -0
- package/dist/RestoreEngine.d.ts.map +1 -0
- package/dist/RestoreEngine.js +432 -0
- package/dist/RestoreEngine.js.map +1 -0
- package/dist/SnapshotBuilder.d.ts +108 -0
- package/dist/SnapshotBuilder.d.ts.map +1 -0
- package/dist/SnapshotBuilder.js +499 -0
- package/dist/SnapshotBuilder.js.map +1 -0
- package/dist/VersionHistoryEngine.d.ts +100 -0
- package/dist/VersionHistoryEngine.d.ts.map +1 -0
- package/dist/VersionHistoryEngine.js +198 -0
- package/dist/VersionHistoryEngine.js.map +1 -0
- package/dist/constants.d.ts +62 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +139 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +283 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +26 -7
package/README.md
CHANGED
|
@@ -1,45 +1,229 @@
|
|
|
1
1
|
# @memberjunction/version-history
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Version labeling, snapshot capture, diff, and restore for MemberJunction records.
|
|
4
|
+
|
|
5
|
+
## Architecture Overview
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
VersionHistoryEngine (facade)
|
|
9
|
+
├── LabelManager — label CRUD and lifecycle
|
|
10
|
+
├── SnapshotBuilder — captures record state into label items (batched)
|
|
11
|
+
├── DiffEngine — compares snapshots between labels
|
|
12
|
+
├── RestoreEngine — applies labeled state back to records
|
|
13
|
+
└── DependencyGraphWalker — traverses entity relationships
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Dependency Graph Walker
|
|
17
|
+
|
|
18
|
+
The walker discovers all records that should be included in a version label. It
|
|
19
|
+
traverses both **reverse relationships** (child records that belong to the root)
|
|
20
|
+
and **forward references** (records the root or its children point to).
|
|
21
|
+
|
|
22
|
+
### Two Key Mechanisms
|
|
23
|
+
|
|
24
|
+
#### 1. EntityRelationship-Driven Reverse Walking
|
|
25
|
+
|
|
26
|
+
Instead of scanning every entity in the system for foreign keys that point to the
|
|
27
|
+
current entity (expensive O(N*M) scan), the walker uses the
|
|
28
|
+
**EntityRelationship metadata** that MemberJunction already maintains.
|
|
29
|
+
|
|
30
|
+
Each entity's `RelatedEntities` array defines which child entities are
|
|
31
|
+
meaningful. These are auto-generated by CodeGen when FK relationships are
|
|
32
|
+
detected, and admins can add/remove them. This means only explicitly registered
|
|
33
|
+
children are walked — not every table that happens to have a matching FK.
|
|
34
|
+
|
|
35
|
+
```mermaid
|
|
36
|
+
graph TD
|
|
37
|
+
A[AI Agents] -->|"EntityRelationship<br/>RelatedEntities"| B[AI Agent Prompts]
|
|
38
|
+
A -->|EntityRelationship| C[AI Agent Actions]
|
|
39
|
+
A -->|EntityRelationship| D[AI Agent Relationships]
|
|
40
|
+
A -->|EntityRelationship| E[AI Agent Models]
|
|
41
|
+
A -->|EntityRelationship| F[AI Agent Configurations]
|
|
42
|
+
A -.-x|"NOT walked<br/>(no EntityRelationship)"| G[Some Random Table<br/>with AgentID FK]
|
|
43
|
+
|
|
44
|
+
style G fill:#fee,stroke:#c00,stroke-dasharray: 5 5
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
#### 2. Ancestor Stack — Prevents Backtracking
|
|
48
|
+
|
|
49
|
+
The walker maintains a **stack of entity type names** representing the path from
|
|
50
|
+
root to the current node. When evaluating any relationship (reverse or forward),
|
|
51
|
+
if the target entity type is already on the ancestor stack, it is **skipped**.
|
|
52
|
+
|
|
53
|
+
This surgically prevents graph explosion without arbitrary depth limits:
|
|
54
|
+
|
|
55
|
+
```mermaid
|
|
56
|
+
graph TD
|
|
57
|
+
Agent["AI Agents<br/>(root)"] --> AP["AI Agent Prompts<br/>(reverse)"]
|
|
58
|
+
AP --> Prompt["AI Prompts<br/>(forward via PromptID)"]
|
|
59
|
+
Prompt -.->|"BLOCKED<br/>AI Agent Prompts<br/>is on ancestor stack"| AP2["Other AI Agent Prompts<br/>referencing same prompt"]
|
|
60
|
+
Prompt --> Model["AI Models<br/>(forward via DefaultModelID)"]
|
|
61
|
+
Model -.->|"BLOCKED<br/>AI Prompts<br/>is on ancestor stack"| Prompt2["Other AI Prompts<br/>using same model"]
|
|
62
|
+
|
|
63
|
+
style AP2 fill:#fee,stroke:#c00,stroke-dasharray: 5 5
|
|
64
|
+
style Prompt2 fill:#fee,stroke:#c00,stroke-dasharray: 5 5
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Walk Algorithm — Step by Step
|
|
68
|
+
|
|
69
|
+
```mermaid
|
|
70
|
+
flowchart TD
|
|
71
|
+
Start([walkChildren called]) --> DepthCheck{Depth >= MaxDepth?}
|
|
72
|
+
DepthCheck -->|Yes| Stop([Return])
|
|
73
|
+
DepthCheck -->|No| Reverse[Walk Reverse Relationships]
|
|
74
|
+
|
|
75
|
+
Reverse --> ReverseLoop{For each EntityRelationship<br/>on current entity}
|
|
76
|
+
ReverseLoop -->|Next rel| AncestorCheckR{Child entity<br/>on ancestor stack?}
|
|
77
|
+
AncestorCheckR -->|Yes| SkipR([Skip — would backtrack])
|
|
78
|
+
AncestorCheckR -->|No| LoadChildren[Load child records<br/>via RunView]
|
|
79
|
+
LoadChildren --> RegisterR[Register nodes + push<br/>child entity onto ancestors]
|
|
80
|
+
RegisterR --> RecurseR[Recurse walkChildren<br/>for each child]
|
|
81
|
+
RecurseR --> PopR[Pop child entity<br/>from ancestors]
|
|
82
|
+
PopR --> ReverseLoop
|
|
83
|
+
|
|
84
|
+
ReverseLoop -->|Done| Forward[Walk Forward References]
|
|
85
|
+
Forward --> ForwardLoop{For each FK field<br/>on current entity}
|
|
86
|
+
ForwardLoop -->|Next FK| SystemCheck{System FK?<br/>UserID, CreatedBy, etc.}
|
|
87
|
+
SystemCheck -->|Yes| SkipF([Skip — infrastructure field])
|
|
88
|
+
SystemCheck -->|No| AncestorCheckF{Target entity<br/>on ancestor stack?}
|
|
89
|
+
AncestorCheckF -->|Yes| SkipF2([Skip — would backtrack])
|
|
90
|
+
AncestorCheckF -->|No| LoadTarget[Load referenced record<br/>via RunView]
|
|
91
|
+
LoadTarget --> RegisterF[Register node + push<br/>target entity onto ancestors]
|
|
92
|
+
RegisterF --> RecurseF[Recurse walkChildren<br/>for referenced record]
|
|
93
|
+
RecurseF --> PopF[Pop target entity<br/>from ancestors]
|
|
94
|
+
PopF --> ForwardLoop
|
|
95
|
+
ForwardLoop -->|Done| Stop
|
|
96
|
+
|
|
97
|
+
SkipR --> ReverseLoop
|
|
98
|
+
SkipF --> ForwardLoop
|
|
99
|
+
SkipF2 --> ForwardLoop
|
|
100
|
+
|
|
101
|
+
style SkipR fill:#fee,stroke:#c00
|
|
102
|
+
style SkipF fill:#fee,stroke:#c00
|
|
103
|
+
style SkipF2 fill:#fee,stroke:#c00
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Concrete Example — Labeling an AI Agent
|
|
107
|
+
|
|
108
|
+
Given an AI Agent with 2 prompts, 1 action, and 1 sub-agent (via AI Agent
|
|
109
|
+
Relationships), the walker produces:
|
|
110
|
+
|
|
111
|
+
```mermaid
|
|
112
|
+
graph TD
|
|
113
|
+
Root["AI Agent (root)<br/>ancestors: [AI Agents]"]
|
|
114
|
+
|
|
115
|
+
Root --> AP1["AI Agent Prompt #1<br/>(reverse)<br/>ancestors: [..., AI Agent Prompts]"]
|
|
116
|
+
Root --> AP2["AI Agent Prompt #2<br/>(reverse)"]
|
|
117
|
+
Root --> AA["AI Agent Action<br/>(reverse)<br/>ancestors: [..., AI Agent Actions]"]
|
|
118
|
+
Root --> AR["AI Agent Relationship<br/>(reverse)<br/>ancestors: [..., AI Agent Relationships]"]
|
|
119
|
+
|
|
120
|
+
AP1 --> P1["AI Prompt #1<br/>(forward via PromptID)<br/>ancestors: [..., AI Prompts]"]
|
|
121
|
+
AP2 --> P2["AI Prompt #2<br/>(forward)"]
|
|
122
|
+
AA --> Act["Action<br/>(forward via ActionID)<br/>ancestors: [..., Actions]"]
|
|
123
|
+
AR --> SubAgent["Sub-Agent<br/>(forward via SubAgentID)<br/>ancestors: [..., AI Agents]"]
|
|
124
|
+
|
|
125
|
+
P1 --> M1["AI Model<br/>(forward via DefaultModelID)"]
|
|
126
|
+
P2 --> M1
|
|
127
|
+
|
|
128
|
+
SubAgent --> SAP["Sub-Agent's Prompt<br/>(reverse)"]
|
|
129
|
+
SAP --> SP["AI Prompt #3<br/>(forward)"]
|
|
130
|
+
|
|
131
|
+
P1 -.->|"BLOCKED: AI Agent Prompts on stack"| X1["Other Agent Prompts"]
|
|
132
|
+
M1 -.->|"BLOCKED: AI Prompts on stack"| X2["Other Prompts"]
|
|
133
|
+
Act -.->|"BLOCKED: skipped via EntityRel filter"| X3["Action Params (403 rows)"]
|
|
134
|
+
|
|
135
|
+
style X1 fill:#fee,stroke:#c00,stroke-dasharray: 5 5
|
|
136
|
+
style X2 fill:#fee,stroke:#c00,stroke-dasharray: 5 5
|
|
137
|
+
style X3 fill:#fee,stroke:#c00,stroke-dasharray: 5 5
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Result**: ~15-25 targeted records instead of 1,363 from the naive approach.
|
|
141
|
+
|
|
142
|
+
### Why This Design
|
|
143
|
+
|
|
144
|
+
| Concern | Solution |
|
|
145
|
+
|---|---|
|
|
146
|
+
| Which children to walk? | **EntityRelationship** — admin-controlled, CodeGen-maintained |
|
|
147
|
+
| Preventing graph explosion? | **Ancestor stack** — blocks backtracking to any entity type on current path |
|
|
148
|
+
| Infrastructure FKs (UserID, etc.)? | **System FK skip list** — regex patterns for known infrastructure fields |
|
|
149
|
+
| Cycle detection? | **Visited set** — `entityName::recordID` prevents revisiting any record |
|
|
150
|
+
| Sub-agent recursion? | Ancestor stack is **path-based** — pops on backtrack, allowing re-entry from a different branch |
|
|
151
|
+
|
|
152
|
+
### Forward FK Skip Patterns
|
|
153
|
+
|
|
154
|
+
The following FK field name patterns are never followed during forward walking,
|
|
155
|
+
as they reference system infrastructure (Users, audit fields) rather than
|
|
156
|
+
business data:
|
|
157
|
+
|
|
158
|
+
- `CreatedByUserID`, `UpdatedByUserID`, `UserID`
|
|
159
|
+
- `ContextUserID`, `ModifiedByUserID`
|
|
160
|
+
- `CreatedBy`, `UpdatedBy`
|
|
161
|
+
- `OwnerID`, `OwnerUserID`
|
|
162
|
+
- `AssignedToID`, `AssignedToUserID`
|
|
163
|
+
- `EntityID` (polymorphic reference)
|
|
164
|
+
|
|
165
|
+
## Snapshot Builder — Batched Capture
|
|
166
|
+
|
|
167
|
+
When capturing records into a version label, the SnapshotBuilder uses **batched
|
|
168
|
+
queries** to minimize database round trips:
|
|
169
|
+
|
|
170
|
+
```mermaid
|
|
171
|
+
sequenceDiagram
|
|
172
|
+
participant SB as SnapshotBuilder
|
|
173
|
+
participant DB as Database
|
|
174
|
+
|
|
175
|
+
Note over SB: Receive flat list of N nodes
|
|
176
|
+
|
|
177
|
+
SB->>SB: Group nodes by EntityID
|
|
178
|
+
loop For each entity group
|
|
179
|
+
SB->>DB: ONE RunView: RecordChanges<br/>WHERE EntityID = X<br/>AND RecordID IN (a, b, c, ...)
|
|
180
|
+
DB-->>SB: Latest changes for all records in group
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
Note over SB: Build lookup map:<br/>entityId::recordId → RecordChange
|
|
184
|
+
|
|
185
|
+
loop For each node without a RecordChange
|
|
186
|
+
SB->>DB: Create synthetic snapshot (Save)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
loop For each node
|
|
190
|
+
SB->>DB: Create VersionLabelItem (Save)
|
|
191
|
+
end
|
|
192
|
+
```
|
|
4
193
|
|
|
5
|
-
**
|
|
194
|
+
**Before batching**: N individual RunView calls (946 for a 1363-record label).
|
|
195
|
+
**After batching**: ~5-10 RunView calls (one per unique entity type in the graph).
|
|
6
196
|
|
|
7
|
-
|
|
197
|
+
## API
|
|
8
198
|
|
|
9
|
-
|
|
199
|
+
### VersionHistoryEngine (main facade)
|
|
10
200
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
2. Enable secure, token-less publishing from CI/CD workflows
|
|
14
|
-
3. Establish provenance for packages published under this name
|
|
201
|
+
```typescript
|
|
202
|
+
const engine = new VersionHistoryEngine();
|
|
15
203
|
|
|
16
|
-
|
|
204
|
+
// Create a label with dependency walking
|
|
205
|
+
const { Label, CaptureResult } = await engine.CreateLabel({
|
|
206
|
+
Name: 'Before Refactor v2',
|
|
207
|
+
Scope: 'Record',
|
|
208
|
+
EntityName: 'AI Agents',
|
|
209
|
+
RecordKey: agentKey,
|
|
210
|
+
IncludeDependencies: true,
|
|
211
|
+
MaxDepth: 10,
|
|
212
|
+
ExcludeEntities: ['AI Agent Runs'], // skip run history
|
|
213
|
+
}, contextUser);
|
|
17
214
|
|
|
18
|
-
|
|
215
|
+
// Diff against current state
|
|
216
|
+
const diff = await engine.DiffLabelToCurrentState(Label.ID, contextUser);
|
|
19
217
|
|
|
20
|
-
|
|
218
|
+
// Restore if needed
|
|
219
|
+
const result = await engine.RestoreToLabel(Label.ID, { DryRun: true }, contextUser);
|
|
220
|
+
```
|
|
21
221
|
|
|
22
|
-
|
|
222
|
+
### WalkOptions
|
|
23
223
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
This package is a placeholder for OIDC configuration only. It:
|
|
32
|
-
- Contains no executable code
|
|
33
|
-
- Provides no functionality
|
|
34
|
-
- Should not be installed as a dependency
|
|
35
|
-
- Exists only for administrative purposes
|
|
36
|
-
|
|
37
|
-
## More Information
|
|
38
|
-
|
|
39
|
-
For more details about npm's trusted publishing feature, see:
|
|
40
|
-
- [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
|
|
41
|
-
- [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
|
|
42
|
-
|
|
43
|
-
---
|
|
44
|
-
|
|
45
|
-
**Maintained for OIDC setup purposes only**
|
|
224
|
+
| Option | Default | Description |
|
|
225
|
+
|---|---|---|
|
|
226
|
+
| `MaxDepth` | `10` | Maximum recursion depth |
|
|
227
|
+
| `EntityFilter` | `[]` | Only include these entities (empty = all) |
|
|
228
|
+
| `ExcludeEntities` | `[]` | Skip these entities entirely |
|
|
229
|
+
| `IncludeDeleted` | `false` | Include soft-deleted records |
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { CompositeKey, UserInfo } from '@memberjunction/core';
|
|
2
|
+
import { DependencyNode, WalkOptions } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Walks entity relationship graphs to discover records for version labeling.
|
|
5
|
+
*
|
|
6
|
+
* ## Two-direction traversal
|
|
7
|
+
*
|
|
8
|
+
* **Reverse**: Uses the entity's `RelatedEntities` (EntityRelationship metadata)
|
|
9
|
+
* to find child records. This is curated by CodeGen and admins — only explicitly
|
|
10
|
+
* registered One-To-Many relationships are walked.
|
|
11
|
+
*
|
|
12
|
+
* **Forward**: Scans the current entity's own FK fields to find records it
|
|
13
|
+
* references (e.g., AI Agent Prompt → AI Prompt via PromptID). Skips
|
|
14
|
+
* system/infrastructure fields (UserID, CreatedBy, etc.).
|
|
15
|
+
*
|
|
16
|
+
* ## Discovery mode — prevents explosion from forward references
|
|
17
|
+
*
|
|
18
|
+
* Records found via **reverse walk** are "owned children" and get full treatment
|
|
19
|
+
* (both reverse + forward walks). Records found via **forward walk** are
|
|
20
|
+
* "referenced records" and only get forward-only treatment (their own FK refs
|
|
21
|
+
* are followed, but their EntityRelationship children are NOT discovered).
|
|
22
|
+
*
|
|
23
|
+
* This prevents the classic explosion pattern:
|
|
24
|
+
* ```
|
|
25
|
+
* Agent → AgentPrompt → Prompt → [reverse to ALL PromptModels] → Model
|
|
26
|
+
* → [reverse to ALL AgentModels across ALL agents] → explosion!
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* With discovery mode:
|
|
30
|
+
* ```
|
|
31
|
+
* Agent → AgentPrompt(reverse,full) → Prompt(forward,forward-only)
|
|
32
|
+
* → Model(forward,forward-only) → STOP (no reverse walk on Model)
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* ## Ancestor stack — prevents backtracking
|
|
36
|
+
*
|
|
37
|
+
* A set of entity names on the current path from root to the current node is
|
|
38
|
+
* maintained. When evaluating any relationship, if the target entity type is
|
|
39
|
+
* already on the ancestor stack, it is skipped. This is a secondary safety net
|
|
40
|
+
* that prevents cycles even within full-mode walks.
|
|
41
|
+
*
|
|
42
|
+
* ## Filters
|
|
43
|
+
*
|
|
44
|
+
* - Only walks entities with `TrackRecordChanges === true`
|
|
45
|
+
* - Skips system FK fields via regex patterns (UserID, CreatedBy, etc.)
|
|
46
|
+
* - Respects `ExcludeEntities` and `EntityFilter` options
|
|
47
|
+
* - Uses a global `visited` set for record-level cycle detection
|
|
48
|
+
*/
|
|
49
|
+
export declare class DependencyGraphWalker {
|
|
50
|
+
/** Cache: entityID → reverse relationships from EntityRelationship metadata */
|
|
51
|
+
private reverseRelCache;
|
|
52
|
+
/** Cache: entityID → forward FK references from field metadata */
|
|
53
|
+
private forwardRefCache;
|
|
54
|
+
/**
|
|
55
|
+
* Walk from a root record through its relationships, building a tree of
|
|
56
|
+
* all records that should be included in a version label.
|
|
57
|
+
*
|
|
58
|
+
* @param entityName - The starting entity name
|
|
59
|
+
* @param recordKey - The starting record's primary key
|
|
60
|
+
* @param options - Controls depth, filtering, etc.
|
|
61
|
+
* @param contextUser - Server-side user context
|
|
62
|
+
* @returns The root DependencyNode with all descendants populated
|
|
63
|
+
*/
|
|
64
|
+
WalkDependents(entityName: string, recordKey: CompositeKey, options: WalkOptions, contextUser: UserInfo): Promise<DependencyNode>;
|
|
65
|
+
/**
|
|
66
|
+
* Flatten a dependency tree into a topologically sorted list.
|
|
67
|
+
* Parents appear before their children, ensuring safe restore ordering.
|
|
68
|
+
*/
|
|
69
|
+
FlattenTopological(root: DependencyNode): DependencyNode[];
|
|
70
|
+
/**
|
|
71
|
+
* Recursively discover and attach child nodes for a given parent node.
|
|
72
|
+
*
|
|
73
|
+
* The `discoveryMode` controls which directions are walked:
|
|
74
|
+
* - `'full'`: both reverse (EntityRelationship) and forward (FK refs)
|
|
75
|
+
* - `'forward-only'`: only forward FK refs — no reverse children discovered
|
|
76
|
+
*
|
|
77
|
+
* Records found via reverse walk are recursed with `'full'` mode.
|
|
78
|
+
* Records found via forward walk are recursed with `'forward-only'` mode.
|
|
79
|
+
*/
|
|
80
|
+
private walkChildren;
|
|
81
|
+
/**
|
|
82
|
+
* Walk reverse relationships: for each EntityRelationship on the parent
|
|
83
|
+
* entity, load matching child records and recurse into them.
|
|
84
|
+
*
|
|
85
|
+
* Skips any child entity type that is already on the ancestor stack,
|
|
86
|
+
* preventing backtracking (e.g., from AI Prompt back to AI Agent Prompts).
|
|
87
|
+
*
|
|
88
|
+
* Children found via reverse walk are recursed with 'full' discovery mode,
|
|
89
|
+
* since they are "owned children" that may have their own children.
|
|
90
|
+
*/
|
|
91
|
+
private walkReverseRelationships;
|
|
92
|
+
/**
|
|
93
|
+
* Walk forward FK references: for each FK field on the parent entity that
|
|
94
|
+
* points to another tracked entity, load the referenced record and recurse.
|
|
95
|
+
*
|
|
96
|
+
* Skips system/infrastructure FKs (UserID, CreatedBy, etc.) and any target
|
|
97
|
+
* entity type already on the ancestor stack.
|
|
98
|
+
*
|
|
99
|
+
* Targets found via forward walk are recursed with 'forward-only' discovery
|
|
100
|
+
* mode — they won't discover their own reverse children, preventing graph
|
|
101
|
+
* explosion from "hub" entities like AI Models.
|
|
102
|
+
*/
|
|
103
|
+
private walkForwardReferences;
|
|
104
|
+
/**
|
|
105
|
+
* Discover reverse relationships for an entity using EntityRelationship
|
|
106
|
+
* metadata. Only includes One-To-Many relationships to entities that have
|
|
107
|
+
* TrackRecordChanges enabled.
|
|
108
|
+
*
|
|
109
|
+
* Uses EntityRelationship (already loaded in memory on EntityInfo) rather
|
|
110
|
+
* than scanning all entities' FK fields. This means only relationships
|
|
111
|
+
* that CodeGen or admins have explicitly registered are walked.
|
|
112
|
+
*/
|
|
113
|
+
private discoverReverseRelationships;
|
|
114
|
+
/**
|
|
115
|
+
* Discover forward FK references on an entity — fields that point TO other
|
|
116
|
+
* entities. Only includes references to entities with TrackRecordChanges
|
|
117
|
+
* enabled, and skips system/infrastructure FK fields.
|
|
118
|
+
*/
|
|
119
|
+
private discoverForwardReferences;
|
|
120
|
+
/**
|
|
121
|
+
* Load child records for a reverse relationship.
|
|
122
|
+
* Queries the child entity where the join field matches the parent's key value.
|
|
123
|
+
*/
|
|
124
|
+
private loadChildRecords;
|
|
125
|
+
/**
|
|
126
|
+
* Load a single record's data by primary key.
|
|
127
|
+
*/
|
|
128
|
+
private loadRecordData;
|
|
129
|
+
/**
|
|
130
|
+
* Build a DependencyNode from record data, register it in the visited set,
|
|
131
|
+
* and attach it as a child of the parent. Returns null if already visited.
|
|
132
|
+
*/
|
|
133
|
+
private registerNode;
|
|
134
|
+
/** Create an empty stats accumulator. */
|
|
135
|
+
private createEmptyStats;
|
|
136
|
+
/** Increment the per-entity record counter. */
|
|
137
|
+
private incrementEntityCount;
|
|
138
|
+
/**
|
|
139
|
+
* Log a detailed summary of the walk: total records, per-entity counts,
|
|
140
|
+
* skips, and suppression stats.
|
|
141
|
+
*/
|
|
142
|
+
private logWalkSummary;
|
|
143
|
+
/** Build a unique key for the visited set: "EntityName::recordID" */
|
|
144
|
+
private visitKey;
|
|
145
|
+
/**
|
|
146
|
+
* Determine the parent key field from an EntityRelationship record.
|
|
147
|
+
* Uses EntityKeyField if specified, otherwise falls back to the first PK.
|
|
148
|
+
*/
|
|
149
|
+
private resolveParentKeyFieldFromRelationship;
|
|
150
|
+
/**
|
|
151
|
+
* Determine the target key field from a FK field definition.
|
|
152
|
+
* Uses RelatedEntityFieldName if specified, otherwise falls back to the
|
|
153
|
+
* target entity's first PK.
|
|
154
|
+
*/
|
|
155
|
+
private resolveParentKeyFieldFromFK;
|
|
156
|
+
/** Check if a field name matches a system/infrastructure FK pattern. */
|
|
157
|
+
private isSystemFKField;
|
|
158
|
+
/** Check if an entity should be skipped based on filter options. */
|
|
159
|
+
private shouldSkipEntity;
|
|
160
|
+
/** Check if an entity has a soft delete field. */
|
|
161
|
+
private entityHasSoftDelete;
|
|
162
|
+
/** Apply defaults to walk options. */
|
|
163
|
+
private resolveDefaults;
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=DependencyGraphWalker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DependencyGraphWalker.d.ts","sourceRoot":"","sources":["../src/DependencyGraphWalker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA0E,QAAQ,EAAuB,MAAM,sBAAsB,CAAC;AAC3J,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AA6FtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,qBAAa,qBAAqB;IAC9B,+EAA+E;IAC/E,OAAO,CAAC,eAAe,CAA4C;IACnE,kEAAkE;IAClE,OAAO,CAAC,eAAe,CAAyC;IAMhE;;;;;;;;;OASG;IACU,cAAc,CACvB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,YAAY,EACvB,OAAO,EAAE,WAAW,EACpB,WAAW,EAAE,QAAQ,GACtB,OAAO,CAAC,cAAc,CAAC;IA2C1B;;;OAGG;IACI,kBAAkB,CAAC,IAAI,EAAE,cAAc,GAAG,cAAc,EAAE;IAiBjE;;;;;;;;;OASG;YACW,YAAY;IA4B1B;;;;;;;;;OASG;YACW,wBAAwB;IAyCtC;;;;;;;;;;OAUG;YACW,qBAAqB;IAwDnC;;;;;;;;OAQG;IACH,OAAO,CAAC,4BAA4B;IA6BpC;;;;OAIG;IACH,OAAO,CAAC,yBAAyB;IA+BjC;;;OAGG;YACW,gBAAgB;IAsC9B;;OAEG;YACW,cAAc;IA0B5B;;;OAGG;IACH,OAAO,CAAC,YAAY;IAqCpB,yCAAyC;IACzC,OAAO,CAAC,gBAAgB;IAUxB,+CAA+C;IAC/C,OAAO,CAAC,oBAAoB;IAK5B;;;OAGG;IACH,OAAO,CAAC,cAAc;IA0BtB,qEAAqE;IACrE,OAAO,CAAC,QAAQ;IAIhB;;;OAGG;IACH,OAAO,CAAC,qCAAqC;IAU7C;;;;OAIG;IACH,OAAO,CAAC,2BAA2B;IAUnC,wEAAwE;IACxE,OAAO,CAAC,eAAe;IAIvB,oEAAoE;IACpE,OAAO,CAAC,gBAAgB;IAUxB,kDAAkD;IAClD,OAAO,CAAC,mBAAmB;IAI3B,sCAAsC;IACtC,OAAO,CAAC,eAAe;CAQ1B"}
|