@aventara/core 1.0.0-rc.3 → 1.0.0-rc.4

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 (73) hide show
  1. package/CHANGELOG.md +130 -1
  2. package/ERRORS.md +6 -6
  3. package/README.md +72 -12
  4. package/dist/adapters/adapter-operation.d.ts +7 -0
  5. package/dist/compiler/contract.compiler.d.ts +6 -0
  6. package/dist/compiler/contract.compiler.js +73 -12
  7. package/dist/config/computed-fields.js +2 -9
  8. package/dist/config/config-diagnostic.guidance.js +3 -3
  9. package/dist/config/config.resolver.d.ts +14 -0
  10. package/dist/config/config.resolver.js +31 -0
  11. package/dist/config/config.validator.js +82 -0
  12. package/dist/config/exact-framework-config.d.ts +5 -0
  13. package/dist/config/framework-config.d.ts +26 -1
  14. package/dist/contracts/client-contract-structure.validator.d.ts +8 -0
  15. package/dist/contracts/client-contract-structure.validator.js +6 -1
  16. package/dist/contracts/field.d.ts +11 -0
  17. package/dist/framework/contract-view.d.ts +41 -10
  18. package/dist/framework/framework.factory.js +39 -11
  19. package/dist/framework/operation-facade.d.ts +24 -5
  20. package/dist/framework/operation-facade.js +2 -1
  21. package/dist/index.d.ts +4 -1
  22. package/dist/internal.d.ts +1 -0
  23. package/dist/internal.js +1 -0
  24. package/dist/operations/operation-arguments.d.ts +1 -1
  25. package/dist/operations/operation-call.d.ts +9 -5
  26. package/dist/operations/operation-result.d.ts +5 -3
  27. package/dist/operations/operation-validator-core.js +6 -0
  28. package/dist/policy/policy-admission.d.ts +22 -0
  29. package/dist/policy/policy-admission.js +400 -0
  30. package/dist/policy/policy-after-update.runtime.d.ts +29 -0
  31. package/dist/policy/policy-after-update.runtime.js +131 -0
  32. package/dist/policy/policy-condition.d.ts +15 -0
  33. package/dist/policy/policy-condition.js +36 -0
  34. package/dist/policy/policy-engine.d.ts +111 -0
  35. package/dist/policy/policy-engine.js +1 -0
  36. package/dist/policy/policy-nested-write.runtime.d.ts +38 -0
  37. package/dist/policy/policy-nested-write.runtime.js +449 -0
  38. package/dist/policy/policy-read.runtime.d.ts +83 -0
  39. package/dist/policy/policy-read.runtime.js +494 -0
  40. package/dist/policy/policy-request.d.ts +37 -0
  41. package/dist/policy/policy-request.js +52 -0
  42. package/dist/policy/policy-session.d.ts +83 -0
  43. package/dist/policy/policy-session.js +234 -0
  44. package/dist/policy/policy-startup.validator.d.ts +8 -0
  45. package/dist/policy/policy-startup.validator.js +106 -0
  46. package/dist/policy/policy-write.runtime.d.ts +30 -0
  47. package/dist/policy/policy-write.runtime.js +394 -0
  48. package/dist/prebuilt/core-version.generated.d.ts +2 -0
  49. package/dist/prebuilt/core-version.generated.js +1 -0
  50. package/dist/prebuilt/prebuilt-contracts.d.ts +40 -0
  51. package/dist/prebuilt/prebuilt-contracts.emitter.d.ts +16 -0
  52. package/dist/prebuilt/prebuilt-contracts.emitter.js +50 -0
  53. package/dist/prebuilt/prebuilt-contracts.js +1 -0
  54. package/dist/prebuilt/prebuilt-contracts.loader.d.ts +21 -0
  55. package/dist/prebuilt/prebuilt-contracts.loader.js +122 -0
  56. package/dist/prebuilt/prebuilt-inputs.fingerprint.d.ts +16 -0
  57. package/dist/prebuilt/prebuilt-inputs.fingerprint.js +192 -0
  58. package/dist/runtime/computed-read-runtime.d.ts +6 -1
  59. package/dist/runtime/computed-read-runtime.js +224 -45
  60. package/dist/runtime/context-executor.runtime.js +4 -2
  61. package/dist/runtime/execution-options.d.ts +8 -0
  62. package/dist/runtime/framework-warning.emitter.d.ts +6 -0
  63. package/dist/runtime/framework-warning.emitter.js +8 -0
  64. package/dist/runtime/level-lifecycle.runtime.js +3 -2
  65. package/dist/runtime/operation-code.guidance.js +3 -3
  66. package/dist/runtime/operation-request.d.ts +7 -0
  67. package/dist/runtime/operation-runtime.d.ts +9 -1
  68. package/dist/runtime/operation-runtime.js +114 -10
  69. package/dist/runtime/readonly-value.d.ts +5 -1
  70. package/dist/runtime/readonly-value.js +67 -28
  71. package/dist/transactions/transaction-runtime.d.ts +4 -1
  72. package/dist/transactions/transaction-runtime.js +27 -5
  73. package/package.json +2 -2
