@rvoh/dream 2.17.0 → 2.17.2
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/cli/index.js +17 -10
- package/dist/cjs/src/db/testDatabasePool.js +12 -3
- package/dist/cjs/src/dream-app/index.js +4 -5
- package/dist/cjs/src/helpers/cli/generateDreamContent.js +13 -6
- package/dist/cjs/src/helpers/cli/generateMigrationContent.js +18 -12
- package/dist/cjs/src/helpers/cli/validateStiChildColumns.js +7 -0
- package/dist/esm/src/cli/index.js +17 -10
- package/dist/esm/src/db/testDatabasePool.js +12 -3
- package/dist/esm/src/dream-app/index.js +4 -5
- package/dist/esm/src/helpers/cli/generateDreamContent.js +13 -6
- package/dist/esm/src/helpers/cli/generateMigrationContent.js +18 -12
- package/dist/esm/src/helpers/cli/validateStiChildColumns.js +7 -0
- package/dist/types/src/cli/index.d.ts +2 -1
- package/dist/types/src/db/testDatabasePool.d.ts +7 -0
- package/dist/types/src/helpers/cli/validateStiChildColumns.d.ts +1 -0
- package/docs/classes/db.DreamMigrationHelpers.html +11 -11
- package/docs/classes/db.KyselyQueryDriver.html +34 -34
- package/docs/classes/db.PostgresQueryDriver.html +35 -35
- package/docs/classes/db.QueryDriverBase.html +33 -33
- 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 +118 -118
- 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 +57 -57
- 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
|
@@ -11,8 +11,8 @@ import DreamCliLogger from './logger/DreamCliLogger.js';
|
|
|
11
11
|
import colorize from './logger/loggable/colorize.js';
|
|
12
12
|
export const CLI_INDENT = ' ';
|
|
13
13
|
const INDENT = CLI_INDENT;
|
|
14
|
-
export const baseColumnsWithTypesDescription = `space separated snake-case
|
|
15
|
-
${INDENT} title:citext subtitle:string body_markdown:text style:enum:post_styles:formal,informal
|
|
14
|
+
export const baseColumnsWithTypesDescription = `space separated snake-case properties like this:
|
|
15
|
+
${INDENT} title:citext subtitle:string body_markdown:text style:enum:post_styles:formal,informal
|
|
16
16
|
${INDENT}
|
|
17
17
|
${INDENT}all properties default to not nullable; null can be allowed by appending ':optional':
|
|
18
18
|
${INDENT} subtitle:string:optional
|
|
@@ -61,8 +61,11 @@ ${INDENT} omit the enum values to leverage an existing enum (omits the en
|
|
|
61
61
|
${INDENT} type:enum:room_types or type:enum:room_types:optional
|
|
62
62
|
${INDENT}
|
|
63
63
|
${INDENT} leveraging arrays, add the "[]" suffix, e.g.: type:enum[]:room_types:bathroom,kitchen,bedroom`;
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
function buildColumnsWithTypesDescription(belongsToDescription) {
|
|
65
|
+
return `${baseColumnsWithTypesDescription}
|
|
66
|
+
${belongsToDescription}`;
|
|
67
|
+
}
|
|
68
|
+
const columnsWithTypesDescription = buildColumnsWithTypesDescription(`
|
|
66
69
|
${INDENT}
|
|
67
70
|
${INDENT} - belongs_to:
|
|
68
71
|
${INDENT} ALWAYS use this instead of adding a raw uuid column for foreign keys. It creates the FK column, adds a database index,
|
|
@@ -80,9 +83,13 @@ ${INDENT} # @deco.
|
|
|
80
83
|
${INDENT} Messaging/Template@template:belongs_to # template_id column, templateId property, template association
|
|
81
84
|
${INDENT} # (strips the namespace from the property/association names while keeping
|
|
82
85
|
${INDENT} # the namespaced model reference intact)
|
|
83
|
-
${INDENT} Aliasing also lets you declare multiple FKs to the same model in one generator call without column collisions
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
+
${INDENT} Aliasing also lets you declare multiple FKs to the same model in one generator call without column collisions.`);
|
|
87
|
+
export const columnsWithTypesDescriptionForStiChild = buildColumnsWithTypesDescription(`
|
|
88
|
+
${INDENT}
|
|
89
|
+
${INDENT} - belongs_to:
|
|
90
|
+
${INDENT} NOT supported for STI children. STI children cannot declare associations; declare all BelongsTo,
|
|
91
|
+
${INDENT} HasOne, and HasMany associations on the STI parent model instead.`);
|
|
92
|
+
const columnsWithTypesDescriptionForMigration = buildColumnsWithTypesDescription(`
|
|
86
93
|
${INDENT}
|
|
87
94
|
${INDENT} - belongs_to:
|
|
88
95
|
${INDENT} ALWAYS use this instead of adding a raw uuid column for foreign keys. It creates the FK column with an index.
|
|
@@ -95,7 +102,7 @@ ${INDENT} User:belongs_to:optional # nullable foreign key
|
|
|
95
102
|
${INDENT}
|
|
96
103
|
${INDENT} rename the FK column with Model@alias (snake_case alias becomes the column name):
|
|
97
104
|
${INDENT} InternalUser@canceled_by:belongs_to:optional # canceled_by_id column with index
|
|
98
|
-
${INDENT} Messaging/Template@template:belongs_to # template_id column (strips the namespace from the column name)
|
|
105
|
+
${INDENT} Messaging/Template@template:belongs_to # template_id column (strips the namespace from the column name)`);
|
|
99
106
|
export default class DreamCLI {
|
|
100
107
|
/**
|
|
101
108
|
* Starts the Dream console
|
|
@@ -222,7 +229,7 @@ ${INDENT} Settings/CommunicationPreferences # src/app/models/Settings/Communi
|
|
|
222
229
|
.alias('g:sti-child')
|
|
223
230
|
.description(`Generates an STI (Single Table Inheritance) child model that extends an existing parent model. The child shares the parent's database table (discriminated by the \`type\` column) and can add child-specific columns. Generates a child model decorated with @STI(Parent), child serializers extending the parent's base serializers, a migration that ALTERs the parent table (not a new table), check constraints, a factory, and spec skeleton.
|
|
224
231
|
${INDENT}
|
|
225
|
-
${INDENT}If the child declares no additional columns, only the model file is generated — no migration is created. STI children share the parent's table, so a no-columns child requires no schema change. Add a migration only by passing positional field:type args, in which case the generator emits one with the appropriate check constraint. STI children never receive @SoftDelete() — soft delete is enforced at the parent level only — and the generator does not accept --no-soft-delete.
|
|
232
|
+
${INDENT}If the child declares no additional columns, only the model file is generated — no migration is created. STI children share the parent's table, so a no-columns child requires no schema change. Add a migration only by passing positional field:type args, in which case the generator emits one with the appropriate check constraint. STI children cannot declare associations — declare them on the parent model instead. The generator rejects belongs_to columns. STI children never receive @SoftDelete() — soft delete is enforced at the parent level only — and the generator does not accept --no-soft-delete.
|
|
226
233
|
${INDENT}
|
|
227
234
|
${INDENT}The parent must already exist (typically generated with g:model --sti-base-serializer or g:resource --sti-base-serializer).
|
|
228
235
|
${INDENT}
|
|
@@ -256,7 +263,7 @@ ${INDENT}
|
|
|
256
263
|
${INDENT}Examples:
|
|
257
264
|
${INDENT} Room # extends src/app/models/Room.ts
|
|
258
265
|
${INDENT} Health/Coach # extends src/app/models/Health/Coach.ts`)
|
|
259
|
-
.argument('[columnsWithTypes...]',
|
|
266
|
+
.argument('[columnsWithTypes...]', columnsWithTypesDescriptionForStiChild)
|
|
260
267
|
.action(async (childModelName, extendsWord, parentModelName, columnsWithTypes, options) => {
|
|
261
268
|
await initializeDreamApp({ bypassDreamIntegrityChecks: true, bypassDbConnectionsDuringInit: true });
|
|
262
269
|
if (extendsWord !== 'extends')
|
|
@@ -58,11 +58,20 @@ const state = {
|
|
|
58
58
|
* probe so they always agree on the index range.
|
|
59
59
|
*/
|
|
60
60
|
export function testDatabasePoolSize(parallelTests) {
|
|
61
|
-
const n =
|
|
62
|
-
? Math.floor(parallelTests)
|
|
63
|
-
: 1;
|
|
61
|
+
const n = normalizeTestDatabaseParallelism(parallelTests);
|
|
64
62
|
return 2 * Math.max(1, n) + POOL_MARGIN;
|
|
65
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Normalize configured test parallelism to the minimum safe pool width input.
|
|
66
|
+
* Even a serial vitest run can overlap old/new worker processes, so missing,
|
|
67
|
+
* invalid, zero, or fractional-below-one values all mean "one active worker",
|
|
68
|
+
* not "disable the per-live-worker database pool".
|
|
69
|
+
*/
|
|
70
|
+
export function normalizeTestDatabaseParallelism(parallelTests) {
|
|
71
|
+
return typeof parallelTests === 'number' && Number.isFinite(parallelTests) && parallelTests > 0
|
|
72
|
+
? Math.max(1, Math.floor(parallelTests))
|
|
73
|
+
: 1;
|
|
74
|
+
}
|
|
66
75
|
/**
|
|
67
76
|
* The database name for a pool index given the un-suffixed base name. Index 1
|
|
68
77
|
* is the base itself (no suffix); higher indexes are `<base>_<index>`. This is
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as util from 'node:util';
|
|
2
|
-
import { claimedTestDatabaseIndexOrNull, claimTestDatabase, testDatabaseNameForIndex, } from '../db/testDatabasePool.js';
|
|
2
|
+
import { claimedTestDatabaseIndexOrNull, claimTestDatabase, normalizeTestDatabaseParallelism, testDatabaseNameForIndex, } from '../db/testDatabasePool.js';
|
|
3
3
|
import validateTable from '../db/validators/validateTable.js';
|
|
4
4
|
import Query from '../dream/Query.js';
|
|
5
5
|
import PostgresQueryDriver from '../dream/QueryDriver/Postgres.js';
|
|
@@ -208,7 +208,7 @@ A new key can also be generated from the CLI:
|
|
|
208
208
|
}
|
|
209
209
|
_parallelTests;
|
|
210
210
|
get parallelTests() {
|
|
211
|
-
return process.env.NODE_ENV === 'test' ? this._parallelTests : undefined;
|
|
211
|
+
return process.env.NODE_ENV === 'test' ? normalizeTestDatabaseParallelism(this._parallelTests) : undefined;
|
|
212
212
|
}
|
|
213
213
|
_unicodeNormalization = 'NFC';
|
|
214
214
|
get unicodeNormalization() {
|
|
@@ -276,7 +276,7 @@ A new key can also be generated from the CLI:
|
|
|
276
276
|
this._inflections = opts.inflections;
|
|
277
277
|
if (opts?.serializerCasing)
|
|
278
278
|
this._serializerCasing = opts.serializerCasing;
|
|
279
|
-
if (opts?.parallelTests)
|
|
279
|
+
if (opts?.parallelTests !== undefined)
|
|
280
280
|
this._parallelTests = opts.parallelTests;
|
|
281
281
|
this._paths = {
|
|
282
282
|
conf: opts?.paths?.conf || 'src/conf',
|
|
@@ -483,9 +483,8 @@ A new key can also be generated from the CLI:
|
|
|
483
483
|
};
|
|
484
484
|
break;
|
|
485
485
|
case 'parallelTests':
|
|
486
|
-
if (process.env.NODE_ENV === 'test'
|
|
486
|
+
if (process.env.NODE_ENV === 'test')
|
|
487
487
|
this._parallelTests = options;
|
|
488
|
-
}
|
|
489
488
|
break;
|
|
490
489
|
case 'unicodeNormalization':
|
|
491
490
|
this._unicodeNormalization = options;
|
|
@@ -8,6 +8,7 @@ import snakeify from '../snakeify.js';
|
|
|
8
8
|
import standardizeFullyQualifiedModelName from '../standardizeFullyQualifiedModelName.js';
|
|
9
9
|
import uniq from '../uniq.js';
|
|
10
10
|
import parseAttribute from './parseAttribute.js';
|
|
11
|
+
import validateStiChildColumns from './validateStiChildColumns.js';
|
|
11
12
|
/**
|
|
12
13
|
* Column names that are automatically emitted by the model generator (and
|
|
13
14
|
* the migration generator). When the user passes any of these explicitly,
|
|
@@ -26,6 +27,8 @@ export function filterAutoGeneratedTimestampColumns(columnsWithTypes) {
|
|
|
26
27
|
}
|
|
27
28
|
export default function generateDreamContent(options) {
|
|
28
29
|
const config = createModelConfig(options);
|
|
30
|
+
if (config.isSTI)
|
|
31
|
+
validateStiChildColumns(options.columnsWithTypes);
|
|
29
32
|
// SoftDelete is incompatible with STI children, so it's only applied to
|
|
30
33
|
// non-STI-child models.
|
|
31
34
|
const includeSoftDelete = !config.isSTI && !!options.softDelete;
|
|
@@ -59,15 +62,19 @@ export default function generateDreamContent(options) {
|
|
|
59
62
|
includeDeletedAt: includeSoftDelete || hasExplicitDeletedAt,
|
|
60
63
|
});
|
|
61
64
|
const decoBlock = decoratorsInUse
|
|
62
|
-
? `const deco = new Decorators<typeof ${config.modelClassName}>()
|
|
63
|
-
|
|
65
|
+
? `const deco = new Decorators<typeof ${config.modelClassName}>()
|
|
66
|
+
|
|
67
|
+
`
|
|
68
|
+
: config.isSTI
|
|
69
|
+
? ''
|
|
70
|
+
: `// Uncomment when adding decorators (@deco.BelongsTo, @deco.Validates, etc.):
|
|
64
71
|
// import { Decorators } from '@rvoh/dream'
|
|
65
|
-
// const deco = new Decorators<typeof ${config.modelClassName}>()
|
|
66
|
-
return `${importSection}
|
|
72
|
+
// const deco = new Decorators<typeof ${config.modelClassName}>()
|
|
67
73
|
|
|
68
|
-
|
|
74
|
+
`;
|
|
75
|
+
return `${importSection}
|
|
69
76
|
|
|
70
|
-
${classDeclaration}
|
|
77
|
+
${decoBlock}${classDeclaration}
|
|
71
78
|
${tableMethod}${serializersMethod}${fieldsSection}
|
|
72
79
|
}
|
|
73
80
|
`.replace(/^\s*$/gm, '');
|
|
@@ -8,7 +8,8 @@ import globalClassNameFromFullyQualifiedModelName from '../globalClassNameFromFu
|
|
|
8
8
|
import snakeify from '../snakeify.js';
|
|
9
9
|
import standardizeFullyQualifiedModelName from '../standardizeFullyQualifiedModelName.js';
|
|
10
10
|
const STI_TYPE_COLUMN_NAME = 'type';
|
|
11
|
-
const
|
|
11
|
+
const DELETED_AT_COLUMN_NAME = 'deleted_at';
|
|
12
|
+
const COLUMNS_TO_INDEX = [STI_TYPE_COLUMN_NAME, DELETED_AT_COLUMN_NAME];
|
|
12
13
|
export default function generateMigrationContent({ connectionName = 'default', table, columnsWithTypes = [], primaryKeyType = 'bigserial', createOrAlter = 'create', stiChildClassName, softDelete = false, } = {}) {
|
|
13
14
|
const altering = createOrAlter === 'alter';
|
|
14
15
|
let requireCitextExtension = false;
|
|
@@ -25,8 +26,8 @@ export default function generateMigrationContent({ connectionName = 'default', t
|
|
|
25
26
|
return name !== 'created_at' && name !== 'updated_at' && name !== 'deleted_at';
|
|
26
27
|
});
|
|
27
28
|
const emitDeletedAtColumn = !altering && (softDelete || userExplicitlyPassedDeletedAt);
|
|
28
|
-
const { columnDefs, columnDrops, indexDefs
|
|
29
|
-
const { columnDefs, columnDrops, indexDefs
|
|
29
|
+
const { columnDefs, columnDrops, indexDefs } = processedColumnsWithTypes.reduce((acc, attributeDeclaration) => {
|
|
30
|
+
const { columnDefs, columnDrops, indexDefs } = acc;
|
|
30
31
|
const [rawSegmentOne, _attributeType, ...descriptors] = attributeDeclaration.split(':');
|
|
31
32
|
if (!rawSegmentOne)
|
|
32
33
|
return acc;
|
|
@@ -129,13 +130,7 @@ export default function generateMigrationContent({ connectionName = 'default', t
|
|
|
129
130
|
}
|
|
130
131
|
columnDrops.push(`.dropColumn('${attributeName}')`);
|
|
131
132
|
if (processedAttrType === 'belongsto' || COLUMNS_TO_INDEX.includes(attributeName)) {
|
|
132
|
-
|
|
133
|
-
indexDefs.push(`await db.schema
|
|
134
|
-
.createIndex('${indexName}')
|
|
135
|
-
.on('${table}')
|
|
136
|
-
.column('${attributeName}')
|
|
137
|
-
.execute()`);
|
|
138
|
-
indexDrops.push(`await db.schema.dropIndex('${indexName}').execute()`);
|
|
133
|
+
indexDefs.push(columnIndexStatement(table, attributeName));
|
|
139
134
|
}
|
|
140
135
|
if (stiChildClassName && !userWantsThisOptional && !arrayAttribute && !isBooleanColumn) {
|
|
141
136
|
checkConstraints.push(`
|
|
@@ -149,7 +144,10 @@ export default function generateMigrationContent({ connectionName = 'default', t
|
|
|
149
144
|
.execute()`);
|
|
150
145
|
}
|
|
151
146
|
return acc;
|
|
152
|
-
}, { columnDefs: [], columnDrops: [], indexDefs: []
|
|
147
|
+
}, { columnDefs: [], columnDrops: [], indexDefs: [] });
|
|
148
|
+
if (emitDeletedAtColumn) {
|
|
149
|
+
indexDefs.push(columnIndexStatement(table, DELETED_AT_COLUMN_NAME));
|
|
150
|
+
}
|
|
153
151
|
if (!table) {
|
|
154
152
|
return `\
|
|
155
153
|
import { Kysely, sql } from 'kysely'
|
|
@@ -196,7 +194,7 @@ ${citextExtension}${generateEnumStatements(columnsWithTypes)} await db.schema
|
|
|
196
194
|
|
|
197
195
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
198
196
|
export async function down(db: Kysely<any>): Promise<void> {
|
|
199
|
-
${
|
|
197
|
+
${altering
|
|
200
198
|
? `await db.schema${newlineDoubleIndent}.alterTable('${table}')${columnDropLines}.execute()`
|
|
201
199
|
: `await db.schema.dropTable('${table}').execute()`}${generateEnumDropStatements(columnsWithTypes)}
|
|
202
200
|
}\
|
|
@@ -393,6 +391,14 @@ function lookupReferencesTable(snakeAssociationName, originalAssociationName) {
|
|
|
393
391
|
function associationNameToForeignKey(associationName) {
|
|
394
392
|
return snakeify(associationName.replace(/\//g, '_').replace(/_id$/, '') + '_id');
|
|
395
393
|
}
|
|
394
|
+
function columnIndexStatement(table, attributeName) {
|
|
395
|
+
const indexName = `${table}_${attributeName}`;
|
|
396
|
+
return `await db.schema
|
|
397
|
+
.createIndex('${indexName}')
|
|
398
|
+
.on('${table}')
|
|
399
|
+
.column('${attributeName}')
|
|
400
|
+
.execute()`;
|
|
401
|
+
}
|
|
396
402
|
export function optionalFromDescriptors(descriptors) {
|
|
397
403
|
const optional = descriptors.at(-1) === 'optional';
|
|
398
404
|
if (optional)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import parseAttribute from './parseAttribute.js';
|
|
2
|
+
export default function validateStiChildColumns(columnsWithTypes) {
|
|
3
|
+
const belongsToColumns = columnsWithTypes.filter(columnWithType => parseAttribute(columnWithType)?.normalizedAttributeType === 'belongsto');
|
|
4
|
+
if (belongsToColumns.length) {
|
|
5
|
+
throw new Error(`STI children cannot declare belongs_to associations. Declare associations on the STI parent instead. Unsupported columns: ${belongsToColumns.join(', ')}`);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
@@ -11,8 +11,8 @@ import DreamCliLogger from './logger/DreamCliLogger.js';
|
|
|
11
11
|
import colorize from './logger/loggable/colorize.js';
|
|
12
12
|
export const CLI_INDENT = ' ';
|
|
13
13
|
const INDENT = CLI_INDENT;
|
|
14
|
-
export const baseColumnsWithTypesDescription = `space separated snake-case
|
|
15
|
-
${INDENT} title:citext subtitle:string body_markdown:text style:enum:post_styles:formal,informal
|
|
14
|
+
export const baseColumnsWithTypesDescription = `space separated snake-case properties like this:
|
|
15
|
+
${INDENT} title:citext subtitle:string body_markdown:text style:enum:post_styles:formal,informal
|
|
16
16
|
${INDENT}
|
|
17
17
|
${INDENT}all properties default to not nullable; null can be allowed by appending ':optional':
|
|
18
18
|
${INDENT} subtitle:string:optional
|
|
@@ -61,8 +61,11 @@ ${INDENT} omit the enum values to leverage an existing enum (omits the en
|
|
|
61
61
|
${INDENT} type:enum:room_types or type:enum:room_types:optional
|
|
62
62
|
${INDENT}
|
|
63
63
|
${INDENT} leveraging arrays, add the "[]" suffix, e.g.: type:enum[]:room_types:bathroom,kitchen,bedroom`;
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
function buildColumnsWithTypesDescription(belongsToDescription) {
|
|
65
|
+
return `${baseColumnsWithTypesDescription}
|
|
66
|
+
${belongsToDescription}`;
|
|
67
|
+
}
|
|
68
|
+
const columnsWithTypesDescription = buildColumnsWithTypesDescription(`
|
|
66
69
|
${INDENT}
|
|
67
70
|
${INDENT} - belongs_to:
|
|
68
71
|
${INDENT} ALWAYS use this instead of adding a raw uuid column for foreign keys. It creates the FK column, adds a database index,
|
|
@@ -80,9 +83,13 @@ ${INDENT} # @deco.
|
|
|
80
83
|
${INDENT} Messaging/Template@template:belongs_to # template_id column, templateId property, template association
|
|
81
84
|
${INDENT} # (strips the namespace from the property/association names while keeping
|
|
82
85
|
${INDENT} # the namespaced model reference intact)
|
|
83
|
-
${INDENT} Aliasing also lets you declare multiple FKs to the same model in one generator call without column collisions
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
+
${INDENT} Aliasing also lets you declare multiple FKs to the same model in one generator call without column collisions.`);
|
|
87
|
+
export const columnsWithTypesDescriptionForStiChild = buildColumnsWithTypesDescription(`
|
|
88
|
+
${INDENT}
|
|
89
|
+
${INDENT} - belongs_to:
|
|
90
|
+
${INDENT} NOT supported for STI children. STI children cannot declare associations; declare all BelongsTo,
|
|
91
|
+
${INDENT} HasOne, and HasMany associations on the STI parent model instead.`);
|
|
92
|
+
const columnsWithTypesDescriptionForMigration = buildColumnsWithTypesDescription(`
|
|
86
93
|
${INDENT}
|
|
87
94
|
${INDENT} - belongs_to:
|
|
88
95
|
${INDENT} ALWAYS use this instead of adding a raw uuid column for foreign keys. It creates the FK column with an index.
|
|
@@ -95,7 +102,7 @@ ${INDENT} User:belongs_to:optional # nullable foreign key
|
|
|
95
102
|
${INDENT}
|
|
96
103
|
${INDENT} rename the FK column with Model@alias (snake_case alias becomes the column name):
|
|
97
104
|
${INDENT} InternalUser@canceled_by:belongs_to:optional # canceled_by_id column with index
|
|
98
|
-
${INDENT} Messaging/Template@template:belongs_to # template_id column (strips the namespace from the column name)
|
|
105
|
+
${INDENT} Messaging/Template@template:belongs_to # template_id column (strips the namespace from the column name)`);
|
|
99
106
|
export default class DreamCLI {
|
|
100
107
|
/**
|
|
101
108
|
* Starts the Dream console
|
|
@@ -222,7 +229,7 @@ ${INDENT} Settings/CommunicationPreferences # src/app/models/Settings/Communi
|
|
|
222
229
|
.alias('g:sti-child')
|
|
223
230
|
.description(`Generates an STI (Single Table Inheritance) child model that extends an existing parent model. The child shares the parent's database table (discriminated by the \`type\` column) and can add child-specific columns. Generates a child model decorated with @STI(Parent), child serializers extending the parent's base serializers, a migration that ALTERs the parent table (not a new table), check constraints, a factory, and spec skeleton.
|
|
224
231
|
${INDENT}
|
|
225
|
-
${INDENT}If the child declares no additional columns, only the model file is generated — no migration is created. STI children share the parent's table, so a no-columns child requires no schema change. Add a migration only by passing positional field:type args, in which case the generator emits one with the appropriate check constraint. STI children never receive @SoftDelete() — soft delete is enforced at the parent level only — and the generator does not accept --no-soft-delete.
|
|
232
|
+
${INDENT}If the child declares no additional columns, only the model file is generated — no migration is created. STI children share the parent's table, so a no-columns child requires no schema change. Add a migration only by passing positional field:type args, in which case the generator emits one with the appropriate check constraint. STI children cannot declare associations — declare them on the parent model instead. The generator rejects belongs_to columns. STI children never receive @SoftDelete() — soft delete is enforced at the parent level only — and the generator does not accept --no-soft-delete.
|
|
226
233
|
${INDENT}
|
|
227
234
|
${INDENT}The parent must already exist (typically generated with g:model --sti-base-serializer or g:resource --sti-base-serializer).
|
|
228
235
|
${INDENT}
|
|
@@ -256,7 +263,7 @@ ${INDENT}
|
|
|
256
263
|
${INDENT}Examples:
|
|
257
264
|
${INDENT} Room # extends src/app/models/Room.ts
|
|
258
265
|
${INDENT} Health/Coach # extends src/app/models/Health/Coach.ts`)
|
|
259
|
-
.argument('[columnsWithTypes...]',
|
|
266
|
+
.argument('[columnsWithTypes...]', columnsWithTypesDescriptionForStiChild)
|
|
260
267
|
.action(async (childModelName, extendsWord, parentModelName, columnsWithTypes, options) => {
|
|
261
268
|
await initializeDreamApp({ bypassDreamIntegrityChecks: true, bypassDbConnectionsDuringInit: true });
|
|
262
269
|
if (extendsWord !== 'extends')
|
|
@@ -58,11 +58,20 @@ const state = {
|
|
|
58
58
|
* probe so they always agree on the index range.
|
|
59
59
|
*/
|
|
60
60
|
export function testDatabasePoolSize(parallelTests) {
|
|
61
|
-
const n =
|
|
62
|
-
? Math.floor(parallelTests)
|
|
63
|
-
: 1;
|
|
61
|
+
const n = normalizeTestDatabaseParallelism(parallelTests);
|
|
64
62
|
return 2 * Math.max(1, n) + POOL_MARGIN;
|
|
65
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Normalize configured test parallelism to the minimum safe pool width input.
|
|
66
|
+
* Even a serial vitest run can overlap old/new worker processes, so missing,
|
|
67
|
+
* invalid, zero, or fractional-below-one values all mean "one active worker",
|
|
68
|
+
* not "disable the per-live-worker database pool".
|
|
69
|
+
*/
|
|
70
|
+
export function normalizeTestDatabaseParallelism(parallelTests) {
|
|
71
|
+
return typeof parallelTests === 'number' && Number.isFinite(parallelTests) && parallelTests > 0
|
|
72
|
+
? Math.max(1, Math.floor(parallelTests))
|
|
73
|
+
: 1;
|
|
74
|
+
}
|
|
66
75
|
/**
|
|
67
76
|
* The database name for a pool index given the un-suffixed base name. Index 1
|
|
68
77
|
* is the base itself (no suffix); higher indexes are `<base>_<index>`. This is
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as util from 'node:util';
|
|
2
|
-
import { claimedTestDatabaseIndexOrNull, claimTestDatabase, testDatabaseNameForIndex, } from '../db/testDatabasePool.js';
|
|
2
|
+
import { claimedTestDatabaseIndexOrNull, claimTestDatabase, normalizeTestDatabaseParallelism, testDatabaseNameForIndex, } from '../db/testDatabasePool.js';
|
|
3
3
|
import validateTable from '../db/validators/validateTable.js';
|
|
4
4
|
import Query from '../dream/Query.js';
|
|
5
5
|
import PostgresQueryDriver from '../dream/QueryDriver/Postgres.js';
|
|
@@ -208,7 +208,7 @@ A new key can also be generated from the CLI:
|
|
|
208
208
|
}
|
|
209
209
|
_parallelTests;
|
|
210
210
|
get parallelTests() {
|
|
211
|
-
return process.env.NODE_ENV === 'test' ? this._parallelTests : undefined;
|
|
211
|
+
return process.env.NODE_ENV === 'test' ? normalizeTestDatabaseParallelism(this._parallelTests) : undefined;
|
|
212
212
|
}
|
|
213
213
|
_unicodeNormalization = 'NFC';
|
|
214
214
|
get unicodeNormalization() {
|
|
@@ -276,7 +276,7 @@ A new key can also be generated from the CLI:
|
|
|
276
276
|
this._inflections = opts.inflections;
|
|
277
277
|
if (opts?.serializerCasing)
|
|
278
278
|
this._serializerCasing = opts.serializerCasing;
|
|
279
|
-
if (opts?.parallelTests)
|
|
279
|
+
if (opts?.parallelTests !== undefined)
|
|
280
280
|
this._parallelTests = opts.parallelTests;
|
|
281
281
|
this._paths = {
|
|
282
282
|
conf: opts?.paths?.conf || 'src/conf',
|
|
@@ -483,9 +483,8 @@ A new key can also be generated from the CLI:
|
|
|
483
483
|
};
|
|
484
484
|
break;
|
|
485
485
|
case 'parallelTests':
|
|
486
|
-
if (process.env.NODE_ENV === 'test'
|
|
486
|
+
if (process.env.NODE_ENV === 'test')
|
|
487
487
|
this._parallelTests = options;
|
|
488
|
-
}
|
|
489
488
|
break;
|
|
490
489
|
case 'unicodeNormalization':
|
|
491
490
|
this._unicodeNormalization = options;
|
|
@@ -8,6 +8,7 @@ import snakeify from '../snakeify.js';
|
|
|
8
8
|
import standardizeFullyQualifiedModelName from '../standardizeFullyQualifiedModelName.js';
|
|
9
9
|
import uniq from '../uniq.js';
|
|
10
10
|
import parseAttribute from './parseAttribute.js';
|
|
11
|
+
import validateStiChildColumns from './validateStiChildColumns.js';
|
|
11
12
|
/**
|
|
12
13
|
* Column names that are automatically emitted by the model generator (and
|
|
13
14
|
* the migration generator). When the user passes any of these explicitly,
|
|
@@ -26,6 +27,8 @@ export function filterAutoGeneratedTimestampColumns(columnsWithTypes) {
|
|
|
26
27
|
}
|
|
27
28
|
export default function generateDreamContent(options) {
|
|
28
29
|
const config = createModelConfig(options);
|
|
30
|
+
if (config.isSTI)
|
|
31
|
+
validateStiChildColumns(options.columnsWithTypes);
|
|
29
32
|
// SoftDelete is incompatible with STI children, so it's only applied to
|
|
30
33
|
// non-STI-child models.
|
|
31
34
|
const includeSoftDelete = !config.isSTI && !!options.softDelete;
|
|
@@ -59,15 +62,19 @@ export default function generateDreamContent(options) {
|
|
|
59
62
|
includeDeletedAt: includeSoftDelete || hasExplicitDeletedAt,
|
|
60
63
|
});
|
|
61
64
|
const decoBlock = decoratorsInUse
|
|
62
|
-
? `const deco = new Decorators<typeof ${config.modelClassName}>()
|
|
63
|
-
|
|
65
|
+
? `const deco = new Decorators<typeof ${config.modelClassName}>()
|
|
66
|
+
|
|
67
|
+
`
|
|
68
|
+
: config.isSTI
|
|
69
|
+
? ''
|
|
70
|
+
: `// Uncomment when adding decorators (@deco.BelongsTo, @deco.Validates, etc.):
|
|
64
71
|
// import { Decorators } from '@rvoh/dream'
|
|
65
|
-
// const deco = new Decorators<typeof ${config.modelClassName}>()
|
|
66
|
-
return `${importSection}
|
|
72
|
+
// const deco = new Decorators<typeof ${config.modelClassName}>()
|
|
67
73
|
|
|
68
|
-
|
|
74
|
+
`;
|
|
75
|
+
return `${importSection}
|
|
69
76
|
|
|
70
|
-
${classDeclaration}
|
|
77
|
+
${decoBlock}${classDeclaration}
|
|
71
78
|
${tableMethod}${serializersMethod}${fieldsSection}
|
|
72
79
|
}
|
|
73
80
|
`.replace(/^\s*$/gm, '');
|
|
@@ -8,7 +8,8 @@ import globalClassNameFromFullyQualifiedModelName from '../globalClassNameFromFu
|
|
|
8
8
|
import snakeify from '../snakeify.js';
|
|
9
9
|
import standardizeFullyQualifiedModelName from '../standardizeFullyQualifiedModelName.js';
|
|
10
10
|
const STI_TYPE_COLUMN_NAME = 'type';
|
|
11
|
-
const
|
|
11
|
+
const DELETED_AT_COLUMN_NAME = 'deleted_at';
|
|
12
|
+
const COLUMNS_TO_INDEX = [STI_TYPE_COLUMN_NAME, DELETED_AT_COLUMN_NAME];
|
|
12
13
|
export default function generateMigrationContent({ connectionName = 'default', table, columnsWithTypes = [], primaryKeyType = 'bigserial', createOrAlter = 'create', stiChildClassName, softDelete = false, } = {}) {
|
|
13
14
|
const altering = createOrAlter === 'alter';
|
|
14
15
|
let requireCitextExtension = false;
|
|
@@ -25,8 +26,8 @@ export default function generateMigrationContent({ connectionName = 'default', t
|
|
|
25
26
|
return name !== 'created_at' && name !== 'updated_at' && name !== 'deleted_at';
|
|
26
27
|
});
|
|
27
28
|
const emitDeletedAtColumn = !altering && (softDelete || userExplicitlyPassedDeletedAt);
|
|
28
|
-
const { columnDefs, columnDrops, indexDefs
|
|
29
|
-
const { columnDefs, columnDrops, indexDefs
|
|
29
|
+
const { columnDefs, columnDrops, indexDefs } = processedColumnsWithTypes.reduce((acc, attributeDeclaration) => {
|
|
30
|
+
const { columnDefs, columnDrops, indexDefs } = acc;
|
|
30
31
|
const [rawSegmentOne, _attributeType, ...descriptors] = attributeDeclaration.split(':');
|
|
31
32
|
if (!rawSegmentOne)
|
|
32
33
|
return acc;
|
|
@@ -129,13 +130,7 @@ export default function generateMigrationContent({ connectionName = 'default', t
|
|
|
129
130
|
}
|
|
130
131
|
columnDrops.push(`.dropColumn('${attributeName}')`);
|
|
131
132
|
if (processedAttrType === 'belongsto' || COLUMNS_TO_INDEX.includes(attributeName)) {
|
|
132
|
-
|
|
133
|
-
indexDefs.push(`await db.schema
|
|
134
|
-
.createIndex('${indexName}')
|
|
135
|
-
.on('${table}')
|
|
136
|
-
.column('${attributeName}')
|
|
137
|
-
.execute()`);
|
|
138
|
-
indexDrops.push(`await db.schema.dropIndex('${indexName}').execute()`);
|
|
133
|
+
indexDefs.push(columnIndexStatement(table, attributeName));
|
|
139
134
|
}
|
|
140
135
|
if (stiChildClassName && !userWantsThisOptional && !arrayAttribute && !isBooleanColumn) {
|
|
141
136
|
checkConstraints.push(`
|
|
@@ -149,7 +144,10 @@ export default function generateMigrationContent({ connectionName = 'default', t
|
|
|
149
144
|
.execute()`);
|
|
150
145
|
}
|
|
151
146
|
return acc;
|
|
152
|
-
}, { columnDefs: [], columnDrops: [], indexDefs: []
|
|
147
|
+
}, { columnDefs: [], columnDrops: [], indexDefs: [] });
|
|
148
|
+
if (emitDeletedAtColumn) {
|
|
149
|
+
indexDefs.push(columnIndexStatement(table, DELETED_AT_COLUMN_NAME));
|
|
150
|
+
}
|
|
153
151
|
if (!table) {
|
|
154
152
|
return `\
|
|
155
153
|
import { Kysely, sql } from 'kysely'
|
|
@@ -196,7 +194,7 @@ ${citextExtension}${generateEnumStatements(columnsWithTypes)} await db.schema
|
|
|
196
194
|
|
|
197
195
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
198
196
|
export async function down(db: Kysely<any>): Promise<void> {
|
|
199
|
-
${
|
|
197
|
+
${altering
|
|
200
198
|
? `await db.schema${newlineDoubleIndent}.alterTable('${table}')${columnDropLines}.execute()`
|
|
201
199
|
: `await db.schema.dropTable('${table}').execute()`}${generateEnumDropStatements(columnsWithTypes)}
|
|
202
200
|
}\
|
|
@@ -393,6 +391,14 @@ function lookupReferencesTable(snakeAssociationName, originalAssociationName) {
|
|
|
393
391
|
function associationNameToForeignKey(associationName) {
|
|
394
392
|
return snakeify(associationName.replace(/\//g, '_').replace(/_id$/, '') + '_id');
|
|
395
393
|
}
|
|
394
|
+
function columnIndexStatement(table, attributeName) {
|
|
395
|
+
const indexName = `${table}_${attributeName}`;
|
|
396
|
+
return `await db.schema
|
|
397
|
+
.createIndex('${indexName}')
|
|
398
|
+
.on('${table}')
|
|
399
|
+
.column('${attributeName}')
|
|
400
|
+
.execute()`;
|
|
401
|
+
}
|
|
396
402
|
export function optionalFromDescriptors(descriptors) {
|
|
397
403
|
const optional = descriptors.at(-1) === 'optional';
|
|
398
404
|
if (optional)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import parseAttribute from './parseAttribute.js';
|
|
2
|
+
export default function validateStiChildColumns(columnsWithTypes) {
|
|
3
|
+
const belongsToColumns = columnsWithTypes.filter(columnWithType => parseAttribute(columnWithType)?.normalizedAttributeType === 'belongsto');
|
|
4
|
+
if (belongsToColumns.length) {
|
|
5
|
+
throw new Error(`STI children cannot declare belongs_to associations. Declare associations on the STI parent instead. Unsupported columns: ${belongsToColumns.join(', ')}`);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
@@ -14,7 +14,8 @@ export type SpawnOptions = Omit<NodeSpawnOptions, 'shell'> & {
|
|
|
14
14
|
args?: string[];
|
|
15
15
|
};
|
|
16
16
|
export declare const CLI_INDENT = " ";
|
|
17
|
-
export declare const baseColumnsWithTypesDescription = "space separated snake-case
|
|
17
|
+
export declare const baseColumnsWithTypesDescription = "space separated snake-case properties like this:\n title:citext subtitle:string body_markdown:text style:enum:post_styles:formal,informal\n \n all properties default to not nullable; null can be allowed by appending ':optional':\n subtitle:string:optional\n \n supported types:\n - uuid:\n - uuid[]:\n a column optimized for storing UUIDs\n \n - citext:\n - citext[]:\n case insensitive text (indexes and queries are automatically case insensitive)\n \n - encrypted:\n encrypted text (used in conjunction with the @deco.Encrypted decorator)\n \n - string:\n - string[]:\n varchar; allowed length defaults to 255, but may be customized, e.g.: subtitle:string:128 or subtitle:string:128:optional\n \n - text\n - text[]\n - date\n - date[]\n - datetime\n - datetime[]\n - time\n - time[]\n - timetz\n - timetz[]\n - integer\n - integer[]\n \n - decimal:\n - decimal[]:\n precision,scale is required, e.g.: volume:decimal:3,2 or volume:decimal:3,2:optional\n \n leveraging arrays, add the \"[]\" suffix, e.g.: volume:decimal[]:3,2\n \n - enum:\n - enum[]:\n include the enum name to automatically create the enum:\n type:enum:room_types:bathroom,kitchen,bedroom or type:enum:room_types:bathroom,kitchen,bedroom:optional\n \n omit the enum values to leverage an existing enum (omits the enum type creation):\n type:enum:room_types or type:enum:room_types:optional\n \n leveraging arrays, add the \"[]\" suffix, e.g.: type:enum[]:room_types:bathroom,kitchen,bedroom";
|
|
18
|
+
export declare const columnsWithTypesDescriptionForStiChild: string;
|
|
18
19
|
export default class DreamCLI {
|
|
19
20
|
/**
|
|
20
21
|
* Starts the Dream console
|
|
@@ -5,6 +5,13 @@ import type DreamApp from '../dream-app/index.js';
|
|
|
5
5
|
* probe so they always agree on the index range.
|
|
6
6
|
*/
|
|
7
7
|
export declare function testDatabasePoolSize(parallelTests: number | undefined): number;
|
|
8
|
+
/**
|
|
9
|
+
* Normalize configured test parallelism to the minimum safe pool width input.
|
|
10
|
+
* Even a serial vitest run can overlap old/new worker processes, so missing,
|
|
11
|
+
* invalid, zero, or fractional-below-one values all mean "one active worker",
|
|
12
|
+
* not "disable the per-live-worker database pool".
|
|
13
|
+
*/
|
|
14
|
+
export declare function normalizeTestDatabaseParallelism(parallelTests: number | undefined): number;
|
|
8
15
|
/**
|
|
9
16
|
* The database name for a pool index given the un-suffixed base name. Index 1
|
|
10
17
|
* is the base itself (no suffix); higher indexes are `<base>_<index>`. This is
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function validateStiChildColumns(columnsWithTypes: string[]): void;
|