@rvoh/dream 2.25.0 → 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/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 -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/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 +8 -2
- package/dist/types/src/cli/index.d.ts +1 -1
- 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/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 +64 -64
- 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
|
@@ -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
|
}
|
|
@@ -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
|
+
}
|
package/dist/esm/src/Dream.js
CHANGED
|
@@ -49,6 +49,7 @@ import cloneDeepSafe from './helpers/cloneDeepSafe.js';
|
|
|
49
49
|
import isJsonColumn from './helpers/db/types/isJsonColumn.js';
|
|
50
50
|
import notEqual from './helpers/notEqual.js';
|
|
51
51
|
import { inferSerializersFromDreamClassOrViewModelClassOrFail } from './serializer/helpers/inferSerializerFromDreamOrViewModel.js';
|
|
52
|
+
import serializerGlobalName from './serializer/helpers/serializerGlobalName.js';
|
|
52
53
|
export default class Dream {
|
|
53
54
|
DB;
|
|
54
55
|
/**
|
|
@@ -580,8 +581,10 @@ export default class Dream {
|
|
|
580
581
|
// One line per serializer: for an STI base the displayed tree is the union across its children,
|
|
581
582
|
// so naming a single child's serializer under the base's own name would misattribute the tree.
|
|
582
583
|
serializers.forEach(serializer => {
|
|
584
|
+
// Interpolated rather than defaulted so the printed line is unchanged for a serializer with no
|
|
585
|
+
// globalName; naming inline serializers in this tree is its own change.
|
|
583
586
|
// eslint-disable-next-line no-console
|
|
584
|
-
console.log(yoctocolors.gray(serializer
|
|
587
|
+
console.log(yoctocolors.gray(`${serializerGlobalName(serializer)}`));
|
|
585
588
|
});
|
|
586
589
|
return this.recursiveSerializationMap(serializers, {
|
|
587
590
|
forDisplay: true,
|
|
@@ -667,6 +670,12 @@ export default class Dream {
|
|
|
667
670
|
* * foreign key fields for belongs to associations (these should usually be verified before being set)
|
|
668
671
|
* * type fields corresponding to polymorphic associations
|
|
669
672
|
*
|
|
673
|
+
* @internal
|
|
674
|
+
*
|
|
675
|
+
* Not part of the public API. Psychic reaches it through the bracketed
|
|
676
|
+
* back-door (`dreamClass['paramSafeColumnsOrFallback']()`), the same escape
|
|
677
|
+
* hatch used for other Dream internals such as `virtualAttributes`.
|
|
678
|
+
*
|
|
670
679
|
* @returns A subset of columns for the given dream class
|
|
671
680
|
*/
|
|
672
681
|
static paramSafeColumnsOrFallback() {
|
|
@@ -5,6 +5,7 @@ import DreamApp from '../dream-app/index.js';
|
|
|
5
5
|
import Encrypt from '../encrypt/index.js';
|
|
6
6
|
import SspawnRequiresDevelopmentOrTest from '../errors/SspawnRequiresDevelopmentOrTest.js';
|
|
7
7
|
import generateDream from '../helpers/cli/generateDream.js';
|
|
8
|
+
import syncWithFailureContext from '../helpers/cli/syncWithFailureContext.js';
|
|
8
9
|
import EnvInternal from '../helpers/EnvInternal.js';
|
|
9
10
|
import loadRepl from '../helpers/loadRepl.js';
|
|
10
11
|
import DreamCliLogger from './logger/DreamCliLogger.js';
|
|
@@ -129,7 +130,9 @@ ${INDENT} create-many-to-many-posts-users # WRONG: selects 'many_posts_
|
|
|
129
130
|
${INDENT} # -to- inside "many-to-many" matches: with no columns
|
|
130
131
|
${INDENT} # this errors; with columns it silently scaffolds it
|
|
131
132
|
${INDENT} create-posts-users-join-table # correct: no marker match
|
|
132
|
-
${INDENT} #
|
|
133
|
+
${INDENT} # with no columns, generates empty up/down methods to
|
|
134
|
+
${INDENT} # write by hand; with columns, scaffolds
|
|
135
|
+
${INDENT} # alterTable('<table-name>') to hand-edit`;
|
|
133
136
|
export default class DreamCLI {
|
|
134
137
|
/**
|
|
135
138
|
* Starts the Dream console
|
|
@@ -184,7 +187,9 @@ ${INDENT} # Remove columns (include -from-<table_name>; columns are described w
|
|
|
184
187
|
${INDENT} # their types, same shorthand as -to-, so the rollback (down) can recreate them)
|
|
185
188
|
${INDENT} pnpm psy g:migration remove-legacy-fields-from-posts legacy_status:string
|
|
186
189
|
${INDENT}
|
|
187
|
-
${INDENT} # General schema change (no -to-/-from- marker —
|
|
190
|
+
${INDENT} # General schema change (no -to-/-from- marker — with no columns, generates
|
|
191
|
+
${INDENT} # empty up/down methods to write by hand; with columns, scaffolds
|
|
192
|
+
${INDENT} # alterTable('<table-name>') to hand-edit)
|
|
188
193
|
${INDENT} pnpm psy g:migration create-unique-index-on-invitations`)
|
|
189
194
|
.argument('<migrationName>', migrationNameArgumentDescription)
|
|
190
195
|
.option('--connection-name <connectionName>', 'the database connection to use for this migration. Only needed for multi-database setups; defaults to "default"')
|
|
@@ -345,7 +350,11 @@ ${INDENT} pnpm psy db:migrate`)
|
|
|
345
350
|
await initializeDreamApp({ bypassDreamIntegrityChecks: true });
|
|
346
351
|
await DreamBin.dbMigrate();
|
|
347
352
|
if (EnvInternal.isTest && !skipSync) {
|
|
348
|
-
await
|
|
353
|
+
await syncWithFailureContext({
|
|
354
|
+
commandName: 'db:migrate',
|
|
355
|
+
completedWork: 'Every pending migration ran successfully',
|
|
356
|
+
recoveryAdvice: 'run `sync` on its own, or re-run `db:migrate` — the migrations it already applied are not re-applied, so it goes straight back to the sync',
|
|
357
|
+
}, async () => await DreamBin.sync(onSync));
|
|
349
358
|
}
|
|
350
359
|
process.exit();
|
|
351
360
|
});
|
|
@@ -362,7 +371,13 @@ ${INDENT} pnpm psy db:rollback --steps=3 # rolls back the last 3 migrations`
|
|
|
362
371
|
await initializeDreamApp({ bypassDreamIntegrityChecks: true });
|
|
363
372
|
await DreamBin.dbRollback({ steps });
|
|
364
373
|
if (EnvInternal.isTest && !skipSync) {
|
|
365
|
-
await
|
|
374
|
+
await syncWithFailureContext({
|
|
375
|
+
commandName: 'db:rollback',
|
|
376
|
+
completedWork: 'The rollback completed successfully and is already committed',
|
|
377
|
+
// never `re-run db:rollback` here: the rollback is already
|
|
378
|
+
// committed, so a second run rolls back a further migration
|
|
379
|
+
recoveryAdvice: 'run `sync` on its own. Do not re-run `db:rollback` — the rollback it would perform is a further one, undoing a migration you did not ask to undo',
|
|
380
|
+
}, async () => await DreamBin.sync(onSync));
|
|
366
381
|
}
|
|
367
382
|
process.exit();
|
|
368
383
|
});
|
|
@@ -409,7 +424,18 @@ ${INDENT}Warning: all existing data will be lost. The seed file (db/seed.ts) wil
|
|
|
409
424
|
logPrefix: ' ',
|
|
410
425
|
logPrefixColor: 'green',
|
|
411
426
|
});
|
|
412
|
-
await
|
|
427
|
+
await syncWithFailureContext({
|
|
428
|
+
commandName: 'db:reset',
|
|
429
|
+
completedWork: 'db:drop, db:create and db:migrate all completed successfully',
|
|
430
|
+
didNotRun: 'db:seed, which db:reset runs after the sync, did not run',
|
|
431
|
+
// the two routes are not interchangeable, so the second one names
|
|
432
|
+
// the env var it needs: db:reset seeds by calling the app's seed
|
|
433
|
+
// hook directly (below), while the `db:seed` *command* refuses to
|
|
434
|
+
// seed under NODE_ENV=test unless DREAM_SEED_DB_IN_TEST=1 — and
|
|
435
|
+
// NODE_ENV=test is the only environment DreamBin.sync does any
|
|
436
|
+
// work in, so it is the only environment this advice is printed in
|
|
437
|
+
recoveryAdvice: 're-run `db:reset`, which seeds directly and so picks up the skipped seeding step, or run `sync` on its own followed by `DREAM_SEED_DB_IN_TEST=1 db:seed` — this sync only runs under `NODE_ENV=test`, and a bare `db:seed` does not seed there',
|
|
438
|
+
}, async () => await DreamBin.sync(onSync));
|
|
413
439
|
DreamCLI.logger.log(arrows, { logPrefix: ' ' });
|
|
414
440
|
DreamCLI.logger.log(colorize('db:seed', { color: 'green' }), {
|
|
415
441
|
logPrefix: ' ',
|