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,13 +1,13 @@
|
|
|
1
1
|
import { isCollection, isLoadedCollection } from "./Collection.js";
|
|
2
2
|
import { AbstractRelationImpl } from "./AbstractRelationImpl.js";
|
|
3
3
|
import { RelationT, RelationU } from "./RelationSymbols.js";
|
|
4
|
+
import { RecursiveCycleError } from "./RecursiveCycleError.js";
|
|
5
|
+
import { isLoadedReference, isReference } from "./Reference.js";
|
|
6
|
+
import { isLoadedOneToOneReference, isOneToOneReference } from "./OneToOneReference.js";
|
|
4
7
|
import { recursiveChildrenBatchLoader } from "../batchloaders/recursiveChildrenBatchLoader.js";
|
|
5
8
|
import { recursiveM2mBatchLoader } from "../batchloaders/recursiveM2mBatchLoader.js";
|
|
6
9
|
import { recursiveParentsBatchLoader } from "../batchloaders/recursiveParentsBatchLoader.js";
|
|
7
10
|
import "./ReadOnlyCollection.js";
|
|
8
|
-
import { RecursiveCycleError } from "./RecursiveCycleError.js";
|
|
9
|
-
import { isLoadedReference, isReference } from "./Reference.js";
|
|
10
|
-
import { isLoadedOneToOneReference, isOneToOneReference } from "./OneToOneReference.js";
|
|
11
11
|
import { fail } from "../utils.js";
|
|
12
12
|
import { lazyField } from "../newEntity.js";
|
|
13
13
|
import { getMetadata } from "../EntityMetadata.js";
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_loading_loadHints = require("../loading/loadHints.cjs");
|
|
2
3
|
const require_relations_PropertySymbols = require("./PropertySymbols.cjs");
|
|
3
4
|
const require_relations_RecursiveCycleError = require("./RecursiveCycleError.cjs");
|
|
4
|
-
const require_reactivity_reactiveHints = require("../reactivity/reactiveHints.cjs");
|
|
5
|
-
const require_loading_loadHints = require("../loading/loadHints.cjs");
|
|
6
5
|
const require_utils = require("../utils.cjs");
|
|
7
6
|
const require_newEntity = require("../newEntity.cjs");
|
|
7
|
+
const require_reactivity_reactiveHints = require("../reactivity/reactiveHints.cjs");
|
|
8
8
|
const require_EntityMetadata = require("../EntityMetadata.cjs");
|
|
9
9
|
require("../Entity.cjs");
|
|
10
10
|
//#region src/relations/hasProperty.ts
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { isLoaded } from "../loading/loadHints.js";
|
|
1
2
|
import { PropertyT } from "./PropertySymbols.js";
|
|
2
3
|
import { RecursiveCycleError } from "./RecursiveCycleError.js";
|
|
3
|
-
import { convertToLoadHint } from "../reactivity/reactiveHints.js";
|
|
4
|
-
import { isLoaded } from "../loading/loadHints.js";
|
|
5
4
|
import { tryResolve } from "../utils.js";
|
|
6
5
|
import { lazyField } from "../newEntity.js";
|
|
6
|
+
import { convertToLoadHint } from "../reactivity/reactiveHints.js";
|
|
7
7
|
import { getMetadata } from "../EntityMetadata.js";
|
|
8
8
|
import "../Entity.js";
|
|
9
9
|
//#region src/relations/hasProperty.ts
|
|
@@ -3,12 +3,8 @@ const require_relations_Collection = require("./Collection.cjs");
|
|
|
3
3
|
const require_relations_CustomCollection = require("./CustomCollection.cjs");
|
|
4
4
|
const require_relations_EnumCollection = require("./EnumCollection.cjs");
|
|
5
5
|
const require_relations_CustomReference = require("./CustomReference.cjs");
|
|
6
|
-
const require_relations_ReactiveGetter = require("./ReactiveGetter.cjs");
|
|
7
|
-
const require_relations_Relation = require("./Relation.cjs");
|
|
8
|
-
const require_relations_ReadOnlyCollection = require("./ReadOnlyCollection.cjs");
|
|
9
|
-
const require_relations_RecursiveCycleError = require("./RecursiveCycleError.cjs");
|
|
10
|
-
const require_relations_RecursiveCollection = require("./RecursiveCollection.cjs");
|
|
11
6
|
const require_relations_hasAsyncMethod = require("./hasAsyncMethod.cjs");
|
|
7
|
+
const require_relations_RecursiveCycleError = require("./RecursiveCycleError.cjs");
|
|
12
8
|
const require_relations_hasProperty = require("./hasProperty.cjs");
|
|
13
9
|
const require_relations_AsyncProperty = require("./AsyncProperty.cjs");
|
|
14
10
|
const require_relations_LazyField = require("./LazyField.cjs");
|
|
@@ -18,6 +14,7 @@ const require_relations_Reference = require("./Reference.cjs");
|
|
|
18
14
|
const require_relations_hasOneDerived = require("./hasOneDerived.cjs");
|
|
19
15
|
const require_relations_hasOneThrough = require("./hasOneThrough.cjs");
|
|
20
16
|
const require_relations_ManyToManyCollection = require("./ManyToManyCollection.cjs");
|
|
17
|
+
const require_relations_Relation = require("./Relation.cjs");
|
|
21
18
|
const require_relations_ManyToManyLargeCollection = require("./ManyToManyLargeCollection.cjs");
|
|
22
19
|
const require_relations_OneToManyCollection = require("./OneToManyCollection.cjs");
|
|
23
20
|
const require_relations_ManyToOneReference = require("./ManyToOneReference.cjs");
|
|
@@ -26,9 +23,12 @@ const require_relations_ReactiveReference = require("./ReactiveReference.cjs");
|
|
|
26
23
|
const require_relations_OneToOneReference = require("./OneToOneReference.cjs");
|
|
27
24
|
const require_relations_PolymorphicReference = require("./PolymorphicReference.cjs");
|
|
28
25
|
const require_relations_ReactiveField = require("./ReactiveField.cjs");
|
|
26
|
+
const require_relations_ReactiveGetter = require("./ReactiveGetter.cjs");
|
|
29
27
|
const require_relations_ReactiveManyToMany = require("./ReactiveManyToMany.cjs");
|
|
30
28
|
const require_relations_ReactiveManyToManyOtherSide = require("./ReactiveManyToManyOtherSide.cjs");
|
|
31
29
|
const require_relations_AsyncReactiveField = require("./AsyncReactiveField.cjs");
|
|
30
|
+
const require_relations_RecursiveCollection = require("./RecursiveCollection.cjs");
|
|
31
|
+
const require_relations_ReadOnlyCollection = require("./ReadOnlyCollection.cjs");
|
|
32
32
|
exports.AsyncPropertyImpl = require_relations_AsyncProperty.AsyncPropertyImpl;
|
|
33
33
|
exports.CustomCollection = require_relations_CustomCollection.CustomCollection;
|
|
34
34
|
exports.CustomReference = require_relations_CustomReference.CustomReference;
|
package/build/relations/index.js
CHANGED
|
@@ -2,12 +2,8 @@ import { isCollection, isLoadedCollection } from "./Collection.js";
|
|
|
2
2
|
import { CustomCollection, hasCustomCollection } from "./CustomCollection.js";
|
|
3
3
|
import { EnumCollectionImpl, hasEnumCollection } from "./EnumCollection.js";
|
|
4
4
|
import { CustomReference, hasCustomReference } from "./CustomReference.js";
|
|
5
|
-
import { hasReactiveGetter, isReactiveGetter } from "./ReactiveGetter.js";
|
|
6
|
-
import { isRelation } from "./Relation.js";
|
|
7
|
-
import { isLoadedReadOnlyCollection, isReadOnlyCollection } from "./ReadOnlyCollection.js";
|
|
8
|
-
import { RecursiveCycleError } from "./RecursiveCycleError.js";
|
|
9
|
-
import { hasRecursiveChildren, hasRecursiveM2m, hasRecursiveParents } from "./RecursiveCollection.js";
|
|
10
5
|
import { hasAsyncMethod } from "./hasAsyncMethod.js";
|
|
6
|
+
import { RecursiveCycleError } from "./RecursiveCycleError.js";
|
|
11
7
|
import { PropertyImpl, hasProperty, hasReactiveProperty, isLoadedProperty, isProperty } from "./hasProperty.js";
|
|
12
8
|
import { AsyncPropertyImpl, hasAsyncProperty, isAsyncProperty, isLoadedAsyncProperty } from "./AsyncProperty.js";
|
|
13
9
|
import { LazyFieldImpl, hasLazyField, isLazyField, isLoadedLazyField } from "./LazyField.js";
|
|
@@ -17,6 +13,7 @@ import { isLoadedReference, isReference } from "./Reference.js";
|
|
|
17
13
|
import { hasOneDerived } from "./hasOneDerived.js";
|
|
18
14
|
import { hasOneThrough } from "./hasOneThrough.js";
|
|
19
15
|
import { ManyToManyCollection, hasManyToMany } from "./ManyToManyCollection.js";
|
|
16
|
+
import { isRelation } from "./Relation.js";
|
|
20
17
|
import { ManyToManyLargeCollection, hasLargeManyToMany } from "./ManyToManyLargeCollection.js";
|
|
21
18
|
import { OneToManyCollection, hasMany } from "./OneToManyCollection.js";
|
|
22
19
|
import { ManyToOneReferenceImpl, hasOne, isManyToOneReference } from "./ManyToOneReference.js";
|
|
@@ -25,7 +22,10 @@ import { ReactiveReferenceImpl, hasReactiveReference, isReactiveReference } from
|
|
|
25
22
|
import { OneToOneReferenceImpl, hasOneToOne, isLoadedOneToOneReference, isOneToOneReference } from "./OneToOneReference.js";
|
|
26
23
|
import { PolymorphicReferenceImpl, hasOnePolymorphic, isPolymorphicReference } from "./PolymorphicReference.js";
|
|
27
24
|
import { hasReactiveField, isReactiveField } from "./ReactiveField.js";
|
|
25
|
+
import { hasReactiveGetter, isReactiveGetter } from "./ReactiveGetter.js";
|
|
28
26
|
import { ReactiveManyToManyImpl, hasReactiveManyToMany, isReactiveManyToMany } from "./ReactiveManyToMany.js";
|
|
29
27
|
import { ReactiveManyToManyOtherSideImpl, hasReactiveManyToManyOtherSide, isReactiveManyToManyOtherSide } from "./ReactiveManyToManyOtherSide.js";
|
|
30
28
|
import { hasAsyncReactiveField, isAsyncReactiveField } from "./AsyncReactiveField.js";
|
|
29
|
+
import { hasRecursiveChildren, hasRecursiveM2m, hasRecursiveParents } from "./RecursiveCollection.js";
|
|
30
|
+
import { isLoadedReadOnlyCollection, isReadOnlyCollection } from "./ReadOnlyCollection.js";
|
|
31
31
|
export { AsyncPropertyImpl, CustomCollection, CustomReference, EnumCollectionImpl, LazyFieldImpl, ManyToManyCollection, ManyToManyLargeCollection, ManyToOneReferenceImpl, OneToManyCollection, OneToManyLargeCollection, OneToOneReferenceImpl, PolymorphicReferenceImpl, PropertyImpl, ReactiveManyToManyImpl, ReactiveManyToManyOtherSideImpl, ReactiveReferenceImpl, RecursiveCycleError, hasAsyncMethod, hasAsyncProperty, hasAsyncReactiveField, hasCustomCollection, hasCustomReference, hasEnumCollection, hasLargeMany, hasLargeManyToMany, hasLazyField, hasMany, hasManyDerived, hasManyThrough, hasManyToMany, hasOne, hasOneDerived, hasOnePolymorphic, hasOneThrough, hasOneToOne, hasProperty, hasReactiveField, hasReactiveGetter, hasReactiveManyToMany, hasReactiveManyToManyOtherSide, hasReactiveProperty, hasReactiveReference, hasRecursiveChildren, hasRecursiveM2m, hasRecursiveParents, isAsyncProperty, isAsyncReactiveField, isCollection, isLazyField, isLoadedAsyncProperty, isLoadedCollection, isLoadedLazyField, isLoadedOneToOneReference, isLoadedProperty, isLoadedReadOnlyCollection, isLoadedReference, isManyToOneReference, isOneToOneReference, isPolymorphicReference, isProperty, isReactiveField, isReactiveGetter, isReactiveManyToMany, isReactiveManyToManyOtherSide, isReactiveReference, isReadOnlyCollection, isReference, isRelation };
|
package/build/resurrection.cjs
CHANGED
|
@@ -24,7 +24,7 @@ async function findExistingIfUniqueBy(em, constructor, input) {
|
|
|
24
24
|
return inMemory[0];
|
|
25
25
|
}
|
|
26
26
|
if (Object.values(where).some((value) => require_Entity.isEntity(value) && value.isNewEntity)) continue;
|
|
27
|
-
const entities = onlySoftDeleted(await em.find(constructor, where, { softDeletes: "include" }));
|
|
27
|
+
const entities = onlySoftDeleted(await em.find(constructor, newWhereEqual(meta, where), { softDeletes: "include" }));
|
|
28
28
|
if (entities.length > 1) throw new require_EntityManager.TooManyError(`Found more than one existing ${meta.type} with ${whereAsString(where)}`);
|
|
29
29
|
else if (entities.length === 1) {
|
|
30
30
|
resurrectIfSoftDeleted(entities[0]);
|
|
@@ -67,8 +67,18 @@ function isQueryableIdentityField(field) {
|
|
|
67
67
|
function whereAsString(where) {
|
|
68
68
|
return Object.entries(where).map(([key, value]) => `${key}=${value}`).join(", ");
|
|
69
69
|
}
|
|
70
|
+
/** Builds exact identity filters for primitives and relations. */
|
|
71
|
+
function newWhereEqual(meta, input) {
|
|
72
|
+
const where = {};
|
|
73
|
+
for (const name of Object.keys(input)) {
|
|
74
|
+
const value = input[name] ?? null;
|
|
75
|
+
where[name] = meta.allFields[name]?.kind === "primitive" ? { eq: value } : value;
|
|
76
|
+
}
|
|
77
|
+
return where;
|
|
78
|
+
}
|
|
70
79
|
//#endregion
|
|
71
80
|
exports.findExistingIfUniqueBy = findExistingIfUniqueBy;
|
|
81
|
+
exports.newWhereEqual = newWhereEqual;
|
|
72
82
|
exports.resurrectIfSoftDeleted = resurrectIfSoftDeleted;
|
|
73
83
|
|
|
74
84
|
//# sourceMappingURL=resurrection.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resurrection.cjs","names":["getBaseMeta","getMetadata","TooManyError","isEntity"],"sources":["../src/resurrection.ts"],"sourcesContent":["import { type Entity, isEntity } from \"src/Entity.ts\";\nimport { type EntityManager, type MaybeAbstractEntityConstructor, TooManyError } from \"src/EntityManager.ts\";\nimport { type EntityMetadata, type Field, getBaseMeta, getMetadata } from \"src/EntityMetadata.ts\";\nimport { setField } from \"src/fields.ts\";\n\n/** Clears the soft-delete marker so a matched entity is updated instead of recreated. */\nexport function resurrectIfSoftDeleted(entity: Entity): void {\n const deletedAt = getBaseMeta(getMetadata(entity)).timestampFields?.deletedAt;\n if (deletedAt && (entity as any).isSoftDeletedEntity) {\n setField(entity, deletedAt, undefined);\n }\n}\n\n/**\n * Finds an existing entity when the input fully covers one configured `uniqueBy` candidate.\n * I.e. with `uniqueBy = [[\"email\"], [\"author\", \"title\"]]`, `{ email }` or `{ authorId, title }` can lookup an existing row.\n */\nexport async function findExistingIfUniqueBy<T extends Entity>(\n em: EntityManager<any, any, any>,\n constructor: MaybeAbstractEntityConstructor<T>,\n input: object,\n): Promise<T | undefined> {\n const meta = getMetadata(constructor);\n const candidates = getUniqueByCandidates(meta, input);\n for (const where of candidates) {\n const inMemory = onlySoftDeleted(em.filterEntities(constructor as any, where as any) as T[]);\n if (inMemory.length > 1) {\n throw new TooManyError(`Found more than one existing ${meta.type} with ${whereAsString(where)}`);\n } else if (inMemory.length === 1) {\n resurrectIfSoftDeleted(inMemory[0]);\n return inMemory[0];\n }\n\n // I.e. `{ publisher: newPublisherEntity }` cannot already exist in the database, so skip the `em.find`.\n const hasNewEntityParam = Object.values(where).some((value) => isEntity(value) && value.isNewEntity);\n if (hasNewEntityParam) continue;\n\n const entities = onlySoftDeleted(await (em as any).find(constructor, where, { softDeletes: \"include\" })) as T[];\n if (entities.length > 1) {\n throw new TooManyError(`Found more than one existing ${meta.type} with ${whereAsString(where)}`);\n } else if (entities.length === 1) {\n resurrectIfSoftDeleted(entities[0]);\n return entities[0];\n }\n }\n return undefined;\n}\n\n/** Returns soft-deleted entities that are safe for upsert to resurrect. */\nfunction onlySoftDeleted<T extends Entity>(entities: T[]): T[] {\n return entities.filter(\n (entity) => !entity.isDeletedEntity && (entity as T & { isSoftDeletedEntity?: boolean }).isSoftDeletedEntity,\n );\n}\n\n/** Builds pushdown-safe where clauses from configured identity fields. */\nfunction getUniqueByCandidates(meta: EntityMetadata, input: object): object[] {\n return (meta.uniqueBy ?? getBaseMeta(meta).uniqueBy ?? []).flatMap((fields) => {\n const where: Record<string, unknown> = {};\n for (const fieldName of fields) {\n const field = meta.allFields[fieldName];\n if (!field || !isQueryableIdentityField(field)) return [];\n const value = getIdentityValue(field, input);\n if (value === undefined || value === null) return [];\n where[field.fieldName] = value;\n }\n return [where];\n });\n}\n\n/** Returns the identity value from the input. */\nfunction getIdentityValue(field: Field, input: object): unknown {\n if (Object.hasOwn(input, field.fieldName)) {\n return normalizeIdentityValue((input as Record<string, unknown>)[field.fieldName]);\n }\n if (field.fieldIdName && Object.hasOwn(input, field.fieldIdName)) {\n return normalizeIdentityValue((input as Record<string, unknown>)[field.fieldIdName]);\n }\n return undefined;\n}\n\n/** Returns raw ids from relation inputs, i.e. `{ publisher: { id: \"p:1\" } }` becomes `{ publisher: \"p:1\" }`. */\nfunction normalizeIdentityValue(value: unknown): unknown {\n if (!isEntity(value) && value && typeof value === \"object\" && Object.hasOwn(value, \"id\")) {\n return (value as { id?: unknown }).id;\n }\n return value;\n}\n\n/** Returns true if Joist can push this identity field into a flat find clause. */\nfunction isQueryableIdentityField(field: Field): boolean {\n return field.kind === \"primitive\" || field.kind === \"enum\" || field.kind === \"m2o\" || field.kind === \"poly\";\n}\n\nfunction whereAsString(where: object): string {\n return Object.entries(where)\n .map(([key, value]) => `${key}=${value}`)\n .join(\", \");\n}\n"],"mappings":";;;;;;;AAMA,SAAgB,uBAAuB,QAAsB;CAC3D,MAAM,YAAYA,uBAAAA,YAAYC,uBAAAA,YAAY,MAAM,CAAC,CAAC,CAAC,iBAAiB;CACpE,IAAI,aAAc,OAAe,qBAC/B,eAAA,SAAS,QAAQ,WAAW,KAAA,CAAS;AAEzC;;;;;AAMA,eAAsB,uBACpB,IACA,aACA,OACwB;CACxB,MAAM,OAAOA,uBAAAA,YAAY,WAAW;CACpC,MAAM,aAAa,sBAAsB,MAAM,KAAK;CACpD,KAAK,MAAM,SAAS,YAAY;EAC9B,MAAM,WAAW,gBAAgB,GAAG,eAAe,aAAoB,KAAY,CAAQ;EAC3F,IAAI,SAAS,SAAS,GACpB,MAAM,IAAIC,sBAAAA,aAAa,gCAAgC,KAAK,KAAK,QAAQ,cAAc,KAAK,GAAG;OAC1F,IAAI,SAAS,WAAW,GAAG;GAChC,uBAAuB,SAAS,EAAE;GAClC,OAAO,SAAS;EAClB;EAIA,IAD0B,OAAO,OAAO,KAAK,CAAC,CAAC,MAAM,UAAUC,eAAAA,SAAS,KAAK,KAAK,MAAM,WACpE,GAAG;EAEvB,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"resurrection.cjs","names":["getBaseMeta","getMetadata","TooManyError","isEntity"],"sources":["../src/resurrection.ts"],"sourcesContent":["import { type Entity, isEntity } from \"src/Entity.ts\";\nimport { type EntityManager, type MaybeAbstractEntityConstructor, TooManyError } from \"src/EntityManager.ts\";\nimport { type EntityMetadata, type Field, getBaseMeta, getMetadata } from \"src/EntityMetadata.ts\";\nimport { setField } from \"src/fields.ts\";\n\n/** Clears the soft-delete marker so a matched entity is updated instead of recreated. */\nexport function resurrectIfSoftDeleted(entity: Entity): void {\n const deletedAt = getBaseMeta(getMetadata(entity)).timestampFields?.deletedAt;\n if (deletedAt && (entity as any).isSoftDeletedEntity) {\n setField(entity, deletedAt, undefined);\n }\n}\n\n/**\n * Finds an existing entity when the input fully covers one configured `uniqueBy` candidate.\n * I.e. with `uniqueBy = [[\"email\"], [\"author\", \"title\"]]`, `{ email }` or `{ authorId, title }` can lookup an existing row.\n */\nexport async function findExistingIfUniqueBy<T extends Entity>(\n em: EntityManager<any, any, any>,\n constructor: MaybeAbstractEntityConstructor<T>,\n input: object,\n): Promise<T | undefined> {\n const meta = getMetadata(constructor);\n const candidates = getUniqueByCandidates(meta, input);\n for (const where of candidates) {\n const inMemory = onlySoftDeleted(em.filterEntities(constructor as any, where as any) as T[]);\n if (inMemory.length > 1) {\n throw new TooManyError(`Found more than one existing ${meta.type} with ${whereAsString(where)}`);\n } else if (inMemory.length === 1) {\n resurrectIfSoftDeleted(inMemory[0]);\n return inMemory[0];\n }\n\n // I.e. `{ publisher: newPublisherEntity }` cannot already exist in the database, so skip the `em.find`.\n const hasNewEntityParam = Object.values(where).some((value) => isEntity(value) && value.isNewEntity);\n if (hasNewEntityParam) continue;\n\n const entities = onlySoftDeleted(\n await (em as any).find(constructor, newWhereEqual(meta, where), { softDeletes: \"include\" }),\n ) as T[];\n if (entities.length > 1) {\n throw new TooManyError(`Found more than one existing ${meta.type} with ${whereAsString(where)}`);\n } else if (entities.length === 1) {\n resurrectIfSoftDeleted(entities[0]);\n return entities[0];\n }\n }\n return undefined;\n}\n\n/** Returns soft-deleted entities that are safe for upsert to resurrect. */\nfunction onlySoftDeleted<T extends Entity>(entities: T[]): T[] {\n return entities.filter(\n (entity) => !entity.isDeletedEntity && (entity as T & { isSoftDeletedEntity?: boolean }).isSoftDeletedEntity,\n );\n}\n\n/** Builds pushdown-safe where clauses from configured identity fields. */\nfunction getUniqueByCandidates(meta: EntityMetadata, input: object): object[] {\n return (meta.uniqueBy ?? getBaseMeta(meta).uniqueBy ?? []).flatMap((fields) => {\n const where: Record<string, unknown> = {};\n for (const fieldName of fields) {\n const field = meta.allFields[fieldName];\n if (!field || !isQueryableIdentityField(field)) return [];\n const value = getIdentityValue(field, input);\n if (value === undefined || value === null) return [];\n where[field.fieldName] = value;\n }\n return [where];\n });\n}\n\n/** Returns the identity value from the input. */\nfunction getIdentityValue(field: Field, input: object): unknown {\n if (Object.hasOwn(input, field.fieldName)) {\n return normalizeIdentityValue((input as Record<string, unknown>)[field.fieldName]);\n }\n if (field.fieldIdName && Object.hasOwn(input, field.fieldIdName)) {\n return normalizeIdentityValue((input as Record<string, unknown>)[field.fieldIdName]);\n }\n return undefined;\n}\n\n/** Returns raw ids from relation inputs, i.e. `{ publisher: { id: \"p:1\" } }` becomes `{ publisher: \"p:1\" }`. */\nfunction normalizeIdentityValue(value: unknown): unknown {\n if (!isEntity(value) && value && typeof value === \"object\" && Object.hasOwn(value, \"id\")) {\n return (value as { id?: unknown }).id;\n }\n return value;\n}\n\n/** Returns true if Joist can push this identity field into a flat find clause. */\nfunction isQueryableIdentityField(field: Field): boolean {\n return field.kind === \"primitive\" || field.kind === \"enum\" || field.kind === \"m2o\" || field.kind === \"poly\";\n}\n\nfunction whereAsString(where: object): string {\n return Object.entries(where)\n .map(([key, value]) => `${key}=${value}`)\n .join(\", \");\n}\n\n/** Builds exact identity filters for primitives and relations. */\nexport function newWhereEqual(meta: EntityMetadata, input: object): object {\n const where: Record<string, unknown> = {};\n\n // Arrays need eq for exact equality; relations must stay as-is because eq would be a nested entity filter.\n // Unset values must match SQL NULL, e.g. publisher: undefined must not omit the publisher filter.\n for (const name of Object.keys(input)) {\n const value = (input as Record<string, unknown>)[name] ?? null;\n where[name] = meta.allFields[name]?.kind === \"primitive\" ? { eq: value } : value;\n }\n\n return where;\n}\n"],"mappings":";;;;;;;AAMA,SAAgB,uBAAuB,QAAsB;CAC3D,MAAM,YAAYA,uBAAAA,YAAYC,uBAAAA,YAAY,MAAM,CAAC,CAAC,CAAC,iBAAiB;CACpE,IAAI,aAAc,OAAe,qBAC/B,eAAA,SAAS,QAAQ,WAAW,KAAA,CAAS;AAEzC;;;;;AAMA,eAAsB,uBACpB,IACA,aACA,OACwB;CACxB,MAAM,OAAOA,uBAAAA,YAAY,WAAW;CACpC,MAAM,aAAa,sBAAsB,MAAM,KAAK;CACpD,KAAK,MAAM,SAAS,YAAY;EAC9B,MAAM,WAAW,gBAAgB,GAAG,eAAe,aAAoB,KAAY,CAAQ;EAC3F,IAAI,SAAS,SAAS,GACpB,MAAM,IAAIC,sBAAAA,aAAa,gCAAgC,KAAK,KAAK,QAAQ,cAAc,KAAK,GAAG;OAC1F,IAAI,SAAS,WAAW,GAAG;GAChC,uBAAuB,SAAS,EAAE;GAClC,OAAO,SAAS;EAClB;EAIA,IAD0B,OAAO,OAAO,KAAK,CAAC,CAAC,MAAM,UAAUC,eAAAA,SAAS,KAAK,KAAK,MAAM,WACpE,GAAG;EAEvB,MAAM,WAAW,gBACf,MAAO,GAAW,KAAK,aAAa,cAAc,MAAM,KAAK,GAAG,EAAE,aAAa,UAAU,CAAC,CAC5F;EACA,IAAI,SAAS,SAAS,GACpB,MAAM,IAAID,sBAAAA,aAAa,gCAAgC,KAAK,KAAK,QAAQ,cAAc,KAAK,GAAG;OAC1F,IAAI,SAAS,WAAW,GAAG;GAChC,uBAAuB,SAAS,EAAE;GAClC,OAAO,SAAS;EAClB;CACF;AAEF;;AAGA,SAAS,gBAAkC,UAAoB;CAC7D,OAAO,SAAS,QACb,WAAW,CAAC,OAAO,mBAAoB,OAAiD,mBAC3F;AACF;;AAGA,SAAS,sBAAsB,MAAsB,OAAyB;CAC5E,QAAQ,KAAK,YAAYF,uBAAAA,YAAY,IAAI,CAAC,CAAC,YAAY,CAAC,EAAA,CAAG,SAAS,WAAW;EAC7E,MAAM,QAAiC,CAAC;EACxC,KAAK,MAAM,aAAa,QAAQ;GAC9B,MAAM,QAAQ,KAAK,UAAU;GAC7B,IAAI,CAAC,SAAS,CAAC,yBAAyB,KAAK,GAAG,OAAO,CAAC;GACxD,MAAM,QAAQ,iBAAiB,OAAO,KAAK;GAC3C,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM,OAAO,CAAC;GACnD,MAAM,MAAM,aAAa;EAC3B;EACA,OAAO,CAAC,KAAK;CACf,CAAC;AACH;;AAGA,SAAS,iBAAiB,OAAc,OAAwB;CAC9D,IAAI,OAAO,OAAO,OAAO,MAAM,SAAS,GACtC,OAAO,uBAAwB,MAAkC,MAAM,UAAU;CAEnF,IAAI,MAAM,eAAe,OAAO,OAAO,OAAO,MAAM,WAAW,GAC7D,OAAO,uBAAwB,MAAkC,MAAM,YAAY;AAGvF;;AAGA,SAAS,uBAAuB,OAAyB;CACvD,IAAI,CAACG,eAAAA,SAAS,KAAK,KAAK,SAAS,OAAO,UAAU,YAAY,OAAO,OAAO,OAAO,IAAI,GACrF,OAAQ,MAA2B;CAErC,OAAO;AACT;;AAGA,SAAS,yBAAyB,OAAuB;CACvD,OAAO,MAAM,SAAS,eAAe,MAAM,SAAS,UAAU,MAAM,SAAS,SAAS,MAAM,SAAS;AACvG;AAEA,SAAS,cAAc,OAAuB;CAC5C,OAAO,OAAO,QAAQ,KAAK,CAAC,CACzB,KAAK,CAAC,KAAK,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC,CACxC,KAAK,IAAI;AACd;;AAGA,SAAgB,cAAc,MAAsB,OAAuB;CACzE,MAAM,QAAiC,CAAC;CAIxC,KAAK,MAAM,QAAQ,OAAO,KAAK,KAAK,GAAG;EACrC,MAAM,QAAS,MAAkC,SAAS;EAC1D,MAAM,QAAQ,KAAK,UAAU,KAAK,EAAE,SAAS,cAAc,EAAE,IAAI,MAAM,IAAI;CAC7E;CAEA,OAAO;AACT"}
|
package/build/resurrection.d.cts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EntityMetadata } from "./EntityMetadata.cjs";
|
|
1
2
|
import { EntityManager, MaybeAbstractEntityConstructor } from "./EntityManager.cjs";
|
|
2
3
|
import { Entity } from "./Entity.cjs";
|
|
3
4
|
//#region src/resurrection.d.ts
|
|
@@ -8,6 +9,8 @@ declare function resurrectIfSoftDeleted(entity: Entity): void;
|
|
|
8
9
|
* I.e. with `uniqueBy = [["email"], ["author", "title"]]`, `{ email }` or `{ authorId, title }` can lookup an existing row.
|
|
9
10
|
*/
|
|
10
11
|
declare function findExistingIfUniqueBy<T extends Entity>(em: EntityManager<any, any, any>, constructor: MaybeAbstractEntityConstructor<T>, input: object): Promise<T | undefined>;
|
|
12
|
+
/** Builds exact identity filters for primitives and relations. */
|
|
13
|
+
declare function newWhereEqual(meta: EntityMetadata, input: object): object;
|
|
11
14
|
//#endregion
|
|
12
|
-
export { findExistingIfUniqueBy, resurrectIfSoftDeleted };
|
|
15
|
+
export { findExistingIfUniqueBy, newWhereEqual, resurrectIfSoftDeleted };
|
|
13
16
|
//# sourceMappingURL=resurrection.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resurrection.d.cts","names":[],"sources":["../src/resurrection.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"resurrection.d.cts","names":[],"sources":["../src/resurrection.ts"],"mappings":";;;;;iBAMgB,uBAAuB,QAAQ;;;;;iBAWzB,uBAAuB,UAAU,QACrD,IAAI,8BACJ,aAAa,+BAA+B,IAC5C,gBACC,QAAQ;;iBAkFK,cAAc,MAAM,gBAAgB"}
|
package/build/resurrection.d.mts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EntityMetadata } from "./EntityMetadata.mjs";
|
|
1
2
|
import { EntityManager, MaybeAbstractEntityConstructor } from "./EntityManager.mjs";
|
|
2
3
|
import { Entity } from "./Entity.mjs";
|
|
3
4
|
//#region src/resurrection.d.ts
|
|
@@ -8,6 +9,8 @@ declare function resurrectIfSoftDeleted(entity: Entity): void;
|
|
|
8
9
|
* I.e. with `uniqueBy = [["email"], ["author", "title"]]`, `{ email }` or `{ authorId, title }` can lookup an existing row.
|
|
9
10
|
*/
|
|
10
11
|
declare function findExistingIfUniqueBy<T extends Entity>(em: EntityManager<any, any, any>, constructor: MaybeAbstractEntityConstructor<T>, input: object): Promise<T | undefined>;
|
|
12
|
+
/** Builds exact identity filters for primitives and relations. */
|
|
13
|
+
declare function newWhereEqual(meta: EntityMetadata, input: object): object;
|
|
11
14
|
//#endregion
|
|
12
|
-
export { findExistingIfUniqueBy, resurrectIfSoftDeleted };
|
|
15
|
+
export { findExistingIfUniqueBy, newWhereEqual, resurrectIfSoftDeleted };
|
|
13
16
|
//# sourceMappingURL=resurrection.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resurrection.d.mts","names":[],"sources":["../src/resurrection.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"resurrection.d.mts","names":[],"sources":["../src/resurrection.ts"],"mappings":";;;;;iBAMgB,uBAAuB,QAAQ;;;;;iBAWzB,uBAAuB,UAAU,QACrD,IAAI,8BACJ,aAAa,+BAA+B,IAC5C,gBACC,QAAQ;;iBAkFK,cAAc,MAAM,gBAAgB"}
|
package/build/resurrection.js
CHANGED
|
@@ -23,7 +23,7 @@ async function findExistingIfUniqueBy(em, constructor, input) {
|
|
|
23
23
|
return inMemory[0];
|
|
24
24
|
}
|
|
25
25
|
if (Object.values(where).some((value) => isEntity(value) && value.isNewEntity)) continue;
|
|
26
|
-
const entities = onlySoftDeleted(await em.find(constructor, where, { softDeletes: "include" }));
|
|
26
|
+
const entities = onlySoftDeleted(await em.find(constructor, newWhereEqual(meta, where), { softDeletes: "include" }));
|
|
27
27
|
if (entities.length > 1) throw new TooManyError(`Found more than one existing ${meta.type} with ${whereAsString(where)}`);
|
|
28
28
|
else if (entities.length === 1) {
|
|
29
29
|
resurrectIfSoftDeleted(entities[0]);
|
|
@@ -66,7 +66,16 @@ function isQueryableIdentityField(field) {
|
|
|
66
66
|
function whereAsString(where) {
|
|
67
67
|
return Object.entries(where).map(([key, value]) => `${key}=${value}`).join(", ");
|
|
68
68
|
}
|
|
69
|
+
/** Builds exact identity filters for primitives and relations. */
|
|
70
|
+
function newWhereEqual(meta, input) {
|
|
71
|
+
const where = {};
|
|
72
|
+
for (const name of Object.keys(input)) {
|
|
73
|
+
const value = input[name] ?? null;
|
|
74
|
+
where[name] = meta.allFields[name]?.kind === "primitive" ? { eq: value } : value;
|
|
75
|
+
}
|
|
76
|
+
return where;
|
|
77
|
+
}
|
|
69
78
|
//#endregion
|
|
70
|
-
export { findExistingIfUniqueBy, resurrectIfSoftDeleted };
|
|
79
|
+
export { findExistingIfUniqueBy, newWhereEqual, resurrectIfSoftDeleted };
|
|
71
80
|
|
|
72
81
|
//# sourceMappingURL=resurrection.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resurrection.js","names":[],"sources":["../src/resurrection.ts"],"sourcesContent":["import { type Entity, isEntity } from \"src/Entity.ts\";\nimport { type EntityManager, type MaybeAbstractEntityConstructor, TooManyError } from \"src/EntityManager.ts\";\nimport { type EntityMetadata, type Field, getBaseMeta, getMetadata } from \"src/EntityMetadata.ts\";\nimport { setField } from \"src/fields.ts\";\n\n/** Clears the soft-delete marker so a matched entity is updated instead of recreated. */\nexport function resurrectIfSoftDeleted(entity: Entity): void {\n const deletedAt = getBaseMeta(getMetadata(entity)).timestampFields?.deletedAt;\n if (deletedAt && (entity as any).isSoftDeletedEntity) {\n setField(entity, deletedAt, undefined);\n }\n}\n\n/**\n * Finds an existing entity when the input fully covers one configured `uniqueBy` candidate.\n * I.e. with `uniqueBy = [[\"email\"], [\"author\", \"title\"]]`, `{ email }` or `{ authorId, title }` can lookup an existing row.\n */\nexport async function findExistingIfUniqueBy<T extends Entity>(\n em: EntityManager<any, any, any>,\n constructor: MaybeAbstractEntityConstructor<T>,\n input: object,\n): Promise<T | undefined> {\n const meta = getMetadata(constructor);\n const candidates = getUniqueByCandidates(meta, input);\n for (const where of candidates) {\n const inMemory = onlySoftDeleted(em.filterEntities(constructor as any, where as any) as T[]);\n if (inMemory.length > 1) {\n throw new TooManyError(`Found more than one existing ${meta.type} with ${whereAsString(where)}`);\n } else if (inMemory.length === 1) {\n resurrectIfSoftDeleted(inMemory[0]);\n return inMemory[0];\n }\n\n // I.e. `{ publisher: newPublisherEntity }` cannot already exist in the database, so skip the `em.find`.\n const hasNewEntityParam = Object.values(where).some((value) => isEntity(value) && value.isNewEntity);\n if (hasNewEntityParam) continue;\n\n const entities = onlySoftDeleted(await (em as any).find(constructor, where, { softDeletes: \"include\" })) as T[];\n if (entities.length > 1) {\n throw new TooManyError(`Found more than one existing ${meta.type} with ${whereAsString(where)}`);\n } else if (entities.length === 1) {\n resurrectIfSoftDeleted(entities[0]);\n return entities[0];\n }\n }\n return undefined;\n}\n\n/** Returns soft-deleted entities that are safe for upsert to resurrect. */\nfunction onlySoftDeleted<T extends Entity>(entities: T[]): T[] {\n return entities.filter(\n (entity) => !entity.isDeletedEntity && (entity as T & { isSoftDeletedEntity?: boolean }).isSoftDeletedEntity,\n );\n}\n\n/** Builds pushdown-safe where clauses from configured identity fields. */\nfunction getUniqueByCandidates(meta: EntityMetadata, input: object): object[] {\n return (meta.uniqueBy ?? getBaseMeta(meta).uniqueBy ?? []).flatMap((fields) => {\n const where: Record<string, unknown> = {};\n for (const fieldName of fields) {\n const field = meta.allFields[fieldName];\n if (!field || !isQueryableIdentityField(field)) return [];\n const value = getIdentityValue(field, input);\n if (value === undefined || value === null) return [];\n where[field.fieldName] = value;\n }\n return [where];\n });\n}\n\n/** Returns the identity value from the input. */\nfunction getIdentityValue(field: Field, input: object): unknown {\n if (Object.hasOwn(input, field.fieldName)) {\n return normalizeIdentityValue((input as Record<string, unknown>)[field.fieldName]);\n }\n if (field.fieldIdName && Object.hasOwn(input, field.fieldIdName)) {\n return normalizeIdentityValue((input as Record<string, unknown>)[field.fieldIdName]);\n }\n return undefined;\n}\n\n/** Returns raw ids from relation inputs, i.e. `{ publisher: { id: \"p:1\" } }` becomes `{ publisher: \"p:1\" }`. */\nfunction normalizeIdentityValue(value: unknown): unknown {\n if (!isEntity(value) && value && typeof value === \"object\" && Object.hasOwn(value, \"id\")) {\n return (value as { id?: unknown }).id;\n }\n return value;\n}\n\n/** Returns true if Joist can push this identity field into a flat find clause. */\nfunction isQueryableIdentityField(field: Field): boolean {\n return field.kind === \"primitive\" || field.kind === \"enum\" || field.kind === \"m2o\" || field.kind === \"poly\";\n}\n\nfunction whereAsString(where: object): string {\n return Object.entries(where)\n .map(([key, value]) => `${key}=${value}`)\n .join(\", \");\n}\n"],"mappings":";;;;;;AAMA,SAAgB,uBAAuB,QAAsB;CAC3D,MAAM,YAAY,YAAY,YAAY,MAAM,CAAC,CAAC,CAAC,iBAAiB;CACpE,IAAI,aAAc,OAAe,qBAC/B,SAAS,QAAQ,WAAW,KAAA,CAAS;AAEzC;;;;;AAMA,eAAsB,uBACpB,IACA,aACA,OACwB;CACxB,MAAM,OAAO,YAAY,WAAW;CACpC,MAAM,aAAa,sBAAsB,MAAM,KAAK;CACpD,KAAK,MAAM,SAAS,YAAY;EAC9B,MAAM,WAAW,gBAAgB,GAAG,eAAe,aAAoB,KAAY,CAAQ;EAC3F,IAAI,SAAS,SAAS,GACpB,MAAM,IAAI,aAAa,gCAAgC,KAAK,KAAK,QAAQ,cAAc,KAAK,GAAG;OAC1F,IAAI,SAAS,WAAW,GAAG;GAChC,uBAAuB,SAAS,EAAE;GAClC,OAAO,SAAS;EAClB;EAIA,IAD0B,OAAO,OAAO,KAAK,CAAC,CAAC,MAAM,UAAU,SAAS,KAAK,KAAK,MAAM,WACpE,GAAG;EAEvB,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"resurrection.js","names":[],"sources":["../src/resurrection.ts"],"sourcesContent":["import { type Entity, isEntity } from \"src/Entity.ts\";\nimport { type EntityManager, type MaybeAbstractEntityConstructor, TooManyError } from \"src/EntityManager.ts\";\nimport { type EntityMetadata, type Field, getBaseMeta, getMetadata } from \"src/EntityMetadata.ts\";\nimport { setField } from \"src/fields.ts\";\n\n/** Clears the soft-delete marker so a matched entity is updated instead of recreated. */\nexport function resurrectIfSoftDeleted(entity: Entity): void {\n const deletedAt = getBaseMeta(getMetadata(entity)).timestampFields?.deletedAt;\n if (deletedAt && (entity as any).isSoftDeletedEntity) {\n setField(entity, deletedAt, undefined);\n }\n}\n\n/**\n * Finds an existing entity when the input fully covers one configured `uniqueBy` candidate.\n * I.e. with `uniqueBy = [[\"email\"], [\"author\", \"title\"]]`, `{ email }` or `{ authorId, title }` can lookup an existing row.\n */\nexport async function findExistingIfUniqueBy<T extends Entity>(\n em: EntityManager<any, any, any>,\n constructor: MaybeAbstractEntityConstructor<T>,\n input: object,\n): Promise<T | undefined> {\n const meta = getMetadata(constructor);\n const candidates = getUniqueByCandidates(meta, input);\n for (const where of candidates) {\n const inMemory = onlySoftDeleted(em.filterEntities(constructor as any, where as any) as T[]);\n if (inMemory.length > 1) {\n throw new TooManyError(`Found more than one existing ${meta.type} with ${whereAsString(where)}`);\n } else if (inMemory.length === 1) {\n resurrectIfSoftDeleted(inMemory[0]);\n return inMemory[0];\n }\n\n // I.e. `{ publisher: newPublisherEntity }` cannot already exist in the database, so skip the `em.find`.\n const hasNewEntityParam = Object.values(where).some((value) => isEntity(value) && value.isNewEntity);\n if (hasNewEntityParam) continue;\n\n const entities = onlySoftDeleted(\n await (em as any).find(constructor, newWhereEqual(meta, where), { softDeletes: \"include\" }),\n ) as T[];\n if (entities.length > 1) {\n throw new TooManyError(`Found more than one existing ${meta.type} with ${whereAsString(where)}`);\n } else if (entities.length === 1) {\n resurrectIfSoftDeleted(entities[0]);\n return entities[0];\n }\n }\n return undefined;\n}\n\n/** Returns soft-deleted entities that are safe for upsert to resurrect. */\nfunction onlySoftDeleted<T extends Entity>(entities: T[]): T[] {\n return entities.filter(\n (entity) => !entity.isDeletedEntity && (entity as T & { isSoftDeletedEntity?: boolean }).isSoftDeletedEntity,\n );\n}\n\n/** Builds pushdown-safe where clauses from configured identity fields. */\nfunction getUniqueByCandidates(meta: EntityMetadata, input: object): object[] {\n return (meta.uniqueBy ?? getBaseMeta(meta).uniqueBy ?? []).flatMap((fields) => {\n const where: Record<string, unknown> = {};\n for (const fieldName of fields) {\n const field = meta.allFields[fieldName];\n if (!field || !isQueryableIdentityField(field)) return [];\n const value = getIdentityValue(field, input);\n if (value === undefined || value === null) return [];\n where[field.fieldName] = value;\n }\n return [where];\n });\n}\n\n/** Returns the identity value from the input. */\nfunction getIdentityValue(field: Field, input: object): unknown {\n if (Object.hasOwn(input, field.fieldName)) {\n return normalizeIdentityValue((input as Record<string, unknown>)[field.fieldName]);\n }\n if (field.fieldIdName && Object.hasOwn(input, field.fieldIdName)) {\n return normalizeIdentityValue((input as Record<string, unknown>)[field.fieldIdName]);\n }\n return undefined;\n}\n\n/** Returns raw ids from relation inputs, i.e. `{ publisher: { id: \"p:1\" } }` becomes `{ publisher: \"p:1\" }`. */\nfunction normalizeIdentityValue(value: unknown): unknown {\n if (!isEntity(value) && value && typeof value === \"object\" && Object.hasOwn(value, \"id\")) {\n return (value as { id?: unknown }).id;\n }\n return value;\n}\n\n/** Returns true if Joist can push this identity field into a flat find clause. */\nfunction isQueryableIdentityField(field: Field): boolean {\n return field.kind === \"primitive\" || field.kind === \"enum\" || field.kind === \"m2o\" || field.kind === \"poly\";\n}\n\nfunction whereAsString(where: object): string {\n return Object.entries(where)\n .map(([key, value]) => `${key}=${value}`)\n .join(\", \");\n}\n\n/** Builds exact identity filters for primitives and relations. */\nexport function newWhereEqual(meta: EntityMetadata, input: object): object {\n const where: Record<string, unknown> = {};\n\n // Arrays need eq for exact equality; relations must stay as-is because eq would be a nested entity filter.\n // Unset values must match SQL NULL, e.g. publisher: undefined must not omit the publisher filter.\n for (const name of Object.keys(input)) {\n const value = (input as Record<string, unknown>)[name] ?? null;\n where[name] = meta.allFields[name]?.kind === \"primitive\" ? { eq: value } : value;\n }\n\n return where;\n}\n"],"mappings":";;;;;;AAMA,SAAgB,uBAAuB,QAAsB;CAC3D,MAAM,YAAY,YAAY,YAAY,MAAM,CAAC,CAAC,CAAC,iBAAiB;CACpE,IAAI,aAAc,OAAe,qBAC/B,SAAS,QAAQ,WAAW,KAAA,CAAS;AAEzC;;;;;AAMA,eAAsB,uBACpB,IACA,aACA,OACwB;CACxB,MAAM,OAAO,YAAY,WAAW;CACpC,MAAM,aAAa,sBAAsB,MAAM,KAAK;CACpD,KAAK,MAAM,SAAS,YAAY;EAC9B,MAAM,WAAW,gBAAgB,GAAG,eAAe,aAAoB,KAAY,CAAQ;EAC3F,IAAI,SAAS,SAAS,GACpB,MAAM,IAAI,aAAa,gCAAgC,KAAK,KAAK,QAAQ,cAAc,KAAK,GAAG;OAC1F,IAAI,SAAS,WAAW,GAAG;GAChC,uBAAuB,SAAS,EAAE;GAClC,OAAO,SAAS;EAClB;EAIA,IAD0B,OAAO,OAAO,KAAK,CAAC,CAAC,MAAM,UAAU,SAAS,KAAK,KAAK,MAAM,WACpE,GAAG;EAEvB,MAAM,WAAW,gBACf,MAAO,GAAW,KAAK,aAAa,cAAc,MAAM,KAAK,GAAG,EAAE,aAAa,UAAU,CAAC,CAC5F;EACA,IAAI,SAAS,SAAS,GACpB,MAAM,IAAI,aAAa,gCAAgC,KAAK,KAAK,QAAQ,cAAc,KAAK,GAAG;OAC1F,IAAI,SAAS,WAAW,GAAG;GAChC,uBAAuB,SAAS,EAAE;GAClC,OAAO,SAAS;EAClB;CACF;AAEF;;AAGA,SAAS,gBAAkC,UAAoB;CAC7D,OAAO,SAAS,QACb,WAAW,CAAC,OAAO,mBAAoB,OAAiD,mBAC3F;AACF;;AAGA,SAAS,sBAAsB,MAAsB,OAAyB;CAC5E,QAAQ,KAAK,YAAY,YAAY,IAAI,CAAC,CAAC,YAAY,CAAC,EAAA,CAAG,SAAS,WAAW;EAC7E,MAAM,QAAiC,CAAC;EACxC,KAAK,MAAM,aAAa,QAAQ;GAC9B,MAAM,QAAQ,KAAK,UAAU;GAC7B,IAAI,CAAC,SAAS,CAAC,yBAAyB,KAAK,GAAG,OAAO,CAAC;GACxD,MAAM,QAAQ,iBAAiB,OAAO,KAAK;GAC3C,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM,OAAO,CAAC;GACnD,MAAM,MAAM,aAAa;EAC3B;EACA,OAAO,CAAC,KAAK;CACf,CAAC;AACH;;AAGA,SAAS,iBAAiB,OAAc,OAAwB;CAC9D,IAAI,OAAO,OAAO,OAAO,MAAM,SAAS,GACtC,OAAO,uBAAwB,MAAkC,MAAM,UAAU;CAEnF,IAAI,MAAM,eAAe,OAAO,OAAO,OAAO,MAAM,WAAW,GAC7D,OAAO,uBAAwB,MAAkC,MAAM,YAAY;AAGvF;;AAGA,SAAS,uBAAuB,OAAyB;CACvD,IAAI,CAAC,SAAS,KAAK,KAAK,SAAS,OAAO,UAAU,YAAY,OAAO,OAAO,OAAO,IAAI,GACrF,OAAQ,MAA2B;CAErC,OAAO;AACT;;AAGA,SAAS,yBAAyB,OAAuB;CACvD,OAAO,MAAM,SAAS,eAAe,MAAM,SAAS,UAAU,MAAM,SAAS,SAAS,MAAM,SAAS;AACvG;AAEA,SAAS,cAAc,OAAuB;CAC5C,OAAO,OAAO,QAAQ,KAAK,CAAC,CACzB,KAAK,CAAC,KAAK,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC,CACxC,KAAK,IAAI;AACd;;AAGA,SAAgB,cAAc,MAAsB,OAAuB;CACzE,MAAM,QAAiC,CAAC;CAIxC,KAAK,MAAM,QAAQ,OAAO,KAAK,KAAK,GAAG;EACrC,MAAM,QAAS,MAAkC,SAAS;EAC1D,MAAM,QAAQ,KAAK,UAAU,KAAK,EAAE,SAAS,cAAc,EAAE,IAAI,MAAM,IAAI;CAC7E;CAEA,OAAO;AACT"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_utils = require("./utils.cjs");
|
|
3
|
+
const require_EntityMetadata = require("./EntityMetadata.cjs");
|
|
4
|
+
//#region src/transitions.ts
|
|
5
|
+
/** The "from" state of a just-created entity, because `undefined` can be a real field value. */
|
|
6
|
+
const created = Symbol("created");
|
|
7
|
+
/** Converts an internal step to the `Transition` that guards and reactions receive. */
|
|
8
|
+
function toTransition(step) {
|
|
9
|
+
return {
|
|
10
|
+
from: step.from === created ? void 0 : step.from,
|
|
11
|
+
to: step.to
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Merges the transition config of `meta` and its base types by field, converting accessors to codes.
|
|
16
|
+
*
|
|
17
|
+
* This runs after boot, because the config is written before the enum metadata exists.
|
|
18
|
+
*/
|
|
19
|
+
function buildFieldTransitions(meta) {
|
|
20
|
+
const byField = /* @__PURE__ */ new Map();
|
|
21
|
+
for (const m of require_EntityMetadata.getBaseAndSelfMetas(meta)) for (const [fieldName, config] of Object.entries(m.config.__data.transitions)) {
|
|
22
|
+
let merged = byField.get(fieldName);
|
|
23
|
+
if (!merged) byField.set(fieldName, merged = {
|
|
24
|
+
tables: [],
|
|
25
|
+
guards: [],
|
|
26
|
+
callbacks: []
|
|
27
|
+
});
|
|
28
|
+
if (config.table) merged.tables.push(toCodesTable(meta, fieldName, config.table));
|
|
29
|
+
for (const { match, run } of config.guards) merged.guards.push({
|
|
30
|
+
matcher: toMatcher(meta, fieldName, match),
|
|
31
|
+
run
|
|
32
|
+
});
|
|
33
|
+
for (const { name, match, run } of config.callbacks) {
|
|
34
|
+
const matcher = toMatcher(meta, fieldName, match);
|
|
35
|
+
merged.callbacks.push({
|
|
36
|
+
name,
|
|
37
|
+
phase: match.phase ?? "flush",
|
|
38
|
+
matcher,
|
|
39
|
+
run
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return byField;
|
|
44
|
+
}
|
|
45
|
+
/** Returns whether `step` matches `matcher`'s `from`, `to`, and `onCreate` settings. */
|
|
46
|
+
function matchesTransition(matcher, step) {
|
|
47
|
+
if (step.from === created) {
|
|
48
|
+
if (!matcher.onCreate) return false;
|
|
49
|
+
} else if (matcher.from && !matcher.from.includes(step.from)) return false;
|
|
50
|
+
return !matcher.to || matcher.to.includes(step.to);
|
|
51
|
+
}
|
|
52
|
+
/** Uses the enum's display name in error messages when there is one, i.e. `Approved` instead of `APPROVED`. */
|
|
53
|
+
function describeValue(meta, fieldName, value) {
|
|
54
|
+
if (value === void 0) return "unset";
|
|
55
|
+
const field = meta.allFields[fieldName];
|
|
56
|
+
return field?.kind === "enum" ? field.enumDetailType.getByCode(value)?.name ?? String(value) : String(value);
|
|
57
|
+
}
|
|
58
|
+
/** Converts a `transitions` table's keys and values to codes, so it can be compared to field values. */
|
|
59
|
+
function toCodesTable(meta, fieldName, table) {
|
|
60
|
+
return new Map(Object.entries(table).map(([from, tos]) => [accessorToCode(meta, fieldName, from), toCodes(meta, fieldName, tos ?? [])]));
|
|
61
|
+
}
|
|
62
|
+
/** Converts a `match`'s accessors to codes. */
|
|
63
|
+
function toMatcher(meta, fieldName, match) {
|
|
64
|
+
return {
|
|
65
|
+
from: toCodes(meta, fieldName, match.from),
|
|
66
|
+
to: toCodes(meta, fieldName, match.to),
|
|
67
|
+
onCreate: match.onCreate !== false && match.from === void 0
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** Converts an enum accessor, i.e. `Rejected`, to its code, i.e. `REJECTED`, and fails on anything else. */
|
|
71
|
+
function accessorToCode(meta, fieldName, value) {
|
|
72
|
+
const field = meta.allFields[fieldName];
|
|
73
|
+
if (field?.kind !== "enum") require_utils.fail(`${meta.type}.${fieldName} is not an enum field, so it can't have transitions`);
|
|
74
|
+
if (typeof value !== "string" || !Object.hasOwn(field.enumType, value)) require_utils.fail(`Unknown ${meta.type}.${fieldName} accessor ${String(value)}, i.e. use "Draft" for AuthorStatus.Draft`);
|
|
75
|
+
return field.enumType[value];
|
|
76
|
+
}
|
|
77
|
+
/** Converts one or more enum accessors to an array of codes, preserving an omitted match. */
|
|
78
|
+
function toCodes(meta, fieldName, values) {
|
|
79
|
+
if (values === void 0) return void 0;
|
|
80
|
+
return typeof values === "string" ? [accessorToCode(meta, fieldName, values)] : values.map((v) => accessorToCode(meta, fieldName, v));
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
exports.buildFieldTransitions = buildFieldTransitions;
|
|
84
|
+
exports.created = created;
|
|
85
|
+
exports.describeValue = describeValue;
|
|
86
|
+
exports.matchesTransition = matchesTransition;
|
|
87
|
+
exports.toTransition = toTransition;
|
|
88
|
+
|
|
89
|
+
//# sourceMappingURL=transitions.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transitions.cjs","names":["getBaseAndSelfMetas"],"sources":["../src/transitions.ts"],"sourcesContent":["import type { Entity } from \"src/Entity.ts\";\nimport { type EntityMetadata, getBaseAndSelfMetas } from \"src/EntityMetadata.ts\";\nimport type { ValidationRuleResult } from \"src/rules.ts\";\nimport { type MaybePromise, fail } from \"src/utils.ts\";\n\n/**\n * Which state transitions of an enum field a `guardTransition` cares about.\n *\n * Omitting `from` or `to` matches any state. Guards never run on creation.\n */\nexport interface GuardTransitionMatch<V> {\n from?: V | readonly V[];\n to?: V | readonly V[];\n}\n\n/**\n * Which state transitions of an enum field an `onTransition` callback handles.\n *\n * Creating an entity with a matching `to` state also matches, unless `from` is set or `onCreate` is `false`.\n */\nexport interface OnTransitionMatch<V> extends GuardTransitionMatch<V> {\n /**\n * Whether `onTransition` fires when an entity is created with a matching `to` state, defaults to `true`.\n *\n * Entering a state by creation usually needs the same side effects as entering it by a transition, and\n * forgetting them is easy to miss. Set `false` for reactions that only make sense for a transition.\n */\n onCreate?: boolean;\n /** `commit` runs in `beforeCommit`, i.e. to enqueue jobs; `flush` (the default) runs as a reaction. */\n phase?: \"flush\" | \"commit\";\n}\n\n/** Maps each state to the states it may transition to, i.e. `{ Draft: [\"Open\"], Open: [\"Closed\"] }`. */\nexport type TransitionTable<V extends PropertyKey> = Partial<Record<V, readonly V[]>>;\n\n/**\n * The transition a guard or reaction is handling, i.e. `{ from: \"DRAFT\", to: \"OPEN\" }`.\n *\n * Guards and callbacks run during reaction passes, after the assignment, so the entity may already be in a\n * later state. This tells them which transition they're handling. `from` is `undefined` for creation.\n */\nexport interface Transition<V> {\n from: V | undefined;\n to: V;\n}\n\n/** The \"from\" state of a just-created entity, because `undefined` can be a real field value. */\nexport const created: unique symbol = Symbol(\"created\");\n\n/** A single recorded transition of a field, with `from` as `created` for creation. */\nexport interface TransitionStep {\n from: unknown;\n to: unknown;\n}\n\n/** A `guardTransition` as registered in config, with its `match` still using accessors. */\nexport interface TransitionGuardConfig<T extends Entity = Entity> {\n match: GuardTransitionMatch<string>;\n run(entity: T, step: TransitionStep): MaybePromise<ValidationRuleResult>;\n}\n\n/** An `onTransition` as registered in config, with its `match` still using accessors. */\nexport interface TransitionCallbackConfig<T extends Entity = Entity, C = unknown> {\n name: string;\n match: OnTransitionMatch<string>;\n run(entity: T, ctx: C, step: TransitionStep): MaybePromise<unknown>;\n}\n\n/** One field's `transitions`, `guardTransition`, and `onTransition` config on a single entity type. */\nexport interface FieldTransitionsConfig<T extends Entity = Entity, C = unknown> {\n /** The `transitions` table, if any; subtypes add their own tables on their own configs. */\n table?: TransitionTable<string>;\n guards: TransitionGuardConfig<T>[];\n callbacks: TransitionCallbackConfig<T, C>[];\n}\n\n/** A `match` with accessors converted to codes, so it can be compared to recorded steps. */\nexport interface TransitionMatcher {\n /** The allowed previous states, or `undefined` for any state. */\n from: readonly unknown[] | undefined;\n /** The allowed new states, or `undefined` for any state. */\n to: readonly unknown[] | undefined;\n /** Whether creation matches, which is never true when `from` is set. */\n onCreate: boolean;\n}\n\n/** A guard with its `match` converted to codes. */\nexport interface TransitionGuard {\n matcher: TransitionMatcher;\n run(entity: Entity, step: TransitionStep): MaybePromise<ValidationRuleResult>;\n}\n\n/** A callback with its `match` converted to codes, dispatched only after its transition's guards pass. */\nexport interface TransitionCallback {\n name: string;\n phase: \"flush\" | \"commit\";\n matcher: TransitionMatcher;\n run(entity: Entity, ctx: unknown, step: TransitionStep): MaybePromise<unknown>;\n}\n\n/** One field's transition config from an entity type and its base types, with accessors converted to codes. */\nexport interface FieldTransitions {\n /** Each table maps a `from` code to its allowed `to` codes; every table must allow a change. */\n tables: ReadonlyMap<unknown, readonly unknown[]>[];\n guards: TransitionGuard[];\n callbacks: TransitionCallback[];\n}\n\n/** Converts an internal step to the `Transition` that guards and reactions receive. */\nexport function toTransition(step: TransitionStep): Transition<any> {\n return { from: step.from === created ? undefined : step.from, to: step.to };\n}\n\n/**\n * Merges the transition config of `meta` and its base types by field, converting accessors to codes.\n *\n * This runs after boot, because the config is written before the enum metadata exists.\n */\nexport function buildFieldTransitions(meta: EntityMetadata): Map<string, FieldTransitions> {\n const byField = new Map<string, FieldTransitions>();\n for (const m of getBaseAndSelfMetas(meta)) {\n for (const [fieldName, config] of Object.entries(m.config.__data.transitions)) {\n let merged = byField.get(fieldName);\n if (!merged) byField.set(fieldName, (merged = { tables: [], guards: [], callbacks: [] }));\n if (config.table) merged.tables.push(toCodesTable(meta, fieldName, config.table));\n for (const { match, run } of config.guards) {\n merged.guards.push({ matcher: toMatcher(meta, fieldName, match), run });\n }\n for (const { name, match, run } of config.callbacks) {\n const matcher = toMatcher(meta, fieldName, match);\n merged.callbacks.push({ name, phase: match.phase ?? \"flush\", matcher, run });\n }\n }\n }\n return byField;\n}\n\n/** Returns whether `step` matches `matcher`'s `from`, `to`, and `onCreate` settings. */\nexport function matchesTransition(matcher: TransitionMatcher, step: TransitionStep): boolean {\n if (step.from === created) {\n if (!matcher.onCreate) return false;\n } else if (matcher.from && !matcher.from.includes(step.from)) {\n return false;\n }\n return !matcher.to || matcher.to.includes(step.to);\n}\n\n/** Uses the enum's display name in error messages when there is one, i.e. `Approved` instead of `APPROVED`. */\nexport function describeValue(meta: EntityMetadata, fieldName: string, value: unknown): string {\n if (value === undefined) return \"unset\";\n const field = meta.allFields[fieldName];\n return field?.kind === \"enum\" ? (field.enumDetailType.getByCode(value)?.name ?? String(value)) : String(value);\n}\n\n/** Converts a `transitions` table's keys and values to codes, so it can be compared to field values. */\nfunction toCodesTable(\n meta: EntityMetadata,\n fieldName: string,\n table: TransitionTable<string>,\n): Map<unknown, readonly unknown[]> {\n return new Map(\n Object.entries(table).map(([from, tos]) => [\n accessorToCode(meta, fieldName, from),\n toCodes(meta, fieldName, tos ?? [])!,\n ]),\n );\n}\n\n/** Converts a `match`'s accessors to codes. */\nfunction toMatcher(meta: EntityMetadata, fieldName: string, match: OnTransitionMatch<string>): TransitionMatcher {\n return {\n from: toCodes(meta, fieldName, match.from),\n to: toCodes(meta, fieldName, match.to),\n // `from` describes a previous state, which a new entity doesn't have\n onCreate: match.onCreate !== false && match.from === undefined,\n };\n}\n\n/** Converts an enum accessor, i.e. `Rejected`, to its code, i.e. `REJECTED`, and fails on anything else. */\nfunction accessorToCode(meta: EntityMetadata, fieldName: string, value: unknown): unknown {\n const field = meta.allFields[fieldName];\n if (field?.kind !== \"enum\") fail(`${meta.type}.${fieldName} is not an enum field, so it can't have transitions`);\n // Only accessors are accepted, even from untyped callers, so every table reads the same way\n if (typeof value !== \"string\" || !Object.hasOwn(field.enumType, value)) {\n fail(`Unknown ${meta.type}.${fieldName} accessor ${String(value)}, i.e. use \"Draft\" for AuthorStatus.Draft`);\n }\n return field.enumType[value];\n}\n\n/** Converts one or more enum accessors to an array of codes, preserving an omitted match. */\nfunction toCodes(\n meta: EntityMetadata,\n fieldName: string,\n values: string | readonly string[] | undefined,\n): unknown[] | undefined {\n if (values === undefined) return undefined;\n return typeof values === \"string\"\n ? [accessorToCode(meta, fieldName, values)]\n : values.map((v) => accessorToCode(meta, fieldName, v));\n}\n"],"mappings":";;;;;AA+CA,MAAa,UAAyB,OAAO,SAAS;;AA8DtD,SAAgB,aAAa,MAAuC;CAClE,OAAO;EAAE,MAAM,KAAK,SAAS,UAAU,KAAA,IAAY,KAAK;EAAM,IAAI,KAAK;CAAG;AAC5E;;;;;;AAOA,SAAgB,sBAAsB,MAAqD;CACzF,MAAM,0BAAU,IAAI,IAA8B;CAClD,KAAK,MAAM,KAAKA,uBAAAA,oBAAoB,IAAI,GACtC,KAAK,MAAM,CAAC,WAAW,WAAW,OAAO,QAAQ,EAAE,OAAO,OAAO,WAAW,GAAG;EAC7E,IAAI,SAAS,QAAQ,IAAI,SAAS;EAClC,IAAI,CAAC,QAAQ,QAAQ,IAAI,WAAY,SAAS;GAAE,QAAQ,CAAC;GAAG,QAAQ,CAAC;GAAG,WAAW,CAAC;EAAE,CAAE;EACxF,IAAI,OAAO,OAAO,OAAO,OAAO,KAAK,aAAa,MAAM,WAAW,OAAO,KAAK,CAAC;EAChF,KAAK,MAAM,EAAE,OAAO,SAAS,OAAO,QAClC,OAAO,OAAO,KAAK;GAAE,SAAS,UAAU,MAAM,WAAW,KAAK;GAAG;EAAI,CAAC;EAExE,KAAK,MAAM,EAAE,MAAM,OAAO,SAAS,OAAO,WAAW;GACnD,MAAM,UAAU,UAAU,MAAM,WAAW,KAAK;GAChD,OAAO,UAAU,KAAK;IAAE;IAAM,OAAO,MAAM,SAAS;IAAS;IAAS;GAAI,CAAC;EAC7E;CACF;CAEF,OAAO;AACT;;AAGA,SAAgB,kBAAkB,SAA4B,MAA+B;CAC3F,IAAI,KAAK,SAAS,SACZ;MAAA,CAAC,QAAQ,UAAU,OAAO;CAAA,OACzB,IAAI,QAAQ,QAAQ,CAAC,QAAQ,KAAK,SAAS,KAAK,IAAI,GACzD,OAAO;CAET,OAAO,CAAC,QAAQ,MAAM,QAAQ,GAAG,SAAS,KAAK,EAAE;AACnD;;AAGA,SAAgB,cAAc,MAAsB,WAAmB,OAAwB;CAC7F,IAAI,UAAU,KAAA,GAAW,OAAO;CAChC,MAAM,QAAQ,KAAK,UAAU;CAC7B,OAAO,OAAO,SAAS,SAAU,MAAM,eAAe,UAAU,KAAK,CAAC,EAAE,QAAQ,OAAO,KAAK,IAAK,OAAO,KAAK;AAC/G;;AAGA,SAAS,aACP,MACA,WACA,OACkC;CAClC,OAAO,IAAI,IACT,OAAO,QAAQ,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,SAAS,CACzC,eAAe,MAAM,WAAW,IAAI,GACpC,QAAQ,MAAM,WAAW,OAAO,CAAC,CAAC,CACpC,CAAC,CACH;AACF;;AAGA,SAAS,UAAU,MAAsB,WAAmB,OAAqD;CAC/G,OAAO;EACL,MAAM,QAAQ,MAAM,WAAW,MAAM,IAAI;EACzC,IAAI,QAAQ,MAAM,WAAW,MAAM,EAAE;EAErC,UAAU,MAAM,aAAa,SAAS,MAAM,SAAS,KAAA;CACvD;AACF;;AAGA,SAAS,eAAe,MAAsB,WAAmB,OAAyB;CACxF,MAAM,QAAQ,KAAK,UAAU;CAC7B,IAAI,OAAO,SAAS,QAAQ,cAAA,KAAK,GAAG,KAAK,KAAK,GAAG,UAAU,oDAAoD;CAE/G,IAAI,OAAO,UAAU,YAAY,CAAC,OAAO,OAAO,MAAM,UAAU,KAAK,GACnE,cAAA,KAAK,WAAW,KAAK,KAAK,GAAG,UAAU,YAAY,OAAO,KAAK,EAAE,0CAA0C;CAE7G,OAAO,MAAM,SAAS;AACxB;;AAGA,SAAS,QACP,MACA,WACA,QACuB;CACvB,IAAI,WAAW,KAAA,GAAW,OAAO,KAAA;CACjC,OAAO,OAAO,WAAW,WACrB,CAAC,eAAe,MAAM,WAAW,MAAM,CAAC,IACxC,OAAO,KAAK,MAAM,eAAe,MAAM,WAAW,CAAC,CAAC;AAC1D"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { MaybePromise } from "./utils.cjs";
|
|
2
|
+
import { ValidationRuleResult } from "./rules.cjs";
|
|
3
|
+
import { EntityMetadata } from "./EntityMetadata.cjs";
|
|
4
|
+
import { Entity } from "./Entity.cjs";
|
|
5
|
+
//#region src/transitions.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Which state transitions of an enum field a `guardTransition` cares about.
|
|
8
|
+
*
|
|
9
|
+
* Omitting `from` or `to` matches any state. Guards never run on creation.
|
|
10
|
+
*/
|
|
11
|
+
interface GuardTransitionMatch<V> {
|
|
12
|
+
from?: V | readonly V[];
|
|
13
|
+
to?: V | readonly V[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Which state transitions of an enum field an `onTransition` callback handles.
|
|
17
|
+
*
|
|
18
|
+
* Creating an entity with a matching `to` state also matches, unless `from` is set or `onCreate` is `false`.
|
|
19
|
+
*/
|
|
20
|
+
interface OnTransitionMatch<V> extends GuardTransitionMatch<V> {
|
|
21
|
+
/**
|
|
22
|
+
* Whether `onTransition` fires when an entity is created with a matching `to` state, defaults to `true`.
|
|
23
|
+
*
|
|
24
|
+
* Entering a state by creation usually needs the same side effects as entering it by a transition, and
|
|
25
|
+
* forgetting them is easy to miss. Set `false` for reactions that only make sense for a transition.
|
|
26
|
+
*/
|
|
27
|
+
onCreate?: boolean;
|
|
28
|
+
/** `commit` runs in `beforeCommit`, i.e. to enqueue jobs; `flush` (the default) runs as a reaction. */
|
|
29
|
+
phase?: "flush" | "commit";
|
|
30
|
+
}
|
|
31
|
+
/** Maps each state to the states it may transition to, i.e. `{ Draft: ["Open"], Open: ["Closed"] }`. */
|
|
32
|
+
type TransitionTable<V extends PropertyKey> = Partial<Record<V, readonly V[]>>;
|
|
33
|
+
/**
|
|
34
|
+
* The transition a guard or reaction is handling, i.e. `{ from: "DRAFT", to: "OPEN" }`.
|
|
35
|
+
*
|
|
36
|
+
* Guards and callbacks run during reaction passes, after the assignment, so the entity may already be in a
|
|
37
|
+
* later state. This tells them which transition they're handling. `from` is `undefined` for creation.
|
|
38
|
+
*/
|
|
39
|
+
interface Transition<V> {
|
|
40
|
+
from: V | undefined;
|
|
41
|
+
to: V;
|
|
42
|
+
}
|
|
43
|
+
/** The "from" state of a just-created entity, because `undefined` can be a real field value. */
|
|
44
|
+
declare const created: unique symbol;
|
|
45
|
+
/** A single recorded transition of a field, with `from` as `created` for creation. */
|
|
46
|
+
interface TransitionStep {
|
|
47
|
+
from: unknown;
|
|
48
|
+
to: unknown;
|
|
49
|
+
}
|
|
50
|
+
/** A `guardTransition` as registered in config, with its `match` still using accessors. */
|
|
51
|
+
interface TransitionGuardConfig<T extends Entity = Entity> {
|
|
52
|
+
match: GuardTransitionMatch<string>;
|
|
53
|
+
run(entity: T, step: TransitionStep): MaybePromise<ValidationRuleResult>;
|
|
54
|
+
}
|
|
55
|
+
/** An `onTransition` as registered in config, with its `match` still using accessors. */
|
|
56
|
+
interface TransitionCallbackConfig<T extends Entity = Entity, C = unknown> {
|
|
57
|
+
name: string;
|
|
58
|
+
match: OnTransitionMatch<string>;
|
|
59
|
+
run(entity: T, ctx: C, step: TransitionStep): MaybePromise<unknown>;
|
|
60
|
+
}
|
|
61
|
+
/** One field's `transitions`, `guardTransition`, and `onTransition` config on a single entity type. */
|
|
62
|
+
interface FieldTransitionsConfig<T extends Entity = Entity, C = unknown> {
|
|
63
|
+
/** The `transitions` table, if any; subtypes add their own tables on their own configs. */
|
|
64
|
+
table?: TransitionTable<string>;
|
|
65
|
+
guards: TransitionGuardConfig<T>[];
|
|
66
|
+
callbacks: TransitionCallbackConfig<T, C>[];
|
|
67
|
+
}
|
|
68
|
+
/** A `match` with accessors converted to codes, so it can be compared to recorded steps. */
|
|
69
|
+
interface TransitionMatcher {
|
|
70
|
+
/** The allowed previous states, or `undefined` for any state. */
|
|
71
|
+
from: readonly unknown[] | undefined;
|
|
72
|
+
/** The allowed new states, or `undefined` for any state. */
|
|
73
|
+
to: readonly unknown[] | undefined;
|
|
74
|
+
/** Whether creation matches, which is never true when `from` is set. */
|
|
75
|
+
onCreate: boolean;
|
|
76
|
+
}
|
|
77
|
+
/** A guard with its `match` converted to codes. */
|
|
78
|
+
interface TransitionGuard {
|
|
79
|
+
matcher: TransitionMatcher;
|
|
80
|
+
run(entity: Entity, step: TransitionStep): MaybePromise<ValidationRuleResult>;
|
|
81
|
+
}
|
|
82
|
+
/** A callback with its `match` converted to codes, dispatched only after its transition's guards pass. */
|
|
83
|
+
interface TransitionCallback {
|
|
84
|
+
name: string;
|
|
85
|
+
phase: "flush" | "commit";
|
|
86
|
+
matcher: TransitionMatcher;
|
|
87
|
+
run(entity: Entity, ctx: unknown, step: TransitionStep): MaybePromise<unknown>;
|
|
88
|
+
}
|
|
89
|
+
/** One field's transition config from an entity type and its base types, with accessors converted to codes. */
|
|
90
|
+
interface FieldTransitions {
|
|
91
|
+
/** Each table maps a `from` code to its allowed `to` codes; every table must allow a change. */
|
|
92
|
+
tables: ReadonlyMap<unknown, readonly unknown[]>[];
|
|
93
|
+
guards: TransitionGuard[];
|
|
94
|
+
callbacks: TransitionCallback[];
|
|
95
|
+
}
|
|
96
|
+
/** Converts an internal step to the `Transition` that guards and reactions receive. */
|
|
97
|
+
declare function toTransition(step: TransitionStep): Transition<any>;
|
|
98
|
+
/**
|
|
99
|
+
* Merges the transition config of `meta` and its base types by field, converting accessors to codes.
|
|
100
|
+
*
|
|
101
|
+
* This runs after boot, because the config is written before the enum metadata exists.
|
|
102
|
+
*/
|
|
103
|
+
declare function buildFieldTransitions(meta: EntityMetadata): Map<string, FieldTransitions>;
|
|
104
|
+
/** Returns whether `step` matches `matcher`'s `from`, `to`, and `onCreate` settings. */
|
|
105
|
+
declare function matchesTransition(matcher: TransitionMatcher, step: TransitionStep): boolean;
|
|
106
|
+
/** Uses the enum's display name in error messages when there is one, i.e. `Approved` instead of `APPROVED`. */
|
|
107
|
+
declare function describeValue(meta: EntityMetadata, fieldName: string, value: unknown): string;
|
|
108
|
+
//#endregion
|
|
109
|
+
export { FieldTransitions, FieldTransitionsConfig, GuardTransitionMatch, OnTransitionMatch, Transition, TransitionCallback, TransitionCallbackConfig, TransitionGuard, TransitionGuardConfig, TransitionMatcher, TransitionStep, TransitionTable, buildFieldTransitions, created, describeValue, matchesTransition, toTransition };
|
|
110
|
+
//# sourceMappingURL=transitions.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transitions.d.cts","names":[],"sources":["../src/transitions.ts"],"mappings":";;;;;;;;;;UAUiB,qBAAqB;EACpC,OAAO,aAAa;EACpB,KAAK,aAAa;;;;;;;UAQH,kBAAkB,WAAW,qBAAqB;;;;;;;EAOjE;;EAEA;;;KAIU,gBAAgB,UAAU,eAAe,QAAQ,OAAO,YAAY;;;;;;;UAQ/D,WAAW;EAC1B,MAAM;EACN,IAAI;;;cAIO;;UAGI;EACf;EACA;;;UAIe,sBAAsB,UAAU,SAAS;EACxD,OAAO;EACP,IAAI,QAAQ,GAAG,MAAM,iBAAiB,aAAa;;;UAIpC,yBAAyB,UAAU,SAAS,QAAQ;EACnE;EACA,OAAO;EACP,IAAI,QAAQ,GAAG,KAAK,GAAG,MAAM,iBAAiB;;;UAI/B,uBAAuB,UAAU,SAAS,QAAQ;;EAEjE,QAAQ;EACR,QAAQ,sBAAsB;EAC9B,WAAW,yBAAyB,GAAG;;;UAIxB;;EAEf;;EAEA;;EAEA;;;UAIe;EACf,SAAS;EACT,IAAI,QAAQ,QAAQ,MAAM,iBAAiB,aAAa;;;UAIzC;EACf;EACA;EACA,SAAS;EACT,IAAI,QAAQ,QAAQ,cAAc,MAAM,iBAAiB;;;UAI1C;;EAEf,QAAQ;EACR,QAAQ;EACR,WAAW;;;iBAIG,aAAa,MAAM,iBAAiB;;;;;;iBASpC,sBAAsB,MAAM,iBAAiB,YAAY;;iBAoBzD,kBAAkB,SAAS,mBAAmB,MAAM;;iBAUpD,cAAc,MAAM,gBAAgB,mBAAmB"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { MaybePromise } from "./utils.mjs";
|
|
2
|
+
import { ValidationRuleResult } from "./rules.mjs";
|
|
3
|
+
import { EntityMetadata } from "./EntityMetadata.mjs";
|
|
4
|
+
import { Entity } from "./Entity.mjs";
|
|
5
|
+
//#region src/transitions.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Which state transitions of an enum field a `guardTransition` cares about.
|
|
8
|
+
*
|
|
9
|
+
* Omitting `from` or `to` matches any state. Guards never run on creation.
|
|
10
|
+
*/
|
|
11
|
+
interface GuardTransitionMatch<V> {
|
|
12
|
+
from?: V | readonly V[];
|
|
13
|
+
to?: V | readonly V[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Which state transitions of an enum field an `onTransition` callback handles.
|
|
17
|
+
*
|
|
18
|
+
* Creating an entity with a matching `to` state also matches, unless `from` is set or `onCreate` is `false`.
|
|
19
|
+
*/
|
|
20
|
+
interface OnTransitionMatch<V> extends GuardTransitionMatch<V> {
|
|
21
|
+
/**
|
|
22
|
+
* Whether `onTransition` fires when an entity is created with a matching `to` state, defaults to `true`.
|
|
23
|
+
*
|
|
24
|
+
* Entering a state by creation usually needs the same side effects as entering it by a transition, and
|
|
25
|
+
* forgetting them is easy to miss. Set `false` for reactions that only make sense for a transition.
|
|
26
|
+
*/
|
|
27
|
+
onCreate?: boolean;
|
|
28
|
+
/** `commit` runs in `beforeCommit`, i.e. to enqueue jobs; `flush` (the default) runs as a reaction. */
|
|
29
|
+
phase?: "flush" | "commit";
|
|
30
|
+
}
|
|
31
|
+
/** Maps each state to the states it may transition to, i.e. `{ Draft: ["Open"], Open: ["Closed"] }`. */
|
|
32
|
+
type TransitionTable<V extends PropertyKey> = Partial<Record<V, readonly V[]>>;
|
|
33
|
+
/**
|
|
34
|
+
* The transition a guard or reaction is handling, i.e. `{ from: "DRAFT", to: "OPEN" }`.
|
|
35
|
+
*
|
|
36
|
+
* Guards and callbacks run during reaction passes, after the assignment, so the entity may already be in a
|
|
37
|
+
* later state. This tells them which transition they're handling. `from` is `undefined` for creation.
|
|
38
|
+
*/
|
|
39
|
+
interface Transition<V> {
|
|
40
|
+
from: V | undefined;
|
|
41
|
+
to: V;
|
|
42
|
+
}
|
|
43
|
+
/** The "from" state of a just-created entity, because `undefined` can be a real field value. */
|
|
44
|
+
declare const created: unique symbol;
|
|
45
|
+
/** A single recorded transition of a field, with `from` as `created` for creation. */
|
|
46
|
+
interface TransitionStep {
|
|
47
|
+
from: unknown;
|
|
48
|
+
to: unknown;
|
|
49
|
+
}
|
|
50
|
+
/** A `guardTransition` as registered in config, with its `match` still using accessors. */
|
|
51
|
+
interface TransitionGuardConfig<T extends Entity = Entity> {
|
|
52
|
+
match: GuardTransitionMatch<string>;
|
|
53
|
+
run(entity: T, step: TransitionStep): MaybePromise<ValidationRuleResult>;
|
|
54
|
+
}
|
|
55
|
+
/** An `onTransition` as registered in config, with its `match` still using accessors. */
|
|
56
|
+
interface TransitionCallbackConfig<T extends Entity = Entity, C = unknown> {
|
|
57
|
+
name: string;
|
|
58
|
+
match: OnTransitionMatch<string>;
|
|
59
|
+
run(entity: T, ctx: C, step: TransitionStep): MaybePromise<unknown>;
|
|
60
|
+
}
|
|
61
|
+
/** One field's `transitions`, `guardTransition`, and `onTransition` config on a single entity type. */
|
|
62
|
+
interface FieldTransitionsConfig<T extends Entity = Entity, C = unknown> {
|
|
63
|
+
/** The `transitions` table, if any; subtypes add their own tables on their own configs. */
|
|
64
|
+
table?: TransitionTable<string>;
|
|
65
|
+
guards: TransitionGuardConfig<T>[];
|
|
66
|
+
callbacks: TransitionCallbackConfig<T, C>[];
|
|
67
|
+
}
|
|
68
|
+
/** A `match` with accessors converted to codes, so it can be compared to recorded steps. */
|
|
69
|
+
interface TransitionMatcher {
|
|
70
|
+
/** The allowed previous states, or `undefined` for any state. */
|
|
71
|
+
from: readonly unknown[] | undefined;
|
|
72
|
+
/** The allowed new states, or `undefined` for any state. */
|
|
73
|
+
to: readonly unknown[] | undefined;
|
|
74
|
+
/** Whether creation matches, which is never true when `from` is set. */
|
|
75
|
+
onCreate: boolean;
|
|
76
|
+
}
|
|
77
|
+
/** A guard with its `match` converted to codes. */
|
|
78
|
+
interface TransitionGuard {
|
|
79
|
+
matcher: TransitionMatcher;
|
|
80
|
+
run(entity: Entity, step: TransitionStep): MaybePromise<ValidationRuleResult>;
|
|
81
|
+
}
|
|
82
|
+
/** A callback with its `match` converted to codes, dispatched only after its transition's guards pass. */
|
|
83
|
+
interface TransitionCallback {
|
|
84
|
+
name: string;
|
|
85
|
+
phase: "flush" | "commit";
|
|
86
|
+
matcher: TransitionMatcher;
|
|
87
|
+
run(entity: Entity, ctx: unknown, step: TransitionStep): MaybePromise<unknown>;
|
|
88
|
+
}
|
|
89
|
+
/** One field's transition config from an entity type and its base types, with accessors converted to codes. */
|
|
90
|
+
interface FieldTransitions {
|
|
91
|
+
/** Each table maps a `from` code to its allowed `to` codes; every table must allow a change. */
|
|
92
|
+
tables: ReadonlyMap<unknown, readonly unknown[]>[];
|
|
93
|
+
guards: TransitionGuard[];
|
|
94
|
+
callbacks: TransitionCallback[];
|
|
95
|
+
}
|
|
96
|
+
/** Converts an internal step to the `Transition` that guards and reactions receive. */
|
|
97
|
+
declare function toTransition(step: TransitionStep): Transition<any>;
|
|
98
|
+
/**
|
|
99
|
+
* Merges the transition config of `meta` and its base types by field, converting accessors to codes.
|
|
100
|
+
*
|
|
101
|
+
* This runs after boot, because the config is written before the enum metadata exists.
|
|
102
|
+
*/
|
|
103
|
+
declare function buildFieldTransitions(meta: EntityMetadata): Map<string, FieldTransitions>;
|
|
104
|
+
/** Returns whether `step` matches `matcher`'s `from`, `to`, and `onCreate` settings. */
|
|
105
|
+
declare function matchesTransition(matcher: TransitionMatcher, step: TransitionStep): boolean;
|
|
106
|
+
/** Uses the enum's display name in error messages when there is one, i.e. `Approved` instead of `APPROVED`. */
|
|
107
|
+
declare function describeValue(meta: EntityMetadata, fieldName: string, value: unknown): string;
|
|
108
|
+
//#endregion
|
|
109
|
+
export { FieldTransitions, FieldTransitionsConfig, GuardTransitionMatch, OnTransitionMatch, Transition, TransitionCallback, TransitionCallbackConfig, TransitionGuard, TransitionGuardConfig, TransitionMatcher, TransitionStep, TransitionTable, buildFieldTransitions, created, describeValue, matchesTransition, toTransition };
|
|
110
|
+
//# sourceMappingURL=transitions.d.mts.map
|