@memberjunction/actions 6.1.0-edge.0 → 6.1.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.
- package/README.md +99 -2
- package/dist/entity-actions/EntityActionDispatchGuard.d.ts +58 -0
- package/dist/entity-actions/EntityActionDispatchGuard.d.ts.map +1 -0
- package/dist/entity-actions/EntityActionDispatchGuard.js +148 -0
- package/dist/entity-actions/EntityActionDispatchGuard.js.map +1 -0
- package/dist/entity-actions/EntityActionEngine.d.ts +5 -2
- package/dist/entity-actions/EntityActionEngine.d.ts.map +1 -1
- package/dist/entity-actions/EntityActionEngine.js +4 -1
- package/dist/entity-actions/EntityActionEngine.js.map +1 -1
- package/dist/entity-actions/EntityActionInvocationTypes.d.ts +60 -7
- package/dist/entity-actions/EntityActionInvocationTypes.d.ts.map +1 -1
- package/dist/entity-actions/EntityActionInvocationTypes.js +127 -29
- package/dist/entity-actions/EntityActionInvocationTypes.js.map +1 -1
- package/dist/generic/ActionEngine.d.ts +70 -3
- package/dist/generic/ActionEngine.d.ts.map +1 -1
- package/dist/generic/ActionEngine.js +101 -10
- package/dist/generic/ActionEngine.js.map +1 -1
- package/dist/generic/OAuth2Manager.d.ts.map +1 -1
- package/dist/generic/OAuth2Manager.js +8 -2
- package/dist/generic/OAuth2Manager.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +11 -10
package/README.md
CHANGED
|
@@ -65,6 +65,9 @@ flowchart TD
|
|
|
65
65
|
- **Entity Action Invocation** — Bind actions to entity CRUD lifecycle events (BeforeCreate, AfterUpdate, etc.)
|
|
66
66
|
- **Batch Entity Actions** — Run actions against Lists or Views of records with consolidated results
|
|
67
67
|
- **Script Evaluation** — Entity action params support runtime script evaluation with entity context
|
|
68
|
+
- **Transition Filters** — Filters see the values on *both* sides of a save, so a gate can express "when Status *becomes* Approved" rather than only "when Status *is* Approved"
|
|
69
|
+
- **Durable Dispatch** — An `After*` binding can opt into surviving a process restart (`EntityAction.RunMode = 'Durable'`)
|
|
70
|
+
- **Execution-Log Retention** — Each log row is stamped with the retention its action declared, and a scheduled purge enforces it
|
|
68
71
|
|
|
69
72
|
## Usage
|
|
70
73
|
|
|
@@ -261,7 +264,7 @@ classDiagram
|
|
|
261
264
|
}
|
|
262
265
|
|
|
263
266
|
class Validate {
|
|
264
|
-
|
|
267
|
+
<<no overrides>>
|
|
265
268
|
}
|
|
266
269
|
|
|
267
270
|
EntityActionInvocationBase <|-- SingleRecord
|
|
@@ -270,9 +273,103 @@ classDiagram
|
|
|
270
273
|
|
|
271
274
|
note for SingleRecord "Registered for: Read, BeforeCreate,\nBeforeUpdate, BeforeDelete, AfterCreate,\nAfterUpdate, AfterDelete, SingleRecord"
|
|
272
275
|
note for MultipleRecords "Registered for: List, View"
|
|
273
|
-
note for Validate "Registered for: Validate"
|
|
276
|
+
note for Validate "Registered for: Validate.\nDeliberately EMPTY — it inherits SingleRecord\nso scope resolution and provenance stay true\nfor Validate, rather than drifting in a copy."
|
|
274
277
|
```
|
|
275
278
|
|
|
279
|
+
### Transition Filters — deciding on the change, not the end state
|
|
280
|
+
|
|
281
|
+
An entity action bound to `AfterUpdate` used to see only the record's *current* state, so "when an
|
|
282
|
+
invoice crosses 90 days" and "when Status becomes Approved" were inexpressible: the second is
|
|
283
|
+
indistinguishable from "when Status **is** Approved", which is true on every subsequent save too.
|
|
284
|
+
|
|
285
|
+
`EntityChangeContext` (in `@memberjunction/actions-base`) carries both sides of the save to the place
|
|
286
|
+
filters run. It is built from `EntityField.OldValue`, which `BaseEntity` has tracked all along — no
|
|
287
|
+
new tracking, just carrying what already existed to where it was needed.
|
|
288
|
+
|
|
289
|
+
Inside an Action Filter's `Code`, the change is available on `ActionFilterContext`:
|
|
290
|
+
|
|
291
|
+
```javascript
|
|
292
|
+
// "when Status becomes Approved" — fires once, on the transition
|
|
293
|
+
return ActionFilterContext.DidFieldChangeToValue('Status', 'Approved');
|
|
294
|
+
|
|
295
|
+
// the raw before/after bags, for anything the shorthands do not cover
|
|
296
|
+
const { OldValues, NewValues } = ActionFilterContext;
|
|
297
|
+
return NewValues.Amount > 100 && OldValues.Amount <= 100;
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
| Name | Meaning |
|
|
301
|
+
|---|---|
|
|
302
|
+
| `DidFieldChange(field)` | the field's value actually differs across this save |
|
|
303
|
+
| `DidFieldChangeToValue(field, value)` | …and its new value equals `value` (compared loosely, so `'1'` matches `1`) |
|
|
304
|
+
| `OldValues` / `NewValues` | both sides, by field name |
|
|
305
|
+
| `change` | the full `EntityChangeContext`, or `undefined` when there was no save behind the run |
|
|
306
|
+
|
|
307
|
+
Three behaviours worth knowing:
|
|
308
|
+
|
|
309
|
+
- **A create reports no changes.** A record whose Status started at `Approved` did not *become*
|
|
310
|
+
anything, so `DidFieldChange` is false for every field on an insert.
|
|
311
|
+
- **Absence reads as false.** A direct invocation or a List/View fan-out has no save behind it, so
|
|
312
|
+
the helpers answer false rather than guessing — filters gate execution, and firing on a question
|
|
313
|
+
nobody could answer is the wrong default.
|
|
314
|
+
- **Evaluation is fail-closed.** A filter that throws, returns a non-boolean, or cannot be resolved
|
|
315
|
+
prevents the run. That is why an `EntityActionFilter` row with `Status = 'Disabled'` is *skipped*
|
|
316
|
+
rather than consulted: a disabled gate that was still evaluated would not be inert, it would block
|
|
317
|
+
the action permanently.
|
|
318
|
+
|
|
319
|
+
**A prevented run still writes an `ActionExecutionLog` row**, carrying
|
|
320
|
+
`ACTION_PREVENTED_BY_FILTER_MESSAGE` as its `Message`. That is deliberate — an operator needs to see
|
|
321
|
+
that a filter refused, rather than wondering why nothing happened. It does mean *"a log row exists"*
|
|
322
|
+
is not the same question as *"the action ran"*, and code (or a test) that conflates the two will
|
|
323
|
+
report a working filter as a failure to gate.
|
|
324
|
+
|
|
325
|
+
> **Behaviour change.** Until this release the refusal branch logged that row and then executed the
|
|
326
|
+
> action anyway, so filters recorded preventing things they did not prevent. They now genuinely
|
|
327
|
+
> prevent. If you have `ActionFilter` rows configured, actions that were firing despite them will
|
|
328
|
+
> stop — which is what the rows always asked for.
|
|
329
|
+
|
|
330
|
+
### Durable dispatch — `After*` work that survives a restart
|
|
331
|
+
|
|
332
|
+
`After*` entity actions are dispatched fire-and-forget so a user's save is not held open by work that
|
|
333
|
+
happens afterwards. The cost is that a process dying mid-flight loses the action, with nothing to
|
|
334
|
+
retry it.
|
|
335
|
+
|
|
336
|
+
Setting `EntityAction.RunMode = 'Durable'` routes that dispatch to the task-graph substrate instead:
|
|
337
|
+
the work becomes a single-node durable graph with the claim protocol, restart recovery and orphan
|
|
338
|
+
reclaim that already exist there. It is per-binding and defaults to `Inline`, because durability
|
|
339
|
+
costs a Task row, a dispatcher hop of latency, and the action's parameters persisted at rest.
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
binding.RunMode = 'Durable'; // MJ: Entity Actions
|
|
343
|
+
await binding.Save();
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Four things the mode does *not* change:
|
|
347
|
+
|
|
348
|
+
- **`Validate` and `Before*` ignore it entirely.** Those run inside the save and can abort it;
|
|
349
|
+
deferring them would decide the save's outcome after it had already happened.
|
|
350
|
+
- **A host with no submitter runs inline.** `RunMode = 'Durable'` asks for the work to be harder to
|
|
351
|
+
lose, so refusing to run it where the durable path is unavailable would make opting in *less*
|
|
352
|
+
reliable than leaving it off. The same fallback covers a failed submission, with the reason logged.
|
|
353
|
+
- **Parameters are redacted before they are persisted.** A parameter the binding marked as not-logged
|
|
354
|
+
arrives at the durable runner absent, not secret.
|
|
355
|
+
- **The self-trigger guard does not follow the work.** `EntityActionDispatchGuard` tracks origin
|
|
356
|
+
through `AsyncLocalStorage`, which a dispatcher in another process is definitionally outside of. A
|
|
357
|
+
durable action that writes back to its own record must set
|
|
358
|
+
`EntitySaveOptions.OriginatingEntityActionIDs` — the explicit channel for exactly this case.
|
|
359
|
+
|
|
360
|
+
### Execution-log retention
|
|
361
|
+
|
|
362
|
+
`Action.RetentionPeriod` (days; `NULL` means indefinite) is stamped onto each `ActionExecutionLog` row
|
|
363
|
+
when the run starts, so the row is self-describing. Retention is therefore decided at write time:
|
|
364
|
+
editing an action's retention changes what is kept *going forward* rather than retroactively deleting
|
|
365
|
+
history written under the previous policy.
|
|
366
|
+
|
|
367
|
+
Enforcement is a scheduled job (`Action Log Retention`), opt-in like every maintenance driver — the
|
|
368
|
+
job type activates nothing until someone creates a `MJ: Scheduled Job` of it with a cron expression.
|
|
369
|
+
It purges oldest-first, bounded per run, and reports when it stopped at its ceiling rather than
|
|
370
|
+
because it was finished. Rows with no retention are kept unless the job is explicitly configured with
|
|
371
|
+
`DefaultRetentionDays`.
|
|
372
|
+
|
|
276
373
|
### Class Hierarchy
|
|
277
374
|
|
|
278
375
|
```mermaid
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { BaseSingleton } from '@memberjunction/global';
|
|
2
|
+
/** What the guard decided to do with a dispatch. */
|
|
3
|
+
export type EntityActionDispatchOutcome =
|
|
4
|
+
/** It ran (possibly followed by coalesced reruns). */
|
|
5
|
+
'Ran'
|
|
6
|
+
/** The action was re-entering itself on the same record; dropped. */
|
|
7
|
+
| 'Suppressed'
|
|
8
|
+
/** A run for this key was already in flight; folded into one pending rerun. */
|
|
9
|
+
| 'Coalesced';
|
|
10
|
+
/**
|
|
11
|
+
* The identity a dispatch is guarded by. Record-level rather than entity-level on purpose: two
|
|
12
|
+
* different invoices changing at once are unrelated events and must not block each other.
|
|
13
|
+
*/
|
|
14
|
+
export declare function BuildEntityActionDispatchKey(entityActionID: string, entityID: string, recordKey: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Guards automatic entity-action dispatch against self-triggering and save bursts.
|
|
17
|
+
*
|
|
18
|
+
* Process-scoped. Two servers behind a load balancer each hold their own view, which is correct for
|
|
19
|
+
* the burst case (each coalesces its own traffic) and is the reason the durable case needs the
|
|
20
|
+
* explicit marker rather than this.
|
|
21
|
+
*/
|
|
22
|
+
export declare class EntityActionDispatchGuard extends BaseSingleton<EntityActionDispatchGuard> {
|
|
23
|
+
static get Instance(): EntityActionDispatchGuard;
|
|
24
|
+
/**
|
|
25
|
+
* The set of dispatch keys currently executing *above* this point in the async call tree.
|
|
26
|
+
* A set rather than a single value because actions legitimately chain: A on record 1 may save
|
|
27
|
+
* record 2 and fire B, and B must still be able to detect re-entry into A.
|
|
28
|
+
*/
|
|
29
|
+
private originStack;
|
|
30
|
+
private inFlight;
|
|
31
|
+
/** True when this key is already executing somewhere up the current async call tree. */
|
|
32
|
+
IsSelfTriggered(key: string): boolean;
|
|
33
|
+
/** True when a run for this key is executing anywhere in this process. */
|
|
34
|
+
IsInFlight(key: string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Run an automatic entity-action dispatch under the guard.
|
|
37
|
+
*
|
|
38
|
+
* @param key from {@link BuildEntityActionDispatchKey}
|
|
39
|
+
* @param run the dispatch itself; invoked at most once per call, possibly later than the call
|
|
40
|
+
* @returns what the guard decided. `'Ran'` resolves only after the run and any coalesced
|
|
41
|
+
* reruns have finished, so a caller that awaits it awaits the whole settled chain.
|
|
42
|
+
*/
|
|
43
|
+
Dispatch(key: string, run: () => Promise<unknown>): Promise<EntityActionDispatchOutcome>;
|
|
44
|
+
/** Run `fn` with `key` pushed onto the ambient origin stack. */
|
|
45
|
+
private runWithOrigin;
|
|
46
|
+
/**
|
|
47
|
+
* Run whatever queued up while the slot was busy, until nothing is left.
|
|
48
|
+
*
|
|
49
|
+
* Deliberately uncapped: each iteration means real dispatches arrived while the previous run was
|
|
50
|
+
* executing, so stopping early would silently drop work. It terminates because arrivals stop —
|
|
51
|
+
* and the case that would *not* terminate, an action re-triggering itself, never reaches here
|
|
52
|
+
* (it is suppressed above). The threshold log exists so a chain that does run away is visible.
|
|
53
|
+
*/
|
|
54
|
+
private drain;
|
|
55
|
+
/** Drops all in-flight bookkeeping. For tests — never call this from application code. */
|
|
56
|
+
ResetForTesting(): void;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=EntityActionDispatchGuard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EntityActionDispatchGuard.d.ts","sourceRoot":"","sources":["../../src/entity-actions/EntityActionDispatchGuard.ts"],"names":[],"mappings":"AAoCA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAGvD,oDAAoD;AACpD,MAAM,MAAM,2BAA2B;AACnC,sDAAsD;AACpD,KAAK;AACP,qEAAqE;GACnE,YAAY;AACd,+EAA+E;GAC7E,WAAW,CAAC;AAElB;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEhH;AAeD;;;;;;GAMG;AACH,qBAAa,yBAA0B,SAAQ,aAAa,CAAC,yBAAyB,CAAC;IACnF,WAAkB,QAAQ,IAAI,yBAAyB,CAEtD;IAED;;;;OAIG;IACH,OAAO,CAAC,WAAW,CAAgD;IAEnE,OAAO,CAAC,QAAQ,CAAoC;IAEpD,wFAAwF;IACjF,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAI5C,0EAA0E;IACnE,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIvC;;;;;;;OAOG;IACU,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,2BAA2B,CAAC;IA6BrG,gEAAgE;IAChE,OAAO,CAAC,aAAa;IAOrB;;;;;;;OAOG;YACW,KAAK;IAmBnB,0FAA0F;IACnF,eAAe,IAAI,IAAI;CAGjC"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Re-entrancy and burst control for automatically-dispatched entity actions.
|
|
3
|
+
*
|
|
4
|
+
* **The problem.** An entity action that runs on `AfterUpdate` and writes back to the record that
|
|
5
|
+
* triggered it re-fires itself, forever. This is not an exotic case — it is the *normal* shape of
|
|
6
|
+
* every enrich-and-write-back automation: "when a ticket changes, summarize it and store the
|
|
7
|
+
* summary" saves the ticket. The same is true of a workflow bound to an entity-change trigger whose
|
|
8
|
+
* graph touches the record it was started by.
|
|
9
|
+
*
|
|
10
|
+
* A second, quieter problem shares the same key: a record saved ten times in a second launches ten
|
|
11
|
+
* overlapping runs of the same after-save action, each reading a state the next one invalidates.
|
|
12
|
+
*
|
|
13
|
+
* **The mechanism.** Every automatic dispatch is keyed by
|
|
14
|
+
* `(entity action, entity, record)` — the identity that both problems are about.
|
|
15
|
+
*
|
|
16
|
+
* - **Self-trigger → suppress.** A dispatch that arrives while its own key is on the origin stack
|
|
17
|
+
* is the action re-entering itself, and is dropped. Deferring it instead would turn an infinite
|
|
18
|
+
* loop into an infinite *sequence*, which is no better.
|
|
19
|
+
* - **Overlap → coalesce, latest wins.** A dispatch for a key that is merely in flight (not on the
|
|
20
|
+
* origin stack — so it came from somewhere else) does not stack. It sets a rerun flag, and one
|
|
21
|
+
* more run happens after the current one finishes. A burst of saves collapses to at most one
|
|
22
|
+
* pending run, and that run sees the final state rather than a stale one.
|
|
23
|
+
*
|
|
24
|
+
* Origin tracking uses `AsyncLocalStorage`, so it propagates through every `await` inside an
|
|
25
|
+
* action — including an agent run, its sub-agents, and any action they invoke — without a single
|
|
26
|
+
* call site having to pass anything down.
|
|
27
|
+
*
|
|
28
|
+
* **Known limit: a durable hop escapes the ambient context.** When work detaches to another process
|
|
29
|
+
* or a later moment (a task graph handed to the dispatcher, a queued job), the origin stack does not
|
|
30
|
+
* travel with it, so a write-back from there is indistinguishable from a user's edit. For those
|
|
31
|
+
* paths the origin must be declared explicitly — `EntitySaveOptions.OriginatingEntityActionIDs`
|
|
32
|
+
* exists for exactly that, and carries the same meaning.
|
|
33
|
+
*
|
|
34
|
+
* @module @memberjunction/actions
|
|
35
|
+
*/
|
|
36
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
37
|
+
import { BaseSingleton } from '@memberjunction/global';
|
|
38
|
+
import { LogStatus } from '@memberjunction/core';
|
|
39
|
+
/**
|
|
40
|
+
* The identity a dispatch is guarded by. Record-level rather than entity-level on purpose: two
|
|
41
|
+
* different invoices changing at once are unrelated events and must not block each other.
|
|
42
|
+
*/
|
|
43
|
+
export function BuildEntityActionDispatchKey(entityActionID, entityID, recordKey) {
|
|
44
|
+
return `${entityActionID}|${entityID}|${recordKey}`.toLowerCase();
|
|
45
|
+
}
|
|
46
|
+
/** Reruns after which a sustained rerun chain is worth a log line. Not a cap — see `drain`. */
|
|
47
|
+
const RERUN_WARN_THRESHOLD = 10;
|
|
48
|
+
/**
|
|
49
|
+
* Guards automatic entity-action dispatch against self-triggering and save bursts.
|
|
50
|
+
*
|
|
51
|
+
* Process-scoped. Two servers behind a load balancer each hold their own view, which is correct for
|
|
52
|
+
* the burst case (each coalesces its own traffic) and is the reason the durable case needs the
|
|
53
|
+
* explicit marker rather than this.
|
|
54
|
+
*/
|
|
55
|
+
export class EntityActionDispatchGuard extends BaseSingleton {
|
|
56
|
+
constructor() {
|
|
57
|
+
super(...arguments);
|
|
58
|
+
/**
|
|
59
|
+
* The set of dispatch keys currently executing *above* this point in the async call tree.
|
|
60
|
+
* A set rather than a single value because actions legitimately chain: A on record 1 may save
|
|
61
|
+
* record 2 and fire B, and B must still be able to detect re-entry into A.
|
|
62
|
+
*/
|
|
63
|
+
this.originStack = new AsyncLocalStorage();
|
|
64
|
+
this.inFlight = new Map();
|
|
65
|
+
}
|
|
66
|
+
static get Instance() {
|
|
67
|
+
return super.getInstance();
|
|
68
|
+
}
|
|
69
|
+
/** True when this key is already executing somewhere up the current async call tree. */
|
|
70
|
+
IsSelfTriggered(key) {
|
|
71
|
+
return this.originStack.getStore()?.has(key) ?? false;
|
|
72
|
+
}
|
|
73
|
+
/** True when a run for this key is executing anywhere in this process. */
|
|
74
|
+
IsInFlight(key) {
|
|
75
|
+
return this.inFlight.has(key);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Run an automatic entity-action dispatch under the guard.
|
|
79
|
+
*
|
|
80
|
+
* @param key from {@link BuildEntityActionDispatchKey}
|
|
81
|
+
* @param run the dispatch itself; invoked at most once per call, possibly later than the call
|
|
82
|
+
* @returns what the guard decided. `'Ran'` resolves only after the run and any coalesced
|
|
83
|
+
* reruns have finished, so a caller that awaits it awaits the whole settled chain.
|
|
84
|
+
*/
|
|
85
|
+
async Dispatch(key, run) {
|
|
86
|
+
if (this.IsSelfTriggered(key)) {
|
|
87
|
+
// The action wrote back to the record that triggered it. Dropping the dispatch is the
|
|
88
|
+
// only outcome that terminates; queuing it would just move the loop.
|
|
89
|
+
return 'Suppressed';
|
|
90
|
+
}
|
|
91
|
+
const existing = this.inFlight.get(key);
|
|
92
|
+
if (existing) {
|
|
93
|
+
// Latest wins: whatever was pending is replaced, because the newer dispatch reflects a
|
|
94
|
+
// newer state of the record and running both would only re-read the same final row.
|
|
95
|
+
existing.RerunPending = true;
|
|
96
|
+
existing.LatestRun = run;
|
|
97
|
+
return 'Coalesced';
|
|
98
|
+
}
|
|
99
|
+
const entry = { RerunPending: false, LatestRun: null, RerunCount: 0 };
|
|
100
|
+
this.inFlight.set(key, entry);
|
|
101
|
+
try {
|
|
102
|
+
await this.runWithOrigin(key, run);
|
|
103
|
+
await this.drain(key, entry);
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
// Released only here — arrivals during the drain coalesce onto this same slot, which is
|
|
107
|
+
// what keeps a burst collapsed instead of alternating run/queue/run.
|
|
108
|
+
this.inFlight.delete(key);
|
|
109
|
+
}
|
|
110
|
+
return 'Ran';
|
|
111
|
+
}
|
|
112
|
+
/** Run `fn` with `key` pushed onto the ambient origin stack. */
|
|
113
|
+
runWithOrigin(key, fn) {
|
|
114
|
+
const parent = this.originStack.getStore();
|
|
115
|
+
const scope = new Set(parent ?? []);
|
|
116
|
+
scope.add(key);
|
|
117
|
+
return this.originStack.run(scope, fn);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Run whatever queued up while the slot was busy, until nothing is left.
|
|
121
|
+
*
|
|
122
|
+
* Deliberately uncapped: each iteration means real dispatches arrived while the previous run was
|
|
123
|
+
* executing, so stopping early would silently drop work. It terminates because arrivals stop —
|
|
124
|
+
* and the case that would *not* terminate, an action re-triggering itself, never reaches here
|
|
125
|
+
* (it is suppressed above). The threshold log exists so a chain that does run away is visible.
|
|
126
|
+
*/
|
|
127
|
+
async drain(key, entry) {
|
|
128
|
+
while (entry.RerunPending) {
|
|
129
|
+
entry.RerunPending = false;
|
|
130
|
+
const next = entry.LatestRun;
|
|
131
|
+
entry.LatestRun = null;
|
|
132
|
+
if (!next) {
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
entry.RerunCount++;
|
|
136
|
+
if (entry.RerunCount === RERUN_WARN_THRESHOLD) {
|
|
137
|
+
LogStatus(`[EntityActionDispatchGuard] ${RERUN_WARN_THRESHOLD} coalesced reruns for ${key} — ` +
|
|
138
|
+
`the record is being saved faster than its after-save action completes.`);
|
|
139
|
+
}
|
|
140
|
+
await this.runWithOrigin(key, next);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/** Drops all in-flight bookkeeping. For tests — never call this from application code. */
|
|
144
|
+
ResetForTesting() {
|
|
145
|
+
this.inFlight.clear();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=EntityActionDispatchGuard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EntityActionDispatchGuard.js","sourceRoot":"","sources":["../../src/entity-actions/EntityActionDispatchGuard.ts"],"names":[],"mappings":"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"}
|
|
@@ -36,10 +36,13 @@ export declare class EntityActionEngineServer extends BaseSingleton<EntityAction
|
|
|
36
36
|
GetActionsByEntityID(entityID: string): MJEntityActionEntityExtended[];
|
|
37
37
|
GetActionsByEntityNameAndInvocationType(entityName: string, invocationType: string, status?: 'Active' | 'Pending' | 'Disabled'): MJEntityActionEntityExtended[];
|
|
38
38
|
/**
|
|
39
|
-
* Method will invoke an action given the provided parameters.
|
|
39
|
+
* Method will invoke an action given the provided parameters.
|
|
40
40
|
* @param params Parameters for the action invocation
|
|
41
|
+
* @returns the action's result, or **null when the action did not run** because the binding is
|
|
42
|
+
* scoped away from this record. Callers must distinguish "did not run" from "ran and
|
|
43
|
+
* failed" — they are not the same answer.
|
|
41
44
|
* @returns
|
|
42
45
|
*/
|
|
43
|
-
RunEntityAction(params: EntityActionInvocationParams): Promise<EntityActionResult>;
|
|
46
|
+
RunEntityAction(params: EntityActionInvocationParams): Promise<EntityActionResult | null>;
|
|
44
47
|
}
|
|
45
48
|
//# sourceMappingURL=EntityActionEngine.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityActionEngine.d.ts","sourceRoot":"","sources":["../../src/entity-actions/EntityActionEngine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAY,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAAE,0BAA0B,EAAE,8BAA8B,EAAE,kCAAkC,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAE1K,OAAO,EAA0B,4BAA4B,EAAE,kBAAkB,EAAE,4BAA4B,EAAE,MAAM,8BAA8B,CAAC;AAEtJ;;GAEG;AACH,qBAAa,wBAAyB,SAAQ,aAAa,CAAC,wBAAwB,CAAC;IACjF,WAAkB,QAAQ,IAAI,wBAAwB,CAErD;IAED;;;;;;;OAOG;IACH,OAAO,KAAK,IAAI,GAEf;IAED;;;OAGG;IACH,OAAO,CAAC,YAAY,CAAC,CAAW;IAEhC,iGAAiG;IACpF,MAAM,CAAC,YAAY,GAAE,OAAe,EAAE,WAAW,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAOvH,wEAAwE;IACxE,IAAW,MAAM,IAAI,OAAO,CAA6B;IAEzD,IAAW,WAAW,IAAI,QAAQ,CAAuD;IACzF,IAAW,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAgC;IAGtE,IAAW,eAAe,IAAI,kCAAkC,EAAE,CAAsC;IACxG,IAAW,OAAO,IAAI,0BAA0B,EAAE,CAA8B;IAChF,IAAW,WAAW,IAAI,8BAA8B,EAAE,CAAkC;IAC5F,IAAW,aAAa,IAAI,4BAA4B,EAAE,CAAoC;IAC9F,IAAW,MAAM,IAAI,yBAAyB,EAAE,CAA6B;IAGtE,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,4BAA4B,EAAE;IAGtH,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,4BAA4B,EAAE;IAGtE,uCAAuC,CAAC,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,4BAA4B,EAAE;IAKtK
|
|
1
|
+
{"version":3,"file":"EntityActionEngine.d.ts","sourceRoot":"","sources":["../../src/entity-actions/EntityActionEngine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAY,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAAE,0BAA0B,EAAE,8BAA8B,EAAE,kCAAkC,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAE1K,OAAO,EAA0B,4BAA4B,EAAE,kBAAkB,EAAE,4BAA4B,EAAE,MAAM,8BAA8B,CAAC;AAEtJ;;GAEG;AACH,qBAAa,wBAAyB,SAAQ,aAAa,CAAC,wBAAwB,CAAC;IACjF,WAAkB,QAAQ,IAAI,wBAAwB,CAErD;IAED;;;;;;;OAOG;IACH,OAAO,KAAK,IAAI,GAEf;IAED;;;OAGG;IACH,OAAO,CAAC,YAAY,CAAC,CAAW;IAEhC,iGAAiG;IACpF,MAAM,CAAC,YAAY,GAAE,OAAe,EAAE,WAAW,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAOvH,wEAAwE;IACxE,IAAW,MAAM,IAAI,OAAO,CAA6B;IAEzD,IAAW,WAAW,IAAI,QAAQ,CAAuD;IACzF,IAAW,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAgC;IAGtE,IAAW,eAAe,IAAI,kCAAkC,EAAE,CAAsC;IACxG,IAAW,OAAO,IAAI,0BAA0B,EAAE,CAA8B;IAChF,IAAW,WAAW,IAAI,8BAA8B,EAAE,CAAkC;IAC5F,IAAW,aAAa,IAAI,4BAA4B,EAAE,CAAoC;IAC9F,IAAW,MAAM,IAAI,yBAAyB,EAAE,CAA6B;IAGtE,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,4BAA4B,EAAE;IAGtH,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,4BAA4B,EAAE;IAGtE,uCAAuC,CAAC,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,4BAA4B,EAAE;IAKtK;;;;;;;OAOG;IACU,eAAe,CAAC,MAAM,EAAE,4BAA4B,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;CAqBzG"}
|
|
@@ -47,8 +47,11 @@ export class EntityActionEngineServer extends BaseSingleton {
|
|
|
47
47
|
return this.Base.GetActionsByEntityNameAndInvocationType(entityName, invocationType, status);
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
|
-
* Method will invoke an action given the provided parameters.
|
|
50
|
+
* Method will invoke an action given the provided parameters.
|
|
51
51
|
* @param params Parameters for the action invocation
|
|
52
|
+
* @returns the action's result, or **null when the action did not run** because the binding is
|
|
53
|
+
* scoped away from this record. Callers must distinguish "did not run" from "ran and
|
|
54
|
+
* failed" — they are not the same answer.
|
|
52
55
|
* @returns
|
|
53
56
|
*/
|
|
54
57
|
async RunEntityAction(params) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityActionEngine.js","sourceRoot":"","sources":["../../src/entity-actions/EntityActionEngine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAGjE,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,sBAAsB,EAAkF,MAAM,8BAA8B,CAAC;AAEtJ;;GAEG;AACH,MAAM,OAAO,wBAAyB,SAAQ,aAAuC;IAC1E,MAAM,KAAK,QAAQ;QACtB,OAAO,KAAK,CAAC,WAAW,EAA4B,CAAC;IACzD,CAAC;IAED;;;;;;;OAOG;IACH,IAAY,IAAI;QACZ,OAAO,sBAAsB,CAAC,QAAQ,CAAC;IAC3C,CAAC;IAQD,iGAAiG;IAC1F,KAAK,CAAC,MAAM,CAAC,eAAwB,KAAK,EAAE,WAAsB,EAAE,QAA4B;QACnG,IAAI,WAAW,EAAE,CAAC;YACd,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC;QACpC,CAAC;QACD,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC;IAChE,CAAC;IAED,wEAAwE;IACxE,IAAW,MAAM,KAAc,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAEzD,IAAW,WAAW,KAAe,OAAO,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;IACzF,IAAW,WAAW,CAAC,KAAe,IAAI,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC,CAAC,CAAC;IAEtE,6FAA6F;IAC7F,IAAW,eAAe,KAA2C,OAAO,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC;IACxG,IAAW,OAAO,KAAmC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IAChF,IAAW,WAAW,KAAuC,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;IAC5F,IAAW,aAAa,KAAqC,OAAO,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC;IAC9F,IAAW,MAAM,KAAkC,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAE7E,wBAAwB;IACjB,sBAAsB,CAAC,UAAkB,EAAE,MAA0C;QACxF,OAAO,IAAI,CAAC,IAAI,CAAC,sBAAsB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAChE,CAAC;IACM,oBAAoB,CAAC,QAAgB;QACxC,OAAO,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACpD,CAAC;IACM,uCAAuC,CAAC,UAAkB,EAAE,cAAsB,EAAE,MAA0C;QACjI,OAAO,IAAI,CAAC,IAAI,CAAC,uCAAuC,CAAC,UAAU,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC;IACjG,CAAC;IAGD
|
|
1
|
+
{"version":3,"file":"EntityActionEngine.js","sourceRoot":"","sources":["../../src/entity-actions/EntityActionEngine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAGjE,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,sBAAsB,EAAkF,MAAM,8BAA8B,CAAC;AAEtJ;;GAEG;AACH,MAAM,OAAO,wBAAyB,SAAQ,aAAuC;IAC1E,MAAM,KAAK,QAAQ;QACtB,OAAO,KAAK,CAAC,WAAW,EAA4B,CAAC;IACzD,CAAC;IAED;;;;;;;OAOG;IACH,IAAY,IAAI;QACZ,OAAO,sBAAsB,CAAC,QAAQ,CAAC;IAC3C,CAAC;IAQD,iGAAiG;IAC1F,KAAK,CAAC,MAAM,CAAC,eAAwB,KAAK,EAAE,WAAsB,EAAE,QAA4B;QACnG,IAAI,WAAW,EAAE,CAAC;YACd,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC;QACpC,CAAC;QACD,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC;IAChE,CAAC;IAED,wEAAwE;IACxE,IAAW,MAAM,KAAc,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAEzD,IAAW,WAAW,KAAe,OAAO,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;IACzF,IAAW,WAAW,CAAC,KAAe,IAAI,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC,CAAC,CAAC;IAEtE,6FAA6F;IAC7F,IAAW,eAAe,KAA2C,OAAO,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC;IACxG,IAAW,OAAO,KAAmC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IAChF,IAAW,WAAW,KAAuC,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;IAC5F,IAAW,aAAa,KAAqC,OAAO,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC;IAC9F,IAAW,MAAM,KAAkC,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAE7E,wBAAwB;IACjB,sBAAsB,CAAC,UAAkB,EAAE,MAA0C;QACxF,OAAO,IAAI,CAAC,IAAI,CAAC,sBAAsB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAChE,CAAC;IACM,oBAAoB,CAAC,QAAgB;QACxC,OAAO,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACpD,CAAC;IACM,uCAAuC,CAAC,UAAkB,EAAE,cAAsB,EAAE,MAA0C;QACjI,OAAO,IAAI,CAAC,IAAI,CAAC,uCAAuC,CAAC,UAAU,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC;IACjG,CAAC;IAGD;;;;;;;OAOG;IACI,KAAK,CAAC,eAAe,CAAC,MAAoC;QAC7D;;;;WAIG;QACH,IAAI,CAAC,MAAM,CAAC,YAAY;YACpB,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;QAE/D,wDAAwD;QACxD,IAAI,CAAC,MAAM,CAAC,cAAc;YACtB,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;QAExD,gHAAgH;QAChH,MAAM,kBAAkB,GAAG,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,cAAc,CAA6B,0BAA0B,EAAE,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;QAC7J,IAAI,CAAC,kBAAkB;YACnB,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;QAElE,8CAA8C;QAC9C,OAAO,kBAAkB,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IACnD,CAAC;CACJ"}
|
|
@@ -1,11 +1,20 @@
|
|
|
1
|
-
import { MJActionParamEntity, MJEntityActionParamEntity } from "@memberjunction/core-entities";
|
|
1
|
+
import { MJActionFilterEntity, MJActionParamEntity, MJEntityActionParamEntity } from "@memberjunction/core-entities";
|
|
2
2
|
import { BaseEntity } from "@memberjunction/core";
|
|
3
|
-
import { ActionInvocationProvenance, ActionParam, ActionResult, EntityActionInvocationParams, EntityActionResult } from "@memberjunction/actions-base";
|
|
3
|
+
import { ActionInvocationProvenance, ActionParam, ActionResult, ActionResultSimple, EntityActionInvocationParams, EntityActionResult, MJActionEntityExtended, RunActionParams } from "@memberjunction/actions-base";
|
|
4
4
|
/**
|
|
5
5
|
* Base class for invocation of any entity action invocation type
|
|
6
6
|
*/
|
|
7
7
|
export declare abstract class EntityActionInvocationBase {
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Runs the action for this invocation type.
|
|
10
|
+
*
|
|
11
|
+
* **Returns null when the action did not run at all** — the binding is scoped
|
|
12
|
+
* (`ScopeEntityID`/`ScopeRecordID`) and this record falls outside it, or a filter refused it.
|
|
13
|
+
* That is an ordinary outcome, not a failure: there is simply no result to report. The type
|
|
14
|
+
* says so because callers were dereferencing it — `HandleEntityActions` guards correctly, the
|
|
15
|
+
* GraphQL resolver did not, and an out-of-scope binding surfaced to clients as a server error.
|
|
16
|
+
*/
|
|
17
|
+
abstract InvokeAction(params: EntityActionInvocationParams): Promise<EntityActionResult | null>;
|
|
9
18
|
/**
|
|
10
19
|
* Case insensitive helper method to find a param by valueType
|
|
11
20
|
* @param allParams
|
|
@@ -46,7 +55,37 @@ export declare abstract class EntityActionInvocationBase {
|
|
|
46
55
|
*/
|
|
47
56
|
export declare class EntityActionInvocationSingleRecord extends EntityActionInvocationBase {
|
|
48
57
|
ValidateParams(params: EntityActionInvocationParams): Promise<boolean>;
|
|
49
|
-
|
|
58
|
+
/**
|
|
59
|
+
* The deferral that hands this run to the durable substrate, or `undefined` to execute normally.
|
|
60
|
+
*
|
|
61
|
+
* Returns undefined for every case that must stay inline: a binding that did not opt in, a
|
|
62
|
+
* lifecycle event that participates in the save, or a host with no submitter registered. The
|
|
63
|
+
* last is a fallback rather than a refusal — `RunMode='Durable'` asks for the work to be harder
|
|
64
|
+
* to lose, so declining to run it where the durable path is unavailable would make the opt-in
|
|
65
|
+
* less reliable than leaving it off.
|
|
66
|
+
*/
|
|
67
|
+
protected BuildDurableDeferral(params: EntityActionInvocationParams, action: MJActionEntityExtended): ((runParams: RunActionParams) => Promise<ActionResultSimple | null>) | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* The filter rows that gate this binding, in the order the binding declares.
|
|
70
|
+
*
|
|
71
|
+
* Two things this does beyond the obvious lookup:
|
|
72
|
+
*
|
|
73
|
+
* 1. **`Disabled` bindings are skipped.** Filters fail closed by design, so a disabled binding
|
|
74
|
+
* that still gated would not merely be inert — it would *prevent* the action, and the only
|
|
75
|
+
* visible symptom is a trigger that silently stopped firing. `Pending` still gates: it is the
|
|
76
|
+
* column default, so treating it as inert would open every gate that was never explicitly
|
|
77
|
+
* activated.
|
|
78
|
+
* 2. **An unresolvable filter is reported separately, and prevents the run.** A binding pointing
|
|
79
|
+
* at an `ActionFilter` the engine cannot see is a misconfiguration; running unfiltered would be
|
|
80
|
+
* the worst possible reading of it, since the whole point of the row is to narrow when this
|
|
81
|
+
* fires. Previously the undefined entry reached the evaluator and threw there — fail-closed by
|
|
82
|
+
* accident, with no usable reason in the log.
|
|
83
|
+
*/
|
|
84
|
+
protected ResolveFilters(params: EntityActionInvocationParams): {
|
|
85
|
+
Filters: MJActionFilterEntity[];
|
|
86
|
+
Unresolved: string[];
|
|
87
|
+
};
|
|
88
|
+
InvokeAction(params: EntityActionInvocationParams): Promise<EntityActionResult | null>;
|
|
50
89
|
}
|
|
51
90
|
/**
|
|
52
91
|
* Base class for invocation of any entity action invocation type that is multiple-record oriented. Handles
|
|
@@ -56,7 +95,7 @@ export declare class EntityActionInvocationSingleRecord extends EntityActionInvo
|
|
|
56
95
|
*/
|
|
57
96
|
export declare class EntityActionInvocationMultipleRecords extends EntityActionInvocationBase {
|
|
58
97
|
ValidateParams(params: EntityActionInvocationParams): Promise<boolean>;
|
|
59
|
-
InvokeAction(params: EntityActionInvocationParams): Promise<EntityActionResult>;
|
|
98
|
+
InvokeAction(params: EntityActionInvocationParams): Promise<EntityActionResult | null>;
|
|
60
99
|
/**
|
|
61
100
|
* Resolves the record set for a View or List invocation into loaded entity objects, which the
|
|
62
101
|
* per-record loop in {@link InvokeAction} then processes one at a time.
|
|
@@ -70,9 +109,23 @@ export declare class EntityActionInvocationMultipleRecords extends EntityActionI
|
|
|
70
109
|
protected isNumericFieldType(type: string): boolean;
|
|
71
110
|
}
|
|
72
111
|
/**
|
|
73
|
-
*
|
|
112
|
+
* Handles the `Validate` invocation type.
|
|
113
|
+
*
|
|
114
|
+
* **Deliberately has no `InvokeAction` of its own.** It used to override the single-record
|
|
115
|
+
* implementation with a near-copy that had drifted into a strict subset: same parameter mapping,
|
|
116
|
+
* same filters, but missing two things the parent does.
|
|
117
|
+
*
|
|
118
|
+
* 1. **Scope resolution.** The override never called `IsEntityActionInScope`, so a binding narrowed
|
|
119
|
+
* to one record via `ScopeEntityID`/`ScopeRecordID` ran `Validate` against *every* record of the
|
|
120
|
+
* entity — the same class of bug as a workflow trigger that claims to be scoped and is not.
|
|
121
|
+
* 2. **Provenance.** `RunAction` was called without it, so logging and redaction could not see which
|
|
122
|
+
* binding produced the run — meaning a whole-record `Validate` parameter was logged raw, ignoring
|
|
123
|
+
* the binding's `LogValue` rows and its `LoggingMode`.
|
|
124
|
+
*
|
|
125
|
+
* Inheriting is what keeps those two facts true for `Validate` forever, rather than until the next
|
|
126
|
+
* time the two copies drift. The class remains because `@RegisterClass` needs a distinct type to
|
|
127
|
+
* resolve the `Validate` key.
|
|
74
128
|
*/
|
|
75
129
|
export declare class EntityActionInvocationValidate extends EntityActionInvocationSingleRecord {
|
|
76
|
-
InvokeAction(params: EntityActionInvocationParams): Promise<EntityActionResult>;
|
|
77
130
|
}
|
|
78
131
|
//# sourceMappingURL=EntityActionInvocationTypes.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityActionInvocationTypes.d.ts","sourceRoot":"","sources":["../../src/entity-actions/EntityActionInvocationTypes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"EntityActionInvocationTypes.d.ts","sourceRoot":"","sources":["../../src/entity-actions/EntityActionInvocationTypes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AACrH,OAAO,EAAE,UAAU,EAA+B,MAAM,sBAAsB,CAAC;AAC/E,OAAO,EACH,0BAA0B,EAC1B,WAAW,EACX,YAAY,EACZ,kBAAkB,EAElB,4BAA4B,EAC5B,kBAAkB,EAElB,sBAAsB,EAGtB,eAAe,EAClB,MAAM,8BAA8B,CAAC;AAYtC;;GAEG;AACH,8BAAsB,0BAA0B;IAC5C;;;;;;;;OAQG;aACa,YAAY,CAAC,MAAM,EAAE,4BAA4B,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAEtG;;;;OAIG;IACI,eAAe,CAAC,SAAS,EAAE,mBAAmB,EAAE,EAAE,SAAS,EAAE,QAAQ,GAAG,eAAe,GAAG,sBAAsB,GAAG,OAAO,GAAG,mBAAmB;IAIhJ,mCAAmC,CAAC,MAAM,EAAE,YAAY,GAAG,kBAAkB;IAUpF;;;;;;;OAOG;IACI,eAAe,CAAC,MAAM,EAAE,4BAA4B,EAAE,YAAY,CAAC,EAAE,UAAU,GAAG,0BAA0B;IAcnH;;;;;;;OAOG;IACU,SAAS,CAAC,MAAM,EAAE,mBAAmB,EAAE,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,EAAE,YAAY,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IA8CxJ,OAAO,CAAC,YAAY,CAAuF;IAE3G;;;;;;OAMG;IACU,cAAc,CAAC,cAAc,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC;CAyBlH;AAED;;GAEG;AACH,qBAQa,kCAAmC,SAAQ,0BAA0B;IACjE,cAAc,CAAC,MAAM,EAAE,4BAA4B,GAAG,OAAO,CAAC,OAAO,CAAC;IAQnF;;;;;;;;OAQG;IACH,SAAS,CAAC,oBAAoB,CAC1B,MAAM,EAAE,4BAA4B,EACpC,MAAM,EAAE,sBAAsB,GAC/B,CAAC,CAAC,SAAS,EAAE,eAAe,KAAK,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC,GAAG,SAAS;IAkDnF;;;;;;;;;;;;;;;OAeG;IACH,SAAS,CAAC,cAAc,CAAC,MAAM,EAAE,4BAA4B,GAAG;QAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC;QAAC,UAAU,EAAE,MAAM,EAAE,CAAA;KAAE;IAkB5G,YAAY,CAAC,MAAM,EAAE,4BAA4B,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;CAkDtG;AAED;;;;;GAKG;AACH,qBAEa,qCAAsC,SAAQ,0BAA0B;IACpE,cAAc,CAAC,MAAM,EAAE,4BAA4B,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBtE,YAAY,CAAC,MAAM,EAAE,4BAA4B,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAyCnG;;;OAGG;cACa,aAAa,CAAC,MAAM,EAAE,4BAA4B,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAW1F,0DAA0D;cAC1C,kBAAkB,CAAC,MAAM,EAAE,4BAA4B,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAY/F,mFAAmF;cACnE,kBAAkB,CAAC,MAAM,EAAE,4BAA4B,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAwC/F,4FAA4F;IAC5F,SAAS,CAAC,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;CAItD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,qBACa,8BAA+B,SAAQ,kCAAkC;CACrF"}
|