@memberjunction/record-cloning-base 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 (59) hide show
  1. package/LICENSE +183 -0
  2. package/README.md +28 -43
  3. package/dist/CloneConfigValidator.d.ts +109 -0
  4. package/dist/CloneConfigValidator.d.ts.map +1 -0
  5. package/dist/CloneConfigValidator.js +348 -0
  6. package/dist/CloneConfigValidator.js.map +1 -0
  7. package/dist/CloneFieldMapper.d.ts +103 -0
  8. package/dist/CloneFieldMapper.d.ts.map +1 -0
  9. package/dist/CloneFieldMapper.js +369 -0
  10. package/dist/CloneFieldMapper.js.map +1 -0
  11. package/dist/ClonePlanHash.d.ts +27 -0
  12. package/dist/ClonePlanHash.d.ts.map +1 -0
  13. package/dist/ClonePlanHash.js +130 -0
  14. package/dist/ClonePlanHash.js.map +1 -0
  15. package/dist/ClonePolicyResolver.d.ts +100 -0
  16. package/dist/ClonePolicyResolver.d.ts.map +1 -0
  17. package/dist/ClonePolicyResolver.js +247 -0
  18. package/dist/ClonePolicyResolver.js.map +1 -0
  19. package/dist/EffectiveOptions.d.ts +78 -0
  20. package/dist/EffectiveOptions.d.ts.map +1 -0
  21. package/dist/EffectiveOptions.js +156 -0
  22. package/dist/EffectiveOptions.js.map +1 -0
  23. package/dist/ExclusionClasses.d.ts +27 -0
  24. package/dist/ExclusionClasses.d.ts.map +1 -0
  25. package/dist/ExclusionClasses.js +81 -0
  26. package/dist/ExclusionClasses.js.map +1 -0
  27. package/dist/FieldMeta.d.ts +15 -0
  28. package/dist/FieldMeta.d.ts.map +1 -0
  29. package/dist/FieldMeta.js +41 -0
  30. package/dist/FieldMeta.js.map +1 -0
  31. package/dist/JsonRemapEngine.d.ts +42 -0
  32. package/dist/JsonRemapEngine.d.ts.map +1 -0
  33. package/dist/JsonRemapEngine.js +251 -0
  34. package/dist/JsonRemapEngine.js.map +1 -0
  35. package/dist/NameTemplate.d.ts +56 -0
  36. package/dist/NameTemplate.d.ts.map +1 -0
  37. package/dist/NameTemplate.js +193 -0
  38. package/dist/NameTemplate.js.map +1 -0
  39. package/dist/RowExclusion.d.ts +9 -0
  40. package/dist/RowExclusion.d.ts.map +1 -0
  41. package/dist/RowExclusion.js +19 -0
  42. package/dist/RowExclusion.js.map +1 -0
  43. package/dist/SensitiveValues.d.ts +12 -0
  44. package/dist/SensitiveValues.d.ts.map +1 -0
  45. package/dist/SensitiveValues.js +25 -0
  46. package/dist/SensitiveValues.js.map +1 -0
  47. package/dist/UniqueKeyClassifier.d.ts +52 -0
  48. package/dist/UniqueKeyClassifier.d.ts.map +1 -0
  49. package/dist/UniqueKeyClassifier.js +161 -0
  50. package/dist/UniqueKeyClassifier.js.map +1 -0
  51. package/dist/index.d.ts +19 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +19 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/types.d.ts +260 -0
  56. package/dist/types.d.ts.map +1 -0
  57. package/dist/types.js +31 -0
  58. package/dist/types.js.map +1 -0
  59. package/package.json +31 -8
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @file FieldMeta.ts
3
+ * Builds the field metadata the field mapper and the config validator read, from an entity's
4
+ * `EntityInfo`. The planner and the Entities-form cloning panel both use it, so a field the engine
5
+ * treats as automatic (key, __mj timestamps, read-only view columns) is treated the same way by
6
+ * validation in the UI.
7
+ */
8
+ import type { EntityInfo, IEntityCloneConfiguration } from '@memberjunction/core';
9
+ import type { FieldMappingFieldMeta } from './CloneFieldMapper.js';
10
+ import type { CloneConfigEntityMeta } from './CloneConfigValidator.js';
11
+ /** Field metadata for every field of `entity`, in the shape `MapFieldsForClone` and `CloneConfigValidator` expect. */
12
+ export declare function FieldMetaFromEntity(entity: EntityInfo): FieldMappingFieldMeta[];
13
+ /** The validator's view of `entity` with `config` as its clone configuration. */
14
+ export declare function CloneConfigMetaFromEntity(entity: EntityInfo, config?: IEntityCloneConfiguration | null): CloneConfigEntityMeta;
15
+ //# sourceMappingURL=FieldMeta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FieldMeta.d.ts","sourceRoot":"","sources":["../src/FieldMeta.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AAClF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAEpE,sHAAsH;AACtH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,GAAG,qBAAqB,EAAE,CAqB/E;AAED,iFAAiF;AACjF,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,yBAAyB,GAAG,IAAI,GAAG,qBAAqB,CAQ9H"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @file FieldMeta.ts
3
+ * Builds the field metadata the field mapper and the config validator read, from an entity's
4
+ * `EntityInfo`. The planner and the Entities-form cloning panel both use it, so a field the engine
5
+ * treats as automatic (key, __mj timestamps, read-only view columns) is treated the same way by
6
+ * validation in the UI.
7
+ */
8
+ /** Field metadata for every field of `entity`, in the shape `MapFieldsForClone` and `CloneConfigValidator` expect. */
9
+ export function FieldMetaFromEntity(entity) {
10
+ return entity.Fields.map((f) => ({
11
+ Name: f.Name,
12
+ IsPrimaryKey: f.IsPrimaryKey,
13
+ IsIdentity: f.AutoIncrement === true,
14
+ IsNameField: entity.NameField?.Name.toLowerCase() === f.Name.toLowerCase() || f.Name.toLowerCase() === 'name',
15
+ IsUnique: f.IsUnique,
16
+ RelatedEntityID: f.RelatedEntityID,
17
+ RelatedEntity: f.RelatedEntity,
18
+ RelatedEntityJoinField: f.RelatedEntityFieldName,
19
+ EntityIDFieldName: f.EntityIDFieldName,
20
+ Type: f.Type,
21
+ IsSPParameter: (upd) => (f.IsSPParameter ? f.IsSPParameter(upd) : true),
22
+ DefaultValue: f.DefaultValue,
23
+ AllowsNull: f.AllowsNull,
24
+ IsCreatedAtField: f.Name === '__mj_CreatedAt',
25
+ IsUpdatedAtField: f.Name === '__mj_UpdatedAt',
26
+ IsSoftDeleteField: false,
27
+ Encrypted: f.Encrypt === true,
28
+ MaxLength: f.MaxLength,
29
+ }));
30
+ }
31
+ /** The validator's view of `entity` with `config` as its clone configuration. */
32
+ export function CloneConfigMetaFromEntity(entity, config) {
33
+ return {
34
+ Name: entity.Name,
35
+ // Same field flags the planner uses, so automatic fields aren't reported as unclassified.
36
+ Fields: FieldMetaFromEntity(entity),
37
+ Relationships: (entity.RelatedEntities ?? []).map((r) => ({ ID: r.ID, RelatedEntity: r.RelatedEntity, RelatedEntityJoinField: r.RelatedEntityJoinField })),
38
+ CloneConfiguration: (config ?? undefined),
39
+ };
40
+ }
41
+ //# sourceMappingURL=FieldMeta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FieldMeta.js","sourceRoot":"","sources":["../src/FieldMeta.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,sHAAsH;AACtH,MAAM,UAAU,mBAAmB,CAAC,MAAkB;IAClD,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC7B,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,YAAY,EAAE,CAAC,CAAC,YAAY;QAC5B,UAAU,EAAE,CAAC,CAAC,aAAa,KAAK,IAAI;QACpC,WAAW,EAAE,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,MAAM;QAC7G,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,eAAe,EAAE,CAAC,CAAC,eAAe;QAClC,aAAa,EAAE,CAAC,CAAC,aAAa;QAC9B,sBAAsB,EAAE,CAAC,CAAC,sBAAsB;QAChD,iBAAiB,EAAE,CAAC,CAAC,iBAAiB;QACtC,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,aAAa,EAAE,CAAC,GAAY,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAChF,YAAY,EAAE,CAAC,CAAC,YAAY;QAC5B,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,gBAAgB,EAAE,CAAC,CAAC,IAAI,KAAK,gBAAgB;QAC7C,gBAAgB,EAAE,CAAC,CAAC,IAAI,KAAK,gBAAgB;QAC7C,iBAAiB,EAAE,KAAK;QACxB,SAAS,EAAE,CAAC,CAAC,OAAO,KAAK,IAAI;QAC7B,SAAS,EAAE,CAAC,CAAC,SAAS;KACzB,CAAC,CAAC,CAAC;AACR,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,yBAAyB,CAAC,MAAkB,EAAE,MAAyC;IACnG,OAAO;QACH,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,0FAA0F;QAC1F,MAAM,EAAE,mBAAmB,CAAC,MAAM,CAAC;QACnC,aAAa,EAAE,CAAC,MAAM,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,aAAa,EAAE,CAAC,CAAC,aAAa,EAAE,sBAAsB,EAAE,CAAC,CAAC,sBAAsB,EAAE,CAAC,CAAC;QAC1J,kBAAkB,EAAE,CAAC,MAAM,IAAI,SAAS,CAA2D;KACtG,CAAC;AACN,CAAC"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @file JsonRemapEngine.ts
3
+ * Path-based JSON key remapping, regeneration, and container cleanup.
4
+ * Client-safe with built-in presets for dashboard UI configs and scheduled jobs.
5
+ * @see plans/record-cloning/README.md §7.6, §13.1
6
+ */
7
+ import { CloneWarning } from './types.js';
8
+ export type JsonRemapMode = 'remap' | 'reuse' | 'regenerate' | 'null' | 'drop';
9
+ export interface JsonRemapRule {
10
+ Path: string;
11
+ Mode: JsonRemapMode;
12
+ TargetEntityName?: string;
13
+ OnMissing?: 'reuse' | 'drop';
14
+ CleanEmptyContainers?: boolean;
15
+ }
16
+ export interface JsonRemapOptions {
17
+ Rules?: JsonRemapRule[];
18
+ Preset?: 'dashboard-ui-config' | 'scheduled-job-configuration';
19
+ KeyMap?: Record<string, string>;
20
+ OnMalformed?: 'block' | 'copy' | 'null';
21
+ CleanEmptyContainers?: boolean;
22
+ }
23
+ export interface JsonRemapResult {
24
+ Success: boolean;
25
+ Output: unknown;
26
+ JsonString: string;
27
+ DropCount: number;
28
+ PathsModified: string[];
29
+ Blocked: boolean;
30
+ Warning?: CloneWarning;
31
+ }
32
+ /** Mints a v4 UUID through MJGlobal's `Uuidv4` (crypto-backed; never `Math.random`). */
33
+ export declare function GenerateUUID(): string;
34
+ /**
35
+ * Built-in presets for common MemberJunction JSON columns.
36
+ */
37
+ export declare const JSON_REMAP_PRESETS: Record<string, JsonRemapRule[]>;
38
+ /**
39
+ * Parses and executes JSON remap rules on an input JSON string or object.
40
+ */
41
+ export declare function ApplyJsonRemap(input: string | Record<string, unknown> | unknown[], options: JsonRemapOptions): JsonRemapResult;
42
+ //# sourceMappingURL=JsonRemapEngine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JsonRemapEngine.d.ts","sourceRoot":"","sources":["../src/JsonRemapEngine.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,OAAO,GAAG,YAAY,GAAG,MAAM,GAAG,MAAM,CAAC;AAE/E,MAAM,WAAW,aAAa;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,aAAa,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC7B,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,gBAAgB;IAC7B,KAAK,CAAC,EAAE,aAAa,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,qBAAqB,GAAG,6BAA6B,CAAC;IAC/D,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IACxC,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,YAAY,CAAC;CAC1B;AAED,wFAAwF;AACxF,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,EAAE,CAe9D,CAAC;AAEF;;GAEG;AACH,wBAAgB,cAAc,CAC1B,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,EAAE,EACnD,OAAO,EAAE,gBAAgB,GAC1B,eAAe,CA+LjB"}
@@ -0,0 +1,251 @@
1
+ /**
2
+ * @file JsonRemapEngine.ts
3
+ * Path-based JSON key remapping, regeneration, and container cleanup.
4
+ * Client-safe with built-in presets for dashboard UI configs and scheduled jobs.
5
+ * @see plans/record-cloning/README.md §7.6, §13.1
6
+ */
7
+ import { Uuidv4 } from '@memberjunction/global';
8
+ /** Mints a v4 UUID through MJGlobal's `Uuidv4` (crypto-backed; never `Math.random`). */
9
+ export function GenerateUUID() {
10
+ return Uuidv4();
11
+ }
12
+ /**
13
+ * Built-in presets for common MemberJunction JSON columns.
14
+ */
15
+ export const JSON_REMAP_PRESETS = {
16
+ 'dashboard-ui-config': [
17
+ { Path: 'content[*].id', Mode: 'regenerate' },
18
+ { Path: 'content[*].content[*].id', Mode: 'regenerate' },
19
+ { Path: 'content[*].componentState.config.viewId', Mode: 'remap', OnMissing: 'reuse' },
20
+ { Path: 'content[*].componentState.config.queryId', Mode: 'remap', OnMissing: 'reuse' },
21
+ { Path: 'content[*].content[*].componentState.config.viewId', Mode: 'remap', OnMissing: 'reuse' },
22
+ { Path: 'content[*].content[*].componentState.config.queryId', Mode: 'remap', OnMissing: 'reuse' },
23
+ ],
24
+ 'scheduled-job-configuration': [
25
+ { Path: 'ConversationID', Mode: 'null' },
26
+ { Path: 'ActionParamID', Mode: 'remap', OnMissing: 'reuse' },
27
+ { Path: 'Params[*].ActionParamID', Mode: 'remap', OnMissing: 'reuse' },
28
+ { Path: 'Params[*].ParamID', Mode: 'remap', OnMissing: 'reuse' },
29
+ ],
30
+ };
31
+ /**
32
+ * Parses and executes JSON remap rules on an input JSON string or object.
33
+ */
34
+ export function ApplyJsonRemap(input, options) {
35
+ const onMalformed = options.OnMalformed || 'block';
36
+ let data;
37
+ const isStringInput = typeof input === 'string';
38
+ if (isStringInput) {
39
+ if (!input || input.trim() === '') {
40
+ return {
41
+ Success: true,
42
+ Output: input,
43
+ JsonString: input,
44
+ DropCount: 0,
45
+ PathsModified: [],
46
+ Blocked: false,
47
+ };
48
+ }
49
+ try {
50
+ data = JSON.parse(input);
51
+ }
52
+ catch (err) {
53
+ if (onMalformed === 'copy') {
54
+ return {
55
+ Success: true,
56
+ Output: input,
57
+ JsonString: input,
58
+ DropCount: 0,
59
+ PathsModified: [],
60
+ Blocked: false,
61
+ Warning: {
62
+ Code: 'PAYLOAD_DROPPED',
63
+ Severity: 'Warning',
64
+ Message: 'Malformed JSON copied as-is per OnMalformed="copy".',
65
+ },
66
+ };
67
+ }
68
+ if (onMalformed === 'null') {
69
+ return {
70
+ Success: true,
71
+ Output: null,
72
+ JsonString: 'null',
73
+ DropCount: 0,
74
+ PathsModified: [],
75
+ Blocked: false,
76
+ Warning: {
77
+ Code: 'PAYLOAD_DROPPED',
78
+ Severity: 'Warning',
79
+ Message: 'Malformed JSON nulled per OnMalformed="null".',
80
+ },
81
+ };
82
+ }
83
+ return {
84
+ Success: false,
85
+ Output: input,
86
+ JsonString: input,
87
+ DropCount: 0,
88
+ PathsModified: [],
89
+ Blocked: true,
90
+ Warning: {
91
+ Code: 'PAYLOAD_DROPPED',
92
+ Severity: 'Error',
93
+ Message: `Payload is malformed JSON and cannot be remapped: ${String(err)}`,
94
+ },
95
+ };
96
+ }
97
+ }
98
+ else {
99
+ // Clone input object so we don't mutate caller's state
100
+ data = JSON.parse(JSON.stringify(input));
101
+ }
102
+ // Assemble rules from preset and explicit rules
103
+ let rules = [];
104
+ if (options.Preset && JSON_REMAP_PRESETS[options.Preset]) {
105
+ rules.push(...JSON_REMAP_PRESETS[options.Preset]);
106
+ }
107
+ if (options.Rules) {
108
+ rules.push(...options.Rules);
109
+ }
110
+ let dropCount = 0;
111
+ const pathsModified = new Set();
112
+ const keyMap = options.KeyMap || {};
113
+ const globalCleanEmpty = options.CleanEmptyContainers ?? true;
114
+ for (const rule of rules) {
115
+ const segments = rule.Path.split('.');
116
+ const ruleCleanEmpty = rule.CleanEmptyContainers ?? globalCleanEmpty;
117
+ const processSegments = (current, segIdx, currentPath) => {
118
+ if (current === null || current === undefined || typeof current !== 'object') {
119
+ return { modified: false, dropped: false };
120
+ }
121
+ const segment = segments[segIdx];
122
+ const isLast = segIdx === segments.length - 1;
123
+ const isArraySegment = segment.endsWith('[*]');
124
+ const propName = isArraySegment ? segment.slice(0, -3) : segment;
125
+ if (isLast) {
126
+ if (isArraySegment) {
127
+ const arr = current[propName];
128
+ if (Array.isArray(arr)) {
129
+ let anyModified = false;
130
+ for (let i = arr.length - 1; i >= 0; i--) {
131
+ const item = arr[i];
132
+ const itemResult = evaluateTerminal(item, rule, keyMap);
133
+ if (itemResult.action === 'drop') {
134
+ arr.splice(i, 1);
135
+ dropCount++;
136
+ anyModified = true;
137
+ pathsModified.add(`${currentPath}.${propName}[${i}]`);
138
+ }
139
+ else if (itemResult.action === 'update') {
140
+ arr[i] = itemResult.newValue;
141
+ anyModified = true;
142
+ pathsModified.add(`${currentPath}.${propName}[${i}]`);
143
+ }
144
+ }
145
+ if (ruleCleanEmpty && arr.length === 0) {
146
+ delete current[propName];
147
+ pathsModified.add(`${currentPath}.${propName}`);
148
+ }
149
+ return { modified: anyModified, dropped: false };
150
+ }
151
+ }
152
+ else {
153
+ const obj = current;
154
+ if (propName in obj) {
155
+ const val = obj[propName];
156
+ const itemResult = evaluateTerminal(val, rule, keyMap);
157
+ if (itemResult.action === 'drop') {
158
+ delete obj[propName];
159
+ dropCount++;
160
+ pathsModified.add(`${currentPath}.${propName}`);
161
+ return { modified: true, dropped: true };
162
+ }
163
+ else if (itemResult.action === 'update') {
164
+ obj[propName] = itemResult.newValue;
165
+ pathsModified.add(`${currentPath}.${propName}`);
166
+ return { modified: true, dropped: false };
167
+ }
168
+ }
169
+ }
170
+ return { modified: false, dropped: false };
171
+ }
172
+ // Intermediate segment
173
+ if (isArraySegment) {
174
+ const arr = current[propName];
175
+ if (Array.isArray(arr)) {
176
+ let anyModified = false;
177
+ for (let i = arr.length - 1; i >= 0; i--) {
178
+ const nextPath = `${currentPath}.${propName}[${i}]`;
179
+ const res = processSegments(arr[i], segIdx + 1, nextPath);
180
+ if (res.dropped) {
181
+ arr.splice(i, 1);
182
+ anyModified = true;
183
+ }
184
+ else if (res.modified) {
185
+ anyModified = true;
186
+ }
187
+ }
188
+ if (ruleCleanEmpty && arr.length === 0) {
189
+ delete current[propName];
190
+ pathsModified.add(`${currentPath}.${propName}`);
191
+ }
192
+ return { modified: anyModified, dropped: false };
193
+ }
194
+ }
195
+ else {
196
+ const obj = current;
197
+ if (propName in obj && obj[propName] !== null && typeof obj[propName] === 'object') {
198
+ const nextPath = `${currentPath}.${propName}`;
199
+ const res = processSegments(obj[propName], segIdx + 1, nextPath);
200
+ if (ruleCleanEmpty && Object.keys(obj[propName]).length === 0) {
201
+ delete obj[propName];
202
+ pathsModified.add(`${currentPath}.${propName}`);
203
+ return { modified: true, dropped: true };
204
+ }
205
+ return res;
206
+ }
207
+ }
208
+ return { modified: false, dropped: false };
209
+ };
210
+ processSegments(data, 0, '$');
211
+ }
212
+ const outputJson = JSON.stringify(data);
213
+ return {
214
+ Success: true,
215
+ Output: data,
216
+ JsonString: outputJson,
217
+ DropCount: dropCount,
218
+ PathsModified: Array.from(pathsModified),
219
+ Blocked: false,
220
+ };
221
+ }
222
+ function evaluateTerminal(val, rule, keyMap) {
223
+ const mode = rule.Mode;
224
+ if (mode === 'reuse') {
225
+ return { action: 'keep' };
226
+ }
227
+ if (mode === 'null') {
228
+ return { action: 'update', newValue: null };
229
+ }
230
+ if (mode === 'drop') {
231
+ return { action: 'drop' };
232
+ }
233
+ if (mode === 'regenerate') {
234
+ return { action: 'update', newValue: GenerateUUID() };
235
+ }
236
+ if (mode === 'remap') {
237
+ if (typeof val === 'string') {
238
+ // With a target entity, only that entity's clone counts: a bare key can belong to a row
239
+ // of another entity (integer keys repeat across tables).
240
+ const key = rule.TargetEntityName ? `${rule.TargetEntityName}::${val}` : val;
241
+ if (key in keyMap)
242
+ return { action: 'update', newValue: keyMap[key] };
243
+ }
244
+ if (rule.OnMissing === 'drop') {
245
+ return { action: 'drop' };
246
+ }
247
+ return { action: 'keep' };
248
+ }
249
+ return { action: 'keep' };
250
+ }
251
+ //# sourceMappingURL=JsonRemapEngine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"JsonRemapEngine.js","sourceRoot":"","sources":["../src/JsonRemapEngine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @file NameTemplate.ts
3
+ * Naming template interpolation and collision avoidance strategy.
4
+ * Evaluates {Name}, {n}, {Date}, and {User} tokens and increments version numbers.
5
+ * @see plans/record-cloning/README.md §7.3, §13.1
6
+ */
7
+ export interface NameTemplateContext {
8
+ SourceRecordName: string;
9
+ Counter?: number;
10
+ DateStr?: string;
11
+ UserName?: string;
12
+ }
13
+ export interface NameTemplateOptions {
14
+ Template?: string;
15
+ Strategy?: 'suffix' | 'increment' | 'prompt' | 'none';
16
+ Context?: Partial<NameTemplateContext>;
17
+ /** Column width in characters (0 or unset: unlimited). The source name is shortened so every candidate fits. */
18
+ MaxLength?: number;
19
+ }
20
+ /**
21
+ * The text every candidate `FindNextAvailableName` could produce starts with, for looking up
22
+ * existing names with a prefix match. Empty when there is no stable prefix. Taken from the
23
+ * candidates themselves, so shortening a long name or trimming a template for a short column
24
+ * can't make the lookup miss a name the search would then produce.
25
+ */
26
+ export declare function NameCollisionPrefix(sourceName: string, options?: NameTemplateOptions): string;
27
+ /**
28
+ * Renders a name template by interpolating supported tokens:
29
+ * - {Name}: Source name
30
+ * - {n}: Counter number (omitted if undefined or empty if counter=1 and template doesn't force it)
31
+ * - {Date}: Current date formatted as YYYY-MM-DD
32
+ * - {User}: User name or identifier
33
+ */
34
+ export declare function RenderNameTemplate(template: string, ctx: NameTemplateContext): string;
35
+ /**
36
+ * Implements the 'increment' naming strategy.
37
+ * Detects trailing versions/integers and bumps them:
38
+ * - "Project v1" -> "Project v2"
39
+ * - "Doc 1.0" -> "Doc 1.1"
40
+ * - "Widget (2)" -> "Widget (3)"
41
+ * - "Widget" -> "Widget 2"
42
+ */
43
+ export declare function IncrementName(name: string): string;
44
+ /**
45
+ * The next name not in `existingNames`, or null when none can be found: the column is too short
46
+ * for a distinct name, or every candidate within MAX_NAME_ATTEMPTS is taken. Comparison is
47
+ * case-insensitive, like SQL Server's default collation. The source name itself is never returned
48
+ * by the renaming strategies ('none' and 'prompt' leave the name to the caller).
49
+ */
50
+ export declare function TryFindNextAvailableName(sourceName: string, existingNames: Set<string> | string[], options?: NameTemplateOptions): string | null;
51
+ /**
52
+ * Finds the next available name for `sourceName`; see {@link TryFindNextAvailableName}. Returns the
53
+ * source name unchanged when no distinct name can be found.
54
+ */
55
+ export declare function FindNextAvailableName(sourceName: string, existingNames: Set<string> | string[], options?: NameTemplateOptions): string;
56
+ //# sourceMappingURL=NameTemplate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NameTemplate.d.ts","sourceRoot":"","sources":["../src/NameTemplate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,mBAAmB;IAChC,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,QAAQ,GAAG,MAAM,CAAC;IACtD,OAAO,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACvC,gHAAgH;IAChH,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,MAAM,CAgB7F;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,mBAAmB,GAAG,MAAM,CAWrF;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA+BlD;AAyBD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACpC,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,EACrC,OAAO,CAAC,EAAE,mBAAmB,GAC9B,MAAM,GAAG,IAAI,CAiBf;AAiDD;;;GAGG;AACH,wBAAgB,qBAAqB,CACjC,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,EACrC,OAAO,CAAC,EAAE,mBAAmB,GAC9B,MAAM,CAER"}
@@ -0,0 +1,193 @@
1
+ /**
2
+ * @file NameTemplate.ts
3
+ * Naming template interpolation and collision avoidance strategy.
4
+ * Evaluates {Name}, {n}, {Date}, and {User} tokens and increments version numbers.
5
+ * @see plans/record-cloning/README.md §7.3, §13.1
6
+ */
7
+ /**
8
+ * The text every candidate `FindNextAvailableName` could produce starts with, for looking up
9
+ * existing names with a prefix match. Empty when there is no stable prefix. Taken from the
10
+ * candidates themselves, so shortening a long name or trimming a template for a short column
11
+ * can't make the lookup miss a name the search would then produce.
12
+ */
13
+ export function NameCollisionPrefix(sourceName, options) {
14
+ const strategy = options?.Strategy || 'suffix';
15
+ if (strategy === 'none' || strategy === 'prompt')
16
+ return '';
17
+ let prefix = null;
18
+ for (const candidate of nameCandidates(sourceName, options)) {
19
+ if (candidate === null)
20
+ continue;
21
+ if (prefix === null) {
22
+ prefix = candidate;
23
+ continue;
24
+ }
25
+ let i = 0;
26
+ while (i < prefix.length && i < candidate.length && prefix[i].toLowerCase() === candidate[i].toLowerCase())
27
+ i++;
28
+ prefix = prefix.slice(0, i);
29
+ if (prefix === '')
30
+ break;
31
+ }
32
+ return prefix ?? '';
33
+ }
34
+ /**
35
+ * Renders a name template by interpolating supported tokens:
36
+ * - {Name}: Source name
37
+ * - {n}: Counter number (omitted if undefined or empty if counter=1 and template doesn't force it)
38
+ * - {Date}: Current date formatted as YYYY-MM-DD
39
+ * - {User}: User name or identifier
40
+ */
41
+ export function RenderNameTemplate(template, ctx) {
42
+ const dateVal = ctx.DateStr || new Date().toISOString().slice(0, 10);
43
+ const userVal = ctx.UserName || '';
44
+ const counterVal = ctx.Counter !== undefined ? String(ctx.Counter) : '';
45
+ return template
46
+ .replace(/\{Name\}/g, () => ctx.SourceRecordName)
47
+ .replace(/\{Date\}/g, () => dateVal)
48
+ .replace(/\{User\}/g, () => userVal)
49
+ .replace(/\{n\}/g, () => counterVal)
50
+ .trim();
51
+ }
52
+ /**
53
+ * Implements the 'increment' naming strategy.
54
+ * Detects trailing versions/integers and bumps them:
55
+ * - "Project v1" -> "Project v2"
56
+ * - "Doc 1.0" -> "Doc 1.1"
57
+ * - "Widget (2)" -> "Widget (3)"
58
+ * - "Widget" -> "Widget 2"
59
+ */
60
+ export function IncrementName(name) {
61
+ // Linear scans instead of anchored lazy regexes, which backtrack polynomially on long digit or whitespace runs.
62
+ // 1. Parenthesized number at end: "Title (1)" -> "Title (2)"
63
+ if (name.endsWith(')')) {
64
+ const [beforeDigits, digits] = splitTrailingDigits(name.slice(0, -1));
65
+ if (digits && beforeDigits.endsWith('(')) {
66
+ const prefix = beforeDigits.slice(0, -1).trimEnd();
67
+ return `${prefix} (${parseInt(digits, 10) + 1})`;
68
+ }
69
+ }
70
+ // 2. Dotted version at end: "Title v1.2" or "Title 1.2" -> "Title 1.3"
71
+ const [beforeMinor, minor] = splitTrailingDigits(name);
72
+ if (minor && beforeMinor.endsWith('.')) {
73
+ const [prefix, major] = splitTrailingDigits(beforeMinor.slice(0, -1));
74
+ if (major) {
75
+ return `${prefix}${major}.${parseInt(minor, 10) + 1}`;
76
+ }
77
+ }
78
+ // 3. Trailing integer with or without 'v'/'V' after a word boundary: "Title v1" -> "Title v2", "Title 5" -> "Title 6"
79
+ if (minor) {
80
+ const withoutV = /[vV]$/.test(beforeMinor) ? beforeMinor.slice(0, -1) : beforeMinor;
81
+ if (withoutV === '' || !/\w$/.test(withoutV)) {
82
+ return `${beforeMinor}${parseInt(minor, 10) + 1}`;
83
+ }
84
+ }
85
+ // 4. No number: append " 2"
86
+ return `${name} 2`;
87
+ }
88
+ /** Splits a string into [everything before the trailing digit run, the trailing digits]. */
89
+ function splitTrailingDigits(value) {
90
+ let i = value.length;
91
+ while (i > 0 && value.charCodeAt(i - 1) >= 48 && value.charCodeAt(i - 1) <= 57) {
92
+ i--;
93
+ }
94
+ return [value.slice(0, i), value.slice(i)];
95
+ }
96
+ /** Most candidates tried before giving up; a column this crowded needs a name from the user. */
97
+ const MAX_NAME_ATTEMPTS = 1000;
98
+ /**
99
+ * `head + keep`, shortening `head` (never `keep`, which carries the counter) to fit `maxLength`.
100
+ * Null when `keep` alone doesn't fit.
101
+ */
102
+ function fitKeeping(head, keep, maxLength) {
103
+ if (!maxLength || maxLength <= 0 || head.length + keep.length <= maxLength)
104
+ return head + keep;
105
+ const room = maxLength - keep.length;
106
+ if (room < 0)
107
+ return null;
108
+ return head.slice(0, room).trimEnd() + keep;
109
+ }
110
+ /**
111
+ * The next name not in `existingNames`, or null when none can be found: the column is too short
112
+ * for a distinct name, or every candidate within MAX_NAME_ATTEMPTS is taken. Comparison is
113
+ * case-insensitive, like SQL Server's default collation. The source name itself is never returned
114
+ * by the renaming strategies ('none' and 'prompt' leave the name to the caller).
115
+ */
116
+ export function TryFindNextAvailableName(sourceName, existingNames, options) {
117
+ const strategy = options?.Strategy || 'suffix';
118
+ if (strategy === 'none' || strategy === 'prompt')
119
+ return sourceName;
120
+ const lowered = new Set([...existingNames].map((n) => n.toLowerCase()));
121
+ const seen = new Set([sourceName.toLowerCase()]);
122
+ for (const candidate of nameCandidates(sourceName, options)) {
123
+ // An increment that no longer fits ends the search; a suffix candidate that doesn't fit is skipped.
124
+ if (candidate === null) {
125
+ if (strategy === 'increment')
126
+ return null;
127
+ continue;
128
+ }
129
+ const key = candidate.toLowerCase();
130
+ if (seen.has(key))
131
+ continue;
132
+ seen.add(key);
133
+ if (!lowered.has(key))
134
+ return candidate;
135
+ }
136
+ return null;
137
+ }
138
+ /**
139
+ * Every name the renaming strategies try, in order, up to MAX_NAME_ATTEMPTS; null for one that
140
+ * can't fit `MaxLength`. Candidates may repeat (a short column cuts several to the same text).
141
+ */
142
+ function* nameCandidates(sourceName, options) {
143
+ const strategy = options?.Strategy || 'suffix';
144
+ const maxLength = options?.MaxLength;
145
+ if (strategy === 'increment') {
146
+ // Fit each step before checking it, keeping the trailing counter whole.
147
+ let next = sourceName;
148
+ for (let i = 0; i < MAX_NAME_ATTEMPTS; i++) {
149
+ next = IncrementName(next);
150
+ const closing = next.endsWith(')') ? ')' : '';
151
+ const [before, digits] = splitTrailingDigits(closing ? next.slice(0, -1) : next);
152
+ const marker = /\s?[(vV]?$/.exec(before)?.[0] ?? '';
153
+ const candidate = fitKeeping(before.slice(0, before.length - marker.length), marker + digits + closing, maxLength);
154
+ yield candidate;
155
+ if (candidate === null)
156
+ return;
157
+ }
158
+ return;
159
+ }
160
+ // 'suffix': the template, then " (n)" (or {n} in the template) until a name is free.
161
+ const template = options?.Template || 'Copy of {Name}';
162
+ const ctx = { SourceRecordName: sourceName, DateStr: options?.Context?.DateStr, UserName: options?.Context?.UserName };
163
+ const hasCounter = template.includes('{n}');
164
+ const candidateFor = (n) => {
165
+ const counter = hasCounter ? String(n) : n === 1 ? '' : ` (${n})`;
166
+ const render = (name) => RenderNameTemplate(template, { ...ctx, SourceRecordName: name, Counter: n });
167
+ const withCounter = (name) => (hasCounter ? render(name) : render(name) + counter);
168
+ const full = withCounter(sourceName);
169
+ if (!maxLength || maxLength <= 0 || full.length <= maxLength)
170
+ return full;
171
+ // Shorten the name first...
172
+ const room = sourceName.length - (full.length - maxLength);
173
+ if (room > 0) {
174
+ const shortened = withCounter(sourceName.slice(0, room).trimEnd());
175
+ // A template without {Name} doesn't get shorter with the name: fall through to trimming its text.
176
+ if (shortened.length <= maxLength)
177
+ return shortened;
178
+ }
179
+ // ...then the template's own text, keeping the counter.
180
+ const bare = hasCounter ? RenderNameTemplate(template, { ...ctx, SourceRecordName: '', Counter: undefined }) : render('');
181
+ return fitKeeping(bare, hasCounter ? String(n) : counter, maxLength);
182
+ };
183
+ for (let n = 1; n <= MAX_NAME_ATTEMPTS; n++)
184
+ yield candidateFor(n);
185
+ }
186
+ /**
187
+ * Finds the next available name for `sourceName`; see {@link TryFindNextAvailableName}. Returns the
188
+ * source name unchanged when no distinct name can be found.
189
+ */
190
+ export function FindNextAvailableName(sourceName, existingNames, options) {
191
+ return TryFindNextAvailableName(sourceName, existingNames, options) ?? sourceName;
192
+ }
193
+ //# sourceMappingURL=NameTemplate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NameTemplate.js","sourceRoot":"","sources":["../src/NameTemplate.ts"],"names":[],"mappings":"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"}