@rvoh/dream 2.35.0 → 2.36.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cjs/src/Dream.js +260 -55
- package/dist/cjs/src/bin/index.js +13 -0
- package/dist/cjs/src/cli/index.js +28 -17
- package/dist/cjs/src/db/migration-helpers/DreamMigrationHelpers.js +23 -21
- package/dist/cjs/src/decorators/Decorators.js +26 -4
- package/dist/cjs/src/decorators/field/Encrypted.js +35 -6
- package/dist/cjs/src/dream/DreamClassTransactionBuilder.js +6 -6
- package/dist/cjs/src/dream/DreamInstanceTransactionBuilder.js +63 -27
- package/dist/cjs/src/dream/LeftJoinLoadBuilder.js +3 -3
- package/dist/cjs/src/dream/LoadBuilder.js +1 -1
- package/dist/cjs/src/dream/Query.js +542 -167
- package/dist/cjs/src/dream/QueryDriver/Base.js +72 -0
- package/dist/cjs/src/dream/QueryDriver/Kysely.js +216 -50
- package/dist/cjs/src/dream/internal/associations/assertRecognizedAssociationConditionKeys.js +48 -0
- package/dist/cjs/src/dream/internal/associations/associationQuery.js +4 -0
- package/dist/cjs/src/dream/internal/reload.js +6 -1
- package/dist/cjs/src/dream/internal/saveDream.js +8 -2
- package/dist/cjs/src/dream/internal/sqlResultToDreamInstance.js +2 -0
- package/dist/cjs/src/dream/internal/undestroyDream.js +1 -1
- package/dist/cjs/src/dream-app/index.js +38 -0
- package/dist/cjs/src/encrypt/InternalEncrypt.js +77 -4
- package/dist/cjs/src/encrypt/defaultEncryptedColumnName.js +10 -0
- package/dist/cjs/src/encrypt/index.js +76 -9
- package/dist/cjs/src/errors/DoNotSetEncryptedFieldsDirectly.js +4 -0
- package/dist/cjs/src/errors/associations/UnrecognizedAssociationConditionKeys.js +25 -0
- package/dist/cjs/src/helpers/Env.js +28 -0
- package/dist/cjs/src/helpers/cli/generateDreamContent.js +2 -1
- package/dist/cjs/src/helpers/cli/generateFactoryContent.js +1 -1
- package/dist/cjs/src/helpers/cli/generateMigrationContent.js +9 -9
- package/dist/cjs/src/helpers/cli/generateSerializerContent.js +21 -8
- package/dist/cjs/src/serializer/SerializerRenderer.js +65 -13
- package/dist/cjs/src/serializer/builders/DreamSerializerBuilder.js +54 -38
- package/dist/cjs/src/serializer/builders/ObjectSerializerBuilder.js +34 -14
- package/dist/cjs/src/serializer/helpers/serializersForAssociatedClass.js +43 -0
- package/dist/esm/src/Dream.js +260 -55
- package/dist/esm/src/bin/index.js +13 -0
- package/dist/esm/src/cli/index.js +28 -17
- package/dist/esm/src/db/migration-helpers/DreamMigrationHelpers.js +23 -21
- package/dist/esm/src/decorators/Decorators.js +26 -4
- package/dist/esm/src/decorators/field/Encrypted.js +35 -6
- package/dist/esm/src/dream/DreamClassTransactionBuilder.js +6 -6
- package/dist/esm/src/dream/DreamInstanceTransactionBuilder.js +63 -27
- package/dist/esm/src/dream/LeftJoinLoadBuilder.js +3 -3
- package/dist/esm/src/dream/LoadBuilder.js +1 -1
- package/dist/esm/src/dream/Query.js +542 -167
- package/dist/esm/src/dream/QueryDriver/Base.js +72 -0
- package/dist/esm/src/dream/QueryDriver/Kysely.js +216 -50
- package/dist/esm/src/dream/internal/associations/assertRecognizedAssociationConditionKeys.js +48 -0
- package/dist/esm/src/dream/internal/associations/associationQuery.js +4 -0
- package/dist/esm/src/dream/internal/reload.js +6 -1
- package/dist/esm/src/dream/internal/saveDream.js +8 -2
- package/dist/esm/src/dream/internal/sqlResultToDreamInstance.js +2 -0
- package/dist/esm/src/dream/internal/undestroyDream.js +1 -1
- package/dist/esm/src/dream-app/index.js +38 -0
- package/dist/esm/src/encrypt/InternalEncrypt.js +77 -4
- package/dist/esm/src/encrypt/defaultEncryptedColumnName.js +10 -0
- package/dist/esm/src/encrypt/index.js +76 -9
- package/dist/esm/src/errors/DoNotSetEncryptedFieldsDirectly.js +4 -0
- package/dist/esm/src/errors/associations/UnrecognizedAssociationConditionKeys.js +25 -0
- package/dist/esm/src/helpers/Env.js +28 -0
- package/dist/esm/src/helpers/cli/generateDreamContent.js +2 -1
- package/dist/esm/src/helpers/cli/generateFactoryContent.js +1 -1
- package/dist/esm/src/helpers/cli/generateMigrationContent.js +9 -9
- package/dist/esm/src/helpers/cli/generateSerializerContent.js +21 -8
- package/dist/esm/src/serializer/SerializerRenderer.js +65 -13
- package/dist/esm/src/serializer/builders/DreamSerializerBuilder.js +54 -38
- package/dist/esm/src/serializer/builders/ObjectSerializerBuilder.js +34 -14
- package/dist/esm/src/serializer/helpers/serializersForAssociatedClass.js +43 -0
- package/dist/types/src/Dream.d.ts +208 -42
- package/dist/types/src/bin/index.d.ts +7 -1
- package/dist/types/src/cli/index.d.ts +13 -0
- package/dist/types/src/db/migration-helpers/DreamMigrationHelpers.d.ts +2 -2
- package/dist/types/src/decorators/Decorators.d.ts +26 -3
- package/dist/types/src/decorators/field/Encrypted.d.ts +6 -1
- package/dist/types/src/dream/DreamClassTransactionBuilder.d.ts +9 -9
- package/dist/types/src/dream/DreamInstanceTransactionBuilder.d.ts +52 -25
- package/dist/types/src/dream/LeftJoinLoadBuilder.d.ts +4 -4
- package/dist/types/src/dream/LoadBuilder.d.ts +1 -1
- package/dist/types/src/dream/Query.d.ts +272 -33
- package/dist/types/src/dream/QueryDriver/Base.d.ts +63 -1
- package/dist/types/src/dream/QueryDriver/Kysely.d.ts +85 -1
- package/dist/types/src/dream/internal/associations/assertRecognizedAssociationConditionKeys.d.ts +29 -0
- package/dist/types/src/dream-app/index.d.ts +56 -2
- package/dist/types/src/encrypt/InternalEncrypt.d.ts +29 -1
- package/dist/types/src/encrypt/defaultEncryptedColumnName.d.ts +7 -0
- package/dist/types/src/encrypt/index.d.ts +75 -6
- package/dist/types/src/errors/associations/UnrecognizedAssociationConditionKeys.d.ts +8 -0
- package/dist/types/src/helpers/Env.d.ts +28 -0
- package/dist/types/src/package-exports/openapi.d.ts +1 -1
- package/dist/types/src/package-exports/types.d.ts +2 -2
- package/dist/types/src/serializer/SerializerRenderer.d.ts +13 -0
- package/dist/types/src/serializer/builders/DreamSerializerBuilder.d.ts +59 -55
- package/dist/types/src/serializer/builders/ObjectSerializerBuilder.d.ts +40 -19
- package/dist/types/src/serializer/helpers/serializersForAssociatedClass.d.ts +17 -0
- package/dist/types/src/types/lifecycle.d.ts +6 -0
- package/dist/types/src/types/openapi.d.ts +52 -29
- package/dist/types/src/types/serializer.d.ts +43 -2
- package/dist/types/src/types/types/lifecycle.ts +6 -0
- package/dist/types/src/types/types/openapi.ts +51 -37
- package/dist/types/src/types/types/serializer.ts +62 -2
- package/dist/types/src/types/types/variadic.ts +99 -43
- package/dist/types/src/types/variadic.d.ts +29 -3
- package/docs/assets/hierarchy.js +1 -1
- package/docs/assets/highlight.css +4 -11
- package/docs/assets/navigation.js +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/classes/db.DreamMigrationHelpers.html +13 -13
- package/docs/classes/db.KyselyQueryDriver.html +85 -46
- package/docs/classes/db.PostgresQueryDriver.html +86 -47
- package/docs/classes/db.QueryDriverBase.html +84 -45
- 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 +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.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 +37 -23
- package/docs/classes/index.Dream.html +270 -142
- package/docs/classes/index.DreamApp.html +40 -10
- package/docs/classes/index.DreamTransaction.html +3 -3
- package/docs/classes/index.Env.html +16 -2
- package/docs/classes/index.Query.html +331 -129
- package/docs/classes/system.CliFileWriter.html +4 -4
- package/docs/classes/system.DreamBin.html +6 -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 +52 -34
- package/docs/classes/system.ObjectSerializerBuilder.html +41 -22
- package/docs/classes/system.PathHelpers.html +4 -4
- package/docs/classes/utils.Encrypt.html +54 -10
- package/docs/classes/utils.Range.html +2 -2
- package/docs/functions/db.closeAllDbConnections.html +1 -1
- package/docs/functions/db.dreamDbConnections.html +1 -1
- package/docs/functions/db.untypedDb.html +1 -1
- package/docs/functions/db.validateColumn.html +1 -1
- package/docs/functions/db.validateTable.html +1 -1
- package/docs/functions/errors.pgErrorType.html +1 -1
- package/docs/functions/index.DreamSerializer.html +1 -1
- package/docs/functions/index.ObjectSerializer.html +1 -1
- package/docs/functions/index.ReplicaSafe.html +1 -1
- package/docs/functions/index.STI.html +1 -1
- package/docs/functions/index.SoftDelete.html +1 -1
- package/docs/functions/utils.camelize.html +1 -1
- package/docs/functions/utils.capitalize.html +1 -1
- package/docs/functions/utils.cloneDeepSafe.html +1 -1
- package/docs/functions/utils.compact.html +1 -1
- package/docs/functions/utils.groupBy.html +1 -1
- package/docs/functions/utils.hyphenize.html +1 -1
- package/docs/functions/utils.intersection.html +1 -1
- package/docs/functions/utils.isEmpty.html +1 -1
- package/docs/functions/utils.normalizeUnicode.html +1 -1
- package/docs/functions/utils.pascalize.html +1 -1
- package/docs/functions/utils.percent.html +1 -1
- package/docs/functions/utils.range.html +1 -1
- package/docs/functions/utils.round.html +1 -1
- package/docs/functions/utils.sanitizeString.html +1 -1
- package/docs/functions/utils.snakeify.html +1 -1
- package/docs/functions/utils.sort.html +1 -1
- package/docs/functions/utils.sortBy.html +1 -1
- package/docs/functions/utils.sortObjectByKey.html +1 -1
- package/docs/functions/utils.sortObjectByValue.html +1 -1
- package/docs/functions/utils.uncapitalize.html +1 -1
- package/docs/functions/utils.uniq.html +1 -1
- package/docs/hierarchy.html +1 -1
- package/docs/index.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 +6 -0
- 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 +10 -0
- 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/modules/openapi.html +1 -1
- package/docs/modules/types.html +1 -1
- 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 +4 -0
- package/docs/types/openapi.OpenapiSchemaCommonFields.html +5 -1
- package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +1 -2
- package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +1 -2
- package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +1 -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 +4 -2
- package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +5 -1
- package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +2 -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 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +1 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +1 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
- package/docs/types/openapi.OpenapiSchemaString.html +1 -1
- package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
- package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
- package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
- package/docs/types/openapi.OpenapiTypeField.html +1 -1
- package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
- package/docs/types/types.CalendarDateDurationUnit.html +1 -1
- package/docs/types/types.CalendarDateObject.html +1 -1
- package/docs/types/types.Camelized.html +1 -1
- package/docs/types/types.ClockTimeObject.html +1 -1
- package/docs/types/types.DbConnectionType.html +1 -1
- package/docs/types/types.DbTypes.html +1 -1
- package/docs/types/types.DreamAssociationMetadata.html +1 -1
- package/docs/types/types.DreamAttributes.html +1 -1
- package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
- package/docs/types/types.DreamClassColumn.html +1 -1
- package/docs/types/types.DreamColumn.html +1 -1
- package/docs/types/types.DreamColumnNames.html +1 -1
- package/docs/types/types.DreamLogLevel.html +1 -1
- package/docs/types/types.DreamLogger.html +2 -2
- package/docs/types/types.DreamModelSerializerType.html +1 -1
- package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
- package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
- package/docs/types/types.DreamParamSafeAttributes.html +1 -1
- package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
- package/docs/types/types.DreamSerializable.html +1 -1
- package/docs/types/types.DreamSerializableArray.html +1 -1
- package/docs/types/types.DreamSerializerKey.html +1 -1
- package/docs/types/types.DreamSerializers.html +1 -1
- package/docs/types/types.DreamVirtualColumns.html +1 -1
- package/docs/types/types.DurationUnit.html +1 -1
- package/docs/types/types.EncryptAlgorithm.html +1 -1
- package/docs/types/types.HasManyStatement.html +1 -1
- package/docs/types/types.HasOneStatement.html +1 -1
- package/docs/types/types.Hyphenized.html +1 -1
- package/docs/types/types.Pascalized.html +1 -1
- package/docs/types/types.PrimaryKeyType.html +1 -1
- package/docs/types/types.RoundingPrecision.html +1 -1
- package/docs/types/types.SerializerCasing.html +1 -1
- package/docs/types/types.SimpleObjectSerializerType.html +1 -1
- package/docs/types/types.Snakeified.html +1 -1
- package/docs/types/types.StrictInterface.html +1 -1
- package/docs/types/types.UpdateableAssociationProperties.html +1 -1
- package/docs/types/types.UpdateableProperties.html +1 -1
- package/docs/types/types.ValidationType.html +1 -1
- package/docs/types/types.ViewModel.html +2 -2
- package/docs/types/types.ViewModelClass.html +1 -1
- package/docs/types/types.WeekdayName.html +1 -1
- package/docs/types/types.WhereStatementForDream.html +1 -1
- package/docs/types/types.WhereStatementForDreamClass.html +1 -1
- package/docs/variables/index.DreamConst.html +1 -1
- package/docs/variables/index.ops.html +1 -1
- package/docs/variables/openapi.openapiPrimitiveTypes.html +1 -1
- package/docs/variables/openapi.openapiShorthandPrimitiveTypes.html +1 -1
- package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
- package/docs/variables/system.primaryKeyTypes.html +1 -1
- package/package.json +1 -1
- package/dist/cjs/src/serializer/helpers/serializerForAssociatedClass.js +0 -37
- package/dist/esm/src/serializer/helpers/serializerForAssociatedClass.js +0 -37
- package/dist/types/src/serializer/helpers/serializerForAssociatedClass.d.ts +0 -9
- package/docs/types/openapi.OpenapiSchemaObjectAnyOf.html +0 -1
- package/docs/types/openapi.OpenapiSchemaObjectAnyOfShorthand.html +0 -1
- package/docs/types/openapi.OpenapiSchemaObjectOneOf.html +0 -1
- package/docs/types/openapi.OpenapiSchemaObjectOneOfShorthand.html +0 -1
|
@@ -554,6 +554,78 @@ export default class QueryDriverBase {
|
|
|
554
554
|
async pluck(...fields) {
|
|
555
555
|
throw new Error('implement pluck in child class');
|
|
556
556
|
}
|
|
557
|
+
/**
|
|
558
|
+
* @internal
|
|
559
|
+
*
|
|
560
|
+
* The order statements that the declared `order` of the associations the
|
|
561
|
+
* Query is built on or joins contributes to its ORDER BY, in the order they
|
|
562
|
+
* sort, each column namespaced by the alias it is read from. Cursor
|
|
563
|
+
* pagination folds these into its cursor.
|
|
564
|
+
*
|
|
565
|
+
* @returns An array of namespaced order statements
|
|
566
|
+
*/
|
|
567
|
+
associationOrderStatements() {
|
|
568
|
+
throw new Error('implement associationOrderStatements in child class');
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* @internal
|
|
572
|
+
*
|
|
573
|
+
* The primary key of each table the Query reaches through a HasOne or
|
|
574
|
+
* HasMany association (one it joins, one an association it joins goes
|
|
575
|
+
* through, or one its association query is built on), namespaced by the
|
|
576
|
+
* alias it is read from, in join order. Such a join can return a row of the
|
|
577
|
+
* Query's table once for each row it matches, and these keys, with the
|
|
578
|
+
* Query's own primary key, tell those rows apart. Cursor pagination pages by
|
|
579
|
+
* them after the Query's own primary key.
|
|
580
|
+
*
|
|
581
|
+
* @returns An array of namespaced primary key columns
|
|
582
|
+
*/
|
|
583
|
+
joinedRowKeyColumns() {
|
|
584
|
+
throw new Error('implement joinedRowKeyColumns in child class');
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* @internal
|
|
588
|
+
*
|
|
589
|
+
* A page of a select that keeps one row of each group (DISTINCT ON, as a
|
|
590
|
+
* Query that calls `distinct`, or an association that declares `distinct`,
|
|
591
|
+
* produces), or null for any other select, whose pages cursor pagination
|
|
592
|
+
* reads as it reads any select's.
|
|
593
|
+
*
|
|
594
|
+
* The page is the values of the Query's order statement columns for each
|
|
595
|
+
* row the select returns that matches any of `rowsAfter` (every row when it
|
|
596
|
+
* is null), sorted by the Query's order statements and limited by its
|
|
597
|
+
* limit. The select chooses each group's row before `rowsAfter` narrows the
|
|
598
|
+
* rows, so that the page holds only rows the select returns.
|
|
599
|
+
*
|
|
600
|
+
* A driver whose selects never keep one row of each group returns null.
|
|
601
|
+
*
|
|
602
|
+
* @param rowsAfter - where statements, any of which a row of the page matches
|
|
603
|
+
* @returns The page's rows, each an array of its order statement columns' values, or null
|
|
604
|
+
*/
|
|
605
|
+
// eslint-disable-next-line @typescript-eslint/require-await, @typescript-eslint/no-unused-vars
|
|
606
|
+
async pluckDistinctOnPage(rowsAfter) {
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* @internal
|
|
611
|
+
*
|
|
612
|
+
* For a select that keeps one row of each group (DISTINCT ON, as a Query
|
|
613
|
+
* that calls `distinct`, or an association that declares `distinct`,
|
|
614
|
+
* produces), the values of `rowKeyColumns` for every row it returns, sorted
|
|
615
|
+
* by those columns, ascending; null for any other select. `findEach` and
|
|
616
|
+
* `pluckEach` read a select that keeps one row of each group from these
|
|
617
|
+
* keys rather than by a cursor, which would narrow the select before it
|
|
618
|
+
* chooses each group's row.
|
|
619
|
+
*
|
|
620
|
+
* A driver whose selects never keep one row of each group returns null.
|
|
621
|
+
*
|
|
622
|
+
* @param rowKeyColumns - the namespaced columns that tell the select's rows apart
|
|
623
|
+
* @returns The rows' values of `rowKeyColumns`, or null
|
|
624
|
+
*/
|
|
625
|
+
// eslint-disable-next-line @typescript-eslint/require-await, @typescript-eslint/no-unused-vars
|
|
626
|
+
async pluckDistinctOnRowKeys(rowKeyColumns) {
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
557
629
|
/**
|
|
558
630
|
* Returns a new Kysely SelectQueryBuilder instance to be used
|
|
559
631
|
* in a sub Query
|
|
@@ -76,6 +76,21 @@ import checkForNeedToBeRunMigrations from './helpers/kysely/checkForNeedToBeRunM
|
|
|
76
76
|
import foreignKeyTypeFromPrimaryKey from './helpers/kysely/foreignKeyTypeFromPrimaryKey.js';
|
|
77
77
|
import runMigration from './helpers/kysely/runMigration.js';
|
|
78
78
|
export default class KyselyQueryDriver extends QueryDriverBase {
|
|
79
|
+
/**
|
|
80
|
+
* @internal
|
|
81
|
+
*
|
|
82
|
+
* While `associationOrderStatements` builds a select, the order statements
|
|
83
|
+
* that association joins add to it, in the order they are added.
|
|
84
|
+
*/
|
|
85
|
+
associationOrderCollector = null;
|
|
86
|
+
/**
|
|
87
|
+
* @internal
|
|
88
|
+
*
|
|
89
|
+
* While `joinedRowKeyColumns` builds a select, the namespaced primary keys of
|
|
90
|
+
* the tables that HasOne and HasMany joins add to it, in the order they are
|
|
91
|
+
* added.
|
|
92
|
+
*/
|
|
93
|
+
joinedRowKeyCollector = null;
|
|
79
94
|
// ATTENTION FRED
|
|
80
95
|
// stop trying to make this async. You never learn...
|
|
81
96
|
//
|
|
@@ -639,7 +654,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
639
654
|
bypassOrder: true,
|
|
640
655
|
});
|
|
641
656
|
const countClause = distinctColumn
|
|
642
|
-
? count(sql `DISTINCT ${distinctColumn}`)
|
|
657
|
+
? count(sql `DISTINCT ${sql.ref(distinctColumn)}`)
|
|
643
658
|
: count(query['namespaceColumn'](query.dreamInstance['_primaryKey']));
|
|
644
659
|
kyselyQuery = kyselyQuery.select(countClause.as('tablecount'));
|
|
645
660
|
const data = await executeDatabaseQuery(kyselyQuery, 'executeTakeFirstOrThrow');
|
|
@@ -884,13 +899,58 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
884
899
|
return new this.constructor(results.clone).buildCommon(results.kyselyQuery);
|
|
885
900
|
}
|
|
886
901
|
buildSelect({ bypassSelectAll = false, bypassOrder = false, columns, } = {}) {
|
|
902
|
+
const selectCore = this.buildSelectCore({ bypassOrder });
|
|
903
|
+
let kyselyQuery = selectCore.kyselyQuery;
|
|
904
|
+
if (this.query['bypassImplicitOrder']) {
|
|
905
|
+
// a keyset cursor compares only the Query's order statements, so the
|
|
906
|
+
// orderings emitted above (a similarity rank, an association's declared
|
|
907
|
+
// `order`, including those of an association query's base select) are
|
|
908
|
+
// cleared rather than left to sort ahead of them
|
|
909
|
+
kyselyQuery = kyselyQuery.clearOrderBy();
|
|
910
|
+
}
|
|
911
|
+
if (this.query['orderStatements'].length && !bypassOrder) {
|
|
912
|
+
this.query['orderStatements'].forEach(orderStatement => {
|
|
913
|
+
this.validatePlainColumn(this.dreamClass, orderStatement.column);
|
|
914
|
+
kyselyQuery = kyselyQuery.orderBy(this.namespaceColumn(orderStatement.column), this.orderByDirection(orderStatement.direction));
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
if (this.query['limitStatement'])
|
|
918
|
+
kyselyQuery = kyselyQuery.limit(this.query['limitStatement']);
|
|
919
|
+
if (this.query['offsetStatement'])
|
|
920
|
+
kyselyQuery = kyselyQuery.offset(this.query['offsetStatement']);
|
|
921
|
+
if (columns) {
|
|
922
|
+
kyselyQuery = kyselyQuery.select(this.columnsWithRequiredLoadColumns(columns).map(column => this.namespaceColumn(column)));
|
|
923
|
+
}
|
|
924
|
+
else if (!bypassSelectAll) {
|
|
925
|
+
kyselyQuery = kyselyQuery.selectAll(this.query['baseSqlAlias']);
|
|
926
|
+
}
|
|
927
|
+
// even though we manually bypass explicit order statements above,
|
|
928
|
+
// associations can contain their own ordering systems. If we do not
|
|
929
|
+
// escape all orders, we can mistakenly allow an order clause to sneak in.
|
|
930
|
+
if (bypassOrder)
|
|
931
|
+
kyselyQuery = kyselyQuery.clearOrderBy();
|
|
932
|
+
return kyselyQuery;
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* @internal
|
|
936
|
+
*
|
|
937
|
+
* The select's source, joins, conditions, DISTINCT ON and similarity
|
|
938
|
+
* ranking, before the Query's own order statements, limit, offset and
|
|
939
|
+
* selection are applied, together with the connection the select runs on.
|
|
940
|
+
*/
|
|
941
|
+
buildSelectCore({ bypassOrder = false } = {}) {
|
|
887
942
|
let kyselyQuery;
|
|
943
|
+
let selectDb;
|
|
888
944
|
if (this.query['baseSelectQuery']) {
|
|
889
945
|
const connectionOverride = this.query['connectionOverride'];
|
|
890
946
|
const query = connectionOverride
|
|
891
947
|
? this.query['baseSelectQuery'].connection(connectionOverride)
|
|
892
948
|
: this.query['baseSelectQuery'];
|
|
893
|
-
|
|
949
|
+
const baseSelectDriver = new this.constructor(query);
|
|
950
|
+
baseSelectDriver.associationOrderCollector = this.associationOrderCollector;
|
|
951
|
+
baseSelectDriver.joinedRowKeyCollector = this.joinedRowKeyCollector;
|
|
952
|
+
selectDb = baseSelectDriver.dbFor('select');
|
|
953
|
+
kyselyQuery = baseSelectDriver.buildSelect({
|
|
894
954
|
bypassSelectAll: true,
|
|
895
955
|
});
|
|
896
956
|
}
|
|
@@ -898,7 +958,8 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
898
958
|
const from = this.query['baseSqlAlias'] === this.query['tableName']
|
|
899
959
|
? this.query['tableName']
|
|
900
960
|
: `${this.query['tableName']} as ${this.query['baseSqlAlias']}`;
|
|
901
|
-
|
|
961
|
+
selectDb = this.dbFor('select');
|
|
962
|
+
kyselyQuery = selectDb.selectFrom(from);
|
|
902
963
|
}
|
|
903
964
|
if (this.query['distinctColumn']) {
|
|
904
965
|
kyselyQuery = kyselyQuery.distinctOn(this.query['distinctColumn']);
|
|
@@ -907,28 +968,135 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
907
968
|
kyselyQuery = this.conditionallyAttachSimilarityColumnsToSelect(kyselyQuery, {
|
|
908
969
|
bypassOrder: bypassOrder || !!this.query['distinctColumn'],
|
|
909
970
|
});
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
971
|
+
return { kyselyQuery, selectDb };
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* @internal
|
|
975
|
+
*
|
|
976
|
+
* The order statements that the declared `order` of the associations the
|
|
977
|
+
* Query is built on or joins contributes to its ORDER BY, in the order they
|
|
978
|
+
* sort, each column namespaced by the alias it is read from. They are read
|
|
979
|
+
* off the same join code that emits them, by building the select.
|
|
980
|
+
*
|
|
981
|
+
* @returns An array of namespaced order statements
|
|
982
|
+
*/
|
|
983
|
+
associationOrderStatements() {
|
|
984
|
+
const collected = [];
|
|
985
|
+
this.associationOrderCollector = collected;
|
|
986
|
+
try {
|
|
987
|
+
this.buildSelect();
|
|
915
988
|
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
if (this.query['offsetStatement'])
|
|
919
|
-
kyselyQuery = kyselyQuery.offset(this.query['offsetStatement']);
|
|
920
|
-
if (columns) {
|
|
921
|
-
kyselyQuery = kyselyQuery.select(this.columnsWithRequiredLoadColumns(columns).map(column => this.namespaceColumn(column)));
|
|
989
|
+
finally {
|
|
990
|
+
this.associationOrderCollector = null;
|
|
922
991
|
}
|
|
923
|
-
|
|
924
|
-
|
|
992
|
+
return collected;
|
|
993
|
+
}
|
|
994
|
+
/**
|
|
995
|
+
* @internal
|
|
996
|
+
*
|
|
997
|
+
* The primary key of each table the Query reaches through a HasOne or
|
|
998
|
+
* HasMany association (one it joins, one an association it joins goes
|
|
999
|
+
* through, or one its association query is built on), namespaced by the
|
|
1000
|
+
* alias it is read from, in join order. They are read off the same join code
|
|
1001
|
+
* that emits the joins, by building the select.
|
|
1002
|
+
*
|
|
1003
|
+
* @returns An array of namespaced primary key columns
|
|
1004
|
+
*/
|
|
1005
|
+
joinedRowKeyColumns() {
|
|
1006
|
+
const collected = [];
|
|
1007
|
+
this.joinedRowKeyCollector = collected;
|
|
1008
|
+
try {
|
|
1009
|
+
this.buildSelect();
|
|
925
1010
|
}
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1011
|
+
finally {
|
|
1012
|
+
this.joinedRowKeyCollector = null;
|
|
1013
|
+
}
|
|
1014
|
+
return collected;
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* @internal
|
|
1018
|
+
*
|
|
1019
|
+
* A page of a select that carries DISTINCT ON (as a Query that calls
|
|
1020
|
+
* `distinct`, or an association that declares `distinct`, produces), or null
|
|
1021
|
+
* for any other select.
|
|
1022
|
+
*
|
|
1023
|
+
* Such a select keeps the row of each group that sorts first in its ORDER
|
|
1024
|
+
* BY, so narrowing it to the rows after a cursor would let it keep a row it
|
|
1025
|
+
* does not return (a group's later row, once the cursor has passed the
|
|
1026
|
+
* group's first). It is read as a derived table instead, and narrowed to
|
|
1027
|
+
* `rowsAfter`, sorted and limited outside it. The Query's order statements
|
|
1028
|
+
* follow the select's own ORDER BY, if it has one, so that the select keeps
|
|
1029
|
+
* the same row of each group every time it runs, and each of their columns
|
|
1030
|
+
* is read out of it under a short alias (as `pluck` reads them), since a
|
|
1031
|
+
* joined table's column cannot be named outside it.
|
|
1032
|
+
*
|
|
1033
|
+
* @param rowsAfter - where statements, any of which a row of the page matches
|
|
1034
|
+
* @returns The page's rows, each an array of its order statement columns' values, or null
|
|
1035
|
+
*/
|
|
1036
|
+
async pluckDistinctOnPage(rowsAfter) {
|
|
1037
|
+
const { kyselyQuery: selectCore, selectDb } = this.buildSelectCore();
|
|
1038
|
+
const selectNode = selectCore.toOperationNode();
|
|
1039
|
+
if (!selectNode.distinctOn?.length)
|
|
1040
|
+
return null;
|
|
1041
|
+
const baseSqlAlias = this.query['baseSqlAlias'];
|
|
1042
|
+
const orderStatements = this.query['orderStatements'];
|
|
1043
|
+
const pageColumns = orderStatements.map((orderStatement, index) => {
|
|
1044
|
+
this.validatePlainColumn(this.dreamClass, orderStatement.column);
|
|
1045
|
+
return { column: this.namespaceColumn(orderStatement.column), alias: `pluck${index}` };
|
|
1046
|
+
});
|
|
1047
|
+
let distinctSelect = selectCore;
|
|
1048
|
+
orderStatements.forEach(orderStatement => {
|
|
1049
|
+
distinctSelect = distinctSelect.orderBy(this.namespaceColumn(orderStatement.column), this.orderByDirection(orderStatement.direction));
|
|
1050
|
+
});
|
|
1051
|
+
distinctSelect = distinctSelect.select(pageColumns.map(({ column, alias }) => `${column} as ${alias}`));
|
|
1052
|
+
let pageSelect = selectDb.selectFrom(distinctSelect.as(baseSqlAlias));
|
|
1053
|
+
pageSelect = pageSelect.select(pageColumns.map(({ alias }) => `${baseSqlAlias}.${alias}`));
|
|
1054
|
+
if (rowsAfter) {
|
|
1055
|
+
const derivedColumns = new Map(pageColumns.map(({ column, alias }) => [column, `${baseSqlAlias}.${alias}`]));
|
|
1056
|
+
pageSelect = pageSelect.where((eb) => eb.or(rowsAfter.map(whereStatement => this.whereStatementToExpressionWrapper(this.dreamClass, eb, Object.fromEntries(Object.entries(whereStatement).map(([column, value]) => [
|
|
1057
|
+
derivedColumns.get(this.namespaceColumn(column)),
|
|
1058
|
+
value,
|
|
1059
|
+
]))))));
|
|
1060
|
+
}
|
|
1061
|
+
pageColumns.forEach(({ alias }, index) => {
|
|
1062
|
+
pageSelect = pageSelect.orderBy(`${baseSqlAlias}.${alias}`, this.orderByDirection(orderStatements[index].direction));
|
|
1063
|
+
});
|
|
1064
|
+
if (this.query['limitStatement'])
|
|
1065
|
+
pageSelect = pageSelect.limit(this.query['limitStatement']);
|
|
1066
|
+
return (await executeDatabaseQuery(pageSelect, 'execute')).map(row => pageColumns.map(({ alias }) => row[alias]));
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* @internal
|
|
1070
|
+
*
|
|
1071
|
+
* For a select that carries DISTINCT ON (as a Query that calls `distinct`,
|
|
1072
|
+
* or an association that declares `distinct`, produces), the values of
|
|
1073
|
+
* `rowKeyColumns` for every row it returns, sorted by those columns,
|
|
1074
|
+
* ascending; null for any other select.
|
|
1075
|
+
*
|
|
1076
|
+
* The select runs as `all()` runs it, with its own ORDER BY choosing each
|
|
1077
|
+
* group's row, and is read as a derived table, sorted outside it. Each of
|
|
1078
|
+
* `rowKeyColumns` is read out of it under a short alias (as `pluck` reads
|
|
1079
|
+
* them), since a joined table's column cannot be named outside it.
|
|
1080
|
+
*
|
|
1081
|
+
* @param rowKeyColumns - the namespaced columns that tell the select's rows apart
|
|
1082
|
+
* @returns The rows' values of `rowKeyColumns`, or null
|
|
1083
|
+
*/
|
|
1084
|
+
async pluckDistinctOnRowKeys(rowKeyColumns) {
|
|
1085
|
+
const { kyselyQuery: selectCore, selectDb } = this.buildSelectCore();
|
|
1086
|
+
if (!selectCore.toOperationNode().distinctOn?.length)
|
|
1087
|
+
return null;
|
|
1088
|
+
const baseSqlAlias = this.query['baseSqlAlias'];
|
|
1089
|
+
const keyColumns = rowKeyColumns.map((column, index) => {
|
|
1090
|
+
this.validatePlainColumn(this.dreamClass, column);
|
|
1091
|
+
return { column: this.namespaceColumn(column), alias: `key${index}` };
|
|
1092
|
+
});
|
|
1093
|
+
const distinctSelect = this.buildSelect({ bypassSelectAll: true }).select(keyColumns.map(({ column, alias }) => `${column} as ${alias}`));
|
|
1094
|
+
let keySelect = selectDb.selectFrom(distinctSelect.as(baseSqlAlias));
|
|
1095
|
+
keySelect = keySelect.select(keyColumns.map(({ alias }) => `${baseSqlAlias}.${alias}`));
|
|
1096
|
+
keyColumns.forEach(({ alias }) => {
|
|
1097
|
+
keySelect = keySelect.orderBy(`${baseSqlAlias}.${alias}`, this.orderByDirection('asc'));
|
|
1098
|
+
});
|
|
1099
|
+
return (await executeDatabaseQuery(keySelect, 'execute')).map(row => keyColumns.map(({ alias }) => row[alias]));
|
|
932
1100
|
}
|
|
933
1101
|
orderByDirection(direction) {
|
|
934
1102
|
return orderByDirection(direction);
|
|
@@ -1500,26 +1668,22 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1500
1668
|
c = val.value;
|
|
1501
1669
|
}
|
|
1502
1670
|
else if (val instanceof Range) {
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
const
|
|
1506
|
-
if (
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1671
|
+
// a bound is present unless it is null or undefined, matching the Range
|
|
1672
|
+
// constructor, so falsy bounds such as 0, 0n and '' still constrain the query
|
|
1673
|
+
const boundConditions = [];
|
|
1674
|
+
if (val.begin != null)
|
|
1675
|
+
boundConditions.push({ operator: '>=', value: val.begin });
|
|
1676
|
+
if (val.end != null)
|
|
1677
|
+
boundConditions.push({ operator: val.excludeEnd ? '<' : '<=', value: val.end });
|
|
1678
|
+
// the Range constructor rejects a range with neither bound, so firstBound is always set
|
|
1679
|
+
const [firstBound, secondBound] = boundConditions;
|
|
1680
|
+
a = attr;
|
|
1681
|
+
b = firstBound.operator;
|
|
1682
|
+
c = firstBound.value;
|
|
1683
|
+
if (secondBound) {
|
|
1510
1684
|
a2 = attr;
|
|
1511
|
-
b2 =
|
|
1512
|
-
c2 =
|
|
1513
|
-
}
|
|
1514
|
-
else if (rangeStart) {
|
|
1515
|
-
a = attr;
|
|
1516
|
-
b = '>=';
|
|
1517
|
-
c = rangeStart;
|
|
1518
|
-
}
|
|
1519
|
-
else {
|
|
1520
|
-
a = attr;
|
|
1521
|
-
b = excludeEnd ? '<' : '<=';
|
|
1522
|
-
c = rangeEnd;
|
|
1685
|
+
b2 = secondBound.operator;
|
|
1686
|
+
c2 = secondBound.value;
|
|
1523
1687
|
}
|
|
1524
1688
|
}
|
|
1525
1689
|
else {
|
|
@@ -2253,6 +2417,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
2253
2417
|
join = this.applyJoinAndStatement(modelClassUsedForJoinConditions, join, joinAndStatement, currentTableAlias);
|
|
2254
2418
|
return join;
|
|
2255
2419
|
});
|
|
2420
|
+
this.joinedRowKeyCollector?.push(this.namespaceColumn(association.modelCB().primaryKey, currentTableAlias));
|
|
2256
2421
|
if (association.type === 'HasMany') {
|
|
2257
2422
|
if (association.order) {
|
|
2258
2423
|
query = this.applyOrderStatementForAssociation({
|
|
@@ -2280,15 +2445,16 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
2280
2445
|
return query;
|
|
2281
2446
|
let selectQuery = query;
|
|
2282
2447
|
const orderStatement = association.order;
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
}
|
|
2448
|
+
const orderQueryStatements = typeof orderStatement === 'string'
|
|
2449
|
+
? [{ column: this.namespaceColumn(orderStatement, tableNameOrAlias), direction: 'asc' }]
|
|
2450
|
+
: Object.keys(orderStatement).map(column => ({
|
|
2451
|
+
column: this.namespaceColumn(column, tableNameOrAlias),
|
|
2452
|
+
direction: orderStatement[column],
|
|
2453
|
+
}));
|
|
2454
|
+
orderQueryStatements.forEach(({ column, direction }) => {
|
|
2455
|
+
selectQuery = selectQuery.orderBy(column, direction);
|
|
2456
|
+
});
|
|
2457
|
+
this.associationOrderCollector?.push(...orderQueryStatements);
|
|
2292
2458
|
return selectQuery;
|
|
2293
2459
|
}
|
|
2294
2460
|
distinctColumnNameForAssociation({ association, tableNameOrAlias, foreignKey, }) {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import UnrecognizedAssociationConditionKeys from '../../../errors/associations/UnrecognizedAssociationConditionKeys.js';
|
|
2
|
+
import isObject from '../../../helpers/isObject.js';
|
|
3
|
+
/**
|
|
4
|
+
* The keys a condition object on an association accepts, whether it is
|
|
5
|
+
* passed in a chain (`preload('posts', { and: { ... } })`), returned from a
|
|
6
|
+
* `preloadFor`/`loadFor` modifier, or passed to `associationQuery`.
|
|
7
|
+
*/
|
|
8
|
+
export const ASSOCIATION_CONDITION_KEYS = ['and', 'andNot', 'andAny'];
|
|
9
|
+
/**
|
|
10
|
+
* The keys accepted by `destroyAssociation`, `reallyDestroyAssociation`
|
|
11
|
+
* and `undestroyAssociation`.
|
|
12
|
+
*/
|
|
13
|
+
export const DESTROY_ASSOCIATION_OPTION_KEYS = [
|
|
14
|
+
...ASSOCIATION_CONDITION_KEYS,
|
|
15
|
+
'bypassAllDefaultScopes',
|
|
16
|
+
'defaultScopesToBypass',
|
|
17
|
+
'cascade',
|
|
18
|
+
'skipHooks',
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* The keys accepted by `updateAssociation`.
|
|
22
|
+
*/
|
|
23
|
+
export const UPDATE_ASSOCIATION_OPTION_KEYS = [
|
|
24
|
+
...ASSOCIATION_CONDITION_KEYS,
|
|
25
|
+
'bypassAllDefaultScopes',
|
|
26
|
+
'defaultScopesToBypass',
|
|
27
|
+
'skipHooks',
|
|
28
|
+
];
|
|
29
|
+
/**
|
|
30
|
+
* Throws UnrecognizedAssociationConditionKeys when `value` is an object
|
|
31
|
+
* carrying any own key outside `acceptedKeys`, including a key whose value
|
|
32
|
+
* is `undefined`. Dream reads only the accepted keys, so an unrecognized key
|
|
33
|
+
* would otherwise be silently ignored (and, for the destroy family, act on
|
|
34
|
+
* every associated row instead of the ones the caller meant to select).
|
|
35
|
+
* Non-object values (`undefined`, `null`) pass through untouched.
|
|
36
|
+
*
|
|
37
|
+
* @param value - The condition object or options object to check
|
|
38
|
+
* @param acceptedKeys - The keys `value` may carry
|
|
39
|
+
* @param associationName - The association the object applies to, for the error message
|
|
40
|
+
* @param methodName - The public method the options were passed to, or null for a chain condition
|
|
41
|
+
*/
|
|
42
|
+
export default function assertRecognizedAssociationConditionKeys(value, acceptedKeys, associationName, methodName = null) {
|
|
43
|
+
if (!isObject(value))
|
|
44
|
+
return;
|
|
45
|
+
const unrecognizedKeys = Object.keys(value).filter(key => !acceptedKeys.includes(key));
|
|
46
|
+
if (unrecognizedKeys.length)
|
|
47
|
+
throw new UnrecognizedAssociationConditionKeys(String(associationName), unrecognizedKeys, acceptedKeys, methodName);
|
|
48
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import applyScopeBypassingSettingsToQuery from '../applyScopeBypassingSettingsToQuery.js';
|
|
2
|
+
import assertRecognizedAssociationConditionKeys, { ASSOCIATION_CONDITION_KEYS, } from './assertRecognizedAssociationConditionKeys.js';
|
|
2
3
|
export default function associationQuery(dream, txn = null, associationName, { joinAndStatements, bypassAllDefaultScopes, defaultScopesToBypass, }) {
|
|
4
|
+
// a condition carrying none of and/andNot/andAny skips the join below, so the
|
|
5
|
+
// chain check in Query never sees it
|
|
6
|
+
assertRecognizedAssociationConditionKeys(joinAndStatements, ASSOCIATION_CONDITION_KEYS, associationName, 'associationQuery');
|
|
3
7
|
const association = dream['associationMetadataMap']()[associationName];
|
|
4
8
|
const associationClass = association.modelCB();
|
|
5
9
|
const dreamClass = dream.constructor;
|
|
@@ -14,7 +14,12 @@ export default async function reload(dream, txn = null) {
|
|
|
14
14
|
[dream['_primaryKey']]: dream.primaryKeyValue(),
|
|
15
15
|
});
|
|
16
16
|
const reloadedRecord = await query.firstOrFail();
|
|
17
|
-
|
|
17
|
+
// a row read from the database, so its @Encrypted backing columns hold the
|
|
18
|
+
// ciphertext Dream stored and are written without setAttribute's decryption check
|
|
19
|
+
dream['_setAttributes'](reloadedRecord.getAttributes(), {
|
|
20
|
+
bypassUserDefinedSetters: true,
|
|
21
|
+
fromDatabase: true,
|
|
22
|
+
});
|
|
18
23
|
dream['freezeAttributes']();
|
|
19
24
|
dream['originalAttributes'] = dream.getAttributes();
|
|
20
25
|
return dream;
|
|
@@ -132,8 +132,14 @@ async function writeDream(dream, txn, { alreadyPersisted, sortableFieldConfigs,
|
|
|
132
132
|
// the row returned by the driver comes from `RETURNING *` (or a
|
|
133
133
|
// `select *` reload on drivers without RETURNING support), so under
|
|
134
134
|
// schema/image skew it can contain columns this build's compiled
|
|
135
|
-
// schema doesn't know about; those
|
|
136
|
-
|
|
135
|
+
// schema doesn't know about; those are filtered out before the row is
|
|
136
|
+
// written back. It is a row read from the database, so its @Encrypted
|
|
137
|
+
// backing columns hold the ciphertext Dream stored and are written
|
|
138
|
+
// without setAttribute's decryption check
|
|
139
|
+
dream['_setAttributes'](filterRowToKnownColumns(data, dream.columns()), {
|
|
140
|
+
bypassUserDefinedSetters: true,
|
|
141
|
+
fromDatabase: true,
|
|
142
|
+
});
|
|
137
143
|
}
|
|
138
144
|
// set frozen attributes to what has already been saved
|
|
139
145
|
dream['freezeAttributes']();
|
|
@@ -16,6 +16,7 @@ export default function sqlResultToDreamInstance(dreamClass, sqlResult) {
|
|
|
16
16
|
throw new STIChildMissing(dreamClass, sqlResult.type, sqlResult[dreamClass.primaryKey]);
|
|
17
17
|
const dreamModel = new extendingDreamClass(filterRowToKnownColumns(sqlResult, extendingDreamClass.columns()), {
|
|
18
18
|
bypassUserDefinedSetters: true,
|
|
19
|
+
fromDatabase: true,
|
|
19
20
|
isPersisted: true,
|
|
20
21
|
_internalUseOnly: true,
|
|
21
22
|
});
|
|
@@ -25,6 +26,7 @@ export default function sqlResultToDreamInstance(dreamClass, sqlResult) {
|
|
|
25
26
|
else {
|
|
26
27
|
const dreamModel = new dreamClass(filterRowToKnownColumns(sqlResult, dreamClass.columns()), {
|
|
27
28
|
bypassUserDefinedSetters: true,
|
|
29
|
+
fromDatabase: true,
|
|
28
30
|
isPersisted: true,
|
|
29
31
|
_internalUseOnly: true,
|
|
30
32
|
});
|
|
@@ -148,7 +148,7 @@ async function undestroyAssociatedRecords(dream, txn, options) {
|
|
|
148
148
|
bypassAllDefaultScopes: options.bypassAllDefaultScopes ?? false,
|
|
149
149
|
defaultScopesToBypass: (options.defaultScopesToBypass ?? []),
|
|
150
150
|
});
|
|
151
|
-
await query.removeDefaultScope(SOFT_DELETE_SCOPE_NAME)
|
|
151
|
+
await query.removeDefaultScope(SOFT_DELETE_SCOPE_NAME)['findEachRecordOnce'](async (record) => {
|
|
152
152
|
markSortableCascadeEdge(record, association);
|
|
153
153
|
await record.txn(txn).undestroy(options);
|
|
154
154
|
});
|
|
@@ -179,6 +179,7 @@ export default class DreamApp {
|
|
|
179
179
|
_specialHooks = {
|
|
180
180
|
dbLog: [],
|
|
181
181
|
replStart: [],
|
|
182
|
+
encryptionLegacyKeyUsed: [],
|
|
182
183
|
};
|
|
183
184
|
get specialHooks() {
|
|
184
185
|
return this._specialHooks;
|
|
@@ -520,6 +521,40 @@ export default class DreamApp {
|
|
|
520
521
|
}
|
|
521
522
|
}
|
|
522
523
|
}
|
|
524
|
+
/**
|
|
525
|
+
* Registers a callback for an app-level event. Callbacks for the same
|
|
526
|
+
* event run in the order they were registered.
|
|
527
|
+
*
|
|
528
|
+
* - `'db:log'`: called with a {@link KyselyLogEvent} each time a query on a
|
|
529
|
+
* Dream database connection completes or fails.
|
|
530
|
+
* - `'repl:start'`: called, and awaited, with the console's REPL context
|
|
531
|
+
* when the Dream console starts.
|
|
532
|
+
* - `'encryption:legacy-key-used'`: called with an
|
|
533
|
+
* {@link EncryptionLegacyKeyUsedEvent} when reading an `@deco.Encrypted`
|
|
534
|
+
* property needed the `legacy` column encryption key, not `current`, to
|
|
535
|
+
* open the stored value. Use it during a key rotation to find rows still
|
|
536
|
+
* encrypted with the old key. The property decrypts on every read, so a
|
|
537
|
+
* record whose value is still under the old key fires the event each time
|
|
538
|
+
* the property is read. A row nobody reads never fires it, so a quiet
|
|
539
|
+
* event does not prove that `legacy` is safe to drop;
|
|
540
|
+
* `Model.reencryptEncryptedColumns()` rewrites every row instead. Neither
|
|
541
|
+
* it nor the migration helpers fire the event. The callback runs
|
|
542
|
+
* synchronously inside the property read, and an error it throws
|
|
543
|
+
* propagates from that read. Reading the same property of the same
|
|
544
|
+
* record inside the callback returns its value without firing the event
|
|
545
|
+
* again, so a callback can read the property and assign the value back,
|
|
546
|
+
* which re-encrypts it under `current` for the record's next save. That
|
|
547
|
+
* holds only while the callback runs synchronously: a callback that
|
|
548
|
+
* reads the property after an `await` fires the event again from that
|
|
549
|
+
* read, so an `async` callback should read the property before its
|
|
550
|
+
* first `await`.
|
|
551
|
+
*
|
|
552
|
+
* ```ts
|
|
553
|
+
* dreamApp.on('encryption:legacy-key-used', ({ dream, property }) => {
|
|
554
|
+
* console.warn(`${dream.constructor.name} ${dream.primaryKeyValue()} ${property} still uses the legacy key`)
|
|
555
|
+
* })
|
|
556
|
+
* ```
|
|
557
|
+
*/
|
|
523
558
|
on(hookEventType, cb) {
|
|
524
559
|
switch (hookEventType) {
|
|
525
560
|
case 'db:log':
|
|
@@ -528,6 +563,9 @@ export default class DreamApp {
|
|
|
528
563
|
case 'repl:start':
|
|
529
564
|
this._specialHooks.replStart.push(cb);
|
|
530
565
|
break;
|
|
566
|
+
case 'encryption:legacy-key-used':
|
|
567
|
+
this._specialHooks.encryptionLegacyKeyUsed.push(cb);
|
|
568
|
+
break;
|
|
531
569
|
}
|
|
532
570
|
}
|
|
533
571
|
}
|