@rvoh/dream 2.27.0 → 2.28.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/dist/cjs/src/Dream.js +63 -1
- package/dist/cjs/src/db/errors.js +4 -0
- package/dist/cjs/src/decorators/field/Encrypted.js +12 -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/Query.js +73 -11
- package/dist/cjs/src/dream/QueryDriver/Base.js +75 -2
- package/dist/cjs/src/dream/QueryDriver/Kysely.js +11 -4
- 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/encrypt/assertNoEncryptedColumnWrites.js +62 -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/errors/encrypt/CannotSetEncryptedColumnInQueryUpdate.js +31 -0
- package/dist/cjs/src/package-exports/errors.js +3 -0
- package/dist/esm/src/Dream.js +63 -1
- package/dist/esm/src/db/errors.js +4 -0
- package/dist/esm/src/decorators/field/Encrypted.js +12 -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/Query.js +73 -11
- package/dist/esm/src/dream/QueryDriver/Base.js +75 -2
- package/dist/esm/src/dream/QueryDriver/Kysely.js +11 -4
- 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/encrypt/assertNoEncryptedColumnWrites.js +62 -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/errors/encrypt/CannotSetEncryptedColumnInQueryUpdate.js +31 -0
- package/dist/esm/src/package-exports/errors.js +3 -0
- package/dist/types/src/Dream.d.ts +56 -0
- 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/Query.d.ts +47 -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/encrypt/assertNoEncryptedColumnWrites.d.ts +27 -0
- 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/errors/encrypt/CannotSetEncryptedColumnInQueryUpdate.d.ts +8 -0
- package/dist/types/src/package-exports/errors.d.ts +3 -0
- 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 +151 -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 +124 -98
- 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
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raised when a batch of a locked `Query#destroy` or `Query#update` on a
|
|
3
|
+
* sortable model would carry its transaction past the number of advisory
|
|
4
|
+
* sort-scope locks Dream is willing to hold at once.
|
|
5
|
+
*
|
|
6
|
+
* The batched, row-locking query APIs take every advisory scope key the batch
|
|
7
|
+
* needs before they claim a single row — one key per sort scope the batch's
|
|
8
|
+
* candidate records occupy, per sortable field the model declares. Advisory
|
|
9
|
+
* locks live in Postgres's cluster-wide shared lock table, whose size is fixed
|
|
10
|
+
* at startup (`max_locks_per_transaction` × `max_connections`), and they are
|
|
11
|
+
* held until the transaction ends. A batch over a table whose sort scope has
|
|
12
|
+
* very high cardinality can therefore consume a meaningful fraction of a
|
|
13
|
+
* resource every other connection in the cluster shares, and exhausting it
|
|
14
|
+
* fails unrelated transactions rather than only this one.
|
|
15
|
+
*
|
|
16
|
+
* So the preflight counts first and refuses, before claiming any row. The count
|
|
17
|
+
* is the whole transaction's: the keys it already holds plus the keys the
|
|
18
|
+
* refused batch would add, since advisory locks are released only when the
|
|
19
|
+
* transaction ends. Inside a transaction Dream opens for the batch that is the
|
|
20
|
+
* batch's own key set and nothing has been locked when this is raised; inside a
|
|
21
|
+
* transaction you own, it also counts every key the run's earlier batches took,
|
|
22
|
+
* and those stay held.
|
|
23
|
+
*
|
|
24
|
+
* The fix is almost always a smaller `batchSize`: the keys one batch needs are
|
|
25
|
+
* bounded by how many distinct sort scopes its records span, so halving the
|
|
26
|
+
* batch size halves the worst case. If the run itself is what accumulates them
|
|
27
|
+
* — a long multi-batch run inside a transaction you opened — either let Dream
|
|
28
|
+
* open a transaction per batch (do not pass a transaction of your own) or
|
|
29
|
+
* commit between runs, which is what releases the locks as it goes.
|
|
30
|
+
*/
|
|
31
|
+
export default class SortableBatchRequiresTooManyScopeLocks extends Error {
|
|
32
|
+
dreamClassName;
|
|
33
|
+
keyCount;
|
|
34
|
+
maxKeyCount;
|
|
35
|
+
constructor(dreamClassName, keyCount, maxKeyCount) {
|
|
36
|
+
super();
|
|
37
|
+
this.dreamClassName = dreamClassName;
|
|
38
|
+
this.keyCount = keyCount;
|
|
39
|
+
this.maxKeyCount = maxKeyCount;
|
|
40
|
+
}
|
|
41
|
+
get message() {
|
|
42
|
+
return `\
|
|
43
|
+
A locked batch over ${this.dreamClassName} would leave its transaction holding ${this.keyCount} sort scope locks, and the limit is ${this.maxKeyCount}.
|
|
44
|
+
|
|
45
|
+
Sortable takes one advisory lock per sort scope a batch touches, per sortable field, before the
|
|
46
|
+
batch claims any row, and holds them until the transaction ends. Advisory locks come out of a
|
|
47
|
+
lock table the whole Postgres cluster shares, so a batch that would carry its transaction past
|
|
48
|
+
this limit is refused rather than risking every other connection's transactions.
|
|
49
|
+
|
|
50
|
+
Use a smaller batchSize. The keys one batch needs are bounded by the number of distinct sort
|
|
51
|
+
scopes its records span, so a smaller batch takes proportionally fewer. If this operation runs
|
|
52
|
+
inside a transaction you opened, note that the locks of earlier batches are still held while
|
|
53
|
+
later batches preflight, and are counted here; letting Dream open a transaction per batch
|
|
54
|
+
releases them as it goes.
|
|
55
|
+
`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raised when a model declaring sortable fields is used on a query driver that
|
|
3
|
+
* cannot take transaction-scoped advisory locks.
|
|
4
|
+
*
|
|
5
|
+
* Sortable computes position values under a lock on the sort scope, without
|
|
6
|
+
* which concurrent writers collide on the same position. A driver that cannot
|
|
7
|
+
* provide that lock fails loudly here rather than racing silently.
|
|
8
|
+
*/
|
|
9
|
+
export default class SortableRequiresAdvisoryTransactionLocks extends Error {
|
|
10
|
+
queryDriverClassName;
|
|
11
|
+
constructor(queryDriverClassName) {
|
|
12
|
+
super();
|
|
13
|
+
this.queryDriverClassName = queryDriverClassName;
|
|
14
|
+
}
|
|
15
|
+
get message() {
|
|
16
|
+
return `\
|
|
17
|
+
${this.queryDriverClassName} cannot be used with the Sortable decorator.
|
|
18
|
+
|
|
19
|
+
Sortable computes position values under a transaction-scoped advisory lock on the sort
|
|
20
|
+
scope, without which concurrent writers collide on the same position. Implement
|
|
21
|
+
\`acquireAdvisoryTransactionLocks\` on ${this.queryDriverClassName} and set
|
|
22
|
+
\`supportsAdvisoryTransactionLocks = true\`, or use the PostgresQueryDriver for models
|
|
23
|
+
that declare sortable fields.
|
|
24
|
+
`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raised when a position-mutating operation cannot settle on the sort scope its
|
|
3
|
+
* record actually occupies.
|
|
4
|
+
*
|
|
5
|
+
* A save, destroy, undestroy or locked batch operation locks the sort scopes it
|
|
6
|
+
* believes it is touching, then reads the rows to find out which scopes they
|
|
7
|
+
* physically occupy. When another writer has moved a row in between, the
|
|
8
|
+
* operation takes the additional scope's lock and reads again. A row that keeps
|
|
9
|
+
* moving exhausts that bound, and the operation raises rather than compacting a
|
|
10
|
+
* scope the row has already left.
|
|
11
|
+
*
|
|
12
|
+
* This is a retryable condition: retrying the operation is the intended
|
|
13
|
+
* response. One caveat for saves the caller did not wrap in a transaction: the
|
|
14
|
+
* model's before-hooks run before the transaction this error aborts, so any
|
|
15
|
+
* effects those hooks had outside the record's own row — writes to other
|
|
16
|
+
* tables, enqueued work — are not rolled back, and run again on retry.
|
|
17
|
+
*/
|
|
18
|
+
export default class SortableScopeDidNotStabilize extends Error {
|
|
19
|
+
dreamClassName;
|
|
20
|
+
rounds;
|
|
21
|
+
constructor(dreamClassName, rounds) {
|
|
22
|
+
super();
|
|
23
|
+
this.dreamClassName = dreamClassName;
|
|
24
|
+
this.rounds = rounds;
|
|
25
|
+
}
|
|
26
|
+
get message() {
|
|
27
|
+
return `\
|
|
28
|
+
The sort scope of a ${this.dreamClassName} record kept changing while its position was being computed.
|
|
29
|
+
|
|
30
|
+
Sortable locks the sort scopes an operation touches and then reads the record's actual scope
|
|
31
|
+
under those locks, taking any additional lock the read reveals. After ${this.rounds} rounds the
|
|
32
|
+
record had still been moved to another scope by a competing writer, so the operation was
|
|
33
|
+
abandoned rather than compacting a scope the record no longer occupies. Retrying the operation
|
|
34
|
+
is the intended response; note that on a save the caller did not wrap in a transaction, the
|
|
35
|
+
model's before-hooks ran before the transaction this error aborted, so their effects outside
|
|
36
|
+
the record's own row are not rolled back and run again on retry.
|
|
37
|
+
`;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raised when a position-mutating operation waited too long for another
|
|
3
|
+
* transaction to release a sort scope.
|
|
4
|
+
*
|
|
5
|
+
* Every position-mutating path — a save, a destroy, an undestroy, a locked
|
|
6
|
+
* batch, `Dream.resort` — computes its positions under an exclusive advisory
|
|
7
|
+
* lock on the sort scope, held until the transaction that took it ends. A
|
|
8
|
+
* writer that finds the scope already locked waits, and that wait is bounded:
|
|
9
|
+
* Dream applies a `lock_timeout` (5 seconds by default, set through the
|
|
10
|
+
* `sortableScopeLockTimeout` DreamApp option) to the acquisition statement
|
|
11
|
+
* alone, and restores whatever `lock_timeout` was in effect afterward, so the
|
|
12
|
+
* transaction's other lock waits — row locks taken by `lock: true`, foreign key
|
|
13
|
+
* checks — are left exactly as the application configured them.
|
|
14
|
+
*
|
|
15
|
+
* This is a retryable condition: the operation touched nothing, and the holder
|
|
16
|
+
* it was waiting for is a transaction that will end. What has to be retried
|
|
17
|
+
* depends on who owns the transaction. When Dream opened it — a plain
|
|
18
|
+
* `save()`, `destroy()`, `undestroy()` or `resort()` — only that call was rolled
|
|
19
|
+
* back, and retrying the call is the whole response. Inside a transaction the
|
|
20
|
+
* caller owns, the failed statement aborts **the entire transaction**: nothing
|
|
21
|
+
* further can run on it, and the caller has to retry the transaction as a whole
|
|
22
|
+
* rather than the sortable operation inside it.
|
|
23
|
+
*
|
|
24
|
+
* Hitting this bound routinely is a signal rather than a transient: it means one
|
|
25
|
+
* sort scope is being written by more concurrent writers than it can serialize,
|
|
26
|
+
* or that something slow is running inside a transaction that holds the scope —
|
|
27
|
+
* on a destroy or an undestroy, that includes your own `afterDestroy` and
|
|
28
|
+
* `afterUpdate` hooks, which run inside the lock window.
|
|
29
|
+
*/
|
|
30
|
+
export default class SortableScopeLockWaitTimedOut extends Error {
|
|
31
|
+
timeoutMs;
|
|
32
|
+
constructor(timeoutMs) {
|
|
33
|
+
super();
|
|
34
|
+
this.timeoutMs = timeoutMs;
|
|
35
|
+
}
|
|
36
|
+
get message() {
|
|
37
|
+
return `\
|
|
38
|
+
Timed out after ${this.timeoutMs}ms waiting for another transaction to release a sort scope.
|
|
39
|
+
|
|
40
|
+
Sortable computes positions under an exclusive advisory lock on the sort scope, held until the
|
|
41
|
+
transaction holding it ends. This operation waited the configured bound and gave up rather than
|
|
42
|
+
waiting indefinitely; it changed nothing, and retrying it is the intended response — but if the
|
|
43
|
+
transaction this ran in was one you opened yourself, the whole transaction is aborted and has to
|
|
44
|
+
be retried as a whole.
|
|
45
|
+
|
|
46
|
+
Change the bound with \`dreamApp.set('sortableScopeLockTimeout', milliseconds)\` in your Dream
|
|
47
|
+
configuration, or set it to 0 to wait without bound.
|
|
48
|
+
`;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export default class CannotSetEncryptedColumnInQueryUpdate extends Error {
|
|
2
|
+
dreamClass;
|
|
3
|
+
encryptedColumnName;
|
|
4
|
+
encryptedProperty;
|
|
5
|
+
constructor(dreamClass, encryptedColumnName, encryptedProperty) {
|
|
6
|
+
super();
|
|
7
|
+
this.dreamClass = dreamClass;
|
|
8
|
+
this.encryptedColumnName = encryptedColumnName;
|
|
9
|
+
this.encryptedProperty = encryptedProperty;
|
|
10
|
+
}
|
|
11
|
+
get message() {
|
|
12
|
+
return `
|
|
13
|
+
Cannot write a non-null value to an @Encrypted backing column from a query-level update.
|
|
14
|
+
|
|
15
|
+
Dream class: ${this.dreamClass.sanitizedName}
|
|
16
|
+
Encrypted backing column: ${this.encryptedColumnName}
|
|
17
|
+
Property to write instead: ${this.encryptedProperty}
|
|
18
|
+
|
|
19
|
+
\`update(attributes, { skipHooks: true })\` compiles to a single UPDATE statement and
|
|
20
|
+
never instantiates a model, so nothing encrypts the value on its way to the column:
|
|
21
|
+
"${this.encryptedColumnName}" would be written as plaintext, exactly as passed.
|
|
22
|
+
|
|
23
|
+
Write through the property instead, so that Dream encrypts the value:
|
|
24
|
+
|
|
25
|
+
await ${this.dreamClass.sanitizedName}.where({ ... }).update({ ${this.encryptedProperty}: 'value' })
|
|
26
|
+
await ${this.dreamClass.sanitizedName}.where({ ... }).update({ ${this.encryptedProperty}: 'value' }, { lock: true, skipHooks: true })
|
|
27
|
+
|
|
28
|
+
Setting "${this.encryptedColumnName}" to null is permitted, and is how this path clears the column.
|
|
29
|
+
`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -12,6 +12,9 @@ export { default as DecryptionParseError } from '../errors/encrypt/DecryptionPar
|
|
|
12
12
|
export { default as DecryptionRotationError } from '../errors/encrypt/DecryptionRotationError.js';
|
|
13
13
|
export { default as RecordNotFound } from '../errors/RecordNotFound.js';
|
|
14
14
|
export { default as MissingSerializersDefinition } from '../errors/serializers/MissingSerializersDefinition.js';
|
|
15
|
+
export { default as SortableBatchRequiresTooManyScopeLocks } from '../errors/SortableBatchRequiresTooManyScopeLocks.js';
|
|
16
|
+
export { default as SortableScopeDidNotStabilize } from '../errors/SortableScopeDidNotStabilize.js';
|
|
17
|
+
export { default as SortableScopeLockWaitTimedOut } from '../errors/SortableScopeLockWaitTimedOut.js';
|
|
15
18
|
export { default as ValidationError } from '../errors/ValidationError.js';
|
|
16
19
|
export { InvalidCalendarDate } from '../utils/datetime/CalendarDate.js';
|
|
17
20
|
export { InvalidClockTime } from '../utils/datetime/ClockTime.js';
|
package/dist/esm/src/Dream.js
CHANGED
|
@@ -273,6 +273,15 @@ export default class Dream {
|
|
|
273
273
|
* assignment simply ensures that it is always an array rather than undefined)
|
|
274
274
|
*/
|
|
275
275
|
static explicitUnsafeParamColumns = Object.freeze([]);
|
|
276
|
+
/**
|
|
277
|
+
* @internal
|
|
278
|
+
*
|
|
279
|
+
* Model storage for encrypted attribute metadata, set on the inheriting class when
|
|
280
|
+
* using the Encrypted decorator (this default assignment simply ensures that it is
|
|
281
|
+
* always an array rather than undefined,
|
|
282
|
+
* freezing ensures that we never modify the static array on the inherited Dream class)
|
|
283
|
+
*/
|
|
284
|
+
static encryptedAttributes = Object.freeze([]);
|
|
276
285
|
/**
|
|
277
286
|
* @internal
|
|
278
287
|
*
|
|
@@ -367,6 +376,23 @@ export default class Dream {
|
|
|
367
376
|
static get isSTIBase() {
|
|
368
377
|
return !!this.extendedBy?.length && !this.isSTIChild;
|
|
369
378
|
}
|
|
379
|
+
/**
|
|
380
|
+
* @internal
|
|
381
|
+
*
|
|
382
|
+
* This class together with every STI class below it, however deeply nested. These
|
|
383
|
+
* are the classes whose rows a query rooted at this class can reach, since an STI
|
|
384
|
+
* child's rows are a subset of its base's.
|
|
385
|
+
*
|
|
386
|
+
* The classes *above* this one are deliberately excluded, as are its siblings: a
|
|
387
|
+
* column that only a sibling declares is meaningless for the rows this class
|
|
388
|
+
* matches, so treating it as this class's business protects nothing and costs a
|
|
389
|
+
* wider walk on every call.
|
|
390
|
+
*
|
|
391
|
+
* @returns An array of Dream classes
|
|
392
|
+
*/
|
|
393
|
+
static selfAndStiDescendants() {
|
|
394
|
+
return [this, ...(this.extendedBy ?? []).flatMap(dreamClass => dreamClass.selfAndStiDescendants())];
|
|
395
|
+
}
|
|
370
396
|
static stiSiblings() {
|
|
371
397
|
const stiBase = this.stiBaseClassOrOwnClass;
|
|
372
398
|
if (!stiBase.isSTIBase)
|
|
@@ -691,6 +717,10 @@ export default class Dream {
|
|
|
691
717
|
return defaultParams;
|
|
692
718
|
}
|
|
693
719
|
static defaultParamSafeColumns() {
|
|
720
|
+
// `columns()` is table scoped, so it offers this class every column of the shared
|
|
721
|
+
// STI table, including those an STI child marks unsafe. A column unsafe for a child
|
|
722
|
+
// is unsafe here, because a query rooted at this class reaches that child's rows.
|
|
723
|
+
const explicitUnsafeParamColumns = new Set(this.selfAndStiDescendants().flatMap(dreamClass => [...dreamClass.explicitUnsafeParamColumns]));
|
|
694
724
|
const columns = [...this.columns()].filter(column => {
|
|
695
725
|
if (this.prototype._primaryKey === column)
|
|
696
726
|
return false;
|
|
@@ -700,7 +730,7 @@ export default class Dream {
|
|
|
700
730
|
return false;
|
|
701
731
|
if (this.prototype._deletedAtField === column)
|
|
702
732
|
return false;
|
|
703
|
-
if (
|
|
733
|
+
if (explicitUnsafeParamColumns.has(column))
|
|
704
734
|
return false;
|
|
705
735
|
if (this.isBelongsToAssociationForeignKey(column, { includeStiSiblings: true }))
|
|
706
736
|
return false;
|
|
@@ -1803,6 +1833,25 @@ export default class Dream {
|
|
|
1803
1833
|
* with no gaps, accounting for the scopes specified in the
|
|
1804
1834
|
* corresponding Sortable decorator.
|
|
1805
1835
|
*
|
|
1836
|
+
* Each sort scope is renumbered in its own transaction, under the same
|
|
1837
|
+
* advisory lock every other position-mutating path takes on that scope, from
|
|
1838
|
+
* a read of the scope taken inside that lock — so a concurrent create or
|
|
1839
|
+
* destroy cannot make the renumbering land on positions it never read.
|
|
1840
|
+
*
|
|
1841
|
+
* **Do not call `resort` from inside a transaction you own.** It opens those
|
|
1842
|
+
* transactions itself, on connections of its own, so it can neither join
|
|
1843
|
+
* yours nor roll back with it: its writes commit on their own. Worse, if your
|
|
1844
|
+
* transaction already holds the scope lock for a scope being renumbered — any
|
|
1845
|
+
* position-mutating write to that scope earlier in the transaction takes it —
|
|
1846
|
+
* `resort` waits on that lock from the outside, where no deadlock detector
|
|
1847
|
+
* can see the cycle, until the wait bound expires and it throws
|
|
1848
|
+
* `SortableScopeLockWaitTimedOut`.
|
|
1849
|
+
*
|
|
1850
|
+
* On an STI hierarchy the position space is shared by every class in the
|
|
1851
|
+
* hierarchy, exactly as it is for creates, updates and destroys. Calling
|
|
1852
|
+
* `resort` on a child class therefore renumbers the hierarchy's rows, not
|
|
1853
|
+
* only the receiver's.
|
|
1854
|
+
*
|
|
1806
1855
|
* ```ts
|
|
1807
1856
|
* class Post extends ApplicationModel {
|
|
1808
1857
|
* @deco.Sortable({ scope: ['user']})
|
|
@@ -3742,6 +3791,19 @@ export default class Dream {
|
|
|
3742
3791
|
* This is usually used for a soft-delete
|
|
3743
3792
|
* pattern
|
|
3744
3793
|
*
|
|
3794
|
+
* The veto is fail-closed, and the record is left
|
|
3795
|
+
* entirely untouched: on a soft-delete model the soft
|
|
3796
|
+
* delete is skipped as well, no `afterDestroy` or
|
|
3797
|
+
* `afterDestroyCommit` hooks run, since the record is
|
|
3798
|
+
* still present, and the destroy returns before the
|
|
3799
|
+
* `dependent: 'destroy'` cascade, so this record's
|
|
3800
|
+
* dependent associations survive too.
|
|
3801
|
+
*
|
|
3802
|
+
* A veto only stops the subtree rooted at the record
|
|
3803
|
+
* that called it. When a record is reached by another
|
|
3804
|
+
* record's cascade, vetoing spares it and its own
|
|
3805
|
+
* dependents; the destroy that reached it carries on.
|
|
3806
|
+
*
|
|
3745
3807
|
* ```ts
|
|
3746
3808
|
* class User extends ApplicationModel {
|
|
3747
3809
|
* @deco.BeforeDestroy()
|
|
@@ -12,6 +12,7 @@ export const FOREIGN_KEY_VIOLATION = 'FOREIGN_KEY_VIOLATION';
|
|
|
12
12
|
export const INTEGRITY_CONSTRAINT_VIOLATION = 'INTEGRITY_CONSTRAINT_VIOLATION';
|
|
13
13
|
export const INVALID_INPUT_SYNTAX = 'INVALID_INPUT_SYNTAX';
|
|
14
14
|
export const COLUMN_OVERFLOW = 'COLUMN_OVERFLOW';
|
|
15
|
+
export const LOCK_NOT_AVAILABLE = 'LOCK_NOT_AVAILABLE';
|
|
15
16
|
export const NOT_NULL_VIOLATION = 'NOT_NULL_VIOLATION';
|
|
16
17
|
export const RESTRICT_VIOLATION = 'RESTRICT_VIOLATION';
|
|
17
18
|
export const UNIQUE_VIOLATION = 'UNIQUE_VIOLATION';
|
|
@@ -26,6 +27,9 @@ export const PG_ERRORS = {
|
|
|
26
27
|
'23000': INTEGRITY_CONSTRAINT_VIOLATION,
|
|
27
28
|
'23001': RESTRICT_VIOLATION,
|
|
28
29
|
'23503': FOREIGN_KEY_VIOLATION,
|
|
30
|
+
// a statement Postgres cancelled because its `lock_timeout` elapsed while it
|
|
31
|
+
// waited for a lock — the bound Dream puts on the sortable scope-lock wait
|
|
32
|
+
'55P03': LOCK_NOT_AVAILABLE,
|
|
29
33
|
};
|
|
30
34
|
function pgErrorFromCode(code) {
|
|
31
35
|
return PG_ERRORS[code] || null;
|
|
@@ -41,6 +41,18 @@ export default function Encrypted(encryptedColumnName) {
|
|
|
41
41
|
}
|
|
42
42
|
;
|
|
43
43
|
dreamClass['explicitUnsafeParamColumns'].push(encryptedKey);
|
|
44
|
+
if (!Object.getOwnPropertyDescriptor(dreamClass, 'encryptedAttributes')) {
|
|
45
|
+
// This pattern allows `encryptedAttributes` on a base STI class and on
|
|
46
|
+
// child STI classes. The new `encryptedAttributes` property will be created
|
|
47
|
+
// on the child STI class, but it will include all the `encryptedAttributes`
|
|
48
|
+
// already declared on the base STI class.
|
|
49
|
+
dreamClass['encryptedAttributes'] = [...dreamClass['encryptedAttributes']];
|
|
50
|
+
}
|
|
51
|
+
;
|
|
52
|
+
dreamClass['encryptedAttributes'].push({
|
|
53
|
+
property: key,
|
|
54
|
+
encryptedColumnName: encryptedKey,
|
|
55
|
+
});
|
|
44
56
|
const dreamPrototype = Object.getPrototypeOf(this);
|
|
45
57
|
Object.defineProperty(dreamPrototype, key, {
|
|
46
58
|
get() {
|
|
@@ -1,17 +1,37 @@
|
|
|
1
|
-
import pascalize from '../../../helpers/pascalize.js';
|
|
2
|
-
import { STI_SCOPE_NAME } from '../../class/STI.js';
|
|
3
|
-
import { afterCreateImplementation } from '../lifecycle/AfterCreate.js';
|
|
4
|
-
import { afterCreateCommitImplementation } from '../lifecycle/AfterCreateCommit.js';
|
|
5
|
-
import { afterDestroyImplementation } from '../lifecycle/AfterDestroy.js';
|
|
6
|
-
import { afterDestroyCommitImplementation } from '../lifecycle/AfterDestroyCommit.js';
|
|
7
|
-
import { afterUpdateImplementation } from '../lifecycle/AfterUpdate.js';
|
|
8
|
-
import { afterUpdateCommitImplementation } from '../lifecycle/AfterUpdateCommit.js';
|
|
9
|
-
import { beforeSaveImplementation } from '../lifecycle/BeforeSave.js';
|
|
10
1
|
import scopeArray from './helpers/scopeArray.js';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Marks an integer column as a sortable position: Dream keeps the positions of
|
|
4
|
+
* every record in a sort scope contiguous, starting at 1, as records are
|
|
5
|
+
* created, moved, destroyed and undestroyed.
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* class Post extends ApplicationModel {
|
|
9
|
+
* @deco.Sortable({ scope: 'user' })
|
|
10
|
+
* public position: number
|
|
11
|
+
* }
|
|
12
|
+
*
|
|
13
|
+
* await post.update({ position: 2 }) // the records at 2 and above shift up
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* A position past the end of the scope is clamped to the end, and a position
|
|
17
|
+
* below 1 — or none at all — lands the record at the end.
|
|
18
|
+
*
|
|
19
|
+
* **A save that changes the sort scope ignores a position given alongside it.**
|
|
20
|
+
* The record lands at the end of the scope it moves into, whatever position the
|
|
21
|
+
* same `update` supplied:
|
|
22
|
+
*
|
|
23
|
+
* ```ts
|
|
24
|
+
* await post.update({ user: otherUser, position: 1 })
|
|
25
|
+
* // post is now the last record in otherUser's scope, not the first
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* Move it in two saves to place it: `await post.update({ user: otherUser })`,
|
|
29
|
+
* then `await post.update({ position: 1 })`.
|
|
30
|
+
*
|
|
31
|
+
* Sortable requires a query driver that supports advisory transaction locks —
|
|
32
|
+
* the `PostgresQueryDriver` does — since every position write serializes the
|
|
33
|
+
* writers of its sort scope on one.
|
|
34
|
+
*/
|
|
15
35
|
export default function Sortable(opts = {}) {
|
|
16
36
|
return function (_, context) {
|
|
17
37
|
const key = context.name;
|
|
@@ -27,116 +47,21 @@ export default function Sortable(opts = {}) {
|
|
|
27
47
|
// already declared on the base STI class.
|
|
28
48
|
dreamClass['sortableFields'] = [...dreamClass['sortableFields']];
|
|
29
49
|
}
|
|
50
|
+
// the decorator registers nothing but this metadata: none of a sortable
|
|
51
|
+
// field's runtime work runs as hooks. A save's preparation and position
|
|
52
|
+
// write run as phases in `saveDream` (`prepareSortableFieldsForSave`,
|
|
53
|
+
// `performSortablePositionWork`), a destroy's lock acquisition, snapshot
|
|
54
|
+
// read and compaction as phases in `destroyDream`
|
|
55
|
+
// (`prepareSortableFieldsForDestroy`, `performSortableDestroyWork`), and
|
|
56
|
+
// an undestroy's restore inline in `undestroyDream` — each seated
|
|
57
|
+
// relative to the user's hooks by the caller, so no user hook code can
|
|
58
|
+
// interleave with the position work and every after-hook observes
|
|
59
|
+
// computed positions and compacted scopes
|
|
30
60
|
;
|
|
31
61
|
dreamClass['sortableFields'].push({
|
|
32
62
|
scope: scopeArray(opts.scope),
|
|
33
63
|
positionField: key,
|
|
34
64
|
});
|
|
35
|
-
const positionField = key;
|
|
36
|
-
const beforeSaveMethodName = `_cacheValueFor${pascalize(key)}`;
|
|
37
|
-
const afterCreateMethodName = `_setNewValueFor${pascalize(key)}AfterCreate`;
|
|
38
|
-
const afterCreateCommitMethodName = `_setNewValueFor${pascalize(key)}AfterCreateCommit`;
|
|
39
|
-
const afterUpdateMethodName = `_updateValueFor${pascalize(key)}AfterUpdate`;
|
|
40
|
-
const afterUpdateCommitMethodName = `_updateValueFor${pascalize(key)}AfteUpdateCommit`;
|
|
41
|
-
const afterDestroyMethodName = `_setValuesAfterDestructionFor${pascalize(key)}AfterDestroy`;
|
|
42
|
-
const afterDestroyCommitMethodName = `_setValuesAfterDestructionFor${pascalize(key)}AfterDestroyCommit`;
|
|
43
|
-
const dreamPrototype = Object.getPrototypeOf(dream);
|
|
44
|
-
// before saving, we remember the new value for position, but clear it from our
|
|
45
|
-
// supervised attributes to prevent position from saving
|
|
46
|
-
dreamPrototype[beforeSaveMethodName] = function () {
|
|
47
|
-
beforeSortableSave({
|
|
48
|
-
dream: this,
|
|
49
|
-
positionField,
|
|
50
|
-
scope: opts.scope,
|
|
51
|
-
});
|
|
52
|
-
};
|
|
53
|
-
// once saved, we can now safely update position in isolation
|
|
54
|
-
dreamPrototype[afterUpdateMethodName] = async function (txn) {
|
|
55
|
-
// if no transaction is provided, leverage update commit hook instead
|
|
56
|
-
if (!txn)
|
|
57
|
-
return;
|
|
58
|
-
const query = dreamClass.query().removeDefaultScope(STI_SCOPE_NAME).txn(txn);
|
|
59
|
-
await afterUpdateSortable({
|
|
60
|
-
dream: this,
|
|
61
|
-
positionField,
|
|
62
|
-
query,
|
|
63
|
-
txn,
|
|
64
|
-
scope: opts.scope,
|
|
65
|
-
});
|
|
66
|
-
};
|
|
67
|
-
dreamPrototype[afterUpdateCommitMethodName] = async function (txn) {
|
|
68
|
-
// if transaction is provided, leverage update hook instead
|
|
69
|
-
if (txn)
|
|
70
|
-
return;
|
|
71
|
-
const query = dreamClass.query().removeDefaultScope(STI_SCOPE_NAME);
|
|
72
|
-
await afterUpdateSortable({
|
|
73
|
-
dream: this,
|
|
74
|
-
positionField,
|
|
75
|
-
query,
|
|
76
|
-
scope: opts.scope,
|
|
77
|
-
});
|
|
78
|
-
};
|
|
79
|
-
// after create, we always want to ensure the position is set, so if they provide one,
|
|
80
|
-
// we need to split existing records on position and update, but otherwise we simply set the new position
|
|
81
|
-
// to be the length of all existing records + 1
|
|
82
|
-
dreamPrototype[afterCreateMethodName] = async function (txn) {
|
|
83
|
-
// if no transaction is provided, leverage create commit hook instead
|
|
84
|
-
if (!txn)
|
|
85
|
-
return;
|
|
86
|
-
const query = dreamClass.query().removeDefaultScope(STI_SCOPE_NAME).txn(txn);
|
|
87
|
-
await afterSortableCreate({
|
|
88
|
-
dream: this,
|
|
89
|
-
positionField,
|
|
90
|
-
query,
|
|
91
|
-
scope: opts.scope,
|
|
92
|
-
txn,
|
|
93
|
-
});
|
|
94
|
-
};
|
|
95
|
-
dreamPrototype[afterCreateCommitMethodName] = async function (txn) {
|
|
96
|
-
// if transaction is provided, leverage create hook instead
|
|
97
|
-
if (txn)
|
|
98
|
-
return;
|
|
99
|
-
const query = dreamClass.query().removeDefaultScope(STI_SCOPE_NAME);
|
|
100
|
-
await afterSortableCreate({
|
|
101
|
-
dream: this,
|
|
102
|
-
positionField,
|
|
103
|
-
query,
|
|
104
|
-
scope: opts.scope,
|
|
105
|
-
});
|
|
106
|
-
};
|
|
107
|
-
// after destroy, auto-adjust positions of all related records with a greater position
|
|
108
|
-
// than this one to maintain incrementing order,
|
|
109
|
-
dreamPrototype[afterDestroyMethodName] = async function (txn) {
|
|
110
|
-
// if no transaction is provided, leverage destroy commit hook instead
|
|
111
|
-
if (!txn)
|
|
112
|
-
return;
|
|
113
|
-
const query = dreamClass.query().removeDefaultScope(STI_SCOPE_NAME).txn(txn);
|
|
114
|
-
await afterSortableDestroy({
|
|
115
|
-
dream: this,
|
|
116
|
-
positionField,
|
|
117
|
-
query,
|
|
118
|
-
scope: opts.scope,
|
|
119
|
-
});
|
|
120
|
-
};
|
|
121
|
-
dreamPrototype[afterDestroyCommitMethodName] = async function (txn) {
|
|
122
|
-
// if transaction is provided, leverage destroy hook instead
|
|
123
|
-
if (txn)
|
|
124
|
-
return;
|
|
125
|
-
const query = dreamClass.query().removeDefaultScope(STI_SCOPE_NAME);
|
|
126
|
-
await afterSortableDestroy({
|
|
127
|
-
dream: this,
|
|
128
|
-
positionField,
|
|
129
|
-
query,
|
|
130
|
-
scope: opts.scope,
|
|
131
|
-
});
|
|
132
|
-
};
|
|
133
|
-
beforeSaveImplementation(dream, beforeSaveMethodName);
|
|
134
|
-
afterCreateImplementation(dream, afterCreateMethodName);
|
|
135
|
-
afterCreateCommitImplementation(dream, afterCreateCommitMethodName);
|
|
136
|
-
afterUpdateImplementation(dream, afterUpdateMethodName);
|
|
137
|
-
afterUpdateCommitImplementation(dream, afterUpdateCommitMethodName);
|
|
138
|
-
afterDestroyImplementation(dream, afterDestroyMethodName);
|
|
139
|
-
afterDestroyCommitImplementation(dream, afterDestroyCommitMethodName);
|
|
140
65
|
});
|
|
141
66
|
};
|
|
142
67
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import Query from '../../../../dream/Query.js';
|
|
2
|
+
import SortableRequiresAdvisoryTransactionLocks from '../../../../errors/SortableRequiresAdvisoryTransactionLocks.js';
|
|
3
|
+
import heldSortableScopeLockKeys from './heldSortableScopeLockKeys.js';
|
|
4
|
+
/**
|
|
5
|
+
* @internal
|
|
6
|
+
*
|
|
7
|
+
* Serializes every writer of a set of sort scopes by taking those scopes'
|
|
8
|
+
* transaction-scoped advisory locks. They are released when the enclosing
|
|
9
|
+
* transaction ends, so there is nothing to release here.
|
|
10
|
+
*
|
|
11
|
+
* Keys this transaction already holds are dropped rather than re-acquired: the
|
|
12
|
+
* lock is held to the end of the transaction either way, so the statement would
|
|
13
|
+
* do nothing but cost a round trip inside the lock window. Whatever is left is
|
|
14
|
+
* taken in the order given — callers that need more than one sort them first,
|
|
15
|
+
* so that two operations touching the same pair of scopes cannot deadlock
|
|
16
|
+
* against each other — and in one statement, on drivers that can express it.
|
|
17
|
+
*
|
|
18
|
+
* Waiting for a key another transaction holds is bounded by the driver, which
|
|
19
|
+
* raises `SortableScopeLockWaitTimedOut` rather than waiting forever; see
|
|
20
|
+
* `QueryDriverBase.acquireAdvisoryTransactionLocks`.
|
|
21
|
+
*
|
|
22
|
+
* @param dream - the record whose connection provides the driver
|
|
23
|
+
* @param txn - the transaction the locks are scoped to
|
|
24
|
+
* @param keys - the scope keys to acquire, in acquisition order
|
|
25
|
+
*/
|
|
26
|
+
export default async function acquireSortableScopeLocks(dream, txn, keys) {
|
|
27
|
+
if (!keys.length)
|
|
28
|
+
return;
|
|
29
|
+
const queryDriverClass = Query.dbDriverClass(dream.connectionName || 'default');
|
|
30
|
+
if (!queryDriverClass.supportsAdvisoryTransactionLocks)
|
|
31
|
+
throw new SortableRequiresAdvisoryTransactionLocks(queryDriverClass.name);
|
|
32
|
+
const heldKeys = heldSortableScopeLockKeys(txn);
|
|
33
|
+
const keysToAcquire = keys.filter(key => !heldKeys.has(key));
|
|
34
|
+
if (!keysToAcquire.length)
|
|
35
|
+
return;
|
|
36
|
+
await queryDriverClass.acquireAdvisoryTransactionLocks(txn, keysToAcquire);
|
|
37
|
+
keysToAcquire.forEach(key => heldKeys.add(key));
|
|
38
|
+
}
|