@rvoh/dream 2.25.0 → 2.27.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 -18
- 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/DreamClassTransactionBuilder.js +0 -18
- package/dist/cjs/src/dream/Query.js +0 -23
- package/dist/cjs/src/dream/QueryDriver/Kysely.js +8 -8
- 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/DropEnumValueRetypeFailed.js +208 -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 -18
- 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/DreamClassTransactionBuilder.js +0 -18
- package/dist/esm/src/dream/Query.js +0 -23
- package/dist/esm/src/dream/QueryDriver/Kysely.js +8 -8
- 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/DropEnumValueRetypeFailed.js +208 -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 +10 -3
- package/dist/types/src/cli/index.d.ts +1 -1
- package/dist/types/src/dream/DreamClassTransactionBuilder.d.ts +3 -2
- package/dist/types/src/dream/Query.d.ts +3 -2
- package/dist/types/src/errors/DropEnumValueRetypeFailed.d.ts +89 -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/associations/shared.d.ts +1 -1
- package/dist/types/src/types/dream.d.ts +3 -0
- package/dist/types/src/types/query.d.ts +1 -1
- package/dist/types/src/types/serializer.d.ts +38 -0
- package/dist/types/src/types/types/associations/shared.ts +8 -6
- package/dist/types/src/types/types/dream.ts +6 -0
- package/dist/types/src/types/types/query.ts +1 -1
- 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 +132 -138
- 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 +93 -80
- 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,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLSTATEs Postgres raises when it cannot re-derive a stored expression against a column's
|
|
3
|
+
* new type. `42883 undefined_function` covers both `operator does not exist: text <> my_enum`
|
|
4
|
+
* (the widen) and `function char_length(my_enum) does not exist` (the restore); `42804
|
|
5
|
+
* datatype_mismatch` covers the argument-type variant and
|
|
6
|
+
* `default for column "x" cannot be cast automatically to type my_enum`.
|
|
7
|
+
*
|
|
8
|
+
* A retype can fail for reasons that have nothing to do with a stored expression — most
|
|
9
|
+
* obviously `22P02 invalid input value for enum` when a value written by `replacements` is not
|
|
10
|
+
* a member of the recreated enum. Those codes are deliberately absent, so the constraints found
|
|
11
|
+
* on the column are reported as context rather than as the diagnosis.
|
|
12
|
+
*
|
|
13
|
+
* These codes say *an* expression could not be re-derived. They do not say **which** one, and
|
|
14
|
+
* the message must not pretend otherwise: a check constraint, a partial index's `WHERE`
|
|
15
|
+
* predicate, an expression index and an uncastable column `DEFAULT` all raise them, and
|
|
16
|
+
* `dropEnumValue` reads only the check constraints. Verified against Postgres 18 —
|
|
17
|
+
* `CREATE INDEX i ON t (id) WHERE val <> 'a'` followed by `ALTER TABLE t ALTER COLUMN val TYPE
|
|
18
|
+
* text` raises exactly `42883 operator does not exist: text <> my_enum`, the same code and the
|
|
19
|
+
* same wording a blocking check constraint produces.
|
|
20
|
+
*/
|
|
21
|
+
const EXPRESSION_RETYPE_SQLSTATES = new Set(['42804', '42883']);
|
|
22
|
+
/**
|
|
23
|
+
* Quote every segment of a possibly schema-qualified table name the way Kysely quotes the
|
|
24
|
+
* identifiers it emits, so a `to_regclass` argument resolves exactly the relation the retype
|
|
25
|
+
* targeted. Exported because `DreamMigrationHelpers`' catalog read and the diagnostic query in
|
|
26
|
+
* this message must name the relation identically; it lives here because that helper already
|
|
27
|
+
* imports this module, and the reverse import would be a cycle.
|
|
28
|
+
*/
|
|
29
|
+
export function quotedRelationName(table) {
|
|
30
|
+
return table
|
|
31
|
+
.split('.')
|
|
32
|
+
.map(segment => `"${segment.replace(/"/g, '""')}"`)
|
|
33
|
+
.join('.');
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Raised when one of the two column retypes inside
|
|
37
|
+
* `DreamMigrationHelpers.dropEnumValue` fails.
|
|
38
|
+
*
|
|
39
|
+
* The raw Postgres error for the common cause of this - a check constraint on the
|
|
40
|
+
* column that references the enum - is `operator does not exist: text <> my_enum`.
|
|
41
|
+
* It names no table, no column and no constraint, and the `text` in it comes from
|
|
42
|
+
* `dropEnumValue`'s own internal widen step, so it appears nowhere in the
|
|
43
|
+
* migration the developer wrote. This error supplies all of that, and keeps the
|
|
44
|
+
* original error reachable through both `originalError` and `cause`.
|
|
45
|
+
*
|
|
46
|
+
* It never claims a check constraint caused a failure it cannot tie to one. The SQLSTATE gate
|
|
47
|
+
* separates "a stored expression could not be re-derived" from "a bad value" — but it cannot
|
|
48
|
+
* say *which* expression, because an index predicate raises the identical code, so even on the
|
|
49
|
+
* gated path the constraints stay context and the message names the other candidates.
|
|
50
|
+
*/
|
|
51
|
+
export default class DropEnumValueRetypeFailed extends Error {
|
|
52
|
+
enumName;
|
|
53
|
+
value;
|
|
54
|
+
table;
|
|
55
|
+
column;
|
|
56
|
+
retypeDirection;
|
|
57
|
+
relationResolved;
|
|
58
|
+
checkConstraints;
|
|
59
|
+
originalError;
|
|
60
|
+
constructor({ enumName, value, table, column, retypeDirection, relationResolved, checkConstraints, originalError, }) {
|
|
61
|
+
super(undefined, { cause: originalError });
|
|
62
|
+
this.enumName = enumName;
|
|
63
|
+
this.value = value;
|
|
64
|
+
this.table = table;
|
|
65
|
+
this.column = column;
|
|
66
|
+
this.retypeDirection = retypeDirection;
|
|
67
|
+
this.relationResolved = relationResolved;
|
|
68
|
+
this.checkConstraints = checkConstraints;
|
|
69
|
+
this.originalError = originalError;
|
|
70
|
+
}
|
|
71
|
+
get message() {
|
|
72
|
+
return `
|
|
73
|
+
DreamMigrationHelpers.dropEnumValue failed while retyping ${this.table}.${this.column}.
|
|
74
|
+
|
|
75
|
+
enum: ${this.enumName}
|
|
76
|
+
value being dropped: '${this.value}'
|
|
77
|
+
table: ${this.table}
|
|
78
|
+
column: ${this.column}
|
|
79
|
+
failing step: ${this.failingStepDescription}
|
|
80
|
+
|
|
81
|
+
${this.stepExplanation}
|
|
82
|
+
|
|
83
|
+
${this.checkConstraintsSection}
|
|
84
|
+
|
|
85
|
+
${this.guidanceSection}
|
|
86
|
+
|
|
87
|
+
The original database error was:
|
|
88
|
+
|
|
89
|
+
${this.originalError.message}
|
|
90
|
+
`;
|
|
91
|
+
}
|
|
92
|
+
get failingStepDescription() {
|
|
93
|
+
return this.retypeDirection === 'toText'
|
|
94
|
+
? `widening ${this.column} to text`
|
|
95
|
+
: `restoring ${this.column} to ${this.enumName}`;
|
|
96
|
+
}
|
|
97
|
+
get stepExplanation() {
|
|
98
|
+
if (this.retypeDirection === 'toText') {
|
|
99
|
+
return `dropEnumValue temporarily retypes every column listed in \`replacements\` to text so that
|
|
100
|
+
the ${this.enumName} type can be dropped and recreated without '${this.value}'. That text type is
|
|
101
|
+
dropEnumValue's own internal step, which is why the database error below can name a
|
|
102
|
+
type your migration never mentioned.`;
|
|
103
|
+
}
|
|
104
|
+
return `dropEnumValue recreated ${this.enumName} without '${this.value}' and was retyping every column
|
|
105
|
+
listed in \`replacements\` back to it.`;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* True when the original error is one Postgres raises because a stored expression on the
|
|
109
|
+
* column could not be re-derived against its new type. A blocking check constraint is one
|
|
110
|
+
* source of that; a partial index predicate, an expression index and an uncastable column
|
|
111
|
+
* default are others. Anything else (a bad value, a missing relation, a permission error) is
|
|
112
|
+
* not evidence that any stored expression was involved.
|
|
113
|
+
*/
|
|
114
|
+
get originalErrorBlockedAnExpression() {
|
|
115
|
+
const code = this.originalError.code;
|
|
116
|
+
return typeof code === 'string' && EXPRESSION_RETYPE_SQLSTATES.has(code);
|
|
117
|
+
}
|
|
118
|
+
get checkConstraintsSection() {
|
|
119
|
+
if (!this.relationResolved) {
|
|
120
|
+
return `The check constraints on ${this.table}.${this.column} could not be read: ${this.table} did not
|
|
121
|
+
resolve to a table when dropEnumValue looked it up, so whether a check constraint blocked
|
|
122
|
+
this retype is unknown. Read the original database error below.`;
|
|
123
|
+
}
|
|
124
|
+
if (this.checkConstraints.length === 0) {
|
|
125
|
+
return `No check constraint is defined on ${this.table}.${this.column}, so a check constraint is not
|
|
126
|
+
what blocked this retype. Read the original database error below.`;
|
|
127
|
+
}
|
|
128
|
+
const constraints = this.checkConstraints
|
|
129
|
+
.map(constraint => ` - ${constraint.name}\n ${constraint.definition}`)
|
|
130
|
+
.join('\n');
|
|
131
|
+
return `Check constraints defined on ${this.table}.${this.column}:
|
|
132
|
+
|
|
133
|
+
${constraints}`;
|
|
134
|
+
}
|
|
135
|
+
get guidanceSection() {
|
|
136
|
+
if (this.originalErrorBlockedAnExpression) {
|
|
137
|
+
return [this.expressionBlockerSection, this.constraintRemedySection].filter(Boolean).join('\n\n');
|
|
138
|
+
}
|
|
139
|
+
if (!this.relationResolved)
|
|
140
|
+
return 'No check constraints could be read, so none can be reconciled here.';
|
|
141
|
+
if (this.checkConstraints.length === 0)
|
|
142
|
+
return 'No check constraints to reconcile.';
|
|
143
|
+
return `The database error below is not the "an expression on this column could not be re-derived
|
|
144
|
+
against its new type" failure that a blocking check constraint produces, so the constraints
|
|
145
|
+
above are listed as context rather than as the diagnosis: they may or may not have anything
|
|
146
|
+
to do with this. Read the original database error below first.`;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* What the SQLSTATE does and does not establish, and every expression that raises it. The
|
|
150
|
+
* candidates are listed rather than diagnosed on purpose: `dropEnumValue` reads the column's
|
|
151
|
+
* check constraints and nothing else, so naming one of them as the cause would be a guess
|
|
152
|
+
* that sends the caller to drop a constraint that changes nothing.
|
|
153
|
+
*/
|
|
154
|
+
get expressionBlockerSection() {
|
|
155
|
+
return `The database error below is the one Postgres raises when an expression stored against
|
|
156
|
+
${this.column} could not be re-derived for the column's new type. The retype re-evaluates every
|
|
157
|
+
such expression at once, and the error names none of them, so it does not say on its own which
|
|
158
|
+
one blocked the retype. It is one of:
|
|
159
|
+
|
|
160
|
+
- a check constraint on the column — ${this.checkConstraintCandidateNote}
|
|
161
|
+
- a partial index whose WHERE predicate references the column (not read)
|
|
162
|
+
- an expression index over the column (not read)
|
|
163
|
+
- the column's DEFAULT, when it cannot be cast to the new type (not read, though that variant
|
|
164
|
+
names the column and the word "default" in the error below)
|
|
165
|
+
|
|
166
|
+
A view, a rule or a generated column that reads ${this.column} blocks the retype with a different
|
|
167
|
+
error that names itself ("cannot alter type of a column used by ..."), so none of those is what
|
|
168
|
+
happened here.
|
|
169
|
+
|
|
170
|
+
The indexes are what dropEnumValue cannot see. List them with:
|
|
171
|
+
|
|
172
|
+
SELECT indexrelid::regclass AS index, pg_get_indexdef(indexrelid) AS definition
|
|
173
|
+
FROM pg_index WHERE indrelid = to_regclass('${quotedRelationName(this.table)}');`;
|
|
174
|
+
}
|
|
175
|
+
get checkConstraintCandidateNote() {
|
|
176
|
+
if (!this.relationResolved)
|
|
177
|
+
return 'dropEnumValue could not read these, as noted above';
|
|
178
|
+
if (this.checkConstraints.length === 0)
|
|
179
|
+
return 'there are none on this column, so this one is ruled out';
|
|
180
|
+
return this.checkConstraints.length === 1 ? 'the one listed above' : 'the ones listed above';
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* The copy-pasteable remedy, offered only when there is a constraint to drop and phrased as
|
|
184
|
+
* conditional on that constraint actually being the blocker — which this error cannot know.
|
|
185
|
+
*/
|
|
186
|
+
get constraintRemedySection() {
|
|
187
|
+
if (!this.relationResolved || this.checkConstraints.length === 0)
|
|
188
|
+
return '';
|
|
189
|
+
const dropCalls = this.checkConstraints
|
|
190
|
+
.map(constraint => ` await DreamMigrationHelpers.dropConstraint(db, '${constraint.name}', { table: '${this.table}' })`)
|
|
191
|
+
.join('\n');
|
|
192
|
+
const them = this.checkConstraints.length === 1 ? 'it' : 'them';
|
|
193
|
+
return `If one of the check constraints above is the blocker, drop ${them} before calling dropEnumValue,
|
|
194
|
+
then re-add the ones that should survive. dropEnumValue deliberately does not do this for you:
|
|
195
|
+
only your migration knows which of these three outcomes each constraint needs.
|
|
196
|
+
|
|
197
|
+
1. a constraint that exists only to forbid '${this.value}' should be dropped and never re-added
|
|
198
|
+
2. a constraint that never mentions '${this.value}' should be re-added exactly as it was
|
|
199
|
+
3. a constraint that mentions '${this.value}' alongside other values should be re-added with
|
|
200
|
+
'${this.value}' struck from it
|
|
201
|
+
|
|
202
|
+
Drop ${them} with:
|
|
203
|
+
|
|
204
|
+
${dropCalls}
|
|
205
|
+
|
|
206
|
+
If dropping ${them} changes nothing, the blocker is one of the other expressions above.`;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -1,8 +1,22 @@
|
|
|
1
|
+
import serializerResolutionContextMessage from './serializerResolutionContextMessage.js';
|
|
1
2
|
export default class MissingSerializersDefinition extends Error {
|
|
2
3
|
viewModel;
|
|
3
|
-
|
|
4
|
+
/**
|
|
5
|
+
* ECMAScript-private (`#`) rather than TypeScript-`private`, unlike the four internal siblings of
|
|
6
|
+
* this error. A TypeScript `private` parameter property is a real, enumerable own property at
|
|
7
|
+
* runtime, and this is the one serializer-resolution error the package exports
|
|
8
|
+
* (`src/package-exports/errors.ts`) — so `Object.keys(err)` and `JSON.stringify(err)` on a
|
|
9
|
+
* consumer's error-logging path would newly report the diagnostic state threaded in here. `#`
|
|
10
|
+
* fields are invisible to both, so the exported error's observable runtime shape is unchanged.
|
|
11
|
+
* `viewModel` keeps its existing form: it predates this and narrowing it is not this change.
|
|
12
|
+
*/
|
|
13
|
+
#serializerKey;
|
|
14
|
+
#resolutionContext;
|
|
15
|
+
constructor(viewModel, serializerKey, resolutionContext) {
|
|
4
16
|
super();
|
|
5
17
|
this.viewModel = viewModel;
|
|
18
|
+
this.#serializerKey = serializerKey;
|
|
19
|
+
this.#resolutionContext = resolutionContext;
|
|
6
20
|
}
|
|
7
21
|
get message() {
|
|
8
22
|
const className = this.viewModel?.sanitizedConstructorName ?? this.viewModel.constructor.name;
|
|
@@ -18,6 +32,6 @@ class ${className} {
|
|
|
18
32
|
}
|
|
19
33
|
}
|
|
20
34
|
...
|
|
21
|
-
}`;
|
|
35
|
+
}${serializerResolutionContextMessage(this.#serializerKey, this.#resolutionContext)}`;
|
|
22
36
|
}
|
|
23
37
|
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import serializerResolutionContextMessage from './serializerResolutionContextMessage.js';
|
|
1
2
|
export default class MissingSerializersDefinitionForKey extends Error {
|
|
2
3
|
viewModel;
|
|
3
4
|
serializerKey;
|
|
4
|
-
|
|
5
|
+
resolutionContext;
|
|
6
|
+
constructor(viewModel, serializerKey, resolutionContext) {
|
|
5
7
|
super();
|
|
6
8
|
this.viewModel = viewModel;
|
|
7
9
|
this.serializerKey = serializerKey;
|
|
10
|
+
this.resolutionContext = resolutionContext;
|
|
8
11
|
}
|
|
9
12
|
get message() {
|
|
10
13
|
const className = this.viewModel?.sanitizedConstructorName ?? this.viewModel.constructor.name;
|
|
@@ -21,6 +24,6 @@ class ${className} {
|
|
|
21
24
|
}
|
|
22
25
|
}
|
|
23
26
|
...
|
|
24
|
-
}`;
|
|
27
|
+
}${serializerResolutionContextMessage(this.serializerKey, this.resolutionContext)}`;
|
|
25
28
|
}
|
|
26
29
|
}
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
+
import serializerResolutionContextMessage from './serializerResolutionContextMessage.js';
|
|
1
2
|
export default class NoGlobalSerializerForSpecifiedKey extends Error {
|
|
2
3
|
viewModel;
|
|
3
4
|
serializerKey;
|
|
4
5
|
globalName;
|
|
5
|
-
|
|
6
|
+
resolutionContext;
|
|
7
|
+
constructor(viewModel, serializerKey, globalName, resolutionContext) {
|
|
6
8
|
super();
|
|
7
9
|
this.viewModel = viewModel;
|
|
8
10
|
this.serializerKey = serializerKey;
|
|
9
11
|
this.globalName = globalName;
|
|
12
|
+
this.resolutionContext = resolutionContext;
|
|
10
13
|
}
|
|
11
14
|
get message() {
|
|
12
15
|
const className = this.viewModel?.sanitizedConstructorName ?? this.viewModel.constructor.name;
|
|
13
16
|
return `
|
|
14
17
|
${className} specified a global name of "${this.globalName}" for serializer key "${this.serializerKey}",
|
|
15
|
-
but no serializer corresponds to "${this.globalName}".
|
|
16
|
-
`;
|
|
18
|
+
but no serializer corresponds to "${this.globalName}".${serializerResolutionContextMessage(this.serializerKey, this.resolutionContext)}`;
|
|
17
19
|
}
|
|
18
20
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import serializerResolutionContextMessage from './serializerResolutionContextMessage.js';
|
|
1
2
|
/**
|
|
2
3
|
* Raised when resolving a serializer key against a class yields an empty set of serializers.
|
|
3
4
|
*
|
|
@@ -11,10 +12,12 @@
|
|
|
11
12
|
export default class NoSerializersResolvedForKey extends Error {
|
|
12
13
|
classDef;
|
|
13
14
|
serializerKey;
|
|
14
|
-
|
|
15
|
+
resolutionContext;
|
|
16
|
+
constructor(classDef, serializerKey, resolutionContext) {
|
|
15
17
|
super();
|
|
16
18
|
this.classDef = classDef;
|
|
17
19
|
this.serializerKey = serializerKey;
|
|
20
|
+
this.resolutionContext = resolutionContext;
|
|
18
21
|
}
|
|
19
22
|
get message() {
|
|
20
23
|
const className = this.classDef === null || this.classDef === undefined
|
|
@@ -25,6 +28,6 @@ No serializers resolved for \`${className}\` under serializer key \`${this.seria
|
|
|
25
28
|
|
|
26
29
|
This should be impossible — a serializer key that cannot be resolved throws while it is being
|
|
27
30
|
resolved, before reaching here — and indicates a bug in Dream rather than a problem with your
|
|
28
|
-
application. Please report it
|
|
31
|
+
application. Please report it.${serializerResolutionContextMessage(this.serializerKey, this.resolutionContext)}`;
|
|
29
32
|
}
|
|
30
33
|
}
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { inspect } from 'node:util';
|
|
2
|
+
import serializerResolutionContextMessage from './serializerResolutionContextMessage.js';
|
|
2
3
|
export default class NonDreamSerializerDerivedFromGlobalSerializerForSpecifiedKey extends Error {
|
|
3
4
|
viewModel;
|
|
4
5
|
serializerKey;
|
|
5
6
|
globalName;
|
|
6
7
|
serializer;
|
|
7
|
-
|
|
8
|
+
resolutionContext;
|
|
9
|
+
constructor(viewModel, serializerKey, globalName, serializer, resolutionContext) {
|
|
8
10
|
super();
|
|
9
11
|
this.viewModel = viewModel;
|
|
10
12
|
this.serializerKey = serializerKey;
|
|
11
13
|
this.globalName = globalName;
|
|
12
14
|
this.serializer = serializer;
|
|
15
|
+
this.resolutionContext = resolutionContext;
|
|
13
16
|
}
|
|
14
17
|
get message() {
|
|
15
18
|
const className = this.viewModel?.sanitizedConstructorName ?? this.viewModel.constructor.name;
|
|
@@ -17,7 +20,6 @@ export default class NonDreamSerializerDerivedFromGlobalSerializerForSpecifiedKe
|
|
|
17
20
|
${className} specified a global name of "${this.globalName}" for serializer key "${this.serializerKey}",
|
|
18
21
|
but the derived serializer is not a Dream serializer:
|
|
19
22
|
|
|
20
|
-
${inspect(this.serializer)}
|
|
21
|
-
`;
|
|
23
|
+
${inspect(this.serializer)}${serializerResolutionContextMessage(this.serializerKey, this.resolutionContext)}`;
|
|
22
24
|
}
|
|
23
25
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @internal
|
|
3
|
+
*
|
|
4
|
+
* Renders the trailing "how did resolution reach this class" block shared by the five
|
|
5
|
+
* serializer-resolution errors.
|
|
6
|
+
*
|
|
7
|
+
* Every line is independently well-formed and a line is omitted entirely — no placeholder, no empty
|
|
8
|
+
* slot — when the path that raised the error does not have that fact. The paths differ, and
|
|
9
|
+
* deliberately so:
|
|
10
|
+
*
|
|
11
|
+
* - the serializer key is on every path, since it is an argument of resolution itself;
|
|
12
|
+
* - the render edge is present only when a `rendersOne`/`rendersMany` led to the failing class. The
|
|
13
|
+
* three root entry points (`Dream.serializationMap`, `Dream.displaySerialization` and
|
|
14
|
+
* `buildSerializerPreloadPaths`) resolve a class the caller named directly, so nothing led there;
|
|
15
|
+
* - the declaring serializer is present only while preload paths and serialization maps are built.
|
|
16
|
+
* `SerializerRenderer` holds the rendered attribute but not the serializer that declared it;
|
|
17
|
+
* - the STI base is present only where expanding one produced the class the error names.
|
|
18
|
+
*/
|
|
19
|
+
export default function serializerResolutionContextMessage(serializerKey, context) {
|
|
20
|
+
const lines = [];
|
|
21
|
+
if (serializerKey !== undefined)
|
|
22
|
+
lines.push(['serializer key', serializerKey]);
|
|
23
|
+
const edge = context?.edge;
|
|
24
|
+
if (edge)
|
|
25
|
+
lines.push([
|
|
26
|
+
'reached through',
|
|
27
|
+
edge.declaredBy
|
|
28
|
+
? `${edge.type} \`${edge.associationName}\`, declared by ${edge.declaredBy}`
|
|
29
|
+
: `${edge.type} \`${edge.associationName}\``,
|
|
30
|
+
]);
|
|
31
|
+
if (context?.stiBase)
|
|
32
|
+
lines.push(['expanded from', `the STI base ${context.stiBase}`]);
|
|
33
|
+
if (lines.length === 0)
|
|
34
|
+
return '';
|
|
35
|
+
return `
|
|
36
|
+
|
|
37
|
+
Serializer resolution context:
|
|
38
|
+
|
|
39
|
+
${lines.map(([label, value]) => ` ${`${label}:`.padEnd(LABEL_COLUMN_WIDTH)}${value}`).join('\n')}`;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Fixed rather than computed from the lines actually present, so that the same fact sits in the
|
|
43
|
+
* same column in every one of these errors — a reader comparing two failures is not also comparing
|
|
44
|
+
* two layouts.
|
|
45
|
+
*/
|
|
46
|
+
const LABEL_COLUMN_WIDTH = 'reached through: '.length;
|
|
@@ -54,7 +54,13 @@ export default function generateFactoryContent({ fullyQualifiedModelName, column
|
|
|
54
54
|
belongsToNames.push(attributeVariable);
|
|
55
55
|
belongsToTypedNames.push(`${attributeVariable}: ${associationModelName}`);
|
|
56
56
|
additionalImports.push(associationFactoryImportStatement);
|
|
57
|
-
|
|
57
|
+
// The parens around `await` are what Prettier itself emits for an
|
|
58
|
+
// `await` operand of `??` (it parenthesizes unconditionally), and this
|
|
59
|
+
// string is written to the consumer's file verbatim — no formatting
|
|
60
|
+
// pass — so omitting them makes every freshly generated factory fail
|
|
61
|
+
// `prettier --check` in their project. See the Prettier fixed-point
|
|
62
|
+
// specs in spec/unit/cli/generateFactoryContent.spec.ts.
|
|
63
|
+
associationCreationStatements.push(`${attributeVariable}: attrs.${attributeVariable} ?? (await create${associationModelName}()),`);
|
|
58
64
|
break;
|
|
59
65
|
}
|
|
60
66
|
case 'encrypted':
|
|
@@ -175,19 +175,8 @@ export default function generateMigrationContent({ connectionName = 'default', t
|
|
|
175
175
|
}
|
|
176
176
|
return acc;
|
|
177
177
|
}, { columnDefs: [], columnDrops: [], indexDefs: [] });
|
|
178
|
-
if (!table)
|
|
179
|
-
return
|
|
180
|
-
import { Kysely, sql } from 'kysely'
|
|
181
|
-
|
|
182
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
183
|
-
export async function up(db: Kysely<any>): Promise<void> {
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
187
|
-
export async function down(db: Kysely<any>): Promise<void> {
|
|
188
|
-
}\
|
|
189
|
-
`;
|
|
190
|
-
}
|
|
178
|
+
if (!table)
|
|
179
|
+
return emptyMigrationContent();
|
|
191
180
|
// An alter migration (either `-to-`/add or `-from-`/remove) with no valid
|
|
192
181
|
// columns to add/drop would otherwise emit a bare `.alterTable(...).execute()`
|
|
193
182
|
// with no column operation at all, which Postgres rejects at migration-run
|
|
@@ -207,6 +196,22 @@ export async function down(db: Kysely<any>): Promise<void> {
|
|
|
207
196
|
!stiChildClassName) {
|
|
208
197
|
throw new NoColumnsToAlterMigration(table, alterDirection, migrationName, alterMarker);
|
|
209
198
|
}
|
|
199
|
+
// The placeholder table is the standalone `g:migration` markerless path (see
|
|
200
|
+
// MIGRATION_TABLE_NAME_PLACEHOLDER above): the migration name resolved no
|
|
201
|
+
// table, so there is nothing to alter. With no columns declared either, there
|
|
202
|
+
// is no column operation to scaffold, so emit genuinely empty `up`/`down`
|
|
203
|
+
// bodies for the developer to fill in rather than an
|
|
204
|
+
// `alterTable('<table-name>')` statement that cannot run as generated.
|
|
205
|
+
//
|
|
206
|
+
// The test is `columnsWithTypes`, deliberately, and not `columnDefs`: the two
|
|
207
|
+
// diverge. A declaration the reduce above filters out entirely (e.g.
|
|
208
|
+
// `comments:has_many`) leaves `columnDefs` empty while the user did type a
|
|
209
|
+
// column, and emptying the bodies there would silently discard it. Markerless
|
|
210
|
+
// *with* columns therefore keeps the placeholder scaffold — the table name is
|
|
211
|
+
// the only part the generator cannot work out, and `g:migration`'s help
|
|
212
|
+
// already documents that case as a stub to hand-edit.
|
|
213
|
+
if (altering && table === MIGRATION_TABLE_NAME_PLACEHOLDER && columnsWithTypes.length === 0)
|
|
214
|
+
return emptyMigrationContent();
|
|
210
215
|
const citextExtension = requireCitextExtension
|
|
211
216
|
? ` await DreamMigrationHelpers.createExtension(db, 'citext')\n\n`
|
|
212
217
|
: '';
|
|
@@ -273,6 +278,68 @@ ${downBody}
|
|
|
273
278
|
}\
|
|
274
279
|
`;
|
|
275
280
|
}
|
|
281
|
+
/**
|
|
282
|
+
* A migration file with nothing in it: correct imports, the standard `up`/`down`
|
|
283
|
+
* signatures, and empty bodies.
|
|
284
|
+
*
|
|
285
|
+
* Three deliberate departures from the scaffolded migrations above. All three
|
|
286
|
+
* exist so that the file this generator writes is clean the instant it lands in
|
|
287
|
+
* a consumer's project — under their formatter as well as their linter. See
|
|
288
|
+
* `spec/unit/cli/generateMigrationContent.spec.ts`
|
|
289
|
+
* ("the generated empty migration is clean under a consumer's tooling"), which
|
|
290
|
+
* runs this exact output through Prettier and through ESLint's
|
|
291
|
+
* `recommended` + `typescript-eslint recommended` and pins zero of both.
|
|
292
|
+
*
|
|
293
|
+
* - The bodies are `{}`, closed on the signature's own line, not `{\n}`, and
|
|
294
|
+
* the file ends in a newline. Prettier collapses an empty block onto one line
|
|
295
|
+
* and requires a final newline, so either spelling is a formatting violation
|
|
296
|
+
* the moment it is written and fails `prettier --check` in the consumer's CI
|
|
297
|
+
* (and this repo's own `pnpm lint`, which ends in `pnpm prettier . --check`);
|
|
298
|
+
* `writeGeneratedFile` writes this string byte-for-byte with no formatting
|
|
299
|
+
* pass. The final newline is why this template does not end with the
|
|
300
|
+
* backslash line-continuation the scaffolded return above uses. (Those
|
|
301
|
+
* scaffolds are missing their own final newline, which is pre-existing and
|
|
302
|
+
* pinned by ~90 `toEqual` expectations across three spec files; it is not
|
|
303
|
+
* this template's to fix. `generateFactoryContent` and
|
|
304
|
+
* `generateUnitSpecContent` already terminate their output the way this one
|
|
305
|
+
* now does.)
|
|
306
|
+
* - `sql` is left out of the import list. Every other generated migration
|
|
307
|
+
* imports it unconditionally because most bodies need it, but an empty body
|
|
308
|
+
* never does, and an unused import is an error under
|
|
309
|
+
* `@typescript-eslint/no-unused-vars` in a file that would otherwise have
|
|
310
|
+
* nothing to report.
|
|
311
|
+
* - `db` keeps its name — the developer's first keystroke in the body is going
|
|
312
|
+
* to be `db.`, and dropping the parameter (with the `Kysely` import that only
|
|
313
|
+
* it uses) would make them type both back — and the existing
|
|
314
|
+
* `no-explicit-any` directive is widened to disable `no-unused-vars` too.
|
|
315
|
+
* `db` is unused until the body is written, and `no-unused-vars` is
|
|
316
|
+
* *error*-level under `typescript-eslint`'s `recommended` with
|
|
317
|
+
* `args: 'after-used'`, so without the directive `psy g:migration <name> &&
|
|
318
|
+
* pnpm lint` fails on a brand-new, untouched file. (The `await`-less `async`
|
|
319
|
+
* needs nothing: `require-await` deliberately ignores empty function bodies.)
|
|
320
|
+
* An `_db` rename is not a substitute: `no-unused-vars` sets no
|
|
321
|
+
* `argsIgnorePattern` by default, so `_db` reports too, and the developer
|
|
322
|
+
* then has to rename it back.
|
|
323
|
+
*
|
|
324
|
+
* The cost of the widened directive is that the `no-unused-vars` half goes
|
|
325
|
+
* stale once the body uses `db`, which ESLint 9 reports via its default
|
|
326
|
+
* `reportUnusedDisableDirectives`. That is a *warning* rather than the two
|
|
327
|
+
* *errors* it replaces, it lands in the file the developer is already
|
|
328
|
+
* editing, and `eslint --fix` removes just that rule name from the directive
|
|
329
|
+
* and leaves `no-explicit-any` in place — so it is strictly the cheaper of
|
|
330
|
+
* the two, in severity and in effort.
|
|
331
|
+
*/
|
|
332
|
+
function emptyMigrationContent() {
|
|
333
|
+
return `\
|
|
334
|
+
import { Kysely } from 'kysely'
|
|
335
|
+
|
|
336
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
|
|
337
|
+
export async function up(db: Kysely<any>): Promise<void> {}
|
|
338
|
+
|
|
339
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
|
|
340
|
+
export async function down(db: Kysely<any>): Promise<void> {}
|
|
341
|
+
`;
|
|
342
|
+
}
|
|
276
343
|
function getAttributeType(attributeType, descriptors) {
|
|
277
344
|
switch (attributeType) {
|
|
278
345
|
case 'string':
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import DreamCLI from '../../cli/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Runs the type/schema sync a command performs after its own work has already
|
|
4
|
+
* succeeded, and — when that sync fails — logs a message naming what completed
|
|
5
|
+
* and naming the sync as the step that failed, before re-raising the original
|
|
6
|
+
* error untouched.
|
|
7
|
+
*
|
|
8
|
+
* Without this, a failing sync surfaces a bare sync error immediately after
|
|
9
|
+
* e.g. `db:rollback`, which reads as a failed rollback even though the
|
|
10
|
+
* rollback itself is already committed.
|
|
11
|
+
*
|
|
12
|
+
* The original error is re-thrown rather than wrapped, so the error a caller
|
|
13
|
+
* sees, and the command's non-zero exit status, are exactly what they were
|
|
14
|
+
* before. Callers keep their own `EnvInternal.isTest` / `--skip-sync` guards:
|
|
15
|
+
* this wrapper takes the sync call, not the decision to make it.
|
|
16
|
+
*/
|
|
17
|
+
export default async function syncWithFailureContext({ commandName, completedWork, didNotRun, recoveryAdvice }, sync) {
|
|
18
|
+
try {
|
|
19
|
+
await sync();
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
DreamCLI.logger.log(syncFailureMessage({ commandName, completedWork, didNotRun, recoveryAdvice }));
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* @internal
|
|
28
|
+
*
|
|
29
|
+
* Builds the explanatory message logged when a post-work sync fails.
|
|
30
|
+
*/
|
|
31
|
+
export function syncFailureMessage({ commandName, completedWork, didNotRun, recoveryAdvice, }) {
|
|
32
|
+
const skipped = didNotRun ? `; ${didNotRun}` : '';
|
|
33
|
+
const recovery = recoveryAdvice ?? 'run `sync` on its own';
|
|
34
|
+
return `
|
|
35
|
+
${commandName}: the sync step failed — not ${commandName}'s own work.
|
|
36
|
+
|
|
37
|
+
${completedWork}. What failed afterward is the sync that ${commandName} runs to regenerate the auto-generated type and schema files${skipped}.
|
|
38
|
+
|
|
39
|
+
The sync error itself is printed alongside this message. Once it is resolved, ${recovery}.
|
|
40
|
+
`;
|
|
41
|
+
}
|
|
@@ -97,7 +97,7 @@ export default class SerializerRenderer {
|
|
|
97
97
|
const associatedObject = data[attribute.name];
|
|
98
98
|
let serializer = null;
|
|
99
99
|
if (associatedObject) {
|
|
100
|
-
serializer = serializerForAssociatedObject(associatedObject, attribute.options);
|
|
100
|
+
serializer = serializerForAssociatedObject(associatedObject, attribute.options, renderTimeResolutionContext(attribute.options, 'rendersOne', attribute.name));
|
|
101
101
|
}
|
|
102
102
|
else if (attribute.options.flatten) {
|
|
103
103
|
/**
|
|
@@ -146,8 +146,12 @@ export default class SerializerRenderer {
|
|
|
146
146
|
const associatedObjects = data[attribute.name];
|
|
147
147
|
if (!associatedObjects)
|
|
148
148
|
throw new RendersManyMustReceiveArray(attribute, associatedObjects);
|
|
149
|
+
// Hoisted out of the map: the context is identical for every element, is immutable, and is
|
|
150
|
+
// read only while an error message is being built, so one object serves the whole
|
|
151
|
+
// collection rather than one per element.
|
|
152
|
+
const resolutionContext = renderTimeResolutionContext(attribute.options, 'rendersMany', attribute.name);
|
|
149
153
|
accumulator[outputAttributeName] = compact(associatedObjects).map(associatedObject => {
|
|
150
|
-
const serializer = serializerForAssociatedObject(associatedObject, attribute.options);
|
|
154
|
+
const serializer = serializerForAssociatedObject(associatedObject, attribute.options, resolutionContext);
|
|
151
155
|
return (
|
|
152
156
|
// passthrough data going into the serializer is the argument that gets
|
|
153
157
|
// used in the custom attribute callback function
|
|
@@ -206,8 +210,32 @@ function _applyRenderingOptionsToAttribute(data, value, attributeName, options,
|
|
|
206
210
|
return round(value, precision);
|
|
207
211
|
return value ?? null;
|
|
208
212
|
}
|
|
209
|
-
|
|
213
|
+
/**
|
|
214
|
+
* `resolutionContext` is error-only: it names the rendersOne/rendersMany this object is being
|
|
215
|
+
* rendered through, so that a serializer that cannot be resolved for it says which association led
|
|
216
|
+
* here rather than only which class failed. It is `undefined` whenever no resolution will happen —
|
|
217
|
+
* see `renderTimeResolutionContext`.
|
|
218
|
+
*/
|
|
219
|
+
function serializerForAssociatedObject(associatedObject, options, resolutionContext) {
|
|
210
220
|
if (options.serializer)
|
|
211
221
|
return options.serializer;
|
|
212
|
-
return inferSerializerFromDreamOrViewModel(associatedObject, options.serializerKey);
|
|
222
|
+
return inferSerializerFromDreamOrViewModel(associatedObject, options.serializerKey, resolutionContext);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Builds the error-only resolution context for one rendered association, or `undefined` when the
|
|
226
|
+
* render will not resolve a serializer at all.
|
|
227
|
+
*
|
|
228
|
+
* Rendering is a hot path — once per rendered association per request, and `rendersMany` renders one
|
|
229
|
+
* element at a time from a collection — so this deliberately allocates nothing it cannot use:
|
|
230
|
+
* `serializerForAssociatedObject` returns `options.serializer` before resolving anything when the
|
|
231
|
+
* association declares its own serializer, and in that case there is no resolution to describe.
|
|
232
|
+
* Callers build this once per rendered association and share it across every element.
|
|
233
|
+
*
|
|
234
|
+
* The renderer holds no serializer object, so the edge carries no `declaredBy` — see
|
|
235
|
+
* `SerializerResolutionContext`.
|
|
236
|
+
*/
|
|
237
|
+
function renderTimeResolutionContext(options, type, associationName) {
|
|
238
|
+
if (options.serializer)
|
|
239
|
+
return undefined;
|
|
240
|
+
return { edge: { type, associationName } };
|
|
213
241
|
}
|