@aventara/core 1.0.0-rc.0 → 1.0.0-rc.10

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 (338) hide show
  1. package/CHANGELOG.md +2068 -1
  2. package/ERRORS.md +142 -0
  3. package/README.md +485 -60
  4. package/dist/adapters/adapter-discovery-diagnostic.d.ts +4 -4
  5. package/dist/adapters/adapter-discovery-diagnostic.guidance.d.ts +6 -0
  6. package/dist/adapters/adapter-discovery-diagnostic.guidance.js +7 -0
  7. package/dist/adapters/adapter-field.d.ts +33 -11
  8. package/dist/adapters/adapter-identifier.d.ts +15 -6
  9. package/dist/adapters/adapter-identifier.js +6 -1
  10. package/dist/adapters/adapter-model.d.ts +18 -9
  11. package/dist/adapters/adapter-operation-executor.interface.d.ts +4 -4
  12. package/dist/adapters/adapter-operation.d.ts +21 -6
  13. package/dist/adapters/adapter-resource.d.ts +23 -9
  14. package/dist/adapters/adapter-sql-statement.d.ts +19 -0
  15. package/dist/adapters/adapter-transaction-executor.interface.d.ts +19 -0
  16. package/dist/adapters/adapter.interface.d.ts +59 -29
  17. package/dist/avz/avz-check.factory.d.ts +83 -0
  18. package/dist/avz/avz-check.factory.js +116 -0
  19. package/dist/avz/avz-check.registry.d.ts +35 -0
  20. package/dist/avz/avz-check.registry.js +9 -0
  21. package/dist/avz/avz.d.ts +203 -0
  22. package/dist/{json → avz}/avz.js +150 -8
  23. package/dist/{json → avz}/index.d.ts +2 -1
  24. package/dist/{json → avz}/json-shape.registry.d.ts +9 -8
  25. package/dist/{json → avz}/zod-issue.translator.d.ts +8 -2
  26. package/dist/{json → avz}/zod-issue.translator.js +7 -0
  27. package/dist/compiler/contract-compiler-diagnostic.d.ts +31 -5
  28. package/dist/compiler/contract-compiler-diagnostic.guidance.d.ts +6 -0
  29. package/dist/compiler/contract-compiler-diagnostic.guidance.js +97 -0
  30. package/dist/compiler/contract.compiler.d.ts +23 -17
  31. package/dist/compiler/contract.compiler.js +753 -443
  32. package/dist/compiler/create-requirements.projector.d.ts +49 -0
  33. package/dist/compiler/create-requirements.projector.js +423 -0
  34. package/dist/compiler/field-validator.compiler.d.ts +47 -0
  35. package/dist/compiler/field-validator.compiler.js +238 -0
  36. package/dist/compiler/managed-elsewhere.projector.d.ts +52 -0
  37. package/dist/compiler/managed-elsewhere.projector.js +179 -0
  38. package/dist/compiler/managed-elsewhere.validator.d.ts +9 -0
  39. package/dist/compiler/managed-elsewhere.validator.js +74 -0
  40. package/dist/config/any-framework-config.d.ts +19 -12
  41. package/dist/config/computed-fields.d.ts +62 -0
  42. package/dist/config/computed-fields.js +73 -0
  43. package/dist/config/config-diagnostic.d.ts +11 -4
  44. package/dist/config/config-diagnostic.guidance.d.ts +6 -0
  45. package/dist/config/config-diagnostic.guidance.js +32 -0
  46. package/dist/config/config-layer.d.ts +15 -6
  47. package/dist/config/config.resolver.d.ts +98 -31
  48. package/dist/config/config.resolver.js +173 -44
  49. package/dist/config/config.validator.d.ts +7 -7
  50. package/dist/config/config.validator.js +792 -69
  51. package/dist/config/define-resource.d.ts +53 -0
  52. package/dist/config/define-resource.js +14 -0
  53. package/dist/config/effective-fields.d.ts +19 -23
  54. package/dist/config/entrypoint.normalizer.d.ts +4 -3
  55. package/dist/config/exact-framework-config.d.ts +325 -0
  56. package/dist/config/field-compute.resolver.d.ts +21 -0
  57. package/dist/config/field-compute.resolver.js +123 -0
  58. package/dist/config/field-pipeline-config.d.ts +47 -0
  59. package/dist/config/forced-operation.validator.d.ts +10 -0
  60. package/dist/config/forced-operation.validator.js +66 -0
  61. package/dist/config/framework-config.d.ts +148 -39
  62. package/dist/config/framework-exemptions-config.d.ts +91 -0
  63. package/dist/config/framework-exemptions-config.js +1 -0
  64. package/dist/config/framework-field-definition.d.ts +78 -0
  65. package/dist/config/framework-field-definition.js +28 -0
  66. package/dist/config/framework-resources-config.d.ts +127 -0
  67. package/dist/config/framework-resources-config.js +1 -0
  68. package/dist/config/pipeline-config.d.ts +56 -20
  69. package/dist/config/pipeline-tree.resolver.d.ts +79 -0
  70. package/dist/config/pipeline-tree.resolver.js +220 -0
  71. package/dist/config/stage-type.eraser.d.ts +65 -0
  72. package/dist/config/stage-type.eraser.js +1 -0
  73. package/dist/contracts/access-route.rules.d.ts +10 -0
  74. package/dist/contracts/access-route.rules.js +45 -0
  75. package/dist/contracts/capability.d.ts +37 -12
  76. package/dist/contracts/capability.js +20 -0
  77. package/dist/contracts/client-contract-structure.validator.d.ts +621 -114
  78. package/dist/contracts/client-contract-structure.validator.js +189 -10
  79. package/dist/contracts/contract-access.d.ts +29 -0
  80. package/dist/contracts/contract-access.js +1 -0
  81. package/dist/contracts/contract-canonicalizer.d.ts +27 -3
  82. package/dist/contracts/contract-canonicalizer.js +168 -29
  83. package/dist/contracts/contract-hash.d.ts +11 -7
  84. package/dist/contracts/contract-hash.js +4 -3
  85. package/dist/contracts/contract-limits.d.ts +4 -1
  86. package/dist/contracts/contract-limits.js +1 -0
  87. package/dist/contracts/contract-lookup.d.ts +5 -5
  88. package/dist/contracts/contract-protocol.d.ts +4 -4
  89. package/dist/contracts/contract.d.ts +51 -17
  90. package/dist/contracts/create-requirements.d.ts +35 -0
  91. package/dist/contracts/create-requirements.js +1 -0
  92. package/dist/contracts/enum-definition.d.ts +1 -1
  93. package/dist/contracts/field-check.d.ts +101 -0
  94. package/dist/contracts/field-check.js +1 -0
  95. package/dist/contracts/field.d.ts +109 -42
  96. package/dist/contracts/identifier.d.ts +4 -4
  97. package/dist/contracts/json-schema.projector.d.ts +11 -0
  98. package/dist/contracts/json-schema.projector.js +78 -0
  99. package/dist/contracts/json-shape.d.ts +12 -1
  100. package/dist/contracts/lifecycle-trait.d.ts +2 -2
  101. package/dist/contracts/resource-operations.d.ts +28 -28
  102. package/dist/contracts/resource.d.ts +15 -9
  103. package/dist/contracts/scalar-value-type.d.ts +17 -15
  104. package/dist/contracts/scalar.d.ts +3 -3
  105. package/dist/contracts/transaction-capability.d.ts +1 -1
  106. package/dist/contracts/wire-scalar-format.d.ts +4 -4
  107. package/dist/framework/access-facade.d.ts +116 -0
  108. package/dist/framework/access-facade.js +120 -0
  109. package/dist/framework/contract-view.d.ts +315 -145
  110. package/dist/framework/framework-construction.error.d.ts +21 -11
  111. package/dist/framework/framework-construction.error.js +20 -4
  112. package/dist/framework/framework.d.ts +71 -26
  113. package/dist/framework/framework.factory.d.ts +8 -6
  114. package/dist/framework/framework.factory.js +150 -38
  115. package/dist/framework/operation-facade.d.ts +192 -54
  116. package/dist/framework/operation-facade.js +10 -1
  117. package/dist/index.d.ts +67 -50
  118. package/dist/index.js +7 -6
  119. package/dist/internal.d.ts +20 -10
  120. package/dist/internal.js +9 -1
  121. package/dist/operations/database-refusal.d.ts +51 -0
  122. package/dist/operations/database-refusal.js +45 -0
  123. package/dist/operations/operation-arguments.d.ts +381 -228
  124. package/dist/operations/operation-call.d.ts +31 -28
  125. package/dist/operations/operation-identity.d.ts +10 -10
  126. package/dist/operations/operation-result.d.ts +25 -23
  127. package/dist/operations/operation-validator-core.d.ts +6 -6
  128. package/dist/operations/operation-validator-core.js +172 -38
  129. package/dist/operations/operation-validator.d.ts +10 -10
  130. package/dist/operations/projection-scope.resolver.d.ts +6 -6
  131. package/dist/operations/structural-limits.walker.d.ts +7 -7
  132. package/dist/operations/structural-limits.walker.js +145 -0
  133. package/dist/operations/validation-code.d.ts +11 -6
  134. package/dist/operations/validation-code.guidance.d.ts +6 -0
  135. package/dist/operations/validation-code.guidance.js +90 -0
  136. package/dist/operations/validation-code.js +4 -0
  137. package/dist/operations/write-scope.resolver.d.ts +10 -6
  138. package/dist/operations/write-scope.resolver.js +2 -0
  139. package/dist/policy/access-provider.interface.d.ts +91 -0
  140. package/dist/policy/access-provider.interface.js +1 -0
  141. package/dist/policy/access-provider.registry.d.ts +17 -0
  142. package/dist/policy/access-provider.registry.js +15 -0
  143. package/dist/policy/access-question.d.ts +20 -0
  144. package/dist/policy/access-question.js +1 -0
  145. package/dist/policy/access-question.registry.d.ts +5 -0
  146. package/dist/policy/access-question.registry.js +19 -0
  147. package/dist/policy/access-question.runtime.d.ts +36 -0
  148. package/dist/policy/access-question.runtime.js +297 -0
  149. package/dist/policy/access-verb.table.d.ts +56 -0
  150. package/dist/policy/access-verb.table.js +21 -0
  151. package/dist/policy/access-verbs.d.ts +46 -0
  152. package/dist/policy/access-verbs.js +1 -0
  153. package/dist/policy/policy-admission.d.ts +20 -0
  154. package/dist/policy/policy-admission.js +409 -0
  155. package/dist/policy/policy-after-update.runtime.d.ts +79 -0
  156. package/dist/policy/policy-after-update.runtime.js +208 -0
  157. package/dist/policy/policy-condition.d.ts +15 -0
  158. package/dist/policy/policy-condition.js +36 -0
  159. package/dist/policy/policy-engine.d.ts +205 -0
  160. package/dist/policy/policy-engine.js +1 -0
  161. package/dist/policy/policy-named-rows.d.ts +18 -0
  162. package/dist/policy/policy-named-rows.js +51 -0
  163. package/dist/policy/policy-nested-write.runtime.d.ts +44 -0
  164. package/dist/policy/policy-nested-write.runtime.js +545 -0
  165. package/dist/policy/policy-read.runtime.d.ts +81 -0
  166. package/dist/policy/policy-read.runtime.js +503 -0
  167. package/dist/policy/policy-reference-write.runtime.d.ts +51 -0
  168. package/dist/policy/policy-reference-write.runtime.js +345 -0
  169. package/dist/policy/policy-request.d.ts +41 -0
  170. package/dist/policy/policy-request.js +63 -0
  171. package/dist/policy/policy-session.d.ts +106 -0
  172. package/dist/policy/policy-session.js +295 -0
  173. package/dist/policy/policy-startup.validator.d.ts +10 -0
  174. package/dist/policy/policy-startup.validator.js +112 -0
  175. package/dist/policy/policy-write.runtime.d.ts +39 -0
  176. package/dist/policy/policy-write.runtime.js +532 -0
  177. package/dist/prebuilt/core-version.generated.d.ts +2 -0
  178. package/dist/prebuilt/core-version.generated.js +1 -0
  179. package/dist/prebuilt/prebuilt-changes.d.ts +29 -0
  180. package/dist/prebuilt/prebuilt-changes.js +65 -0
  181. package/dist/prebuilt/prebuilt-contracts.d.ts +40 -0
  182. package/dist/prebuilt/prebuilt-contracts.emitter.d.ts +16 -0
  183. package/dist/prebuilt/prebuilt-contracts.emitter.js +50 -0
  184. package/dist/prebuilt/prebuilt-contracts.js +1 -0
  185. package/dist/prebuilt/prebuilt-contracts.loader.d.ts +19 -0
  186. package/dist/prebuilt/prebuilt-contracts.loader.js +77 -0
  187. package/dist/prebuilt/prebuilt-inputs.fingerprint.d.ts +16 -0
  188. package/dist/prebuilt/prebuilt-inputs.fingerprint.js +229 -0
  189. package/dist/protocol/absent-route.responder.d.ts +17 -34
  190. package/dist/protocol/absent-route.responder.js +53 -20
  191. package/dist/protocol/contract-document.responder.d.ts +5 -4
  192. package/dist/protocol/contract-document.responder.js +2 -2
  193. package/dist/protocol/http-request.decoder.d.ts +33 -13
  194. package/dist/protocol/http-request.decoder.js +34 -11
  195. package/dist/protocol/http-request.reader.d.ts +9 -6
  196. package/dist/protocol/http-request.reader.js +1 -0
  197. package/dist/protocol/http-response.encoder.d.ts +4 -4
  198. package/dist/protocol/http-response.encoder.js +94 -55
  199. package/dist/protocol/http-status.table.d.ts +2 -2
  200. package/dist/protocol/operation-response.guard.d.ts +2 -2
  201. package/dist/protocol/protocol-headers.d.ts +3 -3
  202. package/dist/protocol/protocol-surface.d.ts +10 -5
  203. package/dist/protocol/protocol-surface.js +7 -0
  204. package/dist/protocol/protocol.binder.d.ts +66 -28
  205. package/dist/protocol/protocol.binder.js +16 -10
  206. package/dist/protocol/protocol.handler.d.ts +34 -14
  207. package/dist/protocol/protocol.handler.js +122 -18
  208. package/dist/protocol/unmounted-route.error.d.ts +4 -4
  209. package/dist/protocol/unmounted-route.error.js +2 -2
  210. package/dist/runtime/caller-data.copier.d.ts +22 -7
  211. package/dist/runtime/caller-data.copier.js +30 -17
  212. package/dist/runtime/caller-data.door.d.ts +18 -11
  213. package/dist/runtime/caller-data.door.js +6 -4
  214. package/dist/runtime/computed-read-runtime.d.ts +12 -6
  215. package/dist/runtime/computed-read-runtime.js +271 -71
  216. package/dist/runtime/computed-value.error.d.ts +7 -6
  217. package/dist/runtime/computed-value.error.js +5 -4
  218. package/dist/runtime/context-executor.runtime.d.ts +60 -0
  219. package/dist/runtime/context-executor.runtime.js +277 -0
  220. package/dist/runtime/core-class.brand.d.ts +10 -0
  221. package/dist/runtime/core-class.brand.js +21 -0
  222. package/dist/runtime/create-requirement.d.ts +34 -79
  223. package/dist/runtime/create-requirement.js +205 -74
  224. package/dist/runtime/decimal.d.ts +16 -2
  225. package/dist/runtime/decimal.js +22 -1
  226. package/dist/runtime/diagnostics-reporter.d.ts +91 -30
  227. package/dist/runtime/diagnostics-reporter.js +37 -1
  228. package/dist/runtime/error-boundary.d.ts +73 -9
  229. package/dist/runtime/error-boundary.js +57 -19
  230. package/dist/runtime/error-guidance.d.ts +12 -0
  231. package/dist/runtime/error-guidance.js +1 -0
  232. package/dist/runtime/error.catalog.d.ts +49 -0
  233. package/dist/runtime/error.catalog.js +49 -0
  234. package/dist/runtime/execution-options.d.ts +18 -2
  235. package/dist/runtime/field-check.evaluator.d.ts +27 -0
  236. package/dist/runtime/field-check.evaluator.js +521 -0
  237. package/dist/runtime/field-compute.d.ts +65 -0
  238. package/dist/runtime/field-compute.js +1 -0
  239. package/dist/runtime/field-stage.plan.d.ts +15 -0
  240. package/dist/runtime/field-stage.plan.js +27 -0
  241. package/dist/runtime/field-stage.runtime.d.ts +37 -0
  242. package/dist/runtime/field-stage.runtime.js +147 -0
  243. package/dist/runtime/field-validator.runtime.d.ts +29 -0
  244. package/dist/runtime/field-validator.runtime.js +126 -0
  245. package/dist/runtime/framework-error-class.table.d.ts +33 -0
  246. package/dist/runtime/framework-error-class.table.js +25 -0
  247. package/dist/runtime/framework-error.d.ts +68 -8
  248. package/dist/runtime/framework-error.factory.d.ts +4 -0
  249. package/dist/runtime/framework-error.factory.js +15 -0
  250. package/dist/runtime/framework-error.js +32 -2
  251. package/dist/runtime/framework-warning.emitter.d.ts +6 -0
  252. package/dist/runtime/framework-warning.emitter.js +8 -0
  253. package/dist/runtime/interceptor-next.error.d.ts +5 -0
  254. package/dist/runtime/interceptor-next.error.js +6 -0
  255. package/dist/runtime/json-value.d.ts +11 -6
  256. package/dist/runtime/level-lifecycle.runtime.d.ts +26 -0
  257. package/dist/runtime/level-lifecycle.runtime.js +40 -0
  258. package/dist/runtime/misuse-reporter.d.ts +12 -12
  259. package/dist/runtime/mutation-planner.d.ts +20 -16
  260. package/dist/runtime/mutation-planner.js +48 -35
  261. package/dist/runtime/nested-write.walker.d.ts +32 -0
  262. package/dist/runtime/nested-write.walker.js +73 -0
  263. package/dist/runtime/operation-code.guidance.d.ts +6 -0
  264. package/dist/runtime/operation-code.guidance.js +167 -0
  265. package/dist/runtime/operation-request.d.ts +45 -11
  266. package/dist/runtime/operation-response.d.ts +17 -17
  267. package/dist/runtime/operation-runtime.d.ts +133 -27
  268. package/dist/runtime/operation-runtime.js +576 -84
  269. package/dist/runtime/outcome-mapper.d.ts +17 -5
  270. package/dist/runtime/outcome-mapper.js +23 -6
  271. package/dist/runtime/output-validator.d.ts +11 -11
  272. package/dist/runtime/output-validator.js +3 -3
  273. package/dist/runtime/owned-execution.registry.d.ts +16 -6
  274. package/dist/runtime/pipe-output.validator.d.ts +43 -18
  275. package/dist/runtime/pipe-output.validator.js +15 -13
  276. package/dist/runtime/pipeline.d.ts +221 -34
  277. package/dist/runtime/readonly-value.d.ts +47 -0
  278. package/dist/runtime/readonly-value.js +189 -0
  279. package/dist/runtime/referential-action.d.ts +1 -1
  280. package/dist/runtime/request-envelope.validator.d.ts +3 -3
  281. package/dist/runtime/request-envelope.validator.js +12 -7
  282. package/dist/runtime/request-id.d.ts +1 -1
  283. package/dist/runtime/request-state.d.ts +14 -0
  284. package/dist/runtime/request-state.js +1 -0
  285. package/dist/runtime/request-state.resolver.d.ts +12 -0
  286. package/dist/runtime/request-state.resolver.js +20 -0
  287. package/dist/runtime/scalar-runtime.d.ts +33 -25
  288. package/dist/runtime/scalar-runtime.js +105 -38
  289. package/dist/runtime/unsent-value.normalizer.d.ts +15 -0
  290. package/dist/runtime/unsent-value.normalizer.js +128 -0
  291. package/dist/transactions/deferred-operation.d.ts +35 -20
  292. package/dist/transactions/operation-arguments-encoder.d.ts +3 -3
  293. package/dist/transactions/operation-arguments-encoder.js +8 -3
  294. package/dist/transactions/operation-fingerprint.js +2 -2
  295. package/dist/transactions/sql-statement.d.ts +85 -0
  296. package/dist/transactions/sql-statement.js +47 -0
  297. package/dist/transactions/sql-statement.validator.d.ts +44 -0
  298. package/dist/transactions/sql-statement.validator.js +167 -0
  299. package/dist/transactions/transaction-envelope.d.ts +9 -8
  300. package/dist/transactions/transaction-integrity-error.d.ts +6 -6
  301. package/dist/transactions/transaction-integrity-error.js +7 -2
  302. package/dist/transactions/transaction-plan-assembler.d.ts +30 -13
  303. package/dist/transactions/transaction-plan-assembler.js +184 -32
  304. package/dist/transactions/transaction-plan-validator.d.ts +50 -25
  305. package/dist/transactions/transaction-plan-validator.js +96 -38
  306. package/dist/transactions/transaction-reference-collector.d.ts +5 -5
  307. package/dist/transactions/transaction-reference-resolver.d.ts +21 -3
  308. package/dist/transactions/transaction-reference-resolver.js +26 -14
  309. package/dist/transactions/transaction-reference-validator.d.ts +74 -4
  310. package/dist/transactions/transaction-reference-validator.js +129 -71
  311. package/dist/transactions/transaction-reference.d.ts +14 -14
  312. package/dist/transactions/transaction-reference.js +5 -0
  313. package/dist/transactions/transaction-request.d.ts +22 -9
  314. package/dist/transactions/transaction-response.d.ts +6 -6
  315. package/dist/transactions/transaction-runtime.d.ts +12 -8
  316. package/dist/transactions/transaction-runtime.js +218 -50
  317. package/dist/transactions/transaction-settlement.runtime.d.ts +52 -0
  318. package/dist/transactions/transaction-settlement.runtime.js +120 -0
  319. package/dist/transactions/transaction-slot-path-error.d.ts +4 -4
  320. package/dist/transactions/transaction-slot-path-error.js +4 -2
  321. package/dist/transactions/transaction-slot-writer.js +2 -2
  322. package/dist/transactions/transaction-stage.runtime.d.ts +83 -0
  323. package/dist/transactions/transaction-stage.runtime.js +126 -0
  324. package/package.json +11 -9
  325. package/src/runtime/field-check.evaluator.ts +720 -0
  326. package/dist/config/define-fields.d.ts +0 -42
  327. package/dist/config/define-fields.js +0 -3
  328. package/dist/config/framework-behaviors-config.d.ts +0 -30
  329. package/dist/config/framework-fields-config.d.ts +0 -42
  330. package/dist/config/framework-restrictions-config.d.ts +0 -142
  331. package/dist/json/avz.d.ts +0 -96
  332. package/dist/runtime/field-behavior.d.ts +0 -48
  333. /package/dist/{config/framework-behaviors-config.js → adapters/adapter-sql-statement.js} +0 -0
  334. /package/dist/{config/framework-fields-config.js → adapters/adapter-transaction-executor.interface.js} +0 -0
  335. /package/dist/{json → avz}/index.js +0 -0
  336. /package/dist/{json → avz}/json-shape.registry.js +0 -0
  337. /package/dist/config/{framework-restrictions-config.js → exact-framework-config.js} +0 -0
  338. /package/dist/{runtime/field-behavior.js → config/field-pipeline-config.js} +0 -0
