@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.
Files changed (330) hide show
  1. package/dist/cjs/src/Dream.js +63 -1
  2. package/dist/cjs/src/db/errors.js +4 -0
  3. package/dist/cjs/src/decorators/field/Encrypted.js +12 -0
  4. package/dist/cjs/src/decorators/field/sortable/Sortable.js +43 -118
  5. package/dist/cjs/src/decorators/field/sortable/helpers/acquireSortableScopeLocks.js +38 -0
  6. package/dist/cjs/src/decorators/field/sortable/helpers/acquireStabilizedSortableBatchLocks.js +183 -0
  7. package/dist/cjs/src/decorators/field/sortable/helpers/acquireStabilizedSortableScopeLocks.js +40 -0
  8. package/dist/cjs/src/decorators/field/sortable/helpers/canonicalScopeValue.js +194 -0
  9. package/dist/cjs/src/decorators/field/sortable/helpers/clearCachedSortableValues.js +18 -0
  10. package/dist/cjs/src/decorators/field/sortable/helpers/decrementScopedRecordsGreaterThanPosition.js +9 -9
  11. package/dist/cjs/src/decorators/field/sortable/helpers/filterQueryToInstanceScope.js +20 -0
  12. package/dist/cjs/src/decorators/field/sortable/helpers/filterQueryToScopeValues.js +32 -0
  13. package/dist/cjs/src/decorators/field/sortable/helpers/heldSortableScopeLockKeys.js +34 -0
  14. package/dist/cjs/src/decorators/field/sortable/helpers/performSortableDestroyWork.js +21 -0
  15. package/dist/cjs/src/decorators/field/sortable/helpers/performSortablePositionWork.js +36 -0
  16. package/dist/cjs/src/decorators/field/sortable/helpers/prepareSortableFieldsForDestroy.js +56 -0
  17. package/dist/cjs/src/decorators/field/sortable/helpers/prepareSortableFieldsForSave.js +84 -0
  18. package/dist/cjs/src/decorators/field/sortable/helpers/resortAllRecords.js +186 -29
  19. package/dist/cjs/src/decorators/field/sortable/helpers/setPosition.js +90 -46
  20. package/dist/cjs/src/decorators/field/sortable/helpers/sortableRowCache.js +73 -0
  21. package/dist/cjs/src/decorators/field/sortable/helpers/sortableScopeColumns.js +42 -0
  22. package/dist/cjs/src/decorators/field/sortable/helpers/sortableScopeLockKey.js +99 -0
  23. package/dist/cjs/src/decorators/field/sortable/helpers/sortableScopeLockKeys.js +72 -0
  24. package/dist/cjs/src/decorators/field/sortable/helpers/sortableSnapshot.js +101 -0
  25. package/dist/cjs/src/decorators/field/sortable/helpers/stabilizeSortableScopeLocks.js +86 -0
  26. package/dist/cjs/src/decorators/field/sortable/helpers/validPosition.js +21 -4
  27. package/dist/cjs/src/decorators/field/sortable/hooks/afterSortableDestroy.js +24 -7
  28. package/dist/cjs/src/decorators/field/sortable/hooks/afterSortableUpdate.js +10 -0
  29. package/dist/cjs/src/dream/Query.js +73 -11
  30. package/dist/cjs/src/dream/QueryDriver/Base.js +75 -2
  31. package/dist/cjs/src/dream/QueryDriver/Kysely.js +11 -4
  32. package/dist/cjs/src/dream/QueryDriver/Postgres.js +74 -0
  33. package/dist/cjs/src/dream/internal/destroyDream.js +60 -6
  34. package/dist/cjs/src/dream/internal/saveDream.js +109 -7
  35. package/dist/cjs/src/dream/internal/similarity/SimilarityBuilder.js +19 -4
  36. package/dist/cjs/src/dream/internal/softDeleteDream.js +13 -1
  37. package/dist/cjs/src/dream/internal/undestroyDream.js +56 -8
  38. package/dist/cjs/src/dream-app/index.js +21 -0
  39. package/dist/cjs/src/encrypt/assertNoEncryptedColumnWrites.js +62 -0
  40. package/dist/cjs/src/errors/SortableBatchRequiresTooManyScopeLocks.js +57 -0
  41. package/dist/cjs/src/errors/SortableRequiresAdvisoryTransactionLocks.js +26 -0
  42. package/dist/cjs/src/errors/SortableScopeDidNotStabilize.js +39 -0
  43. package/dist/cjs/src/errors/SortableScopeLockWaitTimedOut.js +50 -0
  44. package/dist/cjs/src/errors/encrypt/CannotSetEncryptedColumnInQueryUpdate.js +31 -0
  45. package/dist/cjs/src/package-exports/errors.js +3 -0
  46. package/dist/esm/src/Dream.js +63 -1
  47. package/dist/esm/src/db/errors.js +4 -0
  48. package/dist/esm/src/decorators/field/Encrypted.js +12 -0
  49. package/dist/esm/src/decorators/field/sortable/Sortable.js +43 -118
  50. package/dist/esm/src/decorators/field/sortable/helpers/acquireSortableScopeLocks.js +38 -0
  51. package/dist/esm/src/decorators/field/sortable/helpers/acquireStabilizedSortableBatchLocks.js +183 -0
  52. package/dist/esm/src/decorators/field/sortable/helpers/acquireStabilizedSortableScopeLocks.js +40 -0
  53. package/dist/esm/src/decorators/field/sortable/helpers/canonicalScopeValue.js +194 -0
  54. package/dist/esm/src/decorators/field/sortable/helpers/clearCachedSortableValues.js +18 -0
  55. package/dist/esm/src/decorators/field/sortable/helpers/decrementScopedRecordsGreaterThanPosition.js +9 -9
  56. package/dist/esm/src/decorators/field/sortable/helpers/filterQueryToInstanceScope.js +20 -0
  57. package/dist/esm/src/decorators/field/sortable/helpers/filterQueryToScopeValues.js +32 -0
  58. package/dist/esm/src/decorators/field/sortable/helpers/heldSortableScopeLockKeys.js +34 -0
  59. package/dist/esm/src/decorators/field/sortable/helpers/performSortableDestroyWork.js +21 -0
  60. package/dist/esm/src/decorators/field/sortable/helpers/performSortablePositionWork.js +36 -0
  61. package/dist/esm/src/decorators/field/sortable/helpers/prepareSortableFieldsForDestroy.js +56 -0
  62. package/dist/esm/src/decorators/field/sortable/helpers/prepareSortableFieldsForSave.js +84 -0
  63. package/dist/esm/src/decorators/field/sortable/helpers/resortAllRecords.js +186 -29
  64. package/dist/esm/src/decorators/field/sortable/helpers/setPosition.js +90 -46
  65. package/dist/esm/src/decorators/field/sortable/helpers/sortableRowCache.js +73 -0
  66. package/dist/esm/src/decorators/field/sortable/helpers/sortableScopeColumns.js +42 -0
  67. package/dist/esm/src/decorators/field/sortable/helpers/sortableScopeLockKey.js +99 -0
  68. package/dist/esm/src/decorators/field/sortable/helpers/sortableScopeLockKeys.js +72 -0
  69. package/dist/esm/src/decorators/field/sortable/helpers/sortableSnapshot.js +101 -0
  70. package/dist/esm/src/decorators/field/sortable/helpers/stabilizeSortableScopeLocks.js +86 -0
  71. package/dist/esm/src/decorators/field/sortable/helpers/validPosition.js +21 -4
  72. package/dist/esm/src/decorators/field/sortable/hooks/afterSortableDestroy.js +24 -7
  73. package/dist/esm/src/decorators/field/sortable/hooks/afterSortableUpdate.js +10 -0
  74. package/dist/esm/src/dream/Query.js +73 -11
  75. package/dist/esm/src/dream/QueryDriver/Base.js +75 -2
  76. package/dist/esm/src/dream/QueryDriver/Kysely.js +11 -4
  77. package/dist/esm/src/dream/QueryDriver/Postgres.js +74 -0
  78. package/dist/esm/src/dream/internal/destroyDream.js +60 -6
  79. package/dist/esm/src/dream/internal/saveDream.js +109 -7
  80. package/dist/esm/src/dream/internal/similarity/SimilarityBuilder.js +19 -4
  81. package/dist/esm/src/dream/internal/softDeleteDream.js +13 -1
  82. package/dist/esm/src/dream/internal/undestroyDream.js +56 -8
  83. package/dist/esm/src/dream-app/index.js +21 -0
  84. package/dist/esm/src/encrypt/assertNoEncryptedColumnWrites.js +62 -0
  85. package/dist/esm/src/errors/SortableBatchRequiresTooManyScopeLocks.js +57 -0
  86. package/dist/esm/src/errors/SortableRequiresAdvisoryTransactionLocks.js +26 -0
  87. package/dist/esm/src/errors/SortableScopeDidNotStabilize.js +39 -0
  88. package/dist/esm/src/errors/SortableScopeLockWaitTimedOut.js +50 -0
  89. package/dist/esm/src/errors/encrypt/CannotSetEncryptedColumnInQueryUpdate.js +31 -0
  90. package/dist/esm/src/package-exports/errors.js +3 -0
  91. package/dist/types/src/Dream.d.ts +56 -0
  92. package/dist/types/src/db/errors.d.ts +2 -0
  93. package/dist/types/src/decorators/field/sortable/Sortable.d.ts +33 -0
  94. package/dist/types/src/decorators/field/sortable/helpers/acquireSortableScopeLocks.d.ts +25 -0
  95. package/dist/types/src/decorators/field/sortable/helpers/acquireStabilizedSortableBatchLocks.d.ts +91 -0
  96. package/dist/types/src/decorators/field/sortable/helpers/acquireStabilizedSortableScopeLocks.d.ts +31 -0
  97. package/dist/types/src/decorators/field/sortable/helpers/canonicalScopeValue.d.ts +85 -0
  98. package/dist/types/src/decorators/field/sortable/helpers/clearCachedSortableValues.d.ts +13 -0
  99. package/dist/types/src/decorators/field/sortable/helpers/decrementScopedRecordsGreaterThanPosition.d.ts +8 -1
  100. package/dist/types/src/decorators/field/sortable/helpers/filterQueryToInstanceScope.d.ts +24 -0
  101. package/dist/types/src/decorators/field/sortable/helpers/filterQueryToScopeValues.d.ts +22 -0
  102. package/dist/types/src/decorators/field/sortable/helpers/heldSortableScopeLockKeys.d.ts +27 -0
  103. package/dist/types/src/decorators/field/sortable/helpers/performSortableDestroyWork.d.ts +14 -0
  104. package/dist/types/src/decorators/field/sortable/helpers/performSortablePositionWork.d.ts +15 -0
  105. package/dist/types/src/decorators/field/sortable/helpers/prepareSortableFieldsForDestroy.d.ts +41 -0
  106. package/dist/types/src/decorators/field/sortable/helpers/prepareSortableFieldsForSave.d.ts +35 -0
  107. package/dist/types/src/decorators/field/sortable/helpers/resortAllRecords.d.ts +32 -0
  108. package/dist/types/src/decorators/field/sortable/helpers/setPosition.d.ts +27 -11
  109. package/dist/types/src/decorators/field/sortable/helpers/sortableRowCache.d.ts +48 -0
  110. package/dist/types/src/decorators/field/sortable/helpers/sortableScopeColumns.d.ts +30 -0
  111. package/dist/types/src/decorators/field/sortable/helpers/sortableScopeLockKey.d.ts +44 -0
  112. package/dist/types/src/decorators/field/sortable/helpers/sortableScopeLockKeys.d.ts +51 -0
  113. package/dist/types/src/decorators/field/sortable/helpers/sortableSnapshot.d.ts +92 -0
  114. package/dist/types/src/decorators/field/sortable/helpers/stabilizeSortableScopeLocks.d.ts +59 -0
  115. package/dist/types/src/decorators/field/sortable/helpers/validPosition.d.ts +14 -2
  116. package/dist/types/src/decorators/field/sortable/hooks/afterSortableCreate.d.ts +1 -1
  117. package/dist/types/src/decorators/field/sortable/hooks/afterSortableDestroy.d.ts +3 -1
  118. package/dist/types/src/decorators/field/sortable/hooks/afterSortableUpdate.d.ts +1 -1
  119. package/dist/types/src/dream/Query.d.ts +47 -9
  120. package/dist/types/src/dream/QueryDriver/Base.d.ts +61 -1
  121. package/dist/types/src/dream/QueryDriver/Kysely.d.ts +6 -2
  122. package/dist/types/src/dream/QueryDriver/Postgres.d.ts +40 -0
  123. package/dist/types/src/dream/internal/softDeleteDream.d.ts +7 -1
  124. package/dist/types/src/dream-app/index.d.ts +18 -2
  125. package/dist/types/src/encrypt/assertNoEncryptedColumnWrites.d.ts +27 -0
  126. package/dist/types/src/errors/SortableBatchRequiresTooManyScopeLocks.d.ts +37 -0
  127. package/dist/types/src/errors/SortableRequiresAdvisoryTransactionLocks.d.ts +13 -0
  128. package/dist/types/src/errors/SortableScopeDidNotStabilize.d.ts +23 -0
  129. package/dist/types/src/errors/SortableScopeLockWaitTimedOut.d.ts +34 -0
  130. package/dist/types/src/errors/encrypt/CannotSetEncryptedColumnInQueryUpdate.d.ts +8 -0
  131. package/dist/types/src/package-exports/errors.d.ts +3 -0
  132. package/docs/assets/hierarchy.js +1 -1
  133. package/docs/assets/navigation.js +1 -1
  134. package/docs/assets/search.js +1 -1
  135. package/docs/classes/db.DreamMigrationHelpers.html +11 -11
  136. package/docs/classes/db.KyselyQueryDriver.html +88 -41
  137. package/docs/classes/db.PostgresQueryDriver.html +82 -42
  138. package/docs/classes/db.QueryDriverBase.html +86 -39
  139. package/docs/classes/errors.CheckConstraintViolation.html +3 -3
  140. package/docs/classes/errors.ColumnOverflow.html +3 -3
  141. package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +3 -3
  142. package/docs/classes/errors.DataIncompatibleWithDatabaseField.html +3 -3
  143. package/docs/classes/errors.DataTypeColumnTypeMismatch.html +3 -3
  144. package/docs/classes/errors.DecryptionError.html +2 -2
  145. package/docs/classes/errors.DecryptionParseError.html +2 -2
  146. package/docs/classes/errors.DecryptionRotationError.html +3 -3
  147. package/docs/classes/errors.GlobalNameNotSet.html +3 -3
  148. package/docs/classes/errors.InvalidCalendarDate.html +2 -2
  149. package/docs/classes/errors.InvalidClockTime.html +2 -2
  150. package/docs/classes/errors.InvalidClockTimeTz.html +2 -2
  151. package/docs/classes/errors.InvalidDateTime.html +2 -2
  152. package/docs/classes/errors.MissingSerializersDefinition.html +3 -3
  153. package/docs/classes/errors.NonLoadedAssociation.html +3 -3
  154. package/docs/classes/errors.NotNullViolation.html +3 -3
  155. package/docs/classes/errors.RecordNotFound.html +3 -3
  156. package/docs/classes/errors.SortableBatchRequiresTooManyScopeLocks.html +61 -0
  157. package/docs/classes/errors.SortableScopeDidNotStabilize.html +48 -0
  158. package/docs/classes/errors.SortableScopeLockWaitTimedOut.html +58 -0
  159. package/docs/classes/errors.ValidationError.html +3 -3
  160. package/docs/classes/index.CalendarDate.html +33 -33
  161. package/docs/classes/index.ClockTime.html +32 -32
  162. package/docs/classes/index.ClockTimeTz.html +35 -35
  163. package/docs/classes/index.DateTime.html +86 -86
  164. package/docs/classes/index.Decorators.html +19 -19
  165. package/docs/classes/index.Dream.html +151 -124
  166. package/docs/classes/index.DreamApp.html +11 -10
  167. package/docs/classes/index.DreamTransaction.html +2 -2
  168. package/docs/classes/index.Env.html +2 -2
  169. package/docs/classes/index.Query.html +124 -98
  170. package/docs/classes/system.CliFileWriter.html +4 -4
  171. package/docs/classes/system.DreamBin.html +2 -2
  172. package/docs/classes/system.DreamCLI.html +7 -7
  173. package/docs/classes/system.DreamImporter.html +2 -2
  174. package/docs/classes/system.DreamLogos.html +2 -2
  175. package/docs/classes/system.DreamSerializerBuilder.html +11 -11
  176. package/docs/classes/system.ObjectSerializerBuilder.html +8 -8
  177. package/docs/classes/system.PathHelpers.html +3 -3
  178. package/docs/classes/utils.Encrypt.html +3 -3
  179. package/docs/classes/utils.Range.html +2 -2
  180. package/docs/functions/db.closeAllDbConnections.html +1 -1
  181. package/docs/functions/db.dreamDbConnections.html +1 -1
  182. package/docs/functions/db.untypedDb.html +1 -1
  183. package/docs/functions/db.validateColumn.html +1 -1
  184. package/docs/functions/db.validateTable.html +1 -1
  185. package/docs/functions/errors.pgErrorType.html +1 -1
  186. package/docs/functions/index.DreamSerializer.html +1 -1
  187. package/docs/functions/index.ObjectSerializer.html +1 -1
  188. package/docs/functions/index.ReplicaSafe.html +1 -1
  189. package/docs/functions/index.STI.html +1 -1
  190. package/docs/functions/index.SoftDelete.html +1 -1
  191. package/docs/functions/utils.camelize.html +1 -1
  192. package/docs/functions/utils.capitalize.html +1 -1
  193. package/docs/functions/utils.cloneDeepSafe.html +1 -1
  194. package/docs/functions/utils.compact.html +1 -1
  195. package/docs/functions/utils.groupBy.html +1 -1
  196. package/docs/functions/utils.hyphenize.html +1 -1
  197. package/docs/functions/utils.intersection.html +1 -1
  198. package/docs/functions/utils.isEmpty.html +1 -1
  199. package/docs/functions/utils.normalizeUnicode.html +1 -1
  200. package/docs/functions/utils.pascalize.html +1 -1
  201. package/docs/functions/utils.percent.html +1 -1
  202. package/docs/functions/utils.range.html +1 -1
  203. package/docs/functions/utils.round.html +1 -1
  204. package/docs/functions/utils.sanitizeString.html +1 -1
  205. package/docs/functions/utils.snakeify.html +1 -1
  206. package/docs/functions/utils.sort.html +2 -2
  207. package/docs/functions/utils.sortBy.html +1 -1
  208. package/docs/functions/utils.sortObjectByKey.html +1 -1
  209. package/docs/functions/utils.sortObjectByValue.html +1 -1
  210. package/docs/functions/utils.uncapitalize.html +1 -1
  211. package/docs/functions/utils.uniq.html +1 -1
  212. package/docs/hierarchy.html +1 -1
  213. package/docs/interfaces/openapi.OpenapiDescription.html +2 -2
  214. package/docs/interfaces/openapi.OpenapiSchemaProperties.html +1 -1
  215. package/docs/interfaces/openapi.OpenapiSchemaPropertiesShorthand.html +1 -1
  216. package/docs/interfaces/openapi.OpenapiTypeFieldObject.html +1 -1
  217. package/docs/interfaces/types.BelongsToStatement.html +2 -2
  218. package/docs/interfaces/types.DecoratorContext.html +2 -2
  219. package/docs/interfaces/types.DreamAppInitOptions.html +2 -2
  220. package/docs/interfaces/types.DreamAppOpts.html +2 -2
  221. package/docs/interfaces/types.DreamDbConfig.html +5 -5
  222. package/docs/interfaces/types.DurationObject.html +2 -2
  223. package/docs/interfaces/types.EncryptOptions.html +2 -2
  224. package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
  225. package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
  226. package/docs/interfaces/types.SerializerRendererOpts.html +2 -2
  227. package/docs/modules/errors.html +1 -1
  228. package/docs/types/openapi.CommonOpenapiSchemaObjectFields.html +1 -1
  229. package/docs/types/openapi.OpenapiAllTypes.html +1 -1
  230. package/docs/types/openapi.OpenapiFormats.html +1 -1
  231. package/docs/types/openapi.OpenapiNumberFormats.html +1 -1
  232. package/docs/types/openapi.OpenapiPrimitiveBaseTypes.html +1 -1
  233. package/docs/types/openapi.OpenapiPrimitiveTypes.html +1 -1
  234. package/docs/types/openapi.OpenapiSchemaArray.html +1 -1
  235. package/docs/types/openapi.OpenapiSchemaArrayShorthand.html +1 -1
  236. package/docs/types/openapi.OpenapiSchemaBase.html +1 -1
  237. package/docs/types/openapi.OpenapiSchemaBody.html +1 -1
  238. package/docs/types/openapi.OpenapiSchemaBodyShorthand.html +1 -1
  239. package/docs/types/openapi.OpenapiSchemaCommonFields.html +1 -1
  240. package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +2 -2
  241. package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +2 -2
  242. package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +2 -2
  243. package/docs/types/openapi.OpenapiSchemaExpressionRef.html +2 -2
  244. package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +2 -2
  245. package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
  246. package/docs/types/openapi.OpenapiSchemaNull.html +2 -2
  247. package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
  248. package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
  249. package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +1 -1
  250. package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +1 -1
  251. package/docs/types/openapi.OpenapiSchemaObjectAnyOf.html +1 -1
  252. package/docs/types/openapi.OpenapiSchemaObjectAnyOfShorthand.html +1 -1
  253. package/docs/types/openapi.OpenapiSchemaObjectBase.html +1 -1
  254. package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.html +1 -1
  255. package/docs/types/openapi.OpenapiSchemaObjectOneOf.html +1 -1
  256. package/docs/types/openapi.OpenapiSchemaObjectOneOfShorthand.html +1 -1
  257. package/docs/types/openapi.OpenapiSchemaObjectShorthand.html +1 -1
  258. package/docs/types/openapi.OpenapiSchemaPrimitiveGeneric.html +1 -1
  259. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAllOf.html +2 -2
  260. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +2 -2
  261. package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +2 -2
  262. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +2 -2
  263. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +2 -2
  264. package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
  265. package/docs/types/openapi.OpenapiSchemaString.html +1 -1
  266. package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
  267. package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
  268. package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
  269. package/docs/types/openapi.OpenapiTypeField.html +1 -1
  270. package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
  271. package/docs/types/types.CalendarDateDurationUnit.html +1 -1
  272. package/docs/types/types.CalendarDateObject.html +1 -1
  273. package/docs/types/types.Camelized.html +1 -1
  274. package/docs/types/types.ClockTimeObject.html +1 -1
  275. package/docs/types/types.DbConnectionType.html +1 -1
  276. package/docs/types/types.DbTypes.html +1 -1
  277. package/docs/types/types.DreamAssociationMetadata.html +1 -1
  278. package/docs/types/types.DreamAttributes.html +1 -1
  279. package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
  280. package/docs/types/types.DreamClassColumn.html +1 -1
  281. package/docs/types/types.DreamColumn.html +1 -1
  282. package/docs/types/types.DreamColumnNames.html +1 -1
  283. package/docs/types/types.DreamLogLevel.html +1 -1
  284. package/docs/types/types.DreamLogger.html +2 -2
  285. package/docs/types/types.DreamModelSerializerType.html +1 -1
  286. package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
  287. package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
  288. package/docs/types/types.DreamParamSafeAttributes.html +1 -1
  289. package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
  290. package/docs/types/types.DreamSerializable.html +1 -1
  291. package/docs/types/types.DreamSerializableArray.html +1 -1
  292. package/docs/types/types.DreamSerializerKey.html +1 -1
  293. package/docs/types/types.DreamSerializers.html +1 -1
  294. package/docs/types/types.DreamVirtualColumns.html +1 -1
  295. package/docs/types/types.DurationUnit.html +1 -1
  296. package/docs/types/types.EncryptAlgorithm.html +1 -1
  297. package/docs/types/types.HasManyStatement.html +1 -1
  298. package/docs/types/types.HasOneStatement.html +1 -1
  299. package/docs/types/types.Hyphenized.html +1 -1
  300. package/docs/types/types.Pascalized.html +1 -1
  301. package/docs/types/types.PrimaryKeyType.html +1 -1
  302. package/docs/types/types.RoundingPrecision.html +1 -1
  303. package/docs/types/types.SerializerCasing.html +1 -1
  304. package/docs/types/types.SimpleObjectSerializerType.html +1 -1
  305. package/docs/types/types.Snakeified.html +1 -1
  306. package/docs/types/types.StrictInterface.html +1 -1
  307. package/docs/types/types.UpdateableAssociationProperties.html +1 -1
  308. package/docs/types/types.UpdateableProperties.html +1 -1
  309. package/docs/types/types.ValidationType.html +1 -1
  310. package/docs/types/types.ViewModel.html +2 -2
  311. package/docs/types/types.ViewModelClass.html +1 -1
  312. package/docs/types/types.WeekdayName.html +1 -1
  313. package/docs/types/types.WhereStatementForDream.html +1 -1
  314. package/docs/types/types.WhereStatementForDreamClass.html +1 -1
  315. package/docs/variables/index.DreamConst.html +1 -1
  316. package/docs/variables/index.ops.html +1 -1
  317. package/docs/variables/openapi.openapiPrimitiveTypes.html +1 -1
  318. package/docs/variables/openapi.openapiShorthandPrimitiveTypes.html +1 -1
  319. package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
  320. package/docs/variables/system.primaryKeyTypes.html +1 -1
  321. package/package.json +1 -1
  322. package/dist/cjs/src/decorators/field/sortable/helpers/applySortableScopeToQuery.js +0 -13
  323. package/dist/cjs/src/decorators/field/sortable/helpers/sortableQueryExcludingDream.js +0 -7
  324. package/dist/cjs/src/decorators/field/sortable/hooks/beforeSortableSave.js +0 -41
  325. package/dist/esm/src/decorators/field/sortable/helpers/applySortableScopeToQuery.js +0 -13
  326. package/dist/esm/src/decorators/field/sortable/helpers/sortableQueryExcludingDream.js +0 -7
  327. package/dist/esm/src/decorators/field/sortable/hooks/beforeSortableSave.js +0 -41
  328. package/dist/types/src/decorators/field/sortable/helpers/applySortableScopeToQuery.d.ts +0 -12
  329. package/dist/types/src/decorators/field/sortable/helpers/sortableQueryExcludingDream.d.ts +0 -12
  330. package/dist/types/src/decorators/field/sortable/hooks/beforeSortableSave.d.ts +0 -12
