@rvoh/dream 2.40.2 → 2.42.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 +156 -13
- package/dist/cjs/src/cli/index.js +39 -5
- package/dist/cjs/src/db/DreamDbConnection.js +19 -1
- package/dist/cjs/src/dream/DreamClassTransactionBuilder.js +34 -2
- package/dist/cjs/src/dream/Query.js +178 -14
- package/dist/cjs/src/dream/QueryDriver/Base.js +80 -0
- package/dist/cjs/src/dream/QueryDriver/Kysely.js +35 -29
- package/dist/cjs/src/dream/QueryDriver/Postgres.js +83 -1
- package/dist/cjs/src/dream/internal/findOrCreateBy.js +5 -1
- package/dist/cjs/src/dream/internal/findOrCreateLookupStatement.js +19 -0
- package/dist/cjs/src/dream/internal/updateOrCreateBy.js +5 -1
- package/dist/cjs/src/encrypt/algorithms/aes-gcm/decryptAESGCM.js +24 -10
- package/dist/cjs/src/errors/CreateOrFindByFailedToCreateAndFind.js +63 -6
- package/dist/cjs/src/errors/CreateOrUpdateByFailedToCreateAndUpdate.js +41 -6
- package/dist/cjs/src/errors/GeneratedFilesAlreadyExist.js +38 -0
- package/dist/cjs/src/errors/schema-builder/ColumnNamesDoNotRoundTrip.js +28 -0
- package/dist/cjs/src/helpers/cli/ASTConnectionBuilder.js +62 -10
- package/dist/cjs/src/helpers/cli/generateDream.js +109 -18
- package/dist/cjs/src/helpers/cli/generateMigration.js +12 -8
- package/dist/cjs/src/helpers/cli/writeGeneratedFile.js +3 -3
- package/dist/cjs/src/helpers/db/normalizeDataForDb.js +33 -4
- package/dist/cjs/src/ops/index.js +7 -3
- package/dist/esm/src/Dream.js +156 -13
- package/dist/esm/src/cli/index.js +39 -5
- package/dist/esm/src/db/DreamDbConnection.js +19 -1
- package/dist/esm/src/dream/DreamClassTransactionBuilder.js +34 -2
- package/dist/esm/src/dream/Query.js +178 -14
- package/dist/esm/src/dream/QueryDriver/Base.js +80 -0
- package/dist/esm/src/dream/QueryDriver/Kysely.js +35 -29
- package/dist/esm/src/dream/QueryDriver/Postgres.js +83 -1
- package/dist/esm/src/dream/internal/findOrCreateBy.js +5 -1
- package/dist/esm/src/dream/internal/findOrCreateLookupStatement.js +19 -0
- package/dist/esm/src/dream/internal/updateOrCreateBy.js +5 -1
- package/dist/esm/src/encrypt/algorithms/aes-gcm/decryptAESGCM.js +24 -10
- package/dist/esm/src/errors/CreateOrFindByFailedToCreateAndFind.js +63 -6
- package/dist/esm/src/errors/CreateOrUpdateByFailedToCreateAndUpdate.js +41 -6
- package/dist/esm/src/errors/GeneratedFilesAlreadyExist.js +38 -0
- package/dist/esm/src/errors/schema-builder/ColumnNamesDoNotRoundTrip.js +28 -0
- package/dist/esm/src/helpers/cli/ASTConnectionBuilder.js +62 -10
- package/dist/esm/src/helpers/cli/generateDream.js +109 -18
- package/dist/esm/src/helpers/cli/generateMigration.js +12 -8
- package/dist/esm/src/helpers/cli/writeGeneratedFile.js +3 -3
- package/dist/esm/src/helpers/db/normalizeDataForDb.js +33 -4
- package/dist/esm/src/ops/index.js +7 -3
- package/dist/types/src/Dream.d.ts +109 -6
- package/dist/types/src/bin/index.d.ts +11 -0
- package/dist/types/src/cli/index.d.ts +27 -0
- package/dist/types/src/db/DreamDbConnection.d.ts +15 -1
- package/dist/types/src/dream/DreamClassTransactionBuilder.d.ts +34 -2
- package/dist/types/src/dream/Query.d.ts +101 -6
- package/dist/types/src/dream/QueryDriver/Base.d.ts +59 -0
- package/dist/types/src/dream/QueryDriver/Kysely.d.ts +9 -0
- package/dist/types/src/dream/QueryDriver/Postgres.d.ts +29 -1
- package/dist/types/src/dream/internal/executeDatabaseQuery.d.ts +2 -1
- package/dist/types/src/dream/internal/findOrCreateLookupStatement.d.ts +14 -0
- package/dist/types/src/errors/CreateOrFindByFailedToCreateAndFind.d.ts +40 -1
- package/dist/types/src/errors/CreateOrUpdateByFailedToCreateAndUpdate.d.ts +8 -1
- package/dist/types/src/errors/GeneratedFilesAlreadyExist.d.ts +18 -0
- package/dist/types/src/errors/schema-builder/ColumnNamesDoNotRoundTrip.d.ts +12 -0
- package/dist/types/src/helpers/cli/ASTConnectionBuilder.d.ts +23 -1
- package/dist/types/src/helpers/cli/generateDream.d.ts +25 -0
- package/dist/types/src/helpers/cli/generateMigration.d.ts +20 -2
- package/dist/types/src/helpers/cli/syncWithFailureContext.d.ts +7 -2
- package/dist/types/src/helpers/cli/writeGeneratedFile.d.ts +6 -1
- package/dist/types/src/helpers/db/normalizeDataForDb.d.ts +7 -0
- package/dist/types/src/ops/index.d.ts +7 -3
- package/dist/types/src/types/associations/shared.d.ts +7 -5
- package/dist/types/src/types/types/associations/shared.ts +27 -5
- 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 +87 -50
- package/docs/classes/db.PostgresQueryDriver.html +76 -50
- package/docs/classes/db.QueryDriverBase.html +87 -50
- package/docs/classes/errors.CannotSaveMissingDream.html +2 -2
- package/docs/classes/errors.CheckConstraintViolation.html +3 -3
- package/docs/classes/errors.ColumnOverflow.html +3 -3
- package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +6 -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.SortableScopeDidNotStabilize.html +3 -3
- package/docs/classes/errors.SortableScopeLockWaitTimedOut.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 +204 -134
- package/docs/classes/index.DreamApp.html +13 -13
- package/docs/classes/index.DreamTransaction.html +3 -3
- package/docs/classes/index.Env.html +5 -5
- package/docs/classes/index.Query.html +147 -100
- package/docs/classes/system.CliFileWriter.html +4 -4
- package/docs/classes/system.DreamBin.html +7 -3
- 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 +4 -4
- 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 +3 -3
- 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.DecryptCallbacks.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 +6 -6
- package/docs/interfaces/types.DurationObject.html +2 -2
- package/docs/interfaces/types.EncryptOptions.html +2 -2
- package/docs/interfaces/types.EncryptionLegacyKeyUsedEvent.html +4 -4
- package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
- package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
- package/docs/interfaces/types.IntrospectedColumn.html +7 -7
- package/docs/interfaces/types.IntrospectedDatabase.html +2 -2
- package/docs/interfaces/types.IntrospectedEnum.html +2 -2
- package/docs/interfaces/types.IntrospectedTable.html +5 -5
- 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.OpenapiSchemaCombinatorFields.html +1 -1
- package/docs/types/openapi.OpenapiSchemaCommonFields.html +1 -1
- package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +1 -1
- 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.OpenapiSchemaObjectBase.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.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 +1 -1
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +1 -1
- 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.IntrospectedValueType.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 +8 -4
- 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
- package/dist/cjs/src/helpers/cli/generateFactory.js +0 -10
- package/dist/cjs/src/helpers/cli/generateSerializer.js +0 -18
- package/dist/cjs/src/helpers/cli/generateUnitSpec.js +0 -10
- package/dist/esm/src/helpers/cli/generateFactory.js +0 -10
- package/dist/esm/src/helpers/cli/generateSerializer.js +0 -18
- package/dist/esm/src/helpers/cli/generateUnitSpec.js +0 -10
- package/dist/types/src/helpers/cli/generateFactory.d.ts +0 -5
- package/dist/types/src/helpers/cli/generateSerializer.d.ts +0 -9
- package/dist/types/src/helpers/cli/generateUnitSpec.d.ts +0 -3
|
@@ -7,6 +7,21 @@ import ClockTimeTz from '../../utils/datetime/ClockTimeTz.js';
|
|
|
7
7
|
import { DateTime } from '../../utils/datetime/DateTime.js';
|
|
8
8
|
import normalizeUnicode from '../normalizeUnicode.js';
|
|
9
9
|
import { DATE_OR_DATE_ARRAY_COLUMN_CHECK_REGEXP, DATETIME_OR_DATETIME_ARRAY_COLUMN_CHECK_REGEXP, STRING_OR_STRING_ARRAY_COLUMN_CHECK_REGEXP, TIME_WITH_TIMEZONE_COLUMN_CHECK_REGEXP, TIME_WITHOUT_TIMEZONE_COLUMN_CHECK_REGEXP, } from './types/helpers.js';
|
|
10
|
+
const mismatchesRaisedBeforeQuerying = new WeakSet();
|
|
11
|
+
/**
|
|
12
|
+
* Whether `error` is the `DataTypeColumnTypeMismatch` raised here for a date,
|
|
13
|
+
* datetime or time value Dream cannot convert for its column. Values are
|
|
14
|
+
* converted while a statement is built, so such an error means the statement
|
|
15
|
+
* was never sent to the database.
|
|
16
|
+
*/
|
|
17
|
+
export function isMismatchRaisedBeforeQuerying(error) {
|
|
18
|
+
return error instanceof Error && mismatchesRaisedBeforeQuerying.has(error);
|
|
19
|
+
}
|
|
20
|
+
function mismatchRaisedBeforeQuerying(error) {
|
|
21
|
+
const mismatch = new DataTypeColumnTypeMismatch(error);
|
|
22
|
+
mismatchesRaisedBeforeQuerying.add(mismatch);
|
|
23
|
+
return mismatch;
|
|
24
|
+
}
|
|
10
25
|
export function normalizeDataForDb({ val, dreamClass, column, }) {
|
|
11
26
|
const columnType = cachedTypeForAttribute(dreamClass, column);
|
|
12
27
|
if (val instanceof DateTime || DATETIME_OR_DATETIME_ARRAY_COLUMN_CHECK_REGEXP.test(columnType)) {
|
|
@@ -38,11 +53,18 @@ function normalizedString(_val) {
|
|
|
38
53
|
const normalizedValueArray = (alreadyAnArray ? _val : [_val]).map(val => typeof val === 'string' ? normalizeUnicode(val) : val);
|
|
39
54
|
return alreadyAnArray ? normalizedValueArray : normalizedValueArray[0];
|
|
40
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* A value for a date or timestamp column (or an array of them) may be a single
|
|
58
|
+
* value, an array, or, as the `IN` operand against an array column, a list of
|
|
59
|
+
* whole arrays; every nested array is converted element by element.
|
|
60
|
+
*/
|
|
41
61
|
function dateTimeOrDateToSql(dateClass, _val) {
|
|
42
62
|
const alreadyAnArray = Array.isArray(_val);
|
|
43
|
-
const normalizedValueArray = (alreadyAnArray ? _val : [_val]).map(val => {
|
|
63
|
+
const normalizedValueArray = (alreadyAnArray ? _val : [_val]).map((val) => {
|
|
44
64
|
if (val === null || val === undefined)
|
|
45
65
|
return val;
|
|
66
|
+
if (Array.isArray(val))
|
|
67
|
+
return dateTimeOrDateToSql(dateClass, val);
|
|
46
68
|
if (val instanceof DateTime || val instanceof CalendarDate)
|
|
47
69
|
return val.toSQL();
|
|
48
70
|
try {
|
|
@@ -52,18 +74,25 @@ function dateTimeOrDateToSql(dateClass, _val) {
|
|
|
52
74
|
return dateClass.fromISO(val, { zone: 'UTC' }).toSQL();
|
|
53
75
|
}
|
|
54
76
|
catch (error) {
|
|
55
|
-
throw
|
|
77
|
+
throw mismatchRaisedBeforeQuerying(error instanceof Error
|
|
56
78
|
? error
|
|
57
79
|
: new Error(`database column type error: invalid ${dateClass.name} (received ${typeof val}: ${val})`));
|
|
58
80
|
}
|
|
59
81
|
});
|
|
60
82
|
return alreadyAnArray ? normalizedValueArray : normalizedValueArray[0];
|
|
61
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* A value for a time column (or an array of them) may be a single value, an
|
|
86
|
+
* array, or, as the `IN` operand against an array column, a list of whole
|
|
87
|
+
* arrays; every nested array is converted element by element.
|
|
88
|
+
*/
|
|
62
89
|
function clockTimeOrClockTimeTzToSql(clockTimeClass, _val) {
|
|
63
90
|
const alreadyAnArray = Array.isArray(_val);
|
|
64
|
-
const normalizedValueArray = (alreadyAnArray ? _val : [_val]).map(val => {
|
|
91
|
+
const normalizedValueArray = (alreadyAnArray ? _val : [_val]).map((val) => {
|
|
65
92
|
if (val === null || val === undefined)
|
|
66
93
|
return val;
|
|
94
|
+
if (Array.isArray(val))
|
|
95
|
+
return clockTimeOrClockTimeTzToSql(clockTimeClass, val);
|
|
67
96
|
if (val instanceof BaseClockTime)
|
|
68
97
|
return val.toSQL();
|
|
69
98
|
try {
|
|
@@ -73,7 +102,7 @@ function clockTimeOrClockTimeTzToSql(clockTimeClass, _val) {
|
|
|
73
102
|
return clockTimeClass.fromISO(val, { zone: 'UTC' }).toSQL();
|
|
74
103
|
}
|
|
75
104
|
catch (error) {
|
|
76
|
-
throw
|
|
105
|
+
throw mismatchRaisedBeforeQuerying(error instanceof Error
|
|
77
106
|
? error
|
|
78
107
|
: new Error(`database column type error: invalid ${clockTimeClass.name} (received ${typeof val}: ${val})`));
|
|
79
108
|
}
|
|
@@ -119,9 +119,13 @@ const ops = {
|
|
|
119
119
|
*
|
|
120
120
|
* **Array columns:** passing an array means literal array equality — the
|
|
121
121
|
* whole array is bound as a single parameter, so the column must match
|
|
122
|
-
* element-for-element, in order.
|
|
123
|
-
*
|
|
124
|
-
*
|
|
122
|
+
* element-for-element, in order. The plain value form of a where clause
|
|
123
|
+
* targets regular (scalar) columns, so comparing an array column against an
|
|
124
|
+
* array value goes through `ops`: a flat list against an array column
|
|
125
|
+
* (`Post.where({ tags: ['a', 'b'] })`) is a compile error. A list of whole
|
|
126
|
+
* arrays (`Post.where({ tags: [['a', 'b'], ['c']] })`) means `IN`, matching
|
|
127
|
+
* rows whose column equals any one of them, and `ops.any` tests for
|
|
128
|
+
* containment of a single element.
|
|
125
129
|
* Element types are constrained by the column, so an enum array column
|
|
126
130
|
* rejects a string that is not a member of its enum.
|
|
127
131
|
*
|
package/dist/esm/src/Dream.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import yoctocolors from 'yoctocolors';
|
|
2
|
-
import {
|
|
2
|
+
import { STI_SCOPE_NAME } from './decorators/class/STI.js';
|
|
3
3
|
import associationToGetterSetterProp from './decorators/field/association/associationToGetterSetterProp.js';
|
|
4
4
|
import { blankAssociationsFactory } from './decorators/field/association/shared.js';
|
|
5
5
|
import { blankHooksFactory } from './decorators/field/lifecycle/shared.js';
|
|
@@ -20,6 +20,7 @@ import destroyDream from './dream/internal/destroyDream.js';
|
|
|
20
20
|
import { destroyOptions, reallyDestroyOptions, undestroyOptions } from './dream/internal/destroyOptions.js';
|
|
21
21
|
import ensureSTITypeFieldIsSet from './dream/internal/ensureSTITypeFieldIsSet.js';
|
|
22
22
|
import findOrCreateBy from './dream/internal/findOrCreateBy.js';
|
|
23
|
+
import findOrCreateLookupStatement from './dream/internal/findOrCreateLookupStatement.js';
|
|
23
24
|
import mergeSerializerAssociationEdges from './dream/internal/mergeSerializerAssociationEdges.js';
|
|
24
25
|
import printSerializerHierarchyLevel from './dream/internal/printSerializerHierarchyLevel.js';
|
|
25
26
|
import reload from './dream/internal/reload.js';
|
|
@@ -48,6 +49,7 @@ import DreamMissingRequiredOverride from './errors/DreamMissingRequiredOverride.
|
|
|
48
49
|
import NonExistentScopeProvidedToResort from './errors/NonExistentScopeProvidedToResort.js';
|
|
49
50
|
import RecordNotFound from './errors/RecordNotFound.js';
|
|
50
51
|
import areEqual from './helpers/areEqual.js';
|
|
52
|
+
import camelize from './helpers/camelize.js';
|
|
51
53
|
import cloneDeepSafe from './helpers/cloneDeepSafe.js';
|
|
52
54
|
import isJsonColumn from './helpers/db/types/isJsonColumn.js';
|
|
53
55
|
import notEqual from './helpers/notEqual.js';
|
|
@@ -1266,6 +1268,18 @@ export default class Dream {
|
|
|
1266
1268
|
* IMPORTANT: A unique index/uniqueness constraint must exist on
|
|
1267
1269
|
* at least one of the provided attributes
|
|
1268
1270
|
*
|
|
1271
|
+
* The lookup applies the model's default scopes, so a conflicting
|
|
1272
|
+
* record they hide (for example, a soft-deleted one under a plain
|
|
1273
|
+
* unique index) is never returned. When the lookup finds nothing,
|
|
1274
|
+
* `CreateOrFindByFailedToCreateAndFind` is thrown. When the database
|
|
1275
|
+
* identifies the violated unique index (Postgres does, unless it is an
|
|
1276
|
+
* expression or partial index), its message names the `createWith`
|
|
1277
|
+
* attributes that index covers as the likely cause, unless the index
|
|
1278
|
+
* also covers every find attribute. When the index covers only find
|
|
1279
|
+
* attributes, or every find attribute, and the record is found with the
|
|
1280
|
+
* model's default scopes removed, the message names the default scope
|
|
1281
|
+
* likely hiding it. Otherwise it lists the possible causes.
|
|
1282
|
+
*
|
|
1269
1283
|
* ```ts
|
|
1270
1284
|
* const logEntry = await LogEntry.createOrFindBy({ externalId }, { createWith: params })
|
|
1271
1285
|
* ```
|
|
@@ -1287,10 +1301,12 @@ export default class Dream {
|
|
|
1287
1301
|
return dreamModel;
|
|
1288
1302
|
}
|
|
1289
1303
|
catch (err) {
|
|
1290
|
-
if (
|
|
1291
|
-
const
|
|
1304
|
+
if (Query.dbDriverClass(this.prototype.connectionName).isUniqueConstraintViolation(err)) {
|
|
1305
|
+
const lookup = findOrCreateLookupStatement(this, attributes);
|
|
1306
|
+
// where().first() rather than findBy: a value the database rejects is raised, never read as no match
|
|
1307
|
+
const dreamModel = await this.where(lookup).first();
|
|
1292
1308
|
if (!dreamModel)
|
|
1293
|
-
throw new CreateOrFindByFailedToCreateAndFind(this);
|
|
1309
|
+
throw new CreateOrFindByFailedToCreateAndFind(this, await this.createOrFindFailureCause(err, lookup, extraOpts.createWith));
|
|
1294
1310
|
return dreamModel;
|
|
1295
1311
|
}
|
|
1296
1312
|
throw err;
|
|
@@ -1326,6 +1342,18 @@ export default class Dream {
|
|
|
1326
1342
|
* IMPORTANT: A unique index/uniqueness constraint must exist on
|
|
1327
1343
|
* at least one of the provided attributes
|
|
1328
1344
|
*
|
|
1345
|
+
* The lookup applies the model's default scopes, so a conflicting
|
|
1346
|
+
* record they hide (for example, a soft-deleted one under a plain
|
|
1347
|
+
* unique index) is never updated. When the lookup finds nothing,
|
|
1348
|
+
* `CreateOrUpdateByFailedToCreateAndUpdate` is thrown. When the database
|
|
1349
|
+
* identifies the violated unique index (Postgres does, unless it is an
|
|
1350
|
+
* expression or partial index), its message names the `with`
|
|
1351
|
+
* attributes that index covers as the likely cause, unless the index
|
|
1352
|
+
* also covers every find attribute. When the index covers only find
|
|
1353
|
+
* attributes, or every find attribute, and the record is found with the
|
|
1354
|
+
* model's default scopes removed, the message names the default scope
|
|
1355
|
+
* likely hiding it. Otherwise it lists the possible causes.
|
|
1356
|
+
*
|
|
1329
1357
|
* @param attributes - The base attributes for finding which record to update, also used when creating
|
|
1330
1358
|
* @param extraOpts - Additional options
|
|
1331
1359
|
* @param extraOpts.with - additional attributes to persist when updating and creating, but not used for finding
|
|
@@ -1341,10 +1369,12 @@ export default class Dream {
|
|
|
1341
1369
|
}, skipHooks ? { skipHooks } : undefined);
|
|
1342
1370
|
}
|
|
1343
1371
|
catch (err) {
|
|
1344
|
-
if (
|
|
1345
|
-
const
|
|
1372
|
+
if (Query.dbDriverClass(this.prototype.connectionName).isUniqueConstraintViolation(err)) {
|
|
1373
|
+
const lookup = findOrCreateLookupStatement(this, attributes);
|
|
1374
|
+
// where().first() rather than findBy: a value the database rejects is raised, never read as no match
|
|
1375
|
+
const existingRecord = await this.where(lookup).first();
|
|
1346
1376
|
if (!existingRecord)
|
|
1347
|
-
throw new CreateOrUpdateByFailedToCreateAndUpdate(this);
|
|
1377
|
+
throw new CreateOrUpdateByFailedToCreateAndUpdate(this, await this.createOrFindFailureCause(err, lookup, extraOpts.with));
|
|
1348
1378
|
const { with: attrs } = extraOpts;
|
|
1349
1379
|
if (existingRecord) {
|
|
1350
1380
|
existingRecord.assignAttributes(attrs ?? {});
|
|
@@ -1354,6 +1384,64 @@ export default class Dream {
|
|
|
1354
1384
|
throw err;
|
|
1355
1385
|
}
|
|
1356
1386
|
}
|
|
1387
|
+
/**
|
|
1388
|
+
* @internal
|
|
1389
|
+
*
|
|
1390
|
+
* Used by createOrFindBy and createOrUpdateBy after their default-scoped
|
|
1391
|
+
* lookup finds nothing, to tell why. The driver names the columns of the
|
|
1392
|
+
* unique index the insert violated, when it can. When that index covers a
|
|
1393
|
+
* `createWith`/`with` attribute but not every find attribute, one of those
|
|
1394
|
+
* values likely conflicted with another record. When it covers only find
|
|
1395
|
+
* attributes, or every find attribute, a record matching them that the
|
|
1396
|
+
* default scopes hide is the record the insert conflicted with. Otherwise
|
|
1397
|
+
* the cause is undetermined, and a hidden record matching the find
|
|
1398
|
+
* attributes is only a candidate.
|
|
1399
|
+
*/
|
|
1400
|
+
static async createOrFindFailureCause(error, lookup, extraAttributes) {
|
|
1401
|
+
const connectionName = this.prototype.connectionName;
|
|
1402
|
+
const indexColumns = await Query.dbDriverClass(connectionName).violatedUniqueIndexColumns(connectionName, error, this.table);
|
|
1403
|
+
const indexAttributes = indexColumns?.map(column => camelize(column)) ?? null;
|
|
1404
|
+
const extraAttributeNames = Object.keys(extraAttributes ? this.extractAttributesFromUpdateableProperties(extraAttributes) : {});
|
|
1405
|
+
// the conflicting record holds the inserted value in every column of the index, so when the
|
|
1406
|
+
// index covers every find attribute, that record matches them and is not a different record
|
|
1407
|
+
const indexCoversEveryFindAttribute = !!indexAttributes && Object.keys(lookup).every(attribute => indexAttributes.includes(attribute));
|
|
1408
|
+
if (!indexCoversEveryFindAttribute) {
|
|
1409
|
+
const conflictingAttributes = (indexAttributes ?? []).filter(attribute => extraAttributeNames.includes(attribute));
|
|
1410
|
+
if (conflictingAttributes.length)
|
|
1411
|
+
return { kind: 'conflictingAttributes', attributes: conflictingAttributes };
|
|
1412
|
+
}
|
|
1413
|
+
const hidingDefaultScopes = await this.defaultScopesHidingLookup(lookup);
|
|
1414
|
+
const indexCoversOnlyFindAttributes = !!indexAttributes?.every(attribute => attribute in lookup);
|
|
1415
|
+
if (hidingDefaultScopes.length && (indexCoversOnlyFindAttributes || indexCoversEveryFindAttribute))
|
|
1416
|
+
return { kind: 'hiddenByDefaultScope', defaultScopes: hidingDefaultScopes };
|
|
1417
|
+
return {
|
|
1418
|
+
kind: 'undetermined',
|
|
1419
|
+
hidingDefaultScopes,
|
|
1420
|
+
extraAttributesMayConflict: indexAttributes === null && extraAttributeNames.length > 0,
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* @internal
|
|
1425
|
+
*
|
|
1426
|
+
* Used by createOrFindFailureCause. Returns the model's removable default scopes when a
|
|
1427
|
+
* record matching `lookup` exists once they are all removed, and an empty
|
|
1428
|
+
* array otherwise. The STI type discriminator is never removed. One probe
|
|
1429
|
+
* with every removable scope removed cannot tell which of several scopes
|
|
1430
|
+
* hid the record, so all of them are returned as candidates.
|
|
1431
|
+
*/
|
|
1432
|
+
static async defaultScopesHidingLookup(lookup) {
|
|
1433
|
+
const removableDefaultScopes = this['scopes'].default
|
|
1434
|
+
.map(scope => scope.method)
|
|
1435
|
+
.filter(scopeName => scopeName !== STI_SCOPE_NAME);
|
|
1436
|
+
if (!removableDefaultScopes.length)
|
|
1437
|
+
return [];
|
|
1438
|
+
let query = this.query();
|
|
1439
|
+
for (const scopeName of removableDefaultScopes) {
|
|
1440
|
+
query = query.removeDefaultScope(scopeName);
|
|
1441
|
+
}
|
|
1442
|
+
// where() rather than findBy: a value the database rejects is raised, never read as no match
|
|
1443
|
+
return (await query.where(lookup).exists()) ? removableDefaultScopes : [];
|
|
1444
|
+
}
|
|
1357
1445
|
/**
|
|
1358
1446
|
* Returns a new query instance with the distinct query applied.
|
|
1359
1447
|
* If no columnName is provided, then distinct will apply to the
|
|
@@ -1371,12 +1459,25 @@ export default class Dream {
|
|
|
1371
1459
|
}
|
|
1372
1460
|
/**
|
|
1373
1461
|
* Finds a record for the corresponding model with the
|
|
1374
|
-
* specified primary key. Returns null if not found
|
|
1462
|
+
* specified primary key. Returns null if not found,
|
|
1463
|
+
* including when the primary key is a value its column
|
|
1464
|
+
* cannot hold (for example, `'abc'` for an integer
|
|
1465
|
+
* primary key).
|
|
1466
|
+
*
|
|
1467
|
+
* When the database rejects a value elsewhere in the query (for
|
|
1468
|
+
* example, in a `whereNot`, `whereAny` or `leftJoinPreload`
|
|
1469
|
+
* clause), that rejection is raised as `DataTypeColumnTypeMismatch`
|
|
1470
|
+
* or `ColumnOverflow`, since a record may still match. Inside a
|
|
1471
|
+
* transaction, a primary key the database rejects is raised that
|
|
1472
|
+
* way too, because the database has then aborted the transaction.
|
|
1375
1473
|
*
|
|
1376
1474
|
* ```ts
|
|
1377
1475
|
* await User.find(123)
|
|
1378
1476
|
* // User{id: 123}
|
|
1379
1477
|
*
|
|
1478
|
+
* await User.find('abc')
|
|
1479
|
+
* // null
|
|
1480
|
+
*
|
|
1380
1481
|
* await User.find(null)
|
|
1381
1482
|
* // null
|
|
1382
1483
|
*
|
|
@@ -1392,15 +1493,26 @@ export default class Dream {
|
|
|
1392
1493
|
}
|
|
1393
1494
|
/**
|
|
1394
1495
|
* Finds a record for the corresponding model with the
|
|
1395
|
-
* specified primary key. If not found,
|
|
1496
|
+
* specified primary key. If not found, `RecordNotFound` is
|
|
1497
|
+
* raised, including when the primary key is a value its
|
|
1498
|
+
* column cannot hold (for example, `'abc'` for an integer
|
|
1499
|
+
* primary key).
|
|
1500
|
+
*
|
|
1501
|
+
* When the database rejects a value elsewhere in the query (for
|
|
1502
|
+
* example, in a `whereNot`, `whereAny` or `leftJoinPreload`
|
|
1503
|
+
* clause), that rejection is raised as `DataTypeColumnTypeMismatch`
|
|
1504
|
+
* or `ColumnOverflow`, since a record may still match. Inside a
|
|
1505
|
+
* transaction, a primary key the database rejects is raised that
|
|
1506
|
+
* way too, because the database has then aborted the transaction.
|
|
1396
1507
|
*
|
|
1397
1508
|
* ```ts
|
|
1398
|
-
* await User.
|
|
1509
|
+
* await User.findOrFail(123)
|
|
1399
1510
|
* // User{id: 123}
|
|
1400
1511
|
* ```
|
|
1401
1512
|
*
|
|
1402
1513
|
* @param primaryKey - The primaryKey of the record to look up
|
|
1403
|
-
* @returns
|
|
1514
|
+
* @returns The found record
|
|
1515
|
+
* @throws RecordNotFound if no record is found
|
|
1404
1516
|
*/
|
|
1405
1517
|
static async findOrFail(primaryKey) {
|
|
1406
1518
|
return await this.query().findOrFail(primaryKey);
|
|
@@ -1540,7 +1652,22 @@ export default class Dream {
|
|
|
1540
1652
|
/**
|
|
1541
1653
|
* Finds the first record—ordered by primary key—matching
|
|
1542
1654
|
* the corresponding model and the specified where statement.
|
|
1543
|
-
* If not found, null is returned
|
|
1655
|
+
* If not found, null is returned, including when a value the where
|
|
1656
|
+
* statement compares for equality (a plain value or
|
|
1657
|
+
* `ops.equal`) is one its column cannot hold: for example,
|
|
1658
|
+
* `'abc'` for a uuid column, or a date string Dream cannot
|
|
1659
|
+
* parse for a date column.
|
|
1660
|
+
*
|
|
1661
|
+
* A value the database rejects anywhere else (in a list of values,
|
|
1662
|
+
* under another operator such as `ops.not.equal` or `ops.lessThan`,
|
|
1663
|
+
* or elsewhere in the query, such as a `whereNot` or `whereAny`
|
|
1664
|
+
* clause) is raised as `DataTypeColumnTypeMismatch` or
|
|
1665
|
+
* `ColumnOverflow`, since a record may still match; so is a date,
|
|
1666
|
+
* datetime or time string there that Dream cannot parse. Inside a
|
|
1667
|
+
* transaction, an equality value the database rejects is raised that
|
|
1668
|
+
* way too, because the database has then aborted the transaction; a
|
|
1669
|
+
* date, datetime or time string Dream cannot parse never reaches the
|
|
1670
|
+
* database, so it is treated as not found there as well.
|
|
1544
1671
|
*
|
|
1545
1672
|
* ```ts
|
|
1546
1673
|
* await User.findBy({ email: 'how@yadoin' })
|
|
@@ -1556,7 +1683,22 @@ export default class Dream {
|
|
|
1556
1683
|
/**
|
|
1557
1684
|
* Finds the first record—ordered by primary key—matching
|
|
1558
1685
|
* the corresponding model and the specified where statement.
|
|
1559
|
-
* If not found,
|
|
1686
|
+
* If not found, `RecordNotFound` is raised, including when
|
|
1687
|
+
* a value the where statement compares for equality (a plain
|
|
1688
|
+
* value or `ops.equal`) is one its column cannot hold: for
|
|
1689
|
+
* example, `'abc'` for a uuid column, or a date string Dream
|
|
1690
|
+
* cannot parse for a date column.
|
|
1691
|
+
*
|
|
1692
|
+
* A value the database rejects anywhere else (in a list of values,
|
|
1693
|
+
* under another operator such as `ops.not.equal` or `ops.lessThan`,
|
|
1694
|
+
* or elsewhere in the query, such as a `whereNot` or `whereAny`
|
|
1695
|
+
* clause) is raised as `DataTypeColumnTypeMismatch` or
|
|
1696
|
+
* `ColumnOverflow`, since a record may still match; so is a date,
|
|
1697
|
+
* datetime or time string there that Dream cannot parse. Inside a
|
|
1698
|
+
* transaction, an equality value the database rejects is raised that
|
|
1699
|
+
* way too, because the database has then aborted the transaction; a
|
|
1700
|
+
* date, datetime or time string Dream cannot parse never reaches the
|
|
1701
|
+
* database, so it is treated as not found there as well.
|
|
1560
1702
|
*
|
|
1561
1703
|
* ```ts
|
|
1562
1704
|
* await User.findOrFailBy({ email: 'how@yadoin' })
|
|
@@ -1565,6 +1707,7 @@ export default class Dream {
|
|
|
1565
1707
|
*
|
|
1566
1708
|
* @param whereStatement - The where statement used to locate the record
|
|
1567
1709
|
* @returns The first model found matching the whereStatement
|
|
1710
|
+
* @throws RecordNotFound if no record is found
|
|
1568
1711
|
*/
|
|
1569
1712
|
static async findOrFailBy(whereStatement) {
|
|
1570
1713
|
return await this.query().findOrFailBy(whereStatement);
|
|
@@ -252,6 +252,10 @@ ${INDENT} # model is named GroupDanceLesson instead of LessonDanceGroup`)
|
|
|
252
252
|
.option('--admin-serializers', 'also generate AdminSerializer and AdminSummarySerializer variants for admin-facing API endpoints that may expose additional fields', false)
|
|
253
253
|
.option('--internal-serializers', 'also generate InternalSerializer and InternalSummarySerializer variants for internal API endpoints that may expose additional fields', false)
|
|
254
254
|
.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.`)
|
|
255
|
+
.option('--overwrite', 'replace the model, spec, factory and serializer files when they already exist. Without it or --skip-existing, the generator writes nothing and lists the existing files when any of them, or the migration it would write, already exists. An existing migration is kept even with --overwrite, never rewritten or duplicated, since it may already have run; edit it by hand if the columns changed', false)
|
|
256
|
+
.addOption(new Option('--skip-existing', 'keep the model, spec, factory, serializer and migration files that already exist, write only the missing ones, and list each file kept. No migration is written when the model file already exists, even if no migration has the name the generator would use, since the model may already have one under another name. Cannot be combined with --overwrite')
|
|
257
|
+
.default(false)
|
|
258
|
+
.conflicts('overwrite'))
|
|
255
259
|
.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/.
|
|
256
260
|
${INDENT}
|
|
257
261
|
${INDENT}Examples:
|
|
@@ -261,7 +265,11 @@ ${INDENT} Settings/CommunicationPreferences # src/app/models/Settings/Communi
|
|
|
261
265
|
.argument('[columnsWithTypes...]', columnsWithTypesDescription)
|
|
262
266
|
.action(async (modelName, columnsWithTypes, options) => {
|
|
263
267
|
await initializeDreamApp({ bypassDreamIntegrityChecks: true, bypassDbConnectionsDuringInit: true });
|
|
264
|
-
|
|
268
|
+
const { overwrite, skipExisting, ...generatorOptions } = options;
|
|
269
|
+
await DreamBin.generateDream(modelName, columnsWithTypes, {
|
|
270
|
+
...generatorOptions,
|
|
271
|
+
existingFiles: existingFilesMode({ overwrite, skipExisting }),
|
|
272
|
+
});
|
|
265
273
|
process.exit();
|
|
266
274
|
});
|
|
267
275
|
program
|
|
@@ -290,6 +298,10 @@ ${INDENT} pnpm psy g:sti-child --model-name=GroupDanceLesson Lesson/Dance/Group
|
|
|
290
298
|
${INDENT} # model is named GroupDanceLesson instead of LessonDanceGroup`)
|
|
291
299
|
.option('--admin-serializers', 'also generate AdminSerializer and AdminSummarySerializer variants for admin-facing API endpoints that may expose additional fields', false)
|
|
292
300
|
.option('--internal-serializers', 'also generate InternalSerializer and InternalSummarySerializer variants for internal API endpoints that may expose additional fields', false)
|
|
301
|
+
.option('--overwrite', 'replace the model, spec, factory and serializer files when they already exist. Without it or --skip-existing, the generator writes nothing and lists the existing files when any of them, or the migration it would write, already exists. An existing migration is kept even with --overwrite, never rewritten or duplicated, since it may already have run; edit it by hand if the columns changed', false)
|
|
302
|
+
.addOption(new Option('--skip-existing', 'keep the model, spec, factory, serializer and migration files that already exist, write only the missing ones, and list each file kept. No migration is written when the model file already exists, even if no migration has the name the generator would use, since the model may already have one under another name. Cannot be combined with --overwrite')
|
|
303
|
+
.default(false)
|
|
304
|
+
.conflicts('overwrite'))
|
|
293
305
|
.argument('<childModelName>', `The namespaced child model path. By convention, children are nested under the parent name.
|
|
294
306
|
${INDENT}
|
|
295
307
|
${INDENT}Examples:
|
|
@@ -307,7 +319,11 @@ ${INDENT} Health/Coach # extends src/app/models/Health/Coach.ts`)
|
|
|
307
319
|
await initializeDreamApp({ bypassDreamIntegrityChecks: true, bypassDbConnectionsDuringInit: true });
|
|
308
320
|
if (extendsWord !== 'extends')
|
|
309
321
|
throw new Error('Expecting: `<child-name> extends <parent-name> <columns-and-types>');
|
|
310
|
-
|
|
322
|
+
const { overwrite, skipExisting, ...generatorOptions } = options;
|
|
323
|
+
await DreamBin.generateStiChild(childModelName, parentModelName, columnsWithTypes, {
|
|
324
|
+
...generatorOptions,
|
|
325
|
+
existingFiles: existingFilesMode({ overwrite, skipExisting }),
|
|
326
|
+
});
|
|
311
327
|
process.exit();
|
|
312
328
|
});
|
|
313
329
|
program
|
|
@@ -364,7 +380,12 @@ ${INDENT} pnpm psy db:migrate`)
|
|
|
364
380
|
await syncWithFailureContext({
|
|
365
381
|
commandName: 'db:migrate',
|
|
366
382
|
completedWork: 'Every pending migration ran successfully',
|
|
367
|
-
|
|
383
|
+
// never `sync` on its own here: the failed sync reverted the
|
|
384
|
+
// generated types to their pre-migration content, and a Psychic
|
|
385
|
+
// app's `psy sync` boots with the integrity checks on, so it
|
|
386
|
+
// fails against them as soon as a model or serializer uses what
|
|
387
|
+
// the migration added. db:migrate boots with them bypassed
|
|
388
|
+
recoveryAdvice: 're-run `db:migrate` — the migrations it already applied are not re-applied, so it goes straight back to the sync',
|
|
368
389
|
}, async () => await DreamBin.sync(onSync));
|
|
369
390
|
}
|
|
370
391
|
process.exit();
|
|
@@ -444,8 +465,10 @@ ${INDENT}Warning: all existing data will be lost. The seed file (db/seed.ts) wil
|
|
|
444
465
|
// hook directly (below), while the `db:seed` *command* refuses to
|
|
445
466
|
// seed under NODE_ENV=test unless DREAM_SEED_DB_IN_TEST=1 — and
|
|
446
467
|
// NODE_ENV=test is the only environment DreamBin.sync does any
|
|
447
|
-
// work in, so it is the only environment this advice is printed in
|
|
448
|
-
|
|
468
|
+
// work in, so it is the only environment this advice is printed in.
|
|
469
|
+
// The second route re-runs db:migrate rather than `sync` for the
|
|
470
|
+
// reason given at db:migrate's own recovery advice
|
|
471
|
+
recoveryAdvice: 're-run `db:reset`, which seeds directly and so picks up the skipped seeding step, or re-run `db:migrate` followed by `DREAM_SEED_DB_IN_TEST=1 db:seed` — this sync only runs under `NODE_ENV=test`, and a bare `db:seed` does not seed there',
|
|
449
472
|
}, async () => await DreamBin.sync(onSync));
|
|
450
473
|
DreamCLI.logger.log(arrows, { logPrefix: ' ' });
|
|
451
474
|
DreamCLI.logger.log(colorize('db:seed', { color: 'green' }), {
|
|
@@ -577,6 +600,17 @@ ${INDENT}Examples: User, Place, Room/Bedroom, Settings/CommunicationPreferences`
|
|
|
577
600
|
}
|
|
578
601
|
static _logger = undefined;
|
|
579
602
|
}
|
|
603
|
+
/**
|
|
604
|
+
* commander rejects --overwrite together with --skip-existing before an
|
|
605
|
+
* action runs, so at most one of them is set here.
|
|
606
|
+
*/
|
|
607
|
+
function existingFilesMode({ overwrite, skipExisting, }) {
|
|
608
|
+
if (overwrite)
|
|
609
|
+
return 'overwrite';
|
|
610
|
+
if (skipExisting)
|
|
611
|
+
return 'skip';
|
|
612
|
+
return 'refuse';
|
|
613
|
+
}
|
|
580
614
|
function myParseInt(value) {
|
|
581
615
|
const parsedValue = parseInt(value, 10);
|
|
582
616
|
if (isNaN(parsedValue)) {
|
|
@@ -11,6 +11,24 @@ import DreamApp from '../dream-app/index.js';
|
|
|
11
11
|
import { claimedTestDatabaseIndexOrNull } from './testDatabasePool.js';
|
|
12
12
|
import protectAgainstPollutingAssignment from '../helpers/protectAgainstPollutingAssignment.js';
|
|
13
13
|
import { installDbConnectionLeakDiagnosticsIfEnabled, reportLeakedDbConnections, } from './dbConnectionLeakDiagnostics.js';
|
|
14
|
+
/**
|
|
15
|
+
* @internal
|
|
16
|
+
*
|
|
17
|
+
* the CamelCasePlugin every Dream connection carries: it turns each
|
|
18
|
+
* attribute name a query names into the column name it queries, and each
|
|
19
|
+
* column name a result row carries into a camelized key
|
|
20
|
+
*/
|
|
21
|
+
export class DreamCamelCasePlugin extends CamelCasePlugin {
|
|
22
|
+
constructor() {
|
|
23
|
+
super({ underscoreBetweenUppercaseLetters: true });
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* the column name a query naming the given attribute targets
|
|
27
|
+
*/
|
|
28
|
+
columnNameForAttribute(attributeName) {
|
|
29
|
+
return this.snakeCase(attributeName);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
14
32
|
let connections = {};
|
|
15
33
|
export default class DreamDbConnection {
|
|
16
34
|
static getConnection(connectionName, connectionType, dialectProvider) {
|
|
@@ -32,7 +50,7 @@ export default class DreamDbConnection {
|
|
|
32
50
|
});
|
|
33
51
|
},
|
|
34
52
|
dialect: dialectProvider(connectionConf),
|
|
35
|
-
plugins: [new
|
|
53
|
+
plugins: [new DreamCamelCasePlugin()],
|
|
36
54
|
});
|
|
37
55
|
const protectedName = protectAgainstPollutingAssignment(connectionName);
|
|
38
56
|
connections[protectedName] ||= {};
|
|
@@ -294,6 +294,12 @@ export default class DreamClassTransactionBuilder {
|
|
|
294
294
|
* Finds a record for the corresponding model with the
|
|
295
295
|
* specified primary key. If not found, null is returned.
|
|
296
296
|
*
|
|
297
|
+
* A value the database rejects as one its column cannot hold,
|
|
298
|
+
* whether the primary key (for example, `'abc'` for an integer
|
|
299
|
+
* primary key) or a value elsewhere in the query, raises
|
|
300
|
+
* `DataTypeColumnTypeMismatch` or `ColumnOverflow`, because the
|
|
301
|
+
* database has then aborted the transaction.
|
|
302
|
+
*
|
|
297
303
|
* ```ts
|
|
298
304
|
* await ApplicationModel.transaction(async txn => {
|
|
299
305
|
* await User.txn(txn).find(123)
|
|
@@ -317,6 +323,12 @@ export default class DreamClassTransactionBuilder {
|
|
|
317
323
|
* Finds a record for the corresponding model with the
|
|
318
324
|
* specified primary key. If not found, an exception is raised.
|
|
319
325
|
*
|
|
326
|
+
* A value the database rejects as one its column cannot hold,
|
|
327
|
+
* whether the primary key (for example, `'abc'` for an integer
|
|
328
|
+
* primary key) or a value elsewhere in the query, raises
|
|
329
|
+
* `DataTypeColumnTypeMismatch` or `ColumnOverflow`, because the
|
|
330
|
+
* database has then aborted the transaction.
|
|
331
|
+
*
|
|
320
332
|
* ```ts
|
|
321
333
|
* await ApplicationModel.transaction(async txn => {
|
|
322
334
|
* await User.txn(txn).findOrFail(123)
|
|
@@ -333,7 +345,17 @@ export default class DreamClassTransactionBuilder {
|
|
|
333
345
|
/**
|
|
334
346
|
* Finds the first record—ordered by primary key—matching
|
|
335
347
|
* the corresponding model and the specified where statement.
|
|
336
|
-
* If not found, null is returned
|
|
348
|
+
* If not found, null is returned, including when a value the where
|
|
349
|
+
* statement compares for equality (a plain value or `ops.equal`)
|
|
350
|
+
* is a date, datetime or time string Dream cannot parse for its
|
|
351
|
+
* column, which never reaches the database.
|
|
352
|
+
*
|
|
353
|
+
* A value the database rejects as one its column cannot hold
|
|
354
|
+
* (for example, `'abc'` for a uuid column) raises
|
|
355
|
+
* `DataTypeColumnTypeMismatch` or `ColumnOverflow`, because the
|
|
356
|
+
* database has then aborted the transaction. A date, datetime or
|
|
357
|
+
* time string Dream cannot parse anywhere else in the query raises
|
|
358
|
+
* `DataTypeColumnTypeMismatch`.
|
|
337
359
|
*
|
|
338
360
|
* ```ts
|
|
339
361
|
* await ApplicationModel.transaction(async txn => {
|
|
@@ -351,7 +373,17 @@ export default class DreamClassTransactionBuilder {
|
|
|
351
373
|
/**
|
|
352
374
|
* Finds the first record—ordered by primary key—matching
|
|
353
375
|
* the corresponding model and the specified where statement.
|
|
354
|
-
* If not found,
|
|
376
|
+
* If not found, `RecordNotFound` is raised, including when a
|
|
377
|
+
* value the where statement compares for equality (a plain value
|
|
378
|
+
* or `ops.equal`) is a date, datetime or time string Dream cannot
|
|
379
|
+
* parse for its column, which never reaches the database.
|
|
380
|
+
*
|
|
381
|
+
* A value the database rejects as one its column cannot hold
|
|
382
|
+
* (for example, `'abc'` for a uuid column) raises
|
|
383
|
+
* `DataTypeColumnTypeMismatch` or `ColumnOverflow`, because the
|
|
384
|
+
* database has then aborted the transaction. A date, datetime or
|
|
385
|
+
* time string Dream cannot parse anywhere else in the query raises
|
|
386
|
+
* `DataTypeColumnTypeMismatch`.
|
|
355
387
|
*
|
|
356
388
|
* ```ts
|
|
357
389
|
* await ApplicationModel.transaction(async txn => {
|