@rvoh/dream 2.6.1-alpha.1 → 2.7.1
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/bin/index.js +2 -0
- package/dist/cjs/src/cli/index.js +1 -0
- package/dist/cjs/src/helpers/cli/generateDream.js +7 -1
- package/dist/cjs/src/helpers/cli/generateDreamContent.js +44 -9
- package/dist/cjs/src/helpers/cli/generateMigration.js +2 -1
- package/dist/cjs/src/helpers/cli/generateMigrationContent.js +28 -11
- package/dist/cjs/src/helpers/cli/generateSerializerContent.js +53 -50
- package/dist/cjs/src/ops/index.js +245 -1
- package/dist/esm/src/bin/index.js +2 -0
- package/dist/esm/src/cli/index.js +1 -0
- package/dist/esm/src/helpers/cli/generateDream.js +7 -1
- package/dist/esm/src/helpers/cli/generateDreamContent.js +44 -9
- package/dist/esm/src/helpers/cli/generateMigration.js +2 -1
- package/dist/esm/src/helpers/cli/generateMigrationContent.js +28 -11
- package/dist/esm/src/helpers/cli/generateSerializerContent.js +53 -50
- package/dist/esm/src/ops/index.js +245 -1
- package/dist/types/src/bin/index.d.ts +1 -0
- package/dist/types/src/cli/index.d.ts +7 -0
- package/dist/types/src/helpers/cli/generateDream.d.ts +8 -0
- package/dist/types/src/helpers/cli/generateDreamContent.d.ts +11 -1
- package/dist/types/src/helpers/cli/generateMigration.d.ts +7 -1
- package/dist/types/src/helpers/cli/generateMigrationContent.d.ts +7 -1
- package/dist/types/src/ops/index.d.ts +245 -0
- package/dist/types/src/types/associations/shared.d.ts +2 -2
- package/dist/types/src/types/associations/shared.ts +17 -11
- package/docs/assets/search.js +1 -1
- package/docs/classes/db.DreamMigrationHelpers.html +9 -9
- package/docs/classes/db.KyselyQueryDriver.html +32 -32
- package/docs/classes/db.PostgresQueryDriver.html +33 -33
- package/docs/classes/db.QueryDriverBase.html +31 -31
- 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.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 +119 -119
- package/docs/classes/index.DreamApp.html +5 -5
- package/docs/classes/index.DreamTransaction.html +2 -2
- package/docs/classes/index.Env.html +2 -2
- package/docs/classes/index.Query.html +56 -56
- package/docs/classes/system.CliFileWriter.html +4 -4
- package/docs/classes/system.DreamBin.html +2 -2
- package/docs/classes/system.DreamCLI.html +6 -6
- 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 +2 -2
- 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.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 +115 -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
|
@@ -62,6 +62,8 @@ export default class DreamBin {
|
|
|
62
62
|
includeInternalSerializers: options.internalSerializers ?? false,
|
|
63
63
|
...options,
|
|
64
64
|
stiBaseSerializer: false,
|
|
65
|
+
// `@SoftDelete()` is incompatible with STI children — never auto-apply.
|
|
66
|
+
softDelete: false,
|
|
65
67
|
},
|
|
66
68
|
fullyQualifiedParentName,
|
|
67
69
|
});
|
|
@@ -190,6 +190,7 @@ ${INDENT} pnpm psy g:model --model-name=GroupDanceLesson Lesson/Dance/Group
|
|
|
190
190
|
${INDENT} # model is named GroupDanceLesson instead of LessonDanceGroup`)
|
|
191
191
|
.option('--admin-serializers', 'also generate AdminSerializer and AdminSummarySerializer variants for admin-facing API endpoints that may expose additional fields', false)
|
|
192
192
|
.option('--internal-serializers', 'also generate InternalSerializer and InternalSummarySerializer variants for internal API endpoints that may expose additional fields', false)
|
|
193
|
+
.option('--no-soft-delete', `skip generating the @SoftDelete() decorator and the corresponding nullable \`deleted_at\` column. By default, generated models use soft-delete semantics (rows are marked deleted via \`deleted_at\` instead of being removed from the database). Pass this flag when you want records to be hard-deleted.`)
|
|
193
194
|
.argument('<modelName>', `The fully qualified model name, using / for namespacing. This determines the model class name (may be overridden with \`--model-name\`), table name, and file path under src/app/models/.
|
|
194
195
|
${INDENT}
|
|
195
196
|
${INDENT}Examples:
|
|
@@ -9,6 +9,11 @@ import writeGeneratedFile from './writeGeneratedFile.js';
|
|
|
9
9
|
export default async function generateDream({ fullyQualifiedModelName, columnsWithTypes, options, fullyQualifiedParentName, }) {
|
|
10
10
|
fullyQualifiedModelName = standardizeFullyQualifiedModelName(fullyQualifiedModelName);
|
|
11
11
|
const modelClassName = modelClassNameFrom(fullyQualifiedModelName, options.modelName);
|
|
12
|
+
const isSTI = !!fullyQualifiedParentName;
|
|
13
|
+
// `@SoftDelete()` is incompatible with STI children, so we force-disable
|
|
14
|
+
// the soft-delete scaffold when generating an STI child regardless of the
|
|
15
|
+
// caller-provided value.
|
|
16
|
+
const softDelete = !isSTI && !!options.softDelete;
|
|
12
17
|
await writeGeneratedFile({
|
|
13
18
|
dreamPathKey: 'models',
|
|
14
19
|
fileName: `${fullyQualifiedModelName}.ts`,
|
|
@@ -22,6 +27,7 @@ export default async function generateDream({ fullyQualifiedModelName, columnsWi
|
|
|
22
27
|
connectionName: options.connectionName,
|
|
23
28
|
tableName: options.tableName,
|
|
24
29
|
modelClassName,
|
|
30
|
+
softDelete,
|
|
25
31
|
}),
|
|
26
32
|
logLabel: 'dream',
|
|
27
33
|
});
|
|
@@ -37,7 +43,6 @@ export default async function generateDream({ fullyQualifiedModelName, columnsWi
|
|
|
37
43
|
includeInternalSerializers: options.includeInternalSerializers ?? false,
|
|
38
44
|
modelClassName,
|
|
39
45
|
});
|
|
40
|
-
const isSTI = !!fullyQualifiedParentName;
|
|
41
46
|
if (columnsWithTypes.length || !isSTI) {
|
|
42
47
|
await generateMigration({
|
|
43
48
|
connectionName: options.connectionName,
|
|
@@ -47,6 +52,7 @@ export default async function generateDream({ fullyQualifiedModelName, columnsWi
|
|
|
47
52
|
fullyQualifiedParentName,
|
|
48
53
|
tableName: options.tableName,
|
|
49
54
|
modelClassName,
|
|
55
|
+
softDelete,
|
|
50
56
|
});
|
|
51
57
|
}
|
|
52
58
|
}
|
|
@@ -7,19 +7,47 @@ import absoluteDreamPath from '../path/absoluteDreamPath.js';
|
|
|
7
7
|
import snakeify from '../snakeify.js';
|
|
8
8
|
import standardizeFullyQualifiedModelName from '../standardizeFullyQualifiedModelName.js';
|
|
9
9
|
import uniq from '../uniq.js';
|
|
10
|
+
/**
|
|
11
|
+
* Column names that are automatically emitted by the model generator (and
|
|
12
|
+
* the migration generator). When the user passes any of these explicitly,
|
|
13
|
+
* we filter them out of the attribute loop so the model class and migration
|
|
14
|
+
* don't end up with duplicate declarations.
|
|
15
|
+
*/
|
|
16
|
+
const AUTO_GENERATED_TIMESTAMP_COLUMN_NAMES = ['created_at', 'updated_at', 'deleted_at'];
|
|
17
|
+
function columnName(columnWithType) {
|
|
18
|
+
return columnWithType.split(':')[0] ?? '';
|
|
19
|
+
}
|
|
20
|
+
export function hasExplicitColumn(columnsWithTypes, name) {
|
|
21
|
+
return columnsWithTypes.some(col => columnName(col) === name);
|
|
22
|
+
}
|
|
23
|
+
export function filterAutoGeneratedTimestampColumns(columnsWithTypes) {
|
|
24
|
+
return columnsWithTypes.filter(col => !AUTO_GENERATED_TIMESTAMP_COLUMN_NAMES.includes(columnName(col)));
|
|
25
|
+
}
|
|
10
26
|
export default function generateDreamContent(options) {
|
|
11
27
|
const config = createModelConfig(options);
|
|
12
|
-
|
|
13
|
-
|
|
28
|
+
// SoftDelete is incompatible with STI children, so it's only applied to
|
|
29
|
+
// non-STI-child models.
|
|
30
|
+
const includeSoftDelete = !config.isSTI && !!options.softDelete;
|
|
31
|
+
const hasExplicitDeletedAt = hasExplicitColumn(options.columnsWithTypes, 'deleted_at');
|
|
32
|
+
// Filter out timestamp columns that we auto-generate from the attribute
|
|
33
|
+
// processing loop so we don't double-generate them in the model class.
|
|
34
|
+
// We only dedupe on non-STI models — STI children don't get auto timestamps.
|
|
35
|
+
const columnsForAttributes = config.isSTI
|
|
36
|
+
? options.columnsWithTypes
|
|
37
|
+
: filterAutoGeneratedTimestampColumns(options.columnsWithTypes);
|
|
38
|
+
const baseImports = createImportConfig(config, options, { includeSoftDelete });
|
|
39
|
+
const attributesResult = processAttributes(columnsForAttributes, config.modelClassName);
|
|
14
40
|
const allImports = {
|
|
15
41
|
...baseImports,
|
|
16
42
|
modelImportStatements: [...baseImports.modelImportStatements, ...attributesResult.imports],
|
|
17
43
|
};
|
|
18
44
|
const importSection = buildImportSection(allImports);
|
|
19
|
-
const classDeclaration = buildClassDeclaration(config);
|
|
45
|
+
const classDeclaration = buildClassDeclaration(config, { includeSoftDelete });
|
|
20
46
|
const tableMethod = buildTableMethod(config);
|
|
21
47
|
const serializersMethod = buildSerializersMethod(config, options);
|
|
22
|
-
const fieldsSection = buildFieldsSection(config, attributesResult
|
|
48
|
+
const fieldsSection = buildFieldsSection(config, attributesResult, {
|
|
49
|
+
includeDeletedAt: includeSoftDelete || hasExplicitDeletedAt,
|
|
50
|
+
});
|
|
23
51
|
return `${importSection}
|
|
24
52
|
|
|
25
53
|
const deco = new Decorators<typeof ${config.modelClassName}>()
|
|
@@ -50,7 +78,7 @@ export function createModelConfig(options) {
|
|
|
50
78
|
tableName,
|
|
51
79
|
};
|
|
52
80
|
}
|
|
53
|
-
export function createImportConfig(config, options) {
|
|
81
|
+
export function createImportConfig(config, options, { includeSoftDelete }) {
|
|
54
82
|
const dreamTypeImports = ['DreamColumn'];
|
|
55
83
|
const dreamImports = ['Decorators'];
|
|
56
84
|
if (options.serializer) {
|
|
@@ -59,6 +87,9 @@ export function createImportConfig(config, options) {
|
|
|
59
87
|
if (config.isSTI) {
|
|
60
88
|
dreamImports.push('STI');
|
|
61
89
|
}
|
|
90
|
+
if (includeSoftDelete) {
|
|
91
|
+
dreamImports.push('SoftDelete');
|
|
92
|
+
}
|
|
62
93
|
const baseModelName = config.isSTI ? options.fullyQualifiedParentName : config.applicationModelName;
|
|
63
94
|
const modelImportStatements = [importStatementForModel(baseModelName)];
|
|
64
95
|
return {
|
|
@@ -150,10 +181,11 @@ function buildImportSection(imports) {
|
|
|
150
181
|
const modelImports = uniq(imports.modelImportStatements).join('');
|
|
151
182
|
return `${dreamImportLine}${typeImportLine}${modelImports}`;
|
|
152
183
|
}
|
|
153
|
-
function buildClassDeclaration(config) {
|
|
184
|
+
function buildClassDeclaration(config, { includeSoftDelete }) {
|
|
154
185
|
const stiDecorator = config.isSTI ? `@STI(${config.parentModelClassName})\n` : '';
|
|
186
|
+
const softDeleteDecorator = includeSoftDelete ? '@SoftDelete()\n' : '';
|
|
155
187
|
const extendsClause = config.isSTI ? config.parentModelClassName : config.applicationModelName;
|
|
156
|
-
return `${stiDecorator}export default class ${config.modelClassName} extends ${extendsClause} {`;
|
|
188
|
+
return `${stiDecorator}${softDeleteDecorator}export default class ${config.modelClassName} extends ${extendsClause} {`;
|
|
157
189
|
}
|
|
158
190
|
function buildTableMethod(config) {
|
|
159
191
|
if (config.isSTI)
|
|
@@ -195,14 +227,17 @@ function buildSerializersMethod(config, options) {
|
|
|
195
227
|
|
|
196
228
|
`;
|
|
197
229
|
}
|
|
198
|
-
function buildFieldsSection(config, attributes) {
|
|
230
|
+
function buildFieldsSection(config, attributes, { includeDeletedAt }) {
|
|
199
231
|
if (config.isSTI) {
|
|
200
232
|
return `${attributes.formattedFields}${attributes.formattedDecorators}`;
|
|
201
233
|
}
|
|
202
234
|
const idField = ` public id: DreamColumn<${config.modelClassName}, 'id'>`;
|
|
235
|
+
const deletedAtLine = includeDeletedAt
|
|
236
|
+
? `\n public deletedAt: DreamColumn<${config.modelClassName}, 'deletedAt'>`
|
|
237
|
+
: '';
|
|
203
238
|
let timestamps = `
|
|
204
239
|
public createdAt: DreamColumn<${config.modelClassName}, 'createdAt'>
|
|
205
|
-
public updatedAt: DreamColumn<${config.modelClassName}, 'updatedAt'
|
|
240
|
+
public updatedAt: DreamColumn<${config.modelClassName}, 'updatedAt'>${deletedAtLine}
|
|
206
241
|
`;
|
|
207
242
|
if (!attributes.formattedDecorators.length) {
|
|
208
243
|
timestamps = timestamps.replace(/\n$/, '');
|
|
@@ -9,7 +9,7 @@ import dreamPath from '../path/dreamPath.js';
|
|
|
9
9
|
import snakeify from '../snakeify.js';
|
|
10
10
|
import generateStiMigrationContent from './generateStiMigrationContent.js';
|
|
11
11
|
import writeGeneratedFile from './writeGeneratedFile.js';
|
|
12
|
-
export default async function generateMigration({ migrationName, columnsWithTypes, connectionName, fullyQualifiedModelName, fullyQualifiedParentName, tableName: explicitTableName, modelClassName, }) {
|
|
12
|
+
export default async function generateMigration({ migrationName, columnsWithTypes, connectionName, fullyQualifiedModelName, fullyQualifiedParentName, tableName: explicitTableName, modelClassName, softDelete = false, }) {
|
|
13
13
|
const migrationsBasePath = connectionName === 'default'
|
|
14
14
|
? path.join(dreamPath('db'), 'migrations')
|
|
15
15
|
: path.join(dreamPath('db'), 'migrations', connectionName);
|
|
@@ -29,6 +29,7 @@ export default async function generateMigration({ migrationName, columnsWithType
|
|
|
29
29
|
table: explicitTableName || snakeify(pluralize(pascalizePath(fullyQualifiedModelName))),
|
|
30
30
|
columnsWithTypes,
|
|
31
31
|
primaryKeyType: primaryKeyType(connectionName),
|
|
32
|
+
softDelete,
|
|
32
33
|
});
|
|
33
34
|
}
|
|
34
35
|
else {
|
|
@@ -9,11 +9,23 @@ import snakeify from '../snakeify.js';
|
|
|
9
9
|
import standardizeFullyQualifiedModelName from '../standardizeFullyQualifiedModelName.js';
|
|
10
10
|
const STI_TYPE_COLUMN_NAME = 'type';
|
|
11
11
|
const COLUMNS_TO_INDEX = [STI_TYPE_COLUMN_NAME];
|
|
12
|
-
export default function generateMigrationContent({ connectionName = 'default', table, columnsWithTypes = [], primaryKeyType = 'bigserial', createOrAlter = 'create', stiChildClassName, } = {}) {
|
|
12
|
+
export default function generateMigrationContent({ connectionName = 'default', table, columnsWithTypes = [], primaryKeyType = 'bigserial', createOrAlter = 'create', stiChildClassName, softDelete = false, } = {}) {
|
|
13
13
|
const altering = createOrAlter === 'alter';
|
|
14
14
|
let requireCitextExtension = false;
|
|
15
15
|
const checkConstraints = [];
|
|
16
|
-
|
|
16
|
+
// When creating a new table, we automatically emit `created_at`,
|
|
17
|
+
// `updated_at`, and (when soft delete is on) `deleted_at` columns. Filter
|
|
18
|
+
// these out of the user-supplied column list so we don't emit duplicate
|
|
19
|
+
// `.addColumn(...)` calls if the user also specified them explicitly.
|
|
20
|
+
const userExplicitlyPassedDeletedAt = !altering && columnsWithTypes.some(col => (col.split(':')[0] ?? '') === 'deleted_at');
|
|
21
|
+
const processedColumnsWithTypes = altering
|
|
22
|
+
? columnsWithTypes
|
|
23
|
+
: columnsWithTypes.filter(col => {
|
|
24
|
+
const name = col.split(':')[0] ?? '';
|
|
25
|
+
return name !== 'created_at' && name !== 'updated_at' && name !== 'deleted_at';
|
|
26
|
+
});
|
|
27
|
+
const emitDeletedAtColumn = !altering && (softDelete || userExplicitlyPassedDeletedAt);
|
|
28
|
+
const { columnDefs, columnDrops, indexDefs, indexDrops } = processedColumnsWithTypes.reduce((acc, attributeDeclaration) => {
|
|
17
29
|
const { columnDefs, columnDrops, indexDefs, indexDrops } = acc;
|
|
18
30
|
const [nonStandardAttributeName, _attributeType, ...descriptors] = attributeDeclaration.split(':');
|
|
19
31
|
if (!nonStandardAttributeName)
|
|
@@ -143,18 +155,20 @@ export async function down(db: Kysely<any>): Promise<void> {
|
|
|
143
155
|
const columnDropLines = columnDrops.length
|
|
144
156
|
? newlineDoubleIndent + columnDrops.join(newlineDoubleIndent) + newlineDoubleIndent
|
|
145
157
|
: '';
|
|
158
|
+
const timestampColumnLines = altering
|
|
159
|
+
? ''
|
|
160
|
+
: newlineDoubleIndent +
|
|
161
|
+
".addColumn('created_at', 'timestamp', col => col.notNull())" +
|
|
162
|
+
newlineDoubleIndent +
|
|
163
|
+
".addColumn('updated_at', 'timestamp', col => col.notNull())" +
|
|
164
|
+
(emitDeletedAtColumn ? newlineDoubleIndent + ".addColumn('deleted_at', 'timestamp')" : '');
|
|
146
165
|
return `\
|
|
147
166
|
${dreamDbImports.length ? `import { ${dreamDbImports.join(', ')} } from '@rvoh/dream/db'\n` : ''}import { ${kyselyImports.join(', ')} } from 'kysely'
|
|
148
167
|
|
|
149
168
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
150
169
|
export async function up(db: Kysely<any>): Promise<void> {
|
|
151
170
|
${citextExtension}${generateEnumStatements(columnsWithTypes)} await db.schema
|
|
152
|
-
.${altering ? 'alterTable' : 'createTable'}('${table}')${altering ? '' : newlineDoubleIndent + generateIdStr({ primaryKeyType })}${columnDefLines}${
|
|
153
|
-
? ''
|
|
154
|
-
: newlineDoubleIndent +
|
|
155
|
-
".addColumn('created_at', 'timestamp', col => col.notNull())" +
|
|
156
|
-
newlineDoubleIndent +
|
|
157
|
-
".addColumn('updated_at', 'timestamp', col => col.notNull())"}
|
|
171
|
+
.${altering ? 'alterTable' : 'createTable'}('${table}')${altering ? '' : newlineDoubleIndent + generateIdStr({ primaryKeyType })}${columnDefLines}${timestampColumnLines}
|
|
158
172
|
.execute()${indexDefs.length ? `\n${newlineIndent}` : ''}${indexDefs.join(doubleNewlineIndent)}${checkConstraints.join('')}
|
|
159
173
|
}
|
|
160
174
|
|
|
@@ -184,7 +198,10 @@ function getAttributeType(attributeType, descriptors) {
|
|
|
184
198
|
}
|
|
185
199
|
function enumAttributeType(descriptors, asArray = false) {
|
|
186
200
|
const suffix = asArray ? '[]' : '';
|
|
187
|
-
return `sql\`${descriptors[0]}
|
|
201
|
+
return `sql\`${appendEnumSuffix(descriptors[0])}${suffix}\``;
|
|
202
|
+
}
|
|
203
|
+
function appendEnumSuffix(name) {
|
|
204
|
+
return name?.endsWith('_enum') ? name : `${name}_enum`;
|
|
188
205
|
}
|
|
189
206
|
const ENUM_OR_ENUM_ARRAY_REGEX = /:enum:.*:|:enum\[\]:.*:/;
|
|
190
207
|
function generateEnumStatements(columnsWithTypes) {
|
|
@@ -197,7 +214,7 @@ function generateEnumStatements(columnsWithTypes) {
|
|
|
197
214
|
return;
|
|
198
215
|
const columnsWithTypes = columnsWithTypesString.split(/,\s{0,}/);
|
|
199
216
|
return ` await db.schema
|
|
200
|
-
.createType('${enumName}
|
|
217
|
+
.createType('${appendEnumSuffix(enumName)}')
|
|
201
218
|
.asEnum([
|
|
202
219
|
${columnsWithTypes.map(attr => `'${attr}'`).join(',\n ')}
|
|
203
220
|
])
|
|
@@ -213,7 +230,7 @@ function generateEnumDropStatements(columnsWithTypes) {
|
|
|
213
230
|
const columnsWithTypesString = descriptors[0];
|
|
214
231
|
if (columnsWithTypesString === undefined)
|
|
215
232
|
return;
|
|
216
|
-
return `await db.schema.dropType('${enumName}
|
|
233
|
+
return `await db.schema.dropType('${appendEnumSuffix(enumName)}').execute()`;
|
|
217
234
|
}));
|
|
218
235
|
return finalStatements.length ? '\n\n ' + finalStatements.join('\n ') : '';
|
|
219
236
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import serializerNameFromFullyQualifiedModelName from '../../serializer/helpers/serializerNameFromFullyQualifiedModelName.js';
|
|
2
1
|
import camelize from '../camelize.js';
|
|
3
2
|
import globalClassNameFromFullyQualifiedModelName from '../globalClassNameFromFullyQualifiedModelName.js';
|
|
4
3
|
import absoluteDreamPath from '../path/absoluteDreamPath.js';
|
|
@@ -9,9 +8,18 @@ export default function generateSerializerContent({ fullyQualifiedModelName, col
|
|
|
9
8
|
const additionalImports = [];
|
|
10
9
|
const dreamImports = [];
|
|
11
10
|
const isSTI = !!fullyQualifiedParentName;
|
|
11
|
+
// Variants that should be emitted in this file. Each variant generates a
|
|
12
|
+
// matching summary + default serializer pair; STI children import the
|
|
13
|
+
// corresponding variant from the parent module so the inheritance chain
|
|
14
|
+
// stays within the same variant (admin → admin, internal → internal).
|
|
15
|
+
const variantSuffixes = [''];
|
|
16
|
+
if (includeAdminSerializers)
|
|
17
|
+
variantSuffixes.push('Admin');
|
|
18
|
+
if (includeInternalSerializers)
|
|
19
|
+
variantSuffixes.push('Internal');
|
|
12
20
|
if (isSTI) {
|
|
13
21
|
fullyQualifiedParentName = standardizeFullyQualifiedModelName(fullyQualifiedParentName);
|
|
14
|
-
additionalImports.push(importStatementForSerializer(fullyQualifiedModelName, fullyQualifiedParentName));
|
|
22
|
+
additionalImports.push(importStatementForSerializer(fullyQualifiedModelName, fullyQualifiedParentName, variantSuffixes));
|
|
15
23
|
}
|
|
16
24
|
else {
|
|
17
25
|
dreamImports.push('DreamSerializer');
|
|
@@ -23,58 +31,50 @@ export default function generateSerializerContent({ fullyQualifiedModelName, col
|
|
|
23
31
|
: `(${modelInstanceName}: ${modelClassName})`;
|
|
24
32
|
const modelSerializerArgs = `${modelInstanceName}`;
|
|
25
33
|
const dreamSerializerArgs = `${stiBaseSerializer ? `StiChildClass ?? ${modelClassName}` : modelClassName}, ${modelInstanceName}`;
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
: `${summarySerializerClassName}(${stiBaseSerializer ? 'StiChildClass, ' : ''}${modelSerializerArgs})`;
|
|
31
|
-
const summarySerializerExtends = isSTI
|
|
32
|
-
? `${serializerNameFromFullyQualifiedModelName(fullyQualifiedModelNameToSerializerBaseName(fullyQualifiedParentName), 'summary')}(${modelClassName}, ${modelSerializerArgs})`
|
|
33
|
-
: `DreamSerializer(${dreamSerializerArgs})`;
|
|
34
|
-
// Admin variants
|
|
35
|
-
const adminSerializerClassName = serializerClassName.replace(/Serializer$/, 'AdminSerializer');
|
|
36
|
-
const adminSummarySerializerClassName = summarySerializerClassName.replace(/SummarySerializer$/, 'AdminSummarySerializer');
|
|
37
|
-
// end:Admin variants
|
|
38
|
-
// Internal variants
|
|
39
|
-
const internalSerializerClassName = serializerClassName.replace(/Serializer$/, 'InternalSerializer');
|
|
40
|
-
const internalSummarySerializerClassName = summarySerializerClassName.replace(/SummarySerializer$/, 'InternalSummarySerializer');
|
|
41
|
-
// end:Internal variants
|
|
34
|
+
const localSerializerBase = fullyQualifiedModelNameToSerializerBaseName(fullyQualifiedModelName);
|
|
35
|
+
const parentSerializerBase = isSTI
|
|
36
|
+
? fullyQualifiedModelNameToSerializerBaseName(fullyQualifiedParentName)
|
|
37
|
+
: '';
|
|
42
38
|
const additionalModelImports = [];
|
|
43
39
|
const dreamImport = dreamImports.length
|
|
44
40
|
? `import { ${uniq(dreamImports).join(', ')} } from '@rvoh/dream'\n`
|
|
45
41
|
: '';
|
|
46
42
|
const additionalImportsStr = uniq(additionalImports).join('');
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
43
|
+
const buildSerializerPair = (variantSuffix) => {
|
|
44
|
+
const summaryName = variantSerializerClassName(localSerializerBase, variantSuffix, 'summary');
|
|
45
|
+
const defaultName = variantSerializerClassName(localSerializerBase, variantSuffix, 'default');
|
|
46
|
+
const summaryExtends = isSTI
|
|
47
|
+
? `${variantSerializerClassName(parentSerializerBase, variantSuffix, 'summary')}(${modelClassName}, ${modelSerializerArgs})`
|
|
48
|
+
: `DreamSerializer(${dreamSerializerArgs})`;
|
|
49
|
+
const defaultExtends = isSTI
|
|
50
|
+
? `${variantSerializerClassName(parentSerializerBase, variantSuffix, 'default')}(${modelClassName}, ${modelSerializerArgs})`
|
|
51
|
+
: `${summaryName}(${stiBaseSerializer ? 'StiChildClass, ' : ''}${modelSerializerArgs})`;
|
|
52
|
+
const summary = `export const ${summaryName} = ${modelSerializerSignature} =>
|
|
53
|
+
${summaryExtends}${isSTI ? '' : `\n .attribute('id')`}`;
|
|
54
|
+
const defaultBody = columnsWithTypes
|
|
55
|
+
.map(attr => {
|
|
56
|
+
const [name, type] = attr.split(':');
|
|
57
|
+
if (name === undefined)
|
|
58
|
+
return '';
|
|
59
|
+
if (['belongsto', 'hasone', 'hasmany'].includes(camelize(type)?.toLowerCase()))
|
|
60
|
+
return '';
|
|
61
|
+
return `\n ${attribute(modelClassName, name, type, attr, stiBaseSerializer)}`;
|
|
62
|
+
})
|
|
63
|
+
.join('');
|
|
64
|
+
const def = `export const ${defaultName} = ${modelSerializerSignature} =>
|
|
65
|
+
${defaultExtends}${defaultBody}`;
|
|
66
|
+
return `${summary}\n\n${def}`;
|
|
67
|
+
};
|
|
68
|
+
const serializerBlocks = variantSuffixes.map(buildSerializerPair).join('\n\n');
|
|
60
69
|
return `${dreamImport}${additionalImportsStr}${relatedModelImport}${additionalModelImports.join('')}
|
|
61
|
-
${
|
|
62
|
-
|
|
63
|
-
${defaultSerializer}${!includeAdminSerializers
|
|
64
|
-
? ''
|
|
65
|
-
: `
|
|
66
|
-
|
|
67
|
-
${summarySerializer.replace(summarySerializerClassName, adminSummarySerializerClassName)}
|
|
68
|
-
|
|
69
|
-
${defaultSerializer.replace(serializerClassName, adminSerializerClassName).replace(summarySerializerClassName, adminSummarySerializerClassName)}`}${!includeInternalSerializers
|
|
70
|
-
? ''
|
|
71
|
-
: `
|
|
72
|
-
|
|
73
|
-
${summarySerializer.replace(summarySerializerClassName, internalSummarySerializerClassName)}
|
|
74
|
-
|
|
75
|
-
${defaultSerializer.replace(serializerClassName, internalSerializerClassName).replace(summarySerializerClassName, internalSummarySerializerClassName)}`}
|
|
70
|
+
${serializerBlocks}
|
|
76
71
|
`;
|
|
77
72
|
}
|
|
73
|
+
function variantSerializerClassName(serializerBase, variantSuffix, kind) {
|
|
74
|
+
return kind === 'summary'
|
|
75
|
+
? `${serializerBase}${variantSuffix}SummarySerializer`
|
|
76
|
+
: `${serializerBase}${variantSuffix}Serializer`;
|
|
77
|
+
}
|
|
78
78
|
function attribute(modelClassName, name, type, attr, stiBaseSerializer) {
|
|
79
79
|
if (name === 'type' && stiBaseSerializer) {
|
|
80
80
|
return `.attribute('type', { openapi: { type: 'string', enum: [(StiChildClass ?? ${modelClassName}).sanitizedName] } })`;
|
|
@@ -97,11 +97,14 @@ function attributeOptionsSpecifier(type, attr) {
|
|
|
97
97
|
return '';
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
|
-
function importStatementForSerializer(originModelName, destinationModelName) {
|
|
101
|
-
const
|
|
102
|
-
const
|
|
100
|
+
function importStatementForSerializer(originModelName, destinationModelName, variantSuffixes) {
|
|
101
|
+
const base = fullyQualifiedModelNameToSerializerBaseName(destinationModelName);
|
|
102
|
+
const names = variantSuffixes.flatMap(suffix => [
|
|
103
|
+
globalClassNameFromFullyQualifiedModelName(variantSerializerClassName(base, suffix, 'default')),
|
|
104
|
+
globalClassNameFromFullyQualifiedModelName(variantSerializerClassName(base, suffix, 'summary')),
|
|
105
|
+
]);
|
|
103
106
|
const importFrom = absoluteDreamPath('serializers', destinationModelName);
|
|
104
|
-
return `import { ${
|
|
107
|
+
return `import { ${names.join(', ')} } from '${importFrom}'\n`;
|
|
105
108
|
}
|
|
106
109
|
function importStatementForModel(modelClassName, fullyQualifiedModelName) {
|
|
107
110
|
const importFrom = absoluteDreamPath('models', fullyQualifiedModelName);
|