@@ -0,0 +1,194 @@
1
+ import cachedTypeForAttribute from '../../../../helpers/db/cachedTypeForAttribute.js';
2
+ const UTF8 = new TextEncoder();
3
+ /**
4
+ * @internal
5
+ *
6
+ * The canonical text form of one sort-scope value — the thing an advisory lock
7
+ * key and a resort group identity are actually built from.
8
+ *
9
+ * Two writers of the same physical sort scope have to derive the same key, and
10
+ * they do not always hold the value in the same JavaScript shape. A save that
11
+ * has not been written yet holds the *pending* value the caller supplied
12
+ * (`'1.00'`, an uppercase UUID, an object whose keys are in source order),
13
+ * while a snapshot read, a create's post-INSERT read and `resort`'s table read
14
+ * hold the value Postgres *hydrated* (`1`, a lowercase UUID, an object whose
15
+ * keys are in jsonb's own order). Postgres considers those pairs one value;
16
+ * `typeof`-only normalization considers them two, and two keys do not exclude
17
+ * each other.
18
+ *
19
+ * So canonicalization is keyed on the column's runtime `dbType`, and the
20
+ * canonical form of each type is **the form the hydrated value already takes**.
21
+ * That anchoring is what makes this safe to ship without a migration or a
22
+ * coordinated deploy: no key derived from a database-hydrated value moves, and
23
+ * the pending-side keys that do move are exactly the ones that disagree with
24
+ * every hydrated-path writer of the same scope today.
25
+ *
26
+ * The classes handled, and what the hydrated value looks like:
27
+ *
28
+ * - `uuid` — Postgres renders UUIDs lowercase, so the canonical form is
29
+ * lowercase.
30
+ * - `numeric` / `real` / `double precision` — hydrated through
31
+ * `parsePostgresDecimal`, i.e. `parseFloat`, so `'1.00'` and `1` both
32
+ * canonicalize to `'1'`.
33
+ * - `jsonb` — hydrated by `JSON.parse` of jsonb's own output, whose object keys
34
+ * are ordered by key length and then bytewise. Objects are re-emitted in that
35
+ * order, recursively, so property insertion order stops mattering.
36
+ * - `boolean` — hydrated as a JavaScript boolean; Postgres's own input literals
37
+ * (`'t'`, `'yes'`, `'0'`, …) canonicalize to `'true'`/`'false'`.
38
+ * - `date` and the four timestamp/time types — hydrated as `CalendarDate`,
39
+ * `DateTime`, `ClockTime` or `ClockTimeTz`, whose JSON form is the quoted ISO
40
+ * string. Strings are quoted the same way so a pending `'2026-02-07'` and a
41
+ * hydrated `CalendarDate` agree.
42
+ *
43
+ * Everything else — `bigint` and `character varying`, which is to say the
44
+ * foreign keys and text columns nearly every sort scope is made of — is left on
45
+ * the untyped normalization below, unchanged.
46
+ *
47
+ * Two residuals are known and accepted rather than fixed:
48
+ *
49
+ * - **`citext`**: Postgres stores the case a row was written with while
50
+ * comparing case-insensitively, so two rows of one physical scope hydrate to
51
+ * values that differ in case and split the key on their own. Lowercasing
52
+ * would fix the split but would *move* hydrated keys, which is the one thing
53
+ * this design refuses to do.
54
+ * - **Temporal values that agree on the instant but not on the text**: a zone
55
+ * offset or a fractional-second precision that differs from the hydrated
56
+ * rendering still yields a different key. Only the string-vs-object quoting
57
+ * mismatch is canonicalized.
58
+ *
59
+ * Both are over-serialization or a missed exclusion between two *pending*-side
60
+ * writers, never a corrupt position under a key two writers do share.
61
+ *
62
+ * @param dream - the record whose scope column is being canonicalized
63
+ * @param column - the physical database column
64
+ * @param value - the pending or hydrated value held for that column
65
+ * @returns the canonical text form, or `null` for an absent value
66
+ */
67
+ export default function canonicalScopeValue(dream, column, value) {
68
+ if (value === null || value === undefined)
69
+ return null;
70
+ switch (cachedTypeForAttribute(dream.constructor, column)) {
71
+ case 'uuid':
72
+ return canonicalUuid(value);
73
+ case 'numeric':
74
+ case 'real':
75
+ case 'double precision':
76
+ return canonicalNumeric(value);
77
+ case 'jsonb':
78
+ return canonicalJsonb(value);
79
+ case 'boolean':
80
+ return canonicalBoolean(value);
81
+ case 'date':
82
+ case 'timestamp without time zone':
83
+ case 'timestamp with time zone':
84
+ case 'time without time zone':
85
+ case 'time with time zone':
86
+ return canonicalTemporal(value);
87
+ default:
88
+ return untypedScopeValue(value);
89
+ }
90
+ }
91
+ /**
92
+ * @internal
93
+ *
94
+ * The type-blind fallback, and the shape every unrecognized `dbType` keeps:
95
+ * the same foreign key can arrive as a string or a number depending on the path
96
+ * that loaded it, so values are rendered as text and null is kept distinct from
97
+ * every string, including the empty one.
98
+ */
99
+ export function untypedScopeValue(value) {
100
+ if (value === null || value === undefined)
101
+ return null;
102
+ switch (typeof value) {
103
+ case 'string':
104
+ return value;
105
+ case 'number':
106
+ case 'bigint':
107
+ case 'boolean':
108
+ return value.toString();
109
+ default:
110
+ return JSON.stringify(value) ?? null;
111
+ }
112
+ }
113
+ function canonicalUuid(value) {
114
+ return typeof value === 'string' ? value.toLowerCase() : untypedScopeValue(value);
115
+ }
116
+ function canonicalNumeric(value) {
117
+ const parsed = typeof value === 'number' ? value : parseFloat(String(value));
118
+ return Number.isFinite(parsed) ? String(parsed) : untypedScopeValue(value);
119
+ }
120
+ /**
121
+ * @internal
122
+ *
123
+ * Postgres's own boolean input literals, lower-cased and trimmed, which a
124
+ * pending value may hold while the hydrated value is a JavaScript boolean.
125
+ * Exported so the spec drives its cases from the sets themselves rather than
126
+ * from a hand-copied list that can silently drift out of step with them.
127
+ */
128
+ export const BOOLEAN_TRUE_LITERALS = new Set(['t', 'true', 'y', 'yes', 'on', '1']);
129
+ export const BOOLEAN_FALSE_LITERALS = new Set(['f', 'false', 'n', 'no', 'off', '0']);
130
+ function canonicalBoolean(value) {
131
+ if (typeof value === 'boolean')
132
+ return value ? 'true' : 'false';
133
+ if (typeof value === 'string') {
134
+ const literal = value.trim().toLowerCase();
135
+ if (BOOLEAN_TRUE_LITERALS.has(literal))
136
+ return 'true';
137
+ if (BOOLEAN_FALSE_LITERALS.has(literal))
138
+ return 'false';
139
+ }
140
+ return untypedScopeValue(value);
141
+ }
142
+ /**
143
+ * Temporal columns hydrate as `CalendarDate`/`DateTime`/`ClockTime` instances
144
+ * whose `toJSON` is the ISO string, so JSON-encoding both shapes puts a pending
145
+ * ISO string and its hydrated counterpart on the same quoted text.
146
+ */
147
+ function canonicalTemporal(value) {
148
+ return JSON.stringify(value) ?? null;
149
+ }
150
+ /**
151
+ * jsonb neither stores nor renders property insertion order: it orders object
152
+ * keys by length and then bytewise, and a hydrated value therefore always
153
+ * arrives in that order. Re-emitting in the same order makes a pending object
154
+ * literal agree with it, and leaves every hydrated value byte-identical.
155
+ */
156
+ function canonicalJsonb(value) {
157
+ const parsed = typeof value === 'string' ? parseJson(value) : value;
158
+ return JSON.stringify(inJsonbKeyOrder(parsed)) ?? null;
159
+ }
160
+ function parseJson(value) {
161
+ try {
162
+ return JSON.parse(value);
163
+ }
164
+ catch {
165
+ return value;
166
+ }
167
+ }
168
+ function inJsonbKeyOrder(value) {
169
+ if (Array.isArray(value))
170
+ return value.map(inJsonbKeyOrder);
171
+ if (value === null || typeof value !== 'object')
172
+ return value;
173
+ return Object.keys(value)
174
+ .sort(compareJsonbKeys)
175
+ .reduce((acc, key) => {
176
+ acc[key] = inJsonbKeyOrder(value[key]);
177
+ return acc;
178
+ }, {});
179
+ }
180
+ /**
181
+ * jsonb's own key collation: shorter keys first, ties broken by comparing the
182
+ * UTF-8 bytes.
183
+ */
184
+ function compareJsonbKeys(a, b) {
185
+ const bytesA = UTF8.encode(a);
186
+ const bytesB = UTF8.encode(b);
187
+ if (bytesA.length !== bytesB.length)
188
+ return bytesA.length - bytesB.length;
189
+ for (let i = 0; i < bytesA.length; i++) {
190
+ if (bytesA[i] !== bytesB[i])
191
+ return bytesA[i] - bytesB[i];
192
+ }
193
+ return 0;
194
+ }
@@ -1,5 +1,23 @@
1
1
  import sortableCacheValuesName from './sortableCacheValuesName.js';
