@rvoh/dream 2.27.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 -0
- 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/Query.js +64 -9
- 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/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 -0
- 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/Query.js +64 -9
- 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/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 +32 -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 +41 -7
- 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/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 +94 -84
- 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,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
|
+
}
|
|
@@ -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';
|
|
@@ -1460,6 +1460,25 @@ export default class Dream {
|
|
|
1460
1460
|
* with no gaps, accounting for the scopes specified in the
|
|
1461
1461
|
* corresponding Sortable decorator.
|
|
1462
1462
|
*
|
|
1463
|
+
* Each sort scope is renumbered in its own transaction, under the same
|
|
1464
|
+
* advisory lock every other position-mutating path takes on that scope, from
|
|
1465
|
+
* a read of the scope taken inside that lock — so a concurrent create or
|
|
1466
|
+
* destroy cannot make the renumbering land on positions it never read.
|
|
1467
|
+
*
|
|
1468
|
+
* **Do not call `resort` from inside a transaction you own.** It opens those
|
|
1469
|
+
* transactions itself, on connections of its own, so it can neither join
|
|
1470
|
+
* yours nor roll back with it: its writes commit on their own. Worse, if your
|
|
1471
|
+
* transaction already holds the scope lock for a scope being renumbered — any
|
|
1472
|
+
* position-mutating write to that scope earlier in the transaction takes it —
|
|
1473
|
+
* `resort` waits on that lock from the outside, where no deadlock detector
|
|
1474
|
+
* can see the cycle, until the wait bound expires and it throws
|
|
1475
|
+
* `SortableScopeLockWaitTimedOut`.
|
|
1476
|
+
*
|
|
1477
|
+
* On an STI hierarchy the position space is shared by every class in the
|
|
1478
|
+
* hierarchy, exactly as it is for creates, updates and destroys. Calling
|
|
1479
|
+
* `resort` on a child class therefore renumbers the hierarchy's rows, not
|
|
1480
|
+
* only the receiver's.
|
|
1481
|
+
*
|
|
1463
1482
|
* ```ts
|
|
1464
1483
|
* class Post extends ApplicationModel {
|
|
1465
1484
|
* @deco.Sortable({ scope: ['user']})
|
|
@@ -2974,6 +2993,19 @@ export default class Dream {
|
|
|
2974
2993
|
* This is usually used for a soft-delete
|
|
2975
2994
|
* pattern
|
|
2976
2995
|
*
|
|
2996
|
+
* The veto is fail-closed, and the record is left
|
|
2997
|
+
* entirely untouched: on a soft-delete model the soft
|
|
2998
|
+
* delete is skipped as well, no `afterDestroy` or
|
|
2999
|
+
* `afterDestroyCommit` hooks run, since the record is
|
|
3000
|
+
* still present, and the destroy returns before the
|
|
3001
|
+
* `dependent: 'destroy'` cascade, so this record's
|
|
3002
|
+
* dependent associations survive too.
|
|
3003
|
+
*
|
|
3004
|
+
* A veto only stops the subtree rooted at the record
|
|
3005
|
+
* that called it. When a record is reached by another
|
|
3006
|
+
* record's cascade, vetoing spares it and its own
|
|
3007
|
+
* dependents; the destroy that reached it carries on.
|
|
3008
|
+
*
|
|
2977
3009
|
* ```ts
|
|
2978
3010
|
* class User extends ApplicationModel {
|
|
2979
3011
|
* @deco.BeforeDestroy()
|
|
@@ -3,6 +3,7 @@ export declare const FOREIGN_KEY_VIOLATION = "FOREIGN_KEY_VIOLATION";
|
|
|
3
3
|
export declare const INTEGRITY_CONSTRAINT_VIOLATION = "INTEGRITY_CONSTRAINT_VIOLATION";
|
|
4
4
|
export declare const INVALID_INPUT_SYNTAX = "INVALID_INPUT_SYNTAX";
|
|
5
5
|
export declare const COLUMN_OVERFLOW = "COLUMN_OVERFLOW";
|
|
6
|
+
export declare const LOCK_NOT_AVAILABLE = "LOCK_NOT_AVAILABLE";
|
|
6
7
|
export declare const NOT_NULL_VIOLATION = "NOT_NULL_VIOLATION";
|
|
7
8
|
export declare const RESTRICT_VIOLATION = "RESTRICT_VIOLATION";
|
|
8
9
|
export declare const UNIQUE_VIOLATION = "UNIQUE_VIOLATION";
|
|
@@ -17,6 +18,7 @@ export declare const PG_ERRORS: {
|
|
|
17
18
|
readonly '23000': "INTEGRITY_CONSTRAINT_VIOLATION";
|
|
18
19
|
readonly '23001': "RESTRICT_VIOLATION";
|
|
19
20
|
readonly '23503': "FOREIGN_KEY_VIOLATION";
|
|
21
|
+
readonly '55P03': "LOCK_NOT_AVAILABLE";
|
|
20
22
|
};
|
|
21
23
|
type PgErrorType = (typeof PG_ERRORS)[keyof typeof PG_ERRORS];
|
|
22
24
|
export declare function pgErrorType(error: any): PgErrorType | null;
|
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marks an integer column as a sortable position: Dream keeps the positions of
|
|
3
|
+
* every record in a sort scope contiguous, starting at 1, as records are
|
|
4
|
+
* created, moved, destroyed and undestroyed.
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* class Post extends ApplicationModel {
|
|
8
|
+
* @deco.Sortable({ scope: 'user' })
|
|
9
|
+
* public position: number
|
|
10
|
+
* }
|
|
11
|
+
*
|
|
12
|
+
* await post.update({ position: 2 }) // the records at 2 and above shift up
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* A position past the end of the scope is clamped to the end, and a position
|
|
16
|
+
* below 1 — or none at all — lands the record at the end.
|
|
17
|
+
*
|
|
18
|
+
* **A save that changes the sort scope ignores a position given alongside it.**
|
|
19
|
+
* The record lands at the end of the scope it moves into, whatever position the
|
|
20
|
+
* same `update` supplied:
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* await post.update({ user: otherUser, position: 1 })
|
|
24
|
+
* // post is now the last record in otherUser's scope, not the first
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* Move it in two saves to place it: `await post.update({ user: otherUser })`,
|
|
28
|
+
* then `await post.update({ position: 1 })`.
|
|
29
|
+
*
|
|
30
|
+
* Sortable requires a query driver that supports advisory transaction locks —
|
|
31
|
+
* the `PostgresQueryDriver` does — since every position write serializes the
|
|
32
|
+
* writers of its sort scope on one.
|
|
33
|
+
*/
|
|
1
34
|
export default function Sortable(opts?: SortableOpts): any;
|
|
2
35
|
interface SortableOpts {
|
|
3
36
|
/**
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import Dream from '../../../../Dream.js';
|
|
2
|
+
import DreamTransaction from '../../../../dream/DreamTransaction.js';
|
|
3
|
+
/**
|
|
4
|
+
* @internal
|
|
5
|
+
*
|
|
6
|
+
* Serializes every writer of a set of sort scopes by taking those scopes'
|
|
7
|
+
* transaction-scoped advisory locks. They are released when the enclosing
|
|
8
|
+
* transaction ends, so there is nothing to release here.
|
|
9
|
+
*
|
|
10
|
+
* Keys this transaction already holds are dropped rather than re-acquired: the
|
|
11
|
+
* lock is held to the end of the transaction either way, so the statement would
|
|
12
|
+
* do nothing but cost a round trip inside the lock window. Whatever is left is
|
|
13
|
+
* taken in the order given — callers that need more than one sort them first,
|
|
14
|
+
* so that two operations touching the same pair of scopes cannot deadlock
|
|
15
|
+
* against each other — and in one statement, on drivers that can express it.
|
|
16
|
+
*
|
|
17
|
+
* Waiting for a key another transaction holds is bounded by the driver, which
|
|
18
|
+
* raises `SortableScopeLockWaitTimedOut` rather than waiting forever; see
|
|
19
|
+
* `QueryDriverBase.acquireAdvisoryTransactionLocks`.
|
|
20
|
+
*
|
|
21
|
+
* @param dream - the record whose connection provides the driver
|
|
22
|
+
* @param txn - the transaction the locks are scoped to
|
|
23
|
+
* @param keys - the scope keys to acquire, in acquisition order
|
|
24
|
+
*/
|
|
25
|
+
export default function acquireSortableScopeLocks(dream: Dream, txn: DreamTransaction<any>, keys: bigint[]): Promise<void>;
|
package/dist/types/src/decorators/field/sortable/helpers/acquireStabilizedSortableBatchLocks.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import Dream from '../../../../Dream.js';
|
|
2
|
+
import DreamTransaction from '../../../../dream/DreamTransaction.js';
|
|
3
|
+
/**
|
|
4
|
+
* The most advisory sort-scope locks a batch preflight will let one transaction
|
|
5
|
+
* hold.
|
|
6
|
+
*
|
|
7
|
+
* Advisory locks are entries in a cluster-wide shared lock table sized at
|
|
8
|
+
* startup (`max_locks_per_transaction` × `max_connections`, 6400 entries under
|
|
9
|
+
* the stock settings), and a transaction holds every key it takes until it
|
|
10
|
+
* ends. `batchSize` is the caller's to choose and nothing bounds the number of
|
|
11
|
+
* distinct sort scopes a batch's records span, so without a limit a wide enough
|
|
12
|
+
* batch over a high-cardinality scope can starve every other connection in the
|
|
13
|
+
* cluster of a resource they all draw on — a failure that lands on unrelated
|
|
14
|
+
* transactions rather than on the one that caused it.
|
|
15
|
+
*
|
|
16
|
+
* The count is **cumulative over the transaction**: each preflight round counts
|
|
17
|
+
* the keys the transaction already holds together with the keys that round
|
|
18
|
+
* would add. So the bound holds across the rounds of one stabilization loop
|
|
19
|
+
* (each of which can discover a different set of scopes), and across the
|
|
20
|
+
* batches of a multi-batch run inside a caller-owned transaction, where
|
|
21
|
+
* advisory locks are released only when the caller commits.
|
|
22
|
+
*
|
|
23
|
+
* The headroom this leaves: the locked-batch defaults are ten records per batch
|
|
24
|
+
* (`Query.BATCH_SIZES.LOCKED_DESTROY` / `LOCKED_UPDATE`), so reaching the bound
|
|
25
|
+
* takes a caller who raises `batchSize` by two orders of magnitude and whose
|
|
26
|
+
* records each occupy a sort scope of their own — or a long run of such batches
|
|
27
|
+
* inside one transaction. Two shapes consume the budget faster than one key per
|
|
28
|
+
* record: a model declaring more than one sortable field needs one key per
|
|
29
|
+
* field, and the attribute form of `update` needs up to two per record per
|
|
30
|
+
* field, since the record's current scope and its destination scope are
|
|
31
|
+
* different keys.
|
|
32
|
+
*
|
|
33
|
+
* Only the batch preflight enforces this. A per-record save, destroy or
|
|
34
|
+
* undestroy takes its keys without counting them, so what they take is counted
|
|
35
|
+
* by the next preflight in the same transaction but cannot itself raise.
|
|
36
|
+
*/
|
|
37
|
+
export declare const MAX_SORTABLE_BATCH_SCOPE_LOCKS = 1000;
|
|
38
|
+
/**
|
|
39
|
+
* @internal
|
|
40
|
+
*
|
|
41
|
+
* The operation-level scope-lock preflight for the batched, row-locking query
|
|
42
|
+
* APIs — `Query#destroy({ lock: true })` and the attribute form of
|
|
43
|
+
* `Query#update(attributes, { lock: true })`.
|
|
44
|
+
*
|
|
45
|
+
* Why it exists: those operations take *row* locks (`takeAll({ lock: true })`)
|
|
46
|
+
* and then run per-record sortable work that takes *advisory* scope locks. Two
|
|
47
|
+
* transactions running in that order can each hold what the other is waiting
|
|
48
|
+
* for. Acquiring every advisory key the batch needs — across every sortable
|
|
49
|
+
* field and every candidate record — in one sorted pass, before a single row is
|
|
50
|
+
* claimed, removes the inversion: the advisory locks are always taken first, and
|
|
51
|
+
* always in the same order.
|
|
52
|
+
*
|
|
53
|
+
* The key set cannot simply be read once: `Query.lockedBatches` plucks only
|
|
54
|
+
* primary keys, so the candidates' scope columns have to be read separately, and
|
|
55
|
+
* that first read is not protected by any lock. So the read is stabilized the
|
|
56
|
+
* same way the per-record paths stabilize theirs, through the same
|
|
57
|
+
* `stabilizeSortableScopeLocks` protocol — acquire on what the unlocked read
|
|
58
|
+
* implies, re-read under those locks, take any additional key the re-read
|
|
59
|
+
* reveals — with the same bound, and the same `SortableScopeDidNotStabilize`
|
|
60
|
+
* when a batch of rows keeps moving between scopes faster than it can be read.
|
|
61
|
+
*
|
|
62
|
+
* A model with no sortable fields never reaches the driver seam, so this is
|
|
63
|
+
* inert — and free — for every other model.
|
|
64
|
+
*
|
|
65
|
+
* How many keys the transaction may hold is bounded — see
|
|
66
|
+
* {@link MAX_SORTABLE_BATCH_SCOPE_LOCKS} — and a batch that would carry it past
|
|
67
|
+
* the bound is refused before it claims any row. The first round refuses before
|
|
68
|
+
* anything at all is locked; a later round refuses after that loop's earlier
|
|
69
|
+
* rounds took their keys, which stay held until the transaction ends like every
|
|
70
|
+
* advisory lock this branch takes.
|
|
71
|
+
*
|
|
72
|
+
* What the preflight can and cannot see: `incomingScopeValue` recognizes an
|
|
73
|
+
* attribute that names a scope column directly or the BelongsTo association
|
|
74
|
+
* backing it. A destination scope produced any other way — by a custom setter,
|
|
75
|
+
* a virtual attribute, or a `beforeSave` hook that moves the record between
|
|
76
|
+
* scopes during the per-record save — is invisible here, and those shapes are
|
|
77
|
+
* preflight fallbacks like the callback form of `update` and a multi-batch run
|
|
78
|
+
* inside a caller-owned transaction: the missing key is taken as the
|
|
79
|
+
* per-record save reaches it, after this batch's row locks, and the residual
|
|
80
|
+
* failure is Postgres's `40P01` deadlock error — a visible, retryable failure
|
|
81
|
+
* rather than a silent wrong ordering.
|
|
82
|
+
*
|
|
83
|
+
* @param dreamInstance - a bare instance of the Query's model, used for the
|
|
84
|
+
* table, the connection, and the scope column resolution
|
|
85
|
+
* @param txn - the batch's transaction; the locks live until it ends
|
|
86
|
+
* @param primaryKeyValues - the candidate records this batch will try to claim
|
|
87
|
+
* @param incomingAttributes - the attributes the operation will write, when they
|
|
88
|
+
* are knowable before the claim (the attribute form of `update`); a scope
|
|
89
|
+
* column among them means the batch also needs the destination scope's key
|
|
90
|
+
*/
|
|
91
|
+
export default function acquireStabilizedSortableBatchLocks(dreamInstance: Dream, txn: DreamTransaction<any>, primaryKeyValues: unknown[], incomingAttributes?: Record<string, unknown>): Promise<void>;
|
package/dist/types/src/decorators/field/sortable/helpers/acquireStabilizedSortableScopeLocks.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import Dream from '../../../../Dream.js';
|
|
2
|
+
import DreamTransaction from '../../../../dream/DreamTransaction.js';
|
|
3
|
+
import { SortableFieldConfig } from '../Sortable.js';
|
|
4
|
+
import { SortableSnapshotRead } from './sortableSnapshot.js';
|
|
5
|
+
/**
|
|
6
|
+
* @internal
|
|
7
|
+
*
|
|
8
|
+
* Takes the scope locks a per-record position-mutating operation needs, and
|
|
9
|
+
* returns the database snapshot read under them. Every per-record path goes
|
|
10
|
+
* through this: a position- or scope-changing save (from `saveDream`'s sortable
|
|
11
|
+
* phase), a destroy (from the sortable destroy phase), and an undestroy (from
|
|
12
|
+
* `doUndestroyDream`). A destroy and an undestroy have no incoming scope
|
|
13
|
+
* values, so their instance-derived key set is a single key per sortable field —
|
|
14
|
+
* the scope the instance believes the row occupies — and the snapshot is what
|
|
15
|
+
* corrects it.
|
|
16
|
+
*
|
|
17
|
+
* The first pass locks the key set the instance implies, since the snapshot
|
|
18
|
+
* cannot be read until something is locked; every later pass locks what the
|
|
19
|
+
* snapshot read under those locks reveals. `stabilizeSortableScopeLocks` owns
|
|
20
|
+
* that protocol, its bound, and its failure — including the guarantee that a
|
|
21
|
+
* second pass always runs, so the snapshot is read under the locks even when
|
|
22
|
+
* the transaction already held every key the instance implies (a record being
|
|
23
|
+
* walked inside a locked batch, whose preflight took the whole batch's keys).
|
|
24
|
+
*
|
|
25
|
+
* @param dream - the record whose scope is being locked
|
|
26
|
+
* @param txn - the transaction the operation's write runs in
|
|
27
|
+
* @param configs - the sortable fields this operation performs position work for
|
|
28
|
+
* @returns the snapshot read under the locks, alongside whether the row was
|
|
29
|
+
* physically there to read — an empty snapshot map on its own cannot say
|
|
30
|
+
*/
|
|
31
|
+
export default function acquireStabilizedSortableScopeLocks(dream: Dream, txn: DreamTransaction<any>, configs: SortableFieldConfig[]): Promise<SortableSnapshotRead>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import Dream from '../../../../Dream.js';
|
|
2
|
+
/**
|
|
3
|
+
* @internal
|
|
4
|
+
*
|
|
5
|
+
* The canonical text form of one sort-scope value — the thing an advisory lock
|
|
6
|
+
* key and a resort group identity are actually built from.
|
|
7
|
+
*
|
|
8
|
+
* Two writers of the same physical sort scope have to derive the same key, and
|
|
9
|
+
* they do not always hold the value in the same JavaScript shape. A save that
|
|
10
|
+
* has not been written yet holds the *pending* value the caller supplied
|
|
11
|
+
* (`'1.00'`, an uppercase UUID, an object whose keys are in source order),
|
|
12
|
+
* while a snapshot read, a create's post-INSERT read and `resort`'s table read
|
|
13
|
+
* hold the value Postgres *hydrated* (`1`, a lowercase UUID, an object whose
|
|
14
|
+
* keys are in jsonb's own order). Postgres considers those pairs one value;
|
|
15
|
+
* `typeof`-only normalization considers them two, and two keys do not exclude
|
|
16
|
+
* each other.
|
|
17
|
+
*
|
|
18
|
+
* So canonicalization is keyed on the column's runtime `dbType`, and the
|
|
19
|
+
* canonical form of each type is **the form the hydrated value already takes**.
|
|
20
|
+
* That anchoring is what makes this safe to ship without a migration or a
|
|
21
|
+
* coordinated deploy: no key derived from a database-hydrated value moves, and
|
|
22
|
+
* the pending-side keys that do move are exactly the ones that disagree with
|
|
23
|
+
* every hydrated-path writer of the same scope today.
|
|
24
|
+
*
|
|
25
|
+
* The classes handled, and what the hydrated value looks like:
|
|
26
|
+
*
|
|
27
|
+
* - `uuid` — Postgres renders UUIDs lowercase, so the canonical form is
|
|
28
|
+
* lowercase.
|
|
29
|
+
* - `numeric` / `real` / `double precision` — hydrated through
|
|
30
|
+
* `parsePostgresDecimal`, i.e. `parseFloat`, so `'1.00'` and `1` both
|
|
31
|
+
* canonicalize to `'1'`.
|
|
32
|
+
* - `jsonb` — hydrated by `JSON.parse` of jsonb's own output, whose object keys
|
|
33
|
+
* are ordered by key length and then bytewise. Objects are re-emitted in that
|
|
34
|
+
* order, recursively, so property insertion order stops mattering.
|
|
35
|
+
* - `boolean` — hydrated as a JavaScript boolean; Postgres's own input literals
|
|
36
|
+
* (`'t'`, `'yes'`, `'0'`, …) canonicalize to `'true'`/`'false'`.
|
|
37
|
+
* - `date` and the four timestamp/time types — hydrated as `CalendarDate`,
|
|
38
|
+
* `DateTime`, `ClockTime` or `ClockTimeTz`, whose JSON form is the quoted ISO
|
|
39
|
+
* string. Strings are quoted the same way so a pending `'2026-02-07'` and a
|
|
40
|
+
* hydrated `CalendarDate` agree.
|
|
41
|
+
*
|
|
42
|
+
* Everything else — `bigint` and `character varying`, which is to say the
|
|
43
|
+
* foreign keys and text columns nearly every sort scope is made of — is left on
|
|
44
|
+
* the untyped normalization below, unchanged.
|
|
45
|
+
*
|
|
46
|
+
* Two residuals are known and accepted rather than fixed:
|
|
47
|
+
*
|
|
48
|
+
* - **`citext`**: Postgres stores the case a row was written with while
|
|
49
|
+
* comparing case-insensitively, so two rows of one physical scope hydrate to
|
|
50
|
+
* values that differ in case and split the key on their own. Lowercasing
|
|
51
|
+
* would fix the split but would *move* hydrated keys, which is the one thing
|
|
52
|
+
* this design refuses to do.
|
|
53
|
+
* - **Temporal values that agree on the instant but not on the text**: a zone
|
|
54
|
+
* offset or a fractional-second precision that differs from the hydrated
|
|
55
|
+
* rendering still yields a different key. Only the string-vs-object quoting
|
|
56
|
+
* mismatch is canonicalized.
|
|
57
|
+
*
|
|
58
|
+
* Both are over-serialization or a missed exclusion between two *pending*-side
|
|
59
|
+
* writers, never a corrupt position under a key two writers do share.
|
|
60
|
+
*
|
|
61
|
+
* @param dream - the record whose scope column is being canonicalized
|
|
62
|
+
* @param column - the physical database column
|
|
63
|
+
* @param value - the pending or hydrated value held for that column
|
|
64
|
+
* @returns the canonical text form, or `null` for an absent value
|
|
65
|
+
*/
|
|
66
|
+
export default function canonicalScopeValue(dream: Dream, column: string, value: unknown): string | null;
|
|
67
|
+
/**
|
|
68
|
+
* @internal
|
|
69
|
+
*
|
|
70
|
+
* The type-blind fallback, and the shape every unrecognized `dbType` keeps:
|
|
71
|
+
* the same foreign key can arrive as a string or a number depending on the path
|
|
72
|
+
* that loaded it, so values are rendered as text and null is kept distinct from
|
|
73
|
+
* every string, including the empty one.
|
|
74
|
+
*/
|
|
75
|
+
export declare function untypedScopeValue(value: unknown): string | null;
|
|
76
|
+
/**
|
|
77
|
+
* @internal
|
|
78
|
+
*
|
|
79
|
+
* Postgres's own boolean input literals, lower-cased and trimmed, which a
|
|
80
|
+
* pending value may hold while the hydrated value is a JavaScript boolean.
|
|
81
|
+
* Exported so the spec drives its cases from the sets themselves rather than
|
|
82
|
+
* from a hand-copied list that can silently drift out of step with them.
|
|
83
|
+
*/
|
|
84
|
+
export declare const BOOLEAN_TRUE_LITERALS: ReadonlySet<string>;
|
|
85
|
+
export declare const BOOLEAN_FALSE_LITERALS: ReadonlySet<string>;
|
|
@@ -1,2 +1,15 @@
|
|
|
1
1
|
import Dream from '../../../../Dream.js';
|
|
2
|
+
import { SortableFieldConfig } from '../Sortable.js';
|
|
2
3
|
export default function clearCachedSortableValues(dream: Dream, positionField: string): void;
|
|
4
|
+
/**
|
|
5
|
+
* @internal
|
|
6
|
+
*
|
|
7
|
+
* Discards both halves of what a save's sortable phase stashes on the
|
|
8
|
+
* instance: the cached values the position work reads, and the snapshot of the
|
|
9
|
+
* row it would have computed its shift against. The position work clears them
|
|
10
|
+
* itself as it consumes them, so this is for the paths where it does not run —
|
|
11
|
+
* the row was already gone when the snapshot read found it, or the save's
|
|
12
|
+
* transaction rolled back — leaving a later save of the same instance to read
|
|
13
|
+
* the row for itself rather than inherit this save's reading of it.
|
|
14
|
+
*/
|
|
15
|
+
export declare function clearSortableSaveState(dream: Dream, configs: SortableFieldConfig[]): void;
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import Dream from '../../../../Dream.js';
|
|
2
2
|
import Query from '../../../../dream/Query.js';
|
|
3
|
-
|
|
3
|
+
import { SortableSnapshot } from './sortableSnapshot.js';
|
|
4
|
+
export default function decrementPositionForScopedRecordsGreaterThanPosition(position: number, { dream, positionField, query, scope, snapshot, }: {
|
|
4
5
|
dream: Dream;
|
|
5
6
|
positionField: string;
|
|
6
7
|
query: Query<Dream>;
|
|
7
8
|
scope: string | string[] | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* The destroyed row's real scope values, read before the delete. The scope
|
|
11
|
+
* being compacted is the one the row physically occupied, which is not
|
|
12
|
+
* necessarily the one the instance was loaded from.
|
|
13
|
+
*/
|
|
14
|
+
snapshot?: SortableSnapshot | undefined;
|
|
8
15
|
}): Promise<void>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import Dream from '../../../../Dream.js';
|
|
2
|
+
import Query from '../../../../dream/Query.js';
|
|
3
|
+
/**
|
|
4
|
+
* @internal
|
|
5
|
+
*
|
|
6
|
+
* Narrows a Dream `Query` to one sort scope, **reading the scope values off the
|
|
7
|
+
* in-memory instance**. That is only the scope the row physically occupies when
|
|
8
|
+
* the instance is known to agree with the row — inside a transaction that has
|
|
9
|
+
* just written those values, or on a record read under the scope lock.
|
|
10
|
+
*
|
|
11
|
+
* Where the row may have been moved by another writer, the caller wants
|
|
12
|
+
* `filterQueryToScopeValues` instead, which takes each column's value from the
|
|
13
|
+
* caller (the snapshot read under the lock) rather than from the instance.
|
|
14
|
+
*/
|
|
15
|
+
export default function filterQueryToInstanceScope(query: Query<Dream>, dream: Dream, scope?: string | string[]): Query<Dream, Readonly<{
|
|
16
|
+
joinedAssociations: Readonly<[]>;
|
|
17
|
+
rootTableName: string;
|
|
18
|
+
rootTableAlias: string;
|
|
19
|
+
allowPreload: true;
|
|
20
|
+
allowLeftJoinPreload: true;
|
|
21
|
+
allowLimit: true;
|
|
22
|
+
allowOffset: true;
|
|
23
|
+
allowPaginate: true;
|
|
24
|
+
}>>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { SelectQueryBuilder, UpdateQueryBuilder } from 'kysely';
|
|
2
|
+
import Dream from '../../../../Dream.js';
|
|
3
|
+
/**
|
|
4
|
+
* @internal
|
|
5
|
+
*
|
|
6
|
+
* Narrows a kysely query to one sort scope, taking each scope column's value
|
|
7
|
+
* **from the caller** rather than from the in-memory instance — the snapshot
|
|
8
|
+
* read under the scope lock on the paths that compact a scope a record is
|
|
9
|
+
* leaving, which is not necessarily the scope the instance was loaded from.
|
|
10
|
+
*
|
|
11
|
+
* This is also the one place the null branch lives: a null scope value has to
|
|
12
|
+
* be matched with `is null`, since `column = NULL` is never true and the filter
|
|
13
|
+
* would silently match nothing at all in a null-valued sort scope.
|
|
14
|
+
*
|
|
15
|
+
* The instance-reading sibling is `filterQueryToInstanceScope`.
|
|
16
|
+
*
|
|
17
|
+
* @param dream - the record whose class resolves the scope members to columns
|
|
18
|
+
* @param kyselyQuery - the query to narrow
|
|
19
|
+
* @param whereValueCB - the value to match each scope column against
|
|
20
|
+
* @param scope - the sortable field's scope
|
|
21
|
+
*/
|
|
22
|
+
export default function filterQueryToScopeValues<QB extends UpdateQueryBuilder<any, any, any, any> | SelectQueryBuilder<any, any, any>>(dream: Dream, kyselyQuery: QB, whereValueCB: (column: string) => any, scope?: string | string[]): QB;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import DreamTransaction from '../../../../dream/DreamTransaction.js';
|
|
2
|
+
/**
|
|
3
|
+
* @internal
|
|
4
|
+
*
|
|
5
|
+
* The sortable scope-lock keys this transaction already holds.
|
|
6
|
+
*
|
|
7
|
+
* Advisory transaction locks are released only when the transaction ends, so a
|
|
8
|
+
* key taken once is held for the rest of it: every later acquisition of that
|
|
9
|
+
* key is a statement whose entire effect is to observe that the transaction
|
|
10
|
+
* already has what it is asking for. Tracking them turns those statements into
|
|
11
|
+
* nothing at all — the per-field acquisition inside the position write, and
|
|
12
|
+
* every per-record acquisition inside a locked batch whose preflight already
|
|
13
|
+
* took the batch's whole key set.
|
|
14
|
+
*
|
|
15
|
+
* Being the transaction's whole ledger is also what lets the locked-batch
|
|
16
|
+
* preflight count its bound cumulatively rather than per batch: how many scope
|
|
17
|
+
* locks one more batch would leave the transaction holding is this set's size
|
|
18
|
+
* plus whatever of the batch's keys is not already in it.
|
|
19
|
+
*
|
|
20
|
+
* Keyed by the transaction object rather than by the connection, which is what
|
|
21
|
+
* makes it correct rather than merely fast: a nested transaction gets its own
|
|
22
|
+
* `DreamTransaction` and therefore its own set, so keys taken inside a
|
|
23
|
+
* subtransaction that rolls back — releasing its locks with it — are never
|
|
24
|
+
* remembered as held by the transaction that outlives it. The entry is
|
|
25
|
+
* discarded with the transaction object.
|
|
26
|
+
*/
|
|
27
|
+
export default function heldSortableScopeLockKeys(txn: DreamTransaction<any>): Set<bigint>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import Dream from '../../../../Dream.js';
|
|
2
|
+
import DreamTransaction from '../../../../dream/DreamTransaction.js';
|
|
3
|
+
/**
|
|
4
|
+
* @internal
|
|
5
|
+
*
|
|
6
|
+
* The compaction of a sortable destroy: closes the vacancy each sortable
|
|
7
|
+
* field's sort scope is left with, computed from the snapshot the preparation
|
|
8
|
+
* phase (`prepareSortableFieldsForDestroy`) read before the delete. Called
|
|
9
|
+
* directly from `destroyDream` after the delete and before any user
|
|
10
|
+
* `afterDestroy` hook — never registered as a hook among them — so every
|
|
11
|
+
* after-destroy hook observes the compacted scope regardless of where it was
|
|
12
|
+
* declared, exactly as every after-save hook observes the computed position.
|
|
13
|
+
*/
|
|
14
|
+
export default function performSortableDestroyWork(dream: Dream, txn: DreamTransaction<any>): Promise<void>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import Dream from '../../../../Dream.js';
|
|
2
|
+
import DreamTransaction from '../../../../dream/DreamTransaction.js';
|
|
3
|
+
import { SortableFieldConfig } from '../Sortable.js';
|
|
4
|
+
/**
|
|
5
|
+
* @internal
|
|
6
|
+
*
|
|
7
|
+
* The position work of a sortable save: computes each sortable field's real
|
|
8
|
+
* position under the scope lock and writes it. Called directly from
|
|
9
|
+
* `saveDream` after the driver write — never registered as an after-hook — so
|
|
10
|
+
* no user hook code can interleave with it while the scope lock is held, and
|
|
11
|
+
* on a self-opened save it is the last work before COMMIT releases the lock.
|
|
12
|
+
*/
|
|
13
|
+
export default function performSortablePositionWork(dream: Dream, txn: DreamTransaction<any>, sortableFieldConfigs: SortableFieldConfig[], { alreadyPersisted }: {
|
|
14
|
+
alreadyPersisted: boolean;
|
|
15
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import Dream from '../../../../Dream.js';
|
|
2
|
+
import DreamTransaction from '../../../../dream/DreamTransaction.js';
|
|
3
|
+
/**
|
|
4
|
+
* @internal
|
|
5
|
+
*
|
|
6
|
+
* The sortable preparation phase of a destroy. Called from `destroyDream` after
|
|
7
|
+
* every user `beforeDestroy` hook has run and before the delete — a phase, not
|
|
8
|
+
* a hook among them — so the operation's whole advisory key set, across every
|
|
9
|
+
* sortable field, is taken in one sorted stabilized acquisition, with one
|
|
10
|
+
* snapshot SELECT covering all fields. Per-field acquisition would take
|
|
11
|
+
* multi-field keys in declaration order, which deadlocks against the paths
|
|
12
|
+
* that acquire sorted.
|
|
13
|
+
*
|
|
14
|
+
* Pre-delete is the only seat available for the snapshot read: on a hard
|
|
15
|
+
* destroy the row is gone afterwards, and a soft destroy nulls the position
|
|
16
|
+
* columns in the same UPDATE as `deletedAt`. The lock is taken around the read
|
|
17
|
+
* so the snapshot cannot be invalidated between the read and the compaction;
|
|
18
|
+
* it is transaction-scoped, and `destroyDream` opens a transaction that spans
|
|
19
|
+
* the delete and the after-destroy hooks, so one acquisition covers both.
|
|
20
|
+
*
|
|
21
|
+
* The per-field compactions (`performSortableDestroyWork`) consume the cached
|
|
22
|
+
* snapshots.
|
|
23
|
+
*
|
|
24
|
+
* @returns false when the snapshot read found no row — this destroy has nothing
|
|
25
|
+
* to vacate, and compacting from the instance's remembered position would
|
|
26
|
+
* shift a scope some other writer has already closed. The delete's own
|
|
27
|
+
* affected-row count is the stronger signal (a writer outside the sortable
|
|
28
|
+
* path can remove the row after this read), so the caller checks both.
|
|
29
|
+
*/
|
|
30
|
+
export default function prepareSortableFieldsForDestroy(dream: Dream, txn: DreamTransaction<any>): Promise<boolean>;
|
|
31
|
+
/**
|
|
32
|
+
* @internal
|
|
33
|
+
*
|
|
34
|
+
* Discards what the preparation phase stashed on the instance. The per-field
|
|
35
|
+
* compaction clears its own snapshot as it consumes it, so this
|
|
36
|
+
* exists for the path where that compaction never runs: a destroy that removed
|
|
37
|
+
* nothing leaves the snapshot behind, and a later save or destroy of the same
|
|
38
|
+
* instance would compute its shift from a reading of a row this operation
|
|
39
|
+
* already found gone.
|
|
40
|
+
*/
|
|
41
|
+
export declare function clearSortableFieldsForDestroy(dream: Dream): void;
|