@memberjunction/core 6.2.0-edge.1 → 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 (29) hide show
  1. package/dist/generic/JSONType-interfaces/IEntityConfiguration.d.ts +180 -0
  2. package/dist/generic/JSONType-interfaces/IEntityConfiguration.d.ts.map +1 -1
  3. package/dist/generic/JSONType-interfaces/IEntityFieldConfiguration.d.ts +28 -0
  4. package/dist/generic/JSONType-interfaces/IEntityFieldConfiguration.d.ts.map +1 -1
  5. package/dist/generic/JSONType-interfaces/IEntityRelationshipConfiguration.d.ts +76 -0
  6. package/dist/generic/JSONType-interfaces/IEntityRelationshipConfiguration.d.ts.map +1 -1
  7. package/dist/generic/baseEntity.d.ts +201 -11
  8. package/dist/generic/baseEntity.d.ts.map +1 -1
  9. package/dist/generic/baseEntity.js +392 -89
  10. package/dist/generic/baseEntity.js.map +1 -1
  11. package/dist/generic/databaseProviderBase.d.ts +13 -7
  12. package/dist/generic/databaseProviderBase.d.ts.map +1 -1
  13. package/dist/generic/databaseProviderBase.js +18 -9
  14. package/dist/generic/databaseProviderBase.js.map +1 -1
  15. package/dist/generic/entityConfiguration.d.ts +1 -0
  16. package/dist/generic/entityConfiguration.d.ts.map +1 -1
  17. package/dist/generic/entityConfiguration.js.map +1 -1
  18. package/dist/generic/entityInfo.d.ts +28 -1
  19. package/dist/generic/entityInfo.d.ts.map +1 -1
  20. package/dist/generic/entityInfo.js +37 -0
  21. package/dist/generic/entityInfo.js.map +1 -1
  22. package/dist/generic/interfaces.d.ts +12 -3
  23. package/dist/generic/interfaces.d.ts.map +1 -1
  24. package/dist/generic/interfaces.js.map +1 -1
  25. package/dist/generic/providerBase.d.ts +82 -1
  26. package/dist/generic/providerBase.d.ts.map +1 -1
  27. package/dist/generic/providerBase.js +154 -19
  28. package/dist/generic/providerBase.js.map +1 -1
  29. package/package.json +3 -3
@@ -31,6 +31,13 @@ export interface IEntityConfiguration {
31
31
  * Controls whether attachments are permitted and sets entity-level upload policies.
32
32
  */
33
33
  Attachments?: IEntityAttachmentsConfiguration;
34
+ /**
35
+ * Record cloning configuration.
36
+ * Omitted or Enabled=false means the entity cannot be a clone ROOT.
37
+ * It may still be cloned as a child of another root when that root's relationship
38
+ * policy says Deep, unless NotCloneable is true.
39
+ */
40
+ Clone?: IEntityCloneConfiguration;
34
41
  }
35
42
  /**
36
43
  * Configuration for entity-level file attachments.
@@ -117,4 +124,177 @@ export interface IEntityFormConfiguration {
117
124
  */
118
125
  PrimaryRelatedBudget?: number;
119
126
  }