2
+ import { clearSortableSnapshot } from './sortableSnapshot.js';
2
3
  export default function clearCachedSortableValues(dream, positionField) {
3
4
  const cachedValuesName = sortableCacheValuesName(positionField);
4
5
  dream[cachedValuesName] = undefined;
5
6
  }
7
+ /**
8
+ * @internal
9
+ *
10
+ * Discards both halves of what a save's sortable phase stashes on the
11
+ * instance: the cached values the position work reads, and the snapshot of the
12
+ * row it would have computed its shift against. The position work clears them
13
+ * itself as it consumes them, so this is for the paths where it does not run —
14
+ * the row was already gone when the snapshot read found it, or the save's
15
+ * transaction rolled back — leaving a later save of the same instance to read
16
+ * the row for itself rather than inherit this save's reading of it.
17
+ */
18
+ export function clearSortableSaveState(dream, configs) {
19
+ for (const { positionField } of configs) {
20
+ clearCachedSortableValues(dream, positionField);
21
+ clearSortableSnapshot(dream, positionField);
22
+ }
23
+ }
@@ -1,7 +1,7 @@
1
1
  import ops from '../../../../ops/index.js';
2
- import getColumnForSortableScope from './getColumnForSortableScope.js';
3
- import scopeArray from './scopeArray.js';
4
- export default async function decrementPositionForScopedRecordsGreaterThanPosition(position, { dream, positionField, query, scope, }) {
2
+ import filterQueryToScopeValues from './filterQueryToScopeValues.js';
3
+ import { snapshotScopeValue } from './sortableSnapshot.js';
4
+ export default async function decrementPositionForScopedRecordsGreaterThanPosition(position, { dream, positionField, query, scope, snapshot, }) {
5
5
  let kyselyQuery = query
6
6
  .whereNot({ [dream['_primaryKey']]: dream.primaryKeyValue() })
7
7
  .where({
@@ -13,11 +13,11 @@ export default async function decrementPositionForScopedRecordsGreaterThanPositi
13
13
  [positionField]: eb(positionField, '-', 1),
14
14
  };
15
15
  });
16
- for (const singleScope of scopeArray(scope)) {
17
- const column = getColumnForSortableScope(dream, singleScope);
18
- if (column) {
19
- kyselyQuery = kyselyQuery.where(column, '=', dream[column]);
20
- }
21
- }
16
+ // A null scope value has to be matched with `is null`: `column = NULL` is
17
+ // never true, so the compaction would silently shift nothing at all in a
18
+ // null-valued sort scope. `filterQueryToScopeValues` is the one place that
19
+ // branch lives, and it is the same filter every other position-mutating path
20
+ // builds.
21
+ kyselyQuery = filterQueryToScopeValues(dream, kyselyQuery, column => snapshotScopeValue(snapshot, column, () => dream[column]), scope);
22
22
  await kyselyQuery.execute();
23
23
  }
