@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,336 @@
1
+ import { lookupResource } from "../contracts/contract-lookup.js";
2
+ import { advertisedDirectivesAt, } from "../contracts/field.js";
3
+ import { writeScopeOf } from "../contracts/resource-operations.js";
4
+ import { LOOPBACK_WRITE_SCOPE, NESTED_WRITE_SCOPE, TO_MANY_UPDATE_WRITE_SCOPE, } from "../operations/write-scope.resolver.js";
5
+ import { isPlainRecord, isRecord } from "../runtime/record.guard.js";
6
+ import { policyRefusal } from "./policy-admission.js";
7
+ import { allOf, everyOf, noneOf, someOf, whereOf, } from "./policy-condition.js";
8
+ import { NamedRows } from "./policy-named-rows.js";
9
+ const ownedOf = new WeakMap();
10
+ function resourceOf(model, resource) {
11
+ return Object.hasOwn(model.resources, resource)
12
+ ? model.resources[resource]
13
+ : undefined;
14
+ }
15
+ function relationOf(model, resource, name) {
16
+ const fields = resourceOf(model, resource)?.fields ?? {};
17
+ const field = Object.hasOwn(fields, name) ? fields[name] : undefined;
18
+ return field?.kind === "relation" ? field : undefined;
19
+ }
20
+ function isIdentifier(model, resource, fields) {
21
+ return (resourceOf(model, resource)?.identifiers ?? []).some((identifier) => identifier.length === fields.length &&
22
+ identifier.every((field) => fields.includes(field)));
23
+ }
24
+ function ownedReferences(model, resource) {
25
+ let byResource = ownedOf.get(model);
26
+ if (byResource === undefined) {
27
+ byResource = new Map();
28
+ ownedOf.set(model, byResource);
29
+ }
30
+ let owned = byResource.get(resource);
31
+ if (owned === undefined) {
32
+ const relations = Object.entries(resourceOf(model, resource)?.fields ?? {}).flatMap(([name, field]) => {
33
+ const relation = field.kind === "relation" ? field : undefined;
34
+ return relation !== undefined &&
35
+ typeof relation.target === "string" &&
36
+ Array.isArray(relation.referenceFields) &&
37
+ relation.referenceFields.length > 0
38
+ ? [{ name, relation, fields: relation.referenceFields }]
39
+ : [];
40
+ });
41
+ const holders = new Map();
42
+ for (const { fields } of relations) {
43
+ for (const field of fields) {
44
+ holders.set(field, (holders.get(field) ?? 0) + 1);
45
+ }
46
+ }
47
+ owned = relations.map(({ name, relation, fields }) => {
48
+ const references = relation.references;
49
+ return {
50
+ name,
51
+ relation,
52
+ target: relation.target,
53
+ fields,
54
+ references,
55
+ shared: fields.some((field) => (holders.get(field) ?? 0) > 1),
56
+ addressable: references !== undefined &&
57
+ references.length === fields.length &&
58
+ isIdentifier(model, relation.target, references),
59
+ };
60
+ });
61
+ byResource.set(resource, owned);
62
+ }
63
+ return owned;
64
+ }
65
+ function walk(model, record, at, visit) {
66
+ let rebuilt;
67
+ for (const [key, value] of Object.entries(record)) {
68
+ const relation = isRecord(value)
69
+ ? relationOf(model, at.resource, key)
70
+ : undefined;
71
+ if (relation === undefined || !isRecord(value)) {
72
+ continue;
73
+ }
74
+ const nested = (one, stage, scope = NESTED_WRITE_SCOPE) => isRecord(one)
75
+ ? walk(model, one, { resource: relation.target, stage, scope, via: relation.inverse }, visit)
76
+ : one;
77
+ const each = (operand, entry) => Array.isArray(operand)
78
+ ? operand.map((one) => (isRecord(one) ? entry(one) : one))
79
+ : isRecord(operand)
80
+ ? entry(operand)
81
+ : operand;
82
+ const directives = {};
83
+ for (const [directive, operand] of Object.entries(value)) {
84
+ switch (directive) {
85
+ case "$create":
86
+ directives[directive] = each(operand, (one) => nested(one, "create"));
87
+ break;
88
+ case "$connectOrCreate":
89
+ directives[directive] = each(operand, (one) => ({
90
+ ...one,
91
+ create: nested(one.create, "create"),
92
+ }));
93
+ break;
94
+ case "$upsert":
95
+ directives[directive] = each(operand, (one) => ({
96
+ ...one,
97
+ create: nested(one.create, "create"),
98
+ update: nested(one.update, "update"),
99
+ }));
100
+ break;
101
+ case "$update":
102
+ directives[directive] = each(operand, (one) => ({
103
+ ...one,
104
+ data: nested(one.data, "update", relation.cardinality === "many"
105
+ ? TO_MANY_UPDATE_WRITE_SCOPE
106
+ : NESTED_WRITE_SCOPE),
107
+ }));
108
+ break;
109
+ default:
110
+ directives[directive] = operand;
111
+ }
112
+ }
113
+ rebuilt ??= { ...record };
114
+ rebuilt[key] = directives;
115
+ }
116
+ return visit(rebuilt ?? record, at);
117
+ }
118
+ function writtenReferences(model, record, resource) {
119
+ return ownedReferences(model, resource).flatMap((owned) => {
120
+ const written = owned.fields.filter((field) => Object.hasOwn(record, field) && record[field] !== undefined);
121
+ if (written.length === 0) {
122
+ return [];
123
+ }
124
+ const values = written.map((field) => record[field]);
125
+ if (values.some((value) => value === null)) {
126
+ return [
127
+ [
128
+ owned,
129
+ values.every((value) => value === null) &&
130
+ written.length === owned.fields.length
131
+ ? { kind: "null" }
132
+ : { kind: "partial" },
133
+ ],
134
+ ];
135
+ }
136
+ const references = owned.references;
137
+ return [
138
+ [
139
+ owned,
140
+ written.length !== owned.fields.length ||
141
+ references === undefined ||
142
+ references.length !== owned.fields.length ||
143
+ values.some(isPlainRecord)
144
+ ? { kind: "unstated" }
145
+ : { kind: "value", values },
146
+ ],
147
+ ];
148
+ });
149
+ }
150
+ function offers(owned, at, directive) {
151
+ const scope = owned.name === at.via ? LOOPBACK_WRITE_SCOPE : at.scope;
152
+ return (advertisedDirectivesAt(owned.relation.capabilities[at.stage], scope)?.includes(directive) === true);
153
+ }
154
+ function linkable(record, at, written) {
155
+ return written.every(([owned, how]) => {
156
+ if (owned.shared || record[owned.name] !== undefined) {
157
+ return false;
158
+ }
159
+ switch (how.kind) {
160
+ case "value":
161
+ return owned.addressable && offers(owned, at, "$connect");
162
+ case "null":
163
+ return at.stage === "update" && offers(owned, at, "$disconnect");
164
+ default:
165
+ return false;
166
+ }
167
+ });
168
+ }
169
+ export function isHeldTarget(core, unheld, target) {
170
+ return lookupResource(core, target) !== undefined && !unheld.includes(target);
171
+ }
172
+ export function heldTargetsOf(core, unheld) {
173
+ return Object.freeze(Object.keys(core.resources).filter((target) => isHeldTarget(core, unheld, target)));
174
+ }
175
+ function heldScope(held, reads, target) {
176
+ if (!held(target)) {
177
+ return true;
178
+ }
179
+ const read = reads.get(target);
180
+ if (read === undefined) {
181
+ throw new Error(`policy: the reference to Resource "${target}" was not held before the write.`);
182
+ }
183
+ return read;
184
+ }
185
+ function holds(held, reads, written) {
186
+ return written.some(([owned, how]) => (how.kind === "value" || how.kind === "unstated") &&
187
+ heldScope(held, reads, owned.target) !== true);
188
+ }
189
+ function asLinks(held, reads, record, written) {
190
+ const linked = { ...record };
191
+ for (const [owned, how] of written) {
192
+ for (const field of owned.fields) {
193
+ delete linked[field];
194
+ }
195
+ if (how.kind === "null") {
196
+ linked[owned.name] = { $disconnect: true };
197
+ continue;
198
+ }
199
+ if (how.kind !== "value") {
200
+ continue;
201
+ }
202
+ const selector = Object.fromEntries((owned.references ?? []).map((reference, index) => [
203
+ reference,
204
+ how.values[index],
205
+ ]));
206
+ const read = heldScope(held, reads, owned.target);
207
+ if (read !== true && read !== false) {
208
+ selector.AND = [whereOf(read)];
209
+ }
210
+ linked[owned.name] = { $connect: selector };
211
+ }
212
+ return linked;
213
+ }
214
+ function outsideRead(target) {
215
+ return policyRefusal(`The policy does not allow this write to link a row of Resource "${target}" outside what the caller may read.`);
216
+ }
217
+ export async function heldReferences(session, core, model, request, sites) {
218
+ const resource = request.resource;
219
+ const held = (target) => isHeldTarget(core, session.unheld, target);
220
+ const family = resourceOf(model, resource)?.operations?.[request.family];
221
+ const scope = writeScopeOf(isRecord(family) ? family[request.variant] : undefined);
222
+ const top = (stage) => ({
223
+ resource,
224
+ stage,
225
+ scope,
226
+ via: undefined,
227
+ });
228
+ const records = sites.flatMap(({ stage, records }) => records.filter(isRecord).map((record) => [record, top(stage)]));
229
+ const targets = new Set();
230
+ for (const [record, at] of records) {
231
+ walk(model, record, at, (one, where) => {
232
+ for (const [owned, how] of writtenReferences(model, one, where.resource)) {
233
+ if (how.kind === "value" || how.kind === "unstated") {
234
+ targets.add(owned.target);
235
+ }
236
+ }
237
+ return one;
238
+ });
239
+ }
240
+ const reads = new Map();
241
+ for (const target of targets) {
242
+ if (held(target)) {
243
+ reads.set(target, await session.scope(target, "read"));
244
+ }
245
+ }
246
+ let refusal;
247
+ let linked = false;
248
+ const asked = new Map();
249
+ const named = new Map();
250
+ const hold = (record, at) => {
251
+ const written = writtenReferences(model, record, at.resource);
252
+ if (refusal !== undefined || !holds(held, reads, written)) {
253
+ return record;
254
+ }
255
+ for (const [owned, how] of written) {
256
+ const read = how.kind === "value" || how.kind === "unstated"
257
+ ? heldScope(held, reads, owned.target)
258
+ : true;
259
+ if (read === true) {
260
+ continue;
261
+ }
262
+ if (how.kind === "unstated") {
263
+ refusal = policyRefusal(`The policy holds a reference this write sends to Resource "${owned.target}" to what the caller may read, and the write does not state the row it links (send every field of the reference as a value).`);
264
+ return record;
265
+ }
266
+ if (read === false) {
267
+ refusal = outsideRead(owned.target);
268
+ return record;
269
+ }
270
+ }
271
+ if (linkable(record, at, written)) {
272
+ linked = true;
273
+ return record;
274
+ }
275
+ for (const [owned, how] of written) {
276
+ if (how.kind !== "value" ||
277
+ heldScope(held, reads, owned.target) === true) {
278
+ continue;
279
+ }
280
+ const read = heldScope(held, reads, owned.target);
281
+ const selector = everyOf((owned.references ?? []).map((reference, index) => ({
282
+ [reference]: { equals: how.values[index] },
283
+ })));
284
+ if (owned.addressable && selector !== true && selector !== false) {
285
+ let rows = named.get(owned.target);
286
+ if (rows === undefined) {
287
+ rows = new NamedRows();
288
+ named.set(owned.target, rows);
289
+ }
290
+ rows.add(selector, read);
291
+ continue;
292
+ }
293
+ let rows = asked.get(owned.target);
294
+ if (rows === undefined) {
295
+ rows = [];
296
+ asked.set(owned.target, rows);
297
+ }
298
+ rows.push(selector);
299
+ }
300
+ return record;
301
+ };
302
+ for (const [record, at] of records) {
303
+ walk(model, record, at, hold);
304
+ }
305
+ if (refusal !== undefined) {
306
+ return { kind: "refused", response: refusal };
307
+ }
308
+ for (const [target, rows] of named) {
309
+ if (rows.size > 0 && !(await rows.within(session, target))) {
310
+ return { kind: "refused", response: outsideRead(target) };
311
+ }
312
+ }
313
+ for (const [target, rows] of asked) {
314
+ const outside = allOf(someOf(rows), noneOf(heldScope(held, reads, target)));
315
+ if (outside !== false && (await session.exists(target, whereOf(outside)))) {
316
+ return { kind: "refused", response: outsideRead(target) };
317
+ }
318
+ }
319
+ const link = (record, at) => {
320
+ const written = writtenReferences(model, record, at.resource);
321
+ return holds(held, reads, written) && linkable(record, at, written)
322
+ ? asLinks(held, reads, record, written)
323
+ : record;
324
+ };
325
+ return {
326
+ kind: "held",
327
+ linked,
328
+ apply: (value, stage) => {
329
+ if (!linked) {
330
+ return value;
331
+ }
332
+ const one = (record) => isRecord(record) ? walk(model, record, top(stage), link) : record;
333
+ return Array.isArray(value) ? value.map(one) : one(value);
334
+ },
335
+ };
336
+ }
@@ -0,0 +1,72 @@
1
+ import type { AvAdapterModel } from "../adapters/adapter-model.js";
2
+ import type { AvAdapterOperationExecutor } from "../adapters/adapter-operation-executor.interface.js";
3
+ import type { ResolvedPolicyConfig } from "../config/config.resolver.js";
4
+ import type { AvRequestOrigin } from "../config/config-layer.js";
5
+ import type { AvContract, AvContractCore } from "../contracts/contract.js";
6
+ import type { AvOperationIdentity } from "../operations/operation-identity.js";
7
+ import type { DiagnosticsReporter } from "../runtime/diagnostics-reporter.js";
8
+ import { type CallerArgumentsView } from "../runtime/error-boundary.js";
9
+ import type { AvOperationFailureResponse } from "../runtime/operation-response.js";
10
+ import type { AvState } from "../runtime/request-state.js";
11
+ import { type PolicySession } from "./policy-session.js";
12
+ /** The principal an application call runs as (`{ as }`). */
13
+ export type PolicyCaller = {
14
+ readonly principal: unknown;
15
+ };
16
+ /**
17
+ * The principal a call's options name (`{ as }`), or `undefined` when they name
18
+ * none. `null` is a principal: the anonymous one.
19
+ */
20
+ export declare function callerOf(options: unknown): PolicyCaller | undefined;
21
+ /**
22
+ * The policy session of one request, or `undefined` when the policy does not
23
+ * check it; a promise of it when the principal arrives as one. Deriving the
24
+ * principal is server code: a throw, or a rejection, is the caller's to answer
25
+ * as the server's defect.
26
+ */
27
+ export declare function openRequestPolicy(policy: ResolvedPolicyConfig | undefined, core: AvContractCore, request: {
28
+ readonly origin: AvRequestOrigin;
29
+ readonly state: AvState;
30
+ }, caller: PolicyCaller | undefined, adapter: AvAdapterOperationExecutor & {
31
+ readonly model: AvAdapterModel;
32
+ }): PolicySession | undefined | Promise<PolicySession | undefined>;
33
+ /**
34
+ * The principal a remote request runs as, derived once from its state, or
35
+ * `undefined` when no policy checks it; a promise of it when the principal
36
+ * arrives as one. A throw, or a rejection, is the caller's to answer as the
37
+ * server's defect. Under a held state ({@link holdRequestPrincipal}) it is the
38
+ * principal derived the first time, however often it is asked.
39
+ */
40
+ export declare function derivePolicyCaller(policy: ResolvedPolicyConfig | undefined, request: {
41
+ readonly origin: AvRequestOrigin;
42
+ readonly state: AvState;
43
+ }): PolicyCaller | undefined | Promise<PolicyCaller>;
44
+ /**
45
+ * Derives and holds `request`'s origin's principal from a held state that
46
+ * holds none yet; nothing for a state no host held, or for an origin the
47
+ * policy derives no principal for. Never throws and never rejects: the
48
+ * request that names its principal is not answered by it. A throw holds
49
+ * nothing; a rejection is held, observed here, and answers the requests that
50
+ * later read it.
51
+ */
52
+ export declare function holdFirstPrincipal(policy: ResolvedPolicyConfig | undefined, request: {
53
+ readonly origin: AvRequestOrigin;
54
+ readonly state: AvState;
55
+ }): void;
56
+ /**
57
+ * Holds `state` as one request's: every request run under this object reads
58
+ * its principal once, the first time one is derived from it, until the state
59
+ * is held again. A host calls it once per request, on the state it seeds once
60
+ * and shares between that request's calls, before any of them runs. Anything
61
+ * that is not an object is ignored: no request runs under it.
62
+ */
63
+ export declare function holdRequestPrincipal(state: unknown): void;
64
+ /**
65
+ * Policy admission inside its own boundary, as the Framework Validation Layer's
66
+ * is: a refusal, a defect of the engine or its answers (`A3000`), or an adapter
67
+ * failure of an `exists` question (`A3001`), answered without any developer
68
+ * filter; `undefined` when the request goes on to its guards.
69
+ */
70
+ export declare function admitRequestPolicy(session: PolicySession, core: AvContractCore, active: AvContract, request: AvOperationIdentity & {
71
+ readonly resource: string;
72
+ }, args: unknown, report: DiagnosticsReporter, view: CallerArgumentsView): Promise<AvOperationFailureResponse | undefined>;
@@ -0,0 +1,115 @@
1
+ import { AdapterBoundary, sanitizeOperationFailure, } from "../runtime/error-boundary.js";
2
+ import { isThenable } from "../runtime/thenable.guard.js";
3
+ import { admitByPolicy, admitWriteByPolicy } from "./policy-admission.js";
4
+ import { openPolicySession } from "./policy-session.js";
5
+ export function callerOf(options) {
6
+ if (typeof options !== "object" || options === null) {
7
+ return undefined;
8
+ }
9
+ let principal;
10
+ try {
11
+ principal = options.as;
12
+ }
13
+ catch {
14
+ return undefined;
15
+ }
16
+ return principal === undefined ? undefined : { principal };
17
+ }
18
+ export function openRequestPolicy(policy, core, request, caller, adapter) {
19
+ if (policy === undefined) {
20
+ return undefined;
21
+ }
22
+ if (caller !== undefined) {
23
+ holdFirstPrincipal(policy, request);
24
+ }
25
+ const settled = caller ?? derivePolicyCaller(policy, request);
26
+ if (settled === undefined) {
27
+ return undefined;
28
+ }
29
+ return settled instanceof Promise
30
+ ? settled.then(({ principal }) => sessionOf(policy, core, request.origin, adapter, principal))
31
+ : sessionOf(policy, core, request.origin, adapter, settled.principal);
32
+ }
33
+ export function derivePolicyCaller(policy, request) {
34
+ if (policy === undefined || request.origin === "application") {
35
+ return undefined;
36
+ }
37
+ const held = heldOf(policy, request.state);
38
+ if (held === undefined) {
39
+ return principalOf(policy, request);
40
+ }
41
+ const known = held.get(request.origin);
42
+ if (known !== undefined) {
43
+ return known;
44
+ }
45
+ const derived = principalOf(policy, request);
46
+ held.set(request.origin, derived);
47
+ return derived;
48
+ }
49
+ export function holdFirstPrincipal(policy, request) {
50
+ if (policy === undefined || request.origin === "application") {
51
+ return;
52
+ }
53
+ const held = heldOf(policy, request.state);
54
+ if (held === undefined || held.has(request.origin)) {
55
+ return;
56
+ }
57
+ let derived;
58
+ try {
59
+ derived = principalOf(policy, request);
60
+ }
61
+ catch {
62
+ return;
63
+ }
64
+ if (derived instanceof Promise) {
65
+ derived.catch(() => undefined);
66
+ }
67
+ held.set(request.origin, derived);
68
+ }
69
+ function heldOf(policy, state) {
70
+ const held = HELD_PRINCIPALS.get(state);
71
+ if (held === undefined) {
72
+ return undefined;
73
+ }
74
+ let byOrigin = held.get(policy);
75
+ if (byOrigin === undefined) {
76
+ byOrigin = new Map();
77
+ held.set(policy, byOrigin);
78
+ }
79
+ return byOrigin;
80
+ }
81
+ function principalOf(policy, request) {
82
+ const principal = policy.principal({
83
+ $state: request.state,
84
+ $origin: request.origin,
85
+ });
86
+ return isThenable(principal)
87
+ ? Promise.resolve(principal).then((settled) => ({ principal: settled }))
88
+ : { principal };
89
+ }
90
+ const HELD_PRINCIPALS = new WeakMap();
91
+ export function holdRequestPrincipal(state) {
92
+ if (typeof state === "object" && state !== null) {
93
+ HELD_PRINCIPALS.set(state, new Map());
94
+ }
95
+ }
96
+ function sessionOf(policy, core, origin, adapter, principal) {
97
+ return openPolicySession(policy, core, adapter.model, principal, origin, {
98
+ executor: adapter,
99
+ boundary: new AdapterBoundary(),
100
+ });
101
+ }
102
+ export async function admitRequestPolicy(session, core, active, request, args, report, view) {
103
+ try {
104
+ const refused = await admitByPolicy(session, core, active, request, args);
105
+ if (refused !== undefined) {
106
+ return refused;
107
+ }
108
+ return request.family === "find"
109
+ ? undefined
110
+ : await admitWriteByPolicy(session, core, request, args);
111
+ }
112
+ catch (error) {
113
+ return sanitizeOperationFailure(error, session.execution.boundary.originOf(error), report, view);
114
+ }
115
+ }
@@ -0,0 +1,106 @@
1
+ import type { AvAdapterModel } from "../adapters/adapter-model.js";
2
+ import type { AvAdapterOperationExecutor } from "../adapters/adapter-operation-executor.interface.js";
3
+ import type { ResolvedPolicyConfig } from "../config/config.resolver.js";
4
+ import type { AvRequestOrigin } from "../config/config-layer.js";
5
+ import type { AvContractCore } from "../contracts/contract.js";
6
+ import type { AdapterBoundary } from "../runtime/error-boundary.js";
7
+ import type { PolicyCondition } from "./policy-condition.js";
8
+ import type { AvPolicyEngine, AvPolicyWhere } from "./policy-engine.js";
9
+ /**
10
+ * What `fieldsOf` is asked about, beside the question every method states:
11
+ * reading, or writing by one action. Read from the engine's own signature,
12
+ * which writes the union inline (see `AvPolicyEngine.fieldsOf`).
13
+ */
14
+ export type PolicyFieldsAccess = Parameters<AvPolicyEngine["fieldsOf"]>[0] extends infer Question ? Question extends unknown ? Pick<Question, Extract<keyof Question, "access" | "action">> : never : never;
15
+ /** An action a policy question names. */
16
+ export type PolicyAction = "read" | "create" | "update" | "delete";
17
+ /** `fieldsOf`'s answer, held: the names read into sets once. */
18
+ export type PolicyFields = {
19
+ /** `always` was `"all"`: every field that is not governed. */
20
+ readonly all: boolean;
21
+ /** The fields named in `always`, and those granted on every row. */
22
+ readonly listed: ReadonlySet<string>;
23
+ /** Each field readable on some rows only, and the condition those rows meet. */
24
+ readonly perRow: ReadonlyMap<string, AvPolicyWhere>;
25
+ };
26
+ /** Where `exists` runs: an executor, and the boundary its failures are classified by. */
27
+ export type PolicyExecution = {
28
+ readonly executor: AvAdapterOperationExecutor;
29
+ readonly boundary: AdapterBoundary;
30
+ };
31
+ /**
32
+ * A changed row as it was before its update, as the after check states it:
33
+ * the row, and the grants the engine said covered it then (`grantsOf`), when
34
+ * it answers that question.
35
+ */
36
+ export type PolicyBefore = {
37
+ readonly row: Readonly<Record<string, unknown>>;
38
+ readonly grants?: readonly string[];
39
+ };
40
+ /** One request's policy questions and answers. */
41
+ export type PolicySession = {
42
+ readonly principal: unknown;
43
+ readonly origin: AvRequestOrigin;
44
+ /** Where its `exists` runs now. */
45
+ readonly execution: PolicyExecution;
46
+ /**
47
+ * Whether the principal may run `action` on `resource` (`can`): about `row`
48
+ * when given, and on an update's after check, the row `before` it with the
49
+ * grants that covered it then, when the engine answered them.
50
+ */
51
+ can(resource: string, action: PolicyAction, row?: Readonly<Record<string, unknown>>, before?: PolicyBefore): Promise<boolean>;
52
+ /**
53
+ * The grants that cover `row` of `resource` before an update (`grantsOf`),
54
+ * checked; `undefined` when the engine answers no such question.
55
+ */
56
+ grants(resource: string, row: Readonly<Record<string, unknown>>): Promise<readonly string[] | undefined>;
57
+ /** The rows the principal may `action` (`scopeOf`), checked. */
58
+ scope(resource: string, action: "read" | "update" | "delete"): Promise<PolicyCondition>;
59
+ /**
60
+ * The fields the principal may read, or write by one action (`fieldsOf`),
61
+ * checked.
62
+ */
63
+ fields(resource: string, access: PolicyFieldsAccess): Promise<PolicyFields>;
64
+ /** Whether a row of `resource` meets `where`: core's own question, unchecked. */
65
+ exists(resource: string, where: AvPolicyWhere): Promise<boolean>;
66
+ /**
67
+ * How many rows of `resource` meet `where`: core's own question, unchecked,
68
+ * never cached; `undefined` where the Core Contract offers neither a count
69
+ * nor a list of the Resource.
70
+ */
71
+ count(resource: string, where: AvPolicyWhere): Promise<number | undefined>;
72
+ /**
73
+ * The rows of `resource` meeting `where`, the scalar fields `select` names
74
+ * only: core's own read, unchecked, never cached (a write may change them).
75
+ */
76
+ rows(resource: string, where: AvPolicyWhere, select: readonly string[]): Promise<readonly Readonly<Record<string, unknown>>[]>;
77
+ /** Whether the engine lists `action` on `resource` in `afterChecks`. */
78
+ checksAfter(resource: string, action: "update"): boolean;
79
+ /** The Resources the engine lists in `unheld`, read once at construction. */
80
+ readonly unheld: readonly string[];
81
+ /**
82
+ * Notes a field a create sends that the principal may write on some rows
83
+ * only, with its grant: admission sees what the caller sent, and the row it
84
+ * creates is checked against the grant after the insert.
85
+ */
86
+ grantCreate(field: string, grant: AvPolicyWhere): void;
87
+ /** The grants admission noted for the fields a create sends, by field. */
88
+ createGrants(): ReadonlyMap<string, AvPolicyWhere>;
89
+ /** The same session, its `exists` running on `execution`. */
90
+ on(execution: PolicyExecution): PolicySession;
91
+ /**
92
+ * The same session with no `exists` answer kept: a write changed the rows,
93
+ * so a question asked before it may answer differently now.
94
+ */
95
+ renewed(): PolicySession;
96
+ };
97
+ /**
98
+ * Opens the policy session of one request: `principal` under `origin`, its
99
+ * `exists` on `execution` until {@link PolicySession.on} moves it.
100
+ */
101
+ export declare function openPolicySession(policy: ResolvedPolicyConfig, core: AvContractCore, model: AvAdapterModel, principal: unknown, origin: AvRequestOrigin, execution: PolicyExecution): PolicySession;
102
+ /**
103
+ * How the principal may use `field`: on every row it reaches, on the rows a
104
+ * grant names, or not at all.
105
+ */
106
+ export declare function fieldAccess(core: AvContractCore, resource: string, fields: PolicyFields, field: string): "always" | AvPolicyWhere | "never";