127
+ /**
128
+ * Configuration for record cloning at the entity level.
129
+ * @see plans/record-cloning/README.md §4.1
130
+ */
131
+ export interface IEntityCloneConfiguration {
132
+ /** Schema version of this section. Default 1. */
133
+ Version?: 1;
134
+ /** Master switch for cloning this entity as a ROOT. Default false. */
135
+ Enabled?: boolean;
136
+ /** Hard refusal: this entity's rows are never created by the clone engine, as root or as child. Overrides every relationship policy. Use for audit, run, credential and metadata entities. */
137
+ NotCloneable?: boolean;
138
+ /** Shown to users and returned in warnings when NotCloneable or Enabled=false. */
139
+ NotCloneableReason?: string;
140
+ /** Authorization name checked with ancestors. Default 'Clone Records'; resolved per §9.1. */
141
+ RequiredAuthorization?: string;
142
+ /** Caps. Defaults 3 and 500. A plan that exceeds either is Blocked. */
143
+ MaxDepth?: number;
144
+ MaxRecords?: number;
145
+ /** IS-A subtype rows. Default 'include'. */
146
+ Subtypes?: 'include' | 'exclude';
147
+ /** Self-referencing IsHierarchy fields. Default 'subtree'. */
148
+ Hierarchy?: 'subtree' | 'node';
149
+ /** Inbound polymorphic EntityID/RecordID rows (tags, attachments, notes...). Default 'skip'. */
150
+ SoftLinks?: 'skip' | 'include';
151
+ Naming?: ICloneNamingConfig;
152
+ Fields?: ICloneFieldRules;
153
+ /** Per-relationship policy, keyed by "<RelatedEntityName>" or "<RelatedEntityName>.<JoinField>" when an entity has two FKs to the same target. Overrides the relationship's own bag when Locked is false there. */
154
+ Relationships?: Record<string, ICloneRelationshipPolicy>;
155
+ /** Rules applied to descendant rows cloned under THIS root, keyed by descendant entity name. Lets a root shape its children without editing the child entity's bag. */
156
+ Descendants?: Record<string, ICloneDescendantConfig>;
157
+ Hooks?: ICloneHookConfig;
158
+ /** Route the ROOT through an existing creation path instead of a raw Save. Children still clone through the engine against the created root. */
159
+ CreationPath?: ICloneCreationPath;
160
+ /** Offer "create derived record" (BasedOnID-style inheritance) as an alternative to copying. */
161
+ Derivation?: {
162
+ Field: string;
163
+ Label?: string;
164
+ Description?: string;
165
+ };
166
+ /** Persisted embedding columns. Default 'copy' when EmbeddingModelID matches the configured model, else regenerate. */
167
+ Embeddings?: 'copy' | 'regenerate';
168
+ /** The entity's own server class refuses creates by other user types (Users, Roles). The planner blocks early with the entity's reason. */
169
+ RequiredUserType?: 'Owner';
170
+ /** How much the UI may change. Default 'all'. 'none' = confirm only. */
171
+ UserEditable?: 'none' | 'fields' | 'scope' | 'all';
172
+ Presets?: IClonePreset[];
173
+ UI?: {
174
+ Label?: string;
175
+ Icon?: string;
176
+ ConfirmationMessage?: string;
177
+ DefaultPreset?: string;
178
+ /** FK fields the panel offers as retarget pickers (CompanyID → another company). */
179
+ RetargetFields?: string[];
180
+ };
181
+ }
182
+ export interface ICloneNamingConfig {
183
+ /** Template for the name field and any string unique field with no other rule. '{Name}' interpolates the source value; '{n}' the collision counter. Default 'Copy of {Name}'. */
184
+ Template?: string;
185
+ /** Fields the template applies to. Default: the entity's NameField plus every IsUnique string field not otherwise handled. */
186
+ Fields?: string[];
187
+ /** suffix = apply Template and probe for collisions appending ' {n}'; increment = numeric/versioned bump; prompt = user must supply; none = leave untouched. Default 'suffix'. */
188
+ Strategy?: 'suffix' | 'increment' | 'prompt' | 'none';
189
+ }
190
+ export interface ICloneFieldRules {
191
+ /** Never copied; take the column default. Beyond the always-excluded set (PK, __mj_*, identity, computed, virtual, denied-create). */
192
+ Exclude?: string[];
193
+ /** Literal stamps applied after the copy. Values pass through BaseEntity.Set and validation. */
194
+ Reset?: Record<string, unknown>;
195
+ /** Set to the cloning user's ID. */
196
+ Ownership?: string[];
197
+ /** The user must supply a value; un-suffixable uniques such as Email. Missing → plan Blocked. */
198
+ PromptFor?: string[];
199
+ /** Server-minted values (numbers, slugs): blanked so the entity's Save hook allocates. */
200
+ ServerAllocated?: string[];
201
+ /** Rich rewrites. Evaluated per row with the source row as fields, plus clone context (user, now, root, keyMap) — see §7.5. */
202
+ Rules?: Record<string, unknown>;
203
+ /** JSON columns that embed record IDs, one entry per column. */
204
+ JsonRemap?: IJsonRemapColumn[];
205
+ /** Columns that must be cleared together (all-or-nothing CHECK pairs). Each group is cleared as a unit when any member is reset. */
206
+ ClearTogether?: string[][];
207
+ /** Unique keys the metadata cannot see: composite and filtered indexes. See §7.3. */
208
+ UniqueKeys?: Array<{
209
+ Fields: string[];
210
+ Scope: 'Global' | 'Parent' | 'LiveState';
211
+ ScopeField?: string;
212
+ }>;
213
+ /** Drift guard (§13.3): every field must appear in Copy, Exclude, Reset, Ownership, PromptFor, ServerAllocated or JsonRemap, or validation fails. Default false. */
214
+ Strict?: boolean;
215
+ /** Explicit copy allow-list, used with Strict. */
216
+ Copy?: string[];
217
+ }
218
+ export interface IJsonRemapSpec {
219
+ /** Path selector: dot segments and [*] for arrays, e.g. 'layout.content[*].componentState.config.viewId'. */
220
+ Path: string;
221
+ /** remap = rewrite via key map when the target is in the clone set, else per OnMissing; reuse = leave; regenerate = new UUID; null = set null; drop = remove element/key. */
222
+ Mode: 'remap' | 'reuse' | 'regenerate' | 'null' | 'drop';
223
+ /** Entity the ID refers to, for remap. */
224
+ TargetEntityName?: string;
225
+ /** For remap when the referenced record was not cloned: reuse the original (default) or drop the element and count it. */
226
+ OnMissing?: 'reuse' | 'drop';
227
+ /** Remove arrays and objects left empty by a drop. Default true. */
228
+ CleanEmptyContainers?: boolean;
229
+ }
230
+ /** One JSON column to rewrite: explicit rules, a shipped preset, or both (the preset's rules run first). An entry with neither is invalid. */
231
+ export interface IJsonRemapColumn {
232
+ /** The JSON column. */
233
+ Field: string;
234
+ Rules?: IJsonRemapSpec[];
235
+ /** A shipped rule set for a known payload shape. */
236
+ Preset?: 'dashboard-ui-config' | 'scheduled-job-configuration';
237
+ }
238
+ export interface ICloneRelationshipPolicy {
239
+ Policy?: 'Deep' | 'Reference' | 'Skip';
240
+ /** UI may not change it. */
241
+ Locked?: boolean;
242
+ MaxRecords?: number;
243
+ /** Write the source's positional values after the last Add instead of letting the collection renumber. Default false. */
244
+ PreserveSequence?: boolean;
245
+ /** Formula over the child row (fields.X); only rows evaluating true are cloned. */
246
+ IncludeWhen?: string;
247
+ /** Rows left out even when the edge is Deep, e.g. per-device or per-person settings. A row matching any entry is not cloned, nor are its descendants. */
248
+ ExcludeRows?: ICloneRowExclusion[];
249
+ Fields?: ICloneFieldRules;
250
+ }
251
+ /** Matches a child row by one field: equal to one of `Equals`, or a string starting with one of `StartsWith` (case-sensitive). */
252
+ export interface ICloneRowExclusion {
253
+ Field: string;
254
+ Equals?: Array<string | number | boolean>;
255
+ StartsWith?: string[];
256
+ }
257
+ export interface ICloneDescendantConfig {
258
+ Fields?: ICloneFieldRules;
259
+ Naming?: ICloneNamingConfig;
260
+ }
261
+ export interface ICloneHookConfig {
262
+ /** Entity Actions (Create/Update invocations) during the clone save. Default 'fire', as for any create. */
263
+ EntityActions?: 'suppress' | 'fire';
264
+ /** Entity AI Actions. Default 'suppress': each can be a paid model call per cloned row. */
265
+ AIActions?: 'suppress' | 'fire';
266
+ /** Children the entity's own server Save() creates. The engine never clones these, and warns if a relationship policy tries. */
267
+ ServerGeneratedChildren?: string[];
268
+ /** Values set before the save to keep expensive hooks quiet, restored on the row after the clone when RestoreAfterSave is true. */
269
+ PreSaveOverrides?: Record<string, unknown>;
270
+ RestoreAfterSave?: string[];
271
+ /** Action run once per created ROOT after commit, with the new key. */
272
+ PostCloneAction?: string;
273
+ }
274
+ export interface ICloneCreationPath {
275
+ Kind: 'Action' | 'RemoteOperation';
276
+ Name: string;
277
+ /** Source field or formula → input param. */
278
+ InputMapping: Record<string, string>;
279
+ /** Output param holding the created key. */
280
+ OutputKeyParam: string;
281
+ }
282
+ /**
283
+ * A named clone scope a user can pick instead of adjusting options one by one.
284
+ * `Options` apply as if the request had sent them (so `UserEditable` still limits them);
285
+ * `Relationships` override edge policies by related entity name, like `Clone.Relationships`.
286
+ */
287
+ export interface IClonePreset {
288
+ /** Stable identifier sent by clients in `Options.Preset`. */
289
+ Key: string;
290
+ /** Name shown in the preset picker. */
291
+ Label: string;
292
+ Description?: string;
293
+ /** Plan options this preset sets, e.g. `{ "MaxDepth": 2 }`. */
294
+ Options?: Record<string, unknown>;
295
+ /** Edge policy overrides, keyed by related entity name. */
296
+ Relationships?: Record<string, {
297
+ Policy: 'Deep' | 'Reference' | 'Skip';
298
+ }>;
299
+ }
120
300
  //# sourceMappingURL=IEntityConfiguration.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"IEntityConfiguration.d.ts","sourceRoot":"","sources":["../../../src/generic/JSONType-interfaces/IEntityConfiguration.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,oBAAoB;IACjC;;;OAGG;IACH,EAAE,CAAC,EAAE,sBAAsB,CAAC;IAE5B;;;OAGG;IACH,WAAW,CAAC,EAAE,+BAA+B,CAAC;CACjD;AAED;;GAEG;AACH,MAAM,WAAW,+BAA+B;IAC5C;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE/B;;OAEG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;IAEjC;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,sBAAsB;IACnC;;;OAGG;IACH,IAAI,CAAC,EAAE,wBAAwB,CAAC;CACnC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,wBAAwB;IACrC;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,WAAW,GAAG,UAAU,GAAG,MAAM,CAAC;IAE3C;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;;;;;;;OAUG;IACH,iBAAiB,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC;IAEjD;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;CACjC"}
