@rvoh/dream 2.26.0 → 2.28.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 +32 -17
- package/dist/cjs/src/db/errors.js +4 -0
- package/dist/cjs/src/decorators/field/sortable/Sortable.js +43 -118
- package/dist/cjs/src/decorators/field/sortable/helpers/acquireSortableScopeLocks.js +38 -0
- package/dist/cjs/src/decorators/field/sortable/helpers/acquireStabilizedSortableBatchLocks.js +183 -0
- package/dist/cjs/src/decorators/field/sortable/helpers/acquireStabilizedSortableScopeLocks.js +40 -0
- package/dist/cjs/src/decorators/field/sortable/helpers/canonicalScopeValue.js +194 -0
- package/dist/cjs/src/decorators/field/sortable/helpers/clearCachedSortableValues.js +18 -0
- package/dist/cjs/src/decorators/field/sortable/helpers/decrementScopedRecordsGreaterThanPosition.js +9 -9
- package/dist/cjs/src/decorators/field/sortable/helpers/filterQueryToInstanceScope.js +20 -0
- package/dist/cjs/src/decorators/field/sortable/helpers/filterQueryToScopeValues.js +32 -0
- package/dist/cjs/src/decorators/field/sortable/helpers/heldSortableScopeLockKeys.js +34 -0
- package/dist/cjs/src/decorators/field/sortable/helpers/performSortableDestroyWork.js +21 -0
- package/dist/cjs/src/decorators/field/sortable/helpers/performSortablePositionWork.js +36 -0
- package/dist/cjs/src/decorators/field/sortable/helpers/prepareSortableFieldsForDestroy.js +56 -0
- package/dist/cjs/src/decorators/field/sortable/helpers/prepareSortableFieldsForSave.js +84 -0
- package/dist/cjs/src/decorators/field/sortable/helpers/resortAllRecords.js +186 -29
- package/dist/cjs/src/decorators/field/sortable/helpers/setPosition.js +90 -46
- package/dist/cjs/src/decorators/field/sortable/helpers/sortableRowCache.js +73 -0
- package/dist/cjs/src/decorators/field/sortable/helpers/sortableScopeColumns.js +42 -0
- package/dist/cjs/src/decorators/field/sortable/helpers/sortableScopeLockKey.js +99 -0
- package/dist/cjs/src/decorators/field/sortable/helpers/sortableScopeLockKeys.js +72 -0
- package/dist/cjs/src/decorators/field/sortable/helpers/sortableSnapshot.js +101 -0
- package/dist/cjs/src/decorators/field/sortable/helpers/stabilizeSortableScopeLocks.js +86 -0
- package/dist/cjs/src/decorators/field/sortable/helpers/validPosition.js +21 -4
- package/dist/cjs/src/decorators/field/sortable/hooks/afterSortableDestroy.js +24 -7
- package/dist/cjs/src/decorators/field/sortable/hooks/afterSortableUpdate.js +10 -0
- package/dist/cjs/src/dream/DreamClassTransactionBuilder.js +0 -18
- package/dist/cjs/src/dream/Query.js +64 -32
- package/dist/cjs/src/dream/QueryDriver/Base.js +75 -2
- package/dist/cjs/src/dream/QueryDriver/Kysely.js +19 -12
- package/dist/cjs/src/dream/QueryDriver/Postgres.js +74 -0
- package/dist/cjs/src/dream/internal/destroyDream.js +60 -6
- package/dist/cjs/src/dream/internal/saveDream.js +109 -7
- package/dist/cjs/src/dream/internal/similarity/SimilarityBuilder.js +19 -4
- package/dist/cjs/src/dream/internal/softDeleteDream.js +13 -1
- package/dist/cjs/src/dream/internal/undestroyDream.js +56 -8
- package/dist/cjs/src/dream-app/index.js +21 -0
- package/dist/cjs/src/errors/SortableBatchRequiresTooManyScopeLocks.js +57 -0
- package/dist/cjs/src/errors/SortableRequiresAdvisoryTransactionLocks.js +26 -0
- package/dist/cjs/src/errors/SortableScopeDidNotStabilize.js +39 -0
- package/dist/cjs/src/errors/SortableScopeLockWaitTimedOut.js +50 -0
- package/dist/cjs/src/package-exports/errors.js +3 -0
- package/dist/esm/src/Dream.js +32 -17
- package/dist/esm/src/db/errors.js +4 -0
- package/dist/esm/src/decorators/field/sortable/Sortable.js +43 -118
- package/dist/esm/src/decorators/field/sortable/helpers/acquireSortableScopeLocks.js +38 -0
- package/dist/esm/src/decorators/field/sortable/helpers/acquireStabilizedSortableBatchLocks.js +183 -0
- package/dist/esm/src/decorators/field/sortable/helpers/acquireStabilizedSortableScopeLocks.js +40 -0
- package/dist/esm/src/decorators/field/sortable/helpers/canonicalScopeValue.js +194 -0
- package/dist/esm/src/decorators/field/sortable/helpers/clearCachedSortableValues.js +18 -0
- package/dist/esm/src/decorators/field/sortable/helpers/decrementScopedRecordsGreaterThanPosition.js +9 -9
- package/dist/esm/src/decorators/field/sortable/helpers/filterQueryToInstanceScope.js +20 -0
- package/dist/esm/src/decorators/field/sortable/helpers/filterQueryToScopeValues.js +32 -0
- package/dist/esm/src/decorators/field/sortable/helpers/heldSortableScopeLockKeys.js +34 -0
- package/dist/esm/src/decorators/field/sortable/helpers/performSortableDestroyWork.js +21 -0
- package/dist/esm/src/decorators/field/sortable/helpers/performSortablePositionWork.js +36 -0
- package/dist/esm/src/decorators/field/sortable/helpers/prepareSortableFieldsForDestroy.js +56 -0
- package/dist/esm/src/decorators/field/sortable/helpers/prepareSortableFieldsForSave.js +84 -0
- package/dist/esm/src/decorators/field/sortable/helpers/resortAllRecords.js +186 -29
- package/dist/esm/src/decorators/field/sortable/helpers/setPosition.js +90 -46
- package/dist/esm/src/decorators/field/sortable/helpers/sortableRowCache.js +73 -0
- package/dist/esm/src/decorators/field/sortable/helpers/sortableScopeColumns.js +42 -0
- package/dist/esm/src/decorators/field/sortable/helpers/sortableScopeLockKey.js +99 -0
- package/dist/esm/src/decorators/field/sortable/helpers/sortableScopeLockKeys.js +72 -0
- package/dist/esm/src/decorators/field/sortable/helpers/sortableSnapshot.js +101 -0
- package/dist/esm/src/decorators/field/sortable/helpers/stabilizeSortableScopeLocks.js +86 -0
- package/dist/esm/src/decorators/field/sortable/helpers/validPosition.js +21 -4
- package/dist/esm/src/decorators/field/sortable/hooks/afterSortableDestroy.js +24 -7
- package/dist/esm/src/decorators/field/sortable/hooks/afterSortableUpdate.js +10 -0
- package/dist/esm/src/dream/DreamClassTransactionBuilder.js +0 -18
- package/dist/esm/src/dream/Query.js +64 -32
- package/dist/esm/src/dream/QueryDriver/Base.js +75 -2
- package/dist/esm/src/dream/QueryDriver/Kysely.js +19 -12
- package/dist/esm/src/dream/QueryDriver/Postgres.js +74 -0
- package/dist/esm/src/dream/internal/destroyDream.js +60 -6
- package/dist/esm/src/dream/internal/saveDream.js +109 -7
- package/dist/esm/src/dream/internal/similarity/SimilarityBuilder.js +19 -4
- package/dist/esm/src/dream/internal/softDeleteDream.js +13 -1
- package/dist/esm/src/dream/internal/undestroyDream.js +56 -8
- package/dist/esm/src/dream-app/index.js +21 -0
- package/dist/esm/src/errors/SortableBatchRequiresTooManyScopeLocks.js +57 -0
- package/dist/esm/src/errors/SortableRequiresAdvisoryTransactionLocks.js +26 -0
- package/dist/esm/src/errors/SortableScopeDidNotStabilize.js +39 -0
- package/dist/esm/src/errors/SortableScopeLockWaitTimedOut.js +50 -0
- package/dist/esm/src/package-exports/errors.js +3 -0
- package/dist/types/src/Dream.d.ts +35 -2
- package/dist/types/src/db/errors.d.ts +2 -0
- package/dist/types/src/decorators/field/sortable/Sortable.d.ts +33 -0
- package/dist/types/src/decorators/field/sortable/helpers/acquireSortableScopeLocks.d.ts +25 -0
- package/dist/types/src/decorators/field/sortable/helpers/acquireStabilizedSortableBatchLocks.d.ts +91 -0
- package/dist/types/src/decorators/field/sortable/helpers/acquireStabilizedSortableScopeLocks.d.ts +31 -0
- package/dist/types/src/decorators/field/sortable/helpers/canonicalScopeValue.d.ts +85 -0
- package/dist/types/src/decorators/field/sortable/helpers/clearCachedSortableValues.d.ts +13 -0
- package/dist/types/src/decorators/field/sortable/helpers/decrementScopedRecordsGreaterThanPosition.d.ts +8 -1
- package/dist/types/src/decorators/field/sortable/helpers/filterQueryToInstanceScope.d.ts +24 -0
- package/dist/types/src/decorators/field/sortable/helpers/filterQueryToScopeValues.d.ts +22 -0
- package/dist/types/src/decorators/field/sortable/helpers/heldSortableScopeLockKeys.d.ts +27 -0
- package/dist/types/src/decorators/field/sortable/helpers/performSortableDestroyWork.d.ts +14 -0
- package/dist/types/src/decorators/field/sortable/helpers/performSortablePositionWork.d.ts +15 -0
- package/dist/types/src/decorators/field/sortable/helpers/prepareSortableFieldsForDestroy.d.ts +41 -0
- package/dist/types/src/decorators/field/sortable/helpers/prepareSortableFieldsForSave.d.ts +35 -0
- package/dist/types/src/decorators/field/sortable/helpers/resortAllRecords.d.ts +32 -0
- package/dist/types/src/decorators/field/sortable/helpers/setPosition.d.ts +27 -11
- package/dist/types/src/decorators/field/sortable/helpers/sortableRowCache.d.ts +48 -0
- package/dist/types/src/decorators/field/sortable/helpers/sortableScopeColumns.d.ts +30 -0
- package/dist/types/src/decorators/field/sortable/helpers/sortableScopeLockKey.d.ts +44 -0
- package/dist/types/src/decorators/field/sortable/helpers/sortableScopeLockKeys.d.ts +51 -0
- package/dist/types/src/decorators/field/sortable/helpers/sortableSnapshot.d.ts +92 -0
- package/dist/types/src/decorators/field/sortable/helpers/stabilizeSortableScopeLocks.d.ts +59 -0
- package/dist/types/src/decorators/field/sortable/helpers/validPosition.d.ts +14 -2
- package/dist/types/src/decorators/field/sortable/hooks/afterSortableCreate.d.ts +1 -1
- package/dist/types/src/decorators/field/sortable/hooks/afterSortableDestroy.d.ts +3 -1
- package/dist/types/src/decorators/field/sortable/hooks/afterSortableUpdate.d.ts +1 -1
- package/dist/types/src/dream/DreamClassTransactionBuilder.d.ts +3 -2
- package/dist/types/src/dream/Query.d.ts +44 -9
- package/dist/types/src/dream/QueryDriver/Base.d.ts +61 -1
- package/dist/types/src/dream/QueryDriver/Kysely.d.ts +6 -2
- package/dist/types/src/dream/QueryDriver/Postgres.d.ts +40 -0
- package/dist/types/src/dream/internal/softDeleteDream.d.ts +7 -1
- package/dist/types/src/dream-app/index.d.ts +18 -2
- package/dist/types/src/errors/SortableBatchRequiresTooManyScopeLocks.d.ts +37 -0
- package/dist/types/src/errors/SortableRequiresAdvisoryTransactionLocks.d.ts +13 -0
- package/dist/types/src/errors/SortableScopeDidNotStabilize.d.ts +23 -0
- package/dist/types/src/errors/SortableScopeLockWaitTimedOut.d.ts +34 -0
- package/dist/types/src/package-exports/errors.d.ts +3 -0
- package/dist/types/src/types/associations/shared.d.ts +1 -1
- package/dist/types/src/types/dream.d.ts +3 -0
- package/dist/types/src/types/query.d.ts +1 -1
- package/dist/types/src/types/types/associations/shared.ts +8 -6
- package/dist/types/src/types/types/dream.ts +6 -0
- package/dist/types/src/types/types/query.ts +1 -1
- package/docs/assets/hierarchy.js +1 -1
- package/docs/assets/navigation.js +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/classes/db.DreamMigrationHelpers.html +11 -11
- package/docs/classes/db.KyselyQueryDriver.html +88 -41
- package/docs/classes/db.PostgresQueryDriver.html +82 -42
- package/docs/classes/db.QueryDriverBase.html +86 -39
- 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.SortableBatchRequiresTooManyScopeLocks.html +61 -0
- package/docs/classes/errors.SortableScopeDidNotStabilize.html +48 -0
- package/docs/classes/errors.SortableScopeLockWaitTimedOut.html +58 -0
- 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 +159 -124
- package/docs/classes/index.DreamApp.html +11 -10
- package/docs/classes/index.DreamTransaction.html +2 -2
- package/docs/classes/index.Env.html +2 -2
- package/docs/classes/index.Query.html +122 -99
- package/docs/classes/system.CliFileWriter.html +4 -4
- package/docs/classes/system.DreamBin.html +2 -2
- package/docs/classes/system.DreamCLI.html +7 -7
- package/docs/classes/system.DreamImporter.html +2 -2
- package/docs/classes/system.DreamLogos.html +2 -2
- package/docs/classes/system.DreamSerializerBuilder.html +11 -11
- package/docs/classes/system.ObjectSerializerBuilder.html +8 -8
- package/docs/classes/system.PathHelpers.html +3 -3
- package/docs/classes/utils.Encrypt.html +3 -3
- package/docs/classes/utils.Range.html +2 -2
- package/docs/functions/db.closeAllDbConnections.html +1 -1
- package/docs/functions/db.dreamDbConnections.html +1 -1
- package/docs/functions/db.untypedDb.html +1 -1
- package/docs/functions/db.validateColumn.html +1 -1
- package/docs/functions/db.validateTable.html +1 -1
- package/docs/functions/errors.pgErrorType.html +1 -1
- package/docs/functions/index.DreamSerializer.html +1 -1
- package/docs/functions/index.ObjectSerializer.html +1 -1
- package/docs/functions/index.ReplicaSafe.html +1 -1
- package/docs/functions/index.STI.html +1 -1
- package/docs/functions/index.SoftDelete.html +1 -1
- package/docs/functions/utils.camelize.html +1 -1
- package/docs/functions/utils.capitalize.html +1 -1
- package/docs/functions/utils.cloneDeepSafe.html +1 -1
- package/docs/functions/utils.compact.html +1 -1
- package/docs/functions/utils.groupBy.html +1 -1
- package/docs/functions/utils.hyphenize.html +1 -1
- package/docs/functions/utils.intersection.html +1 -1
- package/docs/functions/utils.isEmpty.html +1 -1
- package/docs/functions/utils.normalizeUnicode.html +1 -1
- package/docs/functions/utils.pascalize.html +1 -1
- package/docs/functions/utils.percent.html +1 -1
- package/docs/functions/utils.range.html +1 -1
- package/docs/functions/utils.round.html +1 -1
- package/docs/functions/utils.sanitizeString.html +1 -1
- package/docs/functions/utils.snakeify.html +1 -1
- package/docs/functions/utils.sort.html +2 -2
- package/docs/functions/utils.sortBy.html +1 -1
- package/docs/functions/utils.sortObjectByKey.html +1 -1
- package/docs/functions/utils.sortObjectByValue.html +1 -1
- package/docs/functions/utils.uncapitalize.html +1 -1
- package/docs/functions/utils.uniq.html +1 -1
- package/docs/hierarchy.html +1 -1
- package/docs/interfaces/openapi.OpenapiDescription.html +2 -2
- package/docs/interfaces/openapi.OpenapiSchemaProperties.html +1 -1
- package/docs/interfaces/openapi.OpenapiSchemaPropertiesShorthand.html +1 -1
- package/docs/interfaces/openapi.OpenapiTypeFieldObject.html +1 -1
- package/docs/interfaces/types.BelongsToStatement.html +2 -2
- package/docs/interfaces/types.DecoratorContext.html +2 -2
- package/docs/interfaces/types.DreamAppInitOptions.html +2 -2
- package/docs/interfaces/types.DreamAppOpts.html +2 -2
- package/docs/interfaces/types.DreamDbConfig.html +5 -5
- package/docs/interfaces/types.DurationObject.html +2 -2
- package/docs/interfaces/types.EncryptOptions.html +2 -2
- package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
- package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
- package/docs/interfaces/types.SerializerRendererOpts.html +2 -2
- package/docs/modules/errors.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.OpenapiSchemaCommonFields.html +1 -1
- package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionRef.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +2 -2
- package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
- package/docs/types/openapi.OpenapiSchemaNull.html +2 -2
- package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAnyOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAnyOfShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectBase.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectOneOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectOneOfShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaPrimitiveGeneric.html +1 -1
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionAllOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
- package/docs/types/openapi.OpenapiSchemaString.html +1 -1
- package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
- package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
- package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
- package/docs/types/openapi.OpenapiTypeField.html +1 -1
- package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
- package/docs/types/types.CalendarDateDurationUnit.html +1 -1
- package/docs/types/types.CalendarDateObject.html +1 -1
- package/docs/types/types.Camelized.html +1 -1
- package/docs/types/types.ClockTimeObject.html +1 -1
- package/docs/types/types.DbConnectionType.html +1 -1
- package/docs/types/types.DbTypes.html +1 -1
- package/docs/types/types.DreamAssociationMetadata.html +1 -1
- package/docs/types/types.DreamAttributes.html +1 -1
- package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
- package/docs/types/types.DreamClassColumn.html +1 -1
- package/docs/types/types.DreamColumn.html +1 -1
- package/docs/types/types.DreamColumnNames.html +1 -1
- package/docs/types/types.DreamLogLevel.html +1 -1
- package/docs/types/types.DreamLogger.html +2 -2
- package/docs/types/types.DreamModelSerializerType.html +1 -1
- package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
- package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
- package/docs/types/types.DreamParamSafeAttributes.html +1 -1
- package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
- package/docs/types/types.DreamSerializable.html +1 -1
- package/docs/types/types.DreamSerializableArray.html +1 -1
- package/docs/types/types.DreamSerializerKey.html +1 -1
- package/docs/types/types.DreamSerializers.html +1 -1
- package/docs/types/types.DreamVirtualColumns.html +1 -1
- package/docs/types/types.DurationUnit.html +1 -1
- package/docs/types/types.EncryptAlgorithm.html +1 -1
- package/docs/types/types.HasManyStatement.html +1 -1
- package/docs/types/types.HasOneStatement.html +1 -1
- package/docs/types/types.Hyphenized.html +1 -1
- package/docs/types/types.Pascalized.html +1 -1
- package/docs/types/types.PrimaryKeyType.html +1 -1
- package/docs/types/types.RoundingPrecision.html +1 -1
- package/docs/types/types.SerializerCasing.html +1 -1
- package/docs/types/types.SimpleObjectSerializerType.html +1 -1
- package/docs/types/types.Snakeified.html +1 -1
- package/docs/types/types.StrictInterface.html +1 -1
- package/docs/types/types.UpdateableAssociationProperties.html +1 -1
- package/docs/types/types.UpdateableProperties.html +1 -1
- package/docs/types/types.ValidationType.html +1 -1
- package/docs/types/types.ViewModel.html +2 -2
- package/docs/types/types.ViewModelClass.html +1 -1
- package/docs/types/types.WeekdayName.html +1 -1
- package/docs/types/types.WhereStatementForDream.html +1 -1
- package/docs/types/types.WhereStatementForDreamClass.html +1 -1
- package/docs/variables/index.DreamConst.html +1 -1
- package/docs/variables/index.ops.html +1 -1
- package/docs/variables/openapi.openapiPrimitiveTypes.html +1 -1
- package/docs/variables/openapi.openapiShorthandPrimitiveTypes.html +1 -1
- package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
- package/docs/variables/system.primaryKeyTypes.html +1 -1
- package/package.json +1 -1
- package/dist/cjs/src/decorators/field/sortable/helpers/applySortableScopeToQuery.js +0 -13
- package/dist/cjs/src/decorators/field/sortable/helpers/sortableQueryExcludingDream.js +0 -7
- package/dist/cjs/src/decorators/field/sortable/hooks/beforeSortableSave.js +0 -41
- package/dist/esm/src/decorators/field/sortable/helpers/applySortableScopeToQuery.js +0 -13
- package/dist/esm/src/decorators/field/sortable/helpers/sortableQueryExcludingDream.js +0 -7
- package/dist/esm/src/decorators/field/sortable/hooks/beforeSortableSave.js +0 -41
- package/dist/types/src/decorators/field/sortable/helpers/applySortableScopeToQuery.d.ts +0 -12
- package/dist/types/src/decorators/field/sortable/helpers/sortableQueryExcludingDream.d.ts +0 -12
- package/dist/types/src/decorators/field/sortable/hooks/beforeSortableSave.d.ts +0 -12
|
@@ -265,21 +265,28 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
265
265
|
}
|
|
266
266
|
/**
|
|
267
267
|
* destroys a dream, possibly implementing soft delete if reallyDestroy is false
|
|
268
|
-
* and the record being deleted implements soft delete.
|
|
268
|
+
* and the record being deleted implements soft delete. Does nothing at all when a
|
|
269
|
+
* beforeDestroy hook called `preventDeletion`.
|
|
269
270
|
*
|
|
270
271
|
* @param dream - the dream instance you wish to destroy
|
|
271
272
|
* @param txn - a transaction to encapsulate, consistently provided by underlying dream mechanisms
|
|
272
273
|
* @param reallyDestroy - whether or not to reallyDestroy. If false, soft delete will be attempted when relevant
|
|
274
|
+
* @returns the number of rows removed — zero when a `preventDeletion` veto
|
|
275
|
+
* stopped the write, and zero when the row was already gone, which on the
|
|
276
|
+
* soft delete path includes a row that is already soft deleted
|
|
273
277
|
*/
|
|
274
278
|
static async destroyDream(dream, txn, reallyDestroy) {
|
|
279
|
+
if (dream['_preventDeletion'])
|
|
280
|
+
return 0;
|
|
275
281
|
if (shouldSoftDelete(dream, reallyDestroy)) {
|
|
276
|
-
await softDeleteDream(dream, txn);
|
|
282
|
+
return await softDeleteDream(dream, txn);
|
|
277
283
|
}
|
|
278
|
-
else
|
|
279
|
-
await txn.kyselyTransaction
|
|
284
|
+
else {
|
|
285
|
+
const results = await txn.kyselyTransaction
|
|
280
286
|
.deleteFrom(dream.table)
|
|
281
287
|
.where(dream['_primaryKey'], '=', dream.primaryKeyValue())
|
|
282
288
|
.execute();
|
|
289
|
+
return Number(results[0]?.numDeletedRows ?? 0);
|
|
283
290
|
}
|
|
284
291
|
}
|
|
285
292
|
/**
|
|
@@ -457,7 +464,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
457
464
|
bypassSelectAll: true,
|
|
458
465
|
bypassOrder: true,
|
|
459
466
|
});
|
|
460
|
-
kyselyQuery = kyselyQuery.select(max(columnName));
|
|
467
|
+
kyselyQuery = kyselyQuery.select(max(this.namespaceColumn(columnName)));
|
|
461
468
|
const data = await executeDatabaseQuery(kyselyQuery, 'executeTakeFirstOrThrow');
|
|
462
469
|
return data.max;
|
|
463
470
|
}
|
|
@@ -477,7 +484,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
477
484
|
async maxBy(groupColumn, aggregatedColumn) {
|
|
478
485
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
479
486
|
const { max } = this.dbFor('select').fn;
|
|
480
|
-
return this.groupedAggregate(groupColumn, max(aggregatedColumn));
|
|
487
|
+
return this.groupedAggregate(groupColumn, max(this.namespaceColumn(aggregatedColumn)));
|
|
481
488
|
}
|
|
482
489
|
/**
|
|
483
490
|
* Retrieves the min value of the specified column
|
|
@@ -498,7 +505,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
498
505
|
bypassSelectAll: true,
|
|
499
506
|
bypassOrder: true,
|
|
500
507
|
});
|
|
501
|
-
kyselyQuery = kyselyQuery.select(min(columnName));
|
|
508
|
+
kyselyQuery = kyselyQuery.select(min(this.namespaceColumn(columnName)));
|
|
502
509
|
const data = await executeDatabaseQuery(kyselyQuery, 'executeTakeFirstOrThrow');
|
|
503
510
|
return data.min;
|
|
504
511
|
}
|
|
@@ -518,7 +525,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
518
525
|
async minBy(groupColumn, aggregatedColumn) {
|
|
519
526
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
520
527
|
const { min } = this.dbFor('select').fn;
|
|
521
|
-
return this.groupedAggregate(groupColumn, min(aggregatedColumn));
|
|
528
|
+
return this.groupedAggregate(groupColumn, min(this.namespaceColumn(aggregatedColumn)));
|
|
522
529
|
}
|
|
523
530
|
/**
|
|
524
531
|
* Retrieves the sum value of the specified column
|
|
@@ -539,7 +546,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
539
546
|
bypassSelectAll: true,
|
|
540
547
|
bypassOrder: true,
|
|
541
548
|
});
|
|
542
|
-
kyselyQuery = kyselyQuery.select(sum(columnName));
|
|
549
|
+
kyselyQuery = kyselyQuery.select(sum(this.namespaceColumn(columnName)));
|
|
543
550
|
const data = await executeDatabaseQuery(kyselyQuery, 'executeTakeFirstOrThrow');
|
|
544
551
|
return data.sum === null ? null : parseFloat(data.sum);
|
|
545
552
|
}
|
|
@@ -559,7 +566,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
559
566
|
async sumBy(groupColumn, aggregatedColumn) {
|
|
560
567
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
561
568
|
const { sum } = this.dbFor('select').fn;
|
|
562
|
-
return this.groupedAggregate(groupColumn, sum(aggregatedColumn), rawSum => rawSum === null ? null : parseFloat(rawSum));
|
|
569
|
+
return this.groupedAggregate(groupColumn, sum(this.namespaceColumn(aggregatedColumn)), rawSum => rawSum === null ? null : parseFloat(rawSum));
|
|
563
570
|
}
|
|
564
571
|
/**
|
|
565
572
|
* Retrieves the average value of the specified column
|
|
@@ -580,7 +587,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
580
587
|
bypassSelectAll: true,
|
|
581
588
|
bypassOrder: true,
|
|
582
589
|
});
|
|
583
|
-
kyselyQuery = kyselyQuery.select(avg(columnName));
|
|
590
|
+
kyselyQuery = kyselyQuery.select(avg(this.namespaceColumn(columnName)));
|
|
584
591
|
const data = await executeDatabaseQuery(kyselyQuery, 'executeTakeFirstOrThrow');
|
|
585
592
|
return data.avg;
|
|
586
593
|
}
|
|
@@ -600,7 +607,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
600
607
|
async avgBy(groupColumn, aggregatedColumn) {
|
|
601
608
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
602
609
|
const { avg } = this.dbFor('select').fn;
|
|
603
|
-
return this.groupedAggregate(groupColumn, avg(aggregatedColumn));
|
|
610
|
+
return this.groupedAggregate(groupColumn, avg(this.namespaceColumn(aggregatedColumn)));
|
|
604
611
|
}
|
|
605
612
|
/**
|
|
606
613
|
* Retrieves the number of records in the database
|
|
@@ -10,7 +10,9 @@ import pg from 'pg';
|
|
|
10
10
|
import { sql } from 'kysely';
|
|
11
11
|
import DreamCLI from '../../cli/index.js';
|
|
12
12
|
import { isPrimitiveDataType } from '../../db/dataTypes.js';
|
|
13
|
+
import { LOCK_NOT_AVAILABLE, pgErrorType } from '../../db/errors.js';
|
|
13
14
|
import DreamApp from '../../dream-app/index.js';
|
|
15
|
+
import SortableScopeLockWaitTimedOut from '../../errors/SortableScopeLockWaitTimedOut.js';
|
|
14
16
|
import camelize from '../../helpers/camelize.js';
|
|
15
17
|
import { findCitextArrayOid, findCorrespondingArrayOid, findEnumArrayOids, parsePostgresBigint, parsePostgresDate, parsePostgresDatetime, parsePostgresDatetimeTz, parsePostgresDecimal, parsePostgresTime, parsePostgresTimeTz, } from '../../helpers/customPgParsers.js';
|
|
16
18
|
import { testDatabasePoolSize } from '../../db/testDatabasePool.js';
|
|
@@ -219,6 +221,78 @@ export default class PostgresQueryDriver extends KyselyQueryDriver {
|
|
|
219
221
|
applyRowLock(kyselyQuery, tableAlias) {
|
|
220
222
|
return kyselyQuery.forUpdate(tableAlias);
|
|
221
223
|
}
|
|
224
|
+
static supportsAdvisoryTransactionLocks = true;
|
|
225
|
+
/**
|
|
226
|
+
* @internal
|
|
227
|
+
*
|
|
228
|
+
* Postgres implementation of the transaction-scoped advisory lock seam.
|
|
229
|
+
*
|
|
230
|
+
* Uses the single-`bigint` form `pg_advisory_xact_lock(bigint)`, whose lock
|
|
231
|
+
* space is distinct from the two-`int4` form the test-database pool claims
|
|
232
|
+
* with (see {@link openTestDatabaseLockSession}), so an application lock can
|
|
233
|
+
* never collide with a pool claim.
|
|
234
|
+
*
|
|
235
|
+
* Every key is taken in one statement, over `unnest` of the key array: a
|
|
236
|
+
* function scan yields its rows in array order, so the caller's sorted
|
|
237
|
+
* acquisition order — the thing that keeps two operations over the same
|
|
238
|
+
* scopes from deadlocking — is preserved, while a batch that needs one key
|
|
239
|
+
* per candidate record costs one round trip rather than N.
|
|
240
|
+
*
|
|
241
|
+
* The locks are held until the enclosing transaction ends, and are waiting
|
|
242
|
+
* (no `NOWAIT`): a second transaction asking for the same key blocks at that
|
|
243
|
+
* key's row until the holder commits or rolls back. Because they are
|
|
244
|
+
* transaction-scoped there is nothing to release, and re-acquiring a key
|
|
245
|
+
* within one transaction is a no-op rather than an error.
|
|
246
|
+
*
|
|
247
|
+
* The wait is bounded by `lock_timeout`, taken from
|
|
248
|
+
* {@link DreamApp.sortableScopeLockTimeout} and applied to the acquisition
|
|
249
|
+
* statement alone: the prior value is read in the same statement that sets
|
|
250
|
+
* the bound and restored in the next one, so the transaction's row-lock waits
|
|
251
|
+
* keep whatever the application configured. Postgres cancels the statement
|
|
252
|
+
* with `55P03` when the bound elapses, and that is translated here into
|
|
253
|
+
* {@link SortableScopeLockWaitTimedOut} so no Postgres error code escapes the
|
|
254
|
+
* driver.
|
|
255
|
+
*
|
|
256
|
+
* A timeout of `0` means "no bound", which is what `lock_timeout` itself
|
|
257
|
+
* means by it, and is sent as the bound rather than skipped: an application
|
|
258
|
+
* whose connection carries a `lock_timeout` of its own would otherwise have
|
|
259
|
+
* its acquisition cancelled by that bound, which is neither the unbounded
|
|
260
|
+
* wait `0` asks for nor a cancellation this driver could translate.
|
|
261
|
+
*/
|
|
262
|
+
static async acquireAdvisoryTransactionLocks(txn, keys) {
|
|
263
|
+
if (!keys.length)
|
|
264
|
+
return;
|
|
265
|
+
// the keys are passed as text and cast, since a JS bigint is not a value
|
|
266
|
+
// the pg driver can bind directly
|
|
267
|
+
const lockKeys = keys.map(key => key.toString());
|
|
268
|
+
const timeoutMs = DreamApp.getOrFail().sortableScopeLockTimeout;
|
|
269
|
+
let priorLockTimeout;
|
|
270
|
+
try {
|
|
271
|
+
// one statement, three things in a forced order: `bounded` selects from
|
|
272
|
+
// `prior_setting`, so the prior value is read before the bound replaces
|
|
273
|
+
// it, and both are `from` items, so both are scanned before the target
|
|
274
|
+
// list takes a single lock. Merging them costs nothing where separate
|
|
275
|
+
// statements would cost round trips inside the lock window.
|
|
276
|
+
//
|
|
277
|
+
// The returned column is aliased to a single word deliberately: result
|
|
278
|
+
// keys come back camelized, and a one-word alias is the same either way
|
|
279
|
+
const result = await sql `
|
|
280
|
+
with prior_setting as (select current_setting('lock_timeout') as value),
|
|
281
|
+
bounded as (select set_config('lock_timeout', ${timeoutMs.toString()}, true) as value from prior_setting)
|
|
282
|
+
select prior_setting.value as prior, pg_advisory_xact_lock(sortable_lock_keys.lock_key)
|
|
283
|
+
from prior_setting, bounded, unnest(${lockKeys}::bigint[]) as sortable_lock_keys(lock_key)
|
|
284
|
+
`.execute(txn.kyselyTransaction);
|
|
285
|
+
priorLockTimeout = result.rows[0].prior;
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
if (pgErrorType(error) === LOCK_NOT_AVAILABLE)
|
|
289
|
+
throw new SortableScopeLockWaitTimedOut(timeoutMs);
|
|
290
|
+
throw error;
|
|
291
|
+
}
|
|
292
|
+
// `current_setting` returns the value with its unit ('0', '5s', '250ms'),
|
|
293
|
+
// which is a form `set_config` accepts back unchanged
|
|
294
|
+
await sql `select set_config('lock_timeout', ${priorLockTimeout}, true)`.execute(txn.kyselyTransaction);
|
|
295
|
+
}
|
|
222
296
|
static supportsParallelTestDatabases = true;
|
|
223
297
|
/**
|
|
224
298
|
* @internal
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import performSortableDestroyWork from '../../decorators/field/sortable/helpers/performSortableDestroyWork.js';
|
|
2
|
+
import prepareSortableFieldsForDestroy, { clearSortableFieldsForDestroy, } from '../../decorators/field/sortable/helpers/prepareSortableFieldsForDestroy.js';
|
|
1
3
|
import DreamApp from '../../dream-app/index.js';
|
|
2
4
|
import destroyAssociatedRecords from './destroyAssociatedRecords.js';
|
|
3
5
|
import runHooksFor from './runHooksFor.js';
|
|
@@ -29,6 +31,17 @@ export default async function destroyDream(dream, txn = null, options) {
|
|
|
29
31
|
*/
|
|
30
32
|
async function destroyDreamWithTransaction(dream, txn, options) {
|
|
31
33
|
const { cascade, reallyDestroy, skipHooks } = options;
|
|
34
|
+
if (!skipHooks)
|
|
35
|
+
await runHooksFor('beforeDestroy', dream, true, null, txn);
|
|
36
|
+
// `preventDeletion` is fail-closed: a veto returns before the
|
|
37
|
+
// dependent-destroy cascade, so neither this record nor any of its
|
|
38
|
+
// `dependent: 'destroy'` associations are touched. The check sits outside the
|
|
39
|
+
// hook guard because the flag can also be set by the caller ahead of a
|
|
40
|
+
// `skipHooks` destroy, which runs no hooks but must not cascade either.
|
|
41
|
+
// Recursing through the cascade means a vetoing descendant stops its own
|
|
42
|
+
// subtree while the destroy that reached it carries on.
|
|
43
|
+
if (dream['_preventDeletion'])
|
|
44
|
+
return dream;
|
|
32
45
|
if (cascade) {
|
|
33
46
|
// NOTE: the dependent-destroy tree is loaded lazily, inside
|
|
34
47
|
// destroyAssociatedRecords, so that a cascaded descendant whose
|
|
@@ -37,15 +50,56 @@ async function destroyDreamWithTransaction(dream, txn, options) {
|
|
|
37
50
|
// clone: the hooks and the delete below operate on the original.
|
|
38
51
|
await destroyAssociatedRecords(dream, txn, options);
|
|
39
52
|
}
|
|
40
|
-
|
|
41
|
-
|
|
53
|
+
// Sortable preparation runs as a phase after every user beforeDestroy hook,
|
|
54
|
+
// never as a hook among them: the destroy's whole advisory key set — every
|
|
55
|
+
// sortable field — is acquired in one sorted pass, and the row's real
|
|
56
|
+
// position and scope values are read in one snapshot SELECT while the row
|
|
57
|
+
// still exists. It sits after the cascade so this destroy does not hold the
|
|
58
|
+
// scope lock across every descendant's destroy — true of the destroy itself,
|
|
59
|
+
// not of the transaction it runs in: under `Query#destroy({ lock: true })`
|
|
60
|
+
// the batch's whole key set is already held, taken before any row was
|
|
61
|
+
// claimed. A destroy that skips hooks performs no compaction, and one a
|
|
62
|
+
// cascaded descendant's hook has vetoed will not delete, so neither needs
|
|
63
|
+
// locks or a snapshot.
|
|
64
|
+
let rowFoundBeforeDelete = true;
|
|
65
|
+
if (!skipHooks && !dream['_preventDeletion']) {
|
|
66
|
+
rowFoundBeforeDelete = await prepareSortableFieldsForDestroy(dream, txn);
|
|
42
67
|
}
|
|
43
|
-
await maybeDestroyDream(dream, txn, reallyDestroy);
|
|
44
|
-
|
|
68
|
+
const rowsRemoved = await maybeDestroyDream(dream, txn, reallyDestroy);
|
|
69
|
+
// `preventDeletion` can still arrive after the veto check above has passed:
|
|
70
|
+
// a cascaded descendant's hook can reach back to this record and call it. The
|
|
71
|
+
// record is then still present, so neither the after-destroy hooks nor the
|
|
72
|
+
// reload of a soft deleted row have anything to run against, and the driver
|
|
73
|
+
// leaves the row alone. A veto that late cannot be made fail-closed against
|
|
74
|
+
// the cascade — the descendants are already destroyed by the time it lands.
|
|
75
|
+
const deletionPrevented = dream['_preventDeletion'];
|
|
76
|
+
// A destroy of a row that is already gone — a second `destroy()` on the same
|
|
77
|
+
// instance, a job retry, two workers racing — removes nothing, so there is
|
|
78
|
+
// nothing for the after-destroy hooks to react to and, for a sortable model,
|
|
79
|
+
// no vacancy to close. Compacting anyway would shift the scope a second time
|
|
80
|
+
// from the instance's remembered position, moving survivors onto occupied
|
|
81
|
+
// positions. A veto also removes nothing, and keeps its own path: it is
|
|
82
|
+
// recognized by the flag above, never by the count.
|
|
83
|
+
//
|
|
84
|
+
// Both signals are read because they catch opposite races. The count is the
|
|
85
|
+
// stronger of the two — a writer outside the sortable path can remove the row
|
|
86
|
+
// after the snapshot read, and only the count sees that. The snapshot's
|
|
87
|
+
// existence signal catches the mirror shape: the row was already gone when
|
|
88
|
+
// the snapshot read ran, so this destroy holds no position to vacate, and a
|
|
89
|
+
// row that reappeared under the same primary key is what the delete removed.
|
|
90
|
+
const removedNothing = !deletionPrevented && (rowsRemoved === 0 || !rowFoundBeforeDelete);
|
|
91
|
+
if (removedNothing)
|
|
92
|
+
clearSortableFieldsForDestroy(dream);
|
|
93
|
+
if (!skipHooks && !deletionPrevented && !removedNothing) {
|
|
94
|
+
// The compaction is a phase before the after-destroy hooks, never a hook
|
|
95
|
+
// among them, so every user afterDestroy hook observes the compacted scope
|
|
96
|
+
// regardless of where it was declared — the destroy-side counterpart of
|
|
97
|
+
// performSortablePositionWork running before the after-save hooks.
|
|
98
|
+
await performSortableDestroyWork(dream, txn);
|
|
45
99
|
await runHooksFor('afterDestroy', dream, true, null, txn);
|
|
46
100
|
await runHooksFor('afterDestroyCommit', dream, true, null, txn);
|
|
47
101
|
}
|
|
48
|
-
if (shouldSoftDelete(dream, reallyDestroy)) {
|
|
102
|
+
if (shouldSoftDelete(dream, reallyDestroy) && !deletionPrevented) {
|
|
49
103
|
await dream.txn(txn).reload();
|
|
50
104
|
}
|
|
51
105
|
return dream;
|
|
@@ -62,5 +116,5 @@ function shouldSoftDelete(dream, reallyDestroy) {
|
|
|
62
116
|
*/
|
|
63
117
|
async function maybeDestroyDream(dream, txn, reallyDestroy) {
|
|
64
118
|
const dbDriverClass = DreamApp.getOrFail().dbConnectionQueryDriverClass(dream.connectionName);
|
|
65
|
-
await dbDriverClass.destroyDream(dream, txn, reallyDestroy);
|
|
119
|
+
return await dbDriverClass.destroyDream(dream, txn, reallyDestroy);
|
|
66
120
|
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import acquireStabilizedSortableScopeLocks from '../../decorators/field/sortable/helpers/acquireStabilizedSortableScopeLocks.js';
|
|
2
|
+
import { clearSortableSaveState } from '../../decorators/field/sortable/helpers/clearCachedSortableValues.js';
|
|
3
|
+
import performSortablePositionWork from '../../decorators/field/sortable/helpers/performSortablePositionWork.js';
|
|
4
|
+
import prepareSortableFieldsForSave from '../../decorators/field/sortable/helpers/prepareSortableFieldsForSave.js';
|
|
5
|
+
import { cacheSortableSnapshots } from '../../decorators/field/sortable/helpers/sortableSnapshot.js';
|
|
1
6
|
import ValidationError from '../../errors/ValidationError.js';
|
|
2
7
|
import sqlAttributes from '../../helpers/sqlAttributes.js';
|
|
3
8
|
import { DateTime } from '../../utils/datetime/DateTime.js';
|
|
@@ -13,6 +18,27 @@ export default async function saveDream(dream, txn = null, { skipHooks = false }
|
|
|
13
18
|
await runHooksFor('beforeCreate', dream, alreadyPersisted, null, txn);
|
|
14
19
|
await runHooksFor('beforeSave', dream, alreadyPersisted, null, txn);
|
|
15
20
|
}
|
|
21
|
+
// Where this save opens a transaction of its own (below), a failure inside it
|
|
22
|
+
// rolls the driver write back, so the instance has to be able to go back to
|
|
23
|
+
// what it was when `save` was called: an instance left holding the primary
|
|
24
|
+
// key of a rolled-back INSERT — and left clean, so that the retry the failure
|
|
25
|
+
// invites returns at the `isDirty` check below without writing anything — is
|
|
26
|
+
// a record whose row does not exist. Captured before the sortable phase
|
|
27
|
+
// applies its position sentinel, so the retry sees the caller's own position.
|
|
28
|
+
// Only a model declaring sortable fields can reach that transaction, so a save
|
|
29
|
+
// of any other model copies nothing.
|
|
30
|
+
const preSaveState = txn || !dream.constructor['sortableFields'].length
|
|
31
|
+
? null
|
|
32
|
+
: capturePersistenceState(dream);
|
|
33
|
+
// Sortable computes each record's position under an advisory lock on its sort
|
|
34
|
+
// scope, and that lock is transaction-scoped, so the write and the position
|
|
35
|
+
// write that follows it have to commit together. Preparation runs here as a
|
|
36
|
+
// called phase — after every consumer before-hook, never as a hook among
|
|
37
|
+
// them — so that a position or scope change a user's own `beforeSave` hook
|
|
38
|
+
// introduced is inside the boundary regardless of where the hook was
|
|
39
|
+
// registered. Seated before the change capture, so the position sentinel
|
|
40
|
+
// lands in `beforeSaveChanges` exactly as it always has.
|
|
41
|
+
const sortableFieldConfigs = prepareSortableFieldsForSave(dream, { alreadyPersisted, skipHooks });
|
|
16
42
|
const beforeSaveChanges = dream.changes();
|
|
17
43
|
// need to check validations after running before hooks, or else
|
|
18
44
|
// model hooks that might make a model valid cannot run
|
|
@@ -20,6 +46,69 @@ export default async function saveDream(dream, txn = null, { skipHooks = false }
|
|
|
20
46
|
throw new ValidationError(dream['sanitizedConstructorName'], dream.errors);
|
|
21
47
|
if (alreadyPersisted && !dream.isDirty)
|
|
22
48
|
return dream;
|
|
49
|
+
if (sortableFieldConfigs.length && !txn) {
|
|
50
|
+
// The self-opened transaction holds only Dream's own work: the scope
|
|
51
|
+
// locks, the driver write, and the position write, which is its last
|
|
52
|
+
// statement. COMMIT releases the scope locks before any user after-hook
|
|
53
|
+
// runs; the after-hook and `*Commit` families then run with no
|
|
54
|
+
// transaction, exactly as on a save of a non-sortable model, so a hook
|
|
55
|
+
// that throws cannot roll back the committed row and a slow hook blocks
|
|
56
|
+
// no other writer of the scope.
|
|
57
|
+
try {
|
|
58
|
+
await dream.constructor.transaction(async (sortableTxn) => await writeDream(dream, sortableTxn, { alreadyPersisted, sortableFieldConfigs }));
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
restorePersistenceState(dream, preSaveState);
|
|
62
|
+
clearSortableSaveState(dream, sortableFieldConfigs);
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
await runAfterSaveHooks(dream, null, { skipHooks, alreadyPersisted, beforeSaveChanges });
|
|
66
|
+
return dream;
|
|
67
|
+
}
|
|
68
|
+
await writeDream(dream, txn, { alreadyPersisted, sortableFieldConfigs });
|
|
69
|
+
await runAfterSaveHooks(dream, txn, { skipHooks, alreadyPersisted, beforeSaveChanges });
|
|
70
|
+
return dream;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Everything a write changes about how the instance answers "am I saved, and
|
|
74
|
+
* what has changed since?" — the persisted flag, the attributes the driver's
|
|
75
|
+
* `RETURNING` wrote (the primary key among them), and the three attribute
|
|
76
|
+
* snapshots the dirty-checking reads.
|
|
77
|
+
*/
|
|
78
|
+
function capturePersistenceState(dream) {
|
|
79
|
+
return {
|
|
80
|
+
isPersisted: dream.isPersisted,
|
|
81
|
+
frozenAttributes: { ...dream['frozenAttributes'] },
|
|
82
|
+
originalAttributes: { ...dream['originalAttributes'] },
|
|
83
|
+
attributesFromBeforeLastSave: { ...dream['attributesFromBeforeLastSave'] },
|
|
84
|
+
currentAttributes: { ...dream['currentAttributes'] },
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function restorePersistenceState(dream, state) {
|
|
88
|
+
dream['isPersisted'] = state.isPersisted;
|
|
89
|
+
dream['frozenAttributes'] = state.frozenAttributes;
|
|
90
|
+
dream['originalAttributes'] = state.originalAttributes;
|
|
91
|
+
dream['attributesFromBeforeLastSave'] = state.attributesFromBeforeLastSave;
|
|
92
|
+
dream['currentAttributes'] = state.currentAttributes;
|
|
93
|
+
}
|
|
94
|
+
async function writeDream(dream, txn, { alreadyPersisted, sortableFieldConfigs, }) {
|
|
95
|
+
let rowFoundBeforeWrite = true;
|
|
96
|
+
// A position- or scope-changing update takes its scope locks here, before the
|
|
97
|
+
// write, because the write overwrites the scope column: after it, neither the
|
|
98
|
+
// scope the record is moving out of nor its real position is readable from the
|
|
99
|
+
// row. The locks are therefore held across the update and the position write
|
|
100
|
+
// that follows it — one transaction, one acquisition — and the snapshot the
|
|
101
|
+
// position work is computed from is read here, under them.
|
|
102
|
+
//
|
|
103
|
+
// A create locks later, around its own position computation: it has no scope
|
|
104
|
+
// to move out of and no prior row to snapshot, and the deferrable unique
|
|
105
|
+
// constraint sortable tables carry tolerates two uncommitted rows at the
|
|
106
|
+
// pre-write position sentinel.
|
|
107
|
+
if (txn && alreadyPersisted && sortableFieldConfigs.length) {
|
|
108
|
+
const { rowExists, snapshots } = await acquireStabilizedSortableScopeLocks(dream, txn, sortableFieldConfigs);
|
|
109
|
+
rowFoundBeforeWrite = rowExists;
|
|
110
|
+
cacheSortableSnapshots(dream, snapshots);
|
|
111
|
+
}
|
|
23
112
|
const now = DateTime.now();
|
|
24
113
|
if (!alreadyPersisted && !dream.createdAt && dream.columns().has('createdAt'))
|
|
25
114
|
dream.createdAt = now;
|
|
@@ -41,14 +130,27 @@ export default async function saveDream(dream, txn = null, { skipHooks = false }
|
|
|
41
130
|
dream['freezeAttributes']();
|
|
42
131
|
dream['attributesFromBeforeLastSave'] = dream['originalAttributes'];
|
|
43
132
|
dream['originalAttributes'] = dream.getAttributes();
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
133
|
+
// An update whose row the snapshot read positively found gone holds no
|
|
134
|
+
// position to move: the vacancy it remembers was closed by whoever removed
|
|
135
|
+
// the row, and shifting the scope from the instance's remembered position
|
|
136
|
+
// would move survivors onto occupied ones. The cached values and the empty
|
|
137
|
+
// snapshot go with it, so a later save of this instance reads the row itself.
|
|
138
|
+
if (txn && sortableFieldConfigs.length) {
|
|
139
|
+
if (rowFoundBeforeWrite)
|
|
140
|
+
await performSortablePositionWork(dream, txn, sortableFieldConfigs, { alreadyPersisted });
|
|
48
141
|
else
|
|
49
|
-
|
|
50
|
-
await runHooksFor(alreadyPersisted ? 'afterUpdateCommit' : 'afterCreateCommit', dream, alreadyPersisted, beforeSaveChanges, txn);
|
|
51
|
-
await runHooksFor('afterSaveCommit', dream, alreadyPersisted, beforeSaveChanges, txn);
|
|
142
|
+
clearSortableSaveState(dream, sortableFieldConfigs);
|
|
52
143
|
}
|
|
53
144
|
return dream;
|
|
54
145
|
}
|
|
146
|
+
async function runAfterSaveHooks(dream, txn, { skipHooks, alreadyPersisted, beforeSaveChanges, }) {
|
|
147
|
+
if (skipHooks)
|
|
148
|
+
return;
|
|
149
|
+
await runHooksFor('afterSave', dream, alreadyPersisted, beforeSaveChanges, txn);
|
|
150
|
+
if (alreadyPersisted)
|
|
151
|
+
await runHooksFor('afterUpdate', dream, alreadyPersisted, beforeSaveChanges, txn);
|
|
152
|
+
else
|
|
153
|
+
await runHooksFor('afterCreate', dream, alreadyPersisted, beforeSaveChanges, txn);
|
|
154
|
+
await runHooksFor(alreadyPersisted ? 'afterUpdateCommit' : 'afterCreateCommit', dream, alreadyPersisted, beforeSaveChanges, txn);
|
|
155
|
+
await runHooksFor('afterSaveCommit', dream, alreadyPersisted, beforeSaveChanges, txn);
|
|
156
|
+
}
|
|
@@ -8,7 +8,8 @@ import namespaceColumn from '../../../helpers/namespaceColumn.js';
|
|
|
8
8
|
import { TRIGRAM_OPERATORS } from '../../constants.js';
|
|
9
9
|
import similaritySelectSql from './similaritySelectSql.js';
|
|
10
10
|
import similarityWhereSql from './similarityWhereSql.js';
|
|
11
|
-
import
|
|
11
|
+
import DreamApp from '../../../dream-app/index.js';
|
|
12
|
+
import PostgresQueryDriver from '../../QueryDriver/Postgres.js';
|
|
12
13
|
export default class SimilarityBuilder extends ConnectedToDB {
|
|
13
14
|
whereStatement;
|
|
14
15
|
whereNotStatement;
|
|
@@ -215,7 +216,7 @@ export default class SimilarityBuilder extends ConnectedToDB {
|
|
|
215
216
|
const schema = this.dreamClass.prototype.schema;
|
|
216
217
|
const primaryKeyName = this.dreamClass.primaryKey;
|
|
217
218
|
const { tableName, tableAlias, columnName } = similarityStatement;
|
|
218
|
-
const queryDriverClass =
|
|
219
|
+
const queryDriverClass = postgresQueryDriverForSimilaritySearch(this.dreamInstance.connectionName);
|
|
219
220
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
220
221
|
const { ref } = queryDriverClass.dbFor(this.dreamInstance.connectionName, this.dbConnectionType('select')).dynamic;
|
|
221
222
|
const validatedTableAlias = validateTableAlias(tableAlias);
|
|
@@ -247,7 +248,7 @@ export default class SimilarityBuilder extends ConnectedToDB {
|
|
|
247
248
|
statementType,
|
|
248
249
|
});
|
|
249
250
|
const trigramSearchAlias = this.similaritySearchId(tableAlias, columnName);
|
|
250
|
-
const queryDriverClass =
|
|
251
|
+
const queryDriverClass = postgresQueryDriverForSimilaritySearch(this.dreamInstance.connectionName);
|
|
251
252
|
const selectQuery = queryDriverClass
|
|
252
253
|
.dbFor(this.dreamInstance.connectionName, this.dbConnectionType('select'))
|
|
253
254
|
.selectFrom(validatedTableAlias)
|
|
@@ -261,7 +262,7 @@ export default class SimilarityBuilder extends ConnectedToDB {
|
|
|
261
262
|
const { columnName, opsStatement, tableName } = similarityStatement;
|
|
262
263
|
const validatedTable = validateTable(schema, tableName);
|
|
263
264
|
const validatedPrimaryKey = validateColumn(schema, tableName, primaryKeyName);
|
|
264
|
-
const queryDriverClass =
|
|
265
|
+
const queryDriverClass = postgresQueryDriverForSimilaritySearch(this.dreamInstance.connectionName);
|
|
265
266
|
let nestedQuery = queryDriverClass
|
|
266
267
|
.dbFor(this.dreamInstance.connectionName, this.dbConnectionType('select'))
|
|
267
268
|
.selectFrom(tableName)
|
|
@@ -314,6 +315,20 @@ export default class SimilarityBuilder extends ConnectedToDB {
|
|
|
314
315
|
return `${statementType}_rank_${statementIndex + 1}`;
|
|
315
316
|
}
|
|
316
317
|
}
|
|
318
|
+
/**
|
|
319
|
+
* Trigram similarity search is built on Postgres's `pg_trgm` operators and on
|
|
320
|
+
* kysely builders taken straight off the Postgres driver, so it resolves the
|
|
321
|
+
* connection's query driver to that driver — accepting a consumer subclass of
|
|
322
|
+
* it, which inherits the same operators — and otherwise fails loud rather than
|
|
323
|
+
* emitting SQL another engine cannot run.
|
|
324
|
+
*/
|
|
325
|
+
function postgresQueryDriverForSimilaritySearch(connectionName) {
|
|
326
|
+
const queryDriverClass = DreamApp.getOrFail().dbConnectionQueryDriverClass(connectionName);
|
|
327
|
+
if (queryDriverClass !== PostgresQueryDriver &&
|
|
328
|
+
!(queryDriverClass.prototype instanceof PostgresQueryDriver))
|
|
329
|
+
throw new Error(`${queryDriverClass.name} is not the PostgresQueryDriver, nor a subclass of it. Similarity search (similarity, wordSimilarity, strictWordSimilarity) requires the PostgresQueryDriver.`);
|
|
330
|
+
return queryDriverClass;
|
|
331
|
+
}
|
|
317
332
|
export const SIMILARITY_TYPES = ['where', 'join_and'];
|
|
318
333
|
function removeJoinAndFromObjectHierarchy(obj) {
|
|
319
334
|
if (obj?.isOpsStatement)
|
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
import MissingDeletedAtFieldForSoftDelete from '../../errors/MissingDeletedAtFieldForSoftDelete.js';
|
|
2
2
|
import isDatetimeOrDatetimeArrayColumn from '../../helpers/db/types/isDatetimeOrDatetimeArrayColumn.js';
|
|
3
3
|
import { DateTime } from '../../utils/datetime/DateTime.js';
|
|
4
|
+
/**
|
|
5
|
+
* @internal
|
|
6
|
+
*
|
|
7
|
+
* @returns the number of rows soft deleted — zero when the row is already gone,
|
|
8
|
+
* and zero when it is already soft deleted
|
|
9
|
+
*/
|
|
4
10
|
export default async function softDeleteDream(dream, txn) {
|
|
5
11
|
const deletedAtField = dream['_deletedAtField'];
|
|
6
12
|
const dreamClass = dream.constructor;
|
|
7
13
|
if (!isDatetimeOrDatetimeArrayColumn(dreamClass, deletedAtField)) {
|
|
8
14
|
throw new MissingDeletedAtFieldForSoftDelete(dream.constructor);
|
|
9
15
|
}
|
|
16
|
+
// An already soft deleted row is logically gone, and this UPDATE is what
|
|
17
|
+
// reports the deletion to the caller: matching such a row again would rewrite
|
|
18
|
+
// its `deletedAt` and count a deletion this destroy did not perform, running
|
|
19
|
+
// the after-destroy hooks a second time on a record nothing happened to.
|
|
10
20
|
let query = txn.kyselyTransaction
|
|
11
21
|
.updateTable(dream.table)
|
|
12
22
|
.where(dream['_primaryKey'], '=', dream.primaryKeyValue())
|
|
23
|
+
.where(deletedAtField, 'is', null)
|
|
13
24
|
.set(dream['_deletedAtField'], DateTime.now());
|
|
14
25
|
dreamClass['sortableFields']?.forEach(sortableFieldMetadata => {
|
|
15
26
|
const positionColumn = sortableFieldMetadata.positionField;
|
|
16
27
|
query = query.set(positionColumn, null);
|
|
17
28
|
});
|
|
18
|
-
await query.execute();
|
|
29
|
+
const results = await query.execute();
|
|
30
|
+
return Number(results[0]?.numUpdatedRows ?? 0);
|
|
19
31
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import acquireStabilizedSortableScopeLocks from '../../decorators/field/sortable/helpers/acquireStabilizedSortableScopeLocks.js';
|
|
2
|
+
import filterQueryToScopeValues from '../../decorators/field/sortable/helpers/filterQueryToScopeValues.js';
|
|
3
|
+
import { snapshotScopeValue } from '../../decorators/field/sortable/helpers/sortableSnapshot.js';
|
|
2
4
|
import runHooksFor from './runHooksFor.js';
|
|
3
5
|
/**
|
|
4
6
|
* @internal
|
|
@@ -31,11 +33,23 @@ async function undestroyDreamWithTransaction(dream, txn, options) {
|
|
|
31
33
|
if (!skipHooks) {
|
|
32
34
|
await runHooksFor('beforeUpdate', dream, true, null, txn);
|
|
33
35
|
}
|
|
34
|
-
|
|
36
|
+
// The cascade runs ahead of the restore, matching `destroyDream`: this
|
|
37
|
+
// record's own scope lock is taken inside `doUndestroyDream`, so a cascade
|
|
38
|
+
// seated after it would hold that lock across every descendant's undestroy,
|
|
39
|
+
// and a parent and its children would take their advisory keys in the
|
|
40
|
+
// opposite order to the one the destroy side takes them in. The nested select
|
|
41
|
+
// the cascade builds bypasses the soft-delete scope on this record's own
|
|
42
|
+
// class (`undestroyOptions`), so it reaches the children while this record is
|
|
43
|
+
// still deleted.
|
|
35
44
|
if (cascade) {
|
|
36
45
|
await undestroyAssociatedRecords(dream, txn, options);
|
|
37
46
|
}
|
|
38
|
-
|
|
47
|
+
const restoredRowCount = await doUndestroyDream(dream, txn);
|
|
48
|
+
// A restore of a row that is not deleted — a second `undestroy()`, a job
|
|
49
|
+
// retry, or a record that was never destroyed, which `Query#undestroy` reaches
|
|
50
|
+
// routinely since it lifts the soft-delete scope and walks every match —
|
|
51
|
+
// matches no row, so there is nothing for the after-update hooks to react to.
|
|
52
|
+
if (!skipHooks && restoredRowCount > 0) {
|
|
39
53
|
await runHooksFor('afterUpdate', dream, true, null, txn);
|
|
40
54
|
await runHooksFor('afterUpdateCommit', dream, true, null, txn);
|
|
41
55
|
}
|
|
@@ -45,22 +59,56 @@ async function undestroyDreamWithTransaction(dream, txn, options) {
|
|
|
45
59
|
/**
|
|
46
60
|
* @internal
|
|
47
61
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
62
|
+
* Clears the record's `deletedAt` and, for a sortable model, gives it a
|
|
63
|
+
* position at the end of the sort scope its row physically occupies.
|
|
64
|
+
*
|
|
65
|
+
* Restoring is conditional on the row still being deleted, and the count of
|
|
66
|
+
* rows the statement matched is what the caller reads that off. A row that is
|
|
67
|
+
* already back would otherwise be inside the `max(position)` its new position
|
|
68
|
+
* is computed from, and would move one past itself on every repeat — a
|
|
69
|
+
* permanent gap in the scope.
|
|
70
|
+
*
|
|
71
|
+
* @returns the number of rows restored: 1, or 0 when the row was not deleted
|
|
50
72
|
*/
|
|
51
73
|
async function doUndestroyDream(dream, txn) {
|
|
52
74
|
const updateStatement = txn.kyselyTransaction.updateTable(dream.table);
|
|
53
75
|
let query = updateStatement
|
|
54
76
|
.where(dream['_primaryKey'], '=', dream.primaryKeyValue())
|
|
77
|
+
.where(dream['_deletedAtField'], 'is not', null)
|
|
55
78
|
.set({ [dream['_deletedAtField']]: null });
|
|
56
79
|
const dreamClass = dream.constructor;
|
|
57
|
-
dreamClass['sortableFields']
|
|
80
|
+
const sortableFields = (dreamClass['sortableFields'] ?? []);
|
|
81
|
+
// Undestroy has no sortable hook of its own — the position is recomputed
|
|
82
|
+
// inline, below, before `afterUpdate` ever runs — so the scope lock is taken
|
|
83
|
+
// here, around that recomputation. Without it the `max(position) + 1`
|
|
84
|
+
// subquery races every other writer of the scope the record is being restored
|
|
85
|
+
// into.
|
|
86
|
+
//
|
|
87
|
+
// The scope this record is being restored into is the one its row physically
|
|
88
|
+
// carries, not the one the instance was loaded with: another writer may have
|
|
89
|
+
// moved it between scopes while it was soft deleted, which is what the
|
|
90
|
+
// stabilized acquisition converges on.
|
|
91
|
+
const { snapshots } = await acquireStabilizedSortableScopeLocks(dream, txn, sortableFields);
|
|
92
|
+
sortableFields.forEach(sortableFieldMetadata => {
|
|
58
93
|
const positionColumn = sortableFieldMetadata.positionField;
|
|
94
|
+
const snapshot = snapshots.get(positionColumn);
|
|
59
95
|
query = query.set(eb => ({
|
|
60
|
-
[positionColumn]: eb(
|
|
96
|
+
[positionColumn]: eb(filterQueryToScopeValues(dream, txn.kyselyTransaction.selectFrom(dream.table), column => snapshotScopeValue(snapshot, column, () => dream[column]), sortableFieldMetadata.scope).select(eb =>
|
|
97
|
+
// `max` over a scope with no positioned rows left — an empty
|
|
98
|
+
// scope, or one whose every remaining row is soft deleted and so
|
|
99
|
+
// carries a NULL position — is NULL, and NULL + 1 is NULL, which
|
|
100
|
+
// would restore the record with no position at all. Coalescing
|
|
101
|
+
// before the increment restores it at position 1.
|
|
102
|
+
eb.fn.coalesce(eb.fn.max(positionColumn), eb.lit(0)).as(positionColumn + '_max')), '+', 1),
|
|
61
103
|
}));
|
|
62
104
|
});
|
|
63
|
-
|
|
105
|
+
const [result] = await query.execute();
|
|
106
|
+
// No sortable row cache invalidation here: the restored record took a position
|
|
107
|
+
// at the end of the scope it was restored into and nothing else moved, so no
|
|
108
|
+
// other record's cached preflight row went stale, and this record's own cached
|
|
109
|
+
// row was consumed — destructively — by the snapshot read every sortable field
|
|
110
|
+
// above goes through.
|
|
111
|
+
return Number(result?.numUpdatedRows ?? 0);
|
|
64
112
|
}
|
|
65
113
|
/**
|
|
66
114
|
* @internal
|
|
@@ -212,6 +212,24 @@ export default class DreamApp {
|
|
|
212
212
|
get paginationMaxPageSize() {
|
|
213
213
|
return this._paginationMaxPageSize;
|
|
214
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* How long, in milliseconds, a position-mutating operation on a
|
|
217
|
+
* `@deco.Sortable` model will wait for another transaction to release the
|
|
218
|
+
* sort scope it needs before giving up with
|
|
219
|
+
* `SortableScopeLockWaitTimedOut`. Applied to the lock acquisition statement
|
|
220
|
+
* alone — the transaction's other lock waits keep whatever `lock_timeout`
|
|
221
|
+
* the application has configured.
|
|
222
|
+
*
|
|
223
|
+
* `0` waits without bound, which is what Postgres's `lock_timeout` means by
|
|
224
|
+
* it, and holds even on a connection configured with a `lock_timeout` of its
|
|
225
|
+
* own: the acquisition disables the bound for the duration of the statement.
|
|
226
|
+
*
|
|
227
|
+
* Set it with `dreamApp.set('sortableScopeLockTimeout', milliseconds)`.
|
|
228
|
+
*/
|
|
229
|
+
_sortableScopeLockTimeout = 5000;
|
|
230
|
+
get sortableScopeLockTimeout() {
|
|
231
|
+
return this._sortableScopeLockTimeout;
|
|
232
|
+
}
|
|
215
233
|
_primaryKeyType = 'bigint';
|
|
216
234
|
get primaryKeyType() {
|
|
217
235
|
return this._primaryKeyType;
|
|
@@ -492,6 +510,9 @@ export default class DreamApp {
|
|
|
492
510
|
case 'paginationMaxPageSize':
|
|
493
511
|
this._paginationMaxPageSize = options;
|
|
494
512
|
break;
|
|
513
|
+
case 'sortableScopeLockTimeout':
|
|
514
|
+
this._sortableScopeLockTimeout = options;
|
|
515
|
+
break;
|
|
495
516
|
default: {
|
|
496
517
|
// protection so that if a new ApplyOpt is ever added, this will throw a type error at build time
|
|
497
518
|
const _never = applyOption;
|