velocious 1.0.643 → 1.0.645

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 (279) hide show
  1. package/README.md +21 -10
  2. package/build/authorization/ability.js +36 -13
  3. package/build/background-jobs/adapter.js +9 -0
  4. package/build/background-jobs/main.js +35 -10
  5. package/build/background-jobs/store.js +217 -38
  6. package/build/background-jobs/types.js +15 -0
  7. package/build/configuration-types.js +1 -1
  8. package/build/configuration.js +45 -9
  9. package/build/database/drivers/base-column.js +8 -0
  10. package/build/database/drivers/mssql/sql/update.js +1 -3
  11. package/build/database/drivers/mysql/sql/update.js +1 -3
  12. package/build/database/drivers/pgsql/column.js +22 -0
  13. package/build/database/drivers/pgsql/sql/update.js +1 -3
  14. package/build/database/drivers/sqlite/sql/alter-table.js +11 -1
  15. package/build/database/drivers/sqlite/sql/update.js +1 -3
  16. package/build/database/query/index.js +2 -2
  17. package/build/database/query/model-class-query.js +26 -10
  18. package/build/database/query/preloader/has-many.js +4 -1
  19. package/build/database/query/query-data.js +2 -1
  20. package/build/database/query/update-base.js +14 -0
  21. package/build/database/query/with-count.js +3 -1
  22. package/build/database/record/acts-as-list.js +37 -13
  23. package/build/database/record/attachments/store.js +336 -26
  24. package/build/database/record/auditing.js +6 -3
  25. package/build/database/record/index.js +197 -42
  26. package/build/database/record/instance-relationships/belongs-to.js +2 -1
  27. package/build/database/record/validators/uniqueness.js +8 -5
  28. package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +24 -8
  29. package/build/frontend-model-controller.js +136 -53
  30. package/build/frontend-model-resource/base-resource.js +87 -37
  31. package/build/frontend-model-resource/velocious-attachment-resource.js +98 -31
  32. package/build/frontend-models/base.js +307 -59
  33. package/build/frontend-models/preloader.js +6 -3
  34. package/build/frontend-models/query.js +15 -6
  35. package/build/frontend-models/resource-definition.js +21 -2
  36. package/build/frontend-models/use-destroyed-event.js +1 -1
  37. package/build/frontend-models/use-model-class-event.js +2 -2
  38. package/build/frontend-models/use-updated-event.js +1 -1
  39. package/build/frontend-models/websocket-channel.js +256 -93
  40. package/build/frontend-models/websocket-publishers.js +218 -18
  41. package/build/http-server/client/websocket-session.js +13 -1
  42. package/build/http-server/websocket-channel.js +15 -2
  43. package/build/src/authorization/ability.d.ts +9 -3
  44. package/build/src/authorization/ability.d.ts.map +1 -1
  45. package/build/src/authorization/ability.js +33 -14
  46. package/build/src/background-jobs/adapter.d.ts +6 -0
  47. package/build/src/background-jobs/adapter.d.ts.map +1 -1
  48. package/build/src/background-jobs/adapter.js +9 -1
  49. package/build/src/background-jobs/main.d.ts +6 -0
  50. package/build/src/background-jobs/main.d.ts.map +1 -1
  51. package/build/src/background-jobs/main.js +34 -11
  52. package/build/src/background-jobs/store.d.ts +66 -12
  53. package/build/src/background-jobs/store.d.ts.map +1 -1
  54. package/build/src/background-jobs/store.js +191 -35
  55. package/build/src/background-jobs/types.d.ts +55 -0
  56. package/build/src/background-jobs/types.d.ts.map +1 -1
  57. package/build/src/background-jobs/types.js +16 -1
  58. package/build/src/configuration-types.d.ts +3 -3
  59. package/build/src/configuration-types.d.ts.map +1 -1
  60. package/build/src/configuration-types.js +2 -2
  61. package/build/src/configuration.d.ts +18 -8
  62. package/build/src/configuration.d.ts.map +1 -1
  63. package/build/src/configuration.js +38 -7
  64. package/build/src/database/drivers/base-column.d.ts +5 -0
  65. package/build/src/database/drivers/base-column.d.ts.map +1 -1
  66. package/build/src/database/drivers/base-column.js +8 -1
  67. package/build/src/database/drivers/mssql/sql/update.d.ts.map +1 -1
  68. package/build/src/database/drivers/mssql/sql/update.js +2 -4
  69. package/build/src/database/drivers/mysql/sql/update.d.ts.map +1 -1
  70. package/build/src/database/drivers/mysql/sql/update.js +2 -4
  71. package/build/src/database/drivers/pgsql/column.d.ts +5 -0
  72. package/build/src/database/drivers/pgsql/column.d.ts.map +1 -1
  73. package/build/src/database/drivers/pgsql/column.js +19 -1
  74. package/build/src/database/drivers/pgsql/sql/update.d.ts.map +1 -1
  75. package/build/src/database/drivers/pgsql/sql/update.js +2 -4
  76. package/build/src/database/drivers/sqlite/sql/alter-table.d.ts.map +1 -1
  77. package/build/src/database/drivers/sqlite/sql/alter-table.js +6 -2
  78. package/build/src/database/drivers/sqlite/sql/update.d.ts.map +1 -1
  79. package/build/src/database/drivers/sqlite/sql/update.js +2 -4
  80. package/build/src/database/query/index.d.ts +4 -4
  81. package/build/src/database/query/index.d.ts.map +1 -1
  82. package/build/src/database/query/index.js +3 -3
  83. package/build/src/database/query/model-class-query.d.ts +8 -2
  84. package/build/src/database/query/model-class-query.d.ts.map +1 -1
  85. package/build/src/database/query/model-class-query.js +25 -11
  86. package/build/src/database/query/preloader/has-many.d.ts.map +1 -1
  87. package/build/src/database/query/preloader/has-many.js +4 -2
  88. package/build/src/database/query/query-data.d.ts.map +1 -1
  89. package/build/src/database/query/query-data.js +3 -2
  90. package/build/src/database/query/update-base.d.ts +7 -0
  91. package/build/src/database/query/update-base.d.ts.map +1 -1
  92. package/build/src/database/query/update-base.js +13 -1
  93. package/build/src/database/query/with-count.d.ts.map +1 -1
  94. package/build/src/database/query/with-count.js +3 -2
  95. package/build/src/database/record/acts-as-list.d.ts.map +1 -1
  96. package/build/src/database/record/acts-as-list.js +35 -14
  97. package/build/src/database/record/attachments/store.d.ts +67 -0
  98. package/build/src/database/record/attachments/store.d.ts.map +1 -1
  99. package/build/src/database/record/attachments/store.js +301 -25
  100. package/build/src/database/record/auditing.d.ts.map +1 -1
  101. package/build/src/database/record/auditing.js +7 -4
  102. package/build/src/database/record/index.d.ts +78 -26
  103. package/build/src/database/record/index.d.ts.map +1 -1
  104. package/build/src/database/record/index.js +171 -42
  105. package/build/src/database/record/instance-relationships/belongs-to.d.ts.map +1 -1
  106. package/build/src/database/record/instance-relationships/belongs-to.js +3 -2
  107. package/build/src/database/record/validators/uniqueness.d.ts.map +1 -1
  108. package/build/src/database/record/validators/uniqueness.js +8 -6
  109. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts +4 -4
  110. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts.map +1 -1
  111. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.js +24 -12
  112. package/build/src/frontend-model-controller.d.ts +29 -8
  113. package/build/src/frontend-model-controller.d.ts.map +1 -1
  114. package/build/src/frontend-model-controller.js +122 -51
  115. package/build/src/frontend-model-resource/base-resource.d.ts +51 -24
  116. package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
  117. package/build/src/frontend-model-resource/base-resource.js +83 -37
  118. package/build/src/frontend-model-resource/velocious-attachment-resource.d.ts +45 -26
  119. package/build/src/frontend-model-resource/velocious-attachment-resource.d.ts.map +1 -1
  120. package/build/src/frontend-model-resource/velocious-attachment-resource.js +89 -31
  121. package/build/src/frontend-models/base.d.ts +53 -20
  122. package/build/src/frontend-models/base.d.ts.map +1 -1
  123. package/build/src/frontend-models/base.js +275 -59
  124. package/build/src/frontend-models/preloader.d.ts.map +1 -1
  125. package/build/src/frontend-models/preloader.js +6 -4
  126. package/build/src/frontend-models/query.d.ts +2 -2
  127. package/build/src/frontend-models/query.d.ts.map +1 -1
  128. package/build/src/frontend-models/query.js +14 -7
  129. package/build/src/frontend-models/resource-definition.d.ts.map +1 -1
  130. package/build/src/frontend-models/resource-definition.js +20 -3
  131. package/build/src/frontend-models/use-destroyed-event.d.ts +1 -1
  132. package/build/src/frontend-models/use-destroyed-event.d.ts.map +1 -1
  133. package/build/src/frontend-models/use-destroyed-event.js +2 -2
  134. package/build/src/frontend-models/use-model-class-event.d.ts +2 -2
  135. package/build/src/frontend-models/use-model-class-event.d.ts.map +1 -1
  136. package/build/src/frontend-models/use-model-class-event.js +3 -3
  137. package/build/src/frontend-models/use-updated-event.d.ts +1 -1
  138. package/build/src/frontend-models/use-updated-event.d.ts.map +1 -1
  139. package/build/src/frontend-models/use-updated-event.js +2 -2
  140. package/build/src/frontend-models/websocket-channel.d.ts +60 -41
  141. package/build/src/frontend-models/websocket-channel.d.ts.map +1 -1
  142. package/build/src/frontend-models/websocket-channel.js +225 -85
  143. package/build/src/frontend-models/websocket-publishers.d.ts +9 -0
  144. package/build/src/frontend-models/websocket-publishers.d.ts.map +1 -1
  145. package/build/src/frontend-models/websocket-publishers.js +190 -19
  146. package/build/src/http-server/client/websocket-session.d.ts.map +1 -1
  147. package/build/src/http-server/client/websocket-session.js +11 -2
  148. package/build/src/http-server/websocket-channel.d.ts +20 -8
  149. package/build/src/http-server/websocket-channel.d.ts.map +1 -1
  150. package/build/src/http-server/websocket-channel.js +15 -3
  151. package/build/src/sync/assets/cache.d.ts +288 -0
  152. package/build/src/sync/assets/cache.d.ts.map +1 -0
  153. package/build/src/sync/assets/cache.js +883 -0
  154. package/build/src/sync/assets/types.d.ts +199 -0
  155. package/build/src/sync/assets/types.d.ts.map +1 -0
  156. package/build/src/sync/assets/types.js +56 -0
  157. package/build/src/sync/sync-api-client.d.ts.map +1 -1
  158. package/build/src/sync/sync-api-client.js +4 -3
  159. package/build/src/sync/sync-client.d.ts.map +1 -1
  160. package/build/src/sync/sync-client.js +3 -2
  161. package/build/src/sync/sync-envelope-replay-service.d.ts.map +1 -1
  162. package/build/src/sync/sync-envelope-replay-service.js +6 -5
  163. package/build/src/sync/sync-publisher.d.ts.map +1 -1
  164. package/build/src/sync/sync-publisher.js +4 -3
  165. package/build/src/testing/browser-frontend-model-event-hook-scenarios.d.ts +2 -2
  166. package/build/src/testing/browser-frontend-model-event-hook-scenarios.d.ts.map +1 -1
  167. package/build/src/testing/browser-frontend-model-event-hook-scenarios.js +3 -3
  168. package/build/src/testing/test-profiler.d.ts +4 -4
  169. package/build/src/testing/test-profiler.d.ts.map +1 -1
  170. package/build/src/testing/test-profiler.js +3 -3
  171. package/build/src/testing/test-runner.d.ts +279 -117
  172. package/build/src/testing/test-runner.d.ts.map +1 -1
  173. package/build/src/testing/test-runner.js +503 -933
  174. package/build/src/testing/test.d.ts +11 -72
  175. package/build/src/testing/test.d.ts.map +1 -1
  176. package/build/src/testing/test.js +131 -284
  177. package/build/src/testing/velocious-attempt-executor.d.ts +54 -0
  178. package/build/src/testing/velocious-attempt-executor.d.ts.map +1 -0
  179. package/build/src/testing/velocious-attempt-executor.js +408 -0
  180. package/build/src/testing/velocious-runner-reporter.d.ts +67 -0
  181. package/build/src/testing/velocious-runner-reporter.d.ts.map +1 -0
  182. package/build/src/testing/velocious-runner-reporter.js +240 -0
  183. package/build/src/testing/velocious-suite-hook-executor.d.ts +19 -0
  184. package/build/src/testing/velocious-suite-hook-executor.d.ts.map +1 -0
  185. package/build/src/testing/velocious-suite-hook-executor.js +41 -0
  186. package/build/src/testing/velocious-test-arguments.d.ts +39 -0
  187. package/build/src/testing/velocious-test-arguments.d.ts.map +1 -0
  188. package/build/src/testing/velocious-test-arguments.js +55 -0
  189. package/build/src/utils/model-primary-key.d.ts +73 -0
  190. package/build/src/utils/model-primary-key.d.ts.map +1 -0
  191. package/build/src/utils/model-primary-key.js +144 -0
  192. package/build/src/utils/sha256-bytes-hex.d.ts +7 -0
  193. package/build/src/utils/sha256-bytes-hex.d.ts.map +1 -0
  194. package/build/src/utils/sha256-bytes-hex.js +117 -0
  195. package/build/src/utils/sha256-hex.d.ts +2 -4
  196. package/build/src/utils/sha256-hex.d.ts.map +1 -1
  197. package/build/src/utils/sha256-hex.js +6 -120
  198. package/build/sync/assets/cache.js +998 -0
  199. package/build/sync/assets/types.js +62 -0
  200. package/build/sync/sync-api-client.js +3 -2
  201. package/build/sync/sync-client.js +2 -1
  202. package/build/sync/sync-envelope-replay-service.js +5 -4
  203. package/build/sync/sync-publisher.js +3 -2
  204. package/build/testing/browser-frontend-model-event-hook-scenarios.js +2 -2
  205. package/build/testing/test-profiler.js +2 -2
  206. package/build/testing/test-runner.js +496 -990
  207. package/build/testing/test.js +164 -306
  208. package/build/testing/velocious-attempt-executor.js +441 -0
  209. package/build/testing/velocious-runner-reporter.js +264 -0
  210. package/build/testing/velocious-suite-hook-executor.js +43 -0
  211. package/build/testing/velocious-test-arguments.js +64 -0
  212. package/build/tsconfig.tsbuildinfo +1 -1
  213. package/build/utils/model-primary-key.js +171 -0
  214. package/build/utils/sha256-bytes-hex.js +132 -0
  215. package/build/utils/sha256-hex.js +7 -135
  216. package/package.json +5 -4
  217. package/src/authorization/ability.js +36 -13
  218. package/src/background-jobs/adapter.js +9 -0
  219. package/src/background-jobs/main.js +35 -10
  220. package/src/background-jobs/store.js +217 -38
  221. package/src/background-jobs/types.js +15 -0
  222. package/src/configuration-types.js +1 -1
  223. package/src/configuration.js +45 -9
  224. package/src/database/drivers/base-column.js +8 -0
  225. package/src/database/drivers/mssql/sql/update.js +1 -3
  226. package/src/database/drivers/mysql/sql/update.js +1 -3
  227. package/src/database/drivers/pgsql/column.js +22 -0
  228. package/src/database/drivers/pgsql/sql/update.js +1 -3
  229. package/src/database/drivers/sqlite/sql/alter-table.js +11 -1
  230. package/src/database/drivers/sqlite/sql/update.js +1 -3
  231. package/src/database/query/index.js +2 -2
  232. package/src/database/query/model-class-query.js +26 -10
  233. package/src/database/query/preloader/has-many.js +4 -1
  234. package/src/database/query/query-data.js +2 -1
  235. package/src/database/query/update-base.js +14 -0
  236. package/src/database/query/with-count.js +3 -1
  237. package/src/database/record/acts-as-list.js +37 -13
  238. package/src/database/record/attachments/store.js +336 -26
  239. package/src/database/record/auditing.js +6 -3
  240. package/src/database/record/index.js +197 -42
  241. package/src/database/record/instance-relationships/belongs-to.js +2 -1
  242. package/src/database/record/validators/uniqueness.js +8 -5
  243. package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +24 -8
  244. package/src/frontend-model-controller.js +136 -53
  245. package/src/frontend-model-resource/base-resource.js +87 -37
  246. package/src/frontend-model-resource/velocious-attachment-resource.js +98 -31
  247. package/src/frontend-models/base.js +307 -59
  248. package/src/frontend-models/preloader.js +6 -3
  249. package/src/frontend-models/query.js +15 -6
  250. package/src/frontend-models/resource-definition.js +21 -2
  251. package/src/frontend-models/use-destroyed-event.js +1 -1
  252. package/src/frontend-models/use-model-class-event.js +2 -2
  253. package/src/frontend-models/use-updated-event.js +1 -1
  254. package/src/frontend-models/websocket-channel.js +256 -93
  255. package/src/frontend-models/websocket-publishers.js +218 -18
  256. package/src/http-server/client/websocket-session.js +13 -1
  257. package/src/http-server/websocket-channel.js +15 -2
  258. package/src/sync/assets/cache.js +998 -0
  259. package/src/sync/assets/types.js +62 -0
  260. package/src/sync/sync-api-client.js +3 -2
  261. package/src/sync/sync-client.js +2 -1
  262. package/src/sync/sync-envelope-replay-service.js +5 -4
  263. package/src/sync/sync-publisher.js +3 -2
  264. package/src/testing/browser-frontend-model-event-hook-scenarios.js +2 -2
  265. package/src/testing/test-profiler.js +2 -2
  266. package/src/testing/test-runner.js +496 -990
  267. package/src/testing/test.js +164 -306
  268. package/src/testing/velocious-attempt-executor.js +441 -0
  269. package/src/testing/velocious-runner-reporter.js +264 -0
  270. package/src/testing/velocious-suite-hook-executor.js +43 -0
  271. package/src/testing/velocious-test-arguments.js +64 -0
  272. package/src/utils/model-primary-key.js +171 -0
  273. package/src/utils/sha256-bytes-hex.js +132 -0
  274. package/src/utils/sha256-hex.js +7 -135
  275. package/build/src/testing/testing-package-adapter.d.ts +0 -9
  276. package/build/src/testing/testing-package-adapter.d.ts.map +0 -1
  277. package/build/src/testing/testing-package-adapter.js +0 -69
  278. package/build/testing/testing-package-adapter.js +0 -76
  279. package/src/testing/testing-package-adapter.js +0 -76
