@rvoh/dream 2.24.1 → 2.26.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 +10 -1
- package/dist/cjs/src/cli/index.js +31 -5
- package/dist/cjs/src/db/migration-helpers/DreamMigrationHelpers.js +127 -6
- package/dist/cjs/src/dream/Query.js +247 -67
- package/dist/cjs/src/dream/internal/printSerializerHierarchyLevel.js +4 -3
- package/dist/cjs/src/dream/internal/resolveSerializerAssociationEdges.js +14 -1
- package/dist/cjs/src/errors/BatchingIncompatibleWithLimitOrOffset.js +25 -0
- package/dist/cjs/src/errors/DropEnumValueRetypeFailed.js +208 -0
- package/dist/cjs/src/errors/MissingRequiredLockOptionForUpdateCallback.js +16 -0
- package/dist/cjs/src/errors/serializers/MissingSerializersDefinition.js +16 -2
- package/dist/cjs/src/errors/serializers/MissingSerializersDefinitionForKey.js +5 -2
- package/dist/cjs/src/errors/serializers/NoGlobalSerializerForSpecifiedKey.js +5 -3
- package/dist/cjs/src/errors/serializers/NoSerializersResolvedForKey.js +5 -2
- package/dist/cjs/src/errors/serializers/NonDreamSerializerDerivedFromGlobalSerializerForSpecifiedKey.js +5 -3
- package/dist/cjs/src/errors/serializers/serializerResolutionContextMessage.js +46 -0
- package/dist/cjs/src/helpers/cli/generateFactoryContent.js +7 -1
- package/dist/cjs/src/helpers/cli/generateMigrationContent.js +80 -13
- package/dist/cjs/src/helpers/cli/syncWithFailureContext.js +41 -0
- package/dist/cjs/src/serializer/SerializerRenderer.js +32 -4
- package/dist/cjs/src/serializer/helpers/inferSerializerFromDreamOrViewModel.js +28 -10
- package/dist/cjs/src/serializer/helpers/serializerForAssociatedClass.js +6 -1
- package/dist/cjs/src/serializer/helpers/serializerGlobalName.js +16 -0
- package/dist/esm/src/Dream.js +10 -1
- package/dist/esm/src/cli/index.js +31 -5
- package/dist/esm/src/db/migration-helpers/DreamMigrationHelpers.js +127 -6
- package/dist/esm/src/dream/Query.js +247 -67
- package/dist/esm/src/dream/internal/printSerializerHierarchyLevel.js +4 -3
- package/dist/esm/src/dream/internal/resolveSerializerAssociationEdges.js +14 -1
- package/dist/esm/src/errors/BatchingIncompatibleWithLimitOrOffset.js +25 -0
- package/dist/esm/src/errors/DropEnumValueRetypeFailed.js +208 -0
- package/dist/esm/src/errors/MissingRequiredLockOptionForUpdateCallback.js +16 -0
- package/dist/esm/src/errors/serializers/MissingSerializersDefinition.js +16 -2
- package/dist/esm/src/errors/serializers/MissingSerializersDefinitionForKey.js +5 -2
- package/dist/esm/src/errors/serializers/NoGlobalSerializerForSpecifiedKey.js +5 -3
- package/dist/esm/src/errors/serializers/NoSerializersResolvedForKey.js +5 -2
- package/dist/esm/src/errors/serializers/NonDreamSerializerDerivedFromGlobalSerializerForSpecifiedKey.js +5 -3
- package/dist/esm/src/errors/serializers/serializerResolutionContextMessage.js +46 -0
- package/dist/esm/src/helpers/cli/generateFactoryContent.js +7 -1
- package/dist/esm/src/helpers/cli/generateMigrationContent.js +80 -13
- package/dist/esm/src/helpers/cli/syncWithFailureContext.js +41 -0
- package/dist/esm/src/serializer/SerializerRenderer.js +32 -4
- package/dist/esm/src/serializer/helpers/inferSerializerFromDreamOrViewModel.js +28 -10
- package/dist/esm/src/serializer/helpers/serializerForAssociatedClass.js +6 -1
- package/dist/esm/src/serializer/helpers/serializerGlobalName.js +16 -0
- package/dist/types/src/Dream.d.ts +8 -2
- package/dist/types/src/cli/index.d.ts +1 -1
- package/dist/types/src/dream/Query.d.ts +316 -40
- package/dist/types/src/errors/BatchingIncompatibleWithLimitOrOffset.d.ts +3 -0
- package/dist/types/src/errors/DropEnumValueRetypeFailed.d.ts +89 -0
- package/dist/types/src/errors/MissingRequiredLockOptionForUpdateCallback.d.ts +3 -0
- package/dist/types/src/errors/serializers/MissingSerializersDefinition.d.ts +3 -1
- package/dist/types/src/errors/serializers/MissingSerializersDefinitionForKey.d.ts +3 -1
- package/dist/types/src/errors/serializers/NoGlobalSerializerForSpecifiedKey.d.ts +3 -1
- package/dist/types/src/errors/serializers/NoSerializersResolvedForKey.d.ts +3 -1
- package/dist/types/src/errors/serializers/NonDreamSerializerDerivedFromGlobalSerializerForSpecifiedKey.d.ts +3 -1
- package/dist/types/src/errors/serializers/serializerResolutionContextMessage.d.ts +20 -0
- package/dist/types/src/helpers/cli/syncWithFailureContext.d.ts +58 -0
- package/dist/types/src/serializer/helpers/inferSerializerFromDreamOrViewModel.d.ts +11 -4
- package/dist/types/src/serializer/helpers/serializerGlobalName.d.ts +15 -0
- package/dist/types/src/types/serializer.d.ts +38 -0
- package/dist/types/src/types/types/serializer.ts +40 -0
- package/docs/assets/search.js +1 -1
- 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 +123 -137
- 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 +735 -73
- 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
|
@@ -9,33 +9,51 @@ import expandStiClasses from '../../helpers/sti/expandStiClasses.js';
|
|
|
9
9
|
import uniq from '../../helpers/uniq.js';
|
|
10
10
|
import isDreamSerializer from './isDreamSerializer.js';
|
|
11
11
|
export const DEFAULT_SERIALIZER_KEY = 'default';
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* `resolutionContext` says how resolution reached `obj` — the `rendersOne`/`rendersMany` edge that
|
|
14
|
+
* led here, and the STI base it was expanded from. It is threaded down purely so that the errors
|
|
15
|
+
* below can name the route instead of only the class, and it is optional so that a caller with no
|
|
16
|
+
* route to describe (and every existing caller outside this repo) is unaffected. See
|
|
17
|
+
* `SerializerResolutionContext`.
|
|
18
|
+
*/
|
|
19
|
+
export default function inferSerializerFromDreamOrViewModel(obj, serializerKey = DEFAULT_SERIALIZER_KEY, resolutionContext) {
|
|
13
20
|
if (!obj)
|
|
14
21
|
return null;
|
|
15
22
|
const serializers = obj.serializers;
|
|
16
23
|
if (!serializers)
|
|
17
|
-
throw new MissingSerializersDefinition(obj);
|
|
24
|
+
throw new MissingSerializersDefinition(obj, serializerKey, resolutionContext);
|
|
18
25
|
const serializerOrGlobalName = serializers[serializerKey];
|
|
19
26
|
if (!serializerOrGlobalName)
|
|
20
|
-
throw new MissingSerializersDefinitionForKey(obj, serializerKey);
|
|
27
|
+
throw new MissingSerializersDefinitionForKey(obj, serializerKey, resolutionContext);
|
|
21
28
|
if (isDreamSerializer(serializerOrGlobalName))
|
|
22
29
|
return serializerOrGlobalName;
|
|
23
30
|
const globalName = serializerOrGlobalName;
|
|
24
31
|
const dreamApp = DreamApp.getOrFail();
|
|
25
32
|
const serializer = dreamApp.serializers[globalName];
|
|
26
33
|
if (!serializer)
|
|
27
|
-
throw new NoGlobalSerializerForSpecifiedKey(obj, serializerKey, globalName);
|
|
34
|
+
throw new NoGlobalSerializerForSpecifiedKey(obj, serializerKey, globalName, resolutionContext);
|
|
28
35
|
if (!isDreamSerializer(serializer))
|
|
29
|
-
throw new NonDreamSerializerDerivedFromGlobalSerializerForSpecifiedKey(obj, serializerKey, globalName, serializer);
|
|
36
|
+
throw new NonDreamSerializerDerivedFromGlobalSerializerForSpecifiedKey(obj, serializerKey, globalName, serializer, resolutionContext);
|
|
30
37
|
return serializer;
|
|
31
38
|
}
|
|
32
|
-
export function inferSerializersFromDreamClassOrViewModelClass(classDef, serializerKey = undefined) {
|
|
39
|
+
export function inferSerializersFromDreamClassOrViewModelClass(classDef, serializerKey = undefined, resolutionContext) {
|
|
33
40
|
if (!classDef)
|
|
34
41
|
return [];
|
|
35
42
|
const classes = expandStiClasses(classDef);
|
|
36
|
-
|
|
43
|
+
// This is the one place that knows *whether* the class an error ends up naming came from STI
|
|
44
|
+
// expansion: below this line the class the caller passed is gone, replaced by one child per
|
|
45
|
+
// iteration, and every downstream error names a child. Identity rather than `isSTIBase` because
|
|
46
|
+
// `classDef` may be a ViewModelClass, which `expandStiClasses` hands straight back.
|
|
47
|
+
const expandedFromStiBase = classes.length !== 1 || classes[0] !== classDef;
|
|
48
|
+
const childResolutionContext = expandedFromStiBase
|
|
49
|
+
? { ...resolutionContext, stiBase: stiBaseName(classDef) }
|
|
50
|
+
: resolutionContext;
|
|
51
|
+
const serializers = classes.map(classDef => inferSerializerFromDreamOrViewModel(classDef.prototype, serializerKey, childResolutionContext));
|
|
37
52
|
return uniq(compact(serializers));
|
|
38
53
|
}
|
|
54
|
+
function stiBaseName(classDef) {
|
|
55
|
+
return classDef.sanitizedName ?? classDef.name;
|
|
56
|
+
}
|
|
39
57
|
/**
|
|
40
58
|
* As `inferSerializersFromDreamClassOrViewModelClass`, but guarantees a non-empty result.
|
|
41
59
|
*
|
|
@@ -51,9 +69,9 @@ export function inferSerializersFromDreamClassOrViewModelClass(classDef, seriali
|
|
|
51
69
|
* preload-path building and serialization display — so that they raise one typed error between
|
|
52
70
|
* them.
|
|
53
71
|
*/
|
|
54
|
-
export function inferSerializersFromDreamClassOrViewModelClassOrFail(classDef, serializerKey) {
|
|
55
|
-
const serializers = inferSerializersFromDreamClassOrViewModelClass(classDef, serializerKey);
|
|
72
|
+
export function inferSerializersFromDreamClassOrViewModelClassOrFail(classDef, serializerKey, resolutionContext) {
|
|
73
|
+
const serializers = inferSerializersFromDreamClassOrViewModelClass(classDef, serializerKey, resolutionContext);
|
|
56
74
|
if (serializers.length === 0)
|
|
57
|
-
throw new NoSerializersResolvedForKey(classDef, serializerKey);
|
|
75
|
+
throw new NoSerializersResolvedForKey(classDef, serializerKey, resolutionContext);
|
|
58
76
|
return serializers;
|
|
59
77
|
}
|
|
@@ -28,5 +28,10 @@ export function serializerForAssociatedClass(object, associationName, options) {
|
|
|
28
28
|
// would flatten. Unioning every child's serializer here — which is what buildSerializerPreloadPaths
|
|
29
29
|
// does for preload paths — is meaningless: exactly one set of keys can be flattened into the
|
|
30
30
|
// payload.
|
|
31
|
-
return inferSerializersFromDreamClassOrViewModelClass(associatedClass, options.serializerKey
|
|
31
|
+
return (inferSerializersFromDreamClassOrViewModelClass(associatedClass, options.serializerKey, {
|
|
32
|
+
// Reached only from SerializerRenderer's rendersOne branch, so the edge type is fixed. No
|
|
33
|
+
// declaring serializer: the renderer holds the rendered attribute, not the serializer that
|
|
34
|
+
// declared it.
|
|
35
|
+
edge: { type: 'rendersOne', associationName },
|
|
36
|
+
})[0] ?? null);
|
|
32
37
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @internal
|
|
3
|
+
*
|
|
4
|
+
* The single reader of a serializer's `globalName`.
|
|
5
|
+
*
|
|
6
|
+
* Serializers are plain functions; `globalName` is stamped onto them by `importSerializers` when the
|
|
7
|
+
* app loads them, so the property is not on any serializer type and every reader would otherwise
|
|
8
|
+
* have to invent its own cast — three different spellings of it existed before this helper.
|
|
9
|
+
*
|
|
10
|
+
* `undefined` is a legitimate answer: a serializer built inline (a spec, or a `{ serializer }`
|
|
11
|
+
* option) is never stamped. Callers decide what that means for them — the resolution-context message
|
|
12
|
+
* simply drops the declarer.
|
|
13
|
+
*/
|
|
14
|
+
export default function serializerGlobalName(serializer) {
|
|
15
|
+
return serializer.globalName;
|
|
16
|
+
}
|
|
@@ -8,7 +8,7 @@ import LoadBuilder from './dream/LoadBuilder.js';
|
|
|
8
8
|
import Query from './dream/Query.js';
|
|
9
9
|
import { OnStatementForSpecificColumns, PassthroughOnClause, WhereStatement } from './types/associations/shared.js';
|
|
10
10
|
import { AssociationTableNames, DbConnectionType } from './types/db.js';
|
|
11
|
-
import { AllDefaultScopeNames, AssociationNameToDream, AttributeKeys, CreateOrFindByExtraOpts, DefaultOrNamedScopeName, DreamAssociationNames, DreamAssociationNamesWithoutRequiredOnClauses, DreamAssociationNamesWithPassthroughOnClauses, DreamAssociationNamesWithRequiredOnClauses, DreamAttributes, DreamBelongsToAssociationNames, DreamColumnNames, DreamHasManyAssociationNames, DreamHasOneAssociationNames, DreamModelAssociationNames,
|
|
11
|
+
import { AllDefaultScopeNames, AssociationNameToDream, AttributeKeys, CreateOrFindByExtraOpts, DefaultOrNamedScopeName, DreamAssociationNames, DreamAssociationNamesWithoutRequiredOnClauses, DreamAssociationNamesWithPassthroughOnClauses, DreamAssociationNamesWithRequiredOnClauses, DreamAttributes, DreamBelongsToAssociationNames, DreamColumnNames, DreamHasManyAssociationNames, DreamHasOneAssociationNames, DreamModelAssociationNames, DreamPrimaryKeyType, DreamSerializerKey, FindablePropertiesForClass, GlobalModelNames, GlobalSerializerName, JoinAndStatements, OrderDir, PassthroughColumnNames, PassthroughOnClauseKeys, PluckEachArgs, PrimaryKeyForFind, RequiredOnClauseKeys, TableColumnNames, UpdateableAssociationProperties, UpdateableProperties, UpdateablePropertiesForClass, UpdateOrCreateByExtraOpts } from './types/dream.js';
|
|
12
12
|
import { BaseModelColumnTypes, CursorPaginatedDreamQueryOptions, CursorPaginatedDreamQueryResult, DefaultQueryTypeOptions, FindEachOpts, LoadForModifierFn, PaginatedDreamQueryOptions, PaginatedDreamQueryResult, QueryWithJoinedAssociationsType, QueryWithJoinedAssociationsTypeAndNoPreload } from './types/query.js';
|
|
13
13
|
import { DreamModelSerializerType, SimpleObjectSerializerType } from './types/serializer.js';
|
|
14
14
|
import { ValidationType } from './types/validation.js';
|
|
@@ -458,9 +458,15 @@ export default class Dream {
|
|
|
458
458
|
* * foreign key fields for belongs to associations (these should usually be verified before being set)
|
|
459
459
|
* * type fields corresponding to polymorphic associations
|
|
460
460
|
*
|
|
461
|
+
* @internal
|
|
462
|
+
*
|
|
463
|
+
* Not part of the public API. Psychic reaches it through the bracketed
|
|
464
|
+
* back-door (`dreamClass['paramSafeColumnsOrFallback']()`), the same escape
|
|
465
|
+
* hatch used for other Dream internals such as `virtualAttributes`.
|
|
466
|
+
*
|
|
461
467
|
* @returns A subset of columns for the given dream class
|
|
462
468
|
*/
|
|
463
|
-
static paramSafeColumnsOrFallback
|
|
469
|
+
private static paramSafeColumnsOrFallback;
|
|
464
470
|
private static defaultParamSafeColumns;
|
|
465
471
|
/**
|
|
466
472
|
* @internal
|
|
@@ -23,7 +23,7 @@ export declare const columnsWithTypesDescriptionForStiChild: string;
|
|
|
23
23
|
* example that drifts from what the generator actually produces then fails the
|
|
24
24
|
* suite instead of shipping.
|
|
25
25
|
*/
|
|
26
|
-
export declare const migrationNameArgumentDescription = "Kebab-case name describing the change. A -to-<table_name>\n anywhere in the name \u2014 not just at the end \u2014 auto-generates\n an add-columns alterTable scaffold for that table;\n -from-<table_name> generates a remove-columns scaffold, but\n only when the name contains no -to- at all. The text after\n the marker's first occurrence is snake-cased and pluralized\n into the table name, so add-a-to-b-to-cs targets b_to_cs. A\n hand-written migration must avoid both strings anywhere in\n its name.\n \n Examples:\n add-phone-to-users phone:encrypted # scaffolds alterTable('users', ...)\n remove-status-from-posts status:string # scaffolds alterTable('posts', ...)\n create-many-to-many-posts-users # WRONG: selects 'many_posts_users'\n # -to- inside \"many-to-many\" matches: with no columns\n # this errors; with columns it silently scaffolds it\n create-posts-users-join-table # correct: no marker match\n # scaffolds alterTable('<table-name>') to hand-edit";
|
|
26
|
+
export declare const migrationNameArgumentDescription = "Kebab-case name describing the change. A -to-<table_name>\n anywhere in the name \u2014 not just at the end \u2014 auto-generates\n an add-columns alterTable scaffold for that table;\n -from-<table_name> generates a remove-columns scaffold, but\n only when the name contains no -to- at all. The text after\n the marker's first occurrence is snake-cased and pluralized\n into the table name, so add-a-to-b-to-cs targets b_to_cs. A\n hand-written migration must avoid both strings anywhere in\n its name.\n \n Examples:\n add-phone-to-users phone:encrypted # scaffolds alterTable('users', ...)\n remove-status-from-posts status:string # scaffolds alterTable('posts', ...)\n create-many-to-many-posts-users # WRONG: selects 'many_posts_users'\n # -to- inside \"many-to-many\" matches: with no columns\n # this errors; with columns it silently scaffolds it\n create-posts-users-join-table # correct: no marker match\n # with no columns, generates empty up/down methods to\n # write by hand; with columns, scaffolds\n # alterTable('<table-name>') to hand-edit";
|
|
27
27
|
export default class DreamCLI {
|
|
28
28
|
/**
|
|
29
29
|
* Starts the Dream console
|
|
@@ -27,6 +27,7 @@ export default class Query<DreamInstance extends Dream, QueryTypeOpts extends Re
|
|
|
27
27
|
static readonly BATCH_SIZES: {
|
|
28
28
|
FIND_EACH: number;
|
|
29
29
|
LOCKED_DESTROY: number;
|
|
30
|
+
LOCKED_UPDATE: number;
|
|
30
31
|
PLUCK_EACH: number;
|
|
31
32
|
PLUCK_EACH_THROUGH: number;
|
|
32
33
|
};
|
|
@@ -317,10 +318,16 @@ export default class Query<DreamInstance extends Dream, QueryTypeOpts extends Re
|
|
|
317
318
|
* If you need records processed in a particular order, load them with a
|
|
318
319
|
* regular ordered query instead of `findEach`.
|
|
319
320
|
*
|
|
321
|
+
* A `limit` or `offset` on the Query cannot compose with the batch windows
|
|
322
|
+
* — each batch re-applies the Query's conditions, so the limit would be
|
|
323
|
+
* silently replaced by `batchSize` and the offset re-applied to every
|
|
324
|
+
* window — so a limit- or offset-carrying Query throws at runtime.
|
|
325
|
+
*
|
|
320
326
|
* @param cb - The callback to call for each found record
|
|
321
327
|
* @param __namedParameters - Options for batch processing
|
|
322
328
|
* @param __namedParameters.batchSize - The batch size you wish to collect records in. If not provided, it will default to 1000
|
|
323
329
|
* @returns void
|
|
330
|
+
* @throws BatchingIncompatibleWithLimitOrOffset if the query carries a `limit` or `offset`
|
|
324
331
|
*/
|
|
325
332
|
findEach(this: Query<DreamInstance, QueryTypeOpts>, cb: (instance: DreamInstance) => void | Promise<void>, { batchSize }?: {
|
|
326
333
|
batchSize?: number;
|
|
@@ -639,6 +646,12 @@ export default class Query<DreamInstance extends Dream, QueryTypeOpts extends Re
|
|
|
639
646
|
* // [User{id: 1}, User{id: 2}]
|
|
640
647
|
* ```
|
|
641
648
|
*
|
|
649
|
+
* A limit is incompatible with `paginate` and `leftJoinPreload`, and with
|
|
650
|
+
* the batched and set-write operations — `findEach`, `pluckEach`,
|
|
651
|
+
* `destroy`, `reallyDestroy`, `undestroy`, and `update` — none of which can
|
|
652
|
+
* honor one: the batched and set-write operations throw
|
|
653
|
+
* `BatchingIncompatibleWithLimitOrOffset` on a limit-carrying Query.
|
|
654
|
+
*
|
|
642
655
|
* @returns A cloned Query with the limit clause applied
|
|
643
656
|
*/
|
|
644
657
|
limit<Q extends Query<DreamInstance, any>, Incompatible extends Q['queryTypeOpts'] extends Readonly<{
|
|
@@ -655,6 +668,12 @@ export default class Query<DreamInstance extends Dream, QueryTypeOpts extends Re
|
|
|
655
668
|
* // [User{id: 3}, User{id: 4}]
|
|
656
669
|
* ```
|
|
657
670
|
*
|
|
671
|
+
* An offset is incompatible with `paginate` and `leftJoinPreload`, and with
|
|
672
|
+
* the batched and set-write operations — `findEach`, `pluckEach`,
|
|
673
|
+
* `destroy`, `reallyDestroy`, `undestroy`, and `update` — none of which can
|
|
674
|
+
* honor one: the batched and set-write operations throw
|
|
675
|
+
* `BatchingIncompatibleWithLimitOrOffset` on an offset-carrying Query.
|
|
676
|
+
*
|
|
658
677
|
* @returns A cloned Query with the offset clause applied
|
|
659
678
|
*/
|
|
660
679
|
offset<Q extends Query<DreamInstance, any>, Incompatible extends Q['queryTypeOpts'] extends Readonly<{
|
|
@@ -978,8 +997,14 @@ export default class Query<DreamInstance extends Dream, QueryTypeOpts extends Re
|
|
|
978
997
|
* // 3
|
|
979
998
|
* ```
|
|
980
999
|
*
|
|
1000
|
+
* A `limit` or `offset` on the Query cannot compose with the batch windows
|
|
1001
|
+
* — each window replaces the limit with the batch size and the offset with
|
|
1002
|
+
* the running window offset, silently ignoring both — so a limit- or
|
|
1003
|
+
* offset-carrying Query throws at runtime.
|
|
1004
|
+
*
|
|
981
1005
|
* @param args - a list of column names to pluck, followed by a callback function to call for each set of found fields
|
|
982
1006
|
* @returns void
|
|
1007
|
+
* @throws BatchingIncompatibleWithLimitOrOffset if the query carries a `limit` or `offset`
|
|
983
1008
|
*/
|
|
984
1009
|
pluckEach<Q extends Query<DreamInstance, QueryTypeOpts>, DB extends DreamInstance['DB'], const ColumnNames extends ColumnNamesAccountingForJoinedAssociations<Q['queryTypeOpts']['joinedAssociations'], DB, QueryTypeOpts['rootTableName'], QueryTypeOpts['rootTableAlias']>[], CbArgTypes extends NamespacedOrBaseModelColumnTypes<ColumnNames, Q, DreamInstance>>(this: Q, ...args: PluckEachArgs<ColumnNames, CbArgTypes>): Promise<void>;
|
|
985
1010
|
/**
|
|
@@ -1307,20 +1332,27 @@ export default class Query<DreamInstance extends Dream, QueryTypeOpts extends Re
|
|
|
1307
1332
|
* scale, ties up connections. If that is a risk in your application, set a
|
|
1308
1333
|
* `lock_timeout` on the connection so a stuck batch fails instead of
|
|
1309
1334
|
* hanging.
|
|
1335
|
+
* - **A `limit` or `offset` on the Query is incompatible with `destroy` on
|
|
1336
|
+
* every path.** The batches — locked and unlocked alike — re-apply the
|
|
1337
|
+
* Query's conditions to each window, where a limit or offset would skip
|
|
1338
|
+
* or truncate rows inside every window instead of bounding the run as a
|
|
1339
|
+
* whole, so `destroy` throws rather than silently corrupting the
|
|
1340
|
+
* windows.
|
|
1310
1341
|
* - **If you are destroying a large set and do not need compare-and-set, do
|
|
1311
1342
|
* not pass `lock`.** Plain `destroy()` (hooks and cascade, no locking) or
|
|
1312
1343
|
* {@link Query.delete | delete} (a single statement, no hooks or cascade)
|
|
1313
1344
|
* are the right tools for bulk removal.
|
|
1314
1345
|
*
|
|
1315
|
-
* @param
|
|
1316
|
-
* @param
|
|
1317
|
-
* @param
|
|
1318
|
-
* @param
|
|
1319
|
-
* @param
|
|
1320
|
-
* @param
|
|
1321
|
-
* @param
|
|
1346
|
+
* @param options - Options for destroying the instance
|
|
1347
|
+
* @param options.skipHooks - If true, skips applying model hooks during the destroy operation. Defaults to false
|
|
1348
|
+
* @param options.cascade - If false, skips destroying associations marked `dependent: 'destroy'`. Defaults to true
|
|
1349
|
+
* @param options.lock - If true, each batch is re-selected with an exclusive row lock inside its own transaction before being destroyed, making the destroy a compare-and-set. Defaults to false
|
|
1350
|
+
* @param options.batchSize - The number of records to process per batch. Must be a positive integer. Defaults to 10 when `lock` is true, and to 1000 otherwise
|
|
1351
|
+
* @param options.bypassAllDefaultScopes - If true, bypasses all default scopes when cascade destroying. Defaults to false
|
|
1352
|
+
* @param options.defaultScopesToBypass - An array of default scope names to bypass when cascade destroying. Defaults to an empty array
|
|
1322
1353
|
* @returns The number of records that were removed
|
|
1323
1354
|
* @throws InvalidBatchSize if `batchSize` is not a positive integer
|
|
1355
|
+
* @throws BatchingIncompatibleWithLimitOrOffset if the query carries a `limit` or `offset`
|
|
1324
1356
|
*/
|
|
1325
1357
|
destroy({ skipHooks, cascade, lock, batchSize, }?: {
|
|
1326
1358
|
skipHooks?: boolean | undefined;
|
|
@@ -1331,37 +1363,52 @@ export default class Query<DreamInstance extends Dream, QueryTypeOpts extends Re
|
|
|
1331
1363
|
/**
|
|
1332
1364
|
* @internal
|
|
1333
1365
|
*
|
|
1334
|
-
* The compare-and-set implementation behind `destroy({ lock: true })
|
|
1366
|
+
* The compare-and-set implementation behind `destroy({ lock: true })`,
|
|
1367
|
+
* driven by {@link Query.lockedBatches}, which owns the keyset cursor, the
|
|
1368
|
+
* pluck/claim two-step, and the transaction dispatch.
|
|
1369
|
+
*
|
|
1370
|
+
* Each claimed record is destroyed via the ordinary per-instance path, and
|
|
1371
|
+
* every claimed record counts. (One thing the count does not account for,
|
|
1372
|
+
* because it never has: a `beforeDestroy` hook that sets `_preventDeletion`
|
|
1373
|
+
* leaves the record in place but still counts.)
|
|
1374
|
+
*/
|
|
1375
|
+
private lockedDestroy;
|
|
1376
|
+
/**
|
|
1377
|
+
* @internal
|
|
1378
|
+
*
|
|
1379
|
+
* The shared batch driver behind `destroy({ lock: true })` and
|
|
1380
|
+
* `update(..., { lock: true })`: the keyset cursor, the pluck/claim
|
|
1381
|
+
* two-step, and the transaction dispatch live here, so the locked
|
|
1382
|
+
* operations cannot drift apart.
|
|
1335
1383
|
*
|
|
1336
1384
|
* Each batch is handled in two steps inside one transaction:
|
|
1337
1385
|
*
|
|
1338
1386
|
* 1. an unlocked pluck of the next `batchSize` primary keys, in ascending
|
|
1339
1387
|
* primary key order, which establishes the batch's window (its keyset
|
|
1340
|
-
* cursor) and tells us how many records were *attempted*. Only the keys
|
|
1341
|
-
* read: hydrating and preloading these records would duplicate the
|
|
1342
|
-
* step 2 does over the same rows; then
|
|
1388
|
+
* cursor) and tells us how many records were *attempted*. Only the keys
|
|
1389
|
+
* are read: hydrating and preloading these records would duplicate the
|
|
1390
|
+
* work step 2 does over the same rows; then
|
|
1343
1391
|
* 2. a locked re-read restricted to exactly those primary keys, which
|
|
1344
|
-
* re-applies every condition on the Query while acquiring an exclusive
|
|
1345
|
-
* lock on each row it returns. Records a concurrent transaction has
|
|
1346
|
-
* out of the Query since step 1 do not come back, and are the
|
|
1347
|
-
* run lost the race for.
|
|
1348
|
-
*
|
|
1349
|
-
* The
|
|
1350
|
-
*
|
|
1351
|
-
*
|
|
1352
|
-
*
|
|
1353
|
-
*
|
|
1354
|
-
*
|
|
1355
|
-
*
|
|
1356
|
-
*
|
|
1357
|
-
*
|
|
1358
|
-
* race skips only the records that were lost.
|
|
1392
|
+
* re-applies every condition on the Query while acquiring an exclusive
|
|
1393
|
+
* row lock on each row it returns. Records a concurrent transaction has
|
|
1394
|
+
* moved out of the Query since step 1 do not come back, and are the
|
|
1395
|
+
* records this run lost the race for.
|
|
1396
|
+
*
|
|
1397
|
+
* The claimed records are handed to `perBatch` inside the batch's
|
|
1398
|
+
* transaction; its return value — how many of the claimed records were
|
|
1399
|
+
* processed — accumulates into the returned count.
|
|
1400
|
+
*
|
|
1401
|
+
* The two-step shape is what keeps iteration correct. If the locked read
|
|
1402
|
+
* both claimed rows and advanced the cursor, a batch whose records all lost
|
|
1403
|
+
* the race would come back empty and end the run early, silently skipping
|
|
1404
|
+
* every record after it. Advancing the cursor from step 1 instead means
|
|
1405
|
+
* losing a race skips only the records that were lost.
|
|
1359
1406
|
*
|
|
1360
1407
|
* When the Query already carries a transaction, that transaction is used for
|
|
1361
1408
|
* every batch rather than opening one per batch, so the caller's transaction
|
|
1362
1409
|
* boundary wins and the locks are held until the caller commits.
|
|
1363
1410
|
*/
|
|
1364
|
-
private
|
|
1411
|
+
private lockedBatches;
|
|
1365
1412
|
/**
|
|
1366
1413
|
* Destroys all records matching the Query,
|
|
1367
1414
|
* ignoring the SoftDelete decorator and
|
|
@@ -1380,15 +1427,20 @@ export default class Query<DreamInstance extends Dream, QueryTypeOpts extends Re
|
|
|
1380
1427
|
* // 12
|
|
1381
1428
|
* ```
|
|
1382
1429
|
*
|
|
1383
|
-
*
|
|
1384
|
-
*
|
|
1385
|
-
*
|
|
1386
|
-
*
|
|
1387
|
-
* @param
|
|
1388
|
-
* @param
|
|
1389
|
-
* @param
|
|
1430
|
+
* A `limit` or `offset` on the Query is incompatible with `reallyDestroy`,
|
|
1431
|
+
* exactly as it is with {@link Query.destroy}: a limit- or offset-carrying
|
|
1432
|
+
* Query throws at runtime.
|
|
1433
|
+
*
|
|
1434
|
+
* @param options - Options for destroying the instance
|
|
1435
|
+
* @param options.skipHooks - If true, skips applying model hooks during the destroy operation. Defaults to false
|
|
1436
|
+
* @param options.cascade - If false, skips destroying associations marked `dependent: 'destroy'`. Defaults to true
|
|
1437
|
+
* @param options.lock - If true, each batch is re-selected with an exclusive row lock inside its own transaction before being destroyed, making the destroy a compare-and-set. See {@link Query.destroy} for the full semantics. Defaults to false
|
|
1438
|
+
* @param options.batchSize - The number of records to process per batch. Must be a positive integer. Defaults to 10 when `lock` is true, and to 1000 otherwise
|
|
1439
|
+
* @param options.bypassAllDefaultScopes - If true, bypasses all default scopes when cascade destroying. Defaults to false
|
|
1440
|
+
* @param options.defaultScopesToBypass - An array of default scope names to bypass when cascade destroying. Defaults to an empty array
|
|
1390
1441
|
* @returns The number of records that were removed
|
|
1391
1442
|
* @throws InvalidBatchSize if `batchSize` is not a positive integer
|
|
1443
|
+
* @throws BatchingIncompatibleWithLimitOrOffset if the query carries a `limit` or `offset`
|
|
1392
1444
|
*/
|
|
1393
1445
|
reallyDestroy({ skipHooks, cascade, lock, batchSize, }?: {
|
|
1394
1446
|
skipHooks?: boolean;
|
|
@@ -1408,12 +1460,19 @@ export default class Query<DreamInstance extends Dream, QueryTypeOpts extends Re
|
|
|
1408
1460
|
* // 12
|
|
1409
1461
|
* ```
|
|
1410
1462
|
*
|
|
1411
|
-
*
|
|
1412
|
-
*
|
|
1413
|
-
*
|
|
1414
|
-
*
|
|
1415
|
-
*
|
|
1463
|
+
* A `limit` or `offset` on the Query cannot compose with the batch windows
|
|
1464
|
+
* `undestroy` iterates in — each batch re-applies the Query's conditions,
|
|
1465
|
+
* so the limit would be silently replaced by the batch size and the offset
|
|
1466
|
+
* re-applied to every window — so a limit- or offset-carrying Query throws
|
|
1467
|
+
* at runtime.
|
|
1468
|
+
*
|
|
1469
|
+
* @param options - Options for undestroying the instance
|
|
1470
|
+
* @param options.skipHooks - If true, skips applying model hooks during the undestroy operation. Defaults to false
|
|
1471
|
+
* @param options.cascade - If false, skips undestroying associations marked `dependent: 'destroy'`. Defaults to true
|
|
1472
|
+
* @param options.bypassAllDefaultScopes - If true, bypasses all default scopes when cascade undestroying. Defaults to false
|
|
1473
|
+
* @param options.defaultScopesToBypass - An array of default scope names to bypass when cascade undestroying (soft delete is always bypassed). Defaults to an empty array
|
|
1416
1474
|
* @returns The number of records that were removed
|
|
1475
|
+
* @throws BatchingIncompatibleWithLimitOrOffset if the query carries a `limit` or `offset`
|
|
1417
1476
|
*/
|
|
1418
1477
|
undestroy({ cascade, skipHooks, }?: {
|
|
1419
1478
|
cascade?: boolean;
|
|
@@ -1443,15 +1502,232 @@ export default class Query<DreamInstance extends Dream, QueryTypeOpts extends Re
|
|
|
1443
1502
|
* await User.where({ email: ops.ilike('%burpcollaborator%') }).update({ email: null })
|
|
1444
1503
|
* // 12
|
|
1445
1504
|
* ```
|
|
1446
|
-
*
|
|
1505
|
+
*
|
|
1506
|
+
* Instead of an attributes object, the first argument may be a callback
|
|
1507
|
+
* that derives each record's attributes from the record itself. The
|
|
1508
|
+
* callback (sync or async) receives each matching record and returns the
|
|
1509
|
+
* attributes to write for it, or `undefined` to skip it — a skipped record
|
|
1510
|
+
* gets no write, runs no hooks, and is not counted (a `null` return, from
|
|
1511
|
+
* untyped callers, is treated the same). Only the returned attributes are
|
|
1512
|
+
* written: the callback receives a clone of the record, so mutations made
|
|
1513
|
+
* directly on the received record are discarded rather than riding along
|
|
1514
|
+
* with the write. With a callback, the options object and its `lock` key
|
|
1515
|
+
* are mandatory, and `lock` is a full boolean, so every call site says out
|
|
1516
|
+
* loud whether the callback runs under row locks:
|
|
1517
|
+
*
|
|
1518
|
+
* ```ts
|
|
1519
|
+
* await User.query().update(
|
|
1520
|
+
* user => (user.email.endsWith('@example.com') ? { name: 'example user' } : undefined),
|
|
1521
|
+
* { lock: false }
|
|
1522
|
+
* )
|
|
1523
|
+
* ```
|
|
1524
|
+
*
|
|
1525
|
+
* Under `lock: false`, the callback runs once per matching record —
|
|
1526
|
+
* batched reads (`batchSize` defaults to 1000 here), no locks, no claim
|
|
1527
|
+
* step, no compare-and-set guarantee — the right shape for a derived
|
|
1528
|
+
* backfill where racing writers are not a concern. (On a `@ReplicaSafe`
|
|
1529
|
+
* model, these unlocked batch reads may come from a lagging replica,
|
|
1530
|
+
* feeding stale column values into the callback; use `lock: true` to
|
|
1531
|
+
* derive from the freshest committed state — its transactional reads pin
|
|
1532
|
+
* the primary.) Under `lock: true`, the callback runs once per claimed
|
|
1533
|
+
* record, under that record's row lock, inside the batch's transaction,
|
|
1534
|
+
* receiving a clone of the freshly locked read; Dream applies the returned
|
|
1535
|
+
* attributes to the record it holds and writes before the batch commits,
|
|
1536
|
+
* so there is no transaction handle for the caller to forget. (The record
|
|
1537
|
+
* the callback receives is not bound to the batch's transaction — see the
|
|
1538
|
+
* callback bullet below before touching the database from inside a
|
|
1539
|
+
* callback.) That makes the callback form the tool
|
|
1540
|
+
* for serializing writers on state a `WHERE` clause cannot express — for
|
|
1541
|
+
* example an `@Encrypted` column, whose ciphertext changes on every write
|
|
1542
|
+
* and whose plaintext has no column to compare against in the database:
|
|
1543
|
+
*
|
|
1544
|
+
* ```ts
|
|
1545
|
+
* const rotated = await User.where({ id: userId }).update(
|
|
1546
|
+
* user => (user.secret === expectedSecret ? { secret: newSecret } : undefined),
|
|
1547
|
+
* { lock: true }
|
|
1548
|
+
* )
|
|
1549
|
+
* // 1 — or 0, if a competing writer got there first (the callback saw a
|
|
1550
|
+
* // different secret and skipped the record)
|
|
1551
|
+
* ```
|
|
1552
|
+
*
|
|
1553
|
+
* With a callback, the returned count is the number of records the
|
|
1554
|
+
* callback returned attributes for: skipped records are not counted, and
|
|
1555
|
+
* under `lock: true` records that lost the race never reach the callback
|
|
1556
|
+
* at all. (As with the attributes form, a write that turns out to be a
|
|
1557
|
+
* no-op still counts — the count means "the callback chose to write", not
|
|
1558
|
+
* "SQL was emitted".)
|
|
1559
|
+
*
|
|
1560
|
+
* The `lock` option turns the update into a guarded (compare-and-set)
|
|
1561
|
+
* update: each record is written only if it still matches the Query at the
|
|
1562
|
+
* moment an exclusive row lock on it is acquired. Use it when the Query's
|
|
1563
|
+
* conditions are the thing being changed and a concurrent writer racing this
|
|
1564
|
+
* call must not have its write clobbered:
|
|
1565
|
+
*
|
|
1566
|
+
* ```ts
|
|
1567
|
+
* const updated = await Order.where({ status: 'pending' })
|
|
1568
|
+
* .update({ status: 'processing' }, { lock: true })
|
|
1569
|
+
* // 0 — someone else moved every one of them out of 'pending' first
|
|
1570
|
+
* ```
|
|
1571
|
+
*
|
|
1572
|
+
* With `lock: true`, records are processed in batches, and each batch runs
|
|
1573
|
+
* in its own transaction which first re-selects that batch's records with an
|
|
1574
|
+
* exclusive row lock and then updates them before committing. Because the
|
|
1575
|
+
* database re-checks the Query's conditions after acquiring each row lock, a
|
|
1576
|
+
* record a concurrent transaction has already moved out of the Query drops
|
|
1577
|
+
* out of the locked read and is never updated. The returned count is the
|
|
1578
|
+
* number of records claimed and submitted for writing, so a caller can
|
|
1579
|
+
* detect that it lost a race by comparing the count against what it
|
|
1580
|
+
* expected. (A claimed record whose write turns out to be a no-op — the
|
|
1581
|
+
* attributes already match, or a hook cleared every dirty attribute — still
|
|
1582
|
+
* counts; the count is not a guarantee that SQL was emitted per counted
|
|
1583
|
+
* record.)
|
|
1584
|
+
*
|
|
1585
|
+
* Things to know before relying on it:
|
|
1586
|
+
*
|
|
1587
|
+
* - **The guarantee is per batch, not set-wide.** A run spanning more than
|
|
1588
|
+
* one batch can win a race in batch 1 and lose one in batch 2; nothing
|
|
1589
|
+
* holds the whole matched set still for the duration of the run. Sharing
|
|
1590
|
+
* one transaction across every batch — by threading a transaction onto the
|
|
1591
|
+
* Query itself, `Order.query().txn(txn).where({ ... }).update({ ... },
|
|
1592
|
+
* { lock: true })` — buys two things: the updates become all-or-nothing (a
|
|
1593
|
+
* rollback undoes the whole run), and the row locks taken by every batch
|
|
1594
|
+
* are held until you commit. It does **not** extend the compare-and-set to
|
|
1595
|
+
* the part of the set the run has not reached yet: under READ COMMITTED
|
|
1596
|
+
* each statement takes a fresh snapshot, so a record moved out of the
|
|
1597
|
+
* Query between batch 1 and batch 2 still drops out of batch 2's locked
|
|
1598
|
+
* read. A genuinely set-wide guarantee needs REPEATABLE READ /
|
|
1599
|
+
* SERIALIZABLE, or a single locking statement over the whole set.
|
|
1600
|
+
* - **Only a transaction carried by the Query is shared.** Merely wrapping
|
|
1601
|
+
* the call in `Model.transaction(...)` does not thread that transaction
|
|
1602
|
+
* onto the Query — `Dream.transaction` establishes no ambient transaction,
|
|
1603
|
+
* it only hands a `txn` to your callback — so each batch would open its
|
|
1604
|
+
* own transaction on a different pooled connection. Worse, if the
|
|
1605
|
+
* surrounding transaction has already written to any of the matched rows,
|
|
1606
|
+
* each batch's locked read blocks on locks the surrounding transaction
|
|
1607
|
+
* holds while that transaction awaits the update: a cycle across two
|
|
1608
|
+
* connections, which Postgres's deadlock detector cannot see, so it hangs
|
|
1609
|
+
* rather than aborting. Always pass the transaction with `.txn(txn)`.
|
|
1610
|
+
* - **Behavior above READ COMMITTED differs.** The drop-out-of-the-result
|
|
1611
|
+
* behavior described above is READ COMMITTED (Postgres's default). Under
|
|
1612
|
+
* REPEATABLE READ or SERIALIZABLE, the same race raises a serialization
|
|
1613
|
+
* failure instead of silently returning a lower count. Both are safe; only
|
|
1614
|
+
* one of them is quiet.
|
|
1615
|
+
* - **`batchSize` defaults to 10 here**, not `findEach`'s 1000, and the
|
|
1616
|
+
* default is deliberately small. Every row in a batch stays locked for the
|
|
1617
|
+
* whole batch, including the time spent running each record's hooks — and,
|
|
1618
|
+
* on the callback form, the time spent running the Query's `preload`
|
|
1619
|
+
* queries, which are fetched inside the lock window (the attributes form
|
|
1620
|
+
* strips preloads, which it never consumes) — so an oversized batch blocks
|
|
1621
|
+
* unrelated writers touching those rows and surfaces as timeouts somewhere
|
|
1622
|
+
* else in the application, which is very hard to trace back to this
|
|
1623
|
+
* update. Too small a batch, by contrast, only slows down this call, which
|
|
1624
|
+
* you can see and fix. Lower it further for models with expensive update
|
|
1625
|
+
* hooks.
|
|
1626
|
+
* - **The callback must not write to the database.** The record handed to
|
|
1627
|
+
* the callback is not bound to the batch's transaction: a write through it
|
|
1628
|
+
* (e.g. `await record.update(...)`) goes out on a different pooled
|
|
1629
|
+
* connection and blocks on the batch's own row locks while the batch
|
|
1630
|
+
* transaction waits on the callback — a cycle across two connections that
|
|
1631
|
+
* the database's deadlock detector cannot see, so with no `lock_timeout`
|
|
1632
|
+
* the run hangs indefinitely and pins both connections. Reads through the
|
|
1633
|
+
* record (`reload`, `associationQuery`, ...) likewise run on a separate
|
|
1634
|
+
* connection and snapshot. Derive the attributes and return them; Dream
|
|
1635
|
+
* performs the write inside the batch's transaction.
|
|
1636
|
+
* - **`lock: false` can throw mid-run when racing a deleter.** On the
|
|
1637
|
+
* unlocked paths, a matched record that a concurrent transaction destroys
|
|
1638
|
+
* between a batch's read and that record's write aborts the run with an
|
|
1639
|
+
* error: earlier batches stay committed, and no count is returned. Use
|
|
1640
|
+
* `lock: true` when deleters may race the update — claimed rows are held
|
|
1641
|
+
* under their locks until written.
|
|
1642
|
+
* - **A `limit` or `offset` on the Query is incompatible with `update` on
|
|
1643
|
+
* every path.** The batched paths re-apply the Query's conditions to each
|
|
1644
|
+
* batch window, where a limit or offset would skip or truncate rows
|
|
1645
|
+
* inside every window instead of bounding the run as a whole, and the
|
|
1646
|
+
* single-statement `skipHooks` attributes form cannot express either in
|
|
1647
|
+
* its SQL UPDATE. Every form throws rather than silently corrupting the
|
|
1648
|
+
* windows or ignoring the caller's scope.
|
|
1649
|
+
* - **A batch waits, without bound, for whoever holds the row.** The locked
|
|
1650
|
+
* read is a plain `FOR UPDATE`: if another transaction already holds a
|
|
1651
|
+
* lock on one of the batch's rows, this call blocks until that transaction
|
|
1652
|
+
* ends, holding its own transaction and its pooled connection open the
|
|
1653
|
+
* whole time. Dream sets no `lock_timeout` or `statement_timeout`, so a
|
|
1654
|
+
* long-running writer on the other side stalls the run indefinitely and,
|
|
1655
|
+
* at scale, ties up connections. If that is a risk in your application,
|
|
1656
|
+
* set a `lock_timeout` on the connection so a stuck batch fails instead of
|
|
1657
|
+
* hanging.
|
|
1658
|
+
* - **`skipHooks` composes with `lock`, but stays on the per-instance
|
|
1659
|
+
* path.** With an attributes object and no `lock`, `skipHooks: true`
|
|
1660
|
+
* issues a single `UPDATE ... WHERE` statement that writes raw columns.
|
|
1661
|
+
* With `lock: true`, `skipHooks: true` skips the model hooks on each
|
|
1662
|
+
* claimed record but still writes through the instance, so custom setters
|
|
1663
|
+
* — including the setters encrypted attributes encrypt through — run as
|
|
1664
|
+
* they would for a plain instance update. (Virtual attributes whose
|
|
1665
|
+
* processing lives in a hook are still skipped, exactly as in an instance
|
|
1666
|
+
* update with `skipHooks`.) A callback never takes the single-statement
|
|
1667
|
+
* path: `skipHooks` beside a callback only skips the hooks on each
|
|
1668
|
+
* per-record write, under `lock: true` and `lock: false` alike.
|
|
1669
|
+
* - **If you are updating a large set and do not need compare-and-set, do
|
|
1670
|
+
* not pass `lock`.** Plain `update(attributes)` (hooks, no locking) or
|
|
1671
|
+
* `update(attributes, { skipHooks: true })` (a single statement, no hooks)
|
|
1672
|
+
* are the right tools for bulk updates.
|
|
1673
|
+
*
|
|
1674
|
+
* @param attributesOrCb - The attributes used to update the records, or a callback receiving each record and returning the attributes to write for it (or `undefined` to skip that record). Passing a callback makes `options` and its `lock` key mandatory
|
|
1447
1675
|
* @param options - Options for updating the instance
|
|
1448
1676
|
* @param options.skipHooks - If true, skips applying model hooks. Defaults to false
|
|
1677
|
+
* @param options.lock - If true, each batch is re-selected with an exclusive row lock inside its own transaction before being updated, making the update a compare-and-set. Defaults to false for the attributes form; with a callback there is no default — an explicit boolean is required. One typing asymmetry to know: with an attributes object, an explicit `lock: false` keeps the raw single-statement (`DreamTableSchema`) typing that `skipHooks` uses, so virtual/encrypted attributes type-check only when the options object is omitted entirely or `lock` is `true`
|
|
1678
|
+
* @param options.batchSize - The number of records to process per batch. Must be a positive integer. Defaults to 10 when `lock` is true, and to 1000 for the unlocked callback form
|
|
1449
1679
|
* @returns The number of records that were updated
|
|
1680
|
+
* @throws InvalidBatchSize if `batchSize` is not a positive integer
|
|
1681
|
+
* @throws MissingRequiredLockOptionForUpdateCallback if a callback is passed without an options object carrying a boolean `lock`
|
|
1682
|
+
* @throws BatchingIncompatibleWithLimitOrOffset if the query carries a `limit` or `offset`
|
|
1450
1683
|
*/
|
|
1684
|
+
update(cb: (record: DreamInstance) => UpdateableProperties<DreamInstance> | undefined | Promise<UpdateableProperties<DreamInstance> | undefined>, options: {
|
|
1685
|
+
lock: boolean;
|
|
1686
|
+
batchSize?: number;
|
|
1687
|
+
skipHooks?: boolean;
|
|
1688
|
+
}): Promise<number>;
|
|
1689
|
+
update(attributes: UpdateableProperties<DreamInstance>, options: {
|
|
1690
|
+
lock: true;
|
|
1691
|
+
batchSize?: number;
|
|
1692
|
+
skipHooks?: boolean;
|
|
1693
|
+
}): Promise<number>;
|
|
1451
1694
|
update(attributes: DreamTableSchema<DreamInstance>, options?: {
|
|
1452
1695
|
skipHooks?: boolean;
|
|
1696
|
+
lock?: false;
|
|
1453
1697
|
}): Promise<number>;
|
|
1454
1698
|
update(attributes: UpdateableProperties<DreamInstance>): Promise<number>;
|
|
1699
|
+
/**
|
|
1700
|
+
* @internal
|
|
1701
|
+
*
|
|
1702
|
+
* The compare-and-set implementation behind `update(attributes, { lock: true })`,
|
|
1703
|
+
* driven by {@link Query.lockedBatches}, which owns the keyset cursor, the
|
|
1704
|
+
* pluck/claim two-step, and the transaction dispatch.
|
|
1705
|
+
*
|
|
1706
|
+
* Each claimed record is handed to `cb`, which returns the attributes to
|
|
1707
|
+
* write for it, or a nullish value to skip it. The attributes form is the
|
|
1708
|
+
* constant-callback case (a callback ignoring the record and always
|
|
1709
|
+
* returning the same attributes), which is how the two forms share this
|
|
1710
|
+
* implementation. (`update` hands the user's own callback a clone of each
|
|
1711
|
+
* claimed record, so only the returned attributes reach the write below —
|
|
1712
|
+
* see the dispatch in {@link Query.update}.)
|
|
1713
|
+
*
|
|
1714
|
+
* The count is the number of claimed records the callback returned
|
|
1715
|
+
* attributes for: records attempted but not claimed (lost races) never
|
|
1716
|
+
* reach the callback, and claimed records the callback skipped are not
|
|
1717
|
+
* counted. For the attributes form the callback is total, so the count
|
|
1718
|
+
* degenerates to records claimed and submitted for writing. (A record whose
|
|
1719
|
+
* write turns out to be a no-op still counts: the instance path returns
|
|
1720
|
+
* without issuing SQL when nothing is dirty.)
|
|
1721
|
+
*
|
|
1722
|
+
* Each claimed record is written via the ordinary per-instance update, so
|
|
1723
|
+
* hooks (unless `skipHooks`), custom setters, and virtual/encrypted
|
|
1724
|
+
* attribute preprocessing behave exactly as in a plain instance update.
|
|
1725
|
+
*
|
|
1726
|
+
* No join-load guard is needed here: update's own joins guards have already
|
|
1727
|
+
* thrown on every join-load query (leftJoinPreload always carries left-join
|
|
1728
|
+
* statements).
|
|
1729
|
+
*/
|
|
1730
|
+
private lockedUpdate;
|
|
1455
1731
|
private updateWithoutCallingModelHooks;
|
|
1456
1732
|
private invertOrder;
|
|
1457
1733
|
}
|