@memberjunction/core 5.42.0 → 5.44.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.
Files changed (53) hide show
  1. package/dist/generic/BaseEntitySaveQueue.d.ts +60 -0
  2. package/dist/generic/BaseEntitySaveQueue.d.ts.map +1 -0
  3. package/dist/generic/BaseEntitySaveQueue.js +106 -0
  4. package/dist/generic/BaseEntitySaveQueue.js.map +1 -0
  5. package/dist/generic/EntityFieldRules.d.ts +97 -0
  6. package/dist/generic/EntityFieldRules.d.ts.map +1 -0
  7. package/dist/generic/EntityFieldRules.js +201 -0
  8. package/dist/generic/EntityFieldRules.js.map +1 -0
  9. package/dist/generic/authTypes.d.ts +1 -0
  10. package/dist/generic/authTypes.d.ts.map +1 -1
  11. package/dist/generic/authTypes.js +1 -0
  12. package/dist/generic/authTypes.js.map +1 -1
  13. package/dist/generic/baseEngine.d.ts +70 -0
  14. package/dist/generic/baseEngine.d.ts.map +1 -1
  15. package/dist/generic/baseEngine.js +140 -1
  16. package/dist/generic/baseEngine.js.map +1 -1
  17. package/dist/generic/baseEntity.d.ts.map +1 -1
  18. package/dist/generic/baseEntity.js +35 -15
  19. package/dist/generic/baseEntity.js.map +1 -1
  20. package/dist/generic/compositeKey.js +1 -1
  21. package/dist/generic/compositeKey.js.map +1 -1
  22. package/dist/generic/databaseProviderBase.js +1 -1
  23. package/dist/generic/databaseProviderBase.js.map +1 -1
  24. package/dist/generic/entityInfo.d.ts +9 -0
  25. package/dist/generic/entityInfo.d.ts.map +1 -1
  26. package/dist/generic/entityInfo.js +10 -1
  27. package/dist/generic/entityInfo.js.map +1 -1
  28. package/dist/generic/graphqlTypeNames.d.ts.map +1 -1
  29. package/dist/generic/graphqlTypeNames.js +6 -1
  30. package/dist/generic/graphqlTypeNames.js.map +1 -1
  31. package/dist/generic/interfaces.d.ts +24 -0
  32. package/dist/generic/interfaces.d.ts.map +1 -1
  33. package/dist/generic/interfaces.js.map +1 -1
  34. package/dist/generic/providerBase.d.ts.map +1 -1
  35. package/dist/generic/providerBase.js +5 -0
  36. package/dist/generic/providerBase.js.map +1 -1
  37. package/dist/generic/queryResultEnricher.d.ts +77 -0
  38. package/dist/generic/queryResultEnricher.d.ts.map +1 -0
  39. package/dist/generic/queryResultEnricher.js +41 -0
  40. package/dist/generic/queryResultEnricher.js.map +1 -0
  41. package/dist/generic/runQuery.d.ts +17 -0
  42. package/dist/generic/runQuery.d.ts.map +1 -1
  43. package/dist/generic/runQuery.js.map +1 -1
  44. package/dist/generic/telemetryManager.d.ts +27 -4
  45. package/dist/generic/telemetryManager.d.ts.map +1 -1
  46. package/dist/generic/telemetryManager.js +50 -11
  47. package/dist/generic/telemetryManager.js.map +1 -1
  48. package/dist/index.d.ts +3 -0
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +3 -0
  51. package/dist/index.js.map +1 -1
  52. package/package.json +3 -3
  53. package/readme.md +148 -4
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @fileoverview Entity-aware façade over {@link KeyedSerialTaskQueue} for the fire-and-forget save
3
+ * pattern. Captures the `Save()` calls, `IgnoreDirtyState` force-persist, and error extraction that
4
+ * the agent-step / action-log / prompt-run logging sites otherwise hand-roll. The key correctness
5
+ * property is structural: `Update`'s mutation runs *inside* the post-INSERT task, so it can never be
6
+ * reverted by the INSERT's `finalizeSave` reload (the "stuck at Running" race becomes impossible).
7
+ * @module @memberjunction/core
8
+ */
9
+ import { SerialTaskFlushResult } from '@memberjunction/global';
10
+ import { BaseEntity } from './baseEntity.js';
11
+ /**
12
+ * A per-entity-instance serial save queue. INSERT and UPDATE of the same `BaseEntity` instance are
13
+ * serialized (the UPDATE waits for the INSERT to land); different entities save concurrently. All
14
+ * saves are fire-and-forget and **never throw outward** — a failed `Save()` (returned `false` or a
15
+ * thrown error) is logged and counted; call {@link Flush} at a run/goal boundary to await them and
16
+ * surface the failure count.
17
+ */
18
+ export declare class BaseEntitySaveQueue {
19
+ private readonly queue;
20
+ private readonly onError?;
21
+ /**
22
+ * @param opts.onError Optional handler for a failed save's diagnostic message. Defaults to the
23
+ * global `LogError`. Consumers with structured logging (e.g. a category/metadata logger) pass
24
+ * their own so failures stay in their log stream.
25
+ */
26
+ constructor(opts?: {
27
+ onError?: (message: string) => void;
28
+ });
29
+ /**
30
+ * Fire-and-forget INSERT of a freshly `NewRecord()`'d entity. The entity instance is the
31
+ * serialization key, so a subsequent {@link Update} of the same instance waits for this to land.
32
+ */
33
+ Insert(entity: BaseEntity): void;
34
+ /**
35
+ * Fire-and-forget INSERT that waits for `dependency`'s pending tasks to settle first. Use this for
36
+ * self-referencing foreign keys: a child entity whose FK points at `dependency` must not INSERT
37
+ * until the dependency's own INSERT has landed in the database.
38
+ */
39
+ InsertAfter(entity: BaseEntity, dependency: BaseEntity): void;
40
+ /**
41
+ * Fire-and-forget UPDATE chained after the entity's INSERT. `applyMutation` (if given) runs INSIDE
42
+ * the post-INSERT task — after `finalizeSave`'s reload — so its values always survive; the save is
43
+ * force-persisted with `IgnoreDirtyState`. Pass no mutation only when the fields are already set
44
+ * AND cannot race an in-flight INSERT.
45
+ */
46
+ Update(entity: BaseEntity, applyMutation?: (entity: BaseEntity) => void): void;
47
+ /** Awaits all pending saves and returns failure diagnostics. Call at the run/goal finalize boundary. */
48
+ Flush(): Promise<SerialTaskFlushResult>;
49
+ /**
50
+ * Applies the optional mutation, saves, and returns success — logging and swallowing both a
51
+ * `false` result and a thrown error so a fire-and-forget save can never surface as an unhandled
52
+ * rejection (failures are counted via the queue's `isOk`).
53
+ */
54
+ private runSave;
55
+ /** Routes a failure message to the caller-supplied handler, or the global `LogError` by default. */
56
+ private logFailure;
57
+ private labelFor;
58
+ private saveError;
59
+ }
60
+ //# sourceMappingURL=BaseEntitySaveQueue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BaseEntitySaveQueue.d.ts","sourceRoot":"","sources":["../../src/generic/BaseEntitySaveQueue.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAwB,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AACrF,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAI1C;;;;;;GAMG;AACH,qBAAa,mBAAmB;IAC5B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA8B;IACpD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAA4B;IAErD;;;;OAIG;gBACS,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE;IAI1D;;;OAGG;IACI,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAKvC;;;;OAIG;IACI,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,GAAG,IAAI;IAKpE;;;;;OAKG;IACI,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI;IAOrF,wGAAwG;IACjG,KAAK,IAAI,OAAO,CAAC,qBAAqB,CAAC;IAI9C;;;;OAIG;YACW,OAAO;IAgBrB,oGAAoG;IACpG,OAAO,CAAC,UAAU;IAQlB,OAAO,CAAC,QAAQ;IAUhB,OAAO,CAAC,SAAS;CAGpB"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * @fileoverview Entity-aware façade over {@link KeyedSerialTaskQueue} for the fire-and-forget save
3
+ * pattern. Captures the `Save()` calls, `IgnoreDirtyState` force-persist, and error extraction that
4
+ * the agent-step / action-log / prompt-run logging sites otherwise hand-roll. The key correctness
5
+ * property is structural: `Update`'s mutation runs *inside* the post-INSERT task, so it can never be
6
+ * reverted by the INSERT's `finalizeSave` reload (the "stuck at Running" race becomes impossible).
7
+ * @module @memberjunction/core
8
+ */
9
+ import { KeyedSerialTaskQueue } from '@memberjunction/global';
10
+ import { EntitySaveOptions } from './interfaces.js';
11
+ import { LogError } from './logging.js';
12
+ /**
13
+ * A per-entity-instance serial save queue. INSERT and UPDATE of the same `BaseEntity` instance are
14
+ * serialized (the UPDATE waits for the INSERT to land); different entities save concurrently. All
15
+ * saves are fire-and-forget and **never throw outward** — a failed `Save()` (returned `false` or a
16
+ * thrown error) is logged and counted; call {@link Flush} at a run/goal boundary to await them and
17
+ * surface the failure count.
18
+ */
19
+ export class BaseEntitySaveQueue {
20
+ /**
21
+ * @param opts.onError Optional handler for a failed save's diagnostic message. Defaults to the
22
+ * global `LogError`. Consumers with structured logging (e.g. a category/metadata logger) pass
23
+ * their own so failures stay in their log stream.
24
+ */
25
+ constructor(opts) {
26
+ this.queue = new KeyedSerialTaskQueue();
27
+ this.onError = opts?.onError;
28
+ }
29
+ /**
30
+ * Fire-and-forget INSERT of a freshly `NewRecord()`'d entity. The entity instance is the
31
+ * serialization key, so a subsequent {@link Update} of the same instance waits for this to land.
32
+ */
33
+ Insert(entity) {
34
+ const label = this.labelFor('Insert', entity);
35
+ void this.queue.enqueue(entity, () => this.runSave(entity, label), { isOk: (ok) => ok === true, label });
36
+ }
37
+ /**
38
+ * Fire-and-forget INSERT that waits for `dependency`'s pending tasks to settle first. Use this for
39
+ * self-referencing foreign keys: a child entity whose FK points at `dependency` must not INSERT
40
+ * until the dependency's own INSERT has landed in the database.
41
+ */
42
+ InsertAfter(entity, dependency) {
43
+ const label = this.labelFor('Insert', entity);
44
+ void this.queue.enqueue(entity, () => this.runSave(entity, label), { isOk: (ok) => ok === true, label, after: dependency });
45
+ }
46
+ /**
47
+ * Fire-and-forget UPDATE chained after the entity's INSERT. `applyMutation` (if given) runs INSIDE
48
+ * the post-INSERT task — after `finalizeSave`'s reload — so its values always survive; the save is
49
+ * force-persisted with `IgnoreDirtyState`. Pass no mutation only when the fields are already set
50
+ * AND cannot race an in-flight INSERT.
51
+ */
52
+ Update(entity, applyMutation) {
53
+ const label = this.labelFor('Update', entity);
54
+ const options = new EntitySaveOptions();
55
+ options.IgnoreDirtyState = true;
56
+ void this.queue.enqueue(entity, () => this.runSave(entity, label, options, applyMutation), { isOk: (ok) => ok === true, label });
57
+ }
58
+ /** Awaits all pending saves and returns failure diagnostics. Call at the run/goal finalize boundary. */
59
+ Flush() {
60
+ return this.queue.flush();
61
+ }
62
+ /**
63
+ * Applies the optional mutation, saves, and returns success — logging and swallowing both a
64
+ * `false` result and a thrown error so a fire-and-forget save can never surface as an unhandled
65
+ * rejection (failures are counted via the queue's `isOk`).
66
+ */
67
+ async runSave(entity, label, options, applyMutation) {
68
+ try {
69
+ if (applyMutation) {
70
+ applyMutation(entity);
71
+ }
72
+ const ok = await entity.Save(options);
73
+ if (!ok) {
74
+ this.logFailure(`${label} failed: ${this.saveError(entity)}`);
75
+ }
76
+ return ok;
77
+ }
78
+ catch (e) {
79
+ this.logFailure(`${label} threw: ${e instanceof Error ? e.message : String(e)}`);
80
+ return false;
81
+ }
82
+ }
83
+ /** Routes a failure message to the caller-supplied handler, or the global `LogError` by default. */
84
+ logFailure(message) {
85
+ if (this.onError) {
86
+ this.onError(message);
87
+ }
88
+ else {
89
+ LogError(message);
90
+ }
91
+ }
92
+ labelFor(op, entity) {
93
+ let name = 'entity';
94
+ try {
95
+ name = entity.EntityInfo?.Name ?? 'entity';
96
+ }
97
+ catch {
98
+ /* EntityInfo unavailable (e.g. a bare mock) — fall back to the generic label */
99
+ }
100
+ return `BaseEntitySaveQueue.${op}(${name})`;
101
+ }
102
+ saveError(entity) {
103
+ return entity.LatestResult?.CompleteMessage ?? 'Save returned false';
104
+ }
105
+ }
106
+ //# sourceMappingURL=BaseEntitySaveQueue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BaseEntitySaveQueue.js","sourceRoot":"","sources":["../../src/generic/BaseEntitySaveQueue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,97 @@
1
+ import type { EntityDocumentResolver, FieldChange, FieldRuleSet, PromptResolver } from '@memberjunction/global';
2
+ import { BaseEntity } from './baseEntity.js';
3
+ import { IMetadataProvider } from './interfaces.js';
4
+ import { UserInfo } from './securityInfo.js';
5
+ /** Result of {@link EntityFieldRules.Validate} — a pre-flight check of a rule set against entity metadata. */
6
+ export interface EntityFieldRulesValidation {
7
+ /** True when every rule targets an existing, writable field and all source field references resolve. */
8
+ Valid: boolean;
9
+ /** Human-readable problems, one per offending rule (empty when Valid). */
10
+ Errors: string[];
11
+ }
12
+ /** Options controlling a compute/apply pass. */
13
+ export interface EntityFieldRulesOptions {
14
+ /**
15
+ * Extra data the rules may reference beyond the entity's own fields (merged on top of the entity's
16
+ * field map — a `formula` can read `fields.SomeContextKey`, a `condition` can read `SomeContextKey`).
17
+ * Use for values you already hold: a data context, a query result, an agent's output payload.
18
+ */
19
+ Context?: Record<string, unknown>;
20
+ /** When true, compute the diff but DO NOT write — the returned changes are the preview. */
21
+ DryRun?: boolean;
22
+ }
23
+ /** Outcome of {@link EntityFieldRules.ApplyToEntity}. */
24
+ export interface EntityFieldRulesResult {
25
+ /** The per-field diff (old → new) for every rule — the dry-run preview AND the applied record. */
26
+ Changes: FieldChange[];
27
+ /** Names of the fields that were (or, in dry-run, would be) written. */
28
+ AppliedFields: string[];
29
+ /** True when the entity was saved (always false in dry-run, on error, or when nothing changed). */
30
+ Saved: boolean;
31
+ /** True when this was a dry-run (no write attempted). */
32
+ DryRun: boolean;
33
+ /** Per-rule evaluation errors (condition/source/transform), if any. No write happens when present. */
34
+ Errors: string[];
35
+ /** Save error detail, when a real apply failed to persist. */
36
+ SaveError?: string;
37
+ }
38
+ /**
39
+ * Metadata-aware application of a {@link FieldRuleSet} to MJ entity records. Instantiate once per bulk
40
+ * run and reuse across records so the underlying expression cache is shared. Holds an acting user for
41
+ * the built-in lookup resolver.
42
+ */
43
+ export declare class EntityFieldRules {
44
+ private readonly contextUser?;
45
+ private readonly evaluator;
46
+ /**
47
+ * @param contextUser - The acting user, used by the built-in `RunView`-backed lookup resolver
48
+ * (required server-side; client-side may omit it).
49
+ * @param promptResolver - Optional resolver for `prompt` rule sources. Core does not depend on the AI
50
+ * stack, so this is injected by a higher layer that has an `AIPromptRunner` (the bulk-update
51
+ * processor supplies one). Omit it and any `prompt` rule reports a clear error instead of running.
52
+ * @param entityDocumentResolver - Optional resolver for `entityDocument` rule sources. Core does not
53
+ * depend on the templates/AI stack, so this is injected by a higher layer that can render an Entity
54
+ * Document (the processor supplies one). Omit it and any `entityDocument` rule reports a clear error.
55
+ */
56
+ constructor(contextUser?: UserInfo, promptResolver?: PromptResolver, entityDocumentResolver?: EntityDocumentResolver);
57
+ /**
58
+ * Pre-flight validation of a rule set against an entity's metadata. Pure + synchronous — safe to run
59
+ * in a UX on every edit. Checks each rule's target field exists and is writable, and that any `field`
60
+ * source reference resolves to a real field.
61
+ *
62
+ * @param entityName - The target entity.
63
+ * @param ruleSet - The rules to validate.
64
+ * @param provider - Metadata provider to resolve the entity (defaults to the global provider). Pass
65
+ * the owning provider in multi-provider contexts.
66
+ */
67
+ static Validate(entityName: string, ruleSet: FieldRuleSet, provider?: IMetadataProvider): EntityFieldRulesValidation;
68
+ /**
69
+ * Computes the per-field changes for a loaded entity — WITHOUT mutating it — coercing each new value
70
+ * to the target field's type. This is the dry-run primitive.
71
+ *
72
+ * @param entity - A loaded entity (its current values are the "old" side of the diff).
73
+ * @param ruleSet - The rules to evaluate.
74
+ * @param context - Optional extra data the rules may reference (merged over the entity's fields).
75
+ */
76
+ ComputeForEntity(entity: BaseEntity, ruleSet: FieldRuleSet, context?: Record<string, unknown>): Promise<FieldChange[]>;
77
+ /**
78
+ * Computes and (unless `DryRun`) writes the rule results onto the entity, then `Save()`s it — so MJ
79
+ * Record Changes versioning captures the before/after. No write occurs if any rule errored or nothing
80
+ * changed.
81
+ *
82
+ * @param entity - A loaded entity to update.
83
+ * @param ruleSet - The rules to apply.
84
+ * @param options - {@link EntityFieldRulesOptions} (context + dry-run).
85
+ */
86
+ ApplyToEntity(entity: BaseEntity, ruleSet: FieldRuleSet, options?: EntityFieldRulesOptions): Promise<EntityFieldRulesResult>;
87
+ /** Coerces a computed change's NewValue to the target field's TS type (no-op for un-applied/errored). */
88
+ private coerceChange;
89
+ /** Aligns a raw value to an entity field's TS type. Leaves the value untouched when it can't convert. */
90
+ private static coerceToType;
91
+ private static equal;
92
+ /** A `RunView`-backed resolver for `lookup` rule sources (match one row, return one field). */
93
+ private buildLookupResolver;
94
+ /** Minimal SQL literal rendering for the lookup filter (escapes string quotes; numbers/bools inline). */
95
+ private static sqlLiteral;
96
+ }
97
+ //# sourceMappingURL=EntityFieldRules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EntityFieldRules.d.ts","sourceRoot":"","sources":["../../src/generic/EntityFieldRules.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,sBAAsB,EAAE,WAAW,EAAE,YAAY,EAAkB,cAAc,EAAkB,MAAM,wBAAwB,CAAC;AAChJ,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG1C,8GAA8G;AAC9G,MAAM,WAAW,0BAA0B;IACvC,wGAAwG;IACxG,KAAK,EAAE,OAAO,CAAC;IACf,0EAA0E;IAC1E,MAAM,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,gDAAgD;AAChD,MAAM,WAAW,uBAAuB;IACpC;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,2FAA2F;IAC3F,MAAM,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,yDAAyD;AACzD,MAAM,WAAW,sBAAsB;IACnC,kGAAkG;IAClG,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,wEAAwE;IACxE,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,mGAAmG;IACnG,KAAK,EAAE,OAAO,CAAC;IACf,yDAAyD;IACzD,MAAM,EAAE,OAAO,CAAC;IAChB,sGAAsG;IACtG,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,qBAAa,gBAAgB;IAab,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAZzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsB;IAEhD;;;;;;;;;OASG;gBAC0B,WAAW,CAAC,EAAE,QAAQ,EAAE,cAAc,CAAC,EAAE,cAAc,EAAE,sBAAsB,CAAC,EAAE,sBAAsB;IAQrI;;;;;;;;;OASG;WACW,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,iBAAiB,GAAG,0BAA0B;IAwB3H;;;;;;;OAOG;IACU,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAMnI;;;;;;;;OAQG;IACU,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAmBzI,yGAAyG;IACzG,OAAO,CAAC,YAAY;IAYpB,yGAAyG;IACzG,OAAO,CAAC,MAAM,CAAC,YAAY;IAyB3B,OAAO,CAAC,MAAM,CAAC,KAAK;IAMpB,+FAA+F;IAC/F,OAAO,CAAC,mBAAmB;IAmB3B,yGAAyG;IACzG,OAAO,CAAC,MAAM,CAAC,UAAU;CAM5B"}
@@ -0,0 +1,201 @@
1
+ /**
2
+ * @fileoverview Metadata-aware field rules for MemberJunction entities — the higher-order sibling of
3
+ * the pure field-rules engine in `@memberjunction/global`.
4
+ *
5
+ * The pure engine ({@link FieldRulesEvaluator} / {@link FieldTransformEngine} in `@memberjunction/global`)
6
+ * is deliberately metadata-blind: it computes a per-field diff from a plain `Record<string, unknown>`
7
+ * and an injected lookup resolver, so it can run anywhere (client, server, integration sync — wherever
8
+ * the "record" came from). {@link EntityFieldRules} layers the things that only make sense when the
9
+ * **target is a real MJ entity**, and which need the metadata layer in this package:
10
+ *
11
+ * 1. **Validation** — does each rule's target field exist on the entity, and is it writable
12
+ * (not a primary key / read-only / virtual)? Source `field` references valid? This is a pre-flight
13
+ * check a UX can run before anyone presses "Run".
14
+ * 2. **Type coercion** — a formula that yields the string `"42"` becomes the number `42` for a numeric
15
+ * column, using each field's {@link EntityFieldInfo.TSType}. The pure engine produces raw values;
16
+ * this aligns them to the entity's actual SQL types.
17
+ * 3. **Entity lookups** — a built-in `RunView`-backed resolver for `lookup` rule sources.
18
+ * 4. **Apply** — write the computed values onto a {@link BaseEntity} and `Save()`, so MJ's Record
19
+ * Changes versioning captures the before/after automatically. Dry-run returns the diff and writes nothing.
20
+ *
21
+ * The **target is always an MJ entity**; the **source** may be the entity's own fields plus an optional
22
+ * injected `context` (data context, query result, agent output, related-entity lookups) — all data you
23
+ * already hold. When the *other side* is a live external system (its own protocol, auth, match
24
+ * resolution, sync direction), that is the domain of `@memberjunction/integration`, which uses the same
25
+ * pure transform engine for its per-field transforms. One engine, two purpose-built layers.
26
+ */
27
+ import { FieldRulesEvaluator } from '@memberjunction/global';
28
+ import { EntityFieldTSType } from './entityInfo.js';
29
+ import { Metadata } from './metadata.js';
30
+ import { RunView } from '../views/runView.js';
31
+ /**
32
+ * Metadata-aware application of a {@link FieldRuleSet} to MJ entity records. Instantiate once per bulk
33
+ * run and reuse across records so the underlying expression cache is shared. Holds an acting user for
34
+ * the built-in lookup resolver.
35
+ */
36
+ export class EntityFieldRules {
37
+ /**
38
+ * @param contextUser - The acting user, used by the built-in `RunView`-backed lookup resolver
39
+ * (required server-side; client-side may omit it).
40
+ * @param promptResolver - Optional resolver for `prompt` rule sources. Core does not depend on the AI
41
+ * stack, so this is injected by a higher layer that has an `AIPromptRunner` (the bulk-update
42
+ * processor supplies one). Omit it and any `prompt` rule reports a clear error instead of running.
43
+ * @param entityDocumentResolver - Optional resolver for `entityDocument` rule sources. Core does not
44
+ * depend on the templates/AI stack, so this is injected by a higher layer that can render an Entity
45
+ * Document (the processor supplies one). Omit it and any `entityDocument` rule reports a clear error.
46
+ */
47
+ constructor(contextUser, promptResolver, entityDocumentResolver) {
48
+ this.contextUser = contextUser;
49
+ this.evaluator = new FieldRulesEvaluator({
50
+ LookupResolver: this.buildLookupResolver(),
51
+ PromptResolver: promptResolver,
52
+ EntityDocumentResolver: entityDocumentResolver,
53
+ });
54
+ }
55
+ /**
56
+ * Pre-flight validation of a rule set against an entity's metadata. Pure + synchronous — safe to run
57
+ * in a UX on every edit. Checks each rule's target field exists and is writable, and that any `field`
58
+ * source reference resolves to a real field.
59
+ *
60
+ * @param entityName - The target entity.
61
+ * @param ruleSet - The rules to validate.
62
+ * @param provider - Metadata provider to resolve the entity (defaults to the global provider). Pass
63
+ * the owning provider in multi-provider contexts.
64
+ */
65
+ static Validate(entityName, ruleSet, provider) {
66
+ const md = provider ?? Metadata.Provider;
67
+ const entity = md?.EntityByName(entityName);
68
+ if (!entity) {
69
+ return { Valid: false, Errors: [`entity '${entityName}' not found in metadata`] };
70
+ }
71
+ const field = (name) => entity.FieldByName(name);
72
+ const errors = [];
73
+ ruleSet.Rules.forEach((rule, i) => {
74
+ const target = field(rule.TargetField);
75
+ if (!target) {
76
+ errors.push(`rule ${i + 1}: target field '${rule.TargetField}' does not exist on '${entityName}'`);
77
+ }
78
+ else if (target.ReadOnly) {
79
+ errors.push(`rule ${i + 1}: target field '${rule.TargetField}' is read-only and cannot be set`);
80
+ }
81
+ if (rule.Source.Kind === 'field' && !field(rule.Source.Field)) {
82
+ errors.push(`rule ${i + 1}: source field '${rule.Source.Field}' does not exist on '${entityName}'`);
83
+ }
84
+ });
85
+ return { Valid: errors.length === 0, Errors: errors };
86
+ }
87
+ /**
88
+ * Computes the per-field changes for a loaded entity — WITHOUT mutating it — coercing each new value
89
+ * to the target field's type. This is the dry-run primitive.
90
+ *
91
+ * @param entity - A loaded entity (its current values are the "old" side of the diff).
92
+ * @param ruleSet - The rules to evaluate.
93
+ * @param context - Optional extra data the rules may reference (merged over the entity's fields).
94
+ */
95
+ async ComputeForEntity(entity, ruleSet, context) {
96
+ const record = context ? { ...entity.GetAll(), ...context } : entity.GetAll();
97
+ const changes = await this.evaluator.ComputeChanges(record, ruleSet);
98
+ return changes.map((change) => this.coerceChange(change, entity));
99
+ }
100
+ /**
101
+ * Computes and (unless `DryRun`) writes the rule results onto the entity, then `Save()`s it — so MJ
102
+ * Record Changes versioning captures the before/after. No write occurs if any rule errored or nothing
103
+ * changed.
104
+ *
105
+ * @param entity - A loaded entity to update.
106
+ * @param ruleSet - The rules to apply.
107
+ * @param options - {@link EntityFieldRulesOptions} (context + dry-run).
108
+ */
109
+ async ApplyToEntity(entity, ruleSet, options) {
110
+ const changes = await this.ComputeForEntity(entity, ruleSet, options?.Context);
111
+ const errors = changes.filter((c) => c.Error).map((c) => `${c.Field}: ${c.Error}`);
112
+ const toApply = changes.filter((c) => c.Applied && c.Changed && !c.Error);
113
+ const base = {
114
+ Changes: changes, AppliedFields: toApply.map((c) => c.Field), Saved: false, DryRun: !!options?.DryRun, Errors: errors,
115
+ };
116
+ if (options?.DryRun || errors.length > 0 || toApply.length === 0) {
117
+ return base;
118
+ }
119
+ for (const change of toApply) {
120
+ // Dynamic, rule-driven field names — the legitimate use of Set() (no compile-time property).
121
+ entity.Set(change.Field, change.NewValue);
122
+ }
123
+ const saved = await entity.Save();
124
+ return { ...base, Saved: saved, SaveError: saved ? undefined : (entity.LatestResult?.CompleteMessage ?? 'save failed') };
125
+ }
126
+ /** Coerces a computed change's NewValue to the target field's TS type (no-op for un-applied/errored). */
127
+ coerceChange(change, entity) {
128
+ if (!change.Applied || change.Error || change.NewValue == null) {
129
+ return change;
130
+ }
131
+ const field = entity.EntityInfo.FieldByName(change.Field);
132
+ if (!field) {
133
+ return change;
134
+ }
135
+ const coerced = EntityFieldRules.coerceToType(change.NewValue, field.TSType);
136
+ return coerced === change.NewValue ? change : { ...change, NewValue: coerced, Changed: !EntityFieldRules.equal(change.OldValue, coerced) };
137
+ }
138
+ /** Aligns a raw value to an entity field's TS type. Leaves the value untouched when it can't convert. */
139
+ static coerceToType(value, tsType) {
140
+ switch (tsType) {
141
+ case EntityFieldTSType.Number: {
142
+ const n = Number(value);
143
+ return Number.isFinite(n) ? n : value;
144
+ }
145
+ case EntityFieldTSType.Boolean: {
146
+ if (typeof value === 'boolean')
147
+ return value;
148
+ if (typeof value === 'number')
149
+ return value !== 0;
150
+ const s = String(value).toLowerCase().trim();
151
+ if (s === 'true' || s === '1' || s === 'yes')
152
+ return true;
153
+ if (s === 'false' || s === '0' || s === 'no')
154
+ return false;
155
+ return value;
156
+ }
157
+ case EntityFieldTSType.Date: {
158
+ const d = value instanceof Date ? value : new Date(String(value));
159
+ return isNaN(d.getTime()) ? value : d;
160
+ }
161
+ case EntityFieldTSType.String:
162
+ return typeof value === 'string' ? value : String(value);
163
+ default:
164
+ return value;
165
+ }
166
+ }
167
+ static equal(a, b) {
168
+ if (a === b)
169
+ return true;
170
+ if (a instanceof Date && b instanceof Date)
171
+ return a.getTime() === b.getTime();
172
+ return a == null && b == null;
173
+ }
174
+ /** A `RunView`-backed resolver for `lookup` rule sources (match one row, return one field). */
175
+ buildLookupResolver() {
176
+ return async (lookup) => {
177
+ const result = await new RunView().RunView({
178
+ EntityName: lookup.Entity,
179
+ ExtraFilter: `[${lookup.MatchField}] = ${EntityFieldRules.sqlLiteral(lookup.MatchValue)}`,
180
+ Fields: [lookup.ReturnField],
181
+ MaxRows: 1,
182
+ ResultType: 'simple',
183
+ }, this.contextUser);
184
+ if (!result.Success || !result.Results?.length) {
185
+ return undefined;
186
+ }
187
+ return result.Results[0][lookup.ReturnField];
188
+ };
189
+ }
190
+ /** Minimal SQL literal rendering for the lookup filter (escapes string quotes; numbers/bools inline). */
191
+ static sqlLiteral(value) {
192
+ if (value == null)
193
+ return 'NULL';
194
+ if (typeof value === 'number' || typeof value === 'bigint')
195
+ return String(value);
196
+ if (typeof value === 'boolean')
197
+ return value ? '1' : '0';
198
+ return `'${String(value).replace(/'/g, "''")}'`;
199
+ }
200
+ }
201
+ //# sourceMappingURL=EntityFieldRules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EntityFieldRules.js","sourceRoot":"","sources":["../../src/generic/EntityFieldRules.ts"],"names":[],"mappings":"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"}
@@ -11,6 +11,7 @@ export declare const AUTH_PROVIDER_TYPES: {
11
11
  readonly OKTA: "okta";
12
12
  readonly COGNITO: "cognito";
13
13
  readonly GOOGLE: "google";
14
+ readonly WORKOS: "workos";
14
15
  readonly CUSTOM: "custom";
15
16
  };