@@ -0,0 +1,83 @@
1
+ import type { AvAdapterOperationExecutor } from "../adapters/adapter-operation-executor.interface.js";
2
+ import type { ResolvedPolicyConfig } from "../config/config.resolver.js";
3
+ import type { AvRequestOrigin } from "../config/config-layer.js";
4
+ import type { AvContractCore } from "../contracts/contract.js";
5
+ import type { AdapterBoundary } from "../runtime/error-boundary.js";
6
+ import type { PolicyCondition } from "./policy-condition.js";
7
+ import type { AvPolicyWhere } from "./policy-engine.js";
8
+ /** An action a policy question names. */
9
+ export type PolicyAction = "read" | "create" | "update" | "delete";
10
+ /** `fieldsOf`'s answer, held: the names read into sets once. */
11
+ export type PolicyFields = {
12
+ /** `always` was `"all"`: every field that is not governed. */
13
+ readonly all: boolean;
14
+ /** The fields named in `always`, and those granted on every row. */
15
+ readonly listed: ReadonlySet<string>;
16
+ /** Each field readable on some rows only, and the condition those rows meet. */
17
+ readonly perRow: ReadonlyMap<string, AvPolicyWhere>;
18
+ };
19
+ /**
20
+ * A policy answer core cannot use: a fragment the Core Contract refuses, or an
21
+ * answer of the wrong shape. The engine is server code, so this is the server's
22
+ * defect (`A3000`), never the caller's.
23
+ */
24
+ export declare class PolicyDefectError extends Error {
25
+ readonly name = "PolicyDefectError";
26
+ }
27
+ /** Where `exists` runs: an executor, and the boundary its failures are classified by. */
28
+ export type PolicyExecution = {
29
+ readonly executor: AvAdapterOperationExecutor;
30
+ readonly boundary: AdapterBoundary;
31
+ };
32
+ /** One request's policy questions and answers. */
33
+ export type PolicySession = {
34
+ readonly principal: unknown;
35
+ readonly origin: AvRequestOrigin;
36
+ /** Where its `exists` runs now. */
37
+ readonly execution: PolicyExecution;
38
+ /** Whether the principal may run `action` on `resource` (`can`). */
39
+ can(resource: string, action: PolicyAction, row?: Readonly<Record<string, unknown>>): Promise<boolean>;
40
+ /** The rows the principal may `action` (`scopeOf`), checked. */
41
+ scope(resource: string, action: "read" | "update" | "delete"): Promise<PolicyCondition>;
42
+ /** The fields the principal may read or write (`fieldsOf`), checked. */
43
+ fields(resource: string, access: "read" | "write"): Promise<PolicyFields>;
44
+ /** Whether a row of `resource` meets `where`: core's own question, unchecked. */
45
+ exists(resource: string, where: AvPolicyWhere): Promise<boolean>;
46
+ /**
47
+ * The rows of `resource` meeting `where`, the scalar fields `select` names
48
+ * only: core's own read, unchecked, never cached (a write may change them).
49
+ */
50
+ rows(resource: string, where: AvPolicyWhere, select: readonly string[]): Promise<readonly Readonly<Record<string, unknown>>[]>;
51
+ /** Whether the engine lists `action` on `resource` in `afterChecks`. */
52
+ checksAfter(resource: string, action: "update"): boolean;
53
+ /**
54
+ * Notes a field a create sends that the principal may write on some rows
55
+ * only, with its grant: admission sees what the caller sent, and the row it
56
+ * creates is checked against the grant after the insert.
57
+ */
58
+ grantCreate(field: string, grant: AvPolicyWhere): void;
59
+ /** The grants admission noted for the fields a create sends, by field. */
60
+ createGrants(): ReadonlyMap<string, AvPolicyWhere>;
61
+ /** The same session, its `exists` running on `execution`. */
62
+ on(execution: PolicyExecution): PolicySession;
63
+ /**
64
+ * The same session with no `exists` answer kept: a write changed the rows,
65
+ * so a question asked before it may answer differently now.
66
+ */
67
+ renewed(): PolicySession;
68
+ };
69
+ /**
70
+ * Opens the policy session of one request: `principal` under `origin`, its
71
+ * `exists` on `execution` until {@link PolicySession.on} moves it.
72
+ */
73
+ export declare function openPolicySession(policy: ResolvedPolicyConfig, core: AvContractCore, principal: unknown, origin: AvRequestOrigin, execution: PolicyExecution): PolicySession;
74
+ /**
75
+ * Whether `fields` lets the principal use `field` of `resource` on every row it
76
+ * reaches: listed in `always`, or covered by `"all"` when it is not governed.
77
+ */
78
+ export declare function alwaysReadable(core: AvContractCore, resource: string, fields: PolicyFields, field: string): boolean;
79
+ /**
80
+ * How the principal may use `field`: on every row it reaches, on the rows a
81
+ * grant names, or not at all.
82
+ */
83
+ export declare function fieldAccess(core: AvContractCore, resource: string, fields: PolicyFields, field: string): "always" | AvPolicyWhere | "never";
@@ -0,0 +1,234 @@
1
+ import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
2
+ import { validateOperationArguments } from "../operations/operation-validator-core.js";
3
+ import { normalizeOperationScalarValues } from "../runtime/scalar-runtime.js";
4
+ export class PolicyDefectError extends Error {
5
+ name = "PolicyDefectError";
6
+ }
7
+ function isEmptyRecord(value) {
8
+ return (value !== null &&
9
+ typeof value === "object" &&
10
+ !Array.isArray(value) &&
11
+ Object.keys(value).length === 0);
12
+ }
13
+ function isRecord(value) {
14
+ return value !== null && typeof value === "object" && !Array.isArray(value);
15
+ }
16
+ function checkedFragment(core, resource, where, what) {
17
+ if (!isRecord(where)) {
18
+ throw new PolicyDefectError(`${what} must be a where object.`);
19
+ }
20
+ const request = {
21
+ resource,
22
+ family: "find",
23
+ variant: "many",
24
+ arguments: { where },
25
+ };
26
+ const validation = validateOperationArguments(core, request);
27
+ const normalized = validation.ok
28
+ ? normalizeOperationScalarValues(core, {
29
+ ...request,
30
+ arguments: validation.arguments,
31
+ }, "application")
32
+ : validation;
33
+ if (!normalized.ok) {
34
+ const issues = normalized.issues
35
+ .map((issue) => `${issue.code} at ${(issue.path ?? []).slice(2).join(".") || "where"}: ${issue.message}`)
36
+ .join("; ");
37
+ throw new PolicyDefectError(`${what} is no where of Resource "${resource}" in the Core Contract (${issues}).`);
38
+ }
39
+ return normalized.arguments.where;
40
+ }
41
+ function conditionOf(core, resource, answer, what) {
42
+ if (typeof answer === "boolean") {
43
+ return answer;
44
+ }
45
+ if (isEmptyRecord(answer)) {
46
+ return true;
47
+ }
48
+ return checkedFragment(core, resource, answer, what);
49
+ }
50
+ function fieldsAnswer(core, resource, answer) {
51
+ const what = `fieldsOf(${resource})`;
52
+ if (!isRecord(answer)) {
53
+ throw new PolicyDefectError(`${what} must answer { always, perRow }.`);
54
+ }
55
+ const { always, perRow } = answer;
56
+ if (always !== "all" &&
57
+ !(Array.isArray(always) && always.every((name) => typeof name === "string"))) {
58
+ throw new PolicyDefectError(`${what}.always must be "all" or a list of field names.`);
59
+ }
60
+ if (!isRecord(perRow)) {
61
+ throw new PolicyDefectError(`${what}.perRow must be an object.`);
62
+ }
63
+ const listed = new Set(always === "all" ? [] : always);
64
+ const grants = new Map();
65
+ for (const [field, grant] of Object.entries(perRow)) {
66
+ if (listed.has(field) || grant === undefined) {
67
+ continue;
68
+ }
69
+ const condition = conditionOf(core, resource, grant, `${what}.perRow.${field}`);
70
+ if (condition === true) {
71
+ listed.add(field);
72
+ }
73
+ else if (condition !== false) {
74
+ grants.set(field, condition);
75
+ }
76
+ }
77
+ return { all: always === "all", listed, perRow: grants };
78
+ }
79
+ function questionKey(resource, where) {
80
+ try {
81
+ return `${resource}\u0000${JSON.stringify(where, (_key, value) => typeof value === "bigint" ? `#bigint(${value})` : value)}`;
82
+ }
83
+ catch {
84
+ return undefined;
85
+ }
86
+ }
87
+ function existsOperation(core, resource, where) {
88
+ const contract = lookupResource(core, resource);
89
+ const [identifier] = contract?.identifiers ?? [];
90
+ const first = contract?.operations.find?.first !== undefined;
91
+ return first && identifier !== undefined
92
+ ? {
93
+ resource,
94
+ family: "find",
95
+ variant: "first",
96
+ arguments: { where, select: [...identifier] },
97
+ }
98
+ : {
99
+ resource,
100
+ family: "find",
101
+ variant: "count",
102
+ arguments: { where },
103
+ };
104
+ }
105
+ export function openPolicySession(policy, core, principal, origin, execution) {
106
+ const caches = {
107
+ scopes: new Map(),
108
+ createGrants: new Map(),
109
+ fields: new Map(),
110
+ exists: new WeakMap(),
111
+ };
112
+ return sessionOn(policy, core, principal, origin, execution, caches);
113
+ }
114
+ function sessionOn(policy, core, principal, origin, execution, caches) {
115
+ const { engine } = policy;
116
+ const exists = (resource, where) => {
117
+ const key = questionKey(resource, where);
118
+ let answers = caches.exists.get(execution.executor);
119
+ if (answers === undefined) {
120
+ answers = new Map();
121
+ caches.exists.set(execution.executor, answers);
122
+ }
123
+ const known = key === undefined ? undefined : answers.get(key);
124
+ if (known !== undefined) {
125
+ return known;
126
+ }
127
+ const operation = existsOperation(core, resource, where);
128
+ const answer = execution.boundary
129
+ .execute(() => execution.executor.execute(operation))
130
+ .then((result) => typeof result === "number" ? result > 0 : result != null);
131
+ if (key !== undefined) {
132
+ answers.set(key, answer);
133
+ }
134
+ return answer;
135
+ };
136
+ const engineExists = async (resource, where) => {
137
+ if (typeof resource !== "string" ||
138
+ lookupResource(core, resource) === undefined) {
139
+ throw new PolicyDefectError(`exists was asked about "${String(resource)}", which is no Resource of the Core Contract.`);
140
+ }
141
+ const condition = conditionOf(core, resource, where, "exists's where");
142
+ if (typeof condition === "boolean") {
143
+ return exists(resource, {});
144
+ }
145
+ return exists(resource, condition);
146
+ };
147
+ return {
148
+ principal,
149
+ origin,
150
+ execution,
151
+ async can(resource, action, row) {
152
+ const answer = await engine.can({
153
+ principal,
154
+ origin,
155
+ resource,
156
+ action,
157
+ ...(row === undefined ? {} : { row }),
158
+ exists: engineExists,
159
+ });
160
+ if (typeof answer !== "boolean") {
161
+ throw new PolicyDefectError(`can(${resource}, ${action}) must answer true or false.`);
162
+ }
163
+ return answer;
164
+ },
165
+ scope(resource, action) {
166
+ const key = `${resource}\u0000${action}`;
167
+ let scope = caches.scopes.get(key);
168
+ if (scope === undefined) {
169
+ scope = Promise.resolve()
170
+ .then(() => engine.scopeOf({ principal, origin, resource, action }))
171
+ .then((answer) => conditionOf(core, resource, answer, `scopeOf(${resource}, ${action})`));
172
+ caches.scopes.set(key, scope);
173
+ }
174
+ return scope;
175
+ },
176
+ fields(resource, access) {
177
+ const key = `${resource}\u0000${access}`;
178
+ let fields = caches.fields.get(key);
179
+ if (fields === undefined) {
180
+ fields = Promise.resolve()
181
+ .then(() => engine.fieldsOf({ principal, origin, resource, access }))
182
+ .then((answer) => fieldsAnswer(core, resource, answer));
183
+ caches.fields.set(key, fields);
184
+ }
185
+ return fields;
186
+ },
187
+ exists,
188
+ async rows(resource, where, select) {
189
+ if (lookupResource(core, resource)?.operations.find?.many === undefined) {
190
+ throw new PolicyDefectError(`The policy needs the rows of Resource "${resource}", and its Core Contract offers no find.many.`);
191
+ }
192
+ const operation = {
193
+ resource,
194
+ family: "find",
195
+ variant: "many",
196
+ arguments: { where, select: [...select] },
197
+ };
198
+ const answer = await execution.boundary.execute(() => execution.executor.execute(operation));
199
+ return Array.isArray(answer)
200
+ ? answer.filter((row) => isRecord(row))
201
+ : [];
202
+ },
203
+ grantCreate(field, grant) {
204
+ caches.createGrants.set(field, grant);
205
+ },
206
+ createGrants() {
207
+ return caches.createGrants;
208
+ },
209
+ checksAfter(resource, action) {
210
+ return (Object.hasOwn(policy.afterChecks, resource) &&
211
+ policy.afterChecks[resource]?.includes(action) === true);
212
+ },
213
+ on(next) {
214
+ return sessionOn(policy, core, principal, origin, next, caches);
215
+ },
216
+ renewed() {
217
+ return sessionOn(policy, core, principal, origin, execution, {
218
+ ...caches,
219
+ exists: new WeakMap(),
220
+ });
221
+ },
222
+ };
223
+ }
224
+ export function alwaysReadable(core, resource, fields, field) {
225
+ return (fields.listed.has(field) ||
226
+ (fields.all &&
227
+ lookupField(lookupResource(core, resource), field)?.governed !== true));
228
+ }
229
+ export function fieldAccess(core, resource, fields, field) {
230
+ if (alwaysReadable(core, resource, fields, field)) {
231
+ return "always";
232
+ }
233
+ return fields.perRow.get(field) ?? "never";
234
+ }
@@ -0,0 +1,8 @@
1
+ import type { ResolvedPolicyConfig } from "../config/config.resolver.js";
2
+ import type { AvConfigDiagnostic } from "../config/config-diagnostic.js";
3
+ import type { AvContractCore } from "../contracts/contract.js";
4
+ /**
5
+ * The policy's governed set, rule paths and after checks checked against the
6
+ * Core Contract: every problem, naming the Resource or the rule, or none.
7
+ */
8
+ export declare function policyStartupDiagnostics(core: AvContractCore, policy: ResolvedPolicyConfig): readonly AvConfigDiagnostic[];
@@ -0,0 +1,106 @@
1
+ import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
2
+ const TO_MANY_OPERATORS = new Set(["some", "every", "none"]);
3
+ function invalid(path, message) {
4
+ return { code: "CONFIG_INVALID_VALUE", path, message };
5
+ }
6
+ function governedDiagnostics(core, governed) {
7
+ const diagnostics = [];
8
+ for (const [resourceName, names] of Object.entries(governed)) {
9
+ const path = `policy.engine.governed.${resourceName}`;
10
+ const resource = lookupResource(core, resourceName);
11
+ if (resource === undefined) {
12
+ diagnostics.push(invalid(path, `${path}: "${resourceName}" is no Resource of the Core Contract`));
13
+ continue;
14
+ }
15
+ for (const name of names) {
16
+ if (lookupField(resource, name) === undefined) {
17
+ diagnostics.push(invalid(path, `${path}: "${name}" is no field or relation of Resource "${resourceName}" in the Core Contract`));
18
+ }
19
+ }
20
+ }
21
+ return diagnostics;
22
+ }
23
+ function afterCheckDiagnostics(core, afterChecks) {
24
+ return Object.keys(afterChecks).flatMap((resourceName) => {
25
+ const path = `policy.engine.afterChecks.${resourceName}`;
26
+ return lookupResource(core, resourceName) === undefined
27
+ ? [
28
+ invalid(path, `${path}: "${resourceName}" is no Resource of the Core Contract`),
29
+ ]
30
+ : [];
31
+ });
32
+ }
33
+ function pathFault(core, path) {
34
+ const [resourceName, ...segments] = path;
35
+ let resource = resourceName === undefined ? undefined : lookupResource(core, resourceName);
36
+ if (resource === undefined) {
37
+ return `"${resourceName ?? ""}" is no Resource of the Core Contract`;
38
+ }
39
+ let current = resourceName;
40
+ for (let index = 0; index < segments.length; index += 1) {
41
+ const segment = segments[index];
42
+ const field = lookupField(resource, segment);
43
+ if (field === undefined) {
44
+ return `"${segment}" is no field or relation of Resource "${current}"`;
45
+ }
46
+ const filters = field.capabilities.filter ?? [];
47
+ if (field.kind === "scalar") {
48
+ if (filters.length === 0) {
49
+ return `field "${current}.${segment}" cannot be filtered`;
50
+ }
51
+ const operator = segments[index + 1];
52
+ if (operator !== undefined && !filters.includes(operator)) {
53
+ return `field "${current}.${segment}" has no filter operator "${operator}"`;
54
+ }
55
+ if (index + 2 < segments.length) {
56
+ return `the path goes on past scalar field "${current}.${segment}"`;
57
+ }
58
+ return undefined;
59
+ }
60
+ const target = lookupResource(core, field.target);
61
+ if (target === undefined) {
62
+ return `relation "${current}.${segment}" leads to no Resource of the Core Contract`;
63
+ }
64
+ if (field.cardinality === "many") {
65
+ const operator = segments[index + 1];
66
+ if (operator === undefined || !TO_MANY_OPERATORS.has(operator)) {
67
+ return `to-many relation "${current}.${segment}" must be followed by some, every or none`;
68
+ }
69
+ if (!filters.includes(operator)) {
70
+ return `relation "${current}.${segment}" has no "${operator}" filter`;
71
+ }
72
+ index += 1;
73
+ }
74
+ else if (index + 1 === segments.length) {
75
+ return filters.includes("null")
76
+ ? undefined
77
+ : `relation "${current}.${segment}" has no null filter`;
78
+ }
79
+ else if (!filters.includes("match")) {
80
+ return `relation "${current}.${segment}" cannot filter its target directly`;
81
+ }
82
+ resource = target;
83
+ current = field.target;
84
+ }
85
+ return `the path ends at Resource "${current}", not at a field`;
86
+ }
87
+ function pathDiagnostics(core, paths) {
88
+ const diagnostics = [];
89
+ for (const [rule, list] of Object.entries(paths)) {
90
+ const at = `policy.engine.paths.${rule}`;
91
+ for (const path of list) {
92
+ const fault = pathFault(core, path);
93
+ if (fault !== undefined) {
94
+ diagnostics.push(invalid(at, `rule "${rule}" filters on "${path.join(".")}": ${fault}`));
95
+ }
96
+ }
97
+ }
98
+ return diagnostics;
99
+ }
100
+ export function policyStartupDiagnostics(core, policy) {
101
+ return [
102
+ ...governedDiagnostics(core, policy.governed),
103
+ ...pathDiagnostics(core, policy.paths),
104
+ ...afterCheckDiagnostics(core, policy.afterChecks),
105
+ ];
106
+ }
@@ -0,0 +1,30 @@
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 { PlannedReadOperation } from "../runtime/computed-read-runtime.js";
5
+ import type { AvOperationFailureResponse } from "../runtime/operation-response.js";
6
+ import { type PolicyRead } from "./policy-read.runtime.js";
7
+ import type { PolicySession } from "./policy-session.js";
8
+ /** A write's policy: what the adapter runs, and how a unique write's miss answers. */
9
+ export type PolicyWrite = PolicyRead & {
10
+ /** A unique write that matched nothing: `A4002` for a row the caller can read, `A2003` otherwise. */
11
+ readonly miss?: () => Promise<AvOperationFailureResponse>;
12
+ };
13
+ /**
14
+ * Whether a write the policy checks needs an interactive transaction to roll
15
+ * it back in. Where it has none, {@link enforceWritePolicy} refuses exactly
16
+ * these writes before they run. `planned` plans the request's read, asked only
17
+ * when the answer depends on the rows it returns.
18
+ */
19
+ export declare function writeNeedsTransaction(session: PolicySession, core: AvContractCore, request: AvOperationIdentity & {
20
+ readonly resource: string;
21
+ }, planned: () => PlannedReadOperation, adapterArguments: unknown): Promise<boolean>;
22
+ /**
23
+ * One write's policy (see the module comment): the refusal, the answer when it
24
+ * reaches no row, or what the adapter runs and how its answer is checked.
25
+ * `inTransaction` says whether the write runs in an interactive transaction a
26
+ * failed check rolls back.
27
+ */
28
+ export declare function enforceWritePolicy(session: PolicySession, core: AvContractCore, model: AvAdapterModel, request: AvOperationIdentity & {
29
+ readonly resource: string;
30
+ }, plan: PlannedReadOperation, adapterArguments: unknown, inTransaction: boolean): Promise<PolicyWrite>;