1
+ {"version":3,"file":"IEntityConfiguration.d.ts","sourceRoot":"","sources":["../../../src/generic/JSONType-interfaces/IEntityConfiguration.ts"],"names":[],"mappings":"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"}
@@ -14,6 +14,11 @@ export interface IEntityFieldConfiguration {
14
14
  * Hierarchy and tree structure configuration for self-referencing foreign keys.
15
15
  */
16
16
  Hierarchy?: IEntityFieldHierarchyConfig;
17
+ /**
18
+ * Record cloning configuration for this field.
19
+ * @see plans/record-cloning/README.md §4.3
20
+ */
21
+ Clone?: IEntityFieldCloneConfiguration;
17
22
  }
18
23
  /**
19
24
  * Hierarchy options to explicitly declare recursive tree hierarchies.
@@ -28,4 +33,27 @@ export interface IEntityFieldHierarchyConfig {
28
33
  */
29
34
  MaxDepth?: number;
30
35
  }
36
+ /**
37
+ * Record cloning configuration for an entity field.
38
+ * @see plans/record-cloning/README.md §4.3
39
+ */
40
+ export interface IEntityFieldCloneConfiguration {
41
+ /** Copy (default) | Reset (column default, or Value) | Suffix (naming template) | Prompt | Ownership | ServerAllocated | Remap (FK inside the set → new key) | RemapJSON | Transform */
42
+ Policy?: 'Copy' | 'Reset' | 'Suffix' | 'Prompt' | 'Ownership' | 'ServerAllocated' | 'Remap' | 'RemapJSON' | 'Transform';
43
+ Value?: unknown;
44
+ JsonRemap?: IJsonRemapSpec[];
45
+ Transform?: unknown;
46
+ }
47
+ export interface IJsonRemapSpec {
48
+ /** Path selector: dot segments and [*] for arrays, e.g. 'layout.content[*].componentState.config.viewId'. */
49
+ Path: string;
50
+ /** remap = rewrite via key map when the target is in the clone set, else per OnMissing; reuse = leave; regenerate = new UUID; null = set null; drop = remove element/key. */
51
+ Mode: 'remap' | 'reuse' | 'regenerate' | 'null' | 'drop';
52
+ /** Entity the ID refers to, for remap. */
53
+ TargetEntityName?: string;
54
+ /** For remap when the referenced record was not cloned: reuse the original (default) or drop the element and count it. */
55
+ OnMissing?: 'reuse' | 'drop';
56
+ /** Remove arrays and objects left empty by a drop. Default true. */
57
+ CleanEmptyContainers?: boolean;
58
+ }
31
59
  //# sourceMappingURL=IEntityFieldConfiguration.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"IEntityFieldConfiguration.d.ts","sourceRoot":"","sources":["../../../src/generic/JSONType-interfaces/IEntityFieldConfiguration.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;GAMG;AACH,MAAM,WAAW,yBAAyB;IACtC;;OAEG;IACH,SAAS,CAAC,EAAE,2BAA2B,CAAC;CAC3C;AAED;;GAEG;AACH,MAAM,WAAW,2BAA2B;IACxC;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB"}