16
17
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"authTypes.d.ts","sourceRoot":"","sources":["../../src/generic/authTypes.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,eAAO,MAAM,mBAAmB;;;;;;;CAOtB,CAAC;AAEX;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,OAAO,mBAAmB,CAAC,MAAM,OAAO,mBAAmB,CAAC,CAAC;AAE5F;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAAC;IAEhC;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAElB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAEjB;;OAEG;IACH,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,SAAS,CAAC,EAAE,IAAI,CAAC;IAEjB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAExB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB"}
1
+ {"version":3,"file":"authTypes.d.ts","sourceRoot":"","sources":["../../src/generic/authTypes.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;CAQtB,CAAC;AAEX;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,OAAO,mBAAmB,CAAC,MAAM,OAAO,mBAAmB,CAAC,CAAC;AAE5F;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAAC;IAEhC;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAElB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAEjB;;OAEG;IACH,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,SAAS,CAAC,EAAE,IAAI,CAAC;IAEjB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAExB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB"}
@@ -11,6 +11,7 @@ export const AUTH_PROVIDER_TYPES = {
11
11
  OKTA: 'okta',
12
12
  COGNITO: 'cognito',
13
13
  GOOGLE: 'google',
14
+ WORKOS: 'workos',
14
15
  CUSTOM: 'custom'
15
16
  };
16
17
  //# sourceMappingURL=authTypes.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"authTypes.js","sourceRoot":"","sources":["../../src/generic/authTypes.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,QAAQ;CACR,CAAC"}
1
+ {"version":3,"file":"authTypes.js","sourceRoot":"","sources":["../../src/generic/authTypes.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,OAAO;IACd,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,QAAQ;CACR,CAAC"}
@@ -174,6 +174,26 @@ export interface EngineDataMapEntry {
174
174
  data: unknown[];
175
175
  loadedSuccessfully: boolean;
176
176
  errorMessage?: string;
177
+ /**
178
+ * True when this config was skipped because the current user lacks read
179
+ * permission on its entity. The data array will be empty `[]`.
180
+ */
181
+ permissionDenied?: boolean;
182
+ }
183
+ /**
184
+ * Thrown when engine data is accessed but was never loaded because the
185
+ * current user lacks read permissions on the underlying entities.
186
+ *
187
+ * Consumers that want graceful degradation should check
188
+ * `engine.IsPermissionConstrained` BEFORE accessing properties.
189
+ * This exception is the safety net for code paths that forget to check.
190
+ */
191
+ export declare class PermissionConstrainedError extends Error {
192
+ /** The engine class name (e.g., 'AIEngineBase', 'QueryEngine') */
193
+ readonly EngineName: string;
194
+ /** The entity names that were denied */
195
+ readonly DeniedEntities: string[];
196
+ constructor(engineName: string, deniedEntities: string[]);
177
197
  }
178
198
  export declare abstract class BaseEngine<T> extends BaseSingleton<T> implements IStartupSink {
179
199
  private _loaded;
@@ -188,6 +208,8 @@ export declare abstract class BaseEngine<T> extends BaseSingleton<T> implements
188
208
  private _dataChange$;
189
209
  private _cacheChangeUnsubscribers;
190
210
  private _propertySubjects;
211
+ private _isPermissionConstrained;
212
+ private _deniedEntityNames;
191
213
  /**
192
214
  * Returns an Observable for a specific engine array property. Subscribers receive the
193
215
  * current array immediately (BehaviorSubject semantics), then re-receive the same array
@@ -238,6 +260,42 @@ export declare abstract class BaseEngine<T> extends BaseSingleton<T> implements
238
260
  * @param affectedEntity - For add/update/delete, the entity that was affected
239
261
  */
240
262
  protected NotifyDataChange(config: BaseEnginePropertyConfig, data: unknown[], changeType?: 'refresh' | 'add' | 'update' | 'delete', affectedEntity?: BaseEntity): void;
263
+ /**
264
+ * True when the engine loaded successfully but all entity configs were
265
+ * skipped because the current user lacks read permissions. Accessor
266
+ * properties will throw {@link PermissionConstrainedError} if accessed
267
+ * in this state. Check this flag first to degrade gracefully.
268
+ */
269
+ get IsPermissionConstrained(): boolean;
270
+ /**
271
+ * Retrieves engine-loaded data for a config property by name. This is the
272
+ * canonical accessor for engine getter properties — it checks the data map
273
+ * for permission denial and throws {@link PermissionConstrainedError} with
274
+ * the specific denied entity name(s) if the config was skipped.
275
+ *
276
+ * Subclasses should use this in every getter that exposes engine-loaded data:
277
+ * ```typescript
278
+ * public get Models(): MJAIModelEntityExtended[] {
279
+ * return this.GetConfigData<MJAIModelEntityExtended>('_models');
280
+ * }
281
+ * ```
282
+ *
283
+ * @param propertyName - The config property name (e.g., '_models', '_agents'),
284
+ * matching the PropertyName used in the engine's Config() params array.
285
+ * @returns The data array for the property, or an empty array if not yet loaded.
286
+ * @throws {PermissionConstrainedError} if the property was skipped due to permission denial.
287
+ */
288
+ protected GetConfigData<E>(propertyName: string): E[];
289
+ /**
290
+ * Check if a specific property was skipped due to permission denial.
291
+ * Forward-compatible with a future partial-loading approach.
292
+ */
293
+ IsPropertyPermissionConstrained(propertyName: string): boolean;
294
+ /**
295
+ * List of entity names that were skipped due to permission denial.
296
+ * Empty if not permission-constrained. Useful for logging/diagnostics.
297
+ */
298
+ get PermissionConstrainedEntities(): string[];
241
299
  /**
242
300
  * Controls the default RunView ResultType for all entity configs loaded by this engine.
243
301
  * Override in subclasses to change the default for the entire engine without modifying
@@ -539,6 +597,18 @@ export declare abstract class BaseEngine<T> extends BaseSingleton<T> implements
539
597
  * directly from the database. Passed through from {@link Load} when `forceRefresh` is true (i.e., `Config(true)`).
540
598
  */
541
599
  protected LoadConfigs(configs: Partial<BaseEnginePropertyConfig>[], contextUser: UserInfo, bypassCache?: boolean): Promise<void>;
600
+ /**
601
+ * All-or-nothing permission gate: checks `CanRead` on every entity config. If ANY
602
+ * entity is denied, ALL configs are skipped — the engine is marked permission-constrained
603
+ * and its data arrays are set to empty `[]`. This prevents noisy permission-denied errors
604
+ * and endless retry loops for users with limited permissions (e.g., org-scoped SaaS roles).
605
+ *
606
+ * On the server side with a system user (who has all permissions), this method returns
607
+ * the original configs unchanged — no behavior change for privileged users.
608
+ *
609
+ * @returns The original configs array (all permissions pass) or an empty array (any denied)
610
+ */
611
+ protected CheckPermissionsOrSkipAll(configs: BaseEnginePropertyConfig[], contextUser: UserInfo): BaseEnginePropertyConfig[];
542
612
  /**
543
613
  * Loads a single metadata configuration.
544
614
  * @param config - The metadata configuration to load