@memberjunction/record-cloning 0.0.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.
Files changed (51) hide show
  1. package/LICENSE +183 -0
  2. package/README.md +97 -43
  3. package/dist/CloneAuthorization.d.ts +50 -0
  4. package/dist/CloneAuthorization.d.ts.map +1 -0
  5. package/dist/CloneAuthorization.js +75 -0
  6. package/dist/CloneAuthorization.js.map +1 -0
  7. package/dist/CloneExecutor.d.ts +50 -0
  8. package/dist/CloneExecutor.d.ts.map +1 -0
  9. package/dist/CloneExecutor.js +350 -0
  10. package/dist/CloneExecutor.js.map +1 -0
  11. package/dist/CloneKeys.d.ts +65 -0
  12. package/dist/CloneKeys.d.ts.map +1 -0
  13. package/dist/CloneKeys.js +118 -0
  14. package/dist/CloneKeys.js.map +1 -0
  15. package/dist/CloneMaterializer.d.ts +52 -0
  16. package/dist/CloneMaterializer.d.ts.map +1 -0
  17. package/dist/CloneMaterializer.js +267 -0
  18. package/dist/CloneMaterializer.js.map +1 -0
  19. package/dist/ClonePlanHash.d.ts +11 -0
  20. package/dist/ClonePlanHash.d.ts.map +1 -0
  21. package/dist/ClonePlanHash.js +16 -0
  22. package/dist/ClonePlanHash.js.map +1 -0
  23. package/dist/ClonePlanner.d.ts +33 -0
  24. package/dist/ClonePlanner.d.ts.map +1 -0
  25. package/dist/ClonePlanner.js +870 -0
  26. package/dist/ClonePlanner.js.map +1 -0
  27. package/dist/CloneRecordProcessor.d.ts +28 -0
  28. package/dist/CloneRecordProcessor.d.ts.map +1 -0
  29. package/dist/CloneRecordProcessor.js +101 -0
  30. package/dist/CloneRecordProcessor.js.map +1 -0
  31. package/dist/RecordCloneEngine.d.ts +35 -0
  32. package/dist/RecordCloneEngine.d.ts.map +1 -0
  33. package/dist/RecordCloneEngine.js +60 -0
  34. package/dist/RecordCloneEngine.js.map +1 -0
  35. package/dist/RecordCloningStartup.d.ts +14 -0
  36. package/dist/RecordCloningStartup.d.ts.map +1 -0
  37. package/dist/RecordCloningStartup.js +32 -0
  38. package/dist/RecordCloningStartup.js.map +1 -0
  39. package/dist/__tests__/helpers/cloneAuthorizations.d.ts +7 -0
  40. package/dist/__tests__/helpers/cloneAuthorizations.d.ts.map +1 -0
  41. package/dist/__tests__/helpers/cloneAuthorizations.js +17 -0
  42. package/dist/__tests__/helpers/cloneAuthorizations.js.map +1 -0
  43. package/dist/index.d.ts +15 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +15 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/operations.d.ts +64 -0
  48. package/dist/operations.d.ts.map +1 -0
  49. package/dist/operations.js +390 -0
  50. package/dist/operations.js.map +1 -0
  51. package/package.json +32 -8
