@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.
- package/dist/generic/BaseEntitySaveQueue.d.ts +60 -0
- package/dist/generic/BaseEntitySaveQueue.d.ts.map +1 -0
- package/dist/generic/BaseEntitySaveQueue.js +106 -0
- package/dist/generic/BaseEntitySaveQueue.js.map +1 -0
- package/dist/generic/EntityFieldRules.d.ts +97 -0
- package/dist/generic/EntityFieldRules.d.ts.map +1 -0
- package/dist/generic/EntityFieldRules.js +201 -0
- package/dist/generic/EntityFieldRules.js.map +1 -0
- package/dist/generic/authTypes.d.ts +1 -0
- package/dist/generic/authTypes.d.ts.map +1 -1
- package/dist/generic/authTypes.js +1 -0
- package/dist/generic/authTypes.js.map +1 -1
- package/dist/generic/baseEngine.d.ts +70 -0
- package/dist/generic/baseEngine.d.ts.map +1 -1
- package/dist/generic/baseEngine.js +140 -1
- package/dist/generic/baseEngine.js.map +1 -1
- package/dist/generic/baseEntity.d.ts.map +1 -1
- package/dist/generic/baseEntity.js +35 -15
- package/dist/generic/baseEntity.js.map +1 -1
- package/dist/generic/compositeKey.js +1 -1
- package/dist/generic/compositeKey.js.map +1 -1
- package/dist/generic/databaseProviderBase.js +1 -1
- package/dist/generic/databaseProviderBase.js.map +1 -1
- package/dist/generic/entityInfo.d.ts +9 -0
- package/dist/generic/entityInfo.d.ts.map +1 -1
- package/dist/generic/entityInfo.js +10 -1
- package/dist/generic/entityInfo.js.map +1 -1
- package/dist/generic/graphqlTypeNames.d.ts.map +1 -1
- package/dist/generic/graphqlTypeNames.js +6 -1
- package/dist/generic/graphqlTypeNames.js.map +1 -1
- package/dist/generic/interfaces.d.ts +24 -0
- package/dist/generic/interfaces.d.ts.map +1 -1
- package/dist/generic/interfaces.js.map +1 -1
- package/dist/generic/providerBase.d.ts.map +1 -1
- package/dist/generic/providerBase.js +5 -0
- package/dist/generic/providerBase.js.map +1 -1
- package/dist/generic/queryResultEnricher.d.ts +77 -0
- package/dist/generic/queryResultEnricher.d.ts.map +1 -0
- package/dist/generic/queryResultEnricher.js +41 -0
- package/dist/generic/queryResultEnricher.js.map +1 -0
- package/dist/generic/runQuery.d.ts +17 -0
- package/dist/generic/runQuery.d.ts.map +1 -1
- package/dist/generic/runQuery.js.map +1 -1
- package/dist/generic/telemetryManager.d.ts +27 -4
- package/dist/generic/telemetryManager.d.ts.map +1 -1
- package/dist/generic/telemetryManager.js +50 -11
- package/dist/generic/telemetryManager.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- 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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"}
|
|
@@ -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
|
|
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"}
|
|
@@ -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
|