@aventara/core 1.0.0-rc.10 → 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.
- package/CHANGELOG.md +79 -0
- package/ERRORS.md +82 -82
- package/README.md +57 -1
- package/dist/compiler/contract-compiler-diagnostic.guidance.js +1 -1
- package/dist/compiler/contract.compiler.js +56 -9
- package/dist/compiler/create-requirements.projector.js +1 -3
- package/dist/compiler/field-validator.compiler.js +1 -3
- package/dist/compiler/managed-elsewhere.projector.js +5 -5
- package/dist/config/cache-stage.factory.d.ts +24 -0
- package/dist/config/cache-stage.factory.js +3 -0
- package/dist/config/config.resolver.js +1 -7
- package/dist/config/config.validator.js +61 -13
- package/dist/config/exact-framework-config.d.ts +6 -0
- package/dist/config/field-compute.resolver.js +1 -3
- package/dist/config/pipeline-config.d.ts +16 -0
- package/dist/config/pipeline-tree.resolver.js +2 -3
- package/dist/contracts/canonical-json.writer.d.ts +7 -0
- package/dist/contracts/canonical-json.writer.js +55 -0
- package/dist/contracts/client-contract-structure.validator.d.ts +64 -0
- package/dist/contracts/client-contract-structure.validator.js +12 -0
- package/dist/contracts/contract-canonicalizer.js +5 -0
- package/dist/contracts/contract-limits.d.ts +6 -0
- package/dist/contracts/contract-limits.js +2 -0
- package/dist/contracts/contract.d.ts +15 -0
- package/dist/contracts/json-schema.projector.js +1 -3
- package/dist/framework/access-facade.js +1 -3
- package/dist/framework/framework.factory.js +42 -13
- package/dist/framework/operation-facade.d.ts +67 -6
- package/dist/framework/operation-facade.js +7 -0
- package/dist/framework/operation-set.runner.d.ts +47 -0
- package/dist/framework/operation-set.runner.js +168 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/internal.d.ts +2 -0
- package/dist/internal.js +2 -0
- package/dist/operations/operation-validator-core.js +2 -4
- package/dist/operations/structural-limits.walker.js +1 -3
- package/dist/policy/access-question.runtime.js +1 -3
- package/dist/policy/policy-admission.js +1 -3
- package/dist/policy/policy-after-update.runtime.js +1 -3
- package/dist/policy/policy-nested-write.runtime.js +3 -12
- package/dist/policy/policy-read.runtime.js +1 -3
- package/dist/policy/policy-reference-write.runtime.js +2 -11
- package/dist/policy/policy-request.d.ts +31 -0
- package/dist/policy/policy-request.js +69 -17
- package/dist/policy/policy-session.js +1 -3
- package/dist/policy/policy-write.runtime.js +1 -3
- package/dist/prebuilt/core-version.generated.d.ts +1 -1
- package/dist/prebuilt/core-version.generated.js +1 -1
- package/dist/prebuilt/prebuilt-changes.js +1 -3
- package/dist/prebuilt/prebuilt-contracts.loader.js +1 -3
- package/dist/prebuilt/prebuilt-inputs.fingerprint.js +1 -3
- package/dist/protocol/absent-route.responder.d.ts +69 -16
- package/dist/protocol/absent-route.responder.js +75 -31
- package/dist/protocol/contract-document.responder.d.ts +7 -0
- package/dist/protocol/contract-document.responder.js +3 -2
- package/dist/protocol/http-request.decoder.d.ts +33 -0
- package/dist/protocol/http-request.decoder.js +23 -7
- package/dist/protocol/http-request.reader.d.ts +4 -0
- package/dist/protocol/http-request.reader.js +11 -2
- package/dist/protocol/http-response.encoder.d.ts +12 -0
- package/dist/protocol/http-response.encoder.js +15 -1
- package/dist/protocol/operation-response.guard.js +1 -3
- package/dist/protocol/operation-set.decoder.d.ts +48 -0
- package/dist/protocol/operation-set.decoder.js +185 -0
- package/dist/protocol/operation-set.handler.d.ts +27 -0
- package/dist/protocol/operation-set.handler.js +179 -0
- package/dist/protocol/protocol-headers.d.ts +9 -0
- package/dist/protocol/protocol-headers.js +12 -5
- package/dist/protocol/protocol-surface.d.ts +12 -4
- package/dist/protocol/protocol-surface.js +34 -1
- package/dist/protocol/protocol.binder.d.ts +37 -1
- package/dist/protocol/protocol.binder.js +2 -0
- package/dist/protocol/protocol.handler.d.ts +35 -2
- package/dist/protocol/protocol.handler.js +84 -8
- package/dist/protocol/read-cache.responder.d.ts +26 -0
- package/dist/protocol/read-cache.responder.js +47 -0
- package/dist/protocol/read-query.codec.d.ts +34 -0
- package/dist/protocol/read-query.codec.js +125 -0
- package/dist/runtime/computed-read-runtime.js +2 -8
- package/dist/runtime/create-requirement.js +1 -3
- package/dist/runtime/diagnostics-reporter.d.ts +6 -1
- package/dist/runtime/diagnostics-reporter.js +3 -8
- package/dist/runtime/error.catalog.d.ts +2 -2
- package/dist/runtime/error.catalog.js +1 -1
- package/dist/runtime/field-stage.runtime.d.ts +0 -2
- package/dist/runtime/field-stage.runtime.js +1 -7
- package/dist/runtime/field-validator.runtime.js +2 -1
- package/dist/runtime/mutation-planner.js +1 -3
- package/dist/runtime/nested-write.walker.js +2 -1
- package/dist/runtime/output-validator.js +1 -3
- package/dist/runtime/owned-execution.registry.d.ts +22 -2
- package/dist/runtime/pipeline.d.ts +5 -1
- package/dist/runtime/record.guard.d.ts +14 -0
- package/dist/runtime/record.guard.js +10 -0
- package/dist/runtime/request-envelope.validator.js +13 -10
- package/dist/runtime/scalar-runtime.d.ts +0 -10
- package/dist/runtime/scalar-runtime.js +1 -10
- package/dist/runtime/thenable.guard.d.ts +7 -0
- package/dist/runtime/thenable.guard.js +5 -0
- package/dist/runtime/unsent-value.normalizer.js +2 -1
- package/dist/transactions/operation-arguments-encoder.js +1 -7
- package/dist/transactions/sql-statement.validator.js +2 -1
- package/dist/transactions/transaction-plan-assembler.js +1 -7
- package/dist/transactions/transaction-plan-validator.js +4 -6
- package/dist/transactions/transaction-reference-collector.js +8 -10
- package/dist/transactions/transaction-reference-resolver.js +1 -3
- package/dist/transactions/transaction-runtime.js +31 -2
- package/dist/transactions/transaction-stage.runtime.js +1 -3
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { lookupOwn, lookupResource } from "../contracts/contract-lookup.js";
|
|
2
|
+
import { AVENTARA_PROTOCOL_VERSION } from "../contracts/contract-protocol.js";
|
|
2
3
|
import { isOperationVariantAvailable, } from "../contracts/resource-operations.js";
|
|
3
4
|
import { parseOperationIdentity, } from "../operations/operation-identity.js";
|
|
4
5
|
import { operationUnavailableIssue } from "../operations/operation-validator.js";
|
|
@@ -9,12 +10,15 @@ import { INTERNAL_FAILURE_MESSAGE, operationFailure, } from "../runtime/operatio
|
|
|
9
10
|
import { protocolIdentityFailure } from "../runtime/operation-runtime.js";
|
|
10
11
|
import { mapValidationOutcome } from "../runtime/outcome-mapper.js";
|
|
11
12
|
import { transactionsUnavailableFailure } from "../transactions/transaction-plan-validator.js";
|
|
12
|
-
import { ACCESS_CAN_ROUTE, CONTRACT_ROUTE, GET_ONLY, isProtocolPath, operationRouteSegments, POST_ONLY, readRequest, TRANSACTIONS_ROUTE, } from "./http-request.reader.js";
|
|
13
|
+
import { ACCESS_CAN_ROUTE, ALL_ROUTE, CONTRACT_ROUTE, GET_AND_POST, GET_ONLY, isProtocolPath, operationRouteSegments, POST_ONLY, readRequest, TRANSACTIONS_ROUTE, } from "./http-request.reader.js";
|
|
13
14
|
import { encodeOperationResponse, } from "./http-response.encoder.js";
|
|
14
15
|
import { identityHeadersMissing, transportFailure, } from "./protocol-failure.js";
|
|
15
|
-
import { missingIdentityHeaders } from "./protocol-headers.js";
|
|
16
|
+
import { missingIdentityHeaders, NO_SNIFF, protocolVersionOf, } from "./protocol-headers.js";
|
|
17
|
+
import { advertisesAnyOperation } from "./protocol-surface.js";
|
|
18
|
+
import { uncachedAnswer } from "./read-cache.responder.js";
|
|
19
|
+
import { readQueryOf } from "./read-query.codec.js";
|
|
16
20
|
import { AvUnmountedRouteError } from "./unmounted-route.error.js";
|
|
17
|
-
function resolveOperationRoute(contracts, protocol, route, origin = "client") {
|
|
21
|
+
export function resolveOperationRoute(contracts, protocol, route, origin = "client") {
|
|
18
22
|
const identityFailure = protocolIdentityFailure(contracts, protocol, origin);
|
|
19
23
|
if (identityFailure !== undefined) {
|
|
20
24
|
return { ok: false, failure: identityFailure };
|
|
@@ -58,50 +62,82 @@ function accessCanRouteFailure(contracts, protocol, origin = "client") {
|
|
|
58
62
|
? accessCanUnavailableFailure()
|
|
59
63
|
: undefined));
|
|
60
64
|
}
|
|
65
|
+
function allRouteFailure(contracts, protocol, origin = "client") {
|
|
66
|
+
const identityFailure = protocolIdentityFailure(contracts, protocol, origin);
|
|
67
|
+
if (identityFailure !== undefined) {
|
|
68
|
+
return identityFailure;
|
|
69
|
+
}
|
|
70
|
+
if (advertisesAnyOperation(contracts[origin])) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
const message = "Operation Sets are not available on the active Contract.";
|
|
74
|
+
return operationFailure("A2002", {
|
|
75
|
+
message,
|
|
76
|
+
issues: [validationIssue("V1006", ["operations"], message)],
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function readIdentityOf(read, query) {
|
|
80
|
+
return {
|
|
81
|
+
version: protocolVersionOf(read.headers) ?? AVENTARA_PROTOCOL_VERSION,
|
|
82
|
+
hash: query.v,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
61
85
|
export function checkOperationRoute(contracts, read, origin = "client") {
|
|
86
|
+
const check = operationRouteCheck(contracts, read, origin);
|
|
87
|
+
return check.ok || read.request.method !== "GET"
|
|
88
|
+
? check
|
|
89
|
+
: refusedWith(uncachedAnswer(check.response));
|
|
90
|
+
}
|
|
91
|
+
function operationRouteCheck(contracts, read, origin) {
|
|
62
92
|
const segments = operationRouteSegments(read.request.path);
|
|
63
93
|
if (segments === undefined) {
|
|
64
94
|
return refusedWith(transportFailure("A2000", read.requestId));
|
|
65
95
|
}
|
|
66
|
-
|
|
96
|
+
const get = read.request.method === "GET";
|
|
97
|
+
const queried = get && read.request.query !== undefined && read.request.query !== "";
|
|
98
|
+
const query = queried ? readQueryOf(read.request.query) : undefined;
|
|
99
|
+
if (queried && query === undefined) {
|
|
100
|
+
return refusedWith(transportFailure("A2000", read.requestId));
|
|
101
|
+
}
|
|
102
|
+
const protocol = query === undefined ? read.protocol : readIdentityOf(read, query);
|
|
103
|
+
if (protocol === undefined) {
|
|
67
104
|
return refusedWith(identityFailureOf(read));
|
|
68
105
|
}
|
|
69
|
-
const route = resolveOperationRoute(contracts,
|
|
106
|
+
const route = resolveOperationRoute(contracts, protocol, segments, origin);
|
|
70
107
|
if (!route.ok) {
|
|
71
108
|
return refusedWith(encodeOperationResponse(route.failure, read.requestId));
|
|
72
109
|
}
|
|
73
|
-
|
|
110
|
+
const { family, variant } = route.operation;
|
|
111
|
+
const marks = family === "find"
|
|
112
|
+
? lookupResource(contracts[origin], segments.resource)?.cache
|
|
113
|
+
: undefined;
|
|
114
|
+
const cache = family === "find"
|
|
115
|
+
? lookupOwn(marks, variant)
|
|
116
|
+
: undefined;
|
|
117
|
+
return read.request.method === "POST" || (get && cache !== undefined)
|
|
74
118
|
? {
|
|
75
119
|
ok: true,
|
|
76
|
-
protocol
|
|
120
|
+
protocol,
|
|
77
121
|
resource: segments.resource,
|
|
78
122
|
operation: route.operation,
|
|
123
|
+
cache,
|
|
124
|
+
query,
|
|
79
125
|
}
|
|
80
|
-
: refusedWith(transportFailure("A2012", read.requestId, POST_ONLY));
|
|
81
|
-
}
|
|
82
|
-
export function checkTransactionsRoute(contracts, read, origin = "client") {
|
|
83
|
-
if (read.request.path !== TRANSACTIONS_ROUTE) {
|
|
84
|
-
return refusedWith(transportFailure("A2000", read.requestId));
|
|
85
|
-
}
|
|
86
|
-
if (read.protocol === undefined) {
|
|
87
|
-
return refusedWith(identityFailureOf(read));
|
|
88
|
-
}
|
|
89
|
-
const absent = transactionsRouteFailure(contracts, read.protocol, origin);
|
|
90
|
-
if (absent !== undefined) {
|
|
91
|
-
return refusedWith(encodeOperationResponse(absent, read.requestId));
|
|
92
|
-
}
|
|
93
|
-
return read.request.method === "POST"
|
|
94
|
-
? { ok: true, protocol: read.protocol }
|
|
95
|
-
: refusedWith(transportFailure("A2012", read.requestId, POST_ONLY));
|
|
126
|
+
: refusedWith(transportFailure("A2012", read.requestId, cache === undefined ? POST_ONLY : GET_AND_POST));
|
|
96
127
|
}
|
|
97
|
-
|
|
98
|
-
|
|
128
|
+
const POST_ROUTE_FAILURES = {
|
|
129
|
+
[TRANSACTIONS_ROUTE]: transactionsRouteFailure,
|
|
130
|
+
[ACCESS_CAN_ROUTE]: accessCanRouteFailure,
|
|
131
|
+
[ALL_ROUTE]: allRouteFailure,
|
|
132
|
+
};
|
|
133
|
+
export function checkPostRoute(route, contracts, read, origin = "client") {
|
|
134
|
+
if (read.request.path !== route) {
|
|
99
135
|
return refusedWith(transportFailure("A2000", read.requestId));
|
|
100
136
|
}
|
|
101
137
|
if (read.protocol === undefined) {
|
|
102
138
|
return refusedWith(identityFailureOf(read));
|
|
103
139
|
}
|
|
104
|
-
const absent =
|
|
140
|
+
const absent = POST_ROUTE_FAILURES[route](contracts, read.protocol, origin);
|
|
105
141
|
if (absent !== undefined) {
|
|
106
142
|
return refusedWith(encodeOperationResponse(absent, read.requestId));
|
|
107
143
|
}
|
|
@@ -126,15 +162,19 @@ export function answerAbsentRequest(contracts, received, sink, origin = "client"
|
|
|
126
162
|
}
|
|
127
163
|
if (path === CONTRACT_ROUTE) {
|
|
128
164
|
return method === "GET"
|
|
129
|
-
? unmounted(read.requestId)
|
|
165
|
+
? withNoSniff(unmounted(read.requestId))
|
|
130
166
|
: transportFailure("A2012", read.requestId, GET_ONLY);
|
|
131
167
|
}
|
|
168
|
+
if (path === ALL_ROUTE) {
|
|
169
|
+
const route = checkPostRoute(ALL_ROUTE, contracts, read, origin);
|
|
170
|
+
return route.ok ? unmounted(read.requestId) : route.response;
|
|
171
|
+
}
|
|
132
172
|
if (path === ACCESS_CAN_ROUTE) {
|
|
133
|
-
const route =
|
|
173
|
+
const route = checkPostRoute(ACCESS_CAN_ROUTE, contracts, read, origin);
|
|
134
174
|
return route.ok ? unmounted(read.requestId) : route.response;
|
|
135
175
|
}
|
|
136
176
|
if (path === TRANSACTIONS_ROUTE) {
|
|
137
|
-
const route =
|
|
177
|
+
const route = checkPostRoute(TRANSACTIONS_ROUTE, contracts, read, origin);
|
|
138
178
|
if (!route.ok) {
|
|
139
179
|
return route.response;
|
|
140
180
|
}
|
|
@@ -150,6 +190,7 @@ export function answerAbsentRequest(contracts, received, sink, origin = "client"
|
|
|
150
190
|
return route.response;
|
|
151
191
|
}
|
|
152
192
|
const { family, variant } = route.operation;
|
|
193
|
+
const answer = unmounted(read.requestId);
|
|
153
194
|
createReporter(sink, {
|
|
154
195
|
kind: "operation",
|
|
155
196
|
origin,
|
|
@@ -157,8 +198,11 @@ export function answerAbsentRequest(contracts, received, sink, origin = "client"
|
|
|
157
198
|
family,
|
|
158
199
|
variant,
|
|
159
200
|
requestId: read.requestId,
|
|
160
|
-
})(new AvUnmountedRouteError(`${route.resource}.${family}.${variant}`,
|
|
161
|
-
return
|
|
201
|
+
})(new AvUnmountedRouteError(`${route.resource}.${family}.${variant}`, `${method} ${path}`), "A3000");
|
|
202
|
+
return method === "GET" ? uncachedAnswer(answer) : answer;
|
|
203
|
+
}
|
|
204
|
+
function withNoSniff(response) {
|
|
205
|
+
return { ...response, headers: { ...response.headers, ...NO_SNIFF } };
|
|
162
206
|
}
|
|
163
207
|
function unmounted(requestId) {
|
|
164
208
|
return encodeOperationResponse(operationFailure("A3000", { message: INTERNAL_FAILURE_MESSAGE }), requestId);
|
|
@@ -17,4 +17,11 @@ export declare function contractDocumentOf(client: AvContractClient): ContractDo
|
|
|
17
17
|
* order:
|
|
18
18
|
*/
|
|
19
19
|
export declare function encodeContractRequest(document: ContractDocument, received: AvProtocolRequest): AvProtocolResponse;
|
|
20
|
+
/**
|
|
21
|
+
* Whether `If-None-Match` names the current representation: `*`, or a list holding an
|
|
22
|
+
* entity tag whose opaque tag equals `etag`'s — the WEAK comparison, which `If-None-
|
|
23
|
+
* Match` uses, so a `W/` prefix does not matter. A value that is not a well-formed
|
|
24
|
+
* list matches nothing; an absent header matches nothing.
|
|
25
|
+
*/
|
|
26
|
+
export declare function noneMatchHolds(value: string | undefined, etag: string): boolean;
|
|
20
27
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { canonicalContractTextOf } from "../contracts/contract-canonicalizer.js";
|
|
2
2
|
import { CONTRACT_ROUTE, GET_ONLY, readRequest, } from "./http-request.reader.js";
|
|
3
3
|
import { transportFailure } from "./protocol-failure.js";
|
|
4
|
-
import { headers, headerValue } from "./protocol-headers.js";
|
|
4
|
+
import { headers, headerValue, NO_SNIFF } from "./protocol-headers.js";
|
|
5
5
|
export function contractDocumentOf(client) {
|
|
6
6
|
return Object.freeze({
|
|
7
7
|
body: canonicalContractTextOf(client),
|
|
@@ -23,6 +23,7 @@ export function encodeContractRequest(document, received) {
|
|
|
23
23
|
const validators = {
|
|
24
24
|
ETag: document.etag,
|
|
25
25
|
"Cache-Control": "no-cache",
|
|
26
|
+
...NO_SNIFF,
|
|
26
27
|
[headers.requestId]: requestId,
|
|
27
28
|
};
|
|
28
29
|
return noneMatchHolds(headerValue(read.headers, "if-none-match"), document.etag)
|
|
@@ -34,7 +35,7 @@ export function encodeContractRequest(document, received) {
|
|
|
34
35
|
};
|
|
35
36
|
}
|
|
36
37
|
const IF_NONE_MATCH_MEMBER = /[ \t]*(?:(?:W\/)?("[\x21\x23-\x7e\x80-\xff]*"))?[ \t]*(,|$)/y;
|
|
37
|
-
function noneMatchHolds(value, etag) {
|
|
38
|
+
export function noneMatchHolds(value, etag) {
|
|
38
39
|
if (value === undefined) {
|
|
39
40
|
return false;
|
|
40
41
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { AvContracts } from "../compiler/contract.compiler.js";
|
|
2
2
|
import type { RemoteRequestOrigin } from "../config/config-layer.js";
|
|
3
3
|
import type { AvRequestProtocol, RemoteOperationRequest } from "../runtime/operation-request.js";
|
|
4
|
+
import type { AvTransportContext } from "../runtime/pipeline.js";
|
|
4
5
|
import type { AvAgentTransactionRequest, AvClientTransactionRequest } from "../transactions/transaction-request.js";
|
|
6
|
+
import { type ReadRequest } from "./http-request.reader.js";
|
|
5
7
|
import type { AvProtocolResponse } from "./http-response.encoder.js";
|
|
6
8
|
/** One HTTP request as a host hands it to the protocol. */
|
|
7
9
|
export type AvProtocolRequest = {
|
|
@@ -13,6 +15,12 @@ export type AvProtocolRequest = {
|
|
|
13
15
|
* in the same form as the paths `protocol.surface()` returns.
|
|
14
16
|
*/
|
|
15
17
|
readonly path: string;
|
|
18
|
+
/**
|
|
19
|
+
* The request URL's query, as received (not yet URL-decoded) and without its
|
|
20
|
+
* `?`: `q=…&v=…`. Read only by a cached read's `GET`; omitted or `""` when
|
|
21
|
+
* the URL has none.
|
|
22
|
+
*/
|
|
23
|
+
readonly query?: string;
|
|
16
24
|
/**
|
|
17
25
|
* The request headers as the host received them. Names in any case (they
|
|
18
26
|
* are case-insensitive); a repeated header as its list of values; an entry
|
|
@@ -62,6 +70,8 @@ export type DecodedTransaction = Decoded<AvClientTransactionRequest | AvAgentTra
|
|
|
62
70
|
* the client-origin request `framework.execute` takes, or its answer.
|
|
63
71
|
*/
|
|
64
72
|
export declare function decodeOperationRequest(contracts: AvContracts, received: AvProtocolRequest, origin?: RemoteRequestOrigin): DecodedOperation;
|
|
73
|
+
/** {@link decodeOperationRequest} over a request already read. */
|
|
74
|
+
export declare function decodeOperationRead(contracts: AvContracts, read: ReadRequest, origin: RemoteRequestOrigin): DecodedOperation;
|
|
65
75
|
/**
|
|
66
76
|
* `protocol.decodeTransaction(request)` — the `/_transactions` request as the client-
|
|
67
77
|
* origin plan request `framework.executeTransaction` takes, or its answer. The same
|
|
@@ -87,10 +97,33 @@ type DecodedCanRequest = {
|
|
|
87
97
|
* judge.
|
|
88
98
|
*/
|
|
89
99
|
export declare function decodeCanRequest(contracts: AvContracts, received: AvProtocolRequest, origin?: RemoteRequestOrigin): Decoded<DecodedCanRequest>;
|
|
100
|
+
/**
|
|
101
|
+
* A client request's method — `POST`, or `GET` for a cached read — and its
|
|
102
|
+
* headers, in one spelling.
|
|
103
|
+
*/
|
|
104
|
+
export declare function transportOf(read: ReadRequest): AvTransportContext;
|
|
105
|
+
/**
|
|
106
|
+
* Step 5, after the route checks passed: the body as a JSON object, or the answer
|
|
107
|
+
* refusing it. Only the raw arm is measured, typed and parsed.
|
|
108
|
+
*/
|
|
109
|
+
export declare function readBody(read: ReadRequest, contracts: AvContracts, origin: RemoteRequestOrigin): {
|
|
110
|
+
readonly ok: true;
|
|
111
|
+
readonly value: object;
|
|
112
|
+
} | {
|
|
113
|
+
readonly ok: false;
|
|
114
|
+
readonly response: AvProtocolResponse;
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Records `value` as a body the protocol parsed itself: a cached read's
|
|
118
|
+
* arguments, `JSON.parse` output of its `q` that nothing else holds.
|
|
119
|
+
*/
|
|
120
|
+
export declare function adoptParsedBody(value: object): void;
|
|
90
121
|
/**
|
|
91
122
|
* Whether `value` is a body this decoder parsed from raw content. Only the protocol's
|
|
92
123
|
* own shortcuts ask, about a request they have just decoded, before any host code
|
|
93
124
|
* could hold it; it is exported from no entry point.
|
|
94
125
|
*/
|
|
95
126
|
export declare function isParsedByDecoder(value: unknown): boolean;
|
|
127
|
+
/** Marks `value`, a part of a body this decoder parsed from raw content, as parsed here. */
|
|
128
|
+
export declare function markParsedByDecoder(value: object): void;
|
|
96
129
|
export {};
|
|
@@ -1,16 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { readRequest } from "./http-request.reader.js";
|
|
1
|
+
import { checkOperationRoute, checkPostRoute, } from "./absent-route.responder.js";
|
|
2
|
+
import { ACCESS_CAN_ROUTE, POST_ONLY, readRequest, TRANSACTIONS_ROUTE, } from "./http-request.reader.js";
|
|
3
3
|
import { transportFailure, } from "./protocol-failure.js";
|
|
4
4
|
import { headerValue } from "./protocol-headers.js";
|
|
5
|
+
import { uncachedAnswer } from "./read-cache.responder.js";
|
|
5
6
|
export function decodeOperationRequest(contracts, received, origin = "client") {
|
|
6
7
|
const read = readRequest(received);
|
|
7
8
|
if (!read.ok) {
|
|
8
9
|
return read;
|
|
9
10
|
}
|
|
11
|
+
return decodeOperationRead(contracts, read, origin);
|
|
12
|
+
}
|
|
13
|
+
export function decodeOperationRead(contracts, read, origin) {
|
|
10
14
|
const route = checkOperationRoute(contracts, read, origin);
|
|
11
15
|
if (!route.ok) {
|
|
12
16
|
return refused(route.response, read.requestId);
|
|
13
17
|
}
|
|
18
|
+
if (read.request.method !== "POST") {
|
|
19
|
+
return refused(uncachedAnswer(transportFailure("A2012", read.requestId, POST_ONLY)), read.requestId);
|
|
20
|
+
}
|
|
14
21
|
const body = readBody(read, contracts, origin);
|
|
15
22
|
return body.ok
|
|
16
23
|
? {
|
|
@@ -33,7 +40,7 @@ export function decodeTransactionRequest(contracts, received, origin = "client")
|
|
|
33
40
|
if (!read.ok) {
|
|
34
41
|
return read;
|
|
35
42
|
}
|
|
36
|
-
const route =
|
|
43
|
+
const route = checkPostRoute(TRANSACTIONS_ROUTE, contracts, read, origin);
|
|
37
44
|
if (!route.ok) {
|
|
38
45
|
return refused(route.response, read.requestId);
|
|
39
46
|
}
|
|
@@ -57,7 +64,7 @@ export function decodeCanRequest(contracts, received, origin = "client") {
|
|
|
57
64
|
if (!read.ok) {
|
|
58
65
|
return read;
|
|
59
66
|
}
|
|
60
|
-
const route =
|
|
67
|
+
const route = checkPostRoute(ACCESS_CAN_ROUTE, contracts, read, origin);
|
|
61
68
|
if (!route.ok) {
|
|
62
69
|
return refused(route.response, read.requestId);
|
|
63
70
|
}
|
|
@@ -78,10 +85,13 @@ export function decodeCanRequest(contracts, received, origin = "client") {
|
|
|
78
85
|
function refused(response, requestId) {
|
|
79
86
|
return { ok: false, response, requestId };
|
|
80
87
|
}
|
|
81
|
-
function transportOf(read) {
|
|
82
|
-
return {
|
|
88
|
+
export function transportOf(read) {
|
|
89
|
+
return {
|
|
90
|
+
method: read.request.method === "GET" ? "GET" : "POST",
|
|
91
|
+
headers: read.headers,
|
|
92
|
+
};
|
|
83
93
|
}
|
|
84
|
-
function readBody(read, contracts, origin) {
|
|
94
|
+
export function readBody(read, contracts, origin) {
|
|
85
95
|
const code = bodyValue(read.request.body, headerValue(read.headers, "content-type"), contracts[origin].limits.maxRequestBytes);
|
|
86
96
|
return typeof code === "string"
|
|
87
97
|
? { ok: false, response: transportFailure(code, read.requestId) }
|
|
@@ -115,9 +125,15 @@ function bodyValue(body, contentType, maxRequestBytes) {
|
|
|
115
125
|
return { value };
|
|
116
126
|
}
|
|
117
127
|
const PARSED_HERE = new WeakSet();
|
|
128
|
+
export function adoptParsedBody(value) {
|
|
129
|
+
PARSED_HERE.add(value);
|
|
130
|
+
}
|
|
118
131
|
export function isParsedByDecoder(value) {
|
|
119
132
|
return typeof value === "object" && value !== null && PARSED_HERE.has(value);
|
|
120
133
|
}
|
|
134
|
+
export function markParsedByDecoder(value) {
|
|
135
|
+
PARSED_HERE.add(value);
|
|
136
|
+
}
|
|
121
137
|
function isJsonMediaType(contentType) {
|
|
122
138
|
return (contentType
|
|
123
139
|
?.split(";")[0]
|
|
@@ -6,10 +6,14 @@ import type { AvProtocolResponse } from "./http-response.encoder.js";
|
|
|
6
6
|
export declare const TRANSACTIONS_ROUTE = "/_transactions";
|
|
7
7
|
/** `POST <api>/_access/can`. */
|
|
8
8
|
export declare const ACCESS_CAN_ROUTE = "/_access/can";
|
|
9
|
+
/** `POST <api>/_all`: one Operation Set. */
|
|
10
|
+
export declare const ALL_ROUTE = "/_all";
|
|
9
11
|
/** `GET <api>/_contract`. */
|
|
10
12
|
export declare const CONTRACT_ROUTE = "/_contract";
|
|
11
13
|
/** The methods an operation or `/_transactions` route accepts: `POST`, only. */
|
|
12
14
|
export declare const POST_ONLY: readonly string[];
|
|
15
|
+
/** The methods a cached read's route accepts: `GET` and `POST`. */
|
|
16
|
+
export declare const GET_AND_POST: readonly string[];
|
|
13
17
|
/** The methods the contract route accepts: `GET`, only. */
|
|
14
18
|
export declare const GET_ONLY: readonly string[];
|
|
15
19
|
/**
|
|
@@ -4,8 +4,10 @@ import { adoptedRequestId, normalizeHeaders, protocolIdentityOf, } from "./proto
|
|
|
4
4
|
const OPERATION_ROUTE = /^\/_resources\/([^/]*)\/([^/]*)\/([^/]*)$/;
|
|
5
5
|
export const TRANSACTIONS_ROUTE = "/_transactions";
|
|
6
6
|
export const ACCESS_CAN_ROUTE = "/_access/can";
|
|
7
|
+
export const ALL_ROUTE = "/_all";
|
|
7
8
|
export const CONTRACT_ROUTE = "/_contract";
|
|
8
9
|
export const POST_ONLY = Object.freeze(["POST"]);
|
|
10
|
+
export const GET_AND_POST = Object.freeze(["GET", "POST"]);
|
|
9
11
|
export const GET_ONLY = Object.freeze(["GET"]);
|
|
10
12
|
export function isProtocolPath(path) {
|
|
11
13
|
return path.startsWith("/_");
|
|
@@ -54,14 +56,21 @@ function malformedRequest() {
|
|
|
54
56
|
}
|
|
55
57
|
function wellFormedRequest(received) {
|
|
56
58
|
try {
|
|
57
|
-
const { method, path, headers, body } = received;
|
|
59
|
+
const { method, path, query, headers, body } = received;
|
|
58
60
|
const copied = headerRecord(headers);
|
|
59
61
|
const armed = armedBody(body);
|
|
60
62
|
return typeof method === "string" &&
|
|
61
63
|
typeof path === "string" &&
|
|
64
|
+
(query === undefined || typeof query === "string") &&
|
|
62
65
|
copied !== undefined &&
|
|
63
66
|
armed !== undefined
|
|
64
|
-
? {
|
|
67
|
+
? {
|
|
68
|
+
method,
|
|
69
|
+
path,
|
|
70
|
+
...(query === undefined ? {} : { query }),
|
|
71
|
+
headers: copied,
|
|
72
|
+
body: armed,
|
|
73
|
+
}
|
|
65
74
|
: undefined;
|
|
66
75
|
}
|
|
67
76
|
catch {
|
|
@@ -38,3 +38,15 @@ export declare function encodeOperationResponse(response: AvOperationResponse, r
|
|
|
38
38
|
* as `cause.operation`, as every internal failure inside a plan does.
|
|
39
39
|
*/
|
|
40
40
|
export declare function encodeTransactionResponse(response: AvTransactionResponse, requestId: string, report?: UnencodableReport): AvProtocolResponse;
|
|
41
|
+
/**
|
|
42
|
+
* An Operation Set's answer: `200 A1000`, `data` holding each entry's envelope
|
|
43
|
+
* text under its key, in the order given.
|
|
44
|
+
*/
|
|
45
|
+
export declare function encodeOperationSetResponse(entries: readonly {
|
|
46
|
+
readonly key: string;
|
|
47
|
+
readonly envelope: string;
|
|
48
|
+
}[], requestId: string): AvProtocolResponse;
|
|
49
|
+
/** One line of a streamed Operation Set answer: the entry's key and its envelope text. */
|
|
50
|
+
export declare function operationSetLine(key: string, envelope: string): string;
|
|
51
|
+
/** The headers of a streamed Operation Set answer. */
|
|
52
|
+
export declare function streamedHeaders(requestId: string): Readonly<Record<string, string>>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { encodeBase64, WIRE_SCALAR_PATTERNS, } from "../contracts/wire-scalar-format.js";
|
|
2
2
|
import { isAvDecimal } from "../runtime/decimal.js";
|
|
3
3
|
import { SERIALIZATION_FAILURE_MESSAGE } from "../runtime/error-boundary.js";
|
|
4
|
-
import { operationFailure, } from "../runtime/operation-response.js";
|
|
4
|
+
import { operationFailure, operationSuccess, } from "../runtime/operation-response.js";
|
|
5
5
|
import { httpStatus } from "./http-status.table.js";
|
|
6
6
|
import { headers } from "./protocol-headers.js";
|
|
7
7
|
export function encodeOperationResponse(response, requestId, report) {
|
|
@@ -37,6 +37,20 @@ export function encodeTransactionResponse(response, requestId, report) {
|
|
|
37
37
|
return written(serializationFailure(step), "null", requestId);
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
+
export function encodeOperationSetResponse(entries, requestId) {
|
|
41
|
+
return written(operationSuccess(null, "A1000"), `{${entries.map(({ key, envelope }) => `${JSON.stringify(key)}:${envelope}`).join(",")}}`, requestId);
|
|
42
|
+
}
|
|
43
|
+
export function operationSetLine(key, envelope) {
|
|
44
|
+
return `{"key":${JSON.stringify(key)},"envelope":${envelope}}\n`;
|
|
45
|
+
}
|
|
46
|
+
export function streamedHeaders(requestId) {
|
|
47
|
+
return {
|
|
48
|
+
"Content-Type": "application/x-ndjson",
|
|
49
|
+
"Cache-Control": "no-store",
|
|
50
|
+
"X-Accel-Buffering": "no",
|
|
51
|
+
[headers.requestId]: requestId,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
40
54
|
function serializationFailure(operation) {
|
|
41
55
|
return operationFailure("A3003", {
|
|
42
56
|
message: SERIALIZATION_FAILURE_MESSAGE,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { VALIDATION_CODES } from "../operations/validation-code.js";
|
|
2
2
|
import { OPERATION_CODES, } from "../runtime/operation-response.js";
|
|
3
|
+
import { isRecord } from "../runtime/record.guard.js";
|
|
3
4
|
export function isOperationResponse(value) {
|
|
4
5
|
if (!isRecord(value) || !Object.hasOwn(value, "data")) {
|
|
5
6
|
return false;
|
|
@@ -13,9 +14,6 @@ export function isOperationResponse(value) {
|
|
|
13
14
|
}
|
|
14
15
|
return value.data === null && isCause(value.cause);
|
|
15
16
|
}
|
|
16
|
-
function isRecord(value) {
|
|
17
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18
|
-
}
|
|
19
17
|
function isOperationCode(value) {
|
|
20
18
|
return OPERATION_CODES.some((code) => code === value);
|
|
21
19
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { AvContracts } from "../compiler/contract.compiler.js";
|
|
2
|
+
import type { RemoteRequestOrigin } from "../config/config-layer.js";
|
|
3
|
+
import type { AvRequestProtocol } from "../runtime/operation-request.js";
|
|
4
|
+
import type { AvTransportContext } from "../runtime/pipeline.js";
|
|
5
|
+
import { type AvProtocolRequest } from "./http-request.decoder.js";
|
|
6
|
+
import { type AvProtocolResponse } from "./http-response.encoder.js";
|
|
7
|
+
/** One entry of an Operation Set as the decoder read it: its key, the operation it names, and its arguments. */
|
|
8
|
+
export type OperationSetEntry = {
|
|
9
|
+
readonly key: string;
|
|
10
|
+
readonly resource: string;
|
|
11
|
+
readonly family: string;
|
|
12
|
+
readonly variant: string;
|
|
13
|
+
/** A JSON object: the operation's arguments, as its single route's body. */
|
|
14
|
+
readonly args: object;
|
|
15
|
+
};
|
|
16
|
+
/** A decoded `/_all` request: what every entry shares, and the entries in the request's own key order. */
|
|
17
|
+
export type DecodedOperationSet = {
|
|
18
|
+
readonly origin: RemoteRequestOrigin;
|
|
19
|
+
readonly requestId: string;
|
|
20
|
+
readonly protocol: AvRequestProtocol;
|
|
21
|
+
readonly transport: AvTransportContext;
|
|
22
|
+
readonly entries: readonly OperationSetEntry[];
|
|
23
|
+
/** Whether the request asked for the streamed answer (`Accept: application/x-ndjson`). */
|
|
24
|
+
readonly streamed: boolean;
|
|
25
|
+
};
|
|
26
|
+
/** What `decodeAllRequest` answers: the set, or the response that answers the whole request. */
|
|
27
|
+
type DecodedAll = {
|
|
28
|
+
readonly ok: true;
|
|
29
|
+
readonly request: DecodedOperationSet;
|
|
30
|
+
readonly requestId: string;
|
|
31
|
+
} | {
|
|
32
|
+
readonly ok: false;
|
|
33
|
+
readonly response: AvProtocolResponse;
|
|
34
|
+
readonly requestId: string;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* `POST /_all` — the request as the Operation Set core runs, or the answer that
|
|
38
|
+
* refuses it whole. The same steps as `decodeTransaction` up to the body: request
|
|
39
|
+
* id, path (exactly `/_all`), identity headers, version, hash, the route's
|
|
40
|
+
* presence, method, then size, content type and JSON. Then the body's shape, in
|
|
41
|
+
* order: exactly the `operations` member (`A2004`), an object of entries
|
|
42
|
+
* (`A2004`/`V1001`), at least one (`A2004`/`V1004`), at most `limits.maxAll`
|
|
43
|
+
* (`422 A2009`/`V1014`), every key an identifier (`A2004`/`V1001` at
|
|
44
|
+
* `["operations"]`, the key not echoed), and every entry exactly `resource`,
|
|
45
|
+
* `family` and `variant` strings and an `args` object (`A2004` at its member).
|
|
46
|
+
*/
|
|
47
|
+
export declare function decodeAllRequest(contracts: AvContracts, received: AvProtocolRequest, origin?: RemoteRequestOrigin): DecodedAll;
|
|
48
|
+
export {};
|