1
+ {"version":3,"file":"IEntityFieldConfiguration.d.ts","sourceRoot":"","sources":["../../../src/generic/JSONType-interfaces/IEntityFieldConfiguration.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;GAMG;AACH,MAAM,WAAW,yBAAyB;IACtC;;OAEG;IACH,SAAS,CAAC,EAAE,2BAA2B,CAAC;IAExC;;;OAGG;IACH,KAAK,CAAC,EAAE,8BAA8B,CAAC;CAC1C;AAED;;GAEG;AACH,MAAM,WAAW,2BAA2B;IACxC;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,MAAM,WAAW,8BAA8B;IAC3C,wLAAwL;IACxL,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,WAAW,GAAG,iBAAiB,GAAG,OAAO,GAAG,WAAW,GAAG,WAAW,CAAC;IACxH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,cAAc,EAAE,CAAC;IAC7B,SAAS,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC3B,6GAA6G;IAC7G,IAAI,EAAE,MAAM,CAAC;IACb,6KAA6K;IAC7K,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,YAAY,GAAG,MAAM,GAAG,MAAM,CAAC;IACzD,0CAA0C;IAC1C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,0HAA0H;IAC1H,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC7B,oEAAoE;IACpE,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAClC"}
@@ -32,6 +32,11 @@ export interface IEntityRelationshipConfiguration {
32
32
  * Null = the parent entity's related-role ranker decides.
33
33
  */
34
34
  UI?: IEntityRelationshipUIConfiguration;
35
+ /**
36
+ * Clone policy for rows of RelatedEntity that point at this entity through RelatedEntityJoinField.
37
+ * @see plans/record-cloning/README.md §4.2
38
+ */
39
+ Clone?: ICloneRelationshipPolicy;
35
40
  }
36
41
  /**
37
42
  * How this relationship appears on the parent entity's generated form.
@@ -72,4 +77,75 @@ export interface IEntityRelationshipUIConfiguration {
72
77
  */
73
78
  sortKey?: number;
74
79
  }