@@ -0,0 +1,20 @@
1
+ import sortableScopeColumns from './sortableScopeColumns.js';
2
+ /**
3
+ * @internal
4
+ *
5
+ * Narrows a Dream `Query` to one sort scope, **reading the scope values off the
6
+ * in-memory instance**. That is only the scope the row physically occupies when
7
+ * the instance is known to agree with the row — inside a transaction that has
8
+ * just written those values, or on a record read under the scope lock.
9
+ *
10
+ * Where the row may have been moved by another writer, the caller wants
11
+ * `filterQueryToScopeValues` instead, which takes each column's value from the
12
+ * caller (the snapshot read under the lock) rather than from the instance.
13
+ */
14
+ export default function filterQueryToInstanceScope(query, dream, scope) {
15
+ for (const column of sortableScopeColumns(dream, scope)) {
16
+ const value = dream[column];
17
+ query = query.where({ [column]: value ?? null });
18
+ }
19
+ return query;
20
+ }
@@ -0,0 +1,32 @@
1
+ import sortableScopeColumns from './sortableScopeColumns.js';
2
+ /**
3
+ * @internal
4
+ *
5
+ * Narrows a kysely query to one sort scope, taking each scope column's value
6
+ * **from the caller** rather than from the in-memory instance — the snapshot
7
+ * read under the scope lock on the paths that compact a scope a record is
8
+ * leaving, which is not necessarily the scope the instance was loaded from.
9
+ *
10
+ * This is also the one place the null branch lives: a null scope value has to
11
+ * be matched with `is null`, since `column = NULL` is never true and the filter
12
+ * would silently match nothing at all in a null-valued sort scope.
13
+ *
14
+ * The instance-reading sibling is `filterQueryToInstanceScope`.
15
+ *
16
+ * @param dream - the record whose class resolves the scope members to columns
17
+ * @param kyselyQuery - the query to narrow
18
+ * @param whereValueCB - the value to match each scope column against
19
+ * @param scope - the sortable field's scope
20
+ */
21
+ export default function filterQueryToScopeValues(dream, kyselyQuery, whereValueCB, scope) {
22
+ for (const column of sortableScopeColumns(dream, scope)) {
23
+ const columnValue = whereValueCB(column);
24
+ if (columnValue === null) {
25
+ kyselyQuery = kyselyQuery.where(column, 'is', null);
26
+ }
27
+ else {
28
+ kyselyQuery = kyselyQuery.where(column, '=', columnValue);
29
+ }
30
+ }
31
+ return kyselyQuery;
32
+ }
@@ -0,0 +1,34 @@
1
+ const heldKeysByTransaction = new WeakMap();
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) {
28
+ const held = heldKeysByTransaction.get(txn);
29
+ if (held)
30
+ return held;
31
+ const newHeld = new Set();
32
+ heldKeysByTransaction.set(txn, newHeld);
33
+ return newHeld;
34
+ }
@@ -0,0 +1,21 @@
1
+ import { STI_SCOPE_NAME } from '../../../class/STI.js';
2
+ import afterSortableDestroy from '../hooks/afterSortableDestroy.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 async function performSortableDestroyWork(dream, txn) {
15
+ const dreamClass = dream.constructor;
16
+ const sortableFields = dreamClass['sortableFields'];
17
+ for (const { positionField, scope } of sortableFields) {
18
+ const query = dreamClass.query().removeDefaultScope(STI_SCOPE_NAME).txn(txn);
19
+ await afterSortableDestroy({ dream, positionField, query, scope, txn });
20
+ }
21
+ }
@@ -0,0 +1,36 @@
1
+ import { STI_SCOPE_NAME } from '../../../class/STI.js';
2
+ import afterSortableCreate from '../hooks/afterSortableCreate.js';
3
+ import afterUpdateSortable from '../hooks/afterSortableUpdate.js';
4
+ import acquireSortableScopeLocks from './acquireSortableScopeLocks.js';
5
+ import { sortableCurrentScopeLockKeys } from './sortableScopeLockKeys.js';
6
+ /**
7
+ * @internal
8
+ *
9
+ * The position work of a sortable save: computes each sortable field's real
10
+ * position under the scope lock and writes it. Called directly from
11
+ * `saveDream` after the driver write — never registered as an after-hook — so
12
+ * no user hook code can interleave with it while the scope lock is held, and
13
+ * on a self-opened save it is the last work before COMMIT releases the lock.
14
+ */
15
+ export default async function performSortablePositionWork(dream, txn, sortableFieldConfigs, { alreadyPersisted }) {
16
+ const dreamClass = dream.constructor;
17
+ // A create acquires its whole advisory key set here — every sortable field,
18
+ // one sorted pass — before any field's position is computed, so a
19
+ // multi-field create takes its keys in the same global order as an update or
20
+ // a destroy and cannot deadlock against them. The per-field acquisition
21
+ // inside `setPosition` then re-acquires keys this transaction already holds,
22
+ // which is a no-op. (An update acquired its keys before the driver write, in
23
+ // `saveDream`.)
24
+ if (!alreadyPersisted) {
25
+ await acquireSortableScopeLocks(dream, txn, sortableCurrentScopeLockKeys(dream, sortableFieldConfigs));
26
+ }
27
+ for (const { positionField, scope } of sortableFieldConfigs) {
28
+ const query = dreamClass.query().removeDefaultScope(STI_SCOPE_NAME).txn(txn);
29
+ if (alreadyPersisted) {
30
+ await afterUpdateSortable({ dream, positionField, query, txn, scope });
31
+ }
32
+ else {
33
+ await afterSortableCreate({ dream, positionField, query, txn, scope });
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,56 @@
1
+ import acquireStabilizedSortableScopeLocks from './acquireStabilizedSortableScopeLocks.js';
2
+ import clearCachedSortableValues from './clearCachedSortableValues.js';
3
+ import { cacheSortableSnapshots, clearSortableSnapshot } from './sortableSnapshot.js';
4
+ /**
5
+ * @internal
6
+ *
7
+ * The sortable preparation phase of a destroy. Called from `destroyDream` after
8
+ * every user `beforeDestroy` hook has run and before the delete — a phase, not
9
+ * a hook among them — so the operation's whole advisory key set, across every
10
+ * sortable field, is taken in one sorted stabilized acquisition, with one
11
+ * snapshot SELECT covering all fields. Per-field acquisition would take
12
+ * multi-field keys in declaration order, which deadlocks against the paths
13
+ * that acquire sorted.
14
+ *
15
+ * Pre-delete is the only seat available for the snapshot read: on a hard
16
+ * destroy the row is gone afterwards, and a soft destroy nulls the position
17
+ * columns in the same UPDATE as `deletedAt`. The lock is taken around the read
18
+ * so the snapshot cannot be invalidated between the read and the compaction;
19
+ * it is transaction-scoped, and `destroyDream` opens a transaction that spans
20
+ * the delete and the after-destroy hooks, so one acquisition covers both.
21
+ *
22
+ * The per-field compactions (`performSortableDestroyWork`) consume the cached
23
+ * snapshots.
24
+ *
25
+ * @returns false when the snapshot read found no row — this destroy has nothing
26
+ * to vacate, and compacting from the instance's remembered position would
27
+ * shift a scope some other writer has already closed. The delete's own
28
+ * affected-row count is the stronger signal (a writer outside the sortable
29
+ * path can remove the row after this read), so the caller checks both.
30
+ */
31
+ export default async function prepareSortableFieldsForDestroy(dream, txn) {
32
+ const sortableFields = dream.constructor['sortableFields'];
33
+ if (!sortableFields.length)
34
+ return true;
35
+ const { rowExists, snapshots } = await acquireStabilizedSortableScopeLocks(dream, txn, sortableFields);
36
+ cacheSortableSnapshots(dream, snapshots);
37
+ return rowExists;
38
+ }
39
+ /**
40
+ * @internal
41
+ *
42
+ * Discards what the preparation phase stashed on the instance. The per-field
43
+ * compaction clears its own snapshot as it consumes it, so this
44
+ * exists for the path where that compaction never runs: a destroy that removed
45
+ * nothing leaves the snapshot behind, and a later save or destroy of the same
46
+ * instance would compute its shift from a reading of a row this operation
47
+ * already found gone.
48
+ */
49
+ export function clearSortableFieldsForDestroy(dream) {
50
+ const sortableFields = (dream.constructor['sortableFields'] ??
51
+ []);
52
+ for (const { positionField } of sortableFields) {
53
+ clearCachedSortableValues(dream, positionField);
54
+ clearSortableSnapshot(dream, positionField);
55
+ }
56
+ }
@@ -0,0 +1,84 @@
1
+ import positionIsInvalid from './positionIsInvalid.js';
2
+ import sortableCacheValuesName from './sortableCacheValuesName.js';
3
+ /**
4
+ * @internal
5
+ *
6
+ * The sortable preparation phase of a save. Called from `saveDream` after every
7
+ * consumer before-hook has run — a phase, not a hook among them — so the
8
+ * decision is made from the final state of the instance: a position or scope
9
+ * change introduced by a `beforeSave` hook registered after Sortable's gets the
10
+ * same handling as one made before `save` was called, and a cache is never
11
+ * carried over from a state an intervening hook has made stale.
12
+ *
13
+ * For each sortable field this caches the values the position work needs and
14
+ * applies the position sentinel, then returns the fields whose position work
15
+ * this save will perform — the fields that make the save need a transaction of
16
+ * its own, so that the write and the position write that follows it commit
17
+ * together under one scope lock.
18
+ *
19
+ * - every create on a model with sortable fields performs position work;
20
+ * - an update performs it only when the save changes the position or one of the
21
+ * sortable scope columns.
22
+ *
23
+ * A save that skips hooks performs no position work at all, so it needs no
24
+ * transaction.
25
+ */
26
+ export default function prepareSortableFieldsForSave(dream, { alreadyPersisted, skipHooks }) {
27
+ if (skipHooks)
28
+ return [];
29
+ const sortableFields = dream.constructor['sortableFields'];
30
+ if (!sortableFields.length)
31
+ return [];
32
+ sortableFields.forEach(config => prepareSortableFieldForSave(dream, config));
33
+ if (!alreadyPersisted)
34
+ return [...sortableFields];
35
+ return sortableFields.filter(config => dream[sortableCacheValuesName(config.positionField)] !== undefined);
36
+ }
37
+ /**
38
+ * Caches the values one sortable field's position work needs and applies the
39
+ * position sentinel to the instance, so that the driver write never commits a
40
+ * caller-supplied position the scope has not made room for.
41
+ */
42
+ function prepareSortableFieldForSave(dream, { positionField, scope }) {
43
+ const cachedValuesName = sortableCacheValuesName(positionField);
44
+ const dreamAsAny = dream;
45
+ const changingScope = dream.isPersisted &&
46
+ scope.filter(scopeField => {
47
+ const association = dream['getAssociationMetadata'](scopeField);
48
+ return association
49
+ ? dream.willSaveChangeToAttribute(association.foreignKey())
50
+ : dream.willSaveChangeToAttribute(scopeField);
51
+ }).length > 0;
52
+ if (dream.willSaveChangeToAttribute(positionField) || changingScope) {
53
+ const position = dreamAsAny[positionField];
54
+ // store values to be used by the position work later in the save
55
+ const values = {
56
+ wasNewRecord: dream.isNewRecord,
57
+ changingScope,
58
+ position: positionIsInvalid(position) ? undefined : position,
59
+ previousPosition: dream.isPersisted && dream.willSaveChangeToAttribute(positionField)
60
+ ? dream.changes()[positionField]?.was
61
+ : changingScope
62
+ ? position
63
+ : undefined,
64
+ };
65
+ dreamAsAny[cachedValuesName] = values;
66
+ }
67
+ else {
68
+ // computed from the final state of the instance, so a cache from an earlier,
69
+ // now-stale state must not survive this decision
70
+ dreamAsAny[cachedValuesName] = undefined;
71
+ }
72
+ if (dream.isNewRecord || changingScope) {
73
+ // if the dream is not saved, or is being moved between scopes, set position to 0
74
+ // to prevent collisions with existing position values.
75
+ // the real position is computed and written under the scope lock later in the save.
76
+ dreamAsAny[positionField] = 0;
77
+ }
78
+ else if (dream.willSaveChangeToAttribute(positionField)) {
79
+ // if the dream is saved and the position is being changed, set the position field to
80
+ // undefined, which will cause the update cycle to ignore the position field. The
81
+ // position work later in the save writes the real value.
82
+ dreamAsAny[positionField] = undefined;
83
+ }
84
+ }