@@ -0,0 +1,171 @@
1
+ // @ts-check
2
+
3
+ import isPlainObject from "./plain-object.js"
4
+
5
+ /** @typedef {string | number} ModelPrimaryKeyScalar */
6
+ /** @typedef {string | string[]} ModelPrimaryKeyDefinition */
7
+ /** @typedef {Record<string, ModelPrimaryKeyScalar>} CompositeModelPrimaryKeyValue */
8
+ /** @typedef {ModelPrimaryKeyScalar | CompositeModelPrimaryKeyValue} ModelPrimaryKeyValue */
9
+
10
+ /**
11
+ * Validates an untrusted composite identity and returns its definition-ordered values.
12
+ * @param {string[]} primaryKey - Composite primary-key definition.
13
+ * @param {ReturnType<typeof JSON.parse>} value - Candidate identity.
14
+ * @returns {CompositeModelPrimaryKeyValue} - Validated identity.
15
+ */
16
+ export function validatedCompositePrimaryKeyValue(primaryKey, value) {
17
+ if (!isPlainObject(value)) {
18
+ throw new TypeError("Expected composite primary key identity to be a plain object.")
19
+ }
20
+
21
+ const valueKeys = Object.keys(value)
22
+
23
+ if (valueKeys.length !== primaryKey.length || primaryKey.some((key) => !Object.prototype.hasOwnProperty.call(value, key))) {
24
+ throw new TypeError(`Expected composite primary key identity fields: ${primaryKey.join(", ")}.`)
25
+ }
26
+
27
+ /** @type {CompositeModelPrimaryKeyValue} */
28
+ const validatedValue = {}
29
+
30
+ for (const key of primaryKey) {
31
+ const keyValue = value[key]
32
+
33
+ if (typeof keyValue !== "string" && typeof keyValue !== "number") {
34
+ throw new TypeError(`Expected composite primary key identity field '${key}' to be a string or number.`)
35
+ }
36
+
37
+ validatedValue[key] = keyValue
38
+ }
39
+
40
+ return validatedValue
41
+ }
42
+
43
+ /**
44
+ * Returns a scalar primary-key definition or fails at a feature boundary that cannot represent composite identities.
45
+ * @param {ModelPrimaryKeyDefinition} primaryKey - Primary-key definition.
46
+ * @param {string} operation - Operation requiring a scalar identity.
47
+ * @returns {string} - Scalar primary-key column or attribute name.
48
+ */
49
+ export function scalarModelPrimaryKey(primaryKey, operation) {
50
+ if (Array.isArray(primaryKey)) {
51
+ throw new Error(`${operation} does not support composite primary keys.`)
52
+ }
53
+
54
+ return primaryKey
55
+ }
56
+
57
+ /**
58
+ * Returns a scalar identity or fails at a feature boundary that cannot represent composite identities.
59
+ * @param {ModelPrimaryKeyValue} value - Model identity.
60
+ * @param {string} operation - Operation requiring a scalar identity.
61
+ * @returns {ModelPrimaryKeyScalar} - Scalar identity.
62
+ */
63
+ export function scalarModelPrimaryKeyValue(value, operation) {
64
+ if (typeof value !== "string" && typeof value !== "number") {
65
+ throw new Error(`${operation} does not support composite primary keys.`)
66
+ }
67
+
68
+ return value
69
+ }
70
+
71
+ /**
72
+ * Builds query conditions from a scalar or composite model identity.
73
+ * @param {ModelPrimaryKeyDefinition} primaryKey - Primary-key definition.
74
+ * @param {ReturnType<typeof JSON.parse>} value - Candidate identity.
75
+ * @returns {Record<string, ModelPrimaryKeyScalar>} - Primary-key query conditions.
76
+ */
77
+ export function modelPrimaryKeyConditions(primaryKey, value) {
78
+ if (Array.isArray(primaryKey)) return validatedCompositePrimaryKeyValue(primaryKey, value)
79
+
80
+ return {[primaryKey]: /** @type {ModelPrimaryKeyScalar} */ (value)}
81
+ }
82
+
83
+ /**
84
+ * Builds a qualified SQL predicate matching any supplied composite identity.
85
+ * @param {object} args - Arguments.
86
+ * @param {typeof import("../database/record/index.js").default} args.modelClass - Model class owning the identity attributes.
87
+ * @param {string[]} args.primaryKey - Composite primary-key definition.
88
+ * @param {import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>} args.query - Query receiving the predicate.
89
+ * @param {ModelPrimaryKeyValue[]} args.values - Candidate identities.
90
+ * @returns {string} - Qualified SQL predicate.
91
+ */
92
+ export function compositeModelPrimaryKeyCohortSql({modelClass, primaryKey, query, values}) {
93
+ if (values.length === 0) return "(1 = 0)"
94
+
95
+ const tableSql = query.driver.quoteTable(query.getTableReferenceForJoin())
96
+
97
+ return `(${values.map((value) => {
98
+ const conditions = modelPrimaryKeyConditions(primaryKey, value)
99
+ const identitySql = primaryKey.map((attributeName) => {
100
+ const columnName = modelClass.getColumnNameForAttributeName(attributeName)
101
+
102
+ return `${tableSql}.${query.driver.quoteColumn(columnName)} = ${query.driver.quote(conditions[attributeName])}`
103
+ }).join(" AND ")
104
+
105
+ return `(${identitySql})`
106
+ }).join(" OR ")})`
107
+ }
108
+
109
+ /**
110
+ * Reads a scalar or composite identity through the owning model's value reader.
111
+ * @param {ModelPrimaryKeyDefinition} primaryKey - Primary-key definition.
112
+ * @param {(key: string) => ReturnType<typeof JSON.parse>} read - Primary-key value reader.
113
+ * @returns {ModelPrimaryKeyValue} - Model identity.
114
+ */
115
+ export function readModelPrimaryKeyValue(primaryKey, read) {
116
+ if (!Array.isArray(primaryKey)) return /** @type {ModelPrimaryKeyScalar} */ (read(primaryKey))
117
+
118
+ /** @type {CompositeModelPrimaryKeyValue} */
119
+ const value = {}
120
+
121
+ for (const key of primaryKey) value[key] = /** @type {ModelPrimaryKeyScalar} */ (read(key))
122
+
123
+ return validatedCompositePrimaryKeyValue(primaryKey, value)
124
+ }
125
+
126
+ /**
127
+ * Returns an unambiguous map key while preserving legacy scalar map keys.
128
+ * @param {ModelPrimaryKeyDefinition} primaryKey - Primary-key definition.
129
+ * @param {ReturnType<typeof JSON.parse>} value - Candidate identity.
130
+ * @returns {string} - Canonical identity key.
131
+ */
132
+ export function modelPrimaryKeyCacheKey(primaryKey, value) {
133
+ if (!Array.isArray(primaryKey)) return String(value)
134
+
135
+ const validatedValue = validatedCompositePrimaryKeyValue(primaryKey, value)
136
+
137
+ return JSON.stringify(primaryKey.map((key) => validatedValue[key]))
138
+ }
139
+
140
+ /**
141
+ * Restores a model identity from its canonical string representation.
142
+ * @param {ModelPrimaryKeyDefinition} primaryKey - Primary-key definition.
143
+ * @param {string} cacheKey - Canonical identity key.
144
+ * @returns {ModelPrimaryKeyValue} - Restored identity.
145
+ */
146
+ export function modelPrimaryKeyValueFromCacheKey(primaryKey, cacheKey) {
147
+ if (!Array.isArray(primaryKey)) return cacheKey
148
+
149
+ let parsed
150
+
151
+ try {
152
+ parsed = JSON.parse(cacheKey)
153
+ } catch (error) {
154
+ if (!(error instanceof SyntaxError)) throw error
155
+
156
+ throw new TypeError("Expected composite primary key cache value to be valid JSON.", {cause: error})
157
+ }
158
+
159
+ if (!Array.isArray(parsed) || parsed.length !== primaryKey.length) {
160
+ throw new TypeError(`Expected composite primary key cache value with ${primaryKey.length} fields.`)
161
+ }
162
+
163
+ /** @type {CompositeModelPrimaryKeyValue} */
164
+ const value = {}
165
+
166
+ for (let index = 0; index < primaryKey.length; index += 1) {
167
+ value[primaryKey[index]] = parsed[index]
168
+ }
169
+
170
+ return validatedCompositePrimaryKeyValue(primaryKey, value)
171
+ }
@@ -0,0 +1,132 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * Computes a SHA-256 hex digest for arbitrary bytes without Node-only imports.
5
+ * @param {Uint8Array} bytes - Bytes to digest.
6
+ * @returns {string} Hex digest.
7
+ */
8
+ export default function sha256BytesHex(bytes) {
9
+ const bitLength = bytes.length * 8
10
+ const blockCount = Math.ceil((bytes.length + 9) / 64)
11
+ const block = new Uint8Array(64)
12
+ const hash = SHA256_INITIAL_HASH.slice()
13
+ /** @type {number[]} */
14
+ const words = new Array(64)
15
+
16
+ const highLength = Math.floor(bitLength / 0x100000000)
17
+ const lowLength = bitLength >>> 0
18
+
19
+ for (let blockIndex = 0; blockIndex < blockCount; blockIndex += 1) {
20
+ const offset = blockIndex * 64
21
+ const chunkLength = Math.min(64, Math.max(0, bytes.length - offset))
22
+
23
+ block.fill(0)
24
+ if (chunkLength > 0) block.set(bytes.subarray(offset, offset + chunkLength))
25
+ if (bytes.length >= offset && bytes.length < offset + 64) block[bytes.length - offset] = 0x80
26
+
27
+ if (blockIndex === blockCount - 1) {
28
+ block[56] = (highLength >>> 24) & 0xff
29
+ block[57] = (highLength >>> 16) & 0xff
30
+ block[58] = (highLength >>> 8) & 0xff
31
+ block[59] = highLength & 0xff
32
+ block[60] = (lowLength >>> 24) & 0xff
33
+ block[61] = (lowLength >>> 16) & 0xff
34
+ block[62] = (lowLength >>> 8) & 0xff
35
+ block[63] = lowLength & 0xff
36
+ }
37
+
38
+ for (let index = 0; index < 16; index++) {
39
+ const byteIndex = index * 4
40
+
41
+ words[index] = ((block[byteIndex] << 24) | (block[byteIndex + 1] << 16) | (block[byteIndex + 2] << 8) | block[byteIndex + 3]) >>> 0
42
+ }
43
+
44
+ for (let index = 16; index < 64; index++) {
45
+ const s0 = rotateRight(words[index - 15], 7) ^ rotateRight(words[index - 15], 18) ^ (words[index - 15] >>> 3)
46
+ const s1 = rotateRight(words[index - 2], 17) ^ rotateRight(words[index - 2], 19) ^ (words[index - 2] >>> 10)
47
+
48
+ words[index] = add32(words[index - 16], s0, words[index - 7], s1)
49
+ }
50
+
51
+ let a = hash[0]
52
+ let b = hash[1]
53
+ let c = hash[2]
54
+ let d = hash[3]
55
+ let e = hash[4]
56
+ let f = hash[5]
57
+ let g = hash[6]
58
+ let h = hash[7]
59
+
60
+ for (let index = 0; index < 64; index++) {
61
+ const s1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25)
62
+ const ch = (e & f) ^ ((~e) & g)
63
+ const temp1 = add32(h, s1, ch, SHA256_K[index], words[index])
64
+ const s0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22)
65
+ const maj = (a & b) ^ (a & c) ^ (b & c)
66
+ const temp2 = add32(s0, maj)
67
+
68
+ h = g
69
+ g = f
70
+ f = e
71
+ e = add32(d, temp1)
72
+ d = c
73
+ c = b
74
+ b = a
75
+ a = add32(temp1, temp2)
76
+ }
77
+
78
+ hash[0] = add32(hash[0], a)
79
+ hash[1] = add32(hash[1], b)
80
+ hash[2] = add32(hash[2], c)
81
+ hash[3] = add32(hash[3], d)
82
+ hash[4] = add32(hash[4], e)
83
+ hash[5] = add32(hash[5], f)
84
+ hash[6] = add32(hash[6], g)
85
+ hash[7] = add32(hash[7], h)
86
+ }
87
+
88
+ return hash.map((value) => value.toString(16).padStart(8, "0")).join("")
89
+ }
90
+
91
+ const SHA256_INITIAL_HASH = [
92
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
93
+ 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
94
+ ]
95
+
96
+ const SHA256_K = [
97
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
98
+ 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
99
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
100
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
101
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
102
+ 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
103
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
104
+ 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
105
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
106
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
107
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
108
+ 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
109
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
110
+ 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
111
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
112
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
113
+ ]
114
+
115
+ /**
116
+ * Rotates an unsigned value right.
117
+ * @param {number} value Unsigned value.
118
+ * @param {number} bits Rotation count.
119
+ * @returns {number} Rotated value.
120
+ */
121
+ function rotateRight(value, bits) {
122
+ return (value >>> bits) | (value << (32 - bits))
123
+ }
124
+
125
+ /**
126
+ * Adds numbers with unsigned 32-bit overflow.
127
+ * @param {...number} values Values to add.
128
+ * @returns {number} Unsigned sum.
129
+ */
130
+ function add32(...values) {
131
+ return values.reduce((sum, value) => (sum + value) >>> 0, 0)
132
+ }
@@ -1,141 +1,13 @@
1
+ // @ts-check
2
+
3
+ import sha256BytesHex from "./sha256-bytes-hex.js"
4
+
1
5
  /**
2
6
  * Computes SHA-256 without importing Node-only crypto modules, keeping this
3
- * module safe for Expo/browser bundles. Implementation is a straightforward
4
- * pure-JavaScript SHA-256 derived from FIPS 180-4, using only 32-bit unsigned
5
- * integer arithmetic.
7
+ * module safe for Expo/browser bundles.
6
8
  * @param {string} message - UTF-8 message.
7
- * @returns {string} - Hex digest.
9
+ * @returns {string} Hex digest.
8
10
  */