80
+ /**
81
+ * Clone policy for rows of RelatedEntity that point at this entity through RelatedEntityJoinField.
82
+ * @see plans/record-cloning/README.md §4.2
83
+ */
84
+ export interface ICloneRelationshipPolicy {
85
+ Policy?: 'Deep' | 'Reference' | 'Skip';
86
+ /** UI may not change it. */
87
+ Locked?: boolean;
88
+ MaxRecords?: number;
89
+ /** Write the source's positional values after the last Add instead of letting the collection renumber. Default false. */
90
+ PreserveSequence?: boolean;
91
+ /** Formula over the child row (fields.X); only rows evaluating true are cloned. */
92
+ IncludeWhen?: string;
93
+ /** Rows left out even when the edge is Deep, e.g. per-device or per-person settings. A row matching any entry is not cloned, nor are its descendants. */
94
+ ExcludeRows?: ICloneRowExclusion[];
95
+ Fields?: ICloneFieldRules;
96
+ }
97
+ /** Matches a child row by one field: equal to one of `Equals`, or a string starting with one of `StartsWith` (case-sensitive). */
98
+ export interface ICloneRowExclusion {
99
+ Field: string;
100
+ Equals?: Array<string | number | boolean>;
101
+ StartsWith?: string[];
102
+ }
103
+ export interface ICloneFieldRules {
104
+ /** Never copied; take the column default. Beyond the always-excluded set (PK, __mj_*, identity, computed, virtual, denied-create). */
105
+ Exclude?: string[];
106
+ /** Literal stamps applied after the copy. Values pass through BaseEntity.Set and validation. */
107
+ Reset?: Record<string, unknown>;
108
+ /** Set to the cloning user's ID. */
109
+ Ownership?: string[];
110
+ /** The user must supply a value; un-suffixable uniques such as Email. Missing → plan Blocked. */
111
+ PromptFor?: string[];
112
+ /** Server-minted values (numbers, slugs): blanked so the entity's Save hook allocates. */
113
+ ServerAllocated?: string[];
114
+ /** Rich rewrites. Evaluated per row with the source row as fields, plus clone context (user, now, root, keyMap) — see §7.5. */
115
+ Rules?: Record<string, unknown>;
116
+ /** JSON columns that embed record IDs, one entry per column. */
117
+ JsonRemap?: IJsonRemapColumn[];
118
+ /** Columns that must be cleared together (all-or-nothing CHECK pairs). Each group is cleared as a unit when any member is reset. */
119
+ ClearTogether?: string[][];
120
+ /** Unique keys the metadata cannot see: composite and filtered indexes. See §7.3. */
121
+ UniqueKeys?: Array<{
122
+ Fields: string[];
123
+ Scope: 'Global' | 'Parent' | 'LiveState';
124
+ ScopeField?: string;
125
+ }>;
126
+ /** Drift guard (§13.3): every field must appear in Copy, Exclude, Reset, Ownership, PromptFor, ServerAllocated or JsonRemap, or validation fails. Default false. */
127
+ Strict?: boolean;
128
+ /** Explicit copy allow-list, used with Strict. */
129
+ Copy?: string[];
130
+ }
131
+ export interface IJsonRemapSpec {
132
+ /** Path selector: dot segments and [*] for arrays, e.g. 'layout.content[*].componentState.config.viewId'. */
133
+ Path: string;
134
+ /** remap = rewrite via key map when the target is in the clone set, else per OnMissing; reuse = leave; regenerate = new UUID; null = set null; drop = remove element/key. */
135
+ Mode: 'remap' | 'reuse' | 'regenerate' | 'null' | 'drop';
136
+ /** Entity the ID refers to, for remap. */
137
+ TargetEntityName?: string;
138
+ /** For remap when the referenced record was not cloned: reuse the original (default) or drop the element and count it. */
139
+ OnMissing?: 'reuse' | 'drop';
140
+ /** Remove arrays and objects left empty by a drop. Default true. */
141
+ CleanEmptyContainers?: boolean;
142
+ }
143
+ /** One JSON column to rewrite: explicit rules, a shipped preset, or both (the preset's rules run first). An entry with neither is invalid. */
144
+ export interface IJsonRemapColumn {
145
+ /** The JSON column. */
146
+ Field: string;
147
+ Rules?: IJsonRemapSpec[];
148
+ /** A shipped rule set for a known payload shape. */
149
+ Preset?: 'dashboard-ui-config' | 'scheduled-job-configuration';
150
+ }
75
151
  //# sourceMappingURL=IEntityRelationshipConfiguration.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"IEntityRelationshipConfiguration.d.ts","sourceRoot":"","sources":["../../../src/generic/JSONType-interfaces/IEntityRelationshipConfiguration.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,WAAW,gCAAgC;IAC7C;;;OAGG;IACH,EAAE,CAAC,EAAE,kCAAkC,CAAC;CAC3C;AAED;;;;;;GAMG;AACH,MAAM,WAAW,kCAAkC;IAC/C;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;IAExC;;;OAGG;IACH,QAAQ,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAEhC;;;;OAIG;IACH,IAAI,CAAC,EAAE;QACH,IAAI,EAAE,KAAK,CAAC;QACZ,MAAM,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;IAEF;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB"}
1
+ {"version":3,"file":"IEntityRelationshipConfiguration.d.ts","sourceRoot":"","sources":["../../../src/generic/JSONType-interfaces/IEntityRelationshipConfiguration.ts"],"names":[],"mappings":"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"}
@@ -9,6 +9,19 @@ import { EmbeddedRecord, type EmbeddedRecordOptions } from './embeddedRecord.js'
9
9
  import { EntitySavePlan } from './entitySavePlan.js';
10
10
  import { Subscription } from 'rxjs';
11
11
  import { z } from 'zod';
12
+ /**
13
+ * A field's value and dirty-tracking state, as {@link EntityField.GetState} captures them.
14
+ */
15
+ export interface EntityFieldState {
16
+ /** The field's value. */
17
+ Value: unknown;
18
+ /** The value the field is compared with to decide whether it's dirty. */
19
+ OldValue: unknown;
20
+ /** Whether the source the record was hydrated from omitted the field. See {@link EntityField.NotLoaded}. */
21
+ NotLoaded: boolean;
22
+ /** Whether the field has had no set since it was created or re-armed. */
23
+ NeverSet: boolean;
24
+ }
12
25
  /**
13
26
  * Represents a field in an instance of the BaseEntity class. This class is used to store the value of the field, dirty state, as well as other run-time information about the field. The class encapsulates the underlying field metadata and exposes some of the more commonly
14
27
  * used properties from the entity field metadata.
@@ -189,9 +202,18 @@ export declare class EntityField {
189
202
  ResetOldValue(): void;
190
203
  /**
191
204
  * Restores the dirty-tracking baseline to a previously captured value.
192
- * Used by graph rollback so a retried save still sees the pre-attempt dirty set.
193
205
  */
