@aventara/core 1.0.0-rc.1 → 1.0.0-rc.11

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 (261) hide show
  1. package/CHANGELOG.md +1022 -16
  2. package/ERRORS.md +142 -0
  3. package/README.md +330 -16
  4. package/dist/adapters/adapter-discovery-diagnostic.guidance.d.ts +6 -0
  5. package/dist/adapters/adapter-discovery-diagnostic.guidance.js +7 -0
  6. package/dist/adapters/adapter-field.d.ts +22 -0
  7. package/dist/adapters/adapter-identifier.d.ts +9 -0
  8. package/dist/adapters/adapter-identifier.js +6 -1
  9. package/dist/adapters/adapter-model.d.ts +9 -0
  10. package/dist/adapters/adapter-operation.d.ts +18 -3
  11. package/dist/adapters/adapter-resource.d.ts +5 -0
  12. package/dist/adapters/adapter.interface.d.ts +10 -0
  13. package/dist/avz/avz.d.ts +1 -1
  14. package/dist/avz/json-shape.registry.d.ts +2 -1
  15. package/dist/compiler/contract-compiler-diagnostic.guidance.d.ts +6 -0
  16. package/dist/compiler/contract-compiler-diagnostic.guidance.js +97 -0
  17. package/dist/compiler/contract.compiler.d.ts +6 -0
  18. package/dist/compiler/contract.compiler.js +281 -31
  19. package/dist/compiler/create-requirements.projector.js +1 -3
  20. package/dist/compiler/field-validator.compiler.js +1 -3
  21. package/dist/compiler/managed-elsewhere.projector.d.ts +52 -0
  22. package/dist/compiler/managed-elsewhere.projector.js +179 -0
  23. package/dist/compiler/managed-elsewhere.validator.d.ts +9 -0
  24. package/dist/compiler/managed-elsewhere.validator.js +74 -0
  25. package/dist/config/cache-stage.factory.d.ts +24 -0
  26. package/dist/config/cache-stage.factory.js +3 -0
  27. package/dist/config/computed-fields.d.ts +62 -0
  28. package/dist/config/computed-fields.js +73 -0
  29. package/dist/config/config-diagnostic.guidance.d.ts +6 -0
  30. package/dist/config/config-diagnostic.guidance.js +32 -0
  31. package/dist/config/config.resolver.d.ts +24 -0
  32. package/dist/config/config.resolver.js +66 -7
  33. package/dist/config/config.validator.js +283 -21
  34. package/dist/config/define-resource.d.ts +1 -5
  35. package/dist/config/entrypoint.normalizer.d.ts +3 -2
  36. package/dist/config/exact-framework-config.d.ts +73 -22
  37. package/dist/config/field-compute.resolver.d.ts +2 -0
  38. package/dist/config/field-compute.resolver.js +9 -3
  39. package/dist/config/framework-config.d.ts +53 -15
  40. package/dist/config/framework-resources-config.d.ts +25 -14
  41. package/dist/config/pipeline-config.d.ts +20 -4
  42. package/dist/config/pipeline-tree.resolver.d.ts +2 -2
  43. package/dist/config/pipeline-tree.resolver.js +2 -3
  44. package/dist/contracts/access-route.rules.d.ts +10 -0
  45. package/dist/contracts/access-route.rules.js +45 -0
  46. package/dist/contracts/canonical-json.writer.d.ts +7 -0
  47. package/dist/contracts/canonical-json.writer.js +55 -0
  48. package/dist/contracts/capability.d.ts +26 -1
  49. package/dist/contracts/capability.js +20 -0
  50. package/dist/contracts/client-contract-structure.validator.d.ts +365 -89
  51. package/dist/contracts/client-contract-structure.validator.js +49 -2
  52. package/dist/contracts/contract-access.d.ts +29 -0
  53. package/dist/contracts/contract-access.js +1 -0
  54. package/dist/contracts/contract-canonicalizer.d.ts +9 -1
  55. package/dist/contracts/contract-canonicalizer.js +35 -4
  56. package/dist/contracts/contract-hash.d.ts +0 -1
  57. package/dist/contracts/contract-hash.js +1 -1
  58. package/dist/contracts/contract-limits.d.ts +6 -0
  59. package/dist/contracts/contract-limits.js +2 -0
  60. package/dist/contracts/contract.d.ts +42 -1
  61. package/dist/contracts/field.d.ts +58 -1
  62. package/dist/contracts/json-schema.projector.d.ts +11 -0
  63. package/dist/contracts/json-schema.projector.js +76 -0
  64. package/dist/contracts/scalar-value-type.d.ts +3 -1
  65. package/dist/framework/access-facade.d.ts +116 -0
  66. package/dist/framework/access-facade.js +118 -0
  67. package/dist/framework/contract-view.d.ts +120 -16
  68. package/dist/framework/framework-construction.error.d.ts +14 -4
  69. package/dist/framework/framework-construction.error.js +18 -2
  70. package/dist/framework/framework.d.ts +26 -5
  71. package/dist/framework/framework.factory.d.ts +1 -1
  72. package/dist/framework/framework.factory.js +145 -24
  73. package/dist/framework/operation-facade.d.ts +141 -13
  74. package/dist/framework/operation-facade.js +9 -1
  75. package/dist/framework/operation-set.runner.d.ts +47 -0
  76. package/dist/framework/operation-set.runner.js +168 -0
  77. package/dist/index.d.ts +19 -5
  78. package/dist/index.js +3 -0
  79. package/dist/internal.d.ts +9 -2
  80. package/dist/internal.js +8 -0
  81. package/dist/operations/database-refusal.d.ts +51 -0
  82. package/dist/operations/database-refusal.js +45 -0
  83. package/dist/operations/operation-arguments.d.ts +148 -60
  84. package/dist/operations/operation-call.d.ts +9 -5
  85. package/dist/operations/operation-result.d.ts +6 -4
  86. package/dist/operations/operation-validator-core.js +160 -39
  87. package/dist/operations/operation-validator.d.ts +1 -1
  88. package/dist/operations/structural-limits.walker.d.ts +1 -1
  89. package/dist/operations/structural-limits.walker.js +1 -3
  90. package/dist/operations/validation-code.guidance.d.ts +6 -0
  91. package/dist/operations/validation-code.guidance.js +90 -0
  92. package/dist/operations/write-scope.resolver.d.ts +4 -0
  93. package/dist/operations/write-scope.resolver.js +2 -0
  94. package/dist/policy/access-provider.interface.d.ts +91 -0
  95. package/dist/policy/access-provider.interface.js +1 -0
  96. package/dist/policy/access-provider.registry.d.ts +17 -0
  97. package/dist/policy/access-provider.registry.js +15 -0
  98. package/dist/policy/access-question.d.ts +20 -0
  99. package/dist/policy/access-question.js +1 -0
  100. package/dist/policy/access-question.registry.d.ts +5 -0
  101. package/dist/policy/access-question.registry.js +19 -0
  102. package/dist/policy/access-question.runtime.d.ts +36 -0
  103. package/dist/policy/access-question.runtime.js +295 -0
  104. package/dist/policy/access-verb.table.d.ts +56 -0
  105. package/dist/policy/access-verb.table.js +21 -0
  106. package/dist/policy/access-verbs.d.ts +46 -0
  107. package/dist/policy/access-verbs.js +1 -0
  108. package/dist/policy/policy-admission.d.ts +20 -0
  109. package/dist/policy/policy-admission.js +407 -0
  110. package/dist/policy/policy-after-update.runtime.d.ts +79 -0
  111. package/dist/policy/policy-after-update.runtime.js +206 -0
  112. package/dist/policy/policy-condition.d.ts +15 -0
  113. package/dist/policy/policy-condition.js +36 -0
  114. package/dist/policy/policy-engine.d.ts +205 -0
  115. package/dist/policy/policy-engine.js +1 -0
  116. package/dist/policy/policy-named-rows.d.ts +18 -0
  117. package/dist/policy/policy-named-rows.js +51 -0
  118. package/dist/policy/policy-nested-write.runtime.d.ts +44 -0
  119. package/dist/policy/policy-nested-write.runtime.js +536 -0
  120. package/dist/policy/policy-read.runtime.d.ts +81 -0
  121. package/dist/policy/policy-read.runtime.js +501 -0
  122. package/dist/policy/policy-reference-write.runtime.d.ts +51 -0
  123. package/dist/policy/policy-reference-write.runtime.js +336 -0
  124. package/dist/policy/policy-request.d.ts +72 -0
  125. package/dist/policy/policy-request.js +115 -0
  126. package/dist/policy/policy-session.d.ts +106 -0
  127. package/dist/policy/policy-session.js +293 -0
  128. package/dist/policy/policy-startup.validator.d.ts +10 -0
  129. package/dist/policy/policy-startup.validator.js +112 -0
  130. package/dist/policy/policy-write.runtime.d.ts +39 -0
  131. package/dist/policy/policy-write.runtime.js +530 -0
  132. package/dist/prebuilt/core-version.generated.d.ts +2 -0
  133. package/dist/prebuilt/core-version.generated.js +1 -0
  134. package/dist/prebuilt/prebuilt-changes.d.ts +29 -0
  135. package/dist/prebuilt/prebuilt-changes.js +63 -0
  136. package/dist/prebuilt/prebuilt-contracts.d.ts +40 -0
  137. package/dist/prebuilt/prebuilt-contracts.emitter.d.ts +16 -0
  138. package/dist/prebuilt/prebuilt-contracts.emitter.js +50 -0
  139. package/dist/prebuilt/prebuilt-contracts.js +1 -0
  140. package/dist/prebuilt/prebuilt-contracts.loader.d.ts +19 -0
  141. package/dist/prebuilt/prebuilt-contracts.loader.js +75 -0
  142. package/dist/prebuilt/prebuilt-inputs.fingerprint.d.ts +16 -0
  143. package/dist/prebuilt/prebuilt-inputs.fingerprint.js +227 -0
  144. package/dist/protocol/absent-route.responder.d.ts +49 -14
  145. package/dist/protocol/absent-route.responder.js +92 -15
  146. package/dist/protocol/contract-document.responder.d.ts +9 -1
  147. package/dist/protocol/contract-document.responder.js +3 -2
  148. package/dist/protocol/http-request.decoder.d.ts +52 -3
  149. package/dist/protocol/http-request.decoder.js +45 -6
  150. package/dist/protocol/http-request.reader.d.ts +10 -3
  151. package/dist/protocol/http-request.reader.js +12 -2
  152. package/dist/protocol/http-response.encoder.d.ts +12 -0
  153. package/dist/protocol/http-response.encoder.js +17 -3
  154. package/dist/protocol/operation-response.guard.js +1 -3
  155. package/dist/protocol/operation-set.decoder.d.ts +48 -0
  156. package/dist/protocol/operation-set.decoder.js +185 -0
  157. package/dist/protocol/operation-set.handler.d.ts +27 -0
  158. package/dist/protocol/operation-set.handler.js +179 -0
  159. package/dist/protocol/protocol-headers.d.ts +12 -3
  160. package/dist/protocol/protocol-headers.js +12 -5
  161. package/dist/protocol/protocol-surface.d.ts +16 -3
  162. package/dist/protocol/protocol-surface.js +41 -1
  163. package/dist/protocol/protocol.binder.d.ts +52 -1
  164. package/dist/protocol/protocol.binder.js +6 -1
  165. package/dist/protocol/protocol.handler.d.ts +45 -1
  166. package/dist/protocol/protocol.handler.js +164 -19
  167. package/dist/protocol/read-cache.responder.d.ts +26 -0
  168. package/dist/protocol/read-cache.responder.js +47 -0
  169. package/dist/protocol/read-query.codec.d.ts +34 -0
  170. package/dist/protocol/read-query.codec.js +125 -0
  171. package/dist/runtime/caller-data.copier.d.ts +18 -3
  172. package/dist/runtime/caller-data.copier.js +21 -8
  173. package/dist/runtime/caller-data.door.d.ts +13 -6
  174. package/dist/runtime/computed-read-runtime.d.ts +6 -1
  175. package/dist/runtime/computed-read-runtime.js +229 -48
  176. package/dist/runtime/computed-value.error.d.ts +3 -2
  177. package/dist/runtime/computed-value.error.js +3 -2
  178. package/dist/runtime/context-executor.runtime.d.ts +4 -3
  179. package/dist/runtime/context-executor.runtime.js +4 -2
  180. package/dist/runtime/core-class.brand.d.ts +10 -0
  181. package/dist/runtime/core-class.brand.js +21 -0
  182. package/dist/runtime/create-requirement.d.ts +31 -5
  183. package/dist/runtime/create-requirement.js +130 -24
  184. package/dist/runtime/decimal.d.ts +14 -0
  185. package/dist/runtime/decimal.js +21 -0
  186. package/dist/runtime/diagnostics-reporter.d.ts +49 -9
  187. package/dist/runtime/diagnostics-reporter.js +16 -9
  188. package/dist/runtime/error-boundary.d.ts +9 -2
  189. package/dist/runtime/error-boundary.js +12 -0
  190. package/dist/runtime/error-guidance.d.ts +12 -0
  191. package/dist/runtime/error-guidance.js +1 -0
  192. package/dist/runtime/error.catalog.d.ts +49 -0
  193. package/dist/runtime/error.catalog.js +49 -0
  194. package/dist/runtime/execution-options.d.ts +8 -0
  195. package/dist/runtime/field-check.evaluator.d.ts +0 -2
  196. package/dist/runtime/field-check.evaluator.js +1 -1
  197. package/dist/runtime/field-compute.d.ts +11 -10
  198. package/dist/runtime/field-stage.runtime.d.ts +3 -3
  199. package/dist/runtime/field-stage.runtime.js +9 -11
  200. package/dist/runtime/field-validator.runtime.d.ts +19 -1
  201. package/dist/runtime/field-validator.runtime.js +49 -9
  202. package/dist/runtime/framework-error.js +9 -0
  203. package/dist/runtime/framework-warning.emitter.d.ts +6 -0
  204. package/dist/runtime/framework-warning.emitter.js +8 -0
  205. package/dist/runtime/json-value.d.ts +10 -5
  206. package/dist/runtime/level-lifecycle.runtime.d.ts +4 -3
  207. package/dist/runtime/level-lifecycle.runtime.js +3 -5
  208. package/dist/runtime/mutation-planner.d.ts +1 -6
  209. package/dist/runtime/mutation-planner.js +4 -17
  210. package/dist/runtime/nested-write.walker.js +2 -1
  211. package/dist/runtime/operation-code.guidance.d.ts +6 -0
  212. package/dist/runtime/operation-code.guidance.js +167 -0
  213. package/dist/runtime/operation-request.d.ts +16 -9
  214. package/dist/runtime/operation-runtime.d.ts +35 -17
  215. package/dist/runtime/operation-runtime.js +220 -49
  216. package/dist/runtime/outcome-mapper.d.ts +2 -1
  217. package/dist/runtime/output-validator.d.ts +1 -1
  218. package/dist/runtime/output-validator.js +1 -3
  219. package/dist/runtime/owned-execution.registry.d.ts +34 -4
  220. package/dist/runtime/pipe-output.validator.d.ts +10 -0
  221. package/dist/runtime/pipe-output.validator.js +4 -2
  222. package/dist/runtime/pipeline.d.ts +52 -23
  223. package/dist/runtime/readonly-value.d.ts +20 -5
  224. package/dist/runtime/readonly-value.js +74 -26
  225. package/dist/runtime/record.guard.d.ts +14 -0
  226. package/dist/runtime/record.guard.js +10 -0
  227. package/dist/runtime/referential-action.d.ts +1 -1
  228. package/dist/runtime/request-envelope.validator.js +13 -10
  229. package/dist/runtime/scalar-runtime.d.ts +4 -21
  230. package/dist/runtime/scalar-runtime.js +91 -31
  231. package/dist/runtime/thenable.guard.d.ts +7 -0
  232. package/dist/runtime/thenable.guard.js +5 -0
  233. package/dist/runtime/unsent-value.normalizer.d.ts +15 -0
  234. package/dist/runtime/unsent-value.normalizer.js +129 -0
  235. package/dist/transactions/operation-arguments-encoder.js +9 -10
  236. package/dist/transactions/operation-fingerprint.js +2 -2
  237. package/dist/transactions/sql-statement.d.ts +10 -2
  238. package/dist/transactions/sql-statement.validator.d.ts +2 -2
  239. package/dist/transactions/sql-statement.validator.js +40 -6
  240. package/dist/transactions/transaction-envelope.d.ts +5 -4
  241. package/dist/transactions/transaction-integrity-error.js +2 -0
  242. package/dist/transactions/transaction-plan-assembler.d.ts +12 -5
  243. package/dist/transactions/transaction-plan-assembler.js +49 -17
  244. package/dist/transactions/transaction-plan-validator.d.ts +23 -7
  245. package/dist/transactions/transaction-plan-validator.js +31 -7
  246. package/dist/transactions/transaction-reference-collector.js +8 -10
  247. package/dist/transactions/transaction-reference-resolver.d.ts +3 -2
  248. package/dist/transactions/transaction-reference-resolver.js +1 -3
  249. package/dist/transactions/transaction-reference-validator.d.ts +43 -3
  250. package/dist/transactions/transaction-reference-validator.js +83 -57
  251. package/dist/transactions/transaction-reference.d.ts +1 -1
  252. package/dist/transactions/transaction-reference.js +5 -0
  253. package/dist/transactions/transaction-request.d.ts +8 -8
  254. package/dist/transactions/transaction-runtime.d.ts +5 -2
  255. package/dist/transactions/transaction-runtime.js +108 -27
  256. package/dist/transactions/transaction-settlement.runtime.d.ts +4 -3
  257. package/dist/transactions/transaction-slot-path-error.js +2 -0
  258. package/dist/transactions/transaction-stage.runtime.d.ts +5 -3
  259. package/dist/transactions/transaction-stage.runtime.js +8 -7
  260. package/package.json +6 -5
  261. package/src/runtime/field-check.evaluator.ts +1 -1