9
11
  export default function sha256Hex(message) {
10
- const bytes = utf8Bytes(message)
11
- const padded = [...bytes]
12
- const bitLength = bytes.length * 8
13
- const hash = [...SHA256_INITIAL_HASH]
14
- /** @type {number[]} */
15
- const words = new Array(64)
16
-
17
- padded.push(0x80)
18
- while (padded.length % 64 !== 56) padded.push(0)
19
-
20
- const highLength = Math.floor(bitLength / 0x100000000)
21
- const lowLength = bitLength >>> 0
22
-
23
- for (const value of [highLength, lowLength]) {
24
- padded.push((value >>> 24) & 0xff, (value >>> 16) & 0xff, (value >>> 8) & 0xff, value & 0xff)
25
- }
26
-
27
- for (let offset = 0; offset < padded.length; offset += 64) {
28
- for (let i = 0; i < 16; i++) {
29
- const index = offset + (i * 4)
30
-
31
- words[i] = (((padded[index] || 0) << 24) | ((padded[index + 1] || 0) << 16) | ((padded[index + 2] || 0) << 8) | (padded[index + 3] || 0)) >>> 0
32
- }
33
-
34
- for (let i = 16; i < 64; i++) {
35
- const s0 = rotateRight(words[i - 15], 7) ^ rotateRight(words[i - 15], 18) ^ (words[i - 15] >>> 3)
36
- const s1 = rotateRight(words[i - 2], 17) ^ rotateRight(words[i - 2], 19) ^ (words[i - 2] >>> 10)
37
-
38
- words[i] = add32(words[i - 16], s0, words[i - 7], s1)
39
- }
40
-
41
- let [a, b, c, d, e, f, g, h] = hash
42
-
43
- for (let i = 0; i < 64; i++) {
44
- const s1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25)
45
- const ch = (e & f) ^ ((~e) & g)
46
- const temp1 = add32(h, s1, ch, SHA256_K[i], words[i])
47
- const s0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22)
48
- const maj = (a & b) ^ (a & c) ^ (b & c)
49
- const temp2 = add32(s0, maj)
50
-
51
- h = g
52
- g = f
53
- f = e
54
- e = add32(d, temp1)
55
- d = c
56
- c = b
57
- b = a
58
- a = add32(temp1, temp2)
59
- }
60
-
61
- hash[0] = add32(hash[0], a)
62
- hash[1] = add32(hash[1], b)
63
- hash[2] = add32(hash[2], c)
64
- hash[3] = add32(hash[3], d)
65
- hash[4] = add32(hash[4], e)
66
- hash[5] = add32(hash[5], f)
67
- hash[6] = add32(hash[6], g)
68
- hash[7] = add32(hash[7], h)
69
- }
70
-
71
- return hash.map((value) => value.toString(16).padStart(8, "0")).join("")
72
- }
73
-
74
- const SHA256_INITIAL_HASH = [
75
- 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
76
- 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
77
- ]
78
-
79
- const SHA256_K = [
80
- 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
81
- 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
82
- 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
83
- 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
84
- 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
85
- 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
86
- 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
87
- 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
88
- 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
89
- 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
90
- 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
91
- 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
92
- 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
93
- 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
94
- 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
95
- 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
96
- ]
97
-
98
- /**
99
- * Converts a string to UTF-8 bytes.
100
- * @param {string} value - String value.
101
- * @returns {number[]} - UTF-8 bytes.
102
- */
103
- function utf8Bytes(value) {
104
- /** @type {number[]} */
105
- const bytes = []
106
-
107
- for (const character of value) {
108
- const codePoint = /** @type {number} */ (character.codePointAt(0))
109
-
110
- if (codePoint <= 0x7f) {
111
- bytes.push(codePoint)
112
- } else if (codePoint <= 0x7ff) {
113
- bytes.push(0xc0 | (codePoint >>> 6), 0x80 | (codePoint & 0x3f))
114
- } else if (codePoint <= 0xffff) {
115
- bytes.push(0xe0 | (codePoint >>> 12), 0x80 | ((codePoint >>> 6) & 0x3f), 0x80 | (codePoint & 0x3f))
116
- } else {
117
- bytes.push(0xf0 | (codePoint >>> 18), 0x80 | ((codePoint >>> 12) & 0x3f), 0x80 | ((codePoint >>> 6) & 0x3f), 0x80 | (codePoint & 0x3f))
118
- }
119
- }
120
-
121
- return bytes
122
- }
123
-
124
- /**
125
- * Adds unsigned 32-bit integers.
126
- * @param {...number} values - Values to add.
127
- * @returns {number} - Unsigned 32-bit result.
128
- */
129
- function add32(...values) {
130
- return values.reduce((sum, value) => (sum + value) >>> 0, 0)
131
- }
132
-
133
- /**
134
- * Rotates a 32-bit integer right.
135
- * @param {number} value - Value to rotate.
136
- * @param {number} bits - Bit count.
137
- * @returns {number} - Rotated value.
138
- */
139
- function rotateRight(value, bits) {
140
- return (value >>> bits) | (value << (32 - bits))
12
+ return sha256BytesHex(new TextEncoder().encode(message))
141
13
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "velocious": "build/bin/velocious.js"
4
4
  },