194
206
  RestoreOldValue(value: unknown): void;
207
+ /**
208
+ * Framework-internal: the field's value and dirty-tracking state, so a unit of work that rolls
209
+ * back can put them back with {@link RestoreState}.
210
+ */
211
+ GetState(): EntityFieldState;
212
+ /**
213
+ * Framework-internal: puts back a state {@link GetState} captured. It writes the state as it was,
214
+ * so the read-only and first-set rules of the {@link Value} setter don't apply.
215
+ */
216
+ RestoreState(state: EntityFieldState): void;
195
217
  /**
196
218
  * @deprecated No-op as of the active-status relocation. Active-status (deprecated/disabled)
197
219
  * assertions are now enforced at BaseEntity.Get/Set/SetMany — the entry points genuine code uses —
@@ -228,6 +250,46 @@ export interface RestoreContext {
228
250
  */
229
251
  Reason: string | null;
230
252
  }
253
+ /**
254
+ * Context describing an in-progress clone operation on a BaseEntity.
255
+ *
256
+ * When set on an entity instance via {@link BaseEntity.SetCloneContext}
257
+ * prior to calling Save(), the data provider will write the resulting
258
+ * RecordChange row with `Source='Clone'` and the structured JSON
259
+ * `ChangeContext` column populated.
260
+ *
261
+ * @see plans/record-cloning/README.md §10.3
262
+ */
263
+ export interface CloneContext {
264
+ /** ID of the RecordCloneLog row coordinating this clone operation. */
265
+ CloneLogID: string;
266
+ /** Entity name of the record being cloned. */
267
+ SourceEntityName: string;
268
+ /** Compact URL segment of the source key (bare value for single-column keys). */
269
+ SourceRecordID: string;
270
+ /** Entity name of the root record of the clone graph. */
271
+ RootEntityName: string;
272
+ /** Source key of the root record. */
273
+ RootSourceRecordID: string;
274
+ /** Target key of the root record after insertion. */
275
+ RootTargetRecordID: string;
276
+ /** Depth within the record graph (0 for root). */
277
+ Depth: number;
278
+ /** Relationship route traversed to reach this record. */
279
+ Route: 'RootSave' | 'Collection' | 'Embedded' | 'IsAChain' | 'Sidecar';
280
+ /** Kinds and field names only. Values are already in FullRecordJSON. */
281
+ FieldChangeSummary: Array<{
282
+ Kind: string;
283
+ Fields: string[];
284
+ }>;
285
+ /** Optional explanation entered at clone time. */
286
+ Reason?: string | null;
287
+ }
288
+ /**
289
+ * The `ChangeContext` JSON a clone writes on its Record Change rows (`IRecordChangeContext`,
290
+ * Kind 'Clone'). The one serializer every provider path uses, so the shape can't drift.
291
+ */
292
+ export declare function SerializeCloneChangeContext(context: CloneContext): string;
231
293
  /**
232
294
  * Discriminator for the `Source` column of a RecordChange row.
233
295
  *
@@ -236,8 +298,10 @@ export interface RestoreContext {
236
298
  * (records the platform discovers via direct SQL changes)
237
299
  * - `Restore`: produced by a user-initiated restore — paired with
238
300
  * `RestoredFromID` and optional `RestoreReason` lineage columns
301
+ * - `Clone`: produced by a record clone operation — paired with
302
+ * structured `ChangeContext` JSON provenance
239
303
  */
240
- export type RecordChangeSource = 'Internal' | 'External' | 'Restore';
304
+ export type RecordChangeSource = 'Internal' | 'External' | 'Restore' | 'Clone';
241
305
  /**
242
306
  * Dialect-agnostic payload for a RecordChange row.
243
307
  *
@@ -294,6 +358,11 @@ export interface RecordChangePayload {
294
358
  * not enter one.
295
359
  */
296
360
  restoreReason: string | null;
361
+ /**
362
+ * When `source === 'Clone'`, serialized JSON bag carrying structured provenance
363
+ * (shape = `IRecordChangeContext`). Null otherwise.
364
+ */
365
+ changeContext: string | null;
297
366
  }