@@ -0,0 +1,501 @@
1
+ import { adapterPrimaryIdentifier } from "../adapters/adapter-identifier.js";
2
+ import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
3
+ import { isRecord } from "../runtime/record.guard.js";
4
+ import { policyRefusal } from "./policy-admission.js";
5
+ import { allOf, anyOf, everyOf, noneOf, someOf, } from "./policy-condition.js";
6
+ import { fieldAccess } from "./policy-session.js";
7
+ function relationOf(core, resource, name) {
8
+ const field = lookupField(lookupResource(core, resource), name);
9
+ return field?.kind === "relation"
10
+ ? field
11
+ : undefined;
12
+ }
13
+ function innerFilters(relation, value) {
14
+ return relation.cardinality === "many"
15
+ ? [value.some, value.every, value.none]
16
+ : [value];
17
+ }
18
+ class ReadTargets {
19
+ core;
20
+ resources = new Set();
21
+ constructor(core) {
22
+ this.core = core;
23
+ }
24
+ where(resource, where) {
25
+ if (!isRecord(where)) {
26
+ return;
27
+ }
28
+ for (const [key, value] of Object.entries(where)) {
29
+ if (key === "AND" || key === "OR" || key === "NOT") {
30
+ for (const entry of Array.isArray(value) ? value : [value]) {
31
+ this.where(resource, entry);
32
+ }
33
+ continue;
34
+ }
35
+ const relation = relationOf(this.core, resource, key);
36
+ if (relation === undefined || !isRecord(value)) {
37
+ continue;
38
+ }
39
+ this.resources.add(relation.target);
40
+ for (const filter of innerFilters(relation, value)) {
41
+ this.where(relation.target, filter);
42
+ }
43
+ }
44
+ }
45
+ orderBy(resource, orderBy) {
46
+ for (const entry of Array.isArray(orderBy) ? orderBy : [orderBy]) {
47
+ if (!isRecord(entry)) {
48
+ continue;
49
+ }
50
+ for (const [key, value] of Object.entries(entry)) {
51
+ const relation = relationOf(this.core, resource, key);
52
+ if (relation !== undefined) {
53
+ this.resources.add(relation.target);
54
+ this.orderBy(relation.target, value);
55
+ }
56
+ }
57
+ }
58
+ }
59
+ select(resource, select) {
60
+ if (!Array.isArray(select)) {
61
+ return;
62
+ }
63
+ for (const entry of select) {
64
+ if (!isRecord(entry)) {
65
+ continue;
66
+ }
67
+ for (const [name, value] of Object.entries(entry)) {
68
+ const relation = relationOf(this.core, resource, name);
69
+ if (relation === undefined) {
70
+ continue;
71
+ }
72
+ this.resources.add(relation.target);
73
+ if (isRecord(value)) {
74
+ this.where(relation.target, value.where);
75
+ this.orderBy(relation.target, value.orderBy);
76
+ this.select(relation.target, value.select);
77
+ }
78
+ }
79
+ }
80
+ }
81
+ }
82
+ class ScopedWhere {
83
+ core;
84
+ scopes;
85
+ constructor(core, scopes) {
86
+ this.core = core;
87
+ this.scopes = scopes;
88
+ }
89
+ scope(resource) {
90
+ return this.scopes.get(resource) ?? true;
91
+ }
92
+ of(resource, where) {
93
+ if (!isRecord(where)) {
94
+ return where;
95
+ }
96
+ let changed = false;
97
+ const conditions = [];
98
+ const kept = {};
99
+ for (const [key, value] of Object.entries(where)) {
100
+ if ((key === "AND" || key === "OR") && Array.isArray(value)) {
101
+ const parts = value.map((entry) => this.of(resource, entry));
102
+ if (parts.every((part, index) => part === value[index])) {
103
+ kept[key] = value;
104
+ continue;
105
+ }
106
+ changed = true;
107
+ conditions.push(key === "AND"
108
+ ? everyOf(parts)
109
+ : someOf(parts));
110
+ continue;
111
+ }
112
+ if (key === "NOT") {
113
+ const part = this.of(resource, value);
114
+ if (part === value) {
115
+ kept[key] = value;
116
+ continue;
117
+ }
118
+ changed = true;
119
+ conditions.push(noneOf(part));
120
+ continue;
121
+ }
122
+ const relation = relationOf(this.core, resource, key);
123
+ const filter = relation === undefined || !isRecord(value)
124
+ ? undefined
125
+ : this.relationFilter(key, relation, value);
126
+ if (filter === undefined) {
127
+ kept[key] = value;
128
+ continue;
129
+ }
130
+ changed = true;
131
+ conditions.push(...filter);
132
+ }
133
+ if (!changed) {
134
+ return where;
135
+ }
136
+ return everyOf([
137
+ Object.keys(kept).length === 0 ? true : kept,
138
+ ...conditions,
139
+ ]);
140
+ }
141
+ relationFilter(key, relation, value) {
142
+ const scope = this.scope(relation.target);
143
+ if (relation.cardinality === "one") {
144
+ const inner = this.of(relation.target, value);
145
+ if (scope === true && inner === value) {
146
+ return undefined;
147
+ }
148
+ const condition = allOf(inner, scope);
149
+ return [
150
+ typeof condition === "boolean"
151
+ ? condition && { [key]: {} }
152
+ : { [key]: condition },
153
+ ];
154
+ }
155
+ const inner = Object.keys(value).map((operator) => [operator, this.of(relation.target, value[operator])]);
156
+ if (scope === true &&
157
+ inner.every(([operator, condition]) => condition === value[operator])) {
158
+ return undefined;
159
+ }
160
+ return inner.map(([operator, raw]) => {
161
+ const condition = raw;
162
+ if (operator === "every") {
163
+ const every = anyOf(condition, noneOf(scope));
164
+ return typeof every === "boolean"
165
+ ? every || { [key]: { none: {} } }
166
+ : { [key]: { every } };
167
+ }
168
+ const scoped = allOf(condition, scope);
169
+ if (typeof scoped !== "boolean") {
170
+ return { [key]: { [operator]: scoped } };
171
+ }
172
+ if (operator === "some") {
173
+ return scoped && { [key]: { some: {} } };
174
+ }
175
+ return !scoped || { [key]: { none: {} } };
176
+ });
177
+ }
178
+ }
179
+ function emptyRelationValue(select) {
180
+ const entries = Array.isArray(select) ? select : [];
181
+ if (!entries.includes("$count")) {
182
+ return [];
183
+ }
184
+ return entries.some((entry) => entry !== "$count")
185
+ ? { data: [], count: 0 }
186
+ : { count: 0 };
187
+ }
188
+ function scopedCursor(cursor, scope) {
189
+ if (!isRecord(cursor) || typeof scope === "boolean") {
190
+ return cursor;
191
+ }
192
+ return { ...cursor, AND: [scope] };
193
+ }
194
+ function withIdentifier(select, identifier) {
195
+ const missing = identifier.filter((member) => !select.includes(member));
196
+ return missing.length === 0 ? select : [...select, ...missing];
197
+ }
198
+ export async function enforceReadPolicy(session, core, model, request, plan, adapterArguments, rows) {
199
+ const args = isRecord(adapterArguments) ? adapterArguments : {};
200
+ const root = request.resource;
201
+ const targets = new ReadTargets(core);
202
+ targets.resources.add(root);
203
+ targets.where(root, args.where);
204
+ targets.orderBy(root, args.orderBy);
205
+ targets.select(root, args.select);
206
+ const scopes = new Map(await Promise.all([...targets.resources].map(async (resource) => [resource, await session.scope(resource, "read")])));
207
+ const scoped = new ScopedWhere(core, scopes);
208
+ let sortRefusal = unscopedSort(core, scoped, root, args.orderBy);
209
+ const checks = await returnedRowChecks(session, core, plan);
210
+ let identity = request;
211
+ let rootWhere = args.where;
212
+ let rootCursor = args.cursor;
213
+ const scope = rows === undefined ? scoped.scope(root) : rows.scope;
214
+ if (scope === undefined) {
215
+ }
216
+ else if (request.variant === "unique") {
217
+ if (scope !== true) {
218
+ const within = allOf(args.where, scope);
219
+ if (within === false) {
220
+ return { kind: "empty", data: null };
221
+ }
222
+ const first = firstVariantOf(core, root, request.family);
223
+ if (rows !== undefined &&
224
+ (request.family === "update" || request.family === "delete")) {
225
+ rootWhere = { ...args.where, AND: [scope] };
226
+ }
227
+ else if (first !== undefined) {
228
+ identity = first;
229
+ rootWhere = within;
230
+ }
231
+ else if (!(await session.exists(root, within))) {
232
+ return { kind: "empty", data: null };
233
+ }
234
+ }
235
+ }
236
+ else {
237
+ const where = everyOf([
238
+ args.where === undefined
239
+ ? true
240
+ : scoped.of(root, args.where),
241
+ scope,
242
+ ]);
243
+ if (where === false) {
244
+ return {
245
+ kind: "empty",
246
+ data: request.variant === "many"
247
+ ? []
248
+ : request.variant === "count"
249
+ ? 0
250
+ : null,
251
+ };
252
+ }
253
+ rootWhere = where === true ? undefined : where;
254
+ rootCursor = scopedCursor(args.cursor, scope);
255
+ }
256
+ const rewriteLevel = (resource, select, level) => {
257
+ const entries = [];
258
+ const emptied = [];
259
+ const nested = new Map();
260
+ for (const entry of Array.isArray(select) ? select : []) {
261
+ const [name] = isRecord(entry) ? Object.keys(entry) : [];
262
+ const relation = name === undefined ? undefined : relationOf(core, resource, name);
263
+ if (relation === undefined || name === undefined) {
264
+ entries.push(entry);
265
+ continue;
266
+ }
267
+ const value = isRecord(entry[name])
268
+ ? entry[name]
269
+ : {};
270
+ const nestedLevel = level?.relations.find((candidate) => candidate.field === name)?.projection;
271
+ let rewritten = { ...value };
272
+ sortRefusal ??= unscopedSort(core, scoped, relation.target, value.orderBy);
273
+ if (relation.cardinality === "many") {
274
+ const where = everyOf([
275
+ value.where === undefined
276
+ ? true
277
+ : scoped.of(relation.target, value.where),
278
+ scoped.scope(relation.target),
279
+ ]);
280
+ if (where === false) {
281
+ emptied.push({
282
+ field: name,
283
+ value: emptyRelationValue(value.select),
284
+ });
285
+ continue;
286
+ }
287
+ if (where === true) {
288
+ delete rewritten.where;
289
+ }
290
+ else {
291
+ rewritten.where = where;
292
+ }
293
+ if (value.cursor !== undefined) {
294
+ rewritten.cursor = scopedCursor(value.cursor, scoped.scope(relation.target));
295
+ }
296
+ }
297
+ const inner = rewriteLevel(relation.target, value.select, nestedLevel);
298
+ nested.set(name, inner);
299
+ rewritten = { ...rewritten, select: inner.select };
300
+ entries.push({ [name]: rewritten });
301
+ }
302
+ const checked = level !== undefined &&
303
+ (checks.has(level) ||
304
+ (level === plan.projection && rows?.identified === true));
305
+ const carried = level !== undefined && level === plan.projection
306
+ ? (rows?.carried ?? [])
307
+ : [];
308
+ return {
309
+ select: withIdentifier(checked
310
+ ? withIdentifier(entries, adapterPrimaryIdentifier(model, resource))
311
+ : entries, carried),
312
+ emptied,
313
+ nested,
314
+ };
315
+ };
316
+ const rewritten = rewriteLevel(root, args.select, plan.projection);
317
+ if (sortRefusal !== undefined) {
318
+ return { kind: "refused", response: policyRefusal(sortRefusal) };
319
+ }
320
+ const runArguments = { ...args };
321
+ if (rootWhere === undefined) {
322
+ delete runArguments.where;
323
+ }
324
+ else {
325
+ runArguments.where = rootWhere;
326
+ }
327
+ if (rootCursor !== undefined) {
328
+ runArguments.cursor = rootCursor;
329
+ }
330
+ if (Array.isArray(args.select)) {
331
+ runArguments.select = rewritten.select;
332
+ }
333
+ const changesShape = checks.size > 0 || hasEmptied(rewritten);
334
+ return {
335
+ kind: "run",
336
+ identity,
337
+ arguments: runArguments,
338
+ plan: changesShape ||
339
+ rows?.identified === true ||
340
+ (rows?.carried?.length ?? 0) > 0
341
+ ? { ...plan, materialize: true }
342
+ : plan,
343
+ checks: {
344
+ root: plan.projection === undefined
345
+ ? []
346
+ : (checks.get(plan.projection) ?? []),
347
+ nested: [...checks.keys()].some((level) => level !== plan.projection),
348
+ },
349
+ settle: async (data) => (await rowsReadable(request.family === "find" ? session : session.renewed(), core, model, plan.projection, checks, data))
350
+ ? { ok: true, data: filledEmptied(rewritten, data) }
351
+ : {
352
+ ok: false,
353
+ response: policyRefusal("The policy does not allow reading every row or field this request returns."),
354
+ },
355
+ };
356
+ }
357
+ export async function returnedRowChecks(session, core, plan) {
358
+ const checks = new Map();
359
+ const collect = async (level, reachedOne) => {
360
+ if (level === undefined) {
361
+ return;
362
+ }
363
+ const fields = await session.fields(level.resource, { access: "read" });
364
+ const conditions = [];
365
+ if (reachedOne) {
366
+ const scope = await session.scope(level.resource, "read");
367
+ if (scope !== true) {
368
+ conditions.push(noneOf(scope));
369
+ }
370
+ }
371
+ for (const field of level.requested) {
372
+ const access = fieldAccess(core, level.resource, fields, field);
373
+ if (access !== "always" && access !== "never") {
374
+ conditions.push(noneOf(access));
375
+ }
376
+ }
377
+ if (conditions.length > 0) {
378
+ checks.set(level, conditions);
379
+ }
380
+ for (const relation of level.relations) {
381
+ await collect(relation.projection, relation.cardinality === "one");
382
+ }
383
+ };
384
+ await collect(plan.projection, false);
385
+ return checks;
386
+ }
387
+ function firstVariantOf(core, resource, family) {
388
+ const operations = lookupResource(core, resource)?.operations;
389
+ return family !== "create" &&
390
+ family !== "upsert" &&
391
+ operations?.[family]?.first !== undefined
392
+ ? { family, variant: "first" }
393
+ : undefined;
394
+ }
395
+ function hasEmptied(level) {
396
+ return (level.emptied.length > 0 ||
397
+ [...level.nested.values()].some((nested) => hasEmptied(nested)));
398
+ }
399
+ function unscopedSort(core, scoped, resource, orderBy) {
400
+ for (const entry of Array.isArray(orderBy) ? orderBy : [orderBy]) {
401
+ if (!isRecord(entry)) {
402
+ continue;
403
+ }
404
+ for (const key of Object.keys(entry)) {
405
+ const relation = relationOf(core, resource, key);
406
+ if (relation !== undefined && scoped.scope(relation.target) !== true) {
407
+ return `The policy does not allow sorting by relation "${key}" of Resource "${resource}": it reaches rows of "${relation.target}" this request may not read.`;
408
+ }
409
+ }
410
+ }
411
+ return undefined;
412
+ }
413
+ function rowsByLevel(level, rows, into) {
414
+ if (level === undefined) {
415
+ return;
416
+ }
417
+ const own = into.get(level) ?? [];
418
+ for (const row of rows) {
419
+ if (isRecord(row)) {
420
+ own.push(row);
421
+ }
422
+ }
423
+ into.set(level, own);
424
+ for (const relation of level.relations) {
425
+ const nested = [];
426
+ for (const row of own) {
427
+ const value = row[relation.field];
428
+ if (Array.isArray(value)) {
429
+ nested.push(...value);
430
+ }
431
+ else if (isRecord(value) && Array.isArray(value.data)) {
432
+ nested.push(...value.data);
433
+ }
434
+ else if (relation.cardinality === "one" && isRecord(value)) {
435
+ nested.push(value);
436
+ }
437
+ }
438
+ rowsByLevel(relation.projection, nested, into);
439
+ }
440
+ }
441
+ export function identifiedRows(core, identifier, resource, rows) {
442
+ const [single] = identifier;
443
+ if (identifier.length === 1 && single !== undefined) {
444
+ const values = [...new Set(rows.map((row) => row[single]))];
445
+ const field = lookupField(lookupResource(core, resource), single);
446
+ if (field?.kind === "scalar" && field.capabilities.filter?.includes("in")) {
447
+ return { [single]: { in: values } };
448
+ }
449
+ return someOf(values.map((value) => ({ [single]: value })));
450
+ }
451
+ return someOf(rows.map((row) => Object.fromEntries(identifier.map((member) => [member, row[member]]))));
452
+ }
453
+ async function rowsReadable(session, core, model, projection, checks, data) {
454
+ if (checks.size === 0 || projection === undefined) {
455
+ return true;
456
+ }
457
+ const levels = new Map();
458
+ rowsByLevel(projection, Array.isArray(data) ? data : [data], levels);
459
+ const questions = [...checks].map(async ([level, misses]) => {
460
+ const rows = levels.get(level) ?? [];
461
+ if (rows.length === 0) {
462
+ return false;
463
+ }
464
+ const miss = someOf(misses);
465
+ if (miss === false) {
466
+ return false;
467
+ }
468
+ if (miss === true) {
469
+ return true;
470
+ }
471
+ const identifier = adapterPrimaryIdentifier(model, level.resource);
472
+ const reach = allOf(identifiedRows(core, identifier, level.resource, rows), miss);
473
+ return (reach !== false &&
474
+ session.exists(level.resource, reach === true ? {} : reach));
475
+ });
476
+ const answers = await Promise.all(questions);
477
+ return !answers.some((answer) => answer === true);
478
+ }
479
+ function filledEmptied(scopedLevel, data) {
480
+ if (!hasEmptied(scopedLevel)) {
481
+ return data;
482
+ }
483
+ if (Array.isArray(data)) {
484
+ return data.map((row) => filledEmptied(scopedLevel, row));
485
+ }
486
+ if (!isRecord(data)) {
487
+ return data;
488
+ }
489
+ const row = { ...data };
490
+ for (const emptied of scopedLevel.emptied) {
491
+ row[emptied.field] = structuredClone(emptied.value);
492
+ }
493
+ for (const [field, nested] of scopedLevel.nested) {
494
+ const value = row[field];
495
+ row[field] =
496
+ isRecord(value) && Array.isArray(value.data)
497
+ ? { ...value, data: filledEmptied(nested, value.data) }
498
+ : filledEmptied(nested, value);
499
+ }
500
+ return row;
501
+ }
@@ -0,0 +1,51 @@
1
+ import type { AvAdapterModel } from "../adapters/adapter-model.js";
2
+ import type { AvContractCore } from "../contracts/contract.js";
3
+ import type { AvOperationIdentity } from "../operations/operation-identity.js";
4
+ import type { AvOperationFailureResponse } from "../runtime/operation-response.js";
5
+ import type { PolicySession } from "./policy-session.js";
6
+ /**
7
+ * Whether a reference written to `target` is held to its read scope: where the
8
+ * Core Contract shows `target` and the engine does not list it in `unheld`
9
+ * (see the header). The one test of it: the hold below, a relation directive's
10
+ * link where the writing row holds the reference
11
+ * (`policy-nested-write.runtime.ts`), and the list the engine is told
12
+ * (`heldTargetsOf`) all read it.
13
+ */
14
+ export declare function isHeldTarget(core: AvContractCore, unheld: readonly string[], target: string): boolean;
15
+ /**
16
+ * The Resources a written reference is held to the read scope of, frozen: what
17
+ * `AvPolicyEngine.hosted` is told once the Framework is built.
18
+ */
19
+ export declare function heldTargetsOf(core: AvContractCore, unheld: readonly string[]): readonly string[];
20
+ /** A write's top-level records and the stage each is written at. */
21
+ type ReferenceSite = {
22
+ readonly stage: "create" | "update";
23
+ readonly records: readonly unknown[];
24
+ };
25
+ /**
26
+ * What the policy does to a write's references: refuse the write before it
27
+ * runs, or hand the adapter its records with the held ones written as links.
28
+ */
29
+ export type HeldReferences = {
30
+ readonly kind: "refused";
31
+ readonly response: AvOperationFailureResponse;
32
+ } | {
33
+ readonly kind: "held";
34
+ /** Whether a record the write sends is written as links: the write may fail on one. */
35
+ readonly linked: boolean;
36
+ /** The value of a top-level argument (`data`, `create`, `update`) as the adapter runs it. */
37
+ readonly apply: (value: unknown, stage: "create" | "update") => unknown;
38
+ };
39
+ /**
40
+ * The references a write's records (`sites`, of the request's Resource, nested
41
+ * records included) write, held to their targets' read scopes: the refusal of
42
+ * a value that names no row, or that a record cannot hold inside the write and
43
+ * names a row outside the scope or not there (one count per target Resource);
44
+ * otherwise
45
+ * how the adapter's records write the held references as links. No query for a
46
+ * record written as links, nor where every target's scope is `true`.
47
+ */
48
+ export declare function heldReferences(session: PolicySession, core: AvContractCore, model: AvAdapterModel, request: AvOperationIdentity & {
49
+ readonly resource: string;
50
+ }, sites: readonly ReferenceSite[]): Promise<HeldReferences>;
51
+ export {};