joist-core 2.4.0-next.2 → 2.4.0-next.21
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/build/EntityManager.cjs +25 -11
- package/build/EntityManager.cjs.map +1 -1
- package/build/EntityManager.d.cts +11 -4
- package/build/EntityManager.d.cts.map +1 -1
- package/build/EntityManager.d.mts +11 -4
- package/build/EntityManager.d.mts.map +1 -1
- package/build/EntityManager.js +25 -13
- package/build/EntityManager.js.map +1 -1
- package/build/EntityMetadata.cjs +0 -1
- package/build/EntityMetadata.cjs.map +1 -1
- package/build/EntityMetadata.d.cts +8 -4
- package/build/EntityMetadata.d.cts.map +1 -1
- package/build/EntityMetadata.d.mts +8 -4
- package/build/EntityMetadata.d.mts.map +1 -1
- package/build/EntityMetadata.js +0 -1
- package/build/EntityMetadata.js.map +1 -1
- package/build/IndexManager.cjs +23 -5
- package/build/IndexManager.cjs.map +1 -1
- package/build/IndexManager.d.cts.map +1 -1
- package/build/IndexManager.d.mts.map +1 -1
- package/build/IndexManager.js +23 -5
- package/build/IndexManager.js.map +1 -1
- package/build/batchloaders/populateBatchLoader.cjs +2 -2
- package/build/batchloaders/populateBatchLoader.js +2 -2
- package/build/batchloaders/recursiveChildrenBatchLoader.cjs +1 -1
- package/build/batchloaders/recursiveChildrenBatchLoader.js +1 -1
- package/build/batchloaders/recursiveM2mBatchLoader.cjs +1 -1
- package/build/batchloaders/recursiveM2mBatchLoader.js +1 -1
- package/build/batchloaders/recursiveParentsBatchLoader.cjs +1 -1
- package/build/batchloaders/recursiveParentsBatchLoader.js +1 -1
- package/build/config.cjs +74 -1
- package/build/config.cjs.map +1 -1
- package/build/config.d.cts +83 -2
- package/build/config.d.cts.map +1 -1
- package/build/config.d.mts +83 -2
- package/build/config.d.mts.map +1 -1
- package/build/config.js +74 -1
- package/build/config.js.map +1 -1
- package/build/configure.cjs +6 -2
- package/build/configure.cjs.map +1 -1
- package/build/configure.d.cts.map +1 -1
- package/build/configure.d.mts.map +1 -1
- package/build/configure.js +6 -2
- package/build/configure.js.map +1 -1
- package/build/dataloaders/findDataLoader.cjs +1 -1
- package/build/dataloaders/findDataLoader.js +1 -1
- package/build/dataloaders/findOrCreateDataLoader.cjs +6 -2
- package/build/dataloaders/findOrCreateDataLoader.cjs.map +1 -1
- package/build/dataloaders/findOrCreateDataLoader.d.cts.map +1 -1
- package/build/dataloaders/findOrCreateDataLoader.d.mts.map +1 -1
- package/build/dataloaders/findOrCreateDataLoader.js +7 -3
- package/build/dataloaders/findOrCreateDataLoader.js.map +1 -1
- package/build/dataloaders/findPaginatedDataLoader.cjs +1 -1
- package/build/dataloaders/findPaginatedDataLoader.js +1 -1
- package/build/defaults.cjs +2 -2
- package/build/defaults.js +2 -2
- package/build/fields.cjs +1 -0
- package/build/fields.cjs.map +1 -1
- package/build/fields.d.cts.map +1 -1
- package/build/fields.d.mts.map +1 -1
- package/build/fields.js +1 -0
- package/build/fields.js.map +1 -1
- package/build/index.cjs +13 -8
- package/build/index.cjs.map +1 -1
- package/build/index.d.cts +4 -3
- package/build/index.d.cts.map +1 -1
- package/build/index.d.mts +4 -3
- package/build/index.d.mts.map +1 -1
- package/build/index.js +13 -10
- package/build/index.js.map +1 -1
- package/build/json.cjs +3 -3
- package/build/json.js +3 -3
- package/build/loading/loadHints.cjs +1 -1
- package/build/loading/loadHints.js +1 -1
- package/build/loading/withLoaded.cjs +2 -2
- package/build/loading/withLoaded.js +2 -2
- package/build/newTestInstance.cjs +1 -0
- package/build/newTestInstance.cjs.map +1 -1
- package/build/newTestInstance.d.cts.map +1 -1
- package/build/newTestInstance.d.mts.map +1 -1
- package/build/newTestInstance.js +1 -0
- package/build/newTestInstance.js.map +1 -1
- package/build/preloading/partitionHint.cjs +1 -1
- package/build/preloading/partitionHint.js +1 -1
- package/build/queries/sql/execute.cjs +68 -89
- package/build/queries/sql/execute.cjs.map +1 -1
- package/build/queries/sql/execute.d.cts +35 -18
- package/build/queries/sql/execute.d.cts.map +1 -1
- package/build/queries/sql/execute.d.mts +35 -18
- package/build/queries/sql/execute.d.mts.map +1 -1
- package/build/queries/sql/execute.js +70 -91
- package/build/queries/sql/execute.js.map +1 -1
- package/build/queries/sql/query.cjs +77 -30
- package/build/queries/sql/query.cjs.map +1 -1
- package/build/queries/sql/query.d.cts +64 -21
- package/build/queries/sql/query.d.cts.map +1 -1
- package/build/queries/sql/query.d.mts +64 -21
- package/build/queries/sql/query.d.mts.map +1 -1
- package/build/queries/sql/query.js +75 -30
- package/build/queries/sql/query.js.map +1 -1
- package/build/reactivity/ReactionsManager.cjs +168 -2
- package/build/reactivity/ReactionsManager.cjs.map +1 -1
- package/build/reactivity/ReactionsManager.d.cts +54 -1
- package/build/reactivity/ReactionsManager.d.cts.map +1 -1
- package/build/reactivity/ReactionsManager.d.mts +54 -1
- package/build/reactivity/ReactionsManager.d.mts.map +1 -1
- package/build/reactivity/ReactionsManager.js +169 -3
- package/build/reactivity/ReactionsManager.js.map +1 -1
- package/build/reactivity/reactiveHints.cjs +3 -3
- package/build/reactivity/reactiveHints.cjs.map +1 -1
- package/build/reactivity/reactiveHints.d.cts.map +1 -1
- package/build/reactivity/reactiveHints.d.mts.map +1 -1
- package/build/reactivity/reactiveHints.js +3 -3
- package/build/reactivity/reactiveHints.js.map +1 -1
- package/build/relations/AsyncReactiveField.cjs +3 -3
- package/build/relations/AsyncReactiveField.js +3 -3
- package/build/relations/LazyField.cjs +14 -0
- package/build/relations/LazyField.cjs.map +1 -1
- package/build/relations/LazyField.d.cts +6 -0
- package/build/relations/LazyField.d.cts.map +1 -1
- package/build/relations/LazyField.d.mts +6 -0
- package/build/relations/LazyField.d.mts.map +1 -1
- package/build/relations/LazyField.js +14 -0
- package/build/relations/LazyField.js.map +1 -1
- package/build/relations/ReactiveField.cjs +2 -2
- package/build/relations/ReactiveField.js +2 -2
- package/build/relations/ReactiveManyToMany.cjs +1 -1
- package/build/relations/ReactiveManyToMany.js +1 -1
- package/build/relations/ReactiveReference.cjs +1 -1
- package/build/relations/ReactiveReference.js +1 -1
- package/build/relations/RecursiveCollection.cjs +3 -3
- package/build/relations/RecursiveCollection.js +3 -3
- package/build/relations/hasProperty.cjs +2 -2
- package/build/relations/hasProperty.js +2 -2
- package/build/relations/index.cjs +5 -5
- package/build/relations/index.js +5 -5
- package/build/resurrection.cjs +11 -1
- package/build/resurrection.cjs.map +1 -1
- package/build/resurrection.d.cts +4 -1
- package/build/resurrection.d.cts.map +1 -1
- package/build/resurrection.d.mts +4 -1
- package/build/resurrection.d.mts.map +1 -1
- package/build/resurrection.js +11 -2
- package/build/resurrection.js.map +1 -1
- package/build/transitions.cjs +89 -0
- package/build/transitions.cjs.map +1 -0
- package/build/transitions.d.cts +110 -0
- package/build/transitions.d.cts.map +1 -0
- package/build/transitions.d.mts +110 -0
- package/build/transitions.d.mts.map +1 -0
- package/build/transitions.js +84 -0
- package/build/transitions.js.map +1 -0
- package/package.json +2 -2
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { failIfAnyRejected } from "../utils.js";
|
|
1
2
|
import { followReverseHint } from "./reactiveHints.js";
|
|
3
|
+
import { ValidationErrors } from "../rules.js";
|
|
4
|
+
import { created, describeValue, matchesTransition } from "../transitions.js";
|
|
2
5
|
import { getMetadata } from "../EntityMetadata.js";
|
|
3
6
|
import { runInTrustedContext } from "../trusted.js";
|
|
4
7
|
import { globalLogger, noopReactionLogger } from "../logging/ReactionLogger.js";
|
|
5
8
|
import { NoIdError } from "../index.js";
|
|
6
|
-
import { getEmInternalApi } from "../EntityManager.js";
|
|
9
|
+
import { getEmInternalApi, invokeRule } from "../EntityManager.js";
|
|
7
10
|
//#region src/reactivity/ReactionsManager.ts
|
|
8
11
|
/**
|
|
9
12
|
* Manages the reactivity of tracking which source fields have changed and finding/recalculating
|
|
@@ -11,10 +14,25 @@ import { getEmInternalApi } from "../EntityManager.js";
|
|
|
11
14
|
*
|
|
12
15
|
* Source fields are usually regular/dumb primitives, i.e. `Author.age` is a source field
|
|
13
16
|
* of `Book.isPublic`, but derived fields can themselves be source fields as well.
|
|
17
|
+
* Guarded state transitions share this loop; their commit callbacks wait until SQL has been flushed.
|
|
14
18
|
*/
|
|
15
19
|
var ReactionsManager = class {
|
|
16
20
|
/** Stores all source `Reactables`s that have been marked for later traversal. */
|
|
17
21
|
pendingReactables = /* @__PURE__ */ new Map();
|
|
22
|
+
/** Queued state changes, including changes made by callbacks, are processed in reaction passes. */
|
|
23
|
+
pendingTransitions = [];
|
|
24
|
+
/**
|
|
25
|
+
* Initial assignments are collapsed until reactions first see the entity.
|
|
26
|
+
*
|
|
27
|
+
* Each entity keeps one creation step per transition field. Each step is also in `pendingTransitions`, and
|
|
28
|
+
* later assignments update its `to` in place. An entity usually has one or two transition fields, so a
|
|
29
|
+
* short array is cheaper than a nested Map.
|
|
30
|
+
*/
|
|
31
|
+
transitionCreations = /* @__PURE__ */ new Map();
|
|
32
|
+
/** New entities that reactions have seen, so their assignments are transitions. */
|
|
33
|
+
postCreationWindowEntities = /* @__PURE__ */ new Set();
|
|
34
|
+
/** Only matching callbacks are retained until SQL and final validation finish. */
|
|
35
|
+
pendingCommitTransitions = [];
|
|
18
36
|
/** Failed actions to retry post-hooks, deduped by target field name -> entity (rare error path). */
|
|
19
37
|
actionsPendingTypeErrors = /* @__PURE__ */ new Map();
|
|
20
38
|
/**
|
|
@@ -49,6 +67,76 @@ var ReactionsManager = class {
|
|
|
49
67
|
this.em = em;
|
|
50
68
|
}
|
|
51
69
|
/**
|
|
70
|
+
* Called by `setField` before changing an enum field, to check and queue the transition.
|
|
71
|
+
*
|
|
72
|
+
* An entity has no previous state until Joist's reactions have seen it, so every assignment to a new
|
|
73
|
+
* entity until then is part of its creation. I.e. `em.create` then `entity.status = Open`, or a factory
|
|
74
|
+
* setting a default and then a `withStatus` value, or `em.findOrCreate` creating then upserting, all
|
|
75
|
+
* create the entity as `Open`. Creation isn't checked against the `transitions` table.
|
|
76
|
+
*/
|
|
77
|
+
queueTransition(entity, fieldName, from, to) {
|
|
78
|
+
const meta = getMetadata(entity);
|
|
79
|
+
const transitions = meta.transitions.get(fieldName);
|
|
80
|
+
if (!transitions) return;
|
|
81
|
+
if (entity.isNewEntity && !this.postCreationWindowEntities.has(entity)) {
|
|
82
|
+
let creations = this.transitionCreations.get(entity);
|
|
83
|
+
if (!creations) this.transitionCreations.set(entity, creations = []);
|
|
84
|
+
const existing = creations.find((pending) => pending.fieldName === fieldName);
|
|
85
|
+
if (existing) existing.step.to = to;
|
|
86
|
+
else {
|
|
87
|
+
const pending = {
|
|
88
|
+
entity,
|
|
89
|
+
fieldName,
|
|
90
|
+
step: {
|
|
91
|
+
from: created,
|
|
92
|
+
to
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
creations.push(pending);
|
|
96
|
+
this.pendingTransitions.push(pending);
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
for (const table of transitions.tables) {
|
|
100
|
+
if (table.get(from)?.includes(to)) continue;
|
|
101
|
+
const message = `Cannot change ${fieldName} from ${describeValue(meta, fieldName, from)} to ${describeValue(meta, fieldName, to)}`;
|
|
102
|
+
throw new ValidationErrors([{
|
|
103
|
+
entity,
|
|
104
|
+
message
|
|
105
|
+
}]);
|
|
106
|
+
}
|
|
107
|
+
this.pendingTransitions.push({
|
|
108
|
+
entity,
|
|
109
|
+
fieldName,
|
|
110
|
+
step: {
|
|
111
|
+
from,
|
|
112
|
+
to
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/** Forgets a field's creation transition after a factory assigns its trusted initial value. */
|
|
118
|
+
forgetCreationTransition(entity, fieldName) {
|
|
119
|
+
const creations = this.transitionCreations.get(entity);
|
|
120
|
+
const i = creations?.findIndex((pending) => pending.fieldName === fieldName) ?? -1;
|
|
121
|
+
if (i === -1) return;
|
|
122
|
+
const [pending] = creations.splice(i, 1);
|
|
123
|
+
this.pendingTransitions.splice(this.pendingTransitions.lastIndexOf(pending), 1);
|
|
124
|
+
}
|
|
125
|
+
/** Returns whether callbacks need a commit phase, even if there are no net SQL changes. */
|
|
126
|
+
hasPendingCommitTransitions() {
|
|
127
|
+
return this.pendingCommitTransitions.some((pending) => !pending.entity.isDeletedEntity);
|
|
128
|
+
}
|
|
129
|
+
/** Runs the queued callbacks after all SQL and validation succeed, without rechecking guards. */
|
|
130
|
+
async runCommitTransitions() {
|
|
131
|
+
const batch = this.pendingCommitTransitions.splice(0);
|
|
132
|
+
for (const wave of groupIntoEntityWaves(batch)) {
|
|
133
|
+
const results = await Promise.allSettled(wave.map(async (pending) => {
|
|
134
|
+
if (!pending.entity.isDeletedEntity) await pending.callback.run(pending.entity, this.em.ctx, pending.step);
|
|
135
|
+
}));
|
|
136
|
+
failIfAnyRejected(results);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
52
140
|
* Queue all downstream reactive fields that depend on `fieldName` as a source field.
|
|
53
141
|
*
|
|
54
142
|
* This method can be synchronous b/c we internally queue the `Reactable` reverse
|
|
@@ -109,6 +197,7 @@ var ReactionsManager = class {
|
|
|
109
197
|
if (this.needsRecalc(kind)) this.logger.logStartingRecalc(this.em, kind);
|
|
110
198
|
let loops = 0;
|
|
111
199
|
while (this.needsRecalc(kind)) {
|
|
200
|
+
this.endTransitionCreations();
|
|
112
201
|
if (kind === "reactables") {
|
|
113
202
|
this.#needsRecalc.populate = false;
|
|
114
203
|
this.#needsRecalc.reaction = false;
|
|
@@ -171,6 +260,7 @@ var ReactionsManager = class {
|
|
|
171
260
|
});
|
|
172
261
|
}
|
|
173
262
|
if (failures.length > 0) throw failures[0];
|
|
263
|
+
if (kind === "reactables") await runInTrustedContext(() => this.processPendingTransitions());
|
|
174
264
|
for (const action of actions) if (action.r.runOnce) {
|
|
175
265
|
let set = this.processedActions.get(action.r.name);
|
|
176
266
|
if (!set) this.processedActions.set(action.r.name, set = /* @__PURE__ */ new Set());
|
|
@@ -199,10 +289,19 @@ var ReactionsManager = class {
|
|
|
199
289
|
get hasPendingTypeErrors() {
|
|
200
290
|
return this.actionsPendingTypeErrors.size > 0;
|
|
201
291
|
}
|
|
202
|
-
/** Clears
|
|
292
|
+
/** Clears pending reaction and transition work after a successful flush, including flushes with no SQL. */
|
|
203
293
|
clear() {
|
|
204
294
|
this.pendingReactables = /* @__PURE__ */ new Map();
|
|
205
295
|
this.processedActions.clear();
|
|
296
|
+
this.pendingTransitions.length = 0;
|
|
297
|
+
this.transitionCreations.clear();
|
|
298
|
+
this.postCreationWindowEntities.clear();
|
|
299
|
+
this.pendingCommitTransitions.length = 0;
|
|
300
|
+
this.#needsRecalc = {
|
|
301
|
+
populate: false,
|
|
302
|
+
query: false,
|
|
303
|
+
reaction: false
|
|
304
|
+
};
|
|
206
305
|
}
|
|
207
306
|
setLogger(logger) {
|
|
208
307
|
this.logger = logger ?? noopReactionLogger;
|
|
@@ -224,7 +323,63 @@ var ReactionsManager = class {
|
|
|
224
323
|
byEntity.set(action.entity, action);
|
|
225
324
|
}
|
|
226
325
|
needsRecalc(kind) {
|
|
227
|
-
return kind === "reactables" ? this.#needsRecalc.populate || this.#needsRecalc.reaction : this.#needsRecalc.query;
|
|
326
|
+
return kind === "reactables" ? this.#needsRecalc.populate || this.#needsRecalc.reaction || this.pendingTransitions.length > 0 : this.#needsRecalc.query;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Ends the creation of every new entity so far, before each loop of reactions.
|
|
330
|
+
*
|
|
331
|
+
* After this, assignments to those entities are transitions, i.e. an Approval created as `Requested`
|
|
332
|
+
* that a reaction then auto-approves records `Requested -> Approved`.
|
|
333
|
+
*/
|
|
334
|
+
endTransitionCreations() {
|
|
335
|
+
for (const entity of this.transitionCreations.keys()) this.postCreationWindowEntities.add(entity);
|
|
336
|
+
this.transitionCreations.clear();
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Evaluates each pending transition's guards once, then dispatches its matching callbacks.
|
|
340
|
+
*
|
|
341
|
+
* I.e. Pending -> Signed -> Pending restores an advance's original status, but the Signed guard must still run.
|
|
342
|
+
* Callbacks can append more transitions; taking one batch leaves those for the next reaction pass.
|
|
343
|
+
* Each wave processes one step per entity, so populate requests batch across entities without
|
|
344
|
+
* allowing an entity's next transition to overtake its previous callbacks.
|
|
345
|
+
*
|
|
346
|
+
* We might eventually treat A -> B -> A as an undo and ignore both transitions before reactions
|
|
347
|
+
* have seen them. For now, each transition is meaningful, even when the final state is unchanged.
|
|
348
|
+
*/
|
|
349
|
+
async processPendingTransitions() {
|
|
350
|
+
this.endTransitionCreations();
|
|
351
|
+
if (this.pendingTransitions.length === 0) return;
|
|
352
|
+
const batch = this.pendingTransitions.splice(0);
|
|
353
|
+
for (const wave of groupIntoEntityWaves(batch)) {
|
|
354
|
+
const guardResults = await Promise.allSettled(wave.map((pending) => this.evaluateTransitionGuards(pending)));
|
|
355
|
+
const errors = failIfAnyRejected(guardResults).flat();
|
|
356
|
+
if (errors.length > 0) throw new ValidationErrors(errors);
|
|
357
|
+
const callbackResults = await Promise.allSettled(wave.map((pending) => this.runTransitionCallbacks(pending)));
|
|
358
|
+
failIfAnyRejected(callbackResults);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
/** Evaluates one transition's matching guards before any callbacks in its wave run. */
|
|
362
|
+
async evaluateTransitionGuards(pending) {
|
|
363
|
+
const { entity, fieldName, step } = pending;
|
|
364
|
+
if (entity.isDeletedEntity || step.from === created) return [];
|
|
365
|
+
const guards = getMetadata(entity).transitions.get(fieldName).guards.filter((guard) => matchesTransition(guard.matcher, step));
|
|
366
|
+
const results = await Promise.allSettled(guards.map((guard) => invokeRule(entity, () => guard.run(entity, step))));
|
|
367
|
+
return failIfAnyRejected(results).flat();
|
|
368
|
+
}
|
|
369
|
+
/** Runs one entity's callbacks in registration order after all guards in the wave have passed. */
|
|
370
|
+
async runTransitionCallbacks(pending) {
|
|
371
|
+
const { entity, fieldName, step } = pending;
|
|
372
|
+
if (entity.isDeletedEntity) return;
|
|
373
|
+
for (const callback of getMetadata(entity).transitions.get(fieldName).callbacks) {
|
|
374
|
+
if (entity.isDeletedEntity) break;
|
|
375
|
+
if (!matchesTransition(callback.matcher, step)) continue;
|
|
376
|
+
if (callback.phase === "commit") this.pendingCommitTransitions.push({
|
|
377
|
+
entity,
|
|
378
|
+
callback,
|
|
379
|
+
step
|
|
380
|
+
});
|
|
381
|
+
else await callback.run(entity, this.em.ctx, step);
|
|
382
|
+
}
|
|
228
383
|
}
|
|
229
384
|
getPending(r) {
|
|
230
385
|
let pending = this.pendingReactables.get(r);
|
|
@@ -255,6 +410,17 @@ var ReactionsManager = class {
|
|
|
255
410
|
return (getEmInternalApi(this.em).isMerging(entity) ? meta.reactablesIncludingReadOnlyByField : meta.reactablesByField).get(fieldName) ?? noReactables;
|
|
256
411
|
}
|
|
257
412
|
};
|
|
413
|
+
/** Groups queued work into parallel waves while preserving each entity's input order. */
|
|
414
|
+
function groupIntoEntityWaves(batch) {
|
|
415
|
+
const waves = [];
|
|
416
|
+
const waveByEntity = /* @__PURE__ */ new Map();
|
|
417
|
+
for (const pending of batch) {
|
|
418
|
+
const wave = waveByEntity.get(pending.entity) ?? 0;
|
|
419
|
+
(waves[wave] ??= []).push(pending);
|
|
420
|
+
waveByEntity.set(pending.entity, wave + 1);
|
|
421
|
+
}
|
|
422
|
+
return waves;
|
|
423
|
+
}
|
|
258
424
|
/** A shared frozen array for fields with no downstream reactables. */
|
|
259
425
|
const noReactables = Object.freeze([]);
|
|
260
426
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReactionsManager.js","names":["#needsRecalc","#doAction"],"sources":["../../src/reactivity/ReactionsManager.ts"],"sourcesContent":["import type { Reactable } from \"src/config.ts\";\nimport type { Entity } from \"src/Entity.ts\";\nimport { type EntityManager, getEmInternalApi } from \"src/EntityManager.ts\";\nimport { type EntityMetadata, getMetadata } from \"src/EntityMetadata.ts\";\nimport { NoIdError } from \"src/index.ts\";\nimport { type ReactionLogger, globalLogger, noopReactionLogger } from \"src/logging/ReactionLogger.ts\";\nimport { followReverseHint } from \"src/reactivity/reactiveHints.ts\";\nimport { runInTrustedContext } from \"src/trusted.ts\";\n\nexport type ReactiveAction = { r: Reactable; entity: Entity };\n/**\n * Manages the reactivity of tracking which source fields have changed and finding/recalculating\n * their downstream derived fields.\n *\n * Source fields are usually regular/dumb primitives, i.e. `Author.age` is a source field\n * of `Book.isPublic`, but derived fields can themselves be source fields as well.\n */\nexport class ReactionsManager {\n /** Stores all source `Reactables`s that have been marked for later traversal. */\n private pendingReactables: Map<Reactable, { todo: Set<Entity>; done: Set<Entity> }> = new Map();\n /** Failed actions to retry post-hooks, deduped by target field name -> entity (rare error path). */\n private actionsPendingTypeErrors: Map<string, Map<Entity, ReactiveAction>> = new Map();\n /**\n * A map of entity tagName -> fields that have been marked as dirty.\n *\n * The key of the map is \"just\" the tag name, instead of a specific entity, b/c at the time of\n * being marked dirty, we only have the upstream/source entity + fieldName that has changed, and\n * not the 1-or-more downstream/target entities that will actually recalc.\n *\n * Instead, we just track the dirty fields by type-of-entity, which is enough for `isPendingRecalc`.\n */\n private dirtyFields: Map<string, Set<string>> = new Map();\n /**\n * Tracks which entities have already run a `runOnce` reactable, keyed by the target field\n * name -> entities. We key on `r.name` (not `r` itself) b/c two `Reactable`s reverse-indexed\n * from different source paths can target the same derived field, and must dedupe together.\n * Keying the outer map by field name (of which there are few) instead of by entity (of which\n * there are many) means we allocate just one `Set` per distinct field name, rather than a\n * `Set` per entity or the old per-action `${entity.toTaggedString()}_${r.name}` key strings.\n */\n private processedActions: Map<string, Set<Entity>> = new Map();\n #needsRecalc = { populate: false, query: false, reaction: false };\n // Accessible for EntityManager.runValidation to reuse\n logger: ReactionLogger = globalLogger ?? noopReactionLogger;\n private em: EntityManager;\n /** These are NPEs that *might* have been from invalid m2o fields, so we only throw them after validation. */\n private suppressedTypeErrors: Error[] = [];\n\n constructor(em: EntityManager) {\n this.em = em;\n }\n\n /**\n * Queue all downstream reactive fields that depend on `fieldName` as a source field.\n *\n * This method can be synchronous b/c we internally queue the `Reactable` reverse\n * indexes on the given source `fieldName`, and don't crawl/walk back to the downstream\n * entities/fields until `recalcPendingReactables` is called.\n */\n queueDownstreamReactables(entity: Entity, fieldName: string): void {\n // Use the reverse index of Reactables that configureMetadata sets up\n for (const r of this.getReactablesByField(entity, fieldName)) {\n // We always queue the reactable/entity, even if we're mid-flush or even mid-recalc, to avoid:\n // - firstName is changed from a1 to a2\n // - this triggers firstName's reactable to `Author.initials` to be queued\n // - during the 1st em.flush loop, we recalc `Author.initials` and it didn't change\n // - during the 1st em.flush loop, a hook changes firstName from a2 to b2\n // - if we skip re-queuing firstName's reactables, we will miss that initials needs\n // its `.load()` called again so that it's `setField` marks `initials` as\n // dirty, otherwise it will be left out of any INSERTs/UPDATEs.\n this.getPending(r).todo.add(entity);\n this.#needsRecalc[r.kind] = true;\n this.logger.logQueued(entity, fieldName, r);\n }\n }\n\n /** Dequeues reactivity on `fieldName`, i.e. if it's no longer dirty. */\n dequeueDownstreamReactables(entity: Entity, fieldName: string): void {\n // Use the reverse index of Reactables that configureMetadata sets up\n for (const r of this.getReactablesByField(entity, fieldName)) {\n const pending = this.getPending(r);\n if (pending.done.has(entity)) {\n // Ironically, if we've already run this reactable, asking to dequeue probably means\n // we need to run it again (to recalc its value), b/c this could be a mid-flush change, i.e.:\n // - firstName = a1 from db\n // - firstName is changed to a2, triggers initials reactable\n // - firstName is changed back to a1, which is the original value, so setField\n // thinks we can dequeue the reactable\n // - but actually our reactable needs to be re-run with the restored value\n pending.todo.add(entity);\n this.#needsRecalc[r.kind] = true;\n } else if (r.fields.length === 1) {\n // We can only delete/dequeue a reaction if `fieldName` is the only or last field\n // that had triggered `r` to run. Since we don't track that currently, i.e. we'd\n // need to have a `Pending.dirtyFields`, for now just only dequeue if `r` only\n // has one field (which is us) anyway.\n pending.todo.delete(entity);\n }\n }\n }\n\n /** Queue all downstream reactive fields that depend on this entity being created or deleted. */\n queueAllDownstreamFields(entity: Entity, reason: \"created\" | \"deleted\"): void {\n for (const r of this.getReactables(entity)) {\n this.getPending(r).todo.add(entity);\n this.#needsRecalc[r.kind] = true;\n this.logger.logQueuedAll(entity, reason, r);\n }\n }\n\n /**\n * Returns whether this field might be pending recalc.\n *\n * This is technically a guess, b/c our reaction infra may not yet have crawled from an upstream\n * source field down to the `N` specific target entities that need recalced, and instead just\n * knows \"it will need to do that soon\" i.e. at the next `em.flush`.\n *\n * So, instead this is a heuristic that reports if `fieldName` has been marked dirty for _some_\n * entities of type `entity`, but we don't technically know if that's _this_ entity.\n *\n * I.e. this might return false positives, but should never return false negatives.\n */\n isMaybePendingRecalc(entity: Entity, fieldName: string): boolean {\n return this.getDirtyFields(getMetadata(entity)).has(fieldName);\n }\n\n hasPendingReactiveQueries(): boolean {\n return this.#needsRecalc.query;\n }\n\n /**\n * Given source `Reactable` \"reverse indexes\" that have been queued as dirty by calls\n * to setters, `em.register`, or `em.delete`, asynchronously walks/crawls to the downstream\n * derived fields and calls `.load()` on them to calc.\n *\n * We also do this in a loop to handle reactive fields depending on other reactive fields.\n */\n async recalcPendingReactables(kind: \"reactables\" | \"reactiveQueries\") {\n if (this.needsRecalc(kind)) this.logger.logStartingRecalc(this.em, kind);\n\n let loops = 0;\n while (this.needsRecalc(kind)) {\n // ...we probably should only loop for `kind=reactables` Reactables, and `kind=reactiveQueries`\n // AsyncReactiveFields should probably only have a single loop, after which we return and\n // let `em.flush` push the latest values to the db, so our 2nd-order AsyncReactiveFields\n // will see the latest value.\n if (kind === \"reactables\") {\n this.#needsRecalc.populate = false;\n this.#needsRecalc.reaction = false;\n } else {\n this.#needsRecalc.query = false;\n }\n\n // Dedupe actions by (target field name, entity); see the `processedActions` docs for why\n // we key on `r.name` instead of `r` or the old per-action key strings\n const seen: Map<string, Set<Entity>> = new Map();\n const actions: ReactiveAction[] = [];\n\n const walks = await Promise.all(\n [...this.pendingReactables.entries()].map(async ([r, pending]) => {\n // Skip reactive queries until post-flush\n if (kind === \"reactables\" && r.kind === \"query\") return undefined;\n if (kind === \"reactiveQueries\" && (r.kind === \"populate\" || r.kind === \"reaction\")) return undefined;\n // Copy pending and clear it\n const todo = [...pending.todo];\n if (todo.length === 0) return undefined;\n pending.todo.clear();\n for (const doing of todo) pending.done.add(doing);\n // Walk back from the source to any downstream entities\n const entities = r.path.length === 0 ? todo : await followReverseHint(r.name, todo, r.path);\n const actionableEntities = entities.filter((entity) => !entity.isDeletedEntity && entity instanceof r.cstr);\n let seenEntities = seen.get(r.name);\n if (!seenEntities) seen.set(r.name, (seenEntities = new Set()));\n const processed = r.runOnce ? this.processedActions.get(r.name) : undefined;\n for (const entity of actionableEntities) {\n // We could arrive at the same reactable from multiple paths (eg, 2 dependent fields changed), so we need to\n // dedupe based on the entity and reactable to only run each action once for any given entity per loop\n if (seenEntities.has(entity)) continue;\n // If this reactable has already run and shouldn't run again, then skip it\n if (processed && processed.has(entity)) continue;\n seenEntities.add(entity);\n actions.push({ r, entity });\n }\n return { r, todo, entities: actionableEntities };\n }),\n );\n this.logger.logWalks(walks, \"recalc\");\n this.logger.logLoadingStart(this.em, actions);\n // Use allSettled so that we can watch for derived values that want to use an entity's id\n // i.e. they can fail, but we'll queue them from later.\n const startTime = this.logger.now();\n const results = await runInTrustedContext(() => Promise.allSettled(actions.map((a) => this.#doAction(a))));\n const endTime = this.logger.now();\n this.logger.logLoadingEnd(this.em, endTime - startTime);\n\n // This loop's actions are already unique by (name, entity), so a plain array dedupes fine\n const actionsPendingAssignedIds: ReactiveAction[] = [];\n const failures: any[] = [];\n results.forEach((result, i) => {\n if (result.status === \"rejected\") {\n // Let `author.id` and `book.author.get.firstName` errors run again after flush/hooks fills them in\n if (result.reason instanceof NoIdError) {\n actionsPendingAssignedIds.push(actions[i]);\n } else if (result.reason instanceof TypeError) {\n this.addPendingTypeError(actions[i]);\n } else {\n failures.push(result.reason);\n }\n }\n });\n\n // If we have any actions that need to be re-run because they failed due to a missing id, then we assign ids and\n // re-run them.\n if (actionsPendingAssignedIds.length > 0) {\n await this.em.assignNewIds();\n this.logger.logLoadingStart(this.em, actionsPendingAssignedIds);\n const startTime = this.logger.now();\n const results = await runInTrustedContext(() =>\n Promise.allSettled(actionsPendingAssignedIds.map((a) => this.#doAction(a))),\n );\n const endTime = this.logger.now();\n this.logger.logLoadingEnd(this.em, endTime - startTime);\n results.forEach((result, i) => {\n if (result.status === \"rejected\") {\n if (result.reason instanceof TypeError) {\n this.addPendingTypeError(actionsPendingAssignedIds[i]);\n } else {\n failures.push(result.reason);\n }\n }\n });\n }\n\n if (failures.length > 0) throw failures[0];\n // Record any successful actions that should only run once so we don't run them again\n for (const action of actions) {\n if (action.r.runOnce) {\n let set = this.processedActions.get(action.r.name);\n if (!set) this.processedActions.set(action.r.name, (set = new Set()));\n set.add(action.entity);\n }\n }\n // This should generally not happen, only if two reactive fields depend on each other,\n // which in theory should probably be caught/blow up in the `configureMetadata` step,\n // but if it's not caught sooner, at least don't infinite loop.\n if (loops++ > 50) {\n throw new Error(\"recalc looped too many times, probably a circular dependency\");\n }\n }\n }\n\n async recalcPendingTypeErrors() {\n const actions: ReactiveAction[] = [];\n for (const byEntity of this.actionsPendingTypeErrors.values()) {\n for (const action of byEntity.values()) actions.push(action);\n }\n this.actionsPendingTypeErrors.clear();\n\n const startTime = this.logger.now();\n const results = await runInTrustedContext(() =>\n Promise.allSettled(actions.filter((a) => !a.entity.isDeletedEntity).map((a) => this.#doAction(a))),\n );\n const endTime = this.logger.now();\n this.logger.logLoadingEnd(this.em, endTime - startTime);\n\n const failures: any[] = [];\n results.forEach((result) => {\n if (result.status === \"rejected\") {\n if (result.reason instanceof TypeError) {\n // Defer to the validation error to catch this\n this.suppressedTypeErrors.push(result.reason);\n } else {\n failures.push(result.reason);\n }\n }\n });\n if (failures.length > 0) throw failures[0];\n }\n\n get hasPendingTypeErrors(): boolean {\n return this.actionsPendingTypeErrors.size > 0;\n }\n\n /** Clears all the pending source fields, i.e. after `em.flush` is complete. */\n clear(): void {\n this.pendingReactables = new Map();\n this.processedActions.clear();\n }\n\n setLogger(logger: ReactionLogger | undefined): void {\n this.logger = logger ?? noopReactionLogger;\n }\n\n clearSuppressedTypeErrors(): void {\n this.suppressedTypeErrors = [];\n }\n\n throwIfAnySuppressedTypeErrors(): void {\n if (this.suppressedTypeErrors.length > 0) {\n throw this.suppressedTypeErrors[0];\n }\n }\n\n #doAction(action: ReactiveAction) {\n const { r, entity } = action;\n return r.kind === \"reaction\" ? r.fn(entity, this.em.ctx) : (entity as any)[r.name].load();\n }\n\n /** Remembers a TypeError'd action to retry post-hooks, deduped by (target field name, entity). */\n private addPendingTypeError(action: ReactiveAction): void {\n let byEntity = this.actionsPendingTypeErrors.get(action.r.name);\n if (!byEntity) this.actionsPendingTypeErrors.set(action.r.name, (byEntity = new Map()));\n byEntity.set(action.entity, action);\n }\n\n needsRecalc(kind: \"reactables\" | \"reactiveQueries\"): boolean {\n return kind === \"reactables\" ? this.#needsRecalc.populate || this.#needsRecalc.reaction : this.#needsRecalc.query;\n }\n\n private getPending(r: Reactable): { todo: Set<Entity>; done: Set<Entity> } {\n let pending = this.pendingReactables.get(r);\n if (!pending) {\n pending = { todo: new Set(), done: new Set() };\n this.pendingReactables.set(r, pending);\n // The dirty field name is constant per reactable, so mark it once on the first\n // queue instead of paying the `getMetadata` + map lookup on every queued entity.\n this.getDirtyFields(getMetadata(r.cstr)).add(r.name);\n }\n return pending;\n }\n\n private getDirtyFields(meta: EntityMetadata): Set<string> {\n let dirty = this.dirtyFields.get(meta.tagName);\n if (!dirty) {\n dirty = new Set();\n this.dirtyFields.set(meta.tagName, dirty);\n }\n return dirty;\n }\n\n private getReactables(entity: Entity): Reactable[] {\n // If two books are getting merged, and so a normally-immutable `BookReview.book` is being changed,\n // then even normally-immutable fields need to be recalculated.\n const meta = getMetadata(entity);\n return getEmInternalApi(this.em).isMerging(entity) ? meta.reactablesIncludingReadOnly! : meta.reactables!;\n }\n\n private getReactablesByField(entity: Entity, fieldName: string): readonly Reactable[] {\n const meta = getMetadata(entity);\n const byField = getEmInternalApi(this.em).isMerging(entity)\n ? meta.reactablesIncludingReadOnlyByField!\n : meta.reactablesByField!;\n // Most fields have no reactables, so return a shared empty array instead of allocating one per set\n return byField.get(fieldName) ?? noReactables;\n }\n}\n\n/** A shared frozen array for fields with no downstream reactables. */\nconst noReactables: readonly Reactable[] = Object.freeze([]);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ReactionsManager.js","names":["#needsRecalc","#doAction"],"sources":["../../src/reactivity/ReactionsManager.ts"],"sourcesContent":["import type { Reactable } from \"src/config.ts\";\nimport type { Entity } from \"src/Entity.ts\";\nimport { type EntityManager, getEmInternalApi, invokeRule } from \"src/EntityManager.ts\";\nimport { type EntityMetadata, getMetadata } from \"src/EntityMetadata.ts\";\nimport { NoIdError } from \"src/index.ts\";\nimport { type ReactionLogger, globalLogger, noopReactionLogger } from \"src/logging/ReactionLogger.ts\";\nimport { followReverseHint } from \"src/reactivity/reactiveHints.ts\";\nimport { type ValidationError, ValidationErrors } from \"src/rules.ts\";\nimport {\n type TransitionCallback,\n type TransitionStep,\n created,\n describeValue,\n matchesTransition,\n} from \"src/transitions.ts\";\nimport { runInTrustedContext } from \"src/trusted.ts\";\nimport { failIfAnyRejected } from \"src/utils.ts\";\n\nexport type ReactiveAction = { r: Reactable; entity: Entity };\n/**\n * Manages the reactivity of tracking which source fields have changed and finding/recalculating\n * their downstream derived fields.\n *\n * Source fields are usually regular/dumb primitives, i.e. `Author.age` is a source field\n * of `Book.isPublic`, but derived fields can themselves be source fields as well.\n * Guarded state transitions share this loop; their commit callbacks wait until SQL has been flushed.\n */\nexport class ReactionsManager {\n /** Stores all source `Reactables`s that have been marked for later traversal. */\n private pendingReactables: Map<Reactable, { todo: Set<Entity>; done: Set<Entity> }> = new Map();\n /** Queued state changes, including changes made by callbacks, are processed in reaction passes. */\n private pendingTransitions: PendingTransition[] = [];\n /**\n * Initial assignments are collapsed until reactions first see the entity.\n *\n * Each entity keeps one creation step per transition field. Each step is also in `pendingTransitions`, and\n * later assignments update its `to` in place. An entity usually has one or two transition fields, so a\n * short array is cheaper than a nested Map.\n */\n private transitionCreations: Map<Entity, PendingTransition[]> = new Map();\n /** New entities that reactions have seen, so their assignments are transitions. */\n private postCreationWindowEntities: Set<Entity> = new Set();\n /** Only matching callbacks are retained until SQL and final validation finish. */\n private pendingCommitTransitions: PendingCommitCallback[] = [];\n /** Failed actions to retry post-hooks, deduped by target field name -> entity (rare error path). */\n private actionsPendingTypeErrors: Map<string, Map<Entity, ReactiveAction>> = new Map();\n /**\n * A map of entity tagName -> fields that have been marked as dirty.\n *\n * The key of the map is \"just\" the tag name, instead of a specific entity, b/c at the time of\n * being marked dirty, we only have the upstream/source entity + fieldName that has changed, and\n * not the 1-or-more downstream/target entities that will actually recalc.\n *\n * Instead, we just track the dirty fields by type-of-entity, which is enough for `isPendingRecalc`.\n */\n private dirtyFields: Map<string, Set<string>> = new Map();\n /**\n * Tracks which entities have already run a `runOnce` reactable, keyed by the target field\n * name -> entities. We key on `r.name` (not `r` itself) b/c two `Reactable`s reverse-indexed\n * from different source paths can target the same derived field, and must dedupe together.\n * Keying the outer map by field name (of which there are few) instead of by entity (of which\n * there are many) means we allocate just one `Set` per distinct field name, rather than a\n * `Set` per entity or the old per-action `${entity.toTaggedString()}_${r.name}` key strings.\n */\n private processedActions: Map<string, Set<Entity>> = new Map();\n #needsRecalc = { populate: false, query: false, reaction: false };\n // Accessible for EntityManager.runValidation to reuse\n logger: ReactionLogger = globalLogger ?? noopReactionLogger;\n private em: EntityManager;\n /** These are NPEs that *might* have been from invalid m2o fields, so we only throw them after validation. */\n private suppressedTypeErrors: Error[] = [];\n\n constructor(em: EntityManager) {\n this.em = em;\n }\n\n /**\n * Called by `setField` before changing an enum field, to check and queue the transition.\n *\n * An entity has no previous state until Joist's reactions have seen it, so every assignment to a new\n * entity until then is part of its creation. I.e. `em.create` then `entity.status = Open`, or a factory\n * setting a default and then a `withStatus` value, or `em.findOrCreate` creating then upserting, all\n * create the entity as `Open`. Creation isn't checked against the `transitions` table.\n */\n queueTransition(entity: Entity, fieldName: string, from: unknown, to: unknown): void {\n const meta = getMetadata(entity);\n const transitions = meta.transitions!.get(fieldName);\n if (!transitions) return;\n // If this is a new entity still within its creation window, only keep the last step\n if (entity.isNewEntity && !this.postCreationWindowEntities.has(entity)) {\n let creations = this.transitionCreations.get(entity);\n if (!creations) this.transitionCreations.set(entity, (creations = []));\n const existing = creations.find((pending) => pending.fieldName === fieldName);\n if (existing) {\n existing.step.to = to;\n } else {\n const pending = { entity, fieldName, step: { from: created, to } };\n creations.push(pending);\n this.pendingTransitions.push(pending);\n }\n } else {\n // Base and subtype tables are all checked, so each type can only restrict its base type's changes\n for (const table of transitions.tables) {\n if (table.get(from)?.includes(to)) continue;\n const message = `Cannot change ${fieldName} from ${describeValue(meta, fieldName, from)} to ${describeValue(meta, fieldName, to)}`;\n throw new ValidationErrors([{ entity, message }]);\n }\n this.pendingTransitions.push({ entity, fieldName, step: { from, to } });\n }\n }\n\n /** Forgets a field's creation transition after a factory assigns its trusted initial value. */\n forgetCreationTransition(entity: Entity, fieldName: string): void {\n const creations = this.transitionCreations.get(entity);\n const i = creations?.findIndex((pending) => pending.fieldName === fieldName) ?? -1;\n if (i === -1) return;\n // Remove it from both lists, so `needsRecalc` doesn't run an empty reaction pass for it\n const [pending] = creations!.splice(i, 1);\n this.pendingTransitions.splice(this.pendingTransitions.lastIndexOf(pending), 1);\n }\n\n /** Returns whether callbacks need a commit phase, even if there are no net SQL changes. */\n hasPendingCommitTransitions(): boolean {\n return this.pendingCommitTransitions.some((pending) => !pending.entity.isDeletedEntity);\n }\n\n /** Runs the queued callbacks after all SQL and validation succeed, without rechecking guards. */\n async runCommitTransitions(): Promise<void> {\n const batch = this.pendingCommitTransitions.splice(0);\n for (const wave of groupIntoEntityWaves(batch)) {\n const results = await Promise.allSettled(\n wave.map(async (pending) => {\n if (!pending.entity.isDeletedEntity) {\n await pending.callback.run(pending.entity, this.em.ctx, pending.step);\n }\n }),\n );\n failIfAnyRejected(results);\n }\n }\n\n /**\n * Queue all downstream reactive fields that depend on `fieldName` as a source field.\n *\n * This method can be synchronous b/c we internally queue the `Reactable` reverse\n * indexes on the given source `fieldName`, and don't crawl/walk back to the downstream\n * entities/fields until `recalcPendingReactables` is called.\n */\n queueDownstreamReactables(entity: Entity, fieldName: string): void {\n // Use the reverse index of Reactables that configureMetadata sets up\n for (const r of this.getReactablesByField(entity, fieldName)) {\n // We always queue the reactable/entity, even if we're mid-flush or even mid-recalc, to avoid:\n // - firstName is changed from a1 to a2\n // - this triggers firstName's reactable to `Author.initials` to be queued\n // - during the 1st em.flush loop, we recalc `Author.initials` and it didn't change\n // - during the 1st em.flush loop, a hook changes firstName from a2 to b2\n // - if we skip re-queuing firstName's reactables, we will miss that initials needs\n // its `.load()` called again so that it's `setField` marks `initials` as\n // dirty, otherwise it will be left out of any INSERTs/UPDATEs.\n this.getPending(r).todo.add(entity);\n this.#needsRecalc[r.kind] = true;\n this.logger.logQueued(entity, fieldName, r);\n }\n }\n\n /** Dequeues reactivity on `fieldName`, i.e. if it's no longer dirty. */\n dequeueDownstreamReactables(entity: Entity, fieldName: string): void {\n // Use the reverse index of Reactables that configureMetadata sets up\n for (const r of this.getReactablesByField(entity, fieldName)) {\n const pending = this.getPending(r);\n if (pending.done.has(entity)) {\n // Ironically, if we've already run this reactable, asking to dequeue probably means\n // we need to run it again (to recalc its value), b/c this could be a mid-flush change, i.e.:\n // - firstName = a1 from db\n // - firstName is changed to a2, triggers initials reactable\n // - firstName is changed back to a1, which is the original value, so setField\n // thinks we can dequeue the reactable\n // - but actually our reactable needs to be re-run with the restored value\n pending.todo.add(entity);\n this.#needsRecalc[r.kind] = true;\n } else if (r.fields.length === 1) {\n // We can only delete/dequeue a reaction if `fieldName` is the only or last field\n // that had triggered `r` to run. Since we don't track that currently, i.e. we'd\n // need to have a `Pending.dirtyFields`, for now just only dequeue if `r` only\n // has one field (which is us) anyway.\n pending.todo.delete(entity);\n }\n }\n }\n\n /** Queue all downstream reactive fields that depend on this entity being created or deleted. */\n queueAllDownstreamFields(entity: Entity, reason: \"created\" | \"deleted\"): void {\n for (const r of this.getReactables(entity)) {\n this.getPending(r).todo.add(entity);\n this.#needsRecalc[r.kind] = true;\n this.logger.logQueuedAll(entity, reason, r);\n }\n }\n\n /**\n * Returns whether this field might be pending recalc.\n *\n * This is technically a guess, b/c our reaction infra may not yet have crawled from an upstream\n * source field down to the `N` specific target entities that need recalced, and instead just\n * knows \"it will need to do that soon\" i.e. at the next `em.flush`.\n *\n * So, instead this is a heuristic that reports if `fieldName` has been marked dirty for _some_\n * entities of type `entity`, but we don't technically know if that's _this_ entity.\n *\n * I.e. this might return false positives, but should never return false negatives.\n */\n isMaybePendingRecalc(entity: Entity, fieldName: string): boolean {\n return this.getDirtyFields(getMetadata(entity)).has(fieldName);\n }\n\n hasPendingReactiveQueries(): boolean {\n return this.#needsRecalc.query;\n }\n\n /**\n * Given source `Reactable` \"reverse indexes\" that have been queued as dirty by calls\n * to setters, `em.register`, or `em.delete`, asynchronously walks/crawls to the downstream\n * derived fields and calls `.load()` on them to calc.\n *\n * We also do this in a loop to handle reactive fields depending on other reactive fields.\n */\n async recalcPendingReactables(kind: \"reactables\" | \"reactiveQueries\") {\n if (this.needsRecalc(kind)) this.logger.logStartingRecalc(this.em, kind);\n\n let loops = 0;\n while (this.needsRecalc(kind)) {\n // Reactions are about to see every new entity so far, so later assignments to them are transitions\n this.endTransitionCreations();\n // ...we probably should only loop for `kind=reactables` Reactables, and `kind=reactiveQueries`\n // AsyncReactiveFields should probably only have a single loop, after which we return and\n // let `em.flush` push the latest values to the db, so our 2nd-order AsyncReactiveFields\n // will see the latest value.\n if (kind === \"reactables\") {\n this.#needsRecalc.populate = false;\n this.#needsRecalc.reaction = false;\n } else {\n this.#needsRecalc.query = false;\n }\n\n // Dedupe actions by (target field name, entity); see the `processedActions` docs for why\n // we key on `r.name` instead of `r` or the old per-action key strings\n const seen: Map<string, Set<Entity>> = new Map();\n const actions: ReactiveAction[] = [];\n\n const walks = await Promise.all(\n [...this.pendingReactables.entries()].map(async ([r, pending]) => {\n // Skip reactive queries until post-flush\n if (kind === \"reactables\" && r.kind === \"query\") return undefined;\n if (kind === \"reactiveQueries\" && (r.kind === \"populate\" || r.kind === \"reaction\")) return undefined;\n // Copy pending and clear it\n const todo = [...pending.todo];\n if (todo.length === 0) return undefined;\n pending.todo.clear();\n for (const doing of todo) pending.done.add(doing);\n // Walk back from the source to any downstream entities\n const entities = r.path.length === 0 ? todo : await followReverseHint(r.name, todo, r.path);\n const actionableEntities = entities.filter((entity) => !entity.isDeletedEntity && entity instanceof r.cstr);\n let seenEntities = seen.get(r.name);\n if (!seenEntities) seen.set(r.name, (seenEntities = new Set()));\n const processed = r.runOnce ? this.processedActions.get(r.name) : undefined;\n for (const entity of actionableEntities) {\n // We could arrive at the same reactable from multiple paths (eg, 2 dependent fields changed), so we need to\n // dedupe based on the entity and reactable to only run each action once for any given entity per loop\n if (seenEntities.has(entity)) continue;\n // If this reactable has already run and shouldn't run again, then skip it\n if (processed && processed.has(entity)) continue;\n seenEntities.add(entity);\n actions.push({ r, entity });\n }\n return { r, todo, entities: actionableEntities };\n }),\n );\n this.logger.logWalks(walks, \"recalc\");\n this.logger.logLoadingStart(this.em, actions);\n // Use allSettled so that we can watch for derived values that want to use an entity's id\n // i.e. they can fail, but we'll queue them from later.\n const startTime = this.logger.now();\n const results = await runInTrustedContext(() => Promise.allSettled(actions.map((a) => this.#doAction(a))));\n const endTime = this.logger.now();\n this.logger.logLoadingEnd(this.em, endTime - startTime);\n\n // This loop's actions are already unique by (name, entity), so a plain array dedupes fine\n const actionsPendingAssignedIds: ReactiveAction[] = [];\n const failures: any[] = [];\n results.forEach((result, i) => {\n if (result.status === \"rejected\") {\n // Let `author.id` and `book.author.get.firstName` errors run again after flush/hooks fills them in\n if (result.reason instanceof NoIdError) {\n actionsPendingAssignedIds.push(actions[i]);\n } else if (result.reason instanceof TypeError) {\n this.addPendingTypeError(actions[i]);\n } else {\n failures.push(result.reason);\n }\n }\n });\n\n // If we have any actions that need to be re-run because they failed due to a missing id, then we assign ids and\n // re-run them.\n if (actionsPendingAssignedIds.length > 0) {\n await this.em.assignNewIds();\n this.logger.logLoadingStart(this.em, actionsPendingAssignedIds);\n const startTime = this.logger.now();\n const results = await runInTrustedContext(() =>\n Promise.allSettled(actionsPendingAssignedIds.map((a) => this.#doAction(a))),\n );\n const endTime = this.logger.now();\n this.logger.logLoadingEnd(this.em, endTime - startTime);\n results.forEach((result, i) => {\n if (result.status === \"rejected\") {\n if (result.reason instanceof TypeError) {\n this.addPendingTypeError(actionsPendingAssignedIds[i]);\n } else {\n failures.push(result.reason);\n }\n }\n });\n }\n\n if (failures.length > 0) throw failures[0];\n if (kind === \"reactables\") {\n await runInTrustedContext(() => this.processPendingTransitions());\n }\n // Record any successful actions that should only run once so we don't run them again\n for (const action of actions) {\n if (action.r.runOnce) {\n let set = this.processedActions.get(action.r.name);\n if (!set) this.processedActions.set(action.r.name, (set = new Set()));\n set.add(action.entity);\n }\n }\n // This should generally not happen, only if two reactive fields depend on each other,\n // which in theory should probably be caught/blow up in the `configureMetadata` step,\n // but if it's not caught sooner, at least don't infinite loop.\n if (loops++ > 50) {\n throw new Error(\"recalc looped too many times, probably a circular dependency\");\n }\n }\n }\n\n async recalcPendingTypeErrors() {\n const actions: ReactiveAction[] = [];\n for (const byEntity of this.actionsPendingTypeErrors.values()) {\n for (const action of byEntity.values()) actions.push(action);\n }\n this.actionsPendingTypeErrors.clear();\n\n const startTime = this.logger.now();\n const results = await runInTrustedContext(() =>\n Promise.allSettled(actions.filter((a) => !a.entity.isDeletedEntity).map((a) => this.#doAction(a))),\n );\n const endTime = this.logger.now();\n this.logger.logLoadingEnd(this.em, endTime - startTime);\n\n const failures: any[] = [];\n results.forEach((result) => {\n if (result.status === \"rejected\") {\n if (result.reason instanceof TypeError) {\n // Defer to the validation error to catch this\n this.suppressedTypeErrors.push(result.reason);\n } else {\n failures.push(result.reason);\n }\n }\n });\n if (failures.length > 0) throw failures[0];\n }\n\n get hasPendingTypeErrors(): boolean {\n return this.actionsPendingTypeErrors.size > 0;\n }\n\n /** Clears pending reaction and transition work after a successful flush, including flushes with no SQL. */\n clear(): void {\n this.pendingReactables = new Map();\n this.processedActions.clear();\n this.pendingTransitions.length = 0;\n this.transitionCreations.clear();\n this.postCreationWindowEntities.clear();\n this.pendingCommitTransitions.length = 0;\n this.#needsRecalc = { populate: false, query: false, reaction: false };\n }\n\n setLogger(logger: ReactionLogger | undefined): void {\n this.logger = logger ?? noopReactionLogger;\n }\n\n clearSuppressedTypeErrors(): void {\n this.suppressedTypeErrors = [];\n }\n\n throwIfAnySuppressedTypeErrors(): void {\n if (this.suppressedTypeErrors.length > 0) {\n throw this.suppressedTypeErrors[0];\n }\n }\n\n #doAction(action: ReactiveAction) {\n const { r, entity } = action;\n return r.kind === \"reaction\" ? r.fn(entity, this.em.ctx) : (entity as any)[r.name].load();\n }\n\n /** Remembers a TypeError'd action to retry post-hooks, deduped by (target field name, entity). */\n private addPendingTypeError(action: ReactiveAction): void {\n let byEntity = this.actionsPendingTypeErrors.get(action.r.name);\n if (!byEntity) this.actionsPendingTypeErrors.set(action.r.name, (byEntity = new Map()));\n byEntity.set(action.entity, action);\n }\n\n needsRecalc(kind: \"reactables\" | \"reactiveQueries\"): boolean {\n return kind === \"reactables\"\n ? this.#needsRecalc.populate || this.#needsRecalc.reaction || this.pendingTransitions.length > 0\n : this.#needsRecalc.query;\n }\n\n /**\n * Ends the creation of every new entity so far, before each loop of reactions.\n *\n * After this, assignments to those entities are transitions, i.e. an Approval created as `Requested`\n * that a reaction then auto-approves records `Requested -> Approved`.\n */\n private endTransitionCreations(): void {\n for (const entity of this.transitionCreations.keys()) this.postCreationWindowEntities.add(entity);\n this.transitionCreations.clear();\n }\n\n /**\n * Evaluates each pending transition's guards once, then dispatches its matching callbacks.\n *\n * I.e. Pending -> Signed -> Pending restores an advance's original status, but the Signed guard must still run.\n * Callbacks can append more transitions; taking one batch leaves those for the next reaction pass.\n * Each wave processes one step per entity, so populate requests batch across entities without\n * allowing an entity's next transition to overtake its previous callbacks.\n *\n * We might eventually treat A -> B -> A as an undo and ignore both transitions before reactions\n * have seen them. For now, each transition is meaningful, even when the final state is unchanged.\n */\n private async processPendingTransitions(): Promise<void> {\n // Ordinary reactions may have created more entities during this pass.\n this.endTransitionCreations();\n if (this.pendingTransitions.length === 0) return;\n const batch = this.pendingTransitions.splice(0);\n for (const wave of groupIntoEntityWaves(batch)) {\n const guardResults = await Promise.allSettled(wave.map((pending) => this.evaluateTransitionGuards(pending)));\n const errors = failIfAnyRejected(guardResults).flat();\n if (errors.length > 0) throw new ValidationErrors(errors);\n const callbackResults = await Promise.allSettled(wave.map((pending) => this.runTransitionCallbacks(pending)));\n failIfAnyRejected(callbackResults);\n }\n }\n\n /** Evaluates one transition's matching guards before any callbacks in its wave run. */\n private async evaluateTransitionGuards(pending: PendingTransition): Promise<ValidationError[]> {\n const { entity, fieldName, step } = pending;\n if (entity.isDeletedEntity || step.from === created) return [];\n const guards = getMetadata(entity)\n .transitions!.get(fieldName)!\n .guards.filter((guard) => matchesTransition(guard.matcher, step));\n const results = await Promise.allSettled(guards.map((guard) => invokeRule(entity, () => guard.run(entity, step))));\n return failIfAnyRejected(results).flat();\n }\n\n /** Runs one entity's callbacks in registration order after all guards in the wave have passed. */\n private async runTransitionCallbacks(pending: PendingTransition): Promise<void> {\n const { entity, fieldName, step } = pending;\n if (entity.isDeletedEntity) return;\n for (const callback of getMetadata(entity).transitions!.get(fieldName)!.callbacks) {\n if (entity.isDeletedEntity) break;\n if (!matchesTransition(callback.matcher, step)) continue;\n if (callback.phase === \"commit\") {\n this.pendingCommitTransitions.push({ entity, callback, step });\n } else {\n await callback.run(entity, this.em.ctx, step);\n }\n }\n }\n\n private getPending(r: Reactable): { todo: Set<Entity>; done: Set<Entity> } {\n let pending = this.pendingReactables.get(r);\n if (!pending) {\n pending = { todo: new Set(), done: new Set() };\n this.pendingReactables.set(r, pending);\n // The dirty field name is constant per reactable, so mark it once on the first\n // queue instead of paying the `getMetadata` + map lookup on every queued entity.\n this.getDirtyFields(getMetadata(r.cstr)).add(r.name);\n }\n return pending;\n }\n\n private getDirtyFields(meta: EntityMetadata): Set<string> {\n let dirty = this.dirtyFields.get(meta.tagName);\n if (!dirty) {\n dirty = new Set();\n this.dirtyFields.set(meta.tagName, dirty);\n }\n return dirty;\n }\n\n private getReactables(entity: Entity): Reactable[] {\n // If two books are getting merged, and so a normally-immutable `BookReview.book` is being changed,\n // then even normally-immutable fields need to be recalculated.\n const meta = getMetadata(entity);\n return getEmInternalApi(this.em).isMerging(entity) ? meta.reactablesIncludingReadOnly! : meta.reactables!;\n }\n\n private getReactablesByField(entity: Entity, fieldName: string): readonly Reactable[] {\n const meta = getMetadata(entity);\n const byField = getEmInternalApi(this.em).isMerging(entity)\n ? meta.reactablesIncludingReadOnlyByField!\n : meta.reactablesByField!;\n // Most fields have no reactables, so return a shared empty array instead of allocating one per set\n return byField.get(fieldName) ?? noReactables;\n }\n}\n\n/** A transition awaiting its guards and callbacks. */\ninterface PendingTransition {\n entity: Entity;\n fieldName: string;\n step: TransitionStep;\n}\n\n/** A matching commit callback whose transition's guards have already passed. */\ninterface PendingCommitCallback {\n entity: Entity;\n callback: TransitionCallback;\n step: TransitionStep;\n}\n\n/** Groups queued work into parallel waves while preserving each entity's input order. */\nfunction groupIntoEntityWaves<T extends { entity: Entity }>(batch: readonly T[]): T[][] {\n const waves: T[][] = [];\n const waveByEntity = new Map<Entity, number>();\n // I.e. [ba1 Signed, ba1 Paid, ba2 Signed] becomes [[ba1 Signed, ba2 Signed], [ba1 Paid]].\n for (const pending of batch) {\n const wave = waveByEntity.get(pending.entity) ?? 0;\n (waves[wave] ??= []).push(pending);\n waveByEntity.set(pending.entity, wave + 1);\n }\n return waves;\n}\n\n/** A shared frozen array for fields with no downstream reactables. */\nconst noReactables: readonly Reactable[] = Object.freeze([]);\n"],"mappings":";;;;;;;;;;;;;;;;;;AA2BA,IAAa,mBAAb,MAA8B;;CAE5B,oCAAsF,IAAI,IAAI;;CAE9F,qBAAkD,CAAC;;;;;;;;CAQnD,sCAAgE,IAAI,IAAI;;CAExE,6CAAkD,IAAI,IAAI;;CAE1D,2BAA4D,CAAC;;CAE7D,2CAA6E,IAAI,IAAI;;;;;;;;;;CAUrF,8BAAgD,IAAI,IAAI;;;;;;;;;CASxD,mCAAqD,IAAI,IAAI;CAC7D,eAAe;EAAE,UAAU;EAAO,OAAO;EAAO,UAAU;CAAM;CAEhE,SAAyB,gBAAgB;CACzC;;CAEA,uBAAwC,CAAC;CAEzC,YAAY,IAAmB;EAC7B,KAAK,KAAK;CACZ;;;;;;;;;CAUA,gBAAgB,QAAgB,WAAmB,MAAe,IAAmB;EACnF,MAAM,OAAO,YAAY,MAAM;EAC/B,MAAM,cAAc,KAAK,YAAa,IAAI,SAAS;EACnD,IAAI,CAAC,aAAa;EAElB,IAAI,OAAO,eAAe,CAAC,KAAK,2BAA2B,IAAI,MAAM,GAAG;GACtE,IAAI,YAAY,KAAK,oBAAoB,IAAI,MAAM;GACnD,IAAI,CAAC,WAAW,KAAK,oBAAoB,IAAI,QAAS,YAAY,CAAC,CAAE;GACrE,MAAM,WAAW,UAAU,MAAM,YAAY,QAAQ,cAAc,SAAS;GAC5E,IAAI,UACF,SAAS,KAAK,KAAK;QACd;IACL,MAAM,UAAU;KAAE;KAAQ;KAAW,MAAM;MAAE,MAAM;MAAS;KAAG;IAAE;IACjE,UAAU,KAAK,OAAO;IACtB,KAAK,mBAAmB,KAAK,OAAO;GACtC;EACF,OAAO;GAEL,KAAK,MAAM,SAAS,YAAY,QAAQ;IACtC,IAAI,MAAM,IAAI,IAAI,CAAC,EAAE,SAAS,EAAE,GAAG;IACnC,MAAM,UAAU,iBAAiB,UAAU,QAAQ,cAAc,MAAM,WAAW,IAAI,EAAE,MAAM,cAAc,MAAM,WAAW,EAAE;IAC/H,MAAM,IAAI,iBAAiB,CAAC;KAAE;KAAQ;IAAQ,CAAC,CAAC;GAClD;GACA,KAAK,mBAAmB,KAAK;IAAE;IAAQ;IAAW,MAAM;KAAE;KAAM;IAAG;GAAE,CAAC;EACxE;CACF;;CAGA,yBAAyB,QAAgB,WAAyB;EAChE,MAAM,YAAY,KAAK,oBAAoB,IAAI,MAAM;EACrD,MAAM,IAAI,WAAW,WAAW,YAAY,QAAQ,cAAc,SAAS,KAAK;EAChF,IAAI,MAAM,IAAI;EAEd,MAAM,CAAC,WAAW,UAAW,OAAO,GAAG,CAAC;EACxC,KAAK,mBAAmB,OAAO,KAAK,mBAAmB,YAAY,OAAO,GAAG,CAAC;CAChF;;CAGA,8BAAuC;EACrC,OAAO,KAAK,yBAAyB,MAAM,YAAY,CAAC,QAAQ,OAAO,eAAe;CACxF;;CAGA,MAAM,uBAAsC;EAC1C,MAAM,QAAQ,KAAK,yBAAyB,OAAO,CAAC;EACpD,KAAK,MAAM,QAAQ,qBAAqB,KAAK,GAAG;GAC9C,MAAM,UAAU,MAAM,QAAQ,WAC5B,KAAK,IAAI,OAAO,YAAY;IAC1B,IAAI,CAAC,QAAQ,OAAO,iBAClB,MAAM,QAAQ,SAAS,IAAI,QAAQ,QAAQ,KAAK,GAAG,KAAK,QAAQ,IAAI;GAExE,CAAC,CACH;GACA,kBAAkB,OAAO;EAC3B;CACF;;;;;;;;CASA,0BAA0B,QAAgB,WAAyB;EAEjE,KAAK,MAAM,KAAK,KAAK,qBAAqB,QAAQ,SAAS,GAAG;GAS5D,KAAK,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,MAAM;GAClC,KAAKA,aAAa,EAAE,QAAQ;GAC5B,KAAK,OAAO,UAAU,QAAQ,WAAW,CAAC;EAC5C;CACF;;CAGA,4BAA4B,QAAgB,WAAyB;EAEnE,KAAK,MAAM,KAAK,KAAK,qBAAqB,QAAQ,SAAS,GAAG;GAC5D,MAAM,UAAU,KAAK,WAAW,CAAC;GACjC,IAAI,QAAQ,KAAK,IAAI,MAAM,GAAG;IAQ5B,QAAQ,KAAK,IAAI,MAAM;IACvB,KAAKA,aAAa,EAAE,QAAQ;GAC9B,OAAO,IAAI,EAAE,OAAO,WAAW,GAK7B,QAAQ,KAAK,OAAO,MAAM;EAE9B;CACF;;CAGA,yBAAyB,QAAgB,QAAqC;EAC5E,KAAK,MAAM,KAAK,KAAK,cAAc,MAAM,GAAG;GAC1C,KAAK,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,MAAM;GAClC,KAAKA,aAAa,EAAE,QAAQ;GAC5B,KAAK,OAAO,aAAa,QAAQ,QAAQ,CAAC;EAC5C;CACF;;;;;;;;;;;;;CAcA,qBAAqB,QAAgB,WAA4B;EAC/D,OAAO,KAAK,eAAe,YAAY,MAAM,CAAC,CAAC,CAAC,IAAI,SAAS;CAC/D;CAEA,4BAAqC;EACnC,OAAO,KAAKA,aAAa;CAC3B;;;;;;;;CASA,MAAM,wBAAwB,MAAwC;EACpE,IAAI,KAAK,YAAY,IAAI,GAAG,KAAK,OAAO,kBAAkB,KAAK,IAAI,IAAI;EAEvE,IAAI,QAAQ;EACZ,OAAO,KAAK,YAAY,IAAI,GAAG;GAE7B,KAAK,uBAAuB;GAK5B,IAAI,SAAS,cAAc;IACzB,KAAKA,aAAa,WAAW;IAC7B,KAAKA,aAAa,WAAW;GAC/B,OACE,KAAKA,aAAa,QAAQ;GAK5B,MAAM,uBAAiC,IAAI,IAAI;GAC/C,MAAM,UAA4B,CAAC;GAEnC,MAAM,QAAQ,MAAM,QAAQ,IAC1B,CAAC,GAAG,KAAK,kBAAkB,QAAQ,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,GAAG,aAAa;IAEhE,IAAI,SAAS,gBAAgB,EAAE,SAAS,SAAS,OAAO,KAAA;IACxD,IAAI,SAAS,sBAAsB,EAAE,SAAS,cAAc,EAAE,SAAS,aAAa,OAAO,KAAA;IAE3F,MAAM,OAAO,CAAC,GAAG,QAAQ,IAAI;IAC7B,IAAI,KAAK,WAAW,GAAG,OAAO,KAAA;IAC9B,QAAQ,KAAK,MAAM;IACnB,KAAK,MAAM,SAAS,MAAM,QAAQ,KAAK,IAAI,KAAK;IAGhD,MAAM,sBADW,EAAE,KAAK,WAAW,IAAI,OAAO,MAAM,kBAAkB,EAAE,MAAM,MAAM,EAAE,IAAI,EAAA,CACtD,QAAQ,WAAW,CAAC,OAAO,mBAAmB,kBAAkB,EAAE,IAAI;IAC1G,IAAI,eAAe,KAAK,IAAI,EAAE,IAAI;IAClC,IAAI,CAAC,cAAc,KAAK,IAAI,EAAE,MAAO,+BAAe,IAAI,IAAI,CAAE;IAC9D,MAAM,YAAY,EAAE,UAAU,KAAK,iBAAiB,IAAI,EAAE,IAAI,IAAI,KAAA;IAClE,KAAK,MAAM,UAAU,oBAAoB;KAGvC,IAAI,aAAa,IAAI,MAAM,GAAG;KAE9B,IAAI,aAAa,UAAU,IAAI,MAAM,GAAG;KACxC,aAAa,IAAI,MAAM;KACvB,QAAQ,KAAK;MAAE;MAAG;KAAO,CAAC;IAC5B;IACA,OAAO;KAAE;KAAG;KAAM,UAAU;IAAmB;GACjD,CAAC,CACH;GACA,KAAK,OAAO,SAAS,OAAO,QAAQ;GACpC,KAAK,OAAO,gBAAgB,KAAK,IAAI,OAAO;GAG5C,MAAM,YAAY,KAAK,OAAO,IAAI;GAClC,MAAM,UAAU,MAAM,0BAA0B,QAAQ,WAAW,QAAQ,KAAK,MAAM,KAAKC,UAAU,CAAC,CAAC,CAAC,CAAC;GACzG,MAAM,UAAU,KAAK,OAAO,IAAI;GAChC,KAAK,OAAO,cAAc,KAAK,IAAI,UAAU,SAAS;GAGtD,MAAM,4BAA8C,CAAC;GACrD,MAAM,WAAkB,CAAC;GACzB,QAAQ,SAAS,QAAQ,MAAM;IAC7B,IAAI,OAAO,WAAW,YAAY;KAEhC,IAAI,OAAO,kBAAkB,WAC3B,0BAA0B,KAAK,QAAQ,EAAE;UACpC,IAAI,OAAO,kBAAkB,WAClC,KAAK,oBAAoB,QAAQ,EAAE;UAEnC,SAAS,KAAK,OAAO,MAAM;IAE/B;GACF,CAAC;GAID,IAAI,0BAA0B,SAAS,GAAG;IACxC,MAAM,KAAK,GAAG,aAAa;IAC3B,KAAK,OAAO,gBAAgB,KAAK,IAAI,yBAAyB;IAC9D,MAAM,YAAY,KAAK,OAAO,IAAI;IAClC,MAAM,UAAU,MAAM,0BACpB,QAAQ,WAAW,0BAA0B,KAAK,MAAM,KAAKA,UAAU,CAAC,CAAC,CAAC,CAC5E;IACA,MAAM,UAAU,KAAK,OAAO,IAAI;IAChC,KAAK,OAAO,cAAc,KAAK,IAAI,UAAU,SAAS;IACtD,QAAQ,SAAS,QAAQ,MAAM;KAC7B,IAAI,OAAO,WAAW,YAAY;MAChC,IAAI,OAAO,kBAAkB,WAC3B,KAAK,oBAAoB,0BAA0B,EAAE;WAErD,SAAS,KAAK,OAAO,MAAM;KAE/B;IACF,CAAC;GACH;GAEA,IAAI,SAAS,SAAS,GAAG,MAAM,SAAS;GACxC,IAAI,SAAS,cACX,MAAM,0BAA0B,KAAK,0BAA0B,CAAC;GAGlE,KAAK,MAAM,UAAU,SACnB,IAAI,OAAO,EAAE,SAAS;IACpB,IAAI,MAAM,KAAK,iBAAiB,IAAI,OAAO,EAAE,IAAI;IACjD,IAAI,CAAC,KAAK,KAAK,iBAAiB,IAAI,OAAO,EAAE,MAAO,sBAAM,IAAI,IAAI,CAAE;IACpE,IAAI,IAAI,OAAO,MAAM;GACvB;GAKF,IAAI,UAAU,IACZ,MAAM,IAAI,MAAM,8DAA8D;EAElF;CACF;CAEA,MAAM,0BAA0B;EAC9B,MAAM,UAA4B,CAAC;EACnC,KAAK,MAAM,YAAY,KAAK,yBAAyB,OAAO,GAC1D,KAAK,MAAM,UAAU,SAAS,OAAO,GAAG,QAAQ,KAAK,MAAM;EAE7D,KAAK,yBAAyB,MAAM;EAEpC,MAAM,YAAY,KAAK,OAAO,IAAI;EAClC,MAAM,UAAU,MAAM,0BACpB,QAAQ,WAAW,QAAQ,QAAQ,MAAM,CAAC,EAAE,OAAO,eAAe,CAAC,CAAC,KAAK,MAAM,KAAKA,UAAU,CAAC,CAAC,CAAC,CACnG;EACA,MAAM,UAAU,KAAK,OAAO,IAAI;EAChC,KAAK,OAAO,cAAc,KAAK,IAAI,UAAU,SAAS;EAEtD,MAAM,WAAkB,CAAC;EACzB,QAAQ,SAAS,WAAW;GAC1B,IAAI,OAAO,WAAW,YAAY;IAChC,IAAI,OAAO,kBAAkB,WAE3B,KAAK,qBAAqB,KAAK,OAAO,MAAM;SAE5C,SAAS,KAAK,OAAO,MAAM;GAE/B;EACF,CAAC;EACD,IAAI,SAAS,SAAS,GAAG,MAAM,SAAS;CAC1C;CAEA,IAAI,uBAAgC;EAClC,OAAO,KAAK,yBAAyB,OAAO;CAC9C;;CAGA,QAAc;EACZ,KAAK,oCAAoB,IAAI,IAAI;EACjC,KAAK,iBAAiB,MAAM;EAC5B,KAAK,mBAAmB,SAAS;EACjC,KAAK,oBAAoB,MAAM;EAC/B,KAAK,2BAA2B,MAAM;EACtC,KAAK,yBAAyB,SAAS;EACvC,KAAKD,eAAe;GAAE,UAAU;GAAO,OAAO;GAAO,UAAU;EAAM;CACvE;CAEA,UAAU,QAA0C;EAClD,KAAK,SAAS,UAAU;CAC1B;CAEA,4BAAkC;EAChC,KAAK,uBAAuB,CAAC;CAC/B;CAEA,iCAAuC;EACrC,IAAI,KAAK,qBAAqB,SAAS,GACrC,MAAM,KAAK,qBAAqB;CAEpC;CAEA,UAAU,QAAwB;EAChC,MAAM,EAAE,GAAG,WAAW;EACtB,OAAO,EAAE,SAAS,aAAa,EAAE,GAAG,QAAQ,KAAK,GAAG,GAAG,IAAK,OAAe,EAAE,KAAK,CAAC,KAAK;CAC1F;;CAGA,oBAA4B,QAA8B;EACxD,IAAI,WAAW,KAAK,yBAAyB,IAAI,OAAO,EAAE,IAAI;EAC9D,IAAI,CAAC,UAAU,KAAK,yBAAyB,IAAI,OAAO,EAAE,MAAO,2BAAW,IAAI,IAAI,CAAE;EACtF,SAAS,IAAI,OAAO,QAAQ,MAAM;CACpC;CAEA,YAAY,MAAiD;EAC3D,OAAO,SAAS,eACZ,KAAKA,aAAa,YAAY,KAAKA,aAAa,YAAY,KAAK,mBAAmB,SAAS,IAC7F,KAAKA,aAAa;CACxB;;;;;;;CAQA,yBAAuC;EACrC,KAAK,MAAM,UAAU,KAAK,oBAAoB,KAAK,GAAG,KAAK,2BAA2B,IAAI,MAAM;EAChG,KAAK,oBAAoB,MAAM;CACjC;;;;;;;;;;;;CAaA,MAAc,4BAA2C;EAEvD,KAAK,uBAAuB;EAC5B,IAAI,KAAK,mBAAmB,WAAW,GAAG;EAC1C,MAAM,QAAQ,KAAK,mBAAmB,OAAO,CAAC;EAC9C,KAAK,MAAM,QAAQ,qBAAqB,KAAK,GAAG;GAC9C,MAAM,eAAe,MAAM,QAAQ,WAAW,KAAK,KAAK,YAAY,KAAK,yBAAyB,OAAO,CAAC,CAAC;GAC3G,MAAM,SAAS,kBAAkB,YAAY,CAAC,CAAC,KAAK;GACpD,IAAI,OAAO,SAAS,GAAG,MAAM,IAAI,iBAAiB,MAAM;GACxD,MAAM,kBAAkB,MAAM,QAAQ,WAAW,KAAK,KAAK,YAAY,KAAK,uBAAuB,OAAO,CAAC,CAAC;GAC5G,kBAAkB,eAAe;EACnC;CACF;;CAGA,MAAc,yBAAyB,SAAwD;EAC7F,MAAM,EAAE,QAAQ,WAAW,SAAS;EACpC,IAAI,OAAO,mBAAmB,KAAK,SAAS,SAAS,OAAO,CAAC;EAC7D,MAAM,SAAS,YAAY,MAAM,CAAC,CAC/B,YAAa,IAAI,SAAS,CAAC,CAC3B,OAAO,QAAQ,UAAU,kBAAkB,MAAM,SAAS,IAAI,CAAC;EAClE,MAAM,UAAU,MAAM,QAAQ,WAAW,OAAO,KAAK,UAAU,WAAW,cAAc,MAAM,IAAI,QAAQ,IAAI,CAAC,CAAC,CAAC;EACjH,OAAO,kBAAkB,OAAO,CAAC,CAAC,KAAK;CACzC;;CAGA,MAAc,uBAAuB,SAA2C;EAC9E,MAAM,EAAE,QAAQ,WAAW,SAAS;EACpC,IAAI,OAAO,iBAAiB;EAC5B,KAAK,MAAM,YAAY,YAAY,MAAM,CAAC,CAAC,YAAa,IAAI,SAAS,CAAC,CAAE,WAAW;GACjF,IAAI,OAAO,iBAAiB;GAC5B,IAAI,CAAC,kBAAkB,SAAS,SAAS,IAAI,GAAG;GAChD,IAAI,SAAS,UAAU,UACrB,KAAK,yBAAyB,KAAK;IAAE;IAAQ;IAAU;GAAK,CAAC;QAE7D,MAAM,SAAS,IAAI,QAAQ,KAAK,GAAG,KAAK,IAAI;EAEhD;CACF;CAEA,WAAmB,GAAwD;EACzE,IAAI,UAAU,KAAK,kBAAkB,IAAI,CAAC;EAC1C,IAAI,CAAC,SAAS;GACZ,UAAU;IAAE,sBAAM,IAAI,IAAI;IAAG,sBAAM,IAAI,IAAI;GAAE;GAC7C,KAAK,kBAAkB,IAAI,GAAG,OAAO;GAGrC,KAAK,eAAe,YAAY,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI;EACrD;EACA,OAAO;CACT;CAEA,eAAuB,MAAmC;EACxD,IAAI,QAAQ,KAAK,YAAY,IAAI,KAAK,OAAO;EAC7C,IAAI,CAAC,OAAO;GACV,wBAAQ,IAAI,IAAI;GAChB,KAAK,YAAY,IAAI,KAAK,SAAS,KAAK;EAC1C;EACA,OAAO;CACT;CAEA,cAAsB,QAA6B;EAGjD,MAAM,OAAO,YAAY,MAAM;EAC/B,OAAO,iBAAiB,KAAK,EAAE,CAAC,CAAC,UAAU,MAAM,IAAI,KAAK,8BAA+B,KAAK;CAChG;CAEA,qBAA6B,QAAgB,WAAyC;EACpF,MAAM,OAAO,YAAY,MAAM;EAK/B,QAJgB,iBAAiB,KAAK,EAAE,CAAC,CAAC,UAAU,MAAM,IACtD,KAAK,qCACL,KAAK,kBAAA,CAEM,IAAI,SAAS,KAAK;CACnC;AACF;;AAiBA,SAAS,qBAAmD,OAA4B;CACtF,MAAM,QAAe,CAAC;CACtB,MAAM,+BAAe,IAAI,IAAoB;CAE7C,KAAK,MAAM,WAAW,OAAO;EAC3B,MAAM,OAAO,aAAa,IAAI,QAAQ,MAAM,KAAK;EACjD,CAAC,MAAM,UAAU,CAAC,EAAA,CAAG,KAAK,OAAO;EACjC,aAAa,IAAI,QAAQ,QAAQ,OAAO,CAAC;CAC3C;CACA,OAAO;AACT;;AAGA,MAAM,eAAqC,OAAO,OAAO,CAAC,CAAC"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
2
|
+
const require_relations_hasProperty = require("../relations/hasProperty.cjs");
|
|
3
3
|
const require_relations_ReactiveGetter = require("../relations/ReactiveGetter.cjs");
|
|
4
4
|
const require_relations_RecursiveCollection = require("../relations/RecursiveCollection.cjs");
|
|
5
|
-
const require_relations_hasProperty = require("../relations/hasProperty.cjs");
|
|
6
5
|
require("../relations/index.cjs");
|
|
7
6
|
const require_utils = require("../utils.cjs");
|
|
8
7
|
const require_getProperties = require("../getProperties.cjs");
|
|
8
|
+
const require_normalizeHints = require("../normalizeHints.cjs");
|
|
9
9
|
const require_EntityMetadata = require("../EntityMetadata.cjs");
|
|
10
10
|
const require_EntityManager = require("../EntityManager.cjs");
|
|
11
11
|
const require_Entity = require("../Entity.cjs");
|
|
@@ -238,7 +238,7 @@ function convertToLoadHint(meta, hint, allowCustomKeys = false) {
|
|
|
238
238
|
break;
|
|
239
239
|
case "primitive":
|
|
240
240
|
case "enum":
|
|
241
|
-
if (field.derived === "async") require_utils.mergeNormalizedHints(loadHint, { [key]: {} });
|
|
241
|
+
if (field.derived === "async" || field.kind === "primitive" && field.lazy) require_utils.mergeNormalizedHints(loadHint, { [key]: {} });
|
|
242
242
|
continue;
|
|
243
243
|
case "m2mEnum":
|
|
244
244
|
require_utils.mergeNormalizedHints(loadHint, { [key]: {} });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reactiveHints.cjs","names":["getMetadata","normalizeHint","suffixRe","getProperties","PropertyImpl","ReactiveGetterImpl","RecursiveParentsCollectionImpl","RecursiveChildrenCollectionImpl","RecursiveM2mCollectionImpl","getInstanceData","getEmInternalApi","flatAndUnique","isEntity","isId","getBaseAndSelfMetas","getBaseMeta"],"sources":["../../src/reactivity/reactiveHints.ts"],"sourcesContent":["import { getInstanceData } from \"src/BaseEntity.ts\";\nimport type { Changes } from \"src/changes.ts\";\nimport { type Entity, isEntity } from \"src/Entity.ts\";\nimport { type MaybeAbstractEntityConstructor, getEmInternalApi, isId } from \"src/EntityManager.ts\";\nimport {\n type EntityMetadata,\n type ManyToManyField,\n type ManyToOneField,\n type OneToManyField,\n type OneToOneField,\n type PolymorphicFieldComponent,\n getBaseAndSelfMetas,\n getBaseMeta,\n getMetadata,\n} from \"src/EntityMetadata.ts\";\nimport { getProperties } from \"src/getProperties.ts\";\nimport type { LoadHint, Loadable, Loaded } from \"src/loading/loadHints.ts\";\nimport { type NormalizeHint, type SuffixSeperator, normalizeHint, suffixRe } from \"src/normalizeHints.ts\";\nimport {\n type Collection,\n type EnumCollection,\n type LoadedCollection,\n type LoadedEnumCollection,\n type LoadedProperty,\n type LoadedReadOnlyCollection,\n type LoadedReference,\n type ManyToManyCollection,\n type OneToOneReference,\n type PolymorphicReference,\n type Property,\n PropertyImpl,\n type ReactiveGetter,\n type ReadOnlyCollection,\n type Reference,\n} from \"src/relations/index.ts\";\nimport type { LoadedOneToOneReference } from \"src/relations/OneToOneReference.ts\";\nimport { ReactiveGetterImpl } from \"src/relations/ReactiveGetter.ts\";\nimport {\n RecursiveChildrenCollectionImpl,\n RecursiveM2mCollectionImpl,\n RecursiveParentsCollectionImpl,\n} from \"src/relations/RecursiveCollection.ts\";\nimport type { FieldsOf, RelationsOf } from \"src/typeMap.ts\";\nimport { fail, flatAndUnique, mergeNormalizedHints } from \"src/utils.ts\";\n\n/** The keys in `T` that rules & hooks can react to. */\nexport type Reactable<T extends Entity> =\n // This will be primitives + enums + m2os\n FieldsOf<T> &\n // We include `Loadable` so that we include hasReactiveProperties,\n // which are reversable but won't be in any of our codegen types.\n Loadable<T> &\n Gettable<T> &\n SuffixedFieldsOf<T> &\n SuffixedLoadable<T> &\n SuffixedGettable<T>;\n\n/** Finds `hasReactiveGetters` which are not \"loadable\" b/c they're always loaded. */\nexport type Gettable<T extends Entity> = {\n -readonly [K in keyof T as GettableValue<T[K]> extends never ? never : K]: GettableValue<T[K]>;\n};\n\ntype GettableValue<V> = V extends ReactiveGetter<any, infer P> ? P : never;\n\n/** The fields of `T` suffixed with `:ro` or `_ro`. */\ntype SuffixedFieldsOf<T extends Entity> = {\n [K in keyof FieldsOf<T> & string as `${K}${SuffixSeperator}ro`]: FieldsOf<T>[K];\n};\n\ntype SuffixedLoadable<T extends Entity> = {\n [K in keyof Loadable<T> & string as `${K}${SuffixSeperator}ro`]: Loadable<T>[K];\n};\n\ntype SuffixedGettable<T extends Entity> = {\n [K in keyof Gettable<T> & string as `${K}${SuffixSeperator}ro`]: Gettable<T>[K];\n};\n\n/**\n * A reactive hint of a single key, multiple keys, or nested keys and sub-hints.\n *\n * Reactive hints are different from load hints in that reactive hints include specific\n * fields to react against, while load hints contain only relations, collections, and async\n * properties to preload.\n */\nexport type ReactiveHint<T extends Entity> =\n | (keyof Reactable<T> & string)\n | ReadonlyArray<keyof Reactable<T> & string>\n | NestedReactiveHint<T>;\n\nexport type NestedReactiveHint<T extends Entity> = {\n [K in keyof Reactable<T>]?: Reactable<T>[K] extends infer U extends Entity ? ReactiveHint<U> : {};\n};\n\n/** Given an entity `T` that is being reacted with hint `H`, mark only the `H` attributes visible & populated. */\nexport type Reacted<T extends Entity, H> = Entity & {\n [K in keyof NormalizeHint<H> & keyof T]: T[K] extends OneToOneReference<any, infer U>\n ? LoadedOneToOneReference<T, Entity & Reacted<U, NormalizeHint<H>[K]>>\n : // Add an explicit check for PolymorphicReference because it can add `PolymorphicReference &` which gives access to `idIfSet`\n // ...although maybe LoadedReference should just have `idIfSet` in it as well? LoadedOneToOneReference does...\n T[K] extends PolymorphicReference<any, infer U, infer N>\n ? PolymorphicReference<T, U, N> & LoadedReference<T, Entity & Reacted<U, NormalizeHint<H>[K]>, N>\n : T[K] extends Reference<any, infer U, infer N>\n ? LoadedReference<T, Entity & Reacted<U, NormalizeHint<H>[K]>, N>\n : T[K] extends EnumCollection<any, infer E>\n ? LoadedEnumCollection<T, E>\n : T[K] extends Collection<any, infer U>\n ? LoadedCollection<T, Entity & Reacted<U, NormalizeHint<H>[K]>>\n : T[K] extends ReadOnlyCollection<any, infer U>\n ? LoadedReadOnlyCollection<T, Entity & Reacted<U, NormalizeHint<H>[K]>>\n : T[K] extends Property<any, infer V>\n ? LoadedProperty<any, V>\n : T[K];\n} & {\n /**\n * Gives reactive rules & fields a way to get the full entity if they really need it.\n *\n * This should be used carefully b/c it will side-step the reactive system, and instead\n * reactive rules & fields should explicitly declare access to the fields they need, which\n * will then allow accessing the field via the `Reacted<Author, ...>` type.\n */\n fullNonReactiveAccess: Loaded<T, H>;\n /** Allow detecting if a reactive change is due to nuances like `hasUpdated` or `hasChanged`. */\n changes: Changes<T, keyof (FieldsOf<T> & RelationsOf<T>), keyof NormalizeHint<H>>;\n} & MaybeTransientFields<T>;\n\n/** Allow returning reacted entities from `hasReactiveReference`. */\nexport type MaybeReactedEntity<V> = V extends Entity ? { fullNonReactiveAccess: V } : V;\n\n/**\n * Allow returning reacted entities from `hasReactiveProperty`.\n *\n * The `MaybeReactedEntity` is used by `hasReactiveReference` which already has `undefined | never`\n * broken out as a separate generic; `hasReactiveProperty` does not, and so this type conditionally\n * pulls `undefined` out first, and then defers to `MaybeReactedEntity`.\n */\nexport type MaybeReactedPropertyEntity<V> = V extends (infer V1 extends Entity) | undefined\n ? MaybeReactedEntity<V1> | undefined\n : MaybeReactedEntity<V>;\n\n/**\n * A reactive hint that only allows fields immediately on the entity, i.e. no nested hints.\n *\n * This is used by `ReactiveGetter`s to guarantee that their lambda functions will\n * always be `.get`-able and hence not need loading.\n */\nexport type ShallowReactiveHint<T extends Entity> =\n | (keyof FieldsOf<T> & string)\n | ReadonlyArray<keyof FieldsOf<T> & string>;\n\n/** If the domain model has transient fields, allow reactive behavior to see it, i.e. don't run validation rules for special operations. */\nexport type MaybeTransientFields<T> = \"transientFields\" extends keyof T\n ? { transientFields: T[\"transientFields\"] }\n : {};\n\n/**\n * Takes a reactive hint and returns the `ReactiveTarget`s that, should they change, need to\n * walk back to this watching reactable.\n *\n * @param rootType The original type that contained the reactive rule/field, used for helpful error messages\n * @param entityType The current entity of the step we're walking\n * @param hint The current hint we're walking\n * @param reactForOtherSide the name of our FK column, if the previous collection needs to react to our FK changing\n * @param isFirst\n */\nexport function reverseReactiveHint<T extends Entity>(\n rootType: MaybeAbstractEntityConstructor<T>,\n entityType: MaybeAbstractEntityConstructor<T>,\n hint: ReactiveHint<T>,\n reactForOtherSide?: string,\n reactForOtherSideIsReadOnly?: \"field-read-only\" | \"hint-read-only\" | false,\n isFirst: boolean = true,\n): ReactiveTarget[] {\n const meta = getMetadata(entityType);\n // This is the list of fields for this `entityType` that we will react to their changing values\n const fields: string[] = [];\n const readOnlyFields: string[] = [];\n const maybeRecursive: ReactiveTarget[] = [];\n\n // If the hint before us was a collection, i.e. { books: [\"title\"] }, besides just reacting\n // to our `title` changing, `reactForOtherSide` tells us to react to our `author` field as well.\n if (reactForOtherSide) {\n const _fields = reactForOtherSideIsReadOnly ? readOnlyFields : fields;\n _fields.push(reactForOtherSide);\n if (meta.timestampFields?.deletedAt) {\n _fields.push(meta.timestampFields.deletedAt);\n }\n }\n\n // Look through the hint for our own fields, i.e. `[\"firstName\", \"lastName]`, and nested hints like `{ books: \"title\" }`.\n const subHintTargets: ReactiveTarget[] = Object.entries(normalizeHint(hint)).flatMap(([keyMaybeSuffix, subHint]) => {\n return reverseSubHint(\n rootType,\n entityType,\n hint,\n meta,\n // Pass fields & readOnlyFields, so `reverseSubHint` can append any of our primitive fields it finds\n fields,\n readOnlyFields,\n maybeRecursive,\n keyMaybeSuffix, // maybeSuffix = might end with `_ro` like `name_ro`\n subHint,\n );\n });\n\n const myTargets: ReactiveTarget[] = [];\n // If any of our primitives (or m2o fields) change, establish a reactive path from \"here\" (entityType)\n // that is initially empty (path: []) but will have paths layered on by upstream callers\n if (fields.length > 0 || isFirst || reactForOtherSideIsReadOnly === \"field-read-only\") {\n myTargets.push({ kind: \"update\", entity: entityType, fields, path: [] });\n }\n // Normally read-only fields don't trigger true async reactivity, but IsLoadedCache\n // needs them for same-EM cache invalidation.\n if (readOnlyFields.length > 0) {\n myTargets.push({ kind: \"read-only\", entity: entityType, fields: readOnlyFields, path: [] });\n }\n\n return [...myTargets, ...maybeRecursive, ...subHintTargets];\n}\n\nfunction reverseSubHint(\n rootType: MaybeAbstractEntityConstructor<any>,\n entityType: MaybeAbstractEntityConstructor<any>,\n hint: any,\n meta: EntityMetadata,\n fields: string[],\n readOnlyFields: string[],\n maybeRecursive: ReactiveTarget[],\n keyMaybeSuffix: string,\n subHint: any,\n): ReactiveTarget[] {\n const key = keyMaybeSuffix.replace(suffixRe, \"\");\n const field = meta.allFields[key];\n const isReadOnly = !!keyMaybeSuffix.match(suffixRe) || (field && field.immutable);\n const _fields = isReadOnly ? readOnlyFields : fields;\n if (field) {\n switch (field.kind) {\n case \"m2o\": {\n // If this is a ReactiveReference, maybe we do something different?\n _fields.push(field.fieldName);\n const otherMeta = field.otherMetadata();\n // If this is a ReactiveReference, it won't have an \"other\" side, which is fine if:\n // - we're only reacting to the RR value itself (and have no sub-hints), or\n // - all of our sub-hints are read-only\n const otherField = otherMeta.allFields[field.otherFieldName];\n if (!otherField && !isAllReadOnly(subHint)) {\n throw new Error(\n `Invalid hint in ${rootType.name}.ts, we don't currently support reacting through ReactiveReferences, ${JSON.stringify(hint)}`,\n );\n }\n return reverseReactiveHint(rootType, otherMeta.cstr, subHint, undefined, false, false).map(\n appendPath(maybeAddTypeFilterSuffix(meta, field)),\n );\n }\n case \"poly\": {\n _fields.push(field.fieldName);\n // A poly is basically multiple m2os glued together, so copy/paste the `case m2o` code\n // above but do it for each component FK, and glue them together.\n return field.components.flatMap((comp) => {\n return reverseReactiveHint(rootType, comp.otherMetadata().cstr, subHint, undefined, false, false).map(\n appendPath(maybeAddTypeFilterSuffix(meta, comp)),\n );\n });\n }\n case \"m2m\": {\n // While o2m and o2o can watch for just FK changes by passing `reactForOtherSide` (the FK lives in the other\n // table), for m2m reactivity we push the collection name into the reactive hint, because it's effectively\n // \"the other/reverse side\", and JoinRows will trigger `queueDownstreamReactables` explicitly, instead of\n // `setField` which handles regular/non-m2m mutations.\n _fields.push(field.fieldName);\n const otherFieldName = maybeAddTypeFilterSuffix(meta, field);\n return reverseReactiveHint(\n rootType,\n field.otherMetadata().cstr,\n subHint,\n // For m2m, we can always pass undefined here, as otherwise having the opposite m2m collection\n // recalc all of its children will cause over-reactivity\n undefined,\n false,\n false,\n ).map(appendPath(otherFieldName));\n }\n case \"o2m\":\n case \"o2o\": {\n const isOtherReadOnly = field.otherMetadata().allFields[field.otherFieldName].immutable;\n const otherFieldName = maybeAddTypeFilterSuffix(meta, field);\n // This is not a field, but we want our reverse side to be reactive, so pass reactForOtherSide\n return reverseReactiveHint(\n rootType,\n field.otherMetadata().cstr,\n subHint,\n // IsLoadedCache invalidation always needs to know the otherField name, so we must\n // end up with either a kind=update or kind=read-only target with `otherFieldName` in it.\n field.otherFieldName,\n // We can have three outcomes:\n // - kind=update fields=[otherFieldName] (normal behavior)\n // - kind=update fields=[] + kind=read-only fields=[otherFieldName] (isOtherReadOnly)\n // - kind=read-only fields=[otherFieldName] (hint isReadOnly)\n isReadOnly ? \"hint-read-only\" : isOtherReadOnly ? \"field-read-only\" : false,\n false,\n ).map(appendPath(otherFieldName));\n }\n case \"primaryKey\":\n case \"primitive\":\n case \"enum\":\n _fields.push(key);\n return [];\n case \"m2mEnum\":\n // An enum m2m is a leaf of enum codes (no \"other side\" entity to recurse into); like a m2m,\n // `JoinRows` calls `queueDownstreamReactables` explicitly when its membership changes.\n _fields.push(key);\n return [];\n default:\n throw new Error(`Invalid hint in ${rootType.name}.ts hint ${JSON.stringify(hint)}`);\n }\n } else {\n // We only need to look for ReactiveProperties here, because ReactiveFields & ReactiveReferences\n // have underlying primitive fields that, when/if they change, will be handled in the ^ code.\n //\n // I.e. we specifically don't need to handle RFs & RRs ^, because the EntityManager.flush loop will\n // notice their primitive values changing, and kicking off any downstream reactive fields as necessary.\n const p = getProperties(meta)[key];\n if (p instanceof PropertyImpl) {\n // If the field is marked as readonly (i.e. using the `_ro` suffix), then we can assume that applies to its\n // entire hint as well and can simply omit it. This also allows us to use non-reactive async props as long as\n // they are readonly.\n if (isReadOnly) return [];\n if (!p.reactiveHint) {\n throw new Error(\n `Property ${key} cannot be used in reactive hints in ${rootType.name}.ts hint ${JSON.stringify(\n hint,\n )}, please use hasReactiveProperty instead`,\n );\n }\n return reverseReactiveHint(rootType, meta.cstr, p.reactiveHint, undefined, false, false);\n } else if (p instanceof ReactiveGetterImpl) {\n // If the field is marked as readonly, then we can assume that applies to its entire hint as well and can\n // simply omit it.\n if (isReadOnly) return [];\n return reverseReactiveHint(rootType, meta.cstr, p.reactiveHint, undefined, false, false);\n } else if (p instanceof RecursiveParentsCollectionImpl) {\n // I.e. if this is `Author.mentorsRecursive`:\n // - fieldName=mentorsRecursive (being watched by the caller)\n // - otherFieldName=menteesRecursive (our reversal for walking down)\n // - m2oFieldName = mentor\n //\n // Note that \"reversing parents\" is generally easier, b/c when the tree changes, any given node can\n // still \"walk down\" to find its children w/the \"current value\" childrenRecursive; and any just-now\n // orphaned parents will themselves trigger their own childrenRecursive changes (i.e. we do not need\n // any \"old reference value\" / \"new reference value\" handling).\n //\n // I.e. given a tree of `A is mentor of B is mentor of C`, when B changes mentors, and we need to\n // \"walk to all affected/children mentees, so they can observe their new mentorsRecursive\", merely\n // walking down from (b itself & it's mentees) will find all affected mentees.\n const { otherFieldName, m2oFieldName } = p;\n // When a mentor.firstName/etc field changes, walk to its transitive children (through otherFieldName)\n // via the `return reverseReactiveHint` line below.\n //\n // But if the `author.mentor` field itself changes, we ourselves are also \"a new mentee\" of the mentor, so\n // do `fields.push(m2oFieldName)` to notify our direct reactable (a new mentee), and then our transitive\n // children/mentees. This avoids going \"up to the mentor, and walking down all of his mentees\", which would go\n // through our siblings, which would not have changed/need reactivity.\n _fields.push(m2oFieldName);\n maybeRecursive.push({\n kind: isReadOnly ? \"read-only\" : \"update\",\n entity: entityType,\n fields: recursiveMembershipFields(meta, m2oFieldName),\n path: [otherFieldName],\n });\n // Any reactables who were watching `parentsRecursive`, we'll back down to by going through `childrenRecursive`\n return reverseReactiveHint(rootType, entityType, subHint, undefined, false).map(appendPath(otherFieldName));\n } else if (p instanceof RecursiveChildrenCollectionImpl) {\n const { otherFieldName, o2mFieldName } = p;\n // I.e. this is `Author.menteesRecursive`\n // - fieldName=menteesRecursive` (being watched by the caller)\n // - otherFieldName=mentorsRecursive` (our reversal for walking up)\n // - The o2m's otherFieldName gives us the FK field (e.g., \"mentor\") on the child entity.\n //\n // Note that \"reversing children\" is trickier than reversing parents, b/c when the tree changes,\n // we need to consider both \"old reference value\" and \"new reference value\" scenarios.\n const o2mField = meta.allFields[o2mFieldName] as OneToManyField | OneToOneField;\n const m2oFieldName = o2mField.otherFieldName;\n _fields.push(m2oFieldName);\n maybeRecursive.push({\n kind: isReadOnly ? \"read-only\" : \"update\",\n entity: entityType,\n fields: recursiveMembershipFields(meta, m2oFieldName), // i.e. parent\n path: [otherFieldName], // i.e. parentsRecursive to tell them \"observe your new childrenRecursive\"\n });\n // Any reactables who were watching `childrenRecursive`, we'll up to by going through `parentsRecursive`\n return reverseReactiveHint(rootType, entityType, subHint, undefined, false).map(appendPath(otherFieldName));\n } else if (p instanceof RecursiveM2mCollectionImpl) {\n // I.e. if this is `User.parentsRecursive`:\n // - fieldName=parentsRecursive (being watched by the caller)\n // - otherFieldName=childrenRecursive (our reversal for walking down)\n // - m2mFieldName=parents (the underlying m2m collection)\n //\n // For m2m recursive, the \"change signal\" is when join rows are added/removed, so we push\n // the m2m collection name into fields (like regular m2m handling). To find affected entities\n // that are watching `parentsRecursive`, we walk through `childrenRecursive` (the reverse direction).\n const { otherFieldName, m2mFieldName } = p;\n // When a join row changes (e.g. parents collection is mutated), notify both this entity\n // and its transitive children/parents via otherFieldName\n _fields.push(m2mFieldName);\n maybeRecursive.push({\n kind: isReadOnly ? \"read-only\" : \"update\",\n entity: entityType,\n fields: recursiveMembershipFields(meta, m2mFieldName),\n path: [otherFieldName],\n });\n // Any reactables who were watching this recursive collection, we'll reach via the other direction\n return reverseReactiveHint(rootType, entityType, subHint, undefined, false).map(appendPath(otherFieldName));\n } else {\n throw new Error(`Invalid hint in ${rootType.name}.ts ${JSON.stringify(hint)}`);\n }\n }\n}\n\nfunction maybeAddTypeFilterSuffix(\n meta: EntityMetadata,\n field: ManyToOneField | OneToManyField | ManyToManyField | OneToOneField | PolymorphicFieldComponent,\n): string {\n const otherField = field.otherMetadata().allFields[field.otherFieldName];\n if (!otherField) {\n // This is usually an error, unless if its ReactiveReference which we don't create o2m-s for\n const isReactiveReference = \"kind\" in field && field.kind === \"m2o\" && field.derived === \"async\";\n if (!isReactiveReference) fail(`No field ${field.otherMetadata().type}.${field.otherFieldName}`);\n return field.otherFieldName;\n }\n // If we're Foo, and the other field (which we'll traverse back from at runtime) is actually\n // a poly FK pointing back to multiple `owner=Foo | Bar | Zaz`, add a suffix of `@Foo` so\n // that any runtime traversal that hit `owner` and see a `Bar | Zaz` will stop.\n const nextFieldIsPoly = otherField.kind === \"poly\";\n // If we're a SubType, and the other field points to our base type, assume there might be\n // SubType-specific fields in the path so far, so tell the other side to only walk back through\n // us if its value is actually a SubType.\n const nextFieldPointsToBaseType =\n !!meta.baseType && otherField.kind !== \"poly\" && (otherField as any).otherMetadata().cstr !== meta.cstr;\n return nextFieldIsPoly || nextFieldPointsToBaseType ? `${field.otherFieldName}@${meta.type}` : field.otherFieldName;\n}\n\n/**\n * Walks `reverseHint` for every entity in `entities`.\n *\n * I.e. given `[book1, book2]` and `[\"author\", 'publisher\"]`, will return all the books' authors' publishers.\n *\n * Note that for references (and only references), we walk both through \"the current value\"\n * and prior values remembered in `referenceHistory`. This is fundamentally because references are the true \"owner\"\n * of the relation, while collections are derived.\n *\n * For example, with reversals walking through collections:\n *\n * - Given a hint `book: { author: firstName }`\n * - Which reverses to `author[firstName] -> books`\n * - When we walk through `a1.books`, we use only the current value, which might have only `[b2]` in it.\n * - We don't need to worry about \"the old a1.books\" value, which might have previously had `[b1]` in it,\n * because the `b1.author` changing authors (from `a1` to `a2`) will trigger its own reactivity.\n *\n * Contrast with reversals walking through references:\n *\n * - Given a hint `author: { books: \"title\" }`\n * - Which reverses to `book[title] -> author`\n * - When we traverse through `b1.author`, we use both the current value and remembered values, so that both\n * \"our prior authors\" and \"our new author\" see their latest `author.books` collection values.\n *\n * Delete cleanup runs before reverse hints are walked, so the current relation value may already be unset by the\n * time we get here. For persisted entities, `cleanupOnEntityDeleted` clears the owning FK via `setField`, which\n * remembers the prior FK in `referenceHistory`; this lets us still find the old owner. For collection reversals\n * this is enough because collections are derived from the owning FK: removing `b1` from `a1.books` is represented\n * by `b1.author` changing, not by an independent \"old books\" snapshot.\n *\n * References can also have transient values during a unit of work, i.e. a ReactiveField might observe `b1.author=a2`\n * before the final flush changes it to `a3`. `setField` remembers both original and transient reference values\n * separately from `originalData`, and we walk them in addition to the current value.\n */\nexport async function followReverseHint(\n reactionName: string,\n entities: Entity[],\n reverseHint: string[],\n): Promise<Entity[]> {\n // Start at the current (unique) entities\n let current = new Set(entities);\n const paths = [...reverseHint];\n // And \"walk backwards\" through the reverse hint\n while (paths.length) {\n const path = paths.shift()!;\n const [fieldName, viaType] = path.split(\"@\");\n const promises = new Array(current.size);\n // The path might touch either a reference or a collection\n for (const c of current as Set<any>) {\n const relation = c[fieldName];\n // If `!relation`, I'd really like to `fail`, but atm we're knowingly creating invalid reversals for when\n // a ReactiveField depends on a ReactiveReference; b/c RRs lack an \"other side\", we cannot walk them in\n // reverse, and so for awhile we \"supported\" the RF -> RR pattern by relying on a totally-read-only reactive\n // hint -> dropped during reversal.\n //\n // However, now IsLoadedCache means that even these \"totally read only\" RFs need to be reactive at least\n // during their initial EM creation, to avoid stale values being observed during first-flush mutations,\n // and so we're now stuck with these \"invalid RR reversals\" until we can figure out a better way to handle them.\n // (Now that IsLoadedCache doesn't technically walk paths, it just invalidates given entity+field pairs, so\n // it is not bothered by an invalid/missing RR reversal.)\n if (!relation) continue;\n // fail(`Attempting to react for ${reactionName} for ${c.toString()}, but there is no \"reverse walkable\" field ${c.constructor.name}.${fieldName}`,);\n const currentValuePromise = maybeApplyTypeFilter(relation.load(), viaType);\n // Always wait for the relation itself\n promises.push(currentValuePromise);\n // If we're going from Book.author back to Author to re-validate the Author.books collection,\n // see if Book.author has changed, so we can re-validate both the old author's books and the\n // new author's books.\n const fieldKind = getMetadata(c).allFields[fieldName]?.kind;\n const isReference = fieldKind === \"m2o\" || fieldKind === \"poly\";\n const isManyToMany = fieldKind === \"m2m\";\n // See jsdoc comment about why this is only necessary for references...\n if (isReference) {\n for (const value of getInstanceData(c).getReferenceHistory(fieldName)) {\n promises.push(maybeApplyTypeFilter(loadReferenceHistoryValue(c, value), viaType));\n }\n }\n if (isManyToMany) {\n const m2m = c[fieldName] as ManyToManyCollection<any, any>;\n const joinRows = getEmInternalApi(m2m.entity.em).joinRows(m2m);\n // Return a tuple of [currentRows, removedRows]\n promises.push(joinRows.removedFor(m2m, c));\n }\n // Walk up the old parents\n if (relation instanceof RecursiveParentsCollectionImpl) {\n const { m2oFieldName } = relation;\n for (const value of getInstanceData(c).getReferenceHistory(m2oFieldName)) {\n promises.push(\n // First load the old parent itself\n loadReferenceHistoryValue(c, value).then((oldParent) => {\n if (!oldParent) return undefined;\n // And then resolve its fieldName=parentsRecursive\n const oldParentRecursiveParents = (oldParent as any)[fieldName];\n return oldParentRecursiveParents.load().then((parents: any) => {\n return [oldParent, ...parents];\n });\n }),\n );\n }\n }\n }\n const nextLevel = await Promise.all(promises);\n // Make a new set so that we dedupe as we go\n current = flatAndUnique(nextLevel);\n }\n return [...current];\n}\n\n/** Converts a normalized reactive `hint` into a load hint. */\nexport function convertToLoadHint<T extends Entity>(\n meta: EntityMetadata,\n hint: ReactiveHint<T>,\n allowCustomKeys = false,\n): LoadHint<T> {\n const loadHint = {};\n // Process the hints individually instead of just calling Object.fromEntries so that\n // we can handle inlined reactive hints that overlap.\n for (const [keyMaybeSuffix, subHint] of Object.entries(normalizeHint(hint))) {\n let key = keyMaybeSuffix.replace(suffixRe, \"\");\n let field = meta.allFields[key];\n\n // If `allowCustomKeys` is enabled, we're probably doing `toJSON`,\n // so look for `companyId` & `companyIds` to turn into `company`\n if (!field && allowCustomKeys) {\n const realField = Object.values(meta.allFields).find((f) => f.fieldIdName === key);\n if (realField) {\n field = realField;\n key = realField.fieldName;\n }\n }\n\n if (field) {\n switch (field.kind) {\n case \"m2m\":\n case \"m2o\":\n case \"o2m\":\n case \"o2o\":\n mergeNormalizedHints(loadHint, { [key]: convertToLoadHint(field.otherMetadata(), subHint, allowCustomKeys) });\n break;\n case \"poly\":\n for (const comp of field.components) {\n // Write `{ parent: { child@Type: hint } }` into the load hint\n mergeNormalizedHints(loadHint, {\n [key]: Object.fromEntries(\n Object.entries(convertToLoadHint(comp.otherMetadata(), subHint, allowCustomKeys)).map(\n // Map the subHint keys to add in `@Type`\n ([subKey, subHint]) => [`${subKey}@${comp.otherMetadata().type}`, subHint],\n ),\n ),\n });\n }\n break;\n case \"primitive\":\n case \"enum\":\n if (field.derived === \"async\") {\n mergeNormalizedHints(loadHint, { [key]: {} });\n }\n continue;\n case \"m2mEnum\":\n // The enum codes are a leaf, but the collection itself must be loaded for the reaction to read it.\n mergeNormalizedHints(loadHint, { [key]: {} });\n break;\n case \"primaryKey\":\n continue;\n default:\n throw new Error(`Invalid reactive hint ${meta.tableName} ${JSON.stringify(hint)}`);\n }\n } else {\n const p = getProperties(meta)[key];\n if (p instanceof RecursiveParentsCollectionImpl) {\n mergeNormalizedHints(loadHint, { [p.fieldName]: convertToLoadHint(meta, subHint, allowCustomKeys) });\n } else if (p instanceof RecursiveChildrenCollectionImpl) {\n mergeNormalizedHints(loadHint, { [p.fieldName]: convertToLoadHint(meta, subHint, allowCustomKeys) });\n } else if (p instanceof RecursiveM2mCollectionImpl) {\n mergeNormalizedHints(loadHint, { [p.fieldName]: convertToLoadHint(meta, subHint, allowCustomKeys) });\n } else if (p && p.reactiveHint) {\n mergeNormalizedHints(loadHint, convertToLoadHint(meta, p.reactiveHint, allowCustomKeys));\n } else if (!allowCustomKeys) {\n fail(`Invalid reactive hint on ${meta.tableName} ${JSON.stringify(hint)}`);\n }\n }\n }\n return loadHint as any;\n}\n\n/** An entity that, when `fields` change, should trigger the reactive rule/field pointed to by `path`. */\nexport interface ReactiveTarget {\n kind: \"read-only\" | \"update\";\n /** The entity that contains a field our reactive rule/field accesses. */\n entity: MaybeAbstractEntityConstructor<any>;\n /** The field(s) that our reactive rule/field accesses, plus any implicit fields like FKs. */\n fields: string[];\n /** The path from this `entity` back to the source reactive rule/field. */\n path: string[];\n}\n\nfunction maybeApplyTypeFilter(loadPromise: Promise<Entity | Entity[] | undefined>, viaType: string | undefined) {\n if (viaType) {\n return loadPromise.then((loaded) => {\n if (Array.isArray(loaded)) {\n return loaded.filter((e) => isTypeOrSubType(e, viaType));\n } else if (loaded && isTypeOrSubType(loaded, viaType)) {\n return loaded;\n } else {\n return undefined;\n }\n });\n }\n return loadPromise;\n}\n\n/** Loads a remembered reference value so reverse-hint walking can revisit it. */\nfunction loadReferenceHistoryValue(entity: Entity, value: any): Promise<Entity | undefined> {\n if (isEntity(value)) return Promise.resolve(value);\n if (isId(value)) return entity.em.load(value) as Promise<Entity>;\n return Promise.resolve(undefined);\n}\n\n/** Returns true if `entity` is exactly `typeName` or a subtype of `typeName`.\n *\n * Used to honor poly/type filters like `@Publisher` during reverse-hint walks:\n * a `SmallPublisher` entity should pass an `@Publisher` filter because it is\n * a Publisher via inheritance.\n */\nexport function isTypeOrSubType(entity: Entity, typeName: string): boolean {\n const meta = getMetadata(entity);\n return meta.type === typeName || meta.baseTypes.some((b) => b.type === typeName);\n}\n\nexport function isPolyHint(key: string): boolean {\n return key.includes(\"@\");\n}\n\nexport function splitPolyHintToKeyAndType(key: string): [string, string] {\n const [k, type] = key.split(\"@\");\n return [k, type];\n}\n\n/** If this hint is a poly like `author@Book`, return the type, otherwise return undefined. */\nexport function getRelationFromMaybePolyKey(entity: Entity, key: string): any {\n if (isPolyHint(key)) {\n const [realKey, typeName] = splitPolyHintToKeyAndType(key);\n // Even though `entity[realKey]` might exist, if it's from a different poly type, we don't want it\n const isApplicable = getBaseAndSelfMetas(getMetadata(entity)).some((meta) => meta.type === typeName);\n if (!isApplicable) return undefined;\n return (entity as any)[realKey];\n } else {\n return (entity as any)[key];\n }\n}\n\n/**\n * For an `author -> books -> bookReviews` hint, appends `BookReview.book`, `Book.author`,\n * i.e. the \"other side\" relation, to the reversed path on the way \"out\" from a mutated BookReview\n * to the target `Author` RF/RR/rule.\n */\nfunction appendPath(reverseField: string): (rt: ReactiveTarget) => ReactiveTarget {\n return (rt) => {\n // We can mutate this array b/c it's only owned by a single/continually-updated `ReactiveTarget`\n rt.path = [...rt.path, reverseField];\n return rt;\n };\n}\n\n/** Recursive membership changes on soft deletion or resurrection, even when the underlying relation is unchanged. */\nfunction recursiveMembershipFields(meta: EntityMetadata, fieldName: string): string[] {\n const deletedAt = getBaseMeta(meta).timestampFields?.deletedAt;\n return deletedAt ? [fieldName, deletedAt] : [fieldName];\n}\n\nfunction isAllReadOnly(hint: any): boolean {\n for (const [key, subHint] of Object.entries(normalizeHint(hint))) {\n const okay = (key.endsWith(\"_ro\") || key.endsWith(\":ro\")) && isAllReadOnly(subHint);\n if (!okay) return false;\n }\n return true;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"reactiveHints.cjs","names":["getMetadata","normalizeHint","suffixRe","getProperties","PropertyImpl","ReactiveGetterImpl","RecursiveParentsCollectionImpl","RecursiveChildrenCollectionImpl","RecursiveM2mCollectionImpl","getInstanceData","getEmInternalApi","flatAndUnique","isEntity","isId","getBaseAndSelfMetas","getBaseMeta"],"sources":["../../src/reactivity/reactiveHints.ts"],"sourcesContent":["import { getInstanceData } from \"src/BaseEntity.ts\";\nimport type { Changes } from \"src/changes.ts\";\nimport { type Entity, isEntity } from \"src/Entity.ts\";\nimport { type MaybeAbstractEntityConstructor, getEmInternalApi, isId } from \"src/EntityManager.ts\";\nimport {\n type EntityMetadata,\n type ManyToManyField,\n type ManyToOneField,\n type OneToManyField,\n type OneToOneField,\n type PolymorphicFieldComponent,\n getBaseAndSelfMetas,\n getBaseMeta,\n getMetadata,\n} from \"src/EntityMetadata.ts\";\nimport { getProperties } from \"src/getProperties.ts\";\nimport type { LoadHint, Loadable, Loaded } from \"src/loading/loadHints.ts\";\nimport { type NormalizeHint, type SuffixSeperator, normalizeHint, suffixRe } from \"src/normalizeHints.ts\";\nimport {\n type Collection,\n type EnumCollection,\n type LoadedCollection,\n type LoadedEnumCollection,\n type LoadedProperty,\n type LoadedReadOnlyCollection,\n type LoadedReference,\n type ManyToManyCollection,\n type OneToOneReference,\n type PolymorphicReference,\n type Property,\n PropertyImpl,\n type ReactiveGetter,\n type ReadOnlyCollection,\n type Reference,\n} from \"src/relations/index.ts\";\nimport type { LoadedOneToOneReference } from \"src/relations/OneToOneReference.ts\";\nimport { ReactiveGetterImpl } from \"src/relations/ReactiveGetter.ts\";\nimport {\n RecursiveChildrenCollectionImpl,\n RecursiveM2mCollectionImpl,\n RecursiveParentsCollectionImpl,\n} from \"src/relations/RecursiveCollection.ts\";\nimport type { FieldsOf, RelationsOf } from \"src/typeMap.ts\";\nimport { fail, flatAndUnique, mergeNormalizedHints } from \"src/utils.ts\";\n\n/** The keys in `T` that rules & hooks can react to. */\nexport type Reactable<T extends Entity> =\n // This will be primitives + enums + m2os\n FieldsOf<T> &\n // We include `Loadable` so that we include hasReactiveProperties,\n // which are reversable but won't be in any of our codegen types.\n Loadable<T> &\n Gettable<T> &\n SuffixedFieldsOf<T> &\n SuffixedLoadable<T> &\n SuffixedGettable<T>;\n\n/** Finds `hasReactiveGetters` which are not \"loadable\" b/c they're always loaded. */\nexport type Gettable<T extends Entity> = {\n -readonly [K in keyof T as GettableValue<T[K]> extends never ? never : K]: GettableValue<T[K]>;\n};\n\ntype GettableValue<V> = V extends ReactiveGetter<any, infer P> ? P : never;\n\n/** The fields of `T` suffixed with `:ro` or `_ro`. */\ntype SuffixedFieldsOf<T extends Entity> = {\n [K in keyof FieldsOf<T> & string as `${K}${SuffixSeperator}ro`]: FieldsOf<T>[K];\n};\n\ntype SuffixedLoadable<T extends Entity> = {\n [K in keyof Loadable<T> & string as `${K}${SuffixSeperator}ro`]: Loadable<T>[K];\n};\n\ntype SuffixedGettable<T extends Entity> = {\n [K in keyof Gettable<T> & string as `${K}${SuffixSeperator}ro`]: Gettable<T>[K];\n};\n\n/**\n * A reactive hint of a single key, multiple keys, or nested keys and sub-hints.\n *\n * Reactive hints are different from load hints in that reactive hints include specific\n * fields to react against, while load hints contain only relations, collections, and async\n * properties to preload.\n */\nexport type ReactiveHint<T extends Entity> =\n | (keyof Reactable<T> & string)\n | ReadonlyArray<keyof Reactable<T> & string>\n | NestedReactiveHint<T>;\n\nexport type NestedReactiveHint<T extends Entity> = {\n [K in keyof Reactable<T>]?: Reactable<T>[K] extends infer U extends Entity ? ReactiveHint<U> : {};\n};\n\n/** Given an entity `T` that is being reacted with hint `H`, mark only the `H` attributes visible & populated. */\nexport type Reacted<T extends Entity, H> = Entity & {\n [K in keyof NormalizeHint<H> & keyof T]: T[K] extends OneToOneReference<any, infer U>\n ? LoadedOneToOneReference<T, Entity & Reacted<U, NormalizeHint<H>[K]>>\n : // Add an explicit check for PolymorphicReference because it can add `PolymorphicReference &` which gives access to `idIfSet`\n // ...although maybe LoadedReference should just have `idIfSet` in it as well? LoadedOneToOneReference does...\n T[K] extends PolymorphicReference<any, infer U, infer N>\n ? PolymorphicReference<T, U, N> & LoadedReference<T, Entity & Reacted<U, NormalizeHint<H>[K]>, N>\n : T[K] extends Reference<any, infer U, infer N>\n ? LoadedReference<T, Entity & Reacted<U, NormalizeHint<H>[K]>, N>\n : T[K] extends EnumCollection<any, infer E>\n ? LoadedEnumCollection<T, E>\n : T[K] extends Collection<any, infer U>\n ? LoadedCollection<T, Entity & Reacted<U, NormalizeHint<H>[K]>>\n : T[K] extends ReadOnlyCollection<any, infer U>\n ? LoadedReadOnlyCollection<T, Entity & Reacted<U, NormalizeHint<H>[K]>>\n : T[K] extends Property<any, infer V>\n ? LoadedProperty<any, V>\n : T[K];\n} & {\n /**\n * Gives reactive rules & fields a way to get the full entity if they really need it.\n *\n * This should be used carefully b/c it will side-step the reactive system, and instead\n * reactive rules & fields should explicitly declare access to the fields they need, which\n * will then allow accessing the field via the `Reacted<Author, ...>` type.\n */\n fullNonReactiveAccess: Loaded<T, H>;\n /** Allow detecting if a reactive change is due to nuances like `hasUpdated` or `hasChanged`. */\n changes: Changes<T, keyof (FieldsOf<T> & RelationsOf<T>), keyof NormalizeHint<H>>;\n} & MaybeTransientFields<T>;\n\n/** Allow returning reacted entities from `hasReactiveReference`. */\nexport type MaybeReactedEntity<V> = V extends Entity ? { fullNonReactiveAccess: V } : V;\n\n/**\n * Allow returning reacted entities from `hasReactiveProperty`.\n *\n * The `MaybeReactedEntity` is used by `hasReactiveReference` which already has `undefined | never`\n * broken out as a separate generic; `hasReactiveProperty` does not, and so this type conditionally\n * pulls `undefined` out first, and then defers to `MaybeReactedEntity`.\n */\nexport type MaybeReactedPropertyEntity<V> = V extends (infer V1 extends Entity) | undefined\n ? MaybeReactedEntity<V1> | undefined\n : MaybeReactedEntity<V>;\n\n/**\n * A reactive hint that only allows fields immediately on the entity, i.e. no nested hints.\n *\n * This is used by `ReactiveGetter`s to guarantee that their lambda functions will\n * always be `.get`-able and hence not need loading.\n */\nexport type ShallowReactiveHint<T extends Entity> =\n | (keyof FieldsOf<T> & string)\n | ReadonlyArray<keyof FieldsOf<T> & string>;\n\n/** If the domain model has transient fields, allow reactive behavior to see it, i.e. don't run validation rules for special operations. */\nexport type MaybeTransientFields<T> = \"transientFields\" extends keyof T\n ? { transientFields: T[\"transientFields\"] }\n : {};\n\n/**\n * Takes a reactive hint and returns the `ReactiveTarget`s that, should they change, need to\n * walk back to this watching reactable.\n *\n * @param rootType The original type that contained the reactive rule/field, used for helpful error messages\n * @param entityType The current entity of the step we're walking\n * @param hint The current hint we're walking\n * @param reactForOtherSide the name of our FK column, if the previous collection needs to react to our FK changing\n * @param isFirst\n */\nexport function reverseReactiveHint<T extends Entity>(\n rootType: MaybeAbstractEntityConstructor<T>,\n entityType: MaybeAbstractEntityConstructor<T>,\n hint: ReactiveHint<T>,\n reactForOtherSide?: string,\n reactForOtherSideIsReadOnly?: \"field-read-only\" | \"hint-read-only\" | false,\n isFirst: boolean = true,\n): ReactiveTarget[] {\n const meta = getMetadata(entityType);\n // This is the list of fields for this `entityType` that we will react to their changing values\n const fields: string[] = [];\n const readOnlyFields: string[] = [];\n const maybeRecursive: ReactiveTarget[] = [];\n\n // If the hint before us was a collection, i.e. { books: [\"title\"] }, besides just reacting\n // to our `title` changing, `reactForOtherSide` tells us to react to our `author` field as well.\n if (reactForOtherSide) {\n const _fields = reactForOtherSideIsReadOnly ? readOnlyFields : fields;\n _fields.push(reactForOtherSide);\n if (meta.timestampFields?.deletedAt) {\n _fields.push(meta.timestampFields.deletedAt);\n }\n }\n\n // Look through the hint for our own fields, i.e. `[\"firstName\", \"lastName]`, and nested hints like `{ books: \"title\" }`.\n const subHintTargets: ReactiveTarget[] = Object.entries(normalizeHint(hint)).flatMap(([keyMaybeSuffix, subHint]) => {\n return reverseSubHint(\n rootType,\n entityType,\n hint,\n meta,\n // Pass fields & readOnlyFields, so `reverseSubHint` can append any of our primitive fields it finds\n fields,\n readOnlyFields,\n maybeRecursive,\n keyMaybeSuffix, // maybeSuffix = might end with `_ro` like `name_ro`\n subHint,\n );\n });\n\n const myTargets: ReactiveTarget[] = [];\n // If any of our primitives (or m2o fields) change, establish a reactive path from \"here\" (entityType)\n // that is initially empty (path: []) but will have paths layered on by upstream callers\n if (fields.length > 0 || isFirst || reactForOtherSideIsReadOnly === \"field-read-only\") {\n myTargets.push({ kind: \"update\", entity: entityType, fields, path: [] });\n }\n // Normally read-only fields don't trigger true async reactivity, but IsLoadedCache\n // needs them for same-EM cache invalidation.\n if (readOnlyFields.length > 0) {\n myTargets.push({ kind: \"read-only\", entity: entityType, fields: readOnlyFields, path: [] });\n }\n\n return [...myTargets, ...maybeRecursive, ...subHintTargets];\n}\n\nfunction reverseSubHint(\n rootType: MaybeAbstractEntityConstructor<any>,\n entityType: MaybeAbstractEntityConstructor<any>,\n hint: any,\n meta: EntityMetadata,\n fields: string[],\n readOnlyFields: string[],\n maybeRecursive: ReactiveTarget[],\n keyMaybeSuffix: string,\n subHint: any,\n): ReactiveTarget[] {\n const key = keyMaybeSuffix.replace(suffixRe, \"\");\n const field = meta.allFields[key];\n const isReadOnly = !!keyMaybeSuffix.match(suffixRe) || (field && field.immutable);\n const _fields = isReadOnly ? readOnlyFields : fields;\n if (field) {\n switch (field.kind) {\n case \"m2o\": {\n // If this is a ReactiveReference, maybe we do something different?\n _fields.push(field.fieldName);\n const otherMeta = field.otherMetadata();\n // If this is a ReactiveReference, it won't have an \"other\" side, which is fine if:\n // - we're only reacting to the RR value itself (and have no sub-hints), or\n // - all of our sub-hints are read-only\n const otherField = otherMeta.allFields[field.otherFieldName];\n if (!otherField && !isAllReadOnly(subHint)) {\n throw new Error(\n `Invalid hint in ${rootType.name}.ts, we don't currently support reacting through ReactiveReferences, ${JSON.stringify(hint)}`,\n );\n }\n return reverseReactiveHint(rootType, otherMeta.cstr, subHint, undefined, false, false).map(\n appendPath(maybeAddTypeFilterSuffix(meta, field)),\n );\n }\n case \"poly\": {\n _fields.push(field.fieldName);\n // A poly is basically multiple m2os glued together, so copy/paste the `case m2o` code\n // above but do it for each component FK, and glue them together.\n return field.components.flatMap((comp) => {\n return reverseReactiveHint(rootType, comp.otherMetadata().cstr, subHint, undefined, false, false).map(\n appendPath(maybeAddTypeFilterSuffix(meta, comp)),\n );\n });\n }\n case \"m2m\": {\n // While o2m and o2o can watch for just FK changes by passing `reactForOtherSide` (the FK lives in the other\n // table), for m2m reactivity we push the collection name into the reactive hint, because it's effectively\n // \"the other/reverse side\", and JoinRows will trigger `queueDownstreamReactables` explicitly, instead of\n // `setField` which handles regular/non-m2m mutations.\n _fields.push(field.fieldName);\n const otherFieldName = maybeAddTypeFilterSuffix(meta, field);\n return reverseReactiveHint(\n rootType,\n field.otherMetadata().cstr,\n subHint,\n // For m2m, we can always pass undefined here, as otherwise having the opposite m2m collection\n // recalc all of its children will cause over-reactivity\n undefined,\n false,\n false,\n ).map(appendPath(otherFieldName));\n }\n case \"o2m\":\n case \"o2o\": {\n const isOtherReadOnly = field.otherMetadata().allFields[field.otherFieldName].immutable;\n const otherFieldName = maybeAddTypeFilterSuffix(meta, field);\n // This is not a field, but we want our reverse side to be reactive, so pass reactForOtherSide\n return reverseReactiveHint(\n rootType,\n field.otherMetadata().cstr,\n subHint,\n // IsLoadedCache invalidation always needs to know the otherField name, so we must\n // end up with either a kind=update or kind=read-only target with `otherFieldName` in it.\n field.otherFieldName,\n // We can have three outcomes:\n // - kind=update fields=[otherFieldName] (normal behavior)\n // - kind=update fields=[] + kind=read-only fields=[otherFieldName] (isOtherReadOnly)\n // - kind=read-only fields=[otherFieldName] (hint isReadOnly)\n isReadOnly ? \"hint-read-only\" : isOtherReadOnly ? \"field-read-only\" : false,\n false,\n ).map(appendPath(otherFieldName));\n }\n case \"primaryKey\":\n case \"primitive\":\n case \"enum\":\n _fields.push(key);\n return [];\n case \"m2mEnum\":\n // An enum m2m is a leaf of enum codes (no \"other side\" entity to recurse into); like a m2m,\n // `JoinRows` calls `queueDownstreamReactables` explicitly when its membership changes.\n _fields.push(key);\n return [];\n default:\n throw new Error(`Invalid hint in ${rootType.name}.ts hint ${JSON.stringify(hint)}`);\n }\n } else {\n // We only need to look for ReactiveProperties here, because ReactiveFields & ReactiveReferences\n // have underlying primitive fields that, when/if they change, will be handled in the ^ code.\n //\n // I.e. we specifically don't need to handle RFs & RRs ^, because the EntityManager.flush loop will\n // notice their primitive values changing, and kicking off any downstream reactive fields as necessary.\n const p = getProperties(meta)[key];\n if (p instanceof PropertyImpl) {\n // If the field is marked as readonly (i.e. using the `_ro` suffix), then we can assume that applies to its\n // entire hint as well and can simply omit it. This also allows us to use non-reactive async props as long as\n // they are readonly.\n if (isReadOnly) return [];\n if (!p.reactiveHint) {\n throw new Error(\n `Property ${key} cannot be used in reactive hints in ${rootType.name}.ts hint ${JSON.stringify(\n hint,\n )}, please use hasReactiveProperty instead`,\n );\n }\n return reverseReactiveHint(rootType, meta.cstr, p.reactiveHint, undefined, false, false);\n } else if (p instanceof ReactiveGetterImpl) {\n // If the field is marked as readonly, then we can assume that applies to its entire hint as well and can\n // simply omit it.\n if (isReadOnly) return [];\n return reverseReactiveHint(rootType, meta.cstr, p.reactiveHint, undefined, false, false);\n } else if (p instanceof RecursiveParentsCollectionImpl) {\n // I.e. if this is `Author.mentorsRecursive`:\n // - fieldName=mentorsRecursive (being watched by the caller)\n // - otherFieldName=menteesRecursive (our reversal for walking down)\n // - m2oFieldName = mentor\n //\n // Note that \"reversing parents\" is generally easier, b/c when the tree changes, any given node can\n // still \"walk down\" to find its children w/the \"current value\" childrenRecursive; and any just-now\n // orphaned parents will themselves trigger their own childrenRecursive changes (i.e. we do not need\n // any \"old reference value\" / \"new reference value\" handling).\n //\n // I.e. given a tree of `A is mentor of B is mentor of C`, when B changes mentors, and we need to\n // \"walk to all affected/children mentees, so they can observe their new mentorsRecursive\", merely\n // walking down from (b itself & it's mentees) will find all affected mentees.\n const { otherFieldName, m2oFieldName } = p;\n // When a mentor.firstName/etc field changes, walk to its transitive children (through otherFieldName)\n // via the `return reverseReactiveHint` line below.\n //\n // But if the `author.mentor` field itself changes, we ourselves are also \"a new mentee\" of the mentor, so\n // do `fields.push(m2oFieldName)` to notify our direct reactable (a new mentee), and then our transitive\n // children/mentees. This avoids going \"up to the mentor, and walking down all of his mentees\", which would go\n // through our siblings, which would not have changed/need reactivity.\n _fields.push(m2oFieldName);\n maybeRecursive.push({\n kind: isReadOnly ? \"read-only\" : \"update\",\n entity: entityType,\n fields: recursiveMembershipFields(meta, m2oFieldName),\n path: [otherFieldName],\n });\n // Any reactables who were watching `parentsRecursive`, we'll back down to by going through `childrenRecursive`\n return reverseReactiveHint(rootType, entityType, subHint, undefined, false).map(appendPath(otherFieldName));\n } else if (p instanceof RecursiveChildrenCollectionImpl) {\n const { otherFieldName, o2mFieldName } = p;\n // I.e. this is `Author.menteesRecursive`\n // - fieldName=menteesRecursive` (being watched by the caller)\n // - otherFieldName=mentorsRecursive` (our reversal for walking up)\n // - The o2m's otherFieldName gives us the FK field (e.g., \"mentor\") on the child entity.\n //\n // Note that \"reversing children\" is trickier than reversing parents, b/c when the tree changes,\n // we need to consider both \"old reference value\" and \"new reference value\" scenarios.\n const o2mField = meta.allFields[o2mFieldName] as OneToManyField | OneToOneField;\n const m2oFieldName = o2mField.otherFieldName;\n _fields.push(m2oFieldName);\n maybeRecursive.push({\n kind: isReadOnly ? \"read-only\" : \"update\",\n entity: entityType,\n fields: recursiveMembershipFields(meta, m2oFieldName), // i.e. parent\n path: [otherFieldName], // i.e. parentsRecursive to tell them \"observe your new childrenRecursive\"\n });\n // Any reactables who were watching `childrenRecursive`, we'll up to by going through `parentsRecursive`\n return reverseReactiveHint(rootType, entityType, subHint, undefined, false).map(appendPath(otherFieldName));\n } else if (p instanceof RecursiveM2mCollectionImpl) {\n // I.e. if this is `User.parentsRecursive`:\n // - fieldName=parentsRecursive (being watched by the caller)\n // - otherFieldName=childrenRecursive (our reversal for walking down)\n // - m2mFieldName=parents (the underlying m2m collection)\n //\n // For m2m recursive, the \"change signal\" is when join rows are added/removed, so we push\n // the m2m collection name into fields (like regular m2m handling). To find affected entities\n // that are watching `parentsRecursive`, we walk through `childrenRecursive` (the reverse direction).\n const { otherFieldName, m2mFieldName } = p;\n // When a join row changes (e.g. parents collection is mutated), notify both this entity\n // and its transitive children/parents via otherFieldName\n _fields.push(m2mFieldName);\n maybeRecursive.push({\n kind: isReadOnly ? \"read-only\" : \"update\",\n entity: entityType,\n fields: recursiveMembershipFields(meta, m2mFieldName),\n path: [otherFieldName],\n });\n // Any reactables who were watching this recursive collection, we'll reach via the other direction\n return reverseReactiveHint(rootType, entityType, subHint, undefined, false).map(appendPath(otherFieldName));\n } else {\n throw new Error(`Invalid hint in ${rootType.name}.ts ${JSON.stringify(hint)}`);\n }\n }\n}\n\nfunction maybeAddTypeFilterSuffix(\n meta: EntityMetadata,\n field: ManyToOneField | OneToManyField | ManyToManyField | OneToOneField | PolymorphicFieldComponent,\n): string {\n const otherField = field.otherMetadata().allFields[field.otherFieldName];\n if (!otherField) {\n // This is usually an error, unless if its ReactiveReference which we don't create o2m-s for\n const isReactiveReference = \"kind\" in field && field.kind === \"m2o\" && field.derived === \"async\";\n if (!isReactiveReference) fail(`No field ${field.otherMetadata().type}.${field.otherFieldName}`);\n return field.otherFieldName;\n }\n // If we're Foo, and the other field (which we'll traverse back from at runtime) is actually\n // a poly FK pointing back to multiple `owner=Foo | Bar | Zaz`, add a suffix of `@Foo` so\n // that any runtime traversal that hit `owner` and see a `Bar | Zaz` will stop.\n const nextFieldIsPoly = otherField.kind === \"poly\";\n // If we're a SubType, and the other field points to our base type, assume there might be\n // SubType-specific fields in the path so far, so tell the other side to only walk back through\n // us if its value is actually a SubType.\n const nextFieldPointsToBaseType =\n !!meta.baseType && otherField.kind !== \"poly\" && (otherField as any).otherMetadata().cstr !== meta.cstr;\n return nextFieldIsPoly || nextFieldPointsToBaseType ? `${field.otherFieldName}@${meta.type}` : field.otherFieldName;\n}\n\n/**\n * Walks `reverseHint` for every entity in `entities`.\n *\n * I.e. given `[book1, book2]` and `[\"author\", 'publisher\"]`, will return all the books' authors' publishers.\n *\n * Note that for references (and only references), we walk both through \"the current value\"\n * and prior values remembered in `referenceHistory`. This is fundamentally because references are the true \"owner\"\n * of the relation, while collections are derived.\n *\n * For example, with reversals walking through collections:\n *\n * - Given a hint `book: { author: firstName }`\n * - Which reverses to `author[firstName] -> books`\n * - When we walk through `a1.books`, we use only the current value, which might have only `[b2]` in it.\n * - We don't need to worry about \"the old a1.books\" value, which might have previously had `[b1]` in it,\n * because the `b1.author` changing authors (from `a1` to `a2`) will trigger its own reactivity.\n *\n * Contrast with reversals walking through references:\n *\n * - Given a hint `author: { books: \"title\" }`\n * - Which reverses to `book[title] -> author`\n * - When we traverse through `b1.author`, we use both the current value and remembered values, so that both\n * \"our prior authors\" and \"our new author\" see their latest `author.books` collection values.\n *\n * Delete cleanup runs before reverse hints are walked, so the current relation value may already be unset by the\n * time we get here. For persisted entities, `cleanupOnEntityDeleted` clears the owning FK via `setField`, which\n * remembers the prior FK in `referenceHistory`; this lets us still find the old owner. For collection reversals\n * this is enough because collections are derived from the owning FK: removing `b1` from `a1.books` is represented\n * by `b1.author` changing, not by an independent \"old books\" snapshot.\n *\n * References can also have transient values during a unit of work, i.e. a ReactiveField might observe `b1.author=a2`\n * before the final flush changes it to `a3`. `setField` remembers both original and transient reference values\n * separately from `originalData`, and we walk them in addition to the current value.\n */\nexport async function followReverseHint(\n reactionName: string,\n entities: Entity[],\n reverseHint: string[],\n): Promise<Entity[]> {\n // Start at the current (unique) entities\n let current = new Set(entities);\n const paths = [...reverseHint];\n // And \"walk backwards\" through the reverse hint\n while (paths.length) {\n const path = paths.shift()!;\n const [fieldName, viaType] = path.split(\"@\");\n const promises = new Array(current.size);\n // The path might touch either a reference or a collection\n for (const c of current as Set<any>) {\n const relation = c[fieldName];\n // If `!relation`, I'd really like to `fail`, but atm we're knowingly creating invalid reversals for when\n // a ReactiveField depends on a ReactiveReference; b/c RRs lack an \"other side\", we cannot walk them in\n // reverse, and so for awhile we \"supported\" the RF -> RR pattern by relying on a totally-read-only reactive\n // hint -> dropped during reversal.\n //\n // However, now IsLoadedCache means that even these \"totally read only\" RFs need to be reactive at least\n // during their initial EM creation, to avoid stale values being observed during first-flush mutations,\n // and so we're now stuck with these \"invalid RR reversals\" until we can figure out a better way to handle them.\n // (Now that IsLoadedCache doesn't technically walk paths, it just invalidates given entity+field pairs, so\n // it is not bothered by an invalid/missing RR reversal.)\n if (!relation) continue;\n // fail(`Attempting to react for ${reactionName} for ${c.toString()}, but there is no \"reverse walkable\" field ${c.constructor.name}.${fieldName}`,);\n const currentValuePromise = maybeApplyTypeFilter(relation.load(), viaType);\n // Always wait for the relation itself\n promises.push(currentValuePromise);\n // If we're going from Book.author back to Author to re-validate the Author.books collection,\n // see if Book.author has changed, so we can re-validate both the old author's books and the\n // new author's books.\n const fieldKind = getMetadata(c).allFields[fieldName]?.kind;\n const isReference = fieldKind === \"m2o\" || fieldKind === \"poly\";\n const isManyToMany = fieldKind === \"m2m\";\n // See jsdoc comment about why this is only necessary for references...\n if (isReference) {\n for (const value of getInstanceData(c).getReferenceHistory(fieldName)) {\n promises.push(maybeApplyTypeFilter(loadReferenceHistoryValue(c, value), viaType));\n }\n }\n if (isManyToMany) {\n const m2m = c[fieldName] as ManyToManyCollection<any, any>;\n const joinRows = getEmInternalApi(m2m.entity.em).joinRows(m2m);\n // Return a tuple of [currentRows, removedRows]\n promises.push(joinRows.removedFor(m2m, c));\n }\n // Walk up the old parents\n if (relation instanceof RecursiveParentsCollectionImpl) {\n const { m2oFieldName } = relation;\n for (const value of getInstanceData(c).getReferenceHistory(m2oFieldName)) {\n promises.push(\n // First load the old parent itself\n loadReferenceHistoryValue(c, value).then((oldParent) => {\n if (!oldParent) return undefined;\n // And then resolve its fieldName=parentsRecursive\n const oldParentRecursiveParents = (oldParent as any)[fieldName];\n return oldParentRecursiveParents.load().then((parents: any) => {\n return [oldParent, ...parents];\n });\n }),\n );\n }\n }\n }\n const nextLevel = await Promise.all(promises);\n // Make a new set so that we dedupe as we go\n current = flatAndUnique(nextLevel);\n }\n return [...current];\n}\n\n/** Converts a normalized reactive `hint` into a load hint. */\nexport function convertToLoadHint<T extends Entity>(\n meta: EntityMetadata,\n hint: ReactiveHint<T>,\n allowCustomKeys = false,\n): LoadHint<T> {\n const loadHint = {};\n // Process the hints individually instead of just calling Object.fromEntries so that\n // we can handle inlined reactive hints that overlap.\n for (const [keyMaybeSuffix, subHint] of Object.entries(normalizeHint(hint))) {\n let key = keyMaybeSuffix.replace(suffixRe, \"\");\n let field = meta.allFields[key];\n\n // If `allowCustomKeys` is enabled, we're probably doing `toJSON`,\n // so look for `companyId` & `companyIds` to turn into `company`\n if (!field && allowCustomKeys) {\n const realField = Object.values(meta.allFields).find((f) => f.fieldIdName === key);\n if (realField) {\n field = realField;\n key = realField.fieldName;\n }\n }\n\n if (field) {\n switch (field.kind) {\n case \"m2m\":\n case \"m2o\":\n case \"o2m\":\n case \"o2o\":\n mergeNormalizedHints(loadHint, { [key]: convertToLoadHint(field.otherMetadata(), subHint, allowCustomKeys) });\n break;\n case \"poly\":\n for (const comp of field.components) {\n // Write `{ parent: { child@Type: hint } }` into the load hint\n mergeNormalizedHints(loadHint, {\n [key]: Object.fromEntries(\n Object.entries(convertToLoadHint(comp.otherMetadata(), subHint, allowCustomKeys)).map(\n // Map the subHint keys to add in `@Type`\n ([subKey, subHint]) => [`${subKey}@${comp.otherMetadata().type}`, subHint],\n ),\n ),\n });\n }\n break;\n case \"primitive\":\n case \"enum\":\n // Lazy primitives are excluded from the default SELECT, so rules need them loaded before using `.get`.\n if (field.derived === \"async\" || (field.kind === \"primitive\" && field.lazy)) {\n mergeNormalizedHints(loadHint, { [key]: {} });\n }\n continue;\n case \"m2mEnum\":\n // The enum codes are a leaf, but the collection itself must be loaded for the reaction to read it.\n mergeNormalizedHints(loadHint, { [key]: {} });\n break;\n case \"primaryKey\":\n continue;\n default:\n throw new Error(`Invalid reactive hint ${meta.tableName} ${JSON.stringify(hint)}`);\n }\n } else {\n const p = getProperties(meta)[key];\n if (p instanceof RecursiveParentsCollectionImpl) {\n mergeNormalizedHints(loadHint, { [p.fieldName]: convertToLoadHint(meta, subHint, allowCustomKeys) });\n } else if (p instanceof RecursiveChildrenCollectionImpl) {\n mergeNormalizedHints(loadHint, { [p.fieldName]: convertToLoadHint(meta, subHint, allowCustomKeys) });\n } else if (p instanceof RecursiveM2mCollectionImpl) {\n mergeNormalizedHints(loadHint, { [p.fieldName]: convertToLoadHint(meta, subHint, allowCustomKeys) });\n } else if (p && p.reactiveHint) {\n mergeNormalizedHints(loadHint, convertToLoadHint(meta, p.reactiveHint, allowCustomKeys));\n } else if (!allowCustomKeys) {\n fail(`Invalid reactive hint on ${meta.tableName} ${JSON.stringify(hint)}`);\n }\n }\n }\n return loadHint as any;\n}\n\n/** An entity that, when `fields` change, should trigger the reactive rule/field pointed to by `path`. */\nexport interface ReactiveTarget {\n kind: \"read-only\" | \"update\";\n /** The entity that contains a field our reactive rule/field accesses. */\n entity: MaybeAbstractEntityConstructor<any>;\n /** The field(s) that our reactive rule/field accesses, plus any implicit fields like FKs. */\n fields: string[];\n /** The path from this `entity` back to the source reactive rule/field. */\n path: string[];\n}\n\nfunction maybeApplyTypeFilter(loadPromise: Promise<Entity | Entity[] | undefined>, viaType: string | undefined) {\n if (viaType) {\n return loadPromise.then((loaded) => {\n if (Array.isArray(loaded)) {\n return loaded.filter((e) => isTypeOrSubType(e, viaType));\n } else if (loaded && isTypeOrSubType(loaded, viaType)) {\n return loaded;\n } else {\n return undefined;\n }\n });\n }\n return loadPromise;\n}\n\n/** Loads a remembered reference value so reverse-hint walking can revisit it. */\nfunction loadReferenceHistoryValue(entity: Entity, value: any): Promise<Entity | undefined> {\n if (isEntity(value)) return Promise.resolve(value);\n if (isId(value)) return entity.em.load(value) as Promise<Entity>;\n return Promise.resolve(undefined);\n}\n\n/** Returns true if `entity` is exactly `typeName` or a subtype of `typeName`.\n *\n * Used to honor poly/type filters like `@Publisher` during reverse-hint walks:\n * a `SmallPublisher` entity should pass an `@Publisher` filter because it is\n * a Publisher via inheritance.\n */\nexport function isTypeOrSubType(entity: Entity, typeName: string): boolean {\n const meta = getMetadata(entity);\n return meta.type === typeName || meta.baseTypes.some((b) => b.type === typeName);\n}\n\nexport function isPolyHint(key: string): boolean {\n return key.includes(\"@\");\n}\n\nexport function splitPolyHintToKeyAndType(key: string): [string, string] {\n const [k, type] = key.split(\"@\");\n return [k, type];\n}\n\n/** If this hint is a poly like `author@Book`, return the type, otherwise return undefined. */\nexport function getRelationFromMaybePolyKey(entity: Entity, key: string): any {\n if (isPolyHint(key)) {\n const [realKey, typeName] = splitPolyHintToKeyAndType(key);\n // Even though `entity[realKey]` might exist, if it's from a different poly type, we don't want it\n const isApplicable = getBaseAndSelfMetas(getMetadata(entity)).some((meta) => meta.type === typeName);\n if (!isApplicable) return undefined;\n return (entity as any)[realKey];\n } else {\n return (entity as any)[key];\n }\n}\n\n/**\n * For an `author -> books -> bookReviews` hint, appends `BookReview.book`, `Book.author`,\n * i.e. the \"other side\" relation, to the reversed path on the way \"out\" from a mutated BookReview\n * to the target `Author` RF/RR/rule.\n */\nfunction appendPath(reverseField: string): (rt: ReactiveTarget) => ReactiveTarget {\n return (rt) => {\n // We can mutate this array b/c it's only owned by a single/continually-updated `ReactiveTarget`\n rt.path = [...rt.path, reverseField];\n return rt;\n };\n}\n\n/** Recursive membership changes on soft deletion or resurrection, even when the underlying relation is unchanged. */\nfunction recursiveMembershipFields(meta: EntityMetadata, fieldName: string): string[] {\n const deletedAt = getBaseMeta(meta).timestampFields?.deletedAt;\n return deletedAt ? [fieldName, deletedAt] : [fieldName];\n}\n\nfunction isAllReadOnly(hint: any): boolean {\n for (const [key, subHint] of Object.entries(normalizeHint(hint))) {\n const okay = (key.endsWith(\"_ro\") || key.endsWith(\":ro\")) && isAllReadOnly(subHint);\n if (!okay) return false;\n }\n return true;\n}\n"],"mappings":"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"}
|