298
367
  export declare class DataObjectRelatedEntityParam {
299
368
  relatedEntityName: string;
@@ -468,6 +537,9 @@ export declare class BaseEntityEvent {
468
537
  */
469
538
  provider?: IMetadataProvider;
470
539
  }
540
+ /**
541
+ * Base class used for all entity objects. This class is abstract and is sub-classes for each particular entity using the CodeGen tool. This class provides the basic functionality for loading, saving, and validating entity objects.
542
+ */
471
543
  export declare abstract class BaseEntity<T = unknown> {
472
544
  /**
473
545
  * Metadata describing this entity (name, fields, keys, relationships). Populated during
@@ -711,6 +783,12 @@ export declare abstract class BaseEntity<T = unknown> {
711
783
  * routing through the provider: participants stay ignorant of one another.
712
784
  */
713
785
  private _entityTransactionScope;
786
+ /**
787
+ * The unit of work this record is saved or deleted in, while it runs as part of a larger one: an
788
+ * IS-A chain's initiator sets it on each parent for the parent's write, and a graph sets it on
789
+ * each of its records. See {@link UnitOfWork}.
790
+ */
791
+ private _unitOfWork;
714
792
  /**
715
793
  * Companions registered on this entity, keyed by {@link EntityCompanion.Name}.
716
794
  *
@@ -784,6 +862,14 @@ export declare abstract class BaseEntity<T = unknown> {
784
862
  * This is called by Metadata.GetEntityObject() after constructing the entity.
785
863
  */
786
864
  InitializeParentEntity(): Promise<void>;
865
+ /**
866
+ * Points this child's disjoint parent back at this instance (`parent._childEntity = this`).
867
+ *
868
+ * Same gate as {@link InitializeParentEntity}: a parent that allows several subtypes keeps no
869
+ * single child. `NewRecord()` sets `_childEntity` to null, so this has to run AFTER the
870
+ * parent's `NewRecord()` — calling it before is wiped out.
871
+ */
872
+ private linkParentBackToThisChild;
787
873
  /**
788
874
  * IS-A PROMOTION (#3825): binds this NEW child record to an EXISTING parent row, so saving it
789
875
  * ADDS a subtype to a person/org/product that already exists instead of trying to create a
@@ -1041,6 +1127,14 @@ export declare abstract class BaseEntity<T = unknown> {
1041
1127
  * @returns The linked child BaseEntity, or null if no subtype applies.
1042
1128
  */
1043
1129
  EnsureISAChild(entityName?: string): Promise<BaseEntity | null>;
1130
+ /**
1131
+ * A new parent's child is new too: there is no row to read, so copy the parent's keys.
1132
+ * A saved parent still reads (promotion's usual answer is "no row"), and that miss is not
1133
+ * an error — the same way a subtype-hint probe treats an empty read (MJ#4859).
1134
+ */
1135
+ private loadOrMirrorChildRow;
1136
+ /** Loads the child by the shared key. An empty result is an answer, not a logged error. */
1137
+ private loadChildRowQuietly;
1044
1138
  private mirrorSharedKeysToChild;
1045
1139
  /**
1046
1140
  * Walks a `SubtypeSelector` path from this record to the column holding the subtype's entity
@@ -1204,6 +1298,18 @@ export declare abstract class BaseEntity<T = unknown> {
1204
1298
  * ```
1205
1299
  */
1206
1300
  protected DeclareRelatedRecords<TChild extends BaseEntity = BaseEntity>(options: RelatedRecordCollectionOptions): RelatedRecordCollection<TChild>;
1301
+ /**
1302
+ * Dynamically registers a database-sourced child collection companion on this entity instance.
1303
+ * Used by generic composite persistence engines (Record Cloning, Metadata Sync) when a relationship
1304
+ * has no static collection companion declared on the generated entity class.
1305
+ *
1306
+ * Refuses duplicate names or re-registering an already existing companion.
1307
+ *
1308
+ * @typeParam TChild - The child entity type.
1309
+ * @param options - The collection declaration options.
1310
+ * @returns The registered collection companion.
1311
+ */
1312
+ DeclareRelatedRecordsDynamic<TChild extends BaseEntity = BaseEntity>(options: RelatedRecordCollectionOptions): RelatedRecordCollection<TChild>;
1207
1313
  /**
1208
1314
  * Declares a 1:1 embedded peer on this entity, joined by an owner-held foreign key,
1209
1315
  * and registers it as a companion.
@@ -1424,20 +1530,33 @@ export declare abstract class BaseEntity<T = unknown> {
1424
1530
  */
1425
1531
  private deleteAsGraphNode;
1426
1532
  /**
1427
- * Captures saved/dirty baselines for every plan participant so a rolled-back
1428
- * graph can be retried without re-INSERTing a "saved" peer or skipping it.
1533
+ * Captures each record's IS-A chain, from its leaf up to its root, so a unit of work that rolls
1534
+ * back can put the chain back with {@link restoreChains}. A record in more than one chain is
1535
+ * captured once.
1429
1536
  */
1430
- private captureGraphParticipants;
1537
+ private static captureChains;
1431
1538
  /**
1432
- * Restores in-memory saved/dirty state after the database rolled the graph back.
1539
+ * Puts back, after a rollback, what the unit of work changed in memory. See {@link restoreSnapshot}.
1433
1540
  */
1434
- private revertGraphParticipants;
1541
+ private static restoreChains;
1435
1542
  /**
1436
- * After a graph node Save() the fields look clean and `_everSaved` is true.
1437
- * The DB rollback does not undo that. Restore the pre-attempt baseline so
1438
- * a retry still writes the peer and the owner FK still matches.
1543
+ * Puts this record back as a snapshot captured it, after the unit of work that finalized it
1544
+ * rolled back.
1545
+ *
1546
+ * A save's `finalizeSave()` rebuilds the record's fields from what its write returned, marks it
1547
+ * saved and empties its result history. The database undid the write, so the record gets back
1548
+ * its saved and loaded flags, its result history, and each field's value and tracking state. A
1549
+ * field edited since then keeps the edit, and is compared with its captured baseline again, since
1550
+ * that is what the database holds after the rollback. (A delete leaves nothing to put back: a
1551
+ * record deleted inside a unit of work isn't reset until the unit commits.)
1552
+ *
1553
+ * The history matters to the caller: a save records its failure only when nothing else did,
1554
+ * judged by the history's length when it started, which an emptied history would make wrong.
1555
+ *
1556
+ * A record still holding the captured field objects was never finalized, so nothing about it
1557
+ * changed and it is left alone.
1439
1558
  */
1440
- private revertUncommittedGraphSave;
1559
+ private restoreSnapshot;
1441
1560
  /**
1442
1561
  * Records a graph-level failure on this entity's result history.
1443
1562
  *
@@ -1460,6 +1579,49 @@ export declare abstract class BaseEntity<T = unknown> {
1460
1579
  * would replace the caller's real error with a secondary one that explains less.
1461
1580
  */
1462
1581
  private rollbackEntityTransactionScope;
1582
+ /**
1583
+ * The failure path of a save: rolls back the transaction scope this entity holds, if any, then
1584
+ * settles this save's part in its unit of work (see {@link settleRollback}). `part` is null when
1585
+ * this save took none (see `_innerSave`), and then only the scope is rolled back.
1586
+ */
1587
+ private rollbackChainSave;
1588
+ /**
1589
+ * Starts a unit of work's part in a {@link UnitOfWork}: joins `enclosing` when the unit runs
1590
+ * inside one, and owns a new one when it doesn't. `snapshots` are the unit's records as they are
1591
+ * before it writes them. They join the unit's, so that the outermost unit can put them back.
1592
+ */
1593
+ private static beginUnitOfWork;
1594
+ /**
1595
+ * Runs `work` on `record` as part of `unit` (see {@link UnitOfWork}), and gives the record back
1596
+ * the unit it was in before, however `work` ends. Used for an IS-A parent's delete, which runs
1597
+ * inside its child's chain, along with the graph of its companions if it has any.
1598
+ */
1599
+ private static inUnitOfWork;
1600
+ /**
1601
+ * After a unit of work rolls back: puts its records back as they were before it wrote them, and
1602
+ * drops the deletes the rollback undid, so that nothing resets their records. The owner does this
1603
+ * for everything the unit and the units nested in it wrote. A nested part does it for its own
1604
+ * records only; when the owner then rolls back too, restoring them again changes nothing.
1605
+ */
1606
+ private static settleRollback;
1607
+ /**
1608
+ * After a unit of work commits: its owner resets the records it deleted. A nested part leaves
1609
+ * them to the owner, since the enclosing unit can still roll back.
1610
+ */
1611
+ private static settleCommit;
1612
+ /**
1613
+ * Resets the records a unit of work deleted, once the unit has committed, as each record's own
1614
+ * `Delete()` does when no unit of work is involved. See {@link UnitOfWork}.
1615
+ *
1616
+ * Last-deleted first, skipping a record that no longer reads as saved. `NewRecord()` on a record
1617
+ * also resets every IS-A level above it, and relinks them. A chain's initiator records its delete
1618
+ * after its parents do, so it's reset first, and its parents with it. Resetting a parent again
1619
+ * after that would unlink it from its child and give it a key the child doesn't share.
1620
+ *
1621
+ * The unit has committed, so a reset that throws is logged and the rest still run: the deletes
1622
+ * stand whatever happens here.
1623
+ */
1624
+ private static resetDeletedRecords;
1463
1625
  /**
1464
1626
  * Returns this provider to be used for a given instance of a BaseEntity derived subclass. If the provider is not set, the BaseEntity.Provider is returned.
1465
1627
  */
@@ -1899,6 +2061,34 @@ export declare abstract class BaseEntity<T = unknown> {
1899
2061
  * accidentally tagged as a restore.
1900
2062
  */
1901
2063
  ClearRestoreContext(): void;
2064
+ private _cloneContext;
2065
+ /**
2066
+ * Returns the active clone context for the next save, if any.
2067
+ *
2068
+ * Read by the data provider when generating the RecordChange SQL: when
2069
+ * non-null, the resulting RecordChange row is written with
2070
+ * `Source='Clone'` and `ChangeContext` populated with structured JSON provenance.
2071
+ * Returns null for ordinary saves.
2072
+ */
2073
+ get CloneContext(): CloneContext | null;
2074
+ /**
2075
+ * Marks the next Save() as part of a record clone operation.
2076
+ *
2077
+ * The provider will write a new RecordChange entry with `Source='Clone'`
2078
+ * and `ChangeContext` populated with structured JSON provenance.
2079
+ *
2080
+ * The context persists on the entity until either (a) overwritten by a
2081
+ * subsequent SetCloneContext() call or (b) explicitly cleared via
2082
+ * ClearCloneContext(). It is NOT auto-cleared inside Save() because
2083
+ * TransactionGroup execution is deferred.
2084
+ *
2085
+ * @param context Structured clone context. Throws if missing required fields.
2086
+ */
2087
+ SetCloneContext(context: CloneContext): void;
2088
+ /**
2089
+ * Clears any pending clone context. Safe to call when no context is set.
2090
+ */
2091
+ ClearCloneContext(): void;
1902
2092
  private _pendingSave$;
1903
2093
  /**
1904
2094
  * Saves the current state of the object to the database. Uses the active provider to handle the actual saving of the record.