@@ -0,0 +1,409 @@
1
+ import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
2
+ import { defaultProjectionFields } from "../runtime/computed-read-runtime.js";
3
+ import { valueAt, writeRecordPathsOf } from "../runtime/field-stage.runtime.js";
4
+ import { isRelationMutationDirective, RELATION_MUTATION_RECORD_SITES, } from "../runtime/mutation-planner.js";
5
+ import { operationFailure } from "../runtime/operation-response.js";
6
+ import { everyOf, noneOf, someOf, } from "./policy-condition.js";
7
+ import { fieldAccess, } from "./policy-session.js";
8
+ export function policyRefusal(message) {
9
+ return operationFailure("A4002", { message });
10
+ }
11
+ function policyActionsOf(family) {
12
+ switch (family) {
13
+ case "find":
14
+ return ["read"];
15
+ case "create":
16
+ return ["create"];
17
+ case "update":
18
+ return ["update"];
19
+ case "delete":
20
+ return ["delete"];
21
+ case "upsert":
22
+ return ["create", "update"];
23
+ }
24
+ }
25
+ function isRecord(value) {
26
+ return value !== null && typeof value === "object" && !Array.isArray(value);
27
+ }
28
+ class FieldWalk {
29
+ active;
30
+ used = new Map();
31
+ nodes = [];
32
+ constructor(active) {
33
+ this.active = active;
34
+ }
35
+ use(resource, field) {
36
+ let fields = this.used.get(resource);
37
+ if (fields === undefined) {
38
+ fields = new Set();
39
+ this.used.set(resource, fields);
40
+ }
41
+ fields.add(field);
42
+ }
43
+ node(resource, where) {
44
+ const node = { resource, where, fields: new Set() };
45
+ this.nodes.push(node);
46
+ return node;
47
+ }
48
+ relation(resource, name) {
49
+ const field = lookupField(lookupResource(this.active, resource), name);
50
+ return field?.kind === "relation"
51
+ ? field
52
+ : undefined;
53
+ }
54
+ defaults(resource) {
55
+ for (const field of defaultProjectionFields(this.active, resource)) {
56
+ this.use(resource, field);
57
+ }
58
+ }
59
+ projection(resource, args) {
60
+ if (Array.isArray(args.select)) {
61
+ for (const entry of args.select) {
62
+ if (entry === "$count") {
63
+ continue;
64
+ }
65
+ if (entry === "$defaults") {
66
+ this.defaults(resource);
67
+ continue;
68
+ }
69
+ this.projectionEntry(resource, entry);
70
+ }
71
+ return;
72
+ }
73
+ this.defaults(resource);
74
+ if (Array.isArray(args.include)) {
75
+ for (const entry of args.include) {
76
+ this.projectionEntry(resource, entry);
77
+ }
78
+ }
79
+ }
80
+ projectionEntry(resource, entry) {
81
+ if (typeof entry === "string") {
82
+ this.use(resource, entry);
83
+ const relation = this.relation(resource, entry);
84
+ if (relation !== undefined) {
85
+ this.defaults(relation.target);
86
+ }
87
+ return;
88
+ }
89
+ if (!isRecord(entry)) {
90
+ return;
91
+ }
92
+ for (const [name, value] of Object.entries(entry)) {
93
+ this.use(resource, name);
94
+ const relation = this.relation(resource, name);
95
+ if (relation !== undefined) {
96
+ this.level(relation.target, isRecord(value) ? value : {});
97
+ }
98
+ }
99
+ }
100
+ level(resource, args) {
101
+ const node = this.node(resource, args.where);
102
+ this.where(resource, args.where, node);
103
+ this.orderBy(resource, args.orderBy, node);
104
+ if (isRecord(args.cursor)) {
105
+ for (const field of Object.keys(args.cursor)) {
106
+ this.use(resource, field);
107
+ node.fields.add(field);
108
+ }
109
+ }
110
+ this.projection(resource, args);
111
+ }
112
+ where(resource, where, node) {
113
+ if (!isRecord(where)) {
114
+ return;
115
+ }
116
+ for (const [key, value] of Object.entries(where)) {
117
+ if (key === "AND" || key === "OR" || key === "NOT") {
118
+ for (const entry of Array.isArray(value) ? value : [value]) {
119
+ this.where(resource, entry, node);
120
+ }
121
+ continue;
122
+ }
123
+ this.use(resource, key);
124
+ node.fields.add(key);
125
+ const relation = this.relation(resource, key);
126
+ if (relation === undefined || !isRecord(value)) {
127
+ continue;
128
+ }
129
+ const inner = relation.cardinality === "many"
130
+ ? ["some", "every", "none"].flatMap((operator) => isRecord(value[operator]) ? [value[operator]] : [])
131
+ : [value];
132
+ for (const filter of inner) {
133
+ this.where(relation.target, filter, this.node(relation.target, filter));
134
+ }
135
+ }
136
+ }
137
+ orderBy(resource, orderBy, node) {
138
+ for (const entry of Array.isArray(orderBy) ? orderBy : [orderBy]) {
139
+ if (!isRecord(entry)) {
140
+ continue;
141
+ }
142
+ for (const [key, value] of Object.entries(entry)) {
143
+ this.use(resource, key);
144
+ node.fields.add(key);
145
+ const relation = this.relation(resource, key);
146
+ if (relation?.cardinality === "one" && isRecord(value)) {
147
+ this.orderBy(relation.target, value, this.node(relation.target, undefined));
148
+ }
149
+ }
150
+ }
151
+ }
152
+ }
153
+ function projects(request) {
154
+ return request.family === "upsert" || request.variant !== "count";
155
+ }
156
+ function namedFields(active, request, args) {
157
+ const walk = new FieldWalk(active);
158
+ const level = isRecord(args) ? args : {};
159
+ const root = walk.node(request.resource, level.where);
160
+ walk.where(request.resource, level.where, root);
161
+ walk.orderBy(request.resource, level.orderBy, root);
162
+ if (isRecord(level.cursor)) {
163
+ for (const field of Object.keys(level.cursor)) {
164
+ walk.use(request.resource, field);
165
+ root.fields.add(field);
166
+ }
167
+ }
168
+ if (projects(request)) {
169
+ walk.projection(request.resource, level);
170
+ }
171
+ return walk;
172
+ }
173
+ function withoutConditional(core, resource, where, conditional) {
174
+ if (!isRecord(where)) {
175
+ return true;
176
+ }
177
+ const conditions = [];
178
+ for (const [key, value] of Object.entries(where)) {
179
+ if (key === "AND") {
180
+ for (const entry of Array.isArray(value) ? value : [value]) {
181
+ conditions.push(withoutConditional(core, resource, entry, conditional));
182
+ }
183
+ continue;
184
+ }
185
+ if (key === "OR") {
186
+ conditions.push(Array.isArray(value)
187
+ ? someOf(value.map((entry) => withoutConditional(core, resource, entry, conditional)))
188
+ : true);
189
+ continue;
190
+ }
191
+ conditions.push(holdsConditional(core, resource, { [key]: value }, conditional)
192
+ ? true
193
+ : { [key]: value });
194
+ }
195
+ return everyOf(conditions);
196
+ }
197
+ function holdsConditional(core, resource, where, conditional) {
198
+ if (!isRecord(where)) {
199
+ return false;
200
+ }
201
+ for (const [key, value] of Object.entries(where)) {
202
+ if (key === "AND" || key === "OR" || key === "NOT") {
203
+ if ((Array.isArray(value) ? value : [value]).some((entry) => holdsConditional(core, resource, entry, conditional))) {
204
+ return true;
205
+ }
206
+ continue;
207
+ }
208
+ if (conditional.get(resource)?.has(key) === true) {
209
+ return true;
210
+ }
211
+ const field = lookupField(lookupResource(core, resource), key);
212
+ if (field?.kind !== "relation" || !isRecord(value)) {
213
+ continue;
214
+ }
215
+ const inner = field.cardinality === "many"
216
+ ? [value.some, value.every, value.none]
217
+ : [value];
218
+ if (inner.some((filter) => holdsConditional(core, field.target, filter, conditional))) {
219
+ return true;
220
+ }
221
+ }
222
+ return false;
223
+ }
224
+ export async function admitByPolicy(session, core, active, request, args) {
225
+ for (const action of policyActionsOf(request.family)) {
226
+ if (!(await session.can(request.resource, action))) {
227
+ return policyRefusal(`The policy does not allow ${action} on Resource "${request.resource}".`);
228
+ }
229
+ }
230
+ const named = namedFields(active, request, args);
231
+ const resources = new Set([
232
+ ...named.used.keys(),
233
+ ...named.nodes.map((node) => node.resource),
234
+ ]);
235
+ const answers = new Map(await Promise.all([...resources].map(async (resource) => [
236
+ resource,
237
+ await session.fields(resource, { access: "read" }),
238
+ ])));
239
+ const conditional = new Map();
240
+ for (const [resource, fields] of named.used) {
241
+ const answer = answers.get(resource);
242
+ for (const field of fields) {
243
+ const access = fieldAccess(core, resource, answer, field);
244
+ if (access === "never") {
245
+ return policyRefusal(`The policy does not allow reading field "${field}" of Resource "${resource}".`);
246
+ }
247
+ if (access !== "always") {
248
+ let set = conditional.get(resource);
249
+ if (set === undefined) {
250
+ set = new Set();
251
+ conditional.set(resource, set);
252
+ }
253
+ set.add(field);
254
+ }
255
+ }
256
+ }
257
+ for (const node of named.nodes) {
258
+ const answer = answers.get(node.resource);
259
+ const grants = [];
260
+ const names = [];
261
+ for (const field of node.fields) {
262
+ const access = fieldAccess(core, node.resource, answer, field);
263
+ if (access !== "always" && access !== "never") {
264
+ grants.push(access);
265
+ names.push(field);
266
+ }
267
+ }
268
+ if (grants.length === 0) {
269
+ continue;
270
+ }
271
+ const reach = everyOf([
272
+ await session.scope(node.resource, "read"),
273
+ withoutConditional(core, node.resource, node.where, conditional),
274
+ someOf(grants.map(noneOf)),
275
+ ]);
276
+ if (reach === false) {
277
+ continue;
278
+ }
279
+ if (await session.exists(node.resource, reach === true ? {} : reach)) {
280
+ return policyRefusal(`The policy does not allow filtering, sorting or paging by ${names
281
+ .map((name) => `"${name}"`)
282
+ .join(", ")} of Resource "${node.resource}" on every row this request reaches.`);
283
+ }
284
+ }
285
+ return undefined;
286
+ }
287
+ function sentRecords(request, args) {
288
+ return writeRecordPathsOf(request.family, args).flatMap((path) => {
289
+ const record = valueAt(args, path);
290
+ return isRecord(record)
291
+ ? [
292
+ {
293
+ record,
294
+ stage: request.family === "create" || path[0] === "create"
295
+ ? "create"
296
+ : "update",
297
+ },
298
+ ]
299
+ : [];
300
+ });
301
+ }
302
+ async function admitNestedRecords(session, core, resource, directives) {
303
+ if (!isRecord(directives)) {
304
+ return undefined;
305
+ }
306
+ const owner = lookupResource(core, resource);
307
+ for (const [directive, operand] of Object.entries(directives)) {
308
+ if (!isRelationMutationDirective(directive)) {
309
+ continue;
310
+ }
311
+ for (const entry of Array.isArray(operand) ? operand : [operand]) {
312
+ for (const site of RELATION_MUTATION_RECORD_SITES[directive]) {
313
+ const record = site.key === undefined ? entry : valueAt(entry, [site.key]);
314
+ if (!isRecord(record)) {
315
+ continue;
316
+ }
317
+ const fields = await session.fields(resource, {
318
+ access: "write",
319
+ action: site.stage,
320
+ });
321
+ for (const [field, value] of Object.entries(record)) {
322
+ if (value === undefined) {
323
+ continue;
324
+ }
325
+ const relation = lookupField(owner, field);
326
+ const written = relation?.kind === "relation"
327
+ ? [field, ...(relation.referenceFields ?? [])]
328
+ : [field];
329
+ for (const member of written) {
330
+ if (fieldAccess(core, resource, fields, member) === "never") {
331
+ return policyRefusal(`The policy does not allow writing field "${member}" of Resource "${resource}".`);
332
+ }
333
+ }
334
+ if (relation?.kind === "relation") {
335
+ const refused = await admitNestedRecords(session, core, relation.target, value);
336
+ if (refused !== undefined) {
337
+ return refused;
338
+ }
339
+ }
340
+ }
341
+ }
342
+ }
343
+ }
344
+ return undefined;
345
+ }
346
+ export async function admitWriteByPolicy(session, core, request, args) {
347
+ const records = sentRecords(request, args);
348
+ if (records.length === 0) {
349
+ return undefined;
350
+ }
351
+ const resource = request.resource;
352
+ const grants = [];
353
+ const names = [];
354
+ for (const { record, stage } of records) {
355
+ const fields = await session.fields(resource, {
356
+ access: "write",
357
+ action: stage,
358
+ });
359
+ for (const [field, value] of Object.entries(record)) {
360
+ if (value === undefined) {
361
+ continue;
362
+ }
363
+ const relation = lookupField(lookupResource(core, resource), field);
364
+ const written = relation?.kind === "relation"
365
+ ? [field, ...(relation.referenceFields ?? [])]
366
+ : [field];
367
+ if (relation?.kind === "relation") {
368
+ const refused = await admitNestedRecords(session, core, relation.target, value);
369
+ if (refused !== undefined) {
370
+ return refused;
371
+ }
372
+ }
373
+ for (const member of written) {
374
+ const access = fieldAccess(core, resource, fields, member);
375
+ if (access === "never") {
376
+ return policyRefusal(`The policy does not allow writing field "${member}" of Resource "${resource}".`);
377
+ }
378
+ if (access === "always") {
379
+ continue;
380
+ }
381
+ if (stage === "create") {
382
+ session.grantCreate(member, access);
383
+ continue;
384
+ }
385
+ if (!names.includes(member)) {
386
+ grants.push(access);
387
+ names.push(member);
388
+ }
389
+ }
390
+ }
391
+ }
392
+ if (grants.length === 0) {
393
+ return undefined;
394
+ }
395
+ const level = isRecord(args) ? args : {};
396
+ const reach = everyOf([
397
+ await session.scope(resource, "update"),
398
+ await session.scope(resource, "read"),
399
+ isRecord(level.where) ? level.where : true,
400
+ someOf(grants.map(noneOf)),
401
+ ]);
402
+ if (reach !== false &&
403
+ (await session.exists(resource, reach === true ? {} : reach))) {
404
+ return policyRefusal(`The policy does not allow writing ${names
405
+ .map((name) => `"${name}"`)
406
+ .join(", ")} of Resource "${resource}" on every row this request reaches.`);
407
+ }
408
+ return undefined;
409
+ }
@@ -0,0 +1,79 @@
1
+ import type { AvAdapterModel } from "../adapters/adapter-model.js";
2
+ import type { AvContractCore } from "../contracts/contract.js";
3
+ import type { AvOperationFailureResponse } from "../runtime/operation-response.js";
4
+ import { type PolicyCondition } from "./policy-condition.js";
5
+ import type { PolicySession } from "./policy-session.js";
6
+ type Row = Readonly<Record<string, unknown>>;
7
+ /** The scalar fields of `resource` an after check reads the changed rows by. */
8
+ export declare function rowFieldsOf(core: AvContractCore, resource: string): readonly string[];
9
+ /** What the rows a write changed are checked for, after it. */
10
+ export type AfterWrite = {
11
+ /** The read scope a moved-out row has left, when the data can move one. */
12
+ readonly moveOut: PolicyCondition | undefined;
13
+ /**
14
+ * Whether the after check runs: in a transaction, always; without one, the
15
+ * update is one `afterChecks` lists, and it is refused before it runs.
16
+ */
17
+ readonly after: boolean;
18
+ /**
19
+ * Whether the after check also hands `can` each changed row as it was
20
+ * before the update (`before`): only for an update `afterChecks` lists.
21
+ */
22
+ readonly before: boolean;
23
+ };
24
+ /** The checks after an update whose data is `data`; `undefined` when none applies. */
25
+ export declare function afterUpdate(session: PolicySession, core: AvContractCore, model: AvAdapterModel, resource: string, data: unknown, inTransaction: boolean): Promise<AfterWrite | undefined>;
26
+ /** The fields the after check asks `can` about: the identifier, then every scalar field. */
27
+ export declare function afterCheckFields(core: AvContractCore, model: AvAdapterModel, resource: string): readonly string[];
28
+ /**
29
+ * The rows an update will change as they were before it, and, where the engine
30
+ * answers `grantsOf`, the grants that covered each then, index for index.
31
+ */
32
+ export type RowsBefore = {
33
+ readonly rows: readonly Row[];
34
+ /** `grantsOf`'s answer about each of `rows`; absent when it was not asked. */
35
+ readonly grants?: readonly (readonly string[])[];
36
+ };
37
+ /**
38
+ * The rows an update will change, as they are now, carrying
39
+ * {@link afterCheckFields}: `reach` read inside the update's transaction, for
40
+ * the after check's `before`, with the grants that cover each
41
+ * ({@link coveredBefore}). None when the update is none `afterChecks` lists
42
+ * or it reaches no row. A listed update of a Resource whose Core Contract
43
+ * offers no `find.many` is refused before it runs ({@link beforeUnreadable}).
44
+ */
45
+ export declare function rowsBefore(session: PolicySession, core: AvContractCore, model: AvAdapterModel, resource: string, checks: AfterWrite | undefined, reach: PolicyCondition): Promise<RowsBefore>;
46
+ /**
47
+ * `rows`, read ahead of the write, with the grants the engine says cover each
48
+ * (`grantsOf`, asked now, before the write): for an update `afterChecks` lists
49
+ * whose engine answers that question; otherwise `rows` alone.
50
+ */
51
+ export declare function coveredBefore(session: PolicySession, core: AvContractCore, model: AvAdapterModel, resource: string, checks: AfterWrite | undefined, rows: readonly Row[]): Promise<RowsBefore>;
52
+ /**
53
+ * The refusal of a listed update whose rows cannot be read as they were: the
54
+ * Core Contract offers no `find.many` (as an update with no identifier to find
55
+ * the changed rows by is refused), or `undefined`.
56
+ */
57
+ export declare function beforeUnreadable(core: AvContractCore, resource: string, checks: AfterWrite | undefined): AvOperationFailureResponse | undefined;
58
+ /**
59
+ * The fields to read the rows an update will change by, before it: the
60
+ * identifier, and when the after check hands `can` the row as it was, every
61
+ * field it asks about, so the same read is that `before`.
62
+ */
63
+ export declare function beforeFields(core: AvContractCore, model: AvAdapterModel, resource: string, checks: AfterWrite | undefined): readonly string[];
64
+ /**
65
+ * The condition that `rows` still hold the values they were read with: each
66
+ * row's value of every field the after check reads that the adapter compares
67
+ * by `equals`.
68
+ */
69
+ export declare function unchangedSince(core: AvContractCore, model: AvAdapterModel, resource: string, rows: readonly Row[]): PolicyCondition;
70
+ /**
71
+ * The changed rows held to the move-out and after checks: the refusal, or
72
+ * `undefined`. `returned` says that `changed` are the rows the write answered,
73
+ * carrying {@link afterCheckFields}; `before` are the rows as they were before
74
+ * the write, carrying them too, each handed to `can` with the changed row of
75
+ * its identifier where `checks.before` says so, with the grants that covered
76
+ * it then where the engine answered them.
77
+ */
78
+ export declare function checkChanged(session: PolicySession, core: AvContractCore, model: AvAdapterModel, resource: string, checks: AfterWrite, changed: readonly Row[], returned: boolean, before: RowsBefore): Promise<AvOperationFailureResponse | undefined>;
79
+ export {};
@@ -0,0 +1,208 @@
1
+ import { adapterPrimaryIdentifier } from "../adapters/adapter-identifier.js";
2
+ import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
3
+ import { frozenCopy } from "../runtime/readonly-value.js";
4
+ import { policyRefusal } from "./policy-admission.js";
5
+ import { allOf, everyOf, someOf, whereOf, } from "./policy-condition.js";
6
+ import { identifiedRows } from "./policy-read.runtime.js";
7
+ function isRecord(value) {
8
+ return value !== null && typeof value === "object" && !Array.isArray(value);
9
+ }
10
+ const fieldLists = new WeakMap();
11
+ function listOf(core, key, make) {
12
+ let lists = fieldLists.get(core);
13
+ if (lists === undefined) {
14
+ lists = new Map();
15
+ fieldLists.set(core, lists);
16
+ }
17
+ let fields = lists.get(key);
18
+ if (fields === undefined) {
19
+ fields = make();
20
+ lists.set(key, fields);
21
+ }
22
+ return fields;
23
+ }
24
+ export function rowFieldsOf(core, resource) {
25
+ return listOf(core, `scalar\u0000${resource}`, () => Object.entries(lookupResource(core, resource)?.fields ?? {}).flatMap(([name, field]) => field?.kind === "scalar" && field.capabilities.select ? [name] : []));
26
+ }
27
+ function touches(core, model, resource, scope, data) {
28
+ const owner = lookupResource(core, resource);
29
+ const read = new Set();
30
+ const walk = (where) => {
31
+ if (!isRecord(where)) {
32
+ return;
33
+ }
34
+ for (const [key, value] of Object.entries(where)) {
35
+ if (key === "AND" || key === "OR" || key === "NOT") {
36
+ for (const entry of Array.isArray(value) ? value : [value]) {
37
+ walk(entry);
38
+ }
39
+ continue;
40
+ }
41
+ read.add(key);
42
+ const field = lookupField(owner, key);
43
+ if (field?.kind !== "relation") {
44
+ continue;
45
+ }
46
+ if (field.cardinality === "one" && field.referenceFields !== undefined) {
47
+ for (const reference of field.referenceFields) {
48
+ read.add(reference);
49
+ }
50
+ }
51
+ else {
52
+ for (const member of adapterPrimaryIdentifier(model, resource)) {
53
+ read.add(member);
54
+ }
55
+ }
56
+ }
57
+ };
58
+ walk(scope);
59
+ return Object.entries(data).some(([name, value]) => {
60
+ if (value === undefined) {
61
+ return false;
62
+ }
63
+ const field = lookupField(owner, name);
64
+ const written = field?.kind === "relation"
65
+ ? [name, ...(field.referenceFields ?? [])]
66
+ : [name];
67
+ return written.some((member) => read.has(member));
68
+ });
69
+ }
70
+ export async function afterUpdate(session, core, model, resource, data, inTransaction) {
71
+ const read = await session.scope(resource, "read");
72
+ const moveOut = read !== true &&
73
+ isRecord(data) &&
74
+ touches(core, model, resource, read, data)
75
+ ? read
76
+ : undefined;
77
+ const listed = session.checksAfter(resource, "update");
78
+ const after = inTransaction || listed;
79
+ return moveOut === undefined && !after
80
+ ? undefined
81
+ : { moveOut, after, before: after && listed };
82
+ }
83
+ function identityKey(identifier, row) {
84
+ return JSON.stringify(identifier.map((member) => row[member]), (_key, value) => (typeof value === "bigint" ? `#bigint(${value})` : value));
85
+ }
86
+ export function afterCheckFields(core, model, resource) {
87
+ return listOf(core, `after\u0000${resource}`, () => [
88
+ ...new Set([
89
+ ...adapterPrimaryIdentifier(model, resource),
90
+ ...rowFieldsOf(core, resource),
91
+ ]),
92
+ ]);
93
+ }
94
+ export async function rowsBefore(session, core, model, resource, checks, reach) {
95
+ if (checks?.before !== true || reach === false) {
96
+ return { rows: [] };
97
+ }
98
+ const rows = await session.rows(resource, whereOf(reach), afterCheckFields(core, model, resource));
99
+ return coveredBefore(session, core, model, resource, checks, rows);
100
+ }
101
+ export async function coveredBefore(session, core, model, resource, checks, rows) {
102
+ if (checks?.before !== true || rows.length === 0) {
103
+ return { rows };
104
+ }
105
+ const fields = afterCheckFields(core, model, resource);
106
+ const grants = await Promise.all(rows.map((row) => session.grants(resource, frozenCopy(rowOfFields(row, fields)))));
107
+ return grants.every((answer) => answer !== undefined)
108
+ ? { rows, grants }
109
+ : { rows };
110
+ }
111
+ export function beforeUnreadable(core, resource, checks) {
112
+ return checks?.before === true &&
113
+ lookupResource(core, resource)?.operations.find?.many === undefined
114
+ ? policyRefusal(`The policy checks this update on Resource "${resource}" against the rows as they were before it, and the Resource offers no find.many to read them by.`)
115
+ : undefined;
116
+ }
117
+ export function beforeFields(core, model, resource, checks) {
118
+ return checks?.before === true
119
+ ? afterCheckFields(core, model, resource)
120
+ : adapterPrimaryIdentifier(model, resource);
121
+ }
122
+ function pairedBefore(identifier, asked, before) {
123
+ if (asked.length === 1 && before.length === 1) {
124
+ return [0];
125
+ }
126
+ const earlier = new Map(before.map((row, index) => [identityKey(identifier, row), index]));
127
+ const paired = asked.map((row) => earlier.get(identityKey(identifier, row)));
128
+ return paired.every((index) => index !== undefined) ? paired : undefined;
129
+ }
130
+ export function unchangedSince(core, model, resource, rows) {
131
+ const fields = listOf(core, `cas\u0000${resource}`, () => {
132
+ const owner = Object.hasOwn(model.resources, resource)
133
+ ? model.resources[resource]
134
+ : undefined;
135
+ return afterCheckFields(core, model, resource).filter((name) => {
136
+ const field = owner !== undefined && Object.hasOwn(owner.fields, name)
137
+ ? owner.fields[name]
138
+ : undefined;
139
+ return (field?.kind === "scalar" &&
140
+ field.capabilities.filter?.includes("equals") === true);
141
+ });
142
+ });
143
+ return someOf(rows.map((row) => everyOf(fields
144
+ .filter((name) => Object.hasOwn(row, name))
145
+ .map((name) => ({ [name]: { equals: row[name] } })))));
146
+ }
147
+ function rowOfFields(row, fields) {
148
+ const picked = {};
149
+ for (const field of fields) {
150
+ if (Object.hasOwn(row, field)) {
151
+ picked[field] = row[field];
152
+ }
153
+ }
154
+ return picked;
155
+ }
156
+ export async function checkChanged(session, core, model, resource, checks, changed, returned, before) {
157
+ if (changed.length === 0) {
158
+ return undefined;
159
+ }
160
+ const fresh = session.renewed();
161
+ const identifier = adapterPrimaryIdentifier(model, resource);
162
+ const these = identifiedRows(core, identifier, resource, changed);
163
+ if (these === false) {
164
+ return undefined;
165
+ }
166
+ const kept = checks.moveOut === undefined ? these : allOf(these, checks.moveOut);
167
+ const reread = checks.moveOut !== undefined || !returned;
168
+ const fields = afterCheckFields(core, model, resource);
169
+ const rows = kept === false || !reread
170
+ ? []
171
+ : await fresh.rows(resource, whereOf(kept), checks.after && !returned ? fields : [...identifier]);
172
+ if (checks.moveOut !== undefined) {
173
+ const found = new Set(rows.map((row) => identityKey(identifier, row)));
174
+ if (changed.some((row) => !found.has(identityKey(identifier, row)))) {
175
+ return policyRefusal(`The policy does not allow this change: it moves a row of Resource "${resource}" out of what the caller may read.`);
176
+ }
177
+ }
178
+ if (checks.after) {
179
+ if (!returned && rows.length < changed.length) {
180
+ return policyRefusal(`The policy checks the rows this update on Resource "${resource}" changes after it runs, and a changed row cannot be found again by its identifier (the update changes it), so it cannot be checked.`);
181
+ }
182
+ const asked = returned
183
+ ? changed.map((row) => rowOfFields(row, fields))
184
+ : rows;
185
+ const paired = checks.before
186
+ ? pairedBefore(identifier, asked, before.rows)
187
+ : [];
188
+ if (paired === undefined) {
189
+ return policyRefusal(`The policy checks a changed row of Resource "${resource}" against the row as it was before the update, and a changed row has no such row to pair with (the update changes its identifier).`);
190
+ }
191
+ const answers = await Promise.all(asked.map((row, index) => {
192
+ const at = paired[index];
193
+ const was = at === undefined ? undefined : before.rows[at];
194
+ const grants = at === undefined ? undefined : before.grants?.[at];
195
+ const prior = was === undefined
196
+ ? undefined
197
+ : {
198
+ row: frozenCopy(rowOfFields(was, fields)),
199
+ ...(grants === undefined ? {} : { grants }),
200
+ };
201
+ return fresh.can(resource, "update", frozenCopy(row), prior);
202
+ }));
203
+ if (answers.includes(false)) {
204
+ return policyRefusal(`The policy does not allow this change: a changed row of Resource "${resource}" does not meet the condition its update grant sets.`);
205
+ }
206
+ }
207
+ return undefined;
208
+ }