@@ -0,0 +1,267 @@
1
+ /**
2
+ * @file CloneMaterializer.ts
3
+ * Builds the in-memory entity graph for record cloning.
4
+ * Orchestrates collection routing (declared vs dynamic via DeclareRelatedRecordsDynamic),
5
+ * builds each row from NewRecord() plus the plan's field values, re-stamps join fields,
6
+ * and handles polymorphic IS-A child additions.
7
+ * @see plans/record-cloning/README.md §6, §13.1
8
+ */
9
+ import { LogError, Metadata, RelatedRecordCollection, } from '@memberjunction/core';
10
+ import { SingleKeyField, ToCompositeKey, ToRecordKeyString } from './CloneKeys.js';
11
+ /** Writes every column of a planned target key (single or composite) onto the new row. */
12
+ function applyTargetKey(entity, targetKey) {
13
+ if (!targetKey)
14
+ return; // server-assigned key
15
+ const ck = ToCompositeKey(entity.EntityInfo, targetKey);
16
+ for (const pair of ck.KeyValuePairs) {
17
+ // An empty column points at a parent whose key the database assigns; the save fills it in.
18
+ if (pair.Value === null || pair.Value === undefined || pair.Value === '')
19
+ continue;
20
+ entity.Set(pair.FieldName, pair.Value);
21
+ }
22
+ }
23
+ export class CloneMaterializer {
24
+ constructor(provider) {
25
+ this._provider = provider;
26
+ }
27
+ get Provider() {
28
+ return this._provider ?? Metadata.Provider;
29
+ }
30
+ /**
31
+ * Materializes a ClonePlan into memory.
32
+ */
33
+ async Materialize(plan, contextUser) {
34
+ const md = this.Provider;
35
+ const stagedEntities = new Map();
36
+ const sidecarEntities = [];
37
+ const prerequisiteEntities = [];
38
+ // 1. Build and Initialize Root
39
+ const rootPlanNode = plan.Nodes.find((n) => n.Depth === 0);
40
+ if (!rootPlanNode) {
41
+ throw new Error(`Plan contains no root node (Depth === 0).`);
42
+ }
43
+ const rootEntity = await md.GetEntityObject(rootPlanNode.EntityName, contextUser);
44
+ if (!rootEntity) {
45
+ throw new Error(`Failed to instantiate entity object for root '${rootPlanNode.EntityName}'.`);
46
+ }
47
+ rootEntity.NewRecord();
48
+ // The plan's field values are the whole row: excluded and denied fields keep the column default.
49
+ this.applyFieldChanges(rootEntity, rootPlanNode);
50
+ // Assign the planned target key (minted UUID or derived composite; none when server-assigned)
51
+ applyTargetKey(rootEntity, rootPlanNode.TargetKey);
52
+ const rootNodeKey = rootPlanNode.NodeKey ?? rootPlanNode.Key;
53
+ stagedEntities.set(rootNodeKey, rootEntity);
54
+ // Rows that point at another new row through a column other than their join field (a step
55
+ // path's DestinationStepID, a step's SubAgentID) can't save inside the root's graph save,
56
+ // which saves each row's children before its next sibling: the row they point at may not
57
+ // exist yet. They become sidecars, saved after the graph in dependency order.
58
+ const deferred = CloneMaterializer.rowsToDefer(plan);
59
+ const sidecarKeys = new Map();
60
+ // 2. Build Children by Depth (Depth 1, 2, ...)
61
+ const maxDepth = Math.max(...plan.Nodes.map((n) => n.Depth));
62
+ for (let d = 1; d <= maxDepth; d++) {
63
+ const nodesAtDepth = plan.Nodes.filter((n) => n.Depth === d && n.Action === 'Create');
64
+ for (const childNode of nodesAtDepth) {
65
+ const childNodeKey = childNode.NodeKey ?? childNode.Key;
66
+ // Find discovering edge pointing to this child
67
+ const edge = plan.Edges.find((e) => e.ToKey === childNodeKey && e.Policy === 'Deep');
68
+ if (!edge) {
69
+ continue;
70
+ }
71
+ const parentEntity = stagedEntities.get(edge.FromKey);
72
+ if (!parentEntity) {
73
+ throw new Error(`Parent entity not found in staged graph for edge '${edge.FromKey}' -> '${childNodeKey}'.`);
74
+ }
75
+ // Determine or establish collection companion
76
+ let childEntity;
77
+ if (edge.Kind === 'ForwardFK') {
78
+ // ForwardFK targets are prerequisite entities that must exist before the parent entity can reference them
79
+ childEntity = await md.GetEntityObject(childNode.EntityName, contextUser);
80
+ childEntity.NewRecord();
81
+ prerequisiteEntities.push(childEntity);
82
+ }
83
+ else {
84
+ const collection = deferred.has(childNodeKey)
85
+ ? null
86
+ : this.resolveOrCreateCollection(parentEntity, childNode.EntityName, edge.JoinField, edge.RelationshipID);
87
+ if (collection) {
88
+ childEntity = await collection.Create();
89
+ }
90
+ else {
91
+ // Sidecar route: a deferred row, or one whose collection can't be declared
92
+ childEntity = await md.GetEntityObject(childNode.EntityName, contextUser);
93
+ childEntity.NewRecord();
94
+ sidecarEntities.push(childEntity);
95
+ sidecarKeys.set(childEntity, childNodeKey);
96
+ }
97
+ }
98
+ // The plan's field values are the whole row: excluded and denied fields keep the column default.
99
+ this.applyFieldChanges(childEntity, childNode);
100
+ // Assign the planned target key (minted UUID or derived composite; none when server-assigned)
101
+ applyTargetKey(childEntity, childNode.TargetKey);
102
+ // CRITICAL RULE (§6.7): Re-set the join field after the planned values.
103
+ // Foreign keys reference a single-column key, so both sides of the join use that column.
104
+ if (edge.Kind === 'ForwardFK') {
105
+ // In a ForwardFK, parentEntity owns the FK pointing to childEntity
106
+ const childKeyField = SingleKeyField(childEntity.EntityInfo, 'Pointing a row at its cloned prerequisite');
107
+ parentEntity.Set(edge.JoinField, childEntity.Get(childKeyField));
108
+ }
109
+ else if (edge.Kind === 'SoftLink') {
110
+ // Polymorphic EntityID/RecordID rows store the parent's record-id string, which
111
+ // works for a composite parent key too.
112
+ childEntity.Set(edge.JoinField, ToRecordKeyString(parentEntity.PrimaryKey));
113
+ }
114
+ else {
115
+ const parentKeyField = SingleKeyField(parentEntity.EntityInfo, 'Joining a cloned child to its parent');
116
+ childEntity.Set(edge.JoinField, parentEntity.Get(parentKeyField));
117
+ }
118
+ stagedEntities.set(childNodeKey, childEntity);
119
+ }
120
+ }
121
+ return {
122
+ RootEntity: rootEntity,
123
+ StagedEntities: stagedEntities,
124
+ SidecarEntities: CloneMaterializer.orderSidecars(plan, sidecarEntities, sidecarKeys),
125
+ DeferredNodeKeys: deferred,
126
+ PrerequisiteEntities: prerequisiteEntities,
127
+ };
128
+ }
129
+ /**
130
+ * Create rows with a remapped column, other than the join field to their parent, whose new
131
+ * value is another new row's key: they must save after that row, not inside the graph save.
132
+ */
133
+ static rowsToDefer(plan) {
134
+ const newRowByKey = CloneMaterializer.newRowsByTargetKey(plan);
135
+ const deferred = new Set();
136
+ for (const node of plan.Nodes) {
137
+ if (node.Depth === 0 || node.Action !== 'Create')
138
+ continue;
139
+ const nodeKey = node.NodeKey ?? node.Key;
140
+ const edge = plan.Edges.find((e) => e.ToKey === nodeKey && e.Policy === 'Deep');
141
+ if (!edge || edge.Kind === 'ForwardFK')
142
+ continue;
143
+ const pointsAtNewRow = CloneMaterializer.remappedTargets(node, edge.JoinField, newRowByKey).some((target) => target !== nodeKey);
144
+ if (pointsAtNewRow)
145
+ deferred.add(nodeKey);
146
+ }
147
+ return deferred;
148
+ }
149
+ /** New rows by their planned key, lower-cased, for matching a remapped column's value. */
150
+ static newRowsByTargetKey(plan) {
151
+ const byKey = new Map();
152
+ for (const node of plan.Nodes) {
153
+ if (node.Action !== 'Create')
154
+ continue;
155
+ const key = ToRecordKeyString(node.TargetKey);
156
+ if (key)
157
+ byKey.set(key.toLowerCase(), node.NodeKey ?? node.Key);
158
+ }
159
+ return byKey;
160
+ }
161
+ /** Plan nodes a row points at through remapped columns other than `joinField`. */
162
+ static remappedTargets(node, joinField, newRowByKey) {
163
+ const targets = [];
164
+ for (const change of node.FieldChanges) {
165
+ if (change.Kind !== 'Remap' || change.Field.toLowerCase() === joinField.toLowerCase())
166
+ continue;
167
+ if (typeof change.NewValue !== 'string')
168
+ continue;
169
+ const target = newRowByKey.get(change.NewValue.toLowerCase());
170
+ if (target)
171
+ targets.push(target);
172
+ }
173
+ return targets;
174
+ }
175
+ /**
176
+ * Sidecars in an order where each saves after the sidecars it depends on: its parent, when
177
+ * the parent is a sidecar too, and the new rows its remapped columns point at. Rows saved in
178
+ * the graph save come first anyway. Stable: independent sidecars keep their planned order.
179
+ */
180
+ static orderSidecars(plan, sidecars, keys) {
181
+ if (sidecars.length < 2)
182
+ return sidecars;
183
+ const newRowByKey = CloneMaterializer.newRowsByTargetKey(plan);
184
+ const sidecarKeys = new Set(keys.values());
185
+ const dependsOn = new Map();
186
+ for (const [, nodeKey] of keys) {
187
+ const node = plan.Nodes.find((n) => (n.NodeKey ?? n.Key) === nodeKey);
188
+ const edge = plan.Edges.find((e) => e.ToKey === nodeKey && e.Policy === 'Deep');
189
+ if (!node || !edge)
190
+ continue;
191
+ const deps = [edge.FromKey, ...CloneMaterializer.remappedTargets(node, edge.JoinField, newRowByKey)]
192
+ .filter((k) => k !== nodeKey && sidecarKeys.has(k));
193
+ dependsOn.set(nodeKey, deps);
194
+ }
195
+ const ordered = [];
196
+ const done = new Set();
197
+ const visiting = new Set();
198
+ const byKey = new Map([...keys].map(([entity, key]) => [key, entity]));
199
+ const visit = (key) => {
200
+ if (done.has(key) || visiting.has(key))
201
+ return; // a cycle can't all be inserted anyway; keep the planned order
202
+ visiting.add(key);
203
+ for (const dep of dependsOn.get(key) ?? [])
204
+ visit(dep);
205
+ visiting.delete(key);
206
+ done.add(key);
207
+ const entity = byKey.get(key);
208
+ if (entity)
209
+ ordered.push(entity);
210
+ };
211
+ for (const entity of sidecars) {
212
+ const key = keys.get(entity);
213
+ if (key)
214
+ visit(key);
215
+ }
216
+ return ordered;
217
+ }
218
+ /**
219
+ * Resolves an existing writable collection or declares a dynamic one on the parent entity.
220
+ */
221
+ resolveOrCreateCollection(parent, childEntityName, joinField, relationshipId) {
222
+ // 1. Check if parent already has a declared writable collection for this child
223
+ if (parent.HasCompanions) {
224
+ for (const companion of parent.Companions) {
225
+ if (companion instanceof RelatedRecordCollection) {
226
+ if (companion.RelatedEntityName.toLowerCase() === childEntityName.toLowerCase() &&
227
+ companion.RelatedEntityJoinField.toLowerCase() === joinField.toLowerCase() &&
228
+ !companion.IsReadOnly) {
229
+ return companion;
230
+ }
231
+ }
232
+ }
233
+ }
234
+ // 2. Use DeclareRelatedRecordsDynamic to declare runtime collection companion
235
+ const dynamicCollectionName = `_mj_clone_${childEntityName.replace(/[^a-zA-Z0-9_]/g, '_')}_${joinField}`;
236
+ try {
237
+ return parent.DeclareRelatedRecordsDynamic({
238
+ Name: dynamicCollectionName,
239
+ RelatedEntity: childEntityName,
240
+ RelatedEntityJoinField: joinField,
241
+ Source: 'database',
242
+ ReadOnly: false,
243
+ });
244
+ }
245
+ catch (err) {
246
+ // The row still saves, but after the parent's transaction step rather than inside its
247
+ // save (the sidecar route). Say so: it is the line between the two routes.
248
+ LogError(`[RecordCloning] Could not declare ${childEntityName} rows as a collection of ${parent.EntityInfo?.Name ?? 'the parent'}; saving them as sidecars: ${err instanceof Error ? err.message : String(err)}`);
249
+ return null;
250
+ }
251
+ }
252
+ /**
253
+ * Writes the plan's field values in pipeline order, so later stages (reset, rename, remap,
254
+ * prompt) win over the copied value. Fields the plan excludes are never set.
255
+ */
256
+ applyFieldChanges(entity, planNode) {
257
+ for (const change of planNode.FieldChanges) {
258
+ if (change.Kind === 'Excluded' || change.Kind === 'NotWritable' || change.Kind === 'DeniedRead' || change.Kind === 'DeniedCreate') {
259
+ continue;
260
+ }
261
+ if (change.NewValue !== undefined) {
262
+ entity.Set(change.Field, change.NewValue);
263
+ }
264
+ }
265
+ }
266
+ }
267
+ //# sourceMappingURL=CloneMaterializer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CloneMaterializer.js","sourceRoot":"","sources":["../src/CloneMaterializer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @file ClonePlanHash.ts
3
+ * SHA-256 of a clone plan's canonical form. The hash detects a plan that changed between review
4
+ * and Execute (`PLAN_CHANGED`); only the server computes it, and the browser echoes it back.
5
+ */
6
+ import { ClonePlanHashInput } from '@memberjunction/record-cloning-base';
7
+ /** Hex SHA-256 of `text`, encoded as UTF-8. */
8
+ export declare function Sha256Hex(text: string): string;
9
+ /** The plan hash: SHA-256 over `CanonicalPlanPayload`, which ignores target keys and masks encrypted values. */
10
+ export declare function ComputeClonePlanHash(plan: ClonePlanHashInput): string;
11
+ //# sourceMappingURL=ClonePlanHash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClonePlanHash.d.ts","sourceRoot":"","sources":["../src/ClonePlanHash.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAwB,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AAE/F,+CAA+C;AAC/C,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,gHAAgH;AAChH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,kBAAkB,GAAG,MAAM,CAErE"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @file ClonePlanHash.ts
3
+ * SHA-256 of a clone plan's canonical form. The hash detects a plan that changed between review
4
+ * and Execute (`PLAN_CHANGED`); only the server computes it, and the browser echoes it back.
5
+ */
6
+ import { createHash } from 'node:crypto';
7
+ import { CanonicalPlanPayload } from '@memberjunction/record-cloning-base';
8
+ /** Hex SHA-256 of `text`, encoded as UTF-8. */
9
+ export function Sha256Hex(text) {
10
+ return createHash('sha256').update(text, 'utf8').digest('hex');
11
+ }
12
+ /** The plan hash: SHA-256 over `CanonicalPlanPayload`, which ignores target keys and masks encrypted values. */
13
+ export function ComputeClonePlanHash(plan) {
14
+ return Sha256Hex(CanonicalPlanPayload(plan));
15
+ }
16
+ //# sourceMappingURL=ClonePlanHash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClonePlanHash.js","sourceRoot":"","sources":["../src/ClonePlanHash.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,oBAAoB,EAAsB,MAAM,qCAAqC,CAAC;AAE/F,+CAA+C;AAC/C,MAAM,UAAU,SAAS,CAAC,IAAY;IAClC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACnE,CAAC;AAED,gHAAgH;AAChH,MAAM,UAAU,oBAAoB,CAAC,IAAwB;IACzD,OAAO,SAAS,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC;AACjD,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @file ClonePlanner.ts
3
+ * Computes deterministic clone plans for entity records using graph traversal.
4
+ * Enforces caps, permissions, unique key classification, field transformations, and plan hashing.
5
+ * @see plans/record-cloning/README.md §5, §7, §13.1
6
+ */
7
+ import { IMetadataProvider, UserInfo } from '@memberjunction/core';
8
+ import { ClonePlan, RecordCloneRequest } from '@memberjunction/record-cloning-base';
9
+ export interface ClonePlannerOptions {
10
+ Provider?: IMetadataProvider;
11
+ }
12
+ export declare class ClonePlanner {
13
+ private _provider?;
14
+ constructor(options?: ClonePlannerOptions);
15
+ protected get Provider(): IMetadataProvider;
16
+ /**
17
+ * Generates a fully calculated ClonePlan for the requested record.
18
+ */
19
+ Plan(request: RecordCloneRequest, contextUser: UserInfo): Promise<ClonePlan>;
20
+ /**
21
+ * Values already taken in each field a clone renames, per entity, found with one prefix query
22
+ * per (entity, field). Without them the rename can't avoid "Sales (copy)" on the second clone.
23
+ */
24
+ private loadExistingNames;
25
+ /**
26
+ * Drops rows matching their relationship's `ExcludeRows`, with every row discovered beneath them.
27
+ * The rule is looked up in the same order as the edge's policy (`ResolveEdgePolicy`): the
28
+ * relationship's own bag, then the root's entry by relationship ID, `Child.JoinField`, then
29
+ * `Child`. One Info warning per relationship.
30
+ */
31
+ private dropExcludedRows;
32
+ }
33
+ //# sourceMappingURL=ClonePlanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClonePlanner.d.ts","sourceRoot":"","sources":["../src/ClonePlanner.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAKH,iBAAiB,EAGjB,QAAQ,EACX,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAGH,SAAS,EAST,kBAAkB,EAerB,MAAM,qCAAqC,CAAC;AAM7C,MAAM,WAAW,mBAAmB;IAChC,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAChC;AA6BD,qBAAa,YAAY;IACrB,OAAO,CAAC,SAAS,CAAC,CAAoB;gBAEnB,OAAO,CAAC,EAAE,mBAAmB;IAIhD,SAAS,KAAK,QAAQ,IAAI,iBAAiB,CAE1C;IAED;;OAEG;IACU,IAAI,CACb,OAAO,EAAE,kBAAkB,EAC3B,WAAW,EAAE,QAAQ,GACtB,OAAO,CAAC,SAAS,CAAC;IAmxBrB;;;OAGG;YACW,iBAAiB;IA4C/B;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;CAuD3B"}