@rvoh/dream 2.22.0 → 2.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/src/Dream.js +68 -23
- package/dist/cjs/src/dream/Query.js +39 -8
- package/dist/cjs/src/dream/QueryDriver/Kysely.js +30 -2
- package/dist/cjs/src/dream/internal/buildSerializerPreloadPaths.js +93 -20
- package/dist/cjs/src/dream/internal/convertDreamClassAndAssociationNameTupleArrayToPreloadArgs.js +18 -10
- package/dist/cjs/src/dream/internal/mergeSerializerAssociationEdges.js +75 -0
- package/dist/cjs/src/dream/internal/printSerializerHierarchyLevel.js +50 -8
- package/dist/cjs/src/dream/internal/resolveSerializerAssociationEdges.js +79 -14
- package/dist/cjs/src/errors/serializers/NoSerializersResolvedForKey.js +30 -0
- package/dist/cjs/src/serializer/helpers/inferSerializerFromDreamOrViewModel.js +22 -0
- package/dist/cjs/src/serializer/helpers/serializerForAssociatedClass.js +13 -0
- package/dist/esm/src/Dream.js +68 -23
- package/dist/esm/src/dream/Query.js +39 -8
- package/dist/esm/src/dream/QueryDriver/Kysely.js +30 -2
- package/dist/esm/src/dream/internal/buildSerializerPreloadPaths.js +93 -20
- package/dist/esm/src/dream/internal/convertDreamClassAndAssociationNameTupleArrayToPreloadArgs.js +18 -10
- package/dist/esm/src/dream/internal/mergeSerializerAssociationEdges.js +75 -0
- package/dist/esm/src/dream/internal/printSerializerHierarchyLevel.js +50 -8
- package/dist/esm/src/dream/internal/resolveSerializerAssociationEdges.js +79 -14
- package/dist/esm/src/errors/serializers/NoSerializersResolvedForKey.js +30 -0
- package/dist/esm/src/serializer/helpers/inferSerializerFromDreamOrViewModel.js +22 -0
- package/dist/esm/src/serializer/helpers/serializerForAssociatedClass.js +13 -0
- package/dist/types/src/Dream.d.ts +9 -0
- package/dist/types/src/dream/Query.d.ts +10 -0
- package/dist/types/src/dream/internal/mergeSerializerAssociationEdges.d.ts +69 -0
- package/dist/types/src/dream/internal/printSerializerHierarchyLevel.d.ts +23 -5
- package/dist/types/src/dream/internal/resolveSerializerAssociationEdges.d.ts +27 -1
- package/dist/types/src/errors/serializers/NoSerializersResolvedForKey.d.ts +18 -0
- package/dist/types/src/serializer/helpers/inferSerializerFromDreamOrViewModel.d.ts +16 -0
- package/docs/classes/db.DreamMigrationHelpers.html +11 -11
- package/docs/classes/db.KyselyQueryDriver.html +40 -40
- package/docs/classes/db.PostgresQueryDriver.html +41 -41
- package/docs/classes/db.QueryDriverBase.html +39 -39
- package/docs/classes/errors.CheckConstraintViolation.html +3 -3
- package/docs/classes/errors.ColumnOverflow.html +3 -3
- package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +3 -3
- package/docs/classes/errors.DataIncompatibleWithDatabaseField.html +3 -3
- package/docs/classes/errors.DataTypeColumnTypeMismatch.html +3 -3
- package/docs/classes/errors.DecryptionError.html +2 -2
- package/docs/classes/errors.DecryptionParseError.html +2 -2
- package/docs/classes/errors.DecryptionRotationError.html +3 -3
- package/docs/classes/errors.GlobalNameNotSet.html +3 -3
- package/docs/classes/errors.InvalidCalendarDate.html +2 -2
- package/docs/classes/errors.InvalidClockTime.html +2 -2
- package/docs/classes/errors.InvalidClockTimeTz.html +2 -2
- package/docs/classes/errors.InvalidDateTime.html +2 -2
- package/docs/classes/errors.MissingSerializersDefinition.html +3 -3
- package/docs/classes/errors.NonLoadedAssociation.html +3 -3
- package/docs/classes/errors.NotNullViolation.html +3 -3
- package/docs/classes/errors.RecordNotFound.html +3 -3
- package/docs/classes/errors.ValidationError.html +3 -3
- package/docs/classes/index.CalendarDate.html +33 -33
- package/docs/classes/index.ClockTime.html +32 -32
- package/docs/classes/index.ClockTimeTz.html +35 -35
- package/docs/classes/index.DateTime.html +86 -86
- package/docs/classes/index.Decorators.html +19 -19
- package/docs/classes/index.Dream.html +124 -124
- package/docs/classes/index.DreamApp.html +10 -10
- package/docs/classes/index.DreamTransaction.html +2 -2
- package/docs/classes/index.Env.html +2 -2
- package/docs/classes/index.Query.html +62 -62
- package/docs/classes/system.CliFileWriter.html +4 -4
- package/docs/classes/system.DreamBin.html +2 -2
- package/docs/classes/system.DreamCLI.html +7 -7
- package/docs/classes/system.DreamImporter.html +2 -2
- package/docs/classes/system.DreamLogos.html +2 -2
- package/docs/classes/system.DreamSerializerBuilder.html +11 -11
- package/docs/classes/system.ObjectSerializerBuilder.html +8 -8
- package/docs/classes/system.PathHelpers.html +3 -3
- package/docs/classes/utils.Encrypt.html +3 -3
- package/docs/classes/utils.Range.html +2 -2
- package/docs/functions/db.closeAllDbConnections.html +1 -1
- package/docs/functions/db.dreamDbConnections.html +1 -1
- package/docs/functions/db.untypedDb.html +1 -1
- package/docs/functions/db.validateColumn.html +1 -1
- package/docs/functions/db.validateTable.html +1 -1
- package/docs/functions/errors.pgErrorType.html +1 -1
- package/docs/functions/index.DreamSerializer.html +1 -1
- package/docs/functions/index.ObjectSerializer.html +1 -1
- package/docs/functions/index.ReplicaSafe.html +1 -1
- package/docs/functions/index.STI.html +1 -1
- package/docs/functions/index.SoftDelete.html +1 -1
- package/docs/functions/utils.camelize.html +1 -1
- package/docs/functions/utils.capitalize.html +1 -1
- package/docs/functions/utils.cloneDeepSafe.html +1 -1
- package/docs/functions/utils.compact.html +1 -1
- package/docs/functions/utils.groupBy.html +1 -1
- package/docs/functions/utils.hyphenize.html +1 -1
- package/docs/functions/utils.intersection.html +1 -1
- package/docs/functions/utils.isEmpty.html +1 -1
- package/docs/functions/utils.normalizeUnicode.html +1 -1
- package/docs/functions/utils.pascalize.html +1 -1
- package/docs/functions/utils.percent.html +1 -1
- package/docs/functions/utils.range.html +1 -1
- package/docs/functions/utils.round.html +1 -1
- package/docs/functions/utils.sanitizeString.html +1 -1
- package/docs/functions/utils.snakeify.html +1 -1
- package/docs/functions/utils.sort.html +1 -1
- package/docs/functions/utils.sortBy.html +1 -1
- package/docs/functions/utils.sortObjectByKey.html +1 -1
- package/docs/functions/utils.sortObjectByValue.html +1 -1
- package/docs/functions/utils.uncapitalize.html +1 -1
- package/docs/functions/utils.uniq.html +1 -1
- package/docs/interfaces/openapi.OpenapiDescription.html +2 -2
- package/docs/interfaces/openapi.OpenapiSchemaProperties.html +1 -1
- package/docs/interfaces/openapi.OpenapiSchemaPropertiesShorthand.html +1 -1
- package/docs/interfaces/openapi.OpenapiTypeFieldObject.html +1 -1
- package/docs/interfaces/types.BelongsToStatement.html +2 -2
- package/docs/interfaces/types.DecoratorContext.html +2 -2
- package/docs/interfaces/types.DreamAppInitOptions.html +2 -2
- package/docs/interfaces/types.DreamAppOpts.html +2 -2
- package/docs/interfaces/types.DreamDbConfig.html +5 -5
- package/docs/interfaces/types.DurationObject.html +2 -2
- package/docs/interfaces/types.EncryptOptions.html +2 -2
- package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
- package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
- package/docs/interfaces/types.SerializerRendererOpts.html +2 -2
- package/docs/types/openapi.CommonOpenapiSchemaObjectFields.html +1 -1
- package/docs/types/openapi.OpenapiAllTypes.html +1 -1
- package/docs/types/openapi.OpenapiFormats.html +1 -1
- package/docs/types/openapi.OpenapiNumberFormats.html +1 -1
- package/docs/types/openapi.OpenapiPrimitiveBaseTypes.html +1 -1
- package/docs/types/openapi.OpenapiPrimitiveTypes.html +1 -1
- package/docs/types/openapi.OpenapiSchemaArray.html +1 -1
- package/docs/types/openapi.OpenapiSchemaArrayShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaBase.html +1 -1
- package/docs/types/openapi.OpenapiSchemaBody.html +1 -1
- package/docs/types/openapi.OpenapiSchemaBodyShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaCommonFields.html +1 -1
- package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionRef.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +2 -2
- package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
- package/docs/types/openapi.OpenapiSchemaNull.html +2 -2
- package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAnyOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAnyOfShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectBase.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectOneOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectOneOfShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaPrimitiveGeneric.html +1 -1
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionAllOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
- package/docs/types/openapi.OpenapiSchemaString.html +1 -1
- package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
- package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
- package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
- package/docs/types/openapi.OpenapiTypeField.html +1 -1
- package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
- package/docs/types/types.CalendarDateDurationUnit.html +1 -1
- package/docs/types/types.CalendarDateObject.html +1 -1
- package/docs/types/types.Camelized.html +1 -1
- package/docs/types/types.ClockTimeObject.html +1 -1
- package/docs/types/types.DbConnectionType.html +1 -1
- package/docs/types/types.DbTypes.html +1 -1
- package/docs/types/types.DreamAssociationMetadata.html +1 -1
- package/docs/types/types.DreamAttributes.html +1 -1
- package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
- package/docs/types/types.DreamClassColumn.html +1 -1
- package/docs/types/types.DreamColumn.html +1 -1
- package/docs/types/types.DreamColumnNames.html +1 -1
- package/docs/types/types.DreamLogLevel.html +1 -1
- package/docs/types/types.DreamLogger.html +2 -2
- package/docs/types/types.DreamModelSerializerType.html +1 -1
- package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
- package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
- package/docs/types/types.DreamParamSafeAttributes.html +1 -1
- package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
- package/docs/types/types.DreamSerializable.html +1 -1
- package/docs/types/types.DreamSerializableArray.html +1 -1
- package/docs/types/types.DreamSerializerKey.html +1 -1
- package/docs/types/types.DreamSerializers.html +1 -1
- package/docs/types/types.DreamVirtualColumns.html +1 -1
- package/docs/types/types.DurationUnit.html +1 -1
- package/docs/types/types.EncryptAlgorithm.html +1 -1
- package/docs/types/types.HasManyStatement.html +1 -1
- package/docs/types/types.HasOneStatement.html +1 -1
- package/docs/types/types.Hyphenized.html +1 -1
- package/docs/types/types.Pascalized.html +1 -1
- package/docs/types/types.PrimaryKeyType.html +1 -1
- package/docs/types/types.RoundingPrecision.html +1 -1
- package/docs/types/types.SerializerCasing.html +1 -1
- package/docs/types/types.SimpleObjectSerializerType.html +1 -1
- package/docs/types/types.Snakeified.html +1 -1
- package/docs/types/types.StrictInterface.html +1 -1
- package/docs/types/types.UpdateableAssociationProperties.html +1 -1
- package/docs/types/types.UpdateableProperties.html +1 -1
- package/docs/types/types.ValidationType.html +1 -1
- package/docs/types/types.ViewModel.html +2 -2
- package/docs/types/types.ViewModelClass.html +1 -1
- package/docs/types/types.WeekdayName.html +1 -1
- package/docs/types/types.WhereStatementForDream.html +1 -1
- package/docs/types/types.WhereStatementForDreamClass.html +1 -1
- package/docs/variables/index.DreamConst.html +1 -1
- package/docs/variables/index.ops.html +1 -1
- package/docs/variables/openapi.openapiPrimitiveTypes.html +1 -1
- package/docs/variables/openapi.openapiShorthandPrimitiveTypes.html +1 -1
- package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
- package/docs/variables/system.primaryKeyTypes.html +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import resolveSerializerAssociationEdges from './resolveSerializerAssociationEdges.js';
|
|
2
|
+
/**
|
|
3
|
+
* @internal
|
|
4
|
+
*
|
|
5
|
+
* Resolves the association edges of *every* serializer that can render `dreamClass` at this point
|
|
6
|
+
* in a serialization walk and merges them into one set, so that a walk rooted at an STI base sees
|
|
7
|
+
* the union across its children's serializers rather than one child's view of the class.
|
|
8
|
+
*
|
|
9
|
+
* The merge is keyed on (association, target class), so:
|
|
10
|
+
*
|
|
11
|
+
* - an association several children render identically (all three `stiUnion` serializers render
|
|
12
|
+
* `sandbags`) collapses to a single edge with a single target, rather than repeating per child;
|
|
13
|
+
* - a polymorphic association keeps one target per target class
|
|
14
|
+
* (`Polymorphic/Task#taskable` → `Chore` and `Workout`), which is a real distinction, not a
|
|
15
|
+
* duplicate;
|
|
16
|
+
* - an association only some children render (`heartRatings`, rendered only by Latex's `stiUnion`
|
|
17
|
+
* serializer) is present exactly once.
|
|
18
|
+
*
|
|
19
|
+
* Grouping the serializers *under* the target rather than emitting one target per serializer is
|
|
20
|
+
* what lets a caller recurse once per target class with the full set, so nested trees from
|
|
21
|
+
* different children of the same target merge instead of clobbering one another.
|
|
22
|
+
*
|
|
23
|
+
* Insertion-ordered throughout, so edges come back in the order the serializers declare them.
|
|
24
|
+
*
|
|
25
|
+
* This is the single union both serializer walks run on: `buildSerializerPreloadPaths` turns these
|
|
26
|
+
* edges into preload paths (taking `terminates` and the target classes, ignoring the per-serializer
|
|
27
|
+
* edge type and serializer-side name), and `Dream.recursiveSerializationMap` prints them (taking the
|
|
28
|
+
* per-serializer edge type and name, ignoring `terminates`). Keeping the two walks on one merge is the point: the bug
|
|
29
|
+
* this exists to prevent is `psy i:serialization` and `preloadFor` disagreeing about which
|
|
30
|
+
* associations a serializer key reaches, which a second hand-maintained merge invites back.
|
|
31
|
+
*/
|
|
32
|
+
export default function mergeSerializerAssociationEdges(dreamClass, serializers) {
|
|
33
|
+
/**
|
|
34
|
+
* Each target carries a Set mirroring its `serializers`, so the identity dedupe below is a lookup
|
|
35
|
+
* rather than a scan of the array. Dropped when the merged edges are built at the end.
|
|
36
|
+
*/
|
|
37
|
+
const mergedEdges = new Map();
|
|
38
|
+
for (const serializer of serializers) {
|
|
39
|
+
for (const edge of resolveSerializerAssociationEdges(dreamClass, serializer)) {
|
|
40
|
+
let mergedEdge = mergedEdges.get(edge.associationAs);
|
|
41
|
+
if (mergedEdge === undefined) {
|
|
42
|
+
mergedEdge = { terminates: false, targets: new Map() };
|
|
43
|
+
mergedEdges.set(edge.associationAs, mergedEdge);
|
|
44
|
+
}
|
|
45
|
+
if (edge.targets.length === 0) {
|
|
46
|
+
mergedEdge.terminates = true;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
for (const target of edge.targets) {
|
|
50
|
+
const targetKey = target.dreamClass.globalName;
|
|
51
|
+
const mergedTarget = mergedEdge.targets.get(targetKey);
|
|
52
|
+
const targetSerializer = {
|
|
53
|
+
serializer: target.serializer,
|
|
54
|
+
type: edge.type,
|
|
55
|
+
serializerAssociationName: edge.serializerAssociationName,
|
|
56
|
+
};
|
|
57
|
+
if (mergedTarget === undefined) {
|
|
58
|
+
mergedEdge.targets.set(targetKey, {
|
|
59
|
+
target: { dreamClass: target.dreamClass, serializers: [targetSerializer] },
|
|
60
|
+
serializerSet: new Set([target.serializer]),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
else if (!mergedTarget.serializerSet.has(target.serializer)) {
|
|
64
|
+
mergedTarget.serializerSet.add(target.serializer);
|
|
65
|
+
mergedTarget.target.serializers.push(targetSerializer);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return [...mergedEdges.entries()].map(([associationAs, { terminates, targets }]) => ({
|
|
71
|
+
associationAs,
|
|
72
|
+
terminates,
|
|
73
|
+
targets: [...targets.values()].map(({ target }) => target),
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
@@ -1,16 +1,58 @@
|
|
|
1
1
|
import yoctocolors from 'yoctocolors';
|
|
2
2
|
import { indent } from '../../helpers/indent.js';
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* @internal
|
|
5
|
+
*
|
|
6
|
+
* Prints one level of `psy i:serialization`'s tree: the heading for an association, then the name
|
|
7
|
+
* of every serializer that renders the target of that association at this point in the walk. That
|
|
8
|
+
* is more than one serializer when the target is an STI base, since each child may register a
|
|
9
|
+
* different serializer under the same key.
|
|
10
|
+
*
|
|
11
|
+
* Those serializers are printed under a *single* heading rather than one heading each, because the
|
|
12
|
+
* caller prints the union of their association trees below, indented one level in from the
|
|
13
|
+
* heading. Repeating the heading per serializer would leave that union sitting directly beneath
|
|
14
|
+
* whichever child's serializer happened to print last, reading as though every sibling's
|
|
15
|
+
* associations belonged to that one child. Grouped, the shape matches what `displaySerialization`
|
|
16
|
+
* prints at the root of the tree — one name, then every serializer that can render it, then one
|
|
17
|
+
* merged tree.
|
|
18
|
+
*
|
|
19
|
+
* A target reached under more than one edge type or serializer-side name (two children of an STI
|
|
20
|
+
* base rendering the same class through `rendersOne` and `rendersMany`, say) gets one heading per
|
|
21
|
+
* distinct (type, name), since those are genuinely different lines rather than repetitions of one.
|
|
22
|
+
*/
|
|
23
|
+
export default function printSerializerHierarchyLevel({ targetSerializers, forDisplayDepth, }) {
|
|
4
24
|
const hierarchyLine = '└───';
|
|
5
25
|
const indentation = indent((hierarchyLine.length + 1) * forDisplayDepth, {
|
|
6
26
|
tabWidth: 1,
|
|
7
27
|
});
|
|
8
28
|
const prefix = `${hierarchyLine} `;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
29
|
+
groupByHeading(targetSerializers).forEach(({ type, serializerAssociationName, serializers }) => {
|
|
30
|
+
// eslint-disable-next-line no-console
|
|
31
|
+
console.log(indentation + `${prefix}${type} ${yoctocolors.cyan(serializerAssociationName)}`);
|
|
32
|
+
serializers.forEach(serializer => {
|
|
33
|
+
// eslint-disable-next-line no-console
|
|
34
|
+
console.log(yoctocolors.gray(indentation +
|
|
35
|
+
indent(prefix.length, { tabWidth: 1 }) +
|
|
36
|
+
serializer.globalName));
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Collects the serializers rendering a single merged target into one entry per heading they print
|
|
42
|
+
* under — (edge type, serializer-side association name) — preserving the order the serializers
|
|
43
|
+
* were merged in.
|
|
44
|
+
*/
|
|
45
|
+
function groupByHeading(targetSerializers) {
|
|
46
|
+
const headings = new Map();
|
|
47
|
+
for (const { serializer, type, serializerAssociationName } of targetSerializers) {
|
|
48
|
+
const headingKey = `${type}:${serializerAssociationName}`;
|
|
49
|
+
const heading = headings.get(headingKey);
|
|
50
|
+
if (heading === undefined) {
|
|
51
|
+
headings.set(headingKey, { type, serializerAssociationName, serializers: [serializer] });
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
heading.serializers.push(serializer);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return [...headings.values()];
|
|
16
58
|
}
|
|
@@ -1,19 +1,78 @@
|
|
|
1
|
-
import MissingSerializersDefinition from '../../errors/serializers/MissingSerializersDefinition.js';
|
|
2
1
|
import compact from '../../helpers/compact.js';
|
|
3
2
|
import { inferSerializersFromDreamClassOrViewModelClass } from '../../serializer/helpers/inferSerializerFromDreamOrViewModel.js';
|
|
3
|
+
/**
|
|
4
|
+
* Keyed on the two arguments. Weak on both, so an entry is collectable once either the Dream class
|
|
5
|
+
* or the serializer is. The cached arrays are handed out by reference and must be treated as
|
|
6
|
+
* read-only by callers.
|
|
7
|
+
*/
|
|
8
|
+
let resolvedEdgesCache = new WeakMap();
|
|
9
|
+
/**
|
|
10
|
+
* @internal
|
|
11
|
+
*
|
|
12
|
+
* Drops every memoized edge set. Only needed when a class object that stays in the process has its
|
|
13
|
+
* `serializers` getter replaced, which the memo cannot detect — specs that install or remove one
|
|
14
|
+
* around an example. Replacing the model classes themselves needs no call, since the memo keys on
|
|
15
|
+
* the class and serializer objects and entries resolved against the previous ones become
|
|
16
|
+
* unreachable.
|
|
17
|
+
*/
|
|
18
|
+
export function clearResolvedSerializerAssociationEdgesCache() {
|
|
19
|
+
resolvedEdgesCache = new WeakMap();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* @internal
|
|
23
|
+
*
|
|
24
|
+
* Resolves the rendersOne/rendersMany/delegatedAttribute attributes of `serializer` to the
|
|
25
|
+
* associations of `dreamClass` they preload, along with the classes and serializers each one
|
|
26
|
+
* targets.
|
|
27
|
+
*
|
|
28
|
+
* Memoized on its two arguments, because every call rebuilds a throwaway serializer builder and
|
|
29
|
+
* re-infers the serializers of every association target, and the traversal in
|
|
30
|
+
* buildSerializerPreloadPaths revisits the same (class, serializer) pair many times.
|
|
31
|
+
*
|
|
32
|
+
* The two arguments are not the function's only inputs, though: resolving a target's serializers
|
|
33
|
+
* also reads that target's `serializers` getter, the STI children registered on it, and the
|
|
34
|
+
* DreamApp serializer registry. Loading a fresh set of classes replaces the keys along with that
|
|
35
|
+
* state, so the memo follows it. Mutating a `serializers` getter in place does not, and needs
|
|
36
|
+
* `clearResolvedSerializerAssociationEdgesCache` — see there.
|
|
37
|
+
*/
|
|
4
38
|
export default function resolveSerializerAssociationEdges(dreamClass, serializer) {
|
|
39
|
+
let cachedForDreamClass = resolvedEdgesCache.get(dreamClass);
|
|
40
|
+
if (cachedForDreamClass === undefined) {
|
|
41
|
+
cachedForDreamClass = new WeakMap();
|
|
42
|
+
resolvedEdgesCache.set(dreamClass, cachedForDreamClass);
|
|
43
|
+
}
|
|
44
|
+
const cached = cachedForDreamClass.get(serializer);
|
|
45
|
+
if (cached !== undefined)
|
|
46
|
+
return cached;
|
|
47
|
+
const edges = buildSerializerAssociationEdges(dreamClass, serializer);
|
|
48
|
+
cachedForDreamClass.set(serializer, edges);
|
|
49
|
+
return edges;
|
|
50
|
+
}
|
|
51
|
+
function buildSerializerAssociationEdges(dreamClass, serializer) {
|
|
5
52
|
const serializerBuilder = serializer(undefined, undefined);
|
|
6
53
|
const serializerAssociations = serializerBuilder['attributes'].filter(attribute => ['rendersOne', 'rendersMany', 'delegatedAttribute'].includes(attribute.type));
|
|
7
54
|
return compact(serializerAssociations.map(serializerAssociation => {
|
|
8
55
|
const serializerAssociationName = serializerAssociation.targetName ??
|
|
9
56
|
serializerAssociation.name;
|
|
57
|
+
// `dreamClass` is the class the walk is rooted at, which for an STI base is the base itself
|
|
58
|
+
// even while `serializer` is one child's serializer. That pairing is safe because an STI child
|
|
59
|
+
// cannot declare associations of its own — the `STI` class decorator throws
|
|
60
|
+
// StiChildCannotDefineNewAssociations — so every association a child's serializer can render
|
|
61
|
+
// is declared on the base and resolves here.
|
|
62
|
+
//
|
|
63
|
+
// The one hole: that guard reads decorator metadata and
|
|
64
|
+
// `associationDeclarationNamesFromMetadata` returns [] when the metadata is absent, which is
|
|
65
|
+
// also what happens when the `STI` decorator runs without a `context`. Under a TS config that
|
|
66
|
+
// does not emit decorator metadata the guard silently passes, a child-declared association
|
|
67
|
+
// survives, and it is dropped *here*, silently, by the return below — the symptom is then a
|
|
68
|
+
// missing preload and a NonLoadedAssociation at render, not a decorator error naming the real
|
|
69
|
+
// problem.
|
|
10
70
|
const association = dreamClass['getAssociationMetadata'](serializerAssociationName);
|
|
11
71
|
if (!association)
|
|
12
72
|
return null;
|
|
13
73
|
if (serializerAssociation.type === 'delegatedAttribute') {
|
|
14
74
|
return {
|
|
15
75
|
associationAs: association.as,
|
|
16
|
-
sourceDreamClass: dreamClass,
|
|
17
76
|
type: serializerAssociation.type,
|
|
18
77
|
serializerAssociationName,
|
|
19
78
|
targets: [],
|
|
@@ -26,17 +85,24 @@ export default function resolveSerializerAssociationEdges(dreamClass, serializer
|
|
|
26
85
|
? maybeAssociatedClasses
|
|
27
86
|
: [maybeAssociatedClasses];
|
|
28
87
|
const targets = associatedClasses.flatMap(associatedClass => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
88
|
+
// Deliberately unguarded: every serializer that cannot be resolved while building preload
|
|
89
|
+
// paths throws, and there is no tolerance here by design.
|
|
90
|
+
//
|
|
91
|
+
// Why: an unresolvable serializer is a configuration error, and the first query that walks
|
|
92
|
+
// the serializer graph is where it should be found — no rows, no rendering required.
|
|
93
|
+
// Swallowing it means the only way to discover a broken `serializerKey` (or a missing
|
|
94
|
+
// `serializers` getter) is a spec that creates a row of the affected class *and* renders it.
|
|
95
|
+
//
|
|
96
|
+
// Why the obvious tolerance is also wrong, not merely undesirable:
|
|
97
|
+
// `inferSerializersFromDreamClassOrViewModelClass` below expands STI and throws on the
|
|
98
|
+
// *first* child that fails, so catching here would drop the serializers of *every* child of
|
|
99
|
+
// this target. An edge with no targets is treated as terminal by buildSerializerPreloadPaths,
|
|
100
|
+
// so the association is preloaded but never descended into, and a row of a *well-formed*
|
|
101
|
+
// sibling fails at render with NonLoadedAssociation — an error naming a class that has
|
|
102
|
+
// nothing wrong with it.
|
|
103
|
+
const serializers = serializerAssociation.options.serializer
|
|
104
|
+
? compact([serializerAssociation.options.serializer])
|
|
105
|
+
: compact(inferSerializersFromDreamClassOrViewModelClass(associatedClass, serializerAssociation.options.serializerKey));
|
|
40
106
|
return serializers.map(associatedSerializer => ({
|
|
41
107
|
dreamClass: associatedClass,
|
|
42
108
|
serializer: associatedSerializer,
|
|
@@ -44,7 +110,6 @@ export default function resolveSerializerAssociationEdges(dreamClass, serializer
|
|
|
44
110
|
});
|
|
45
111
|
return {
|
|
46
112
|
associationAs: association.as,
|
|
47
|
-
sourceDreamClass: dreamClass,
|
|
48
113
|
type: serializerAssociation.type,
|
|
49
114
|
serializerAssociationName,
|
|
50
115
|
targets,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raised when resolving a serializer key against a class yields an empty set of serializers.
|
|
3
|
+
*
|
|
4
|
+
* This is an internal-invariant failure, not a misconfiguration: an unresolvable serializer key
|
|
5
|
+
* raises `MissingSerializersDefinitionForKey` (and a class with no `serializers` getter at all
|
|
6
|
+
* raises `MissingSerializersDefinition`) while it is being resolved, and STI expansion always
|
|
7
|
+
* yields at least one class, so there is no supported application shape that reaches this. It is
|
|
8
|
+
* typed rather than a bare `Error` so that it can be asserted on, and so that the two callers that
|
|
9
|
+
* guard the invariant raise the same thing.
|
|
10
|
+
*/
|
|
11
|
+
export default class NoSerializersResolvedForKey extends Error {
|
|
12
|
+
classDef;
|
|
13
|
+
serializerKey;
|
|
14
|
+
constructor(classDef, serializerKey) {
|
|
15
|
+
super();
|
|
16
|
+
this.classDef = classDef;
|
|
17
|
+
this.serializerKey = serializerKey;
|
|
18
|
+
}
|
|
19
|
+
get message() {
|
|
20
|
+
const className = this.classDef === null || this.classDef === undefined
|
|
21
|
+
? String(this.classDef)
|
|
22
|
+
: (this.classDef.globalName ?? this.classDef.name);
|
|
23
|
+
return `
|
|
24
|
+
No serializers resolved for \`${className}\` under serializer key \`${this.serializerKey}\`
|
|
25
|
+
|
|
26
|
+
This should be impossible — a serializer key that cannot be resolved throws while it is being
|
|
27
|
+
resolved, before reaching here — and indicates a bug in Dream rather than a problem with your
|
|
28
|
+
application. Please report it.`;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -3,6 +3,7 @@ import MissingSerializersDefinition from '../../errors/serializers/MissingSerial
|
|
|
3
3
|
import MissingSerializersDefinitionForKey from '../../errors/serializers/MissingSerializersDefinitionForKey.js';
|
|
4
4
|
import NoGlobalSerializerForSpecifiedKey from '../../errors/serializers/NoGlobalSerializerForSpecifiedKey.js';
|
|
5
5
|
import NonDreamSerializerDerivedFromGlobalSerializerForSpecifiedKey from '../../errors/serializers/NonDreamSerializerDerivedFromGlobalSerializerForSpecifiedKey.js';
|
|
6
|
+
import NoSerializersResolvedForKey from '../../errors/serializers/NoSerializersResolvedForKey.js';
|
|
6
7
|
import compact from '../../helpers/compact.js';
|
|
7
8
|
import expandStiClasses from '../../helpers/sti/expandStiClasses.js';
|
|
8
9
|
import uniq from '../../helpers/uniq.js';
|
|
@@ -35,3 +36,24 @@ export function inferSerializersFromDreamClassOrViewModelClass(classDef, seriali
|
|
|
35
36
|
const serializers = classes.map(classDef => inferSerializerFromDreamOrViewModel(classDef.prototype, serializerKey));
|
|
36
37
|
return uniq(compact(serializers));
|
|
37
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* As `inferSerializersFromDreamClassOrViewModelClass`, but guarantees a non-empty result.
|
|
41
|
+
*
|
|
42
|
+
* The guard is unreachable today, and deliberately not phrased as "no serializer found for key": a
|
|
43
|
+
* serializer key that a class does not register throws `MissingSerializersDefinitionForKey` (and a
|
|
44
|
+
* class with no `serializers` getter at all throws `MissingSerializersDefinition`) from inside the
|
|
45
|
+
* inference above, and `expandStiClasses` always yields at least one class. It exists only so that
|
|
46
|
+
* a future change to any of those cannot turn an unresolvable serializer key into a silently empty
|
|
47
|
+
* serializer set — which, for the callers below, means a silently empty preload set and a
|
|
48
|
+
* `NonLoadedAssociation` at render rather than an error naming the real problem.
|
|
49
|
+
*
|
|
50
|
+
* Used by everything that walks a serializer graph rooted on a class rather than an instance —
|
|
51
|
+
* preload-path building and serialization display — so that they raise one typed error between
|
|
52
|
+
* them.
|
|
53
|
+
*/
|
|
54
|
+
export function inferSerializersFromDreamClassOrViewModelClassOrFail(classDef, serializerKey) {
|
|
55
|
+
const serializers = inferSerializersFromDreamClassOrViewModelClass(classDef, serializerKey);
|
|
56
|
+
if (serializers.length === 0)
|
|
57
|
+
throw new NoSerializersResolvedForKey(classDef, serializerKey);
|
|
58
|
+
return serializers;
|
|
59
|
+
}
|
|
@@ -15,5 +15,18 @@ export function serializerForAssociatedClass(object, associationName, options) {
|
|
|
15
15
|
const associatedClass = association.modelCB();
|
|
16
16
|
if (Array.isArray(associatedClass))
|
|
17
17
|
throw new Error('rendersOne flatten is incompatible with a polymorphic belongs-to association');
|
|
18
|
+
// Taking the first is only a narrowing when `associatedClass` is an STI base, where
|
|
19
|
+
// inferSerializersFromDreamClassOrViewModelClass returns one serializer per child sorted by
|
|
20
|
+
// sanitizedName. It is correct here because there is no associated row: this function is reached
|
|
21
|
+
// only from SerializerRenderer's `flatten: true` branch for a *null* associated object, so no
|
|
22
|
+
// child of the base is the right one — the row that would have decided is absent. (rendersMany
|
|
23
|
+
// never reaches here and does not accept `flatten`.)
|
|
24
|
+
//
|
|
25
|
+
// It is observable, though, so it is not "nothing happens": the picked serializer is invoked and
|
|
26
|
+
// its output spread into the parent's payload, so the parent JSON carries the
|
|
27
|
+
// alphabetically-first child's flattened keys as nulls, and omits keys only a later-sorting child
|
|
28
|
+
// would flatten. Unioning every child's serializer here — which is what buildSerializerPreloadPaths
|
|
29
|
+
// does for preload paths — is meaningless: exactly one set of keys can be flattened into the
|
|
30
|
+
// payload.
|
|
18
31
|
return inferSerializersFromDreamClassOrViewModelClass(associatedClass, options.serializerKey)[0] ?? null;
|
|
19
32
|
}
|
|
@@ -429,6 +429,15 @@ export default class Dream {
|
|
|
429
429
|
private static setGlobalName;
|
|
430
430
|
private static serializationMap;
|
|
431
431
|
private static displaySerialization;
|
|
432
|
+
/**
|
|
433
|
+
* @internal
|
|
434
|
+
*
|
|
435
|
+
* Maps the association tree reachable from `serializers` — every serializer that can render this
|
|
436
|
+
* class at this point in the walk, which is more than one when this class is an STI base. The
|
|
437
|
+
* serializers are merged into one edge set (see mergeSerializerAssociationEdges) rather than
|
|
438
|
+
* walked one at a time, so an association several of them render is visited once and the nested
|
|
439
|
+
* trees of a shared target merge instead of clobbering each other.
|
|
440
|
+
*/
|
|
432
441
|
private static recursiveSerializationMap;
|
|
433
442
|
/**
|
|
434
443
|
* Returns the column names for the given model
|
|
@@ -7,6 +7,16 @@ import { CursorPaginatedDreamQueryOptions, CursorPaginatedDreamQueryResult, Defa
|
|
|
7
7
|
import { JoinedAssociation, JoinedAssociationsTypeFromAssociations, QueryTypeOptions, VariadicJoinsArgs, VariadicLeftJoinLoadArgs, VariadicLoadArgs } from '../types/variadic.js';
|
|
8
8
|
import DreamTransaction from './DreamTransaction.js';
|
|
9
9
|
import QueryDriverBase from './QueryDriver/Base.js';
|
|
10
|
+
/**
|
|
11
|
+
* @internal
|
|
12
|
+
*
|
|
13
|
+
* Drops every memoized preload path. Only needed when a class object that stays in the process has
|
|
14
|
+
* its `serializers` getter replaced, which the cache cannot detect — specs that install or remove
|
|
15
|
+
* one around an example. Replacing the model classes themselves needs no call, since the cache
|
|
16
|
+
* keys on the class object. See `clearResolvedSerializerAssociationEdgesCache`, which has the same
|
|
17
|
+
* contract and is cleared alongside it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function clearSerializerPreloadPathsCache(): void;
|
|
10
20
|
export default class Query<DreamInstance extends Dream, QueryTypeOpts extends Readonly<QueryTypeOptions> = DefaultQueryTypeOptions<DreamInstance>> {
|
|
11
21
|
/**
|
|
12
22
|
* @internal
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import Dream from '../../Dream.js';
|
|
2
|
+
import { SerializerType } from '../../types/serializer.js';
|
|
3
|
+
import { ResolvedSerializerAssociationEdge } from './resolveSerializerAssociationEdges.js';
|
|
4
|
+
/**
|
|
5
|
+
* One contributing serializer's view of a merged target: the serializer that renders the target
|
|
6
|
+
* through this association, together with how it renders it. The edge type and the serializer-side
|
|
7
|
+
* association name belong here rather than on the target because they vary per contributing
|
|
8
|
+
* serializer — two children of an STI base can reach the same target class through the same
|
|
9
|
+
* association under different names or different edge types.
|
|
10
|
+
*/
|
|
11
|
+
export interface MergedSerializerAssociationTargetSerializer {
|
|
12
|
+
serializer: SerializerType;
|
|
13
|
+
type: ResolvedSerializerAssociationEdge['type'];
|
|
14
|
+
serializerAssociationName: string;
|
|
15
|
+
}
|
|
16
|
+
export interface MergedSerializerAssociationTarget {
|
|
17
|
+
dreamClass: typeof Dream;
|
|
18
|
+
/**
|
|
19
|
+
* Every serializer that renders this target through this association, deduped by serializer
|
|
20
|
+
* identity. More than one when the target is itself an STI base (one serializer per child).
|
|
21
|
+
*/
|
|
22
|
+
serializers: MergedSerializerAssociationTargetSerializer[];
|
|
23
|
+
}
|
|
24
|
+
export interface MergedSerializerAssociationEdge {
|
|
25
|
+
associationAs: string;
|
|
26
|
+
/**
|
|
27
|
+
* true when at least one contributing serializer terminates this association — a
|
|
28
|
+
* delegatedAttribute (which always carries `targets: []`), or a rendersOne/rendersMany resolving
|
|
29
|
+
* to no serializer.
|
|
30
|
+
*
|
|
31
|
+
* Independent of `targets`: an association one child terminates and another descends into (as
|
|
32
|
+
* Balloon's `stiUnion` key does with `balloonLine`) is both terminating and targeted. The preload
|
|
33
|
+
* walk emits a leaf path for the terminating side; the display walk ignores it, since a
|
|
34
|
+
* terminated association prints nothing of its own.
|
|
35
|
+
*/
|
|
36
|
+
terminates: boolean;
|
|
37
|
+
targets: MergedSerializerAssociationTarget[];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* @internal
|
|
41
|
+
*
|
|
42
|
+
* Resolves the association edges of *every* serializer that can render `dreamClass` at this point
|
|
43
|
+
* in a serialization walk and merges them into one set, so that a walk rooted at an STI base sees
|
|
44
|
+
* the union across its children's serializers rather than one child's view of the class.
|
|
45
|
+
*
|
|
46
|
+
* The merge is keyed on (association, target class), so:
|
|
47
|
+
*
|
|
48
|
+
* - an association several children render identically (all three `stiUnion` serializers render
|
|
49
|
+
* `sandbags`) collapses to a single edge with a single target, rather than repeating per child;
|
|
50
|
+
* - a polymorphic association keeps one target per target class
|
|
51
|
+
* (`Polymorphic/Task#taskable` → `Chore` and `Workout`), which is a real distinction, not a
|
|
52
|
+
* duplicate;
|
|
53
|
+
* - an association only some children render (`heartRatings`, rendered only by Latex's `stiUnion`
|
|
54
|
+
* serializer) is present exactly once.
|
|
55
|
+
*
|
|
56
|
+
* Grouping the serializers *under* the target rather than emitting one target per serializer is
|
|
57
|
+
* what lets a caller recurse once per target class with the full set, so nested trees from
|
|
58
|
+
* different children of the same target merge instead of clobbering one another.
|
|
59
|
+
*
|
|
60
|
+
* Insertion-ordered throughout, so edges come back in the order the serializers declare them.
|
|
61
|
+
*
|
|
62
|
+
* This is the single union both serializer walks run on: `buildSerializerPreloadPaths` turns these
|
|
63
|
+
* edges into preload paths (taking `terminates` and the target classes, ignoring the per-serializer
|
|
64
|
+
* edge type and serializer-side name), and `Dream.recursiveSerializationMap` prints them (taking the
|
|
65
|
+
* per-serializer edge type and name, ignoring `terminates`). Keeping the two walks on one merge is the point: the bug
|
|
66
|
+
* this exists to prevent is `psy i:serialization` and `preloadFor` disagreeing about which
|
|
67
|
+
* associations a serializer key reaches, which a second hand-maintained merge invites back.
|
|
68
|
+
*/
|
|
69
|
+
export default function mergeSerializerAssociationEdges(dreamClass: typeof Dream, serializers: SerializerType[]): MergedSerializerAssociationEdge[];
|
|
@@ -1,7 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { MergedSerializerAssociationTargetSerializer } from './mergeSerializerAssociationEdges.js';
|
|
2
|
+
/**
|
|
3
|
+
* @internal
|
|
4
|
+
*
|
|
5
|
+
* Prints one level of `psy i:serialization`'s tree: the heading for an association, then the name
|
|
6
|
+
* of every serializer that renders the target of that association at this point in the walk. That
|
|
7
|
+
* is more than one serializer when the target is an STI base, since each child may register a
|
|
8
|
+
* different serializer under the same key.
|
|
9
|
+
*
|
|
10
|
+
* Those serializers are printed under a *single* heading rather than one heading each, because the
|
|
11
|
+
* caller prints the union of their association trees below, indented one level in from the
|
|
12
|
+
* heading. Repeating the heading per serializer would leave that union sitting directly beneath
|
|
13
|
+
* whichever child's serializer happened to print last, reading as though every sibling's
|
|
14
|
+
* associations belonged to that one child. Grouped, the shape matches what `displaySerialization`
|
|
15
|
+
* prints at the root of the tree — one name, then every serializer that can render it, then one
|
|
16
|
+
* merged tree.
|
|
17
|
+
*
|
|
18
|
+
* A target reached under more than one edge type or serializer-side name (two children of an STI
|
|
19
|
+
* base rendering the same class through `rendersOne` and `rendersMany`, say) gets one heading per
|
|
20
|
+
* distinct (type, name), since those are genuinely different lines rather than repetitions of one.
|
|
21
|
+
*/
|
|
22
|
+
export default function printSerializerHierarchyLevel({ targetSerializers, forDisplayDepth, }: {
|
|
23
|
+
targetSerializers: MergedSerializerAssociationTargetSerializer[];
|
|
6
24
|
forDisplayDepth: number;
|
|
7
25
|
}): void;
|
|
@@ -2,7 +2,6 @@ import Dream from '../../Dream.js';
|
|
|
2
2
|
import { DreamModelSerializerType, SimpleObjectSerializerType } from '../../types/serializer.js';
|
|
3
3
|
export interface ResolvedSerializerAssociationEdge {
|
|
4
4
|
associationAs: string;
|
|
5
|
-
sourceDreamClass: typeof Dream;
|
|
6
5
|
type: 'rendersOne' | 'rendersMany' | 'delegatedAttribute';
|
|
7
6
|
serializerAssociationName: string;
|
|
8
7
|
targets: {
|
|
@@ -10,4 +9,31 @@ export interface ResolvedSerializerAssociationEdge {
|
|
|
10
9
|
serializer: DreamModelSerializerType | SimpleObjectSerializerType;
|
|
11
10
|
}[];
|
|
12
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* @internal
|
|
14
|
+
*
|
|
15
|
+
* Drops every memoized edge set. Only needed when a class object that stays in the process has its
|
|
16
|
+
* `serializers` getter replaced, which the memo cannot detect — specs that install or remove one
|
|
17
|
+
* around an example. Replacing the model classes themselves needs no call, since the memo keys on
|
|
18
|
+
* the class and serializer objects and entries resolved against the previous ones become
|
|
19
|
+
* unreachable.
|
|
20
|
+
*/
|
|
21
|
+
export declare function clearResolvedSerializerAssociationEdgesCache(): void;
|
|
22
|
+
/**
|
|
23
|
+
* @internal
|
|
24
|
+
*
|
|
25
|
+
* Resolves the rendersOne/rendersMany/delegatedAttribute attributes of `serializer` to the
|
|
26
|
+
* associations of `dreamClass` they preload, along with the classes and serializers each one
|
|
27
|
+
* targets.
|
|
28
|
+
*
|
|
29
|
+
* Memoized on its two arguments, because every call rebuilds a throwaway serializer builder and
|
|
30
|
+
* re-infers the serializers of every association target, and the traversal in
|
|
31
|
+
* buildSerializerPreloadPaths revisits the same (class, serializer) pair many times.
|
|
32
|
+
*
|
|
33
|
+
* The two arguments are not the function's only inputs, though: resolving a target's serializers
|
|
34
|
+
* also reads that target's `serializers` getter, the STI children registered on it, and the
|
|
35
|
+
* DreamApp serializer registry. Loading a fresh set of classes replaces the keys along with that
|
|
36
|
+
* state, so the memo follows it. Mutating a `serializers` getter in place does not, and needs
|
|
37
|
+
* `clearResolvedSerializerAssociationEdgesCache` — see there.
|
|
38
|
+
*/
|
|
13
39
|
export default function resolveSerializerAssociationEdges(dreamClass: typeof Dream, serializer: DreamModelSerializerType | SimpleObjectSerializerType): ResolvedSerializerAssociationEdge[];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import Dream from '../../Dream.js';
|
|
2
|
+
import { ViewModelClass } from '../../types/dream.js';
|
|
3
|
+
/**
|
|
4
|
+
* Raised when resolving a serializer key against a class yields an empty set of serializers.
|
|
5
|
+
*
|
|
6
|
+
* This is an internal-invariant failure, not a misconfiguration: an unresolvable serializer key
|
|
7
|
+
* raises `MissingSerializersDefinitionForKey` (and a class with no `serializers` getter at all
|
|
8
|
+
* raises `MissingSerializersDefinition`) while it is being resolved, and STI expansion always
|
|
9
|
+
* yields at least one class, so there is no supported application shape that reaches this. It is
|
|
10
|
+
* typed rather than a bare `Error` so that it can be asserted on, and so that the two callers that
|
|
11
|
+
* guard the invariant raise the same thing.
|
|
12
|
+
*/
|
|
13
|
+
export default class NoSerializersResolvedForKey extends Error {
|
|
14
|
+
private classDef;
|
|
15
|
+
private serializerKey;
|
|
16
|
+
constructor(classDef: typeof Dream | ViewModelClass | null | undefined, serializerKey: string);
|
|
17
|
+
get message(): string;
|
|
18
|
+
}
|
|
@@ -4,3 +4,19 @@ import { DreamModelSerializerType, SerializerType, SimpleObjectSerializerType }
|
|
|
4
4
|
export declare const DEFAULT_SERIALIZER_KEY = "default";
|
|
5
5
|
export default function inferSerializerFromDreamOrViewModel<T extends Dream | ViewModel | null | undefined, ReturnType extends T extends null | undefined ? null : T extends Dream | ViewModel ? SerializerType : never>(obj: T, serializerKey?: string | undefined): ReturnType;
|
|
6
6
|
export declare function inferSerializersFromDreamClassOrViewModelClass(classDef: typeof Dream | ViewModelClass | null | undefined, serializerKey?: string | undefined): (DreamModelSerializerType | SimpleObjectSerializerType)[];
|
|
7
|
+
/**
|
|
8
|
+
* As `inferSerializersFromDreamClassOrViewModelClass`, but guarantees a non-empty result.
|
|
9
|
+
*
|
|
10
|
+
* The guard is unreachable today, and deliberately not phrased as "no serializer found for key": a
|
|
11
|
+
* serializer key that a class does not register throws `MissingSerializersDefinitionForKey` (and a
|
|
12
|
+
* class with no `serializers` getter at all throws `MissingSerializersDefinition`) from inside the
|
|
13
|
+
* inference above, and `expandStiClasses` always yields at least one class. It exists only so that
|
|
14
|
+
* a future change to any of those cannot turn an unresolvable serializer key into a silently empty
|
|
15
|
+
* serializer set — which, for the callers below, means a silently empty preload set and a
|
|
16
|
+
* `NonLoadedAssociation` at render rather than an error naming the real problem.
|
|
17
|
+
*
|
|
18
|
+
* Used by everything that walks a serializer graph rooted on a class rather than an instance —
|
|
19
|
+
* preload-path building and serialization display — so that they raise one typed error between
|
|
20
|
+
* them.
|
|
21
|
+
*/
|
|
22
|
+
export declare function inferSerializersFromDreamClassOrViewModelClassOrFail(classDef: typeof Dream | ViewModelClass | null | undefined, serializerKey: string): (DreamModelSerializerType | SimpleObjectSerializerType)[];
|