5
5
  "name": "velocious",
6
- "version": "1.0.643",
6
+ "version": "1.0.645",
7
7
  "main": "build/index.js",
8
8
  "types": "build/index.d.ts",
9
9
  "files": [
@@ -64,7 +64,6 @@
64
64
  "description": "",
65
65
  "dependencies": {
66
66
  "@js-temporal/polyfill": "^0.5.1",
67
- "@velocious/testing": "0.0.1",
68
67
  "awaitery": "^1.0.19",
69
68
  "bcryptjs": "^3.0.2",
70
69
  "better-localstorage": "^1.0.7",
@@ -92,10 +91,11 @@
92
91
  "ws": "^8.21.3"
93
92
  },
94
93
  "peerDependencies": {
94
+ "@velocious/testing": "0.0.9",
95
95
  "react": ">=18",
96
+ "smtp-connection": "^4.0.2",
96
97
  "sqlite": "^5.1.1",
97
- "sqlite3": "^6.0.1",
98
- "smtp-connection": "^4.0.2"
98
+ "sqlite3": "^6.0.1"
99
99
  },
100
100
  "peerDependenciesMeta": {
101
101
  "react": {
@@ -121,6 +121,7 @@
121
121
  "@types/react-dom": "^19.2.3",
122
122
  "@types/strftime": "^0.9.8",
123
123
  "@types/ws": "^8.18.1",
124
+ "@velocious/testing": "0.0.9",
124
125
  "chokidar-cli": "^3.0.0",
125
126
  "cpy-cli": "^7.0.0",
126
127
  "esbuild": "^0.28.0",
@@ -189,9 +189,10 @@ export default class VelociousAuthorizationAbility {
189
189
  * @param {string} args.action - Requested action.
190
190
  * @param {typeof import("../database/record/index.js").default} args.modelClass - Model class.
191
191
  * @param {import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>} args.query - Query.
192
+ * @param {() => import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>} [args.ruleQueryFactory] - Optional factory for the queries that evaluate individual conditional rules.
192
193
  * @returns {import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>} - Authorized query.
193
194
  */
194
- applyToQuery({action, modelClass, query}) {
195
+ applyToQuery({action, modelClass, query, ruleQueryFactory}) {
195
196
  this.loadAbilitiesForModelClass(modelClass)
196
197
 
197
198
  const applicableRules = this.rulesFor({action, modelClass})
@@ -203,18 +204,18 @@ export default class VelociousAuthorizationAbility {
203
204
  }
204
205
 
205
206
  if (allowRules.some((rule) => !rule.conditions)) {
206
- this.applyDenyRules({action, denyRules, modelClass, query})
207
+ this.applyDenyRules({action, denyRules, modelClass, query, ruleQueryFactory})
207
208
  return query
208
209
  }
209
210
 
210
- const allowSqlParts = this.conditionSqlParts({action, modelClass, query, rules: allowRules})
211
+ const allowSqlParts = this.conditionSqlParts({action, modelClass, query, ruleQueryFactory, rules: allowRules})
211
212
 
212
213
  if (allowSqlParts.length === 0) {
213
214
  return query.where("1=0")
214
215
  }
215
216
 
216
217
  query.where(`(${allowSqlParts.join(" OR ")})`)
217
- this.applyDenyRules({action, denyRules, modelClass, query})
218
+ this.applyDenyRules({action, denyRules, modelClass, query, ruleQueryFactory})
218
219
 
219
220
  return query
220
221
  }
@@ -240,19 +241,20 @@ export default class VelociousAuthorizationAbility {
240
241
  * @param {string} args.action - Action.
241
242
  * @param {typeof import("../database/record/index.js").default} args.modelClass - Model class.
242
243
  * @param {import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>} args.query - Base query.
244
+ * @param {() => import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>} [args.ruleQueryFactory] - Optional conditional-rule query factory.
243
245
  * @param {AbilityRuleType[]} args.rules - Rules.
244
246
  * @returns {string[]} - SQL condition parts.
245
247
  */
246
- conditionSqlParts({action, modelClass, query, rules}) {
247
- const pk = modelClass.primaryKey()
248
- const quotedBaseTable = query.driver.quoteTable(modelClass.tableName())
249
- const quotedPk = query.driver.quoteColumn(pk)
248
+ conditionSqlParts({action, modelClass, query, ruleQueryFactory, rules}) {
249
+ const primaryKey = modelClass.primaryKey()
250
+ const primaryKeyAttributes = Array.isArray(primaryKey) ? primaryKey : [primaryKey]
251
+ const quotedBaseTable = query.driver.quoteTable(query.getTableReferenceForJoin())
250
252
  const sqlParts = []
251
253
 
252
254
  for (const rule of rules) {
253
255
  if (!rule.conditions) continue
254
256
 
255
- const scopedQuery = modelClass._newQuery()
257
+ const scopedQuery = ruleQueryFactory ? ruleQueryFactory() : modelClass._newQuery()
256
258
  const resultQuery = this.applyRuleCondition({
257
259
  action,
258
260
  conditions: rule.conditions,
@@ -260,7 +262,16 @@ export default class VelociousAuthorizationAbility {
260
262
  query: scopedQuery
261
263
  })
262
264
  const finalQuery = resultQuery || scopedQuery
263
- const selectedPkSql = `${quotedBaseTable}.${quotedPk}`
265
+
266
+ if (ruleQueryFactory && finalQuery !== scopedQuery) {
267
+ const finalFroms = finalQuery.getFroms()
268
+
269
+ finalFroms.splice(0, finalFroms.length, ...scopedQuery.getFroms())
270
+ }
271
+
272
+ const quotedScopedTable = query.driver.quoteTable(finalQuery.getTableReferenceForJoin())
273
+ const primaryKeyColumns = primaryKeyAttributes.map((attributeName) => modelClass.getColumnNameForAttributeName(attributeName))
274
+ const selectedPkSql = primaryKeyColumns.map((columnName) => `${quotedScopedTable}.${query.driver.quoteColumn(columnName)}`)
264
275
 
265
276
  if (finalQuery._distinct) {
266
277
  query.distinct(true)
@@ -268,7 +279,18 @@ export default class VelociousAuthorizationAbility {
268
279
 
269
280
  finalQuery.select(selectedPkSql)
270
281
 
271
- sqlParts.push(`${quotedBaseTable}.${quotedPk} IN (${finalQuery.toSql()})`)
282
+ if (Array.isArray(primaryKey)) {
283
+ const authorizedRowsAlias = query.driver.quoteTable("velocious_authorized_rows")
284
+ const identitySql = primaryKeyColumns.map((columnName) => {
285
+ const quotedColumn = query.driver.quoteColumn(columnName)
286
+
287
+ return `${authorizedRowsAlias}.${quotedColumn} = ${quotedBaseTable}.${quotedColumn}`
288
+ }).join(" AND ")
289
+
290
+ sqlParts.push(`EXISTS (SELECT 1 FROM (${finalQuery.toSql()}) AS ${authorizedRowsAlias} WHERE ${identitySql})`)
291
+ } else {
292
+ sqlParts.push(`${quotedBaseTable}.${query.driver.quoteColumn(primaryKeyColumns[0])} IN (${finalQuery.toSql()})`)
293
+ }
272
294
  }
273
295
 
274
296
  return sqlParts
@@ -281,9 +303,10 @@ export default class VelociousAuthorizationAbility {
281
303
  * @param {AbilityRuleType[]} args.denyRules - Deny rules.
282
304
  * @param {typeof import("../database/record/index.js").default} args.modelClass - Model class.
283
305
  * @param {import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>} args.query - Query.
306
+ * @param {() => import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>} [args.ruleQueryFactory] - Optional conditional-rule query factory.
284
307
  * @returns {void} - No return value.
285
308
  */
286
- applyDenyRules({action, denyRules, modelClass, query}) {
309
+ applyDenyRules({action, denyRules, modelClass, query, ruleQueryFactory}) {
287
310
  if (denyRules.length === 0) return
288
311
 
289
312
  if (denyRules.some((rule) => !rule.conditions)) {
@@ -291,7 +314,7 @@ export default class VelociousAuthorizationAbility {
291
314
  return
292
315
  }
293
316
 
294
- const denySqlParts = this.conditionSqlParts({action, modelClass, query, rules: denyRules})
317
+ const denySqlParts = this.conditionSqlParts({action, modelClass, query, ruleQueryFactory, rules: denyRules})
295
318
 
296
319
  if (denySqlParts.length > 0) {
297
320
  query.where(`NOT (${denySqlParts.join(" OR ")})`)
@@ -48,6 +48,15 @@ export default class BackgroundJobsAdapter {
48
48
  */
49
49
  async reconcileQueueConcurrency() { throw new Error("BackgroundJobsAdapter#reconcileQueueConcurrency is not implemented") }
50
50
 
51
+ /**
52
+ * Repairs drift in adapter-owned durable active concurrency counts. Adapters
53
+ * without duplicate active-count persistence can keep this no-op result.
54
+ * @returns {Promise<import("./types.js").BackgroundJobConcurrencyReconciliation>} - Repair summary.
55
+ */
56
+ async reconcileActiveConcurrency() {
57
+ return {candidateCount: 0, checkedCount: 0, repairedCount: 0, repairs: [], repairsTruncatedCount: 0}
58
+ }
59
+
51
60
  /**
52
61
  * Enqueues a job.
53
62
  * @param {{jobName: string, args: Array<ReturnType<typeof JSON.parse>>, options?: import("./types.js").BackgroundJobOptions}} _args - Job request.