@rvoh/dream 2.18.1 → 2.20.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 +186 -0
- package/dist/cjs/src/db/DreamDbConnection.js +6 -0
- package/dist/cjs/src/decorators/class/SoftDelete.js +12 -0
- package/dist/cjs/src/dream/DreamClassTransactionBuilder.js +113 -0
- package/dist/cjs/src/dream/Query.js +148 -0
- package/dist/cjs/src/dream/QueryDriver/Base.js +84 -0
- package/dist/cjs/src/dream/QueryDriver/Kysely.js +420 -81
- package/dist/cjs/src/dream/internal/filterRowToKnownColumns.js +41 -0
- package/dist/cjs/src/dream/internal/saveDream.js +6 -1
- package/dist/cjs/src/dream/internal/sqlResultToDreamInstance.js +9 -2
- package/dist/cjs/src/errors/CannotNamespaceAssociationFilterToAnotherTable.js +27 -0
- package/dist/cjs/src/errors/schema-builder/CannotIgnoreAssociationColumn.js +38 -0
- package/dist/cjs/src/errors/schema-builder/CannotIgnoreEncryptedColumn.js +20 -0
- package/dist/cjs/src/errors/schema-builder/CannotIgnorePrimaryKey.js +18 -0
- package/dist/cjs/src/errors/schema-builder/CannotIgnoreSoftDeleteColumn.js +21 -0
- package/dist/cjs/src/errors/schema-builder/CannotIgnoreSortablePositionColumn.js +20 -0
- package/dist/cjs/src/errors/schema-builder/CannotIgnoreSortableScopeColumn.js +24 -0
- package/dist/cjs/src/errors/schema-builder/CannotIgnoreStiTypeColumn.js +17 -0
- package/dist/cjs/src/errors/schema-builder/ConflictingIgnoredColumns.js +27 -0
- package/dist/cjs/src/errors/schema-builder/IgnoredColumnMustBeCamelCase.js +21 -0
- package/dist/cjs/src/helpers/cli/ASTConnectionBuilder.js +38 -1
- package/dist/cjs/src/helpers/cli/ASTKyselyCodegenEnhancer.js +60 -0
- package/dist/cjs/src/helpers/cli/generateMigrationContent.js +21 -13
- package/dist/cjs/src/helpers/cli/resolveIgnoredColumns.js +198 -0
- package/dist/esm/src/Dream.js +186 -0
- package/dist/esm/src/db/DreamDbConnection.js +6 -0
- package/dist/esm/src/decorators/class/SoftDelete.js +12 -0
- package/dist/esm/src/dream/DreamClassTransactionBuilder.js +113 -0
- package/dist/esm/src/dream/Query.js +148 -0
- package/dist/esm/src/dream/QueryDriver/Base.js +84 -0
- package/dist/esm/src/dream/QueryDriver/Kysely.js +420 -81
- package/dist/esm/src/dream/internal/filterRowToKnownColumns.js +41 -0
- package/dist/esm/src/dream/internal/saveDream.js +6 -1
- package/dist/esm/src/dream/internal/sqlResultToDreamInstance.js +9 -2
- package/dist/esm/src/errors/CannotNamespaceAssociationFilterToAnotherTable.js +27 -0
- package/dist/esm/src/errors/schema-builder/CannotIgnoreAssociationColumn.js +38 -0
- package/dist/esm/src/errors/schema-builder/CannotIgnoreEncryptedColumn.js +20 -0
- package/dist/esm/src/errors/schema-builder/CannotIgnorePrimaryKey.js +18 -0
- package/dist/esm/src/errors/schema-builder/CannotIgnoreSoftDeleteColumn.js +21 -0
- package/dist/esm/src/errors/schema-builder/CannotIgnoreSortablePositionColumn.js +20 -0
- package/dist/esm/src/errors/schema-builder/CannotIgnoreSortableScopeColumn.js +24 -0
- package/dist/esm/src/errors/schema-builder/CannotIgnoreStiTypeColumn.js +17 -0
- package/dist/esm/src/errors/schema-builder/ConflictingIgnoredColumns.js +27 -0
- package/dist/esm/src/errors/schema-builder/IgnoredColumnMustBeCamelCase.js +21 -0
- package/dist/esm/src/helpers/cli/ASTConnectionBuilder.js +38 -1
- package/dist/esm/src/helpers/cli/ASTKyselyCodegenEnhancer.js +60 -0
- package/dist/esm/src/helpers/cli/generateMigrationContent.js +21 -13
- package/dist/esm/src/helpers/cli/resolveIgnoredColumns.js +198 -0
- package/dist/types/src/Dream.d.ts +178 -4
- package/dist/types/src/decorators/class/SoftDelete.d.ts +12 -0
- package/dist/types/src/dream/DreamClassTransactionBuilder.d.ts +103 -0
- package/dist/types/src/dream/Query.d.ts +138 -0
- package/dist/types/src/dream/QueryDriver/Base.d.ts +69 -0
- package/dist/types/src/dream/QueryDriver/Kysely.d.ts +187 -8
- package/dist/types/src/dream/internal/filterRowToKnownColumns.d.ts +30 -0
- package/dist/types/src/errors/CannotNamespaceAssociationFilterToAnotherTable.d.ts +8 -0
- package/dist/types/src/errors/schema-builder/CannotIgnoreAssociationColumn.d.ts +12 -0
- package/dist/types/src/errors/schema-builder/CannotIgnoreEncryptedColumn.d.ts +7 -0
- package/dist/types/src/errors/schema-builder/CannotIgnorePrimaryKey.d.ts +6 -0
- package/dist/types/src/errors/schema-builder/CannotIgnoreSoftDeleteColumn.d.ts +7 -0
- package/dist/types/src/errors/schema-builder/CannotIgnoreSortablePositionColumn.d.ts +7 -0
- package/dist/types/src/errors/schema-builder/CannotIgnoreSortableScopeColumn.d.ts +8 -0
- package/dist/types/src/errors/schema-builder/CannotIgnoreStiTypeColumn.d.ts +6 -0
- package/dist/types/src/errors/schema-builder/ConflictingIgnoredColumns.d.ts +7 -0
- package/dist/types/src/errors/schema-builder/IgnoredColumnMustBeCamelCase.d.ts +7 -0
- package/dist/types/src/helpers/cli/ASTConnectionBuilder.d.ts +20 -0
- package/dist/types/src/helpers/cli/ASTKyselyCodegenEnhancer.d.ts +13 -0
- package/dist/types/src/helpers/cli/resolveIgnoredColumns.d.ts +41 -0
- package/dist/types/src/types/associations/shared.d.ts +4 -1
- package/dist/types/src/types/dream.d.ts +17 -0
- package/dist/types/src/types/types/associations/shared.ts +10 -1
- package/dist/types/src/types/types/dream.ts +53 -0
- package/dist/types/src/types/types/variadic.ts +179 -140
- package/dist/types/src/types/variadic.d.ts +15 -10
- package/docs/assets/hierarchy.js +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/classes/db.DreamMigrationHelpers.html +11 -11
- package/docs/classes/db.KyselyQueryDriver.html +96 -34
- package/docs/classes/db.PostgresQueryDriver.html +97 -35
- package/docs/classes/db.QueryDriverBase.html +77 -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 +247 -119
- 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 +155 -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 +12 -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/hierarchy.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 +3 -3
- package/dist/cjs/src/dream/internal/associations/throughAssociationHasOptionsBesidesThroughAndSource.js +0 -11
- package/dist/cjs/src/errors/associations/ThroughAssociationConditionsIncompatibleWithThroughAssociationSource.js +0 -17
- package/dist/esm/src/dream/internal/associations/throughAssociationHasOptionsBesidesThroughAndSource.js +0 -11
- package/dist/esm/src/errors/associations/ThroughAssociationConditionsIncompatibleWithThroughAssociationSource.js +0 -17
- package/dist/types/src/dream/internal/associations/throughAssociationHasOptionsBesidesThroughAndSource.d.ts +0 -13
- package/dist/types/src/errors/associations/ThroughAssociationConditionsIncompatibleWithThroughAssociationSource.d.ts +0 -12
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import protectAgainstPollutingAssignment from '../../helpers/protectAgainstPollutingAssignment.js';
|
|
2
|
+
/**
|
|
3
|
+
* @internal
|
|
4
|
+
*
|
|
5
|
+
* Returns an object containing only the keys of `row` that are columns
|
|
6
|
+
* the compiled schema knows about (per the provided column set).
|
|
7
|
+
*
|
|
8
|
+
* Under schema/image skew (e.g. a rolling deploy in which a migration adds
|
|
9
|
+
* a column while containers built against the previous schema are still
|
|
10
|
+
* draining, or application code is rolled back after an add-column
|
|
11
|
+
* migration), a `RETURNING *` / `select *` row can include columns this
|
|
12
|
+
* build has never heard of. Passing such keys to `setAttributes` would
|
|
13
|
+
* assign them as plain properties — invoking a same-named user-defined
|
|
14
|
+
* setter, or throwing on a getter-only property — so they must be dropped
|
|
15
|
+
* before hydration.
|
|
16
|
+
*
|
|
17
|
+
* Keys are intersected rather than enumerated from the column set so that
|
|
18
|
+
* a column missing from the row (the dropped-column direction of skew)
|
|
19
|
+
* simply does not appear, rather than appearing with an `undefined` value.
|
|
20
|
+
*
|
|
21
|
+
* Identity fast path: when every key of `row` is a known column (the
|
|
22
|
+
* no-skew steady state), `row` itself is returned unchanged, without
|
|
23
|
+
* allocating a copy; a filtered copy is built only when at least one
|
|
24
|
+
* unknown key is present.
|
|
25
|
+
*
|
|
26
|
+
* @param row - a raw database row
|
|
27
|
+
* @param columns - the compiled column set for the Dream class being hydrated
|
|
28
|
+
* @returns `row` itself when every key is a known column; otherwise a new
|
|
29
|
+
* object containing only the known-column entries of `row`
|
|
30
|
+
*/
|
|
31
|
+
export default function filterRowToKnownColumns(row, columns) {
|
|
32
|
+
const keys = Object.keys(row);
|
|
33
|
+
if (keys.every(key => columns.has(key)))
|
|
34
|
+
return row;
|
|
35
|
+
const filtered = {};
|
|
36
|
+
keys.forEach(key => {
|
|
37
|
+
if (columns.has(key))
|
|
38
|
+
filtered[protectAgainstPollutingAssignment(key)] = row[key];
|
|
39
|
+
});
|
|
40
|
+
return filtered;
|
|
41
|
+
}
|
|
@@ -2,6 +2,7 @@ import ValidationError from '../../errors/ValidationError.js';
|
|
|
2
2
|
import sqlAttributes from '../../helpers/sqlAttributes.js';
|
|
3
3
|
import { DateTime } from '../../utils/datetime/DateTime.js';
|
|
4
4
|
import Query from '../Query.js';
|
|
5
|
+
import filterRowToKnownColumns from './filterRowToKnownColumns.js';
|
|
5
6
|
import runHooksFor from './runHooksFor.js';
|
|
6
7
|
export default async function saveDream(dream, txn = null, { skipHooks = false } = {}) {
|
|
7
8
|
const alreadyPersisted = dream.isPersisted;
|
|
@@ -30,7 +31,11 @@ export default async function saveDream(dream, txn = null, { skipHooks = false }
|
|
|
30
31
|
if (!alreadyPersisted || hasUnsavedData) {
|
|
31
32
|
const data = await Query.dbDriverClass(dream.connectionName || 'default').saveDream(dream, txn);
|
|
32
33
|
dream['isPersisted'] = true;
|
|
33
|
-
|
|
34
|
+
// the row returned by the driver comes from `RETURNING *` (or a
|
|
35
|
+
// `select *` reload on drivers without RETURNING support), so under
|
|
36
|
+
// schema/image skew it can contain columns this build's compiled
|
|
37
|
+
// schema doesn't know about; those must never reach setAttributes
|
|
38
|
+
dream.setAttributes(filterRowToKnownColumns(data, dream.columns()));
|
|
34
39
|
}
|
|
35
40
|
// set frozen attributes to what has already been saved
|
|
36
41
|
dream['freezeAttributes']();
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import STIChildMissing from '../../errors/sti/STIChildMissing.js';
|
|
2
|
+
import filterRowToKnownColumns from './filterRowToKnownColumns.js';
|
|
2
3
|
export default function sqlResultToDreamInstance(dreamClass, sqlResult) {
|
|
4
|
+
// base-model reads select `*`, so under schema/image skew (or with a
|
|
5
|
+
// column declared in ignoredColumns but not yet dropped) the row can
|
|
6
|
+
// contain columns the compiled schema doesn't know about. Passing such
|
|
7
|
+
// keys into the constructor would assign them as plain properties —
|
|
8
|
+
// invoking a same-named user-defined setter, or throwing on a
|
|
9
|
+
// getter-only property — so they are dropped before hydration.
|
|
3
10
|
if (dreamClass['isSTIBase']) {
|
|
4
11
|
const extendingDreamClass = findExtendingDreamClass(dreamClass, sqlResult.type);
|
|
5
12
|
if (!extendingDreamClass)
|
|
6
13
|
throw new STIChildMissing(dreamClass, sqlResult.type, sqlResult[dreamClass.primaryKey]);
|
|
7
|
-
const dreamModel = new extendingDreamClass(sqlResult, {
|
|
14
|
+
const dreamModel = new extendingDreamClass(filterRowToKnownColumns(sqlResult, extendingDreamClass.columns()), {
|
|
8
15
|
bypassUserDefinedSetters: true,
|
|
9
16
|
isPersisted: true,
|
|
10
17
|
_internalUseOnly: true,
|
|
@@ -13,7 +20,7 @@ export default function sqlResultToDreamInstance(dreamClass, sqlResult) {
|
|
|
13
20
|
return dreamModel;
|
|
14
21
|
}
|
|
15
22
|
else {
|
|
16
|
-
const dreamModel = new dreamClass(sqlResult, {
|
|
23
|
+
const dreamModel = new dreamClass(filterRowToKnownColumns(sqlResult, dreamClass.columns()), {
|
|
17
24
|
bypassUserDefinedSetters: true,
|
|
18
25
|
isPersisted: true,
|
|
19
26
|
_internalUseOnly: true,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export default class CannotNamespaceAssociationFilterToAnotherTable extends Error {
|
|
2
|
+
dreamClass;
|
|
3
|
+
key;
|
|
4
|
+
expectedAlias;
|
|
5
|
+
constructor(dreamClass, key, expectedAlias) {
|
|
6
|
+
super();
|
|
7
|
+
this.dreamClass = dreamClass;
|
|
8
|
+
this.key = key;
|
|
9
|
+
this.expectedAlias = expectedAlias;
|
|
10
|
+
}
|
|
11
|
+
get message() {
|
|
12
|
+
return `
|
|
13
|
+
Cannot filter on an association via a where-clause key namespaced to another table.
|
|
14
|
+
|
|
15
|
+
dream class: ${this.dreamClass.sanitizedName}
|
|
16
|
+
where-clause key: ${this.key}
|
|
17
|
+
table alias this where clause applies to: ${this.expectedAlias}
|
|
18
|
+
|
|
19
|
+
A Dream instance (or array of Dream instances) as a where-clause value resolves
|
|
20
|
+
an association on the model the where clause applies to, so the key may not be
|
|
21
|
+
namespaced to a different table. To filter a joined table by one of its
|
|
22
|
+
associations, pass the filter in the join's on-clause instead, e.g.:
|
|
23
|
+
|
|
24
|
+
.innerJoin('association', { and: { myBelongsToAssociation: instances } })
|
|
25
|
+
`;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export default class CannotIgnoreAssociationColumn extends Error {
|
|
2
|
+
tableName;
|
|
3
|
+
columnName;
|
|
4
|
+
modelClass;
|
|
5
|
+
association;
|
|
6
|
+
columnRole;
|
|
7
|
+
constructor(tableName, columnName, modelClass, association, columnRole) {
|
|
8
|
+
super();
|
|
9
|
+
this.tableName = tableName;
|
|
10
|
+
this.columnName = columnName;
|
|
11
|
+
this.modelClass = modelClass;
|
|
12
|
+
this.association = association;
|
|
13
|
+
this.columnRole = columnRole;
|
|
14
|
+
}
|
|
15
|
+
get message() {
|
|
16
|
+
return `
|
|
17
|
+
The models backed by the "${this.tableName}" table declare
|
|
18
|
+
"${this.columnName}" in ignoredColumns, but "${this.columnName}" is the
|
|
19
|
+
${this.columnRole} of the ${this.association.type} association
|
|
20
|
+
"${this.association.as}" on ${this.modelClass.sanitizedName}.
|
|
21
|
+
|
|
22
|
+
Associations read and write their columns by name, so ignoring
|
|
23
|
+
"${this.columnName}" would silently break "${this.association.as}" at
|
|
24
|
+
runtime: ${this.consequence}. Remove the
|
|
25
|
+
"${this.association.as}" association from ${this.modelClass.sanitizedName}
|
|
26
|
+
(or point it at a different column) before ignoring "${this.columnName}".
|
|
27
|
+
`;
|
|
28
|
+
}
|
|
29
|
+
get consequence() {
|
|
30
|
+
if (this.columnRole === 'primary key override')
|
|
31
|
+
return `associating a record would read "${this.columnName}" from an
|
|
32
|
+
instance that no longer hydrates it, silently persisting an empty foreign
|
|
33
|
+
key, and association loads would join on a column missing from the
|
|
34
|
+
generated schema`;
|
|
35
|
+
return `writes to the column would stop persisting, and association loads
|
|
36
|
+
would reference a column missing from the generated schema`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export default class CannotIgnoreEncryptedColumn extends Error {
|
|
2
|
+
modelClass;
|
|
3
|
+
columnName;
|
|
4
|
+
constructor(modelClass, columnName) {
|
|
5
|
+
super();
|
|
6
|
+
this.modelClass = modelClass;
|
|
7
|
+
this.columnName = columnName;
|
|
8
|
+
}
|
|
9
|
+
get message() {
|
|
10
|
+
return `
|
|
11
|
+
${this.modelClass.sanitizedName} declares "${this.columnName}" in
|
|
12
|
+
ignoredColumns, but "${this.columnName}" is the backing column of an
|
|
13
|
+
@Encrypted property on ${this.modelClass.sanitizedName}.
|
|
14
|
+
|
|
15
|
+
@Encrypted stores its ciphertext in this column, so it can never be
|
|
16
|
+
ignored. Remove the @Encrypted declaration (and its property) from
|
|
17
|
+
${this.modelClass.sanitizedName} before ignoring "${this.columnName}".
|
|
18
|
+
`;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export default class CannotIgnorePrimaryKey extends Error {
|
|
2
|
+
modelClass;
|
|
3
|
+
constructor(modelClass) {
|
|
4
|
+
super();
|
|
5
|
+
this.modelClass = modelClass;
|
|
6
|
+
}
|
|
7
|
+
get message() {
|
|
8
|
+
return `
|
|
9
|
+
${this.modelClass.sanitizedName} declares its primary key ("${this.modelClass.primaryKey}")
|
|
10
|
+
in ignoredColumns.
|
|
11
|
+
|
|
12
|
+
The primary key is required to identify rows for hydration, updates, and
|
|
13
|
+
associations, so it can never be ignored. Remove
|
|
14
|
+
"${this.modelClass.primaryKey}" from the ignoredColumns getter on
|
|
15
|
+
${this.modelClass.sanitizedName}.
|
|
16
|
+
`;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export default class CannotIgnoreSoftDeleteColumn extends Error {
|
|
2
|
+
modelClass;
|
|
3
|
+
columnName;
|
|
4
|
+
constructor(modelClass, columnName) {
|
|
5
|
+
super();
|
|
6
|
+
this.modelClass = modelClass;
|
|
7
|
+
this.columnName = columnName;
|
|
8
|
+
}
|
|
9
|
+
get message() {
|
|
10
|
+
return `
|
|
11
|
+
${this.modelClass.sanitizedName} declares "${this.columnName}" in
|
|
12
|
+
ignoredColumns, but ${this.modelClass.sanitizedName} is a SoftDelete model
|
|
13
|
+
and "${this.columnName}" is its deletedAtField.
|
|
14
|
+
|
|
15
|
+
SoftDelete writes this column on destroy and filters on it in its default
|
|
16
|
+
scope, so it can never be ignored. Remove the @SoftDelete decorator from
|
|
17
|
+
${this.modelClass.sanitizedName} (or point deletedAtField at a different
|
|
18
|
+
column) before ignoring "${this.columnName}".
|
|
19
|
+
`;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export default class CannotIgnoreSortablePositionColumn extends Error {
|
|
2
|
+
modelClass;
|
|
3
|
+
columnName;
|
|
4
|
+
constructor(modelClass, columnName) {
|
|
5
|
+
super();
|
|
6
|
+
this.modelClass = modelClass;
|
|
7
|
+
this.columnName = columnName;
|
|
8
|
+
}
|
|
9
|
+
get message() {
|
|
10
|
+
return `
|
|
11
|
+
${this.modelClass.sanitizedName} declares "${this.columnName}" in
|
|
12
|
+
ignoredColumns, but "${this.columnName}" is the position field of an
|
|
13
|
+
@Sortable declaration on ${this.modelClass.sanitizedName}.
|
|
14
|
+
|
|
15
|
+
@Sortable reads and rewrites this column to keep positions contiguous, so
|
|
16
|
+
it can never be ignored. Remove the @Sortable decorator (and its property)
|
|
17
|
+
from ${this.modelClass.sanitizedName} before ignoring "${this.columnName}".
|
|
18
|
+
`;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export default class CannotIgnoreSortableScopeColumn extends Error {
|
|
2
|
+
modelClass;
|
|
3
|
+
columnName;
|
|
4
|
+
positionField;
|
|
5
|
+
constructor(modelClass, columnName, positionField) {
|
|
6
|
+
super();
|
|
7
|
+
this.modelClass = modelClass;
|
|
8
|
+
this.columnName = columnName;
|
|
9
|
+
this.positionField = positionField;
|
|
10
|
+
}
|
|
11
|
+
get message() {
|
|
12
|
+
return `
|
|
13
|
+
${this.modelClass.sanitizedName} declares "${this.columnName}" in
|
|
14
|
+
ignoredColumns, but "${this.columnName}" is a scope column of the @Sortable
|
|
15
|
+
declaration on "${this.positionField}" on ${this.modelClass.sanitizedName}.
|
|
16
|
+
|
|
17
|
+
@Sortable reads this column by name on every save and destroy to partition
|
|
18
|
+
position values, so it can never be ignored. Remove "${this.columnName}"
|
|
19
|
+
from the @Sortable scope on "${this.positionField}" (or remove the
|
|
20
|
+
@Sortable decorator and its property) on ${this.modelClass.sanitizedName}
|
|
21
|
+
before ignoring "${this.columnName}".
|
|
22
|
+
`;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export default class CannotIgnoreStiTypeColumn extends Error {
|
|
2
|
+
modelClass;
|
|
3
|
+
constructor(modelClass) {
|
|
4
|
+
super();
|
|
5
|
+
this.modelClass = modelClass;
|
|
6
|
+
}
|
|
7
|
+
get message() {
|
|
8
|
+
return `
|
|
9
|
+
${this.modelClass.sanitizedName} participates in single-table inheritance
|
|
10
|
+
and declares the STI "type" column in ignoredColumns.
|
|
11
|
+
|
|
12
|
+
The "type" column determines which model class each row hydrates into, so
|
|
13
|
+
an STI model can never ignore it. Remove "type" from the ignoredColumns
|
|
14
|
+
getter on ${this.modelClass.sanitizedName}.
|
|
15
|
+
`;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export default class ConflictingIgnoredColumns extends Error {
|
|
2
|
+
tableName;
|
|
3
|
+
modelClasses;
|
|
4
|
+
constructor(tableName, modelClasses) {
|
|
5
|
+
super();
|
|
6
|
+
this.tableName = tableName;
|
|
7
|
+
this.modelClasses = modelClasses;
|
|
8
|
+
}
|
|
9
|
+
get message() {
|
|
10
|
+
const declarations = this.modelClasses
|
|
11
|
+
.map(modelClass => ` ${modelClass.sanitizedName}: [${[...modelClass.prototype.ignoredColumns]
|
|
12
|
+
.sort()
|
|
13
|
+
.map(column => `'${column}'`)
|
|
14
|
+
.join(', ')}]`)
|
|
15
|
+
.join('\n');
|
|
16
|
+
return `
|
|
17
|
+
Models sharing the table "${this.tableName}" declare different ignoredColumns:
|
|
18
|
+
|
|
19
|
+
${declarations}
|
|
20
|
+
|
|
21
|
+
There is only one generated schema per table, so every model backed by a
|
|
22
|
+
table must agree on which of its columns are ignored. Align the
|
|
23
|
+
ignoredColumns getters (STI children inherit the base model's getter, so
|
|
24
|
+
the common fix is to declare ignored columns only on the base model).
|
|
25
|
+
`;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import camelize from '../../helpers/camelize.js';
|
|
2
|
+
export default class IgnoredColumnMustBeCamelCase extends Error {
|
|
3
|
+
modelClass;
|
|
4
|
+
columnName;
|
|
5
|
+
constructor(modelClass, columnName) {
|
|
6
|
+
super();
|
|
7
|
+
this.modelClass = modelClass;
|
|
8
|
+
this.columnName = columnName;
|
|
9
|
+
}
|
|
10
|
+
get message() {
|
|
11
|
+
return `
|
|
12
|
+
${this.modelClass.sanitizedName} declares "${this.columnName}" in
|
|
13
|
+
ignoredColumns, but ignored columns must be declared in camelCase.
|
|
14
|
+
|
|
15
|
+
Dream camelizes database column names when generating types, so a
|
|
16
|
+
declaration that is not camelCase can never match a generated column and
|
|
17
|
+
would be silently inert. Declare "${camelize(this.columnName)}" in the
|
|
18
|
+
ignoredColumns getter on ${this.modelClass.sanitizedName} instead.
|
|
19
|
+
`;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -11,6 +11,7 @@ import intersection from '../intersection.js';
|
|
|
11
11
|
import sortBy from '../sortBy.js';
|
|
12
12
|
import uniq from '../uniq.js';
|
|
13
13
|
import ASTBuilder from './ASTBuilder.js';
|
|
14
|
+
import resolveIgnoredColumns from './resolveIgnoredColumns.js';
|
|
14
15
|
/**
|
|
15
16
|
* @internal
|
|
16
17
|
*
|
|
@@ -265,12 +266,48 @@ may need to update the table getter in the corresponding Dream.
|
|
|
265
266
|
default: uniq(models.flatMap(model => model['scopes'].default.map(scopeStatement => scopeStatement.method))),
|
|
266
267
|
named: uniq(models.flatMap(model => model['scopes'].named.map(scopeStatement => scopeStatement.method))),
|
|
267
268
|
},
|
|
268
|
-
columns: await this.getColumnData(tableName, associationData),
|
|
269
|
+
columns: this.withoutIgnoredColumns(await this.getColumnData(tableName, associationData), tableName),
|
|
269
270
|
virtualColumns: uniq(models.flatMap(model => model['virtualAttributes'].map(prop => prop.property) || [])),
|
|
270
271
|
associations: associationData,
|
|
271
272
|
serializerKeys,
|
|
272
273
|
};
|
|
273
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* @internal
|
|
277
|
+
*
|
|
278
|
+
* resolves the ignored columns declared by the models backed by the
|
|
279
|
+
* given table (validating the declarations; see resolveIgnoredColumns)
|
|
280
|
+
*/
|
|
281
|
+
ignoredColumnsForTable(tableName) {
|
|
282
|
+
const dreamApp = DreamApp.getOrFail();
|
|
283
|
+
const allModels = Object.values(dreamApp.models).filter(model => model.prototype?.connectionName === this.connectionName);
|
|
284
|
+
const models = allModels.filter(model => model.table === tableName);
|
|
285
|
+
return resolveIgnoredColumns(models, tableName, allModels);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* @internal
|
|
289
|
+
*
|
|
290
|
+
* returns the provided column data without the columns that the table's
|
|
291
|
+
* models declare in ignoredColumns. This is what removes ignored columns
|
|
292
|
+
* from the generated dream schema file: `columns()` reads the generated
|
|
293
|
+
* schema at runtime, so every column enumeration built from `columns()`
|
|
294
|
+
* (preload and join-load select lists, save hydration, attribute
|
|
295
|
+
* definition) inherits this filtering. A column that is ignored but not
|
|
296
|
+
* present in the introspected table (e.g. after the drop migration has
|
|
297
|
+
* run but before the declaration is removed) is a no-op.
|
|
298
|
+
*/
|
|
299
|
+
withoutIgnoredColumns(columnData, tableName) {
|
|
300
|
+
const ignoredColumns = this.ignoredColumnsForTable(tableName);
|
|
301
|
+
if (!ignoredColumns.size)
|
|
302
|
+
return columnData;
|
|
303
|
+
return Object.keys(columnData)
|
|
304
|
+
.filter(columnName => !ignoredColumns.has(columnName))
|
|
305
|
+
.reduce((filtered, columnName) => {
|
|
306
|
+
;
|
|
307
|
+
filtered[columnName] = columnData[columnName];
|
|
308
|
+
return filtered;
|
|
309
|
+
}, {});
|
|
310
|
+
}
|
|
274
311
|
/**
|
|
275
312
|
* @internal
|
|
276
313
|
*
|
|
@@ -25,6 +25,7 @@ export default class ASTKyselyCodegenEnhancer extends ASTConnectionBuilder {
|
|
|
25
25
|
async enhance() {
|
|
26
26
|
let dbSourceFile = await this.getDbSourceFile();
|
|
27
27
|
dbSourceFile = this.camelizeKeys(dbSourceFile);
|
|
28
|
+
dbSourceFile = this.removeIgnoredColumns(dbSourceFile);
|
|
28
29
|
dbSourceFile = this.replaceTimestampExport(dbSourceFile);
|
|
29
30
|
dbSourceFile = await this.replaceTimeFieldsInInterfaces(dbSourceFile);
|
|
30
31
|
dbSourceFile = this.addMissingImports(dbSourceFile);
|
|
@@ -283,6 +284,65 @@ ${output}`);
|
|
|
283
284
|
const result = ts.transform(dbSourceFile, [transformer]);
|
|
284
285
|
return result.transformed[0];
|
|
285
286
|
}
|
|
287
|
+
/**
|
|
288
|
+
* @internal
|
|
289
|
+
*
|
|
290
|
+
* removes columns declared in model `ignoredColumns` getters from the
|
|
291
|
+
* table interfaces generated by kysely-codegen. This is what removes
|
|
292
|
+
* ignored columns from the generated db types file: every column-name
|
|
293
|
+
* level type (`DreamColumnNames`, `UpdateableProperties`, where-clause
|
|
294
|
+
* statements) derives from the Kysely `DB` interface, so filtering here
|
|
295
|
+
* turns any remaining code reference to an ignored column into a type
|
|
296
|
+
* error. Runs after camelizeKeys so that member names align with the
|
|
297
|
+
* camelCase column names models declare in ignoredColumns.
|
|
298
|
+
*/
|
|
299
|
+
removeIgnoredColumns(dbSourceFile) {
|
|
300
|
+
const dbInterface = this.findDbExport(dbSourceFile, 'DB');
|
|
301
|
+
if (!dbInterface)
|
|
302
|
+
return dbSourceFile;
|
|
303
|
+
// the DB interface maps table names to table interfaces (e.g.
|
|
304
|
+
// `collars: Collars`), so it provides the table name each table
|
|
305
|
+
// interface corresponds to
|
|
306
|
+
const ignoredColumnsByInterfaceName = {};
|
|
307
|
+
dbInterface.members.forEach(member => {
|
|
308
|
+
if (!ts.isPropertySignature(member))
|
|
309
|
+
return;
|
|
310
|
+
if (!member.type || !ts.isTypeReferenceNode(member.type))
|
|
311
|
+
return;
|
|
312
|
+
if (!ts.isIdentifier(member.type.typeName))
|
|
313
|
+
return;
|
|
314
|
+
const tableName = ts.isIdentifier(member.name)
|
|
315
|
+
? member.name.text
|
|
316
|
+
: ts.isStringLiteral(member.name)
|
|
317
|
+
? member.name.text
|
|
318
|
+
: null;
|
|
319
|
+
if (!tableName)
|
|
320
|
+
return;
|
|
321
|
+
const ignoredColumns = this.ignoredColumnsForTable(tableName);
|
|
322
|
+
if (ignoredColumns.size)
|
|
323
|
+
ignoredColumnsByInterfaceName[member.type.typeName.text] = ignoredColumns;
|
|
324
|
+
});
|
|
325
|
+
if (!Object.keys(ignoredColumnsByInterfaceName).length)
|
|
326
|
+
return dbSourceFile;
|
|
327
|
+
// @ts-expect-error cannot lock this type down, though implementation is correct
|
|
328
|
+
const transformer = context => {
|
|
329
|
+
const visit = node => {
|
|
330
|
+
const interfaceNode = this.exportedInterfaceOrNull(node);
|
|
331
|
+
const ignoredColumns = interfaceNode
|
|
332
|
+
? ignoredColumnsByInterfaceName[interfaceNode.name.text]
|
|
333
|
+
: undefined;
|
|
334
|
+
if (interfaceNode && ignoredColumns) {
|
|
335
|
+
return f.updateInterfaceDeclaration(interfaceNode, interfaceNode.modifiers, interfaceNode.name, interfaceNode.typeParameters, interfaceNode.heritageClauses, interfaceNode.members.filter(member => !(ts.isPropertySignature(member) &&
|
|
336
|
+
(ts.isIdentifier(member.name) || ts.isStringLiteral(member.name)) &&
|
|
337
|
+
ignoredColumns.has(member.name.text))));
|
|
338
|
+
}
|
|
339
|
+
return ts.visitEachChild(node, visit, context);
|
|
340
|
+
};
|
|
341
|
+
return node => ts.visitNode(node, visit);
|
|
342
|
+
};
|
|
343
|
+
const result = ts.transform(dbSourceFile, [transformer]);
|
|
344
|
+
return result.transformed[0];
|
|
345
|
+
}
|
|
286
346
|
/**
|
|
287
347
|
* @internal
|
|
288
348
|
*
|
|
@@ -8,8 +8,13 @@ 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
|
-
|
|
12
|
-
|
|
11
|
+
// deleted_at is deliberately NOT in this list: the SoftDelete default scope's
|
|
12
|
+
// `WHERE deleted_at IS NULL` is unselective on healthy tables, Dream internals
|
|
13
|
+
// never issue a query a deleted_at index could serve, and the useful partial
|
|
14
|
+
// index alternatives are dialect-specific SQL the generator must not emit by
|
|
15
|
+
// default. See spec/unit/cli/generateMigrationContent.spec.ts
|
|
16
|
+
// ("deleted_at is deliberately NOT indexed") and the CHANGELOG.
|
|
17
|
+
const COLUMNS_TO_INDEX = [STI_TYPE_COLUMN_NAME];
|
|
13
18
|
export default function generateMigrationContent({ connectionName = 'default', table, columnsWithTypes = [], primaryKeyType = 'bigserial', createOrAlter = 'create', stiChildClassName, softDelete = false, } = {}) {
|
|
14
19
|
const altering = createOrAlter === 'alter';
|
|
15
20
|
let requireCitextExtension = false;
|
|
@@ -43,13 +48,18 @@ export default function generateMigrationContent({ connectionName = 'default', t
|
|
|
43
48
|
return acc;
|
|
44
49
|
/**
|
|
45
50
|
* Automatically set email columns to citext since different casings of
|
|
46
|
-
* email address are the same email address
|
|
51
|
+
* email address are the same email address. Skip this when the user
|
|
52
|
+
* explicitly asked for an encrypted column, since encrypted columns
|
|
53
|
+
* are always stored as encrypted text and must not be overridden by
|
|
54
|
+
* the name-based heuristic.
|
|
47
55
|
*/
|
|
48
|
-
const attributeType =
|
|
49
|
-
?
|
|
50
|
-
: /
|
|
51
|
-
? '
|
|
52
|
-
:
|
|
56
|
+
const attributeType = _attributeType === 'encrypted' || _attributeType === 'encrypted[]'
|
|
57
|
+
? _attributeType
|
|
58
|
+
: /email$/.test(nonStandardAttributeName)
|
|
59
|
+
? 'citext'
|
|
60
|
+
: /uuid$/.test(nonStandardAttributeName)
|
|
61
|
+
? 'uuid'
|
|
62
|
+
: _attributeType;
|
|
53
63
|
const processedAttrType = camelize(attributeType)?.toLowerCase();
|
|
54
64
|
const userWantsThisOptional = optionalFromDescriptors(descriptors);
|
|
55
65
|
// when creating a migration for an STI child, we don't want to include notNull;
|
|
@@ -115,6 +125,7 @@ export default function generateMigrationContent({ connectionName = 'default', t
|
|
|
115
125
|
attributeName = `encrypted_${attributeName}`;
|
|
116
126
|
columnDefs.push(generateColumnStr(attributeName, 'text', descriptors, {
|
|
117
127
|
omitInlineNonNull,
|
|
128
|
+
skipUniqueHeuristic: true,
|
|
118
129
|
}));
|
|
119
130
|
break;
|
|
120
131
|
// TODO: determine if we need to support encrypted[] in the future
|
|
@@ -145,9 +156,6 @@ export default function generateMigrationContent({ connectionName = 'default', t
|
|
|
145
156
|
}
|
|
146
157
|
return acc;
|
|
147
158
|
}, { columnDefs: [], columnDrops: [], indexDefs: [] });
|
|
148
|
-
if (emitDeletedAtColumn) {
|
|
149
|
-
indexDefs.push(columnIndexStatement(table, DELETED_AT_COLUMN_NAME));
|
|
150
|
-
}
|
|
151
159
|
if (!table) {
|
|
152
160
|
return `\
|
|
153
161
|
import { Kysely, sql } from 'kysely'
|
|
@@ -274,12 +282,12 @@ function generateDecimalStr(attributeName, { descriptors, omitInlineNonNull: opt
|
|
|
274
282
|
: `'decimal(${scale}, ${precision})'`;
|
|
275
283
|
return `.addColumn('${attributeName}', ${decimalStatement}${optional ? '' : `, col => ${columnModifiers}`})`;
|
|
276
284
|
}
|
|
277
|
-
function generateColumnStr(attributeName, attributeType, descriptors, { omitInlineNonNull: optional }) {
|
|
285
|
+
function generateColumnStr(attributeName, attributeType, descriptors, { omitInlineNonNull: optional, skipUniqueHeuristic = false, }) {
|
|
278
286
|
let returnStr = `.addColumn('${attributeName}', ${attributeTypeString(attributeType)}`;
|
|
279
287
|
const providedDefaultArg = descriptors.find(d => /^default\(/.test(d));
|
|
280
288
|
const providedDefault = providedDefaultArg?.replace(/^default\(/, '')?.replace(/\)$/, '');
|
|
281
289
|
const notNull = !optional;
|
|
282
|
-
const isUnique = /(email|token|uuid)$/.test(attributeName);
|
|
290
|
+
const isUnique = !skipUniqueHeuristic && /(email|token|uuid)$/.test(attributeName);
|
|
283
291
|
const hasExtraValues = providedDefault || notNull || isUnique;
|
|
284
292
|
const isArray = /\[\]$/.test(attributeType);
|
|
285
293
|
const needsJsonDefault = notNull && !isArray && (attributeType === 'jsonb' || attributeType === 'json');
|