@aventara/client 0.1.0-pilot.1 → 0.1.0-pilot.2
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/README.md +41 -7
- package/dist/avclient.bin.js +0 -10
- package/dist/cli/command.parser.d.ts +15 -10
- package/dist/cli/command.parser.js +13 -19
- package/dist/cli/generate.command.js +0 -6
- package/dist/cli/generation-failure.renderer.js +0 -14
- package/dist/cli/generation-success.renderer.d.ts +4 -1
- package/dist/cli/generation-success.renderer.js +0 -13
- package/dist/cli/terminal.prompter.d.ts +1 -2
- package/dist/cli/warning.renderer.d.ts +2 -2
- package/dist/cli/warning.renderer.js +0 -8
- package/dist/cli.d.ts +8 -16
- package/dist/cli.js +5 -25
- package/dist/config/client-config.interface.d.ts +18 -16
- package/dist/config/client-config.interface.js +0 -13
- package/dist/config/config.loader.d.ts +34 -22
- package/dist/config/config.loader.js +49 -52
- package/dist/config/config.resolver.d.ts +13 -19
- package/dist/config/config.resolver.js +9 -48
- package/dist/config/env.cascade.d.ts +12 -14
- package/dist/config/env.cascade.js +0 -19
- package/dist/config/module-style.resolver.d.ts +52 -0
- package/dist/config/module-style.resolver.js +75 -0
- package/dist/config/tsconfig.locator.d.ts +45 -0
- package/dist/config/tsconfig.locator.js +52 -0
- package/dist/contract/contract.acceptance.d.ts +12 -26
- package/dist/contract/contract.acceptance.js +0 -54
- package/dist/contract/contract.fetcher.d.ts +12 -17
- package/dist/contract/contract.fetcher.js +0 -24
- package/dist/contract/contract.loader.d.ts +4 -5
- package/dist/contract/contract.loader.js +0 -10
- package/dist/emit/banner.emitter.d.ts +11 -12
- package/dist/emit/banner.emitter.js +0 -26
- package/dist/emit/client-surface.emitter.d.ts +17 -21
- package/dist/emit/client-surface.emitter.js +29 -55
- package/dist/emit/client-tree.emitter.d.ts +11 -20
- package/dist/emit/client-tree.emitter.js +12 -54
- package/dist/emit/contract-carrier.emitter.d.ts +5 -6
- package/dist/emit/contract-carrier.emitter.js +0 -28
- package/dist/emit/derivation.emitter.d.ts +7 -7
- package/dist/emit/derivation.emitter.js +2 -161
- package/dist/emit/descriptor.emitter.js +2 -28
- package/dist/emit/emitted-tree.interface.d.ts +40 -17
- package/dist/emit/emitted-tree.interface.js +6 -16
- package/dist/emit/enum.emitter.d.ts +4 -4
- package/dist/emit/enum.emitter.js +0 -24
- package/dist/emit/module-specifier.scanner.d.ts +25 -0
- package/dist/emit/module-specifier.scanner.js +160 -0
- package/dist/emit/module-style.interface.d.ts +58 -0
- package/dist/emit/module-style.interface.js +8 -0
- package/dist/emit/name.deriver.d.ts +33 -61
- package/dist/emit/name.deriver.js +0 -134
- package/dist/emit/named-type.emitter.d.ts +14 -21
- package/dist/emit/named-type.emitter.js +3 -30
- package/dist/emit/runtime.emitter.d.ts +23 -50
- package/dist/emit/runtime.emitter.js +68 -159
- package/dist/emit/scalar.codec.d.ts +20 -33
- package/dist/emit/scalar.codec.js +13 -69
- package/dist/emit/transaction.emitter.d.ts +6 -14
- package/dist/emit/transaction.emitter.js +24 -33
- package/dist/generate.d.ts +17 -34
- package/dist/generate.js +14 -22
- package/dist/index.js +0 -5
- package/dist/init/client-config.template.d.ts +6 -4
- package/dist/init/client-config.template.js +10 -13
- package/dist/init/client-init.errors.js +0 -3
- package/dist/init/client-init.orchestrator.js +1 -9
- package/dist/init/client-init.planner.d.ts +1 -9
- package/dist/init/client-init.planner.js +6 -24
- package/dist/init/client-init.questions.d.ts +8 -12
- package/dist/init/client-init.questions.js +0 -11
- package/dist/init/client-project.inspector.d.ts +6 -0
- package/dist/init/client-project.inspector.js +2 -2
- package/dist/node-version.guard.js +0 -12
- package/dist/output/output.validator.d.ts +22 -22
- package/dist/output/output.validator.js +46 -59
- package/dist/output/output.writer.d.ts +56 -52
- package/dist/output/output.writer.js +71 -133
- package/package.json +6 -4
|
@@ -1,41 +1,15 @@
|
|
|
1
1
|
import { OPERATION_CODES, VALIDATION_CODES, } from "@aventara/core";
|
|
2
2
|
import { AvProtocol } from "@aventara/core/protocol";
|
|
3
3
|
import { entrypointHref } from "../config/config.resolver.js";
|
|
4
|
+
import { moduleSpecifierWriter, } from "./module-style.interface.js";
|
|
4
5
|
import { wireGrammarLiteral } from "./scalar.codec.js";
|
|
5
|
-
/**
|
|
6
|
-
* The runtime modules the generated client carries: `metadata.ts`, the
|
|
7
|
-
* framework `Decimal`, the outcome codes and error classes, and the transport
|
|
8
|
-
* here; the scalar codec in `scalar.codec.ts`.
|
|
9
|
-
*/
|
|
10
|
-
/**
|
|
11
|
-
* `runtime/decimal.ts` — the framework-owned `Decimal` of §6.2, the generated
|
|
12
|
-
* runtime value of the `decimal` scalar. Bundled with the client and imported
|
|
13
|
-
* from nothing: its public type is its own, never an ORM's (§6.2, C-801).
|
|
14
|
-
*
|
|
15
|
-
* It holds the decimal string it was made from, digit for digit, and does no
|
|
16
|
-
* arithmetic — the surface is core's `Decimal` (a string constructor and
|
|
17
|
-
* `toString`) plus `toJSON`, so a value means the same on both sides of the wire.
|
|
18
|
-
* A proven decimal library may later sit behind it (§6.2); none is needed to
|
|
19
|
-
* carry digits.
|
|
20
|
-
*
|
|
21
|
-
* `toJSON` is the architect's (2026-10-04, S6 answers): it returns the decimal
|
|
22
|
-
* string, so a Decimal in a consumer's own JSON — a log line, a cache entry — is
|
|
23
|
-
* its digits rather than `{}`. No `equals`, no arithmetic. The request body never
|
|
24
|
-
* relies on it: `serializeWireBody` refuses an unencoded Decimal regardless, so a
|
|
25
|
-
* value the codec did not encode cannot slip onto the wire through `toJSON`.
|
|
26
|
-
*
|
|
27
|
-
* The grammar is core's `Decimal` grammar, read from `AvProtocol.scalarFormats`
|
|
28
|
-
* and emitted by value (C2, R7; `protocol-parity.spec.ts`). Unlike
|
|
29
|
-
* core's, the constructor refuses a non-string outright: a pattern test coerces
|
|
30
|
-
* its argument, so `5` would otherwise pass as `"5"` and be kept as a number.
|
|
31
|
-
*/
|
|
32
6
|
export function emitDecimalModule() {
|
|
33
7
|
return {
|
|
34
8
|
path: "runtime/decimal.ts",
|
|
35
9
|
source: `const DECIMAL_STRING = ${wireGrammarLiteral("decimal")};
|
|
36
10
|
|
|
37
11
|
/**
|
|
38
|
-
* An exact decimal: the runtime value of the "decimal" scalar
|
|
12
|
+
* An exact decimal: the runtime value of the "decimal" scalar.
|
|
39
13
|
*
|
|
40
14
|
* It holds the decimal string it was made from, digit for digit, and is sent as
|
|
41
15
|
* that string. It does no arithmetic.
|
|
@@ -64,55 +38,28 @@ export class Decimal {
|
|
|
64
38
|
`,
|
|
65
39
|
};
|
|
66
40
|
}
|
|
67
|
-
/**
|
|
68
|
-
* `metadata.ts` — what this client was generated against: the ClientContract
|
|
69
|
-
* hash and the protocol version (Phase 12-partial Q4 = A), and the resolved
|
|
70
|
-
* entrypoint as the client's default (§15.2; Phase 12-rest Q5 = A). Nothing else:
|
|
71
|
-
* no driver, no timestamp, no generator version, no host path — anything a re-run
|
|
72
|
-
* over the same inputs could change breaks byte-equality (§19.3's spirit). The
|
|
73
|
-
* entrypoint is an input like the contract: changing it regenerates different
|
|
74
|
-
* bytes and leaves the hash alone (§15.2), and it never carries credentials
|
|
75
|
-
* (refused at resolution, Q5).
|
|
76
|
-
*
|
|
77
|
-
* The hash and the version are what every operation request sends (§12.4), from
|
|
78
|
-
* `runtime/transport.ts`; all three are internal — `AvClient.ts` exports none.
|
|
79
|
-
*/
|
|
80
41
|
export function emitMetadataModule(protocol, entrypoint) {
|
|
81
42
|
return {
|
|
82
43
|
path: "metadata.ts",
|
|
83
|
-
source: "/** The hash of the ClientContract this client was generated against
|
|
44
|
+
source: "/** The hash of the ClientContract this client was generated against. */\n" +
|
|
84
45
|
`export const CLIENT_CONTRACT_HASH = ${JSON.stringify(protocol.hash)};\n` +
|
|
85
46
|
"\n" +
|
|
86
|
-
"/** The Aventara protocol version that ClientContract speaks
|
|
47
|
+
"/** The Aventara protocol version that ClientContract speaks. */\n" +
|
|
87
48
|
`export const PROTOCOL_VERSION = ${JSON.stringify(protocol.version)};\n` +
|
|
88
49
|
"\n" +
|
|
89
|
-
"/** The entrypoint the client was generated from: its default deployment
|
|
50
|
+
"/** The entrypoint the client was generated from: its default deployment. */\n" +
|
|
90
51
|
`export const DEFAULT_ENTRYPOINT = ${JSON.stringify(entrypointHref(entrypoint))};\n`,
|
|
91
52
|
};
|
|
92
53
|
}
|
|
93
|
-
/**
|
|
94
|
-
* # The FrameworkError subclass set (Q2 = B)
|
|
95
|
-
*
|
|
96
|
-
* One subclass per code CLASS, plus the two the specification names by
|
|
97
|
-
* behaviour: `NotFoundError` (§13.4, A2003) and the contract-mismatch error
|
|
98
|
-
* (invariant 15, A2005). Each class's doc line is emitted above it.
|
|
99
|
-
*
|
|
100
|
-
* The keys are the emitted class names, in UTF-16 code-unit order.
|
|
101
|
-
*/
|
|
102
54
|
const FRAMEWORK_ERROR_CLASSES = {
|
|
103
55
|
AuthError: "The caller is not authenticated, or is not permitted to run the operation.",
|
|
104
|
-
ConflictError: "The operation conflicted with stored state, and nothing was written. A2008 fails the same way if sent again; A2013 and A2014 may succeed if sent again, and no retry is automatic
|
|
56
|
+
ConflictError: "The operation conflicted with stored state, and nothing was written. A2008 fails the same way if sent again; A2013 and A2014 may succeed if sent again, and no retry is automatic.",
|
|
105
57
|
ContractMismatchError: "This client was generated against a ClientContract other than the one the server serves (invariant 15). Regenerate it with `avclient generate`.",
|
|
106
|
-
InternalError: "The server failed. Nothing in its cause is actionable; the diagnostics are in the server's logs
|
|
107
|
-
NotFoundError: "A strict unique target does not exist
|
|
58
|
+
InternalError: "The server failed. Nothing in its cause is actionable; the diagnostics are in the server's logs.",
|
|
59
|
+
NotFoundError: "A strict unique target does not exist.",
|
|
108
60
|
ProtocolError: "The request was not one the server could route or interpret: an unknown Resource or operation, a protocol version it does not speak, or a body, media type, size or method it refuses.",
|
|
109
61
|
ValidationError: "The request broke a Contract rule: a field, type, capability, projection, reference or limit. The cause's issues say which, by code and path.",
|
|
110
62
|
};
|
|
111
|
-
/**
|
|
112
|
-
* The class every error code throws. A mapped type over core's
|
|
113
|
-
* `OperationErrorCode`, so a code core adds is a compile error here until it is
|
|
114
|
-
* classified — the classification is total by construction, never by care.
|
|
115
|
-
*/
|
|
116
63
|
const FRAMEWORK_ERROR_CLASS_OF = {
|
|
117
64
|
A2000: "ProtocolError",
|
|
118
65
|
A2001: "ProtocolError",
|
|
@@ -133,23 +80,13 @@ const FRAMEWORK_ERROR_CLASS_OF = {
|
|
|
133
80
|
A3001: "InternalError",
|
|
134
81
|
A3002: "InternalError",
|
|
135
82
|
A3003: "InternalError",
|
|
83
|
+
A3004: "InternalError",
|
|
136
84
|
A4000: "AuthError",
|
|
137
85
|
A4001: "AuthError",
|
|
138
86
|
A4002: "AuthError",
|
|
139
87
|
};
|
|
140
|
-
/**
|
|
141
|
-
* The `FrameworkError` subclasses the generated runtime declares, in UTF-16
|
|
142
|
-
* code-unit order — names the root namespace reserves (`name.deriver.ts`).
|
|
143
|
-
*/
|
|
144
88
|
export const FRAMEWORK_ERROR_CLASS_NAMES = Object.keys(FRAMEWORK_ERROR_CLASSES);
|
|
145
|
-
/** The error codes, in core's allocation order. */
|
|
146
89
|
const ERROR_CODES = OPERATION_CODES.filter((code) => !code.startsWith("A1"));
|
|
147
|
-
/**
|
|
148
|
-
* The names `AvClient.ts` re-exports from `generated/runtime/errors.ts`: the classes and the
|
|
149
|
-
* two code unions §15.4 requires, and `Cause` and `ValidationIssue` — the types a
|
|
150
|
-
* thrown FrameworkError carries, public by the architect's decision of
|
|
151
|
-
* 2026-10-04 — in UTF-16 code-unit order.
|
|
152
|
-
*/
|
|
153
90
|
export function errorsModuleExports() {
|
|
154
91
|
return [
|
|
155
92
|
...FRAMEWORK_ERROR_CLASS_NAMES,
|
|
@@ -165,20 +102,19 @@ export function errorsModuleExports() {
|
|
|
165
102
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
166
103
|
});
|
|
167
104
|
}
|
|
168
|
-
/** One code array as emitted source: a `const` tuple, one code per line. */
|
|
169
105
|
function codeArray(codes) {
|
|
170
106
|
return `[\n${codes.map((code) => `\t${JSON.stringify(code)},\n`).join("")}] as const`;
|
|
171
107
|
}
|
|
172
108
|
const ERRORS_MODULE_HEAD = `/**
|
|
173
|
-
* The outcome codes and error classes of Aventara protocol version 1
|
|
109
|
+
* The outcome codes and error classes of Aventara protocol version 1.
|
|
174
110
|
*
|
|
175
111
|
* A1xxx resolves; every other code throws the FrameworkError subclass of its
|
|
176
|
-
* class
|
|
112
|
+
* class; a response that is not a framework envelope throws
|
|
177
113
|
* TransportError. Branch on the class or on \`code\`, never on the message.
|
|
178
114
|
*/
|
|
179
115
|
`;
|
|
180
116
|
const ERRORS_MODULE_TYPES = `
|
|
181
|
-
/** One actionable validation issue
|
|
117
|
+
/** One actionable validation issue; \`path\` locates it in the request. */
|
|
182
118
|
export interface ValidationIssue {
|
|
183
119
|
readonly path?: readonly (string | number)[];
|
|
184
120
|
readonly code: ValidationCode;
|
|
@@ -186,9 +122,9 @@ export interface ValidationIssue {
|
|
|
186
122
|
}
|
|
187
123
|
|
|
188
124
|
/**
|
|
189
|
-
* Why an operation failed
|
|
125
|
+
* Why an operation failed. Rebuilt member by member from the envelope:
|
|
190
126
|
* a member the protocol does not define is never carried, so no stack, SQL or
|
|
191
|
-
* server path the server should not have sent reaches it
|
|
127
|
+
* server path the server should not have sent reaches it.
|
|
192
128
|
*/
|
|
193
129
|
export interface Cause {
|
|
194
130
|
readonly message: string;
|
|
@@ -198,7 +134,7 @@ export interface Cause {
|
|
|
198
134
|
}
|
|
199
135
|
|
|
200
136
|
/**
|
|
201
|
-
* A framework outcome that throws
|
|
137
|
+
* A framework outcome that throws: every code outside A1xxx. It is
|
|
202
138
|
* always thrown as the subclass of its code's class; catch FrameworkError to
|
|
203
139
|
* catch them all.
|
|
204
140
|
*/
|
|
@@ -218,7 +154,7 @@ export class FrameworkError<C extends OperationErrorCode = OperationErrorCode> e
|
|
|
218
154
|
`;
|
|
219
155
|
const ERRORS_MODULE_TAIL = `
|
|
220
156
|
/**
|
|
221
|
-
* A failure that never produced a framework envelope
|
|
157
|
+
* A failure that never produced a framework envelope: no response
|
|
222
158
|
* arrived, or the one that did is not an Aventara response envelope — a proxy's
|
|
223
159
|
* HTML error page, a truncated body, JSON of another shape. It carries no
|
|
224
160
|
* cause: nothing in such a response is the framework's to report.
|
|
@@ -234,16 +170,6 @@ export class TransportError extends Error {
|
|
|
234
170
|
}
|
|
235
171
|
}
|
|
236
172
|
`;
|
|
237
|
-
/**
|
|
238
|
-
* `runtime/errors.ts` — §13's vocabulary as the generated client carries it.
|
|
239
|
-
*
|
|
240
|
-
* Both code unions are emitted from core's exported arrays (Q3), so the emitted
|
|
241
|
-
* tree states each code exactly once and core stays their one source; the
|
|
242
|
-
* subclass set and the code each one throws are Q2's (above), accepted as-is by
|
|
243
|
-
* the architect on 2026-10-04. Exported beyond what `AvClient.ts` re-exports: the
|
|
244
|
-
* two arrays, `OperationErrorCode` and `frameworkErrorOf`, which
|
|
245
|
-
* `runtime/transport.ts` reads.
|
|
246
|
-
*/
|
|
247
173
|
export function emitErrorsModule() {
|
|
248
174
|
const classes = Object.entries(FRAMEWORK_ERROR_CLASSES).map(([name, summary]) => {
|
|
249
175
|
const codes = ERROR_CODES.filter((code) => FRAMEWORK_ERROR_CLASS_OF[code] === name);
|
|
@@ -255,15 +181,15 @@ export function emitErrorsModule() {
|
|
|
255
181
|
return {
|
|
256
182
|
path: "runtime/errors.ts",
|
|
257
183
|
source: ERRORS_MODULE_HEAD +
|
|
258
|
-
"\n/** The outcome codes of the protocol, in allocation order
|
|
184
|
+
"\n/** The outcome codes of the protocol, in allocation order. */\n" +
|
|
259
185
|
`export const OPERATION_CODES = ${codeArray(OPERATION_CODES)};\n` +
|
|
260
|
-
"\n/** An outcome code
|
|
186
|
+
"\n/** An outcome code. */\n" +
|
|
261
187
|
"export type OperationCode = (typeof OPERATION_CODES)[number];\n" +
|
|
262
|
-
"\n/** An outcome code that throws: every code outside A1xxx
|
|
188
|
+
"\n/** An outcome code that throws: every code outside A1xxx. */\n" +
|
|
263
189
|
`export type OperationErrorCode = Exclude<OperationCode, \`A1\${string}\`>;\n` +
|
|
264
|
-
"\n/** The validation codes of the protocol, in allocation order
|
|
190
|
+
"\n/** The validation codes of the protocol, in allocation order. */\n" +
|
|
265
191
|
`export const VALIDATION_CODES = ${codeArray(VALIDATION_CODES)};\n` +
|
|
266
|
-
"\n/** A validation code
|
|
192
|
+
"\n/** A validation code: what one issue in a cause is about. */\n" +
|
|
267
193
|
"export type ValidationCode = (typeof VALIDATION_CODES)[number];\n" +
|
|
268
194
|
ERRORS_MODULE_TYPES +
|
|
269
195
|
classes.join("") +
|
|
@@ -278,36 +204,12 @@ export function emitErrorsModule() {
|
|
|
278
204
|
"}\n",
|
|
279
205
|
};
|
|
280
206
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
* serialized (§15.7), and the envelope read back by §13.5's rule.
|
|
287
|
-
*
|
|
288
|
-
* `execute` is Q1 = B's primitive: exported from this module for the
|
|
289
|
-
* per-variant methods to wrap, and re-exported from nowhere — it is not public
|
|
290
|
-
* surface. It reads no capability and no operation descriptor; the caller names
|
|
291
|
-
* the operation.
|
|
292
|
-
*
|
|
293
|
-
* Fetch and AbortSignal are the platform's, reached through the structural types
|
|
294
|
-
* below so the module compiles with neither DOM nor Node types (§15.7, U5).
|
|
295
|
-
*
|
|
296
|
-
* Stale-route recovery is not here: it is F-716, Phase 10's, and the emitted
|
|
297
|
-
* module documents it as a hole in its own doc comment (S9).
|
|
298
|
-
*/
|
|
299
|
-
export function emitTransportModule() {
|
|
300
|
-
return { path: "runtime/transport.ts", source: TRANSPORT_MODULE };
|
|
207
|
+
export function emitTransportModule(style) {
|
|
208
|
+
return {
|
|
209
|
+
path: "runtime/transport.ts",
|
|
210
|
+
source: transportModuleSource(moduleSpecifierWriter(style)),
|
|
211
|
+
};
|
|
301
212
|
}
|
|
302
|
-
/**
|
|
303
|
-
* Core's identity header names (`AvProtocol.headers`, §12.4), split back into
|
|
304
|
-
* the one wire prefix and each header's own part, so the emitted transport
|
|
305
|
-
* spells both from one constant — its freeze stays a one-line change in the
|
|
306
|
-
* generated code — and from core's values rather than restated text.
|
|
307
|
-
*
|
|
308
|
-
* @throws Error at load when core's names stop sharing one `<prefix>-` head:
|
|
309
|
-
* the emitted spelling could no longer be the names core sends.
|
|
310
|
-
*/
|
|
311
213
|
const IDENTITY_HEADERS = (() => {
|
|
312
214
|
const { protocolVersion, contractHash, requestId } = AvProtocol.headers;
|
|
313
215
|
const prefix = protocolVersion.slice(0, protocolVersion.indexOf("-"));
|
|
@@ -323,9 +225,10 @@ const IDENTITY_HEADERS = (() => {
|
|
|
323
225
|
requestId: requestId.slice(prefix.length),
|
|
324
226
|
};
|
|
325
227
|
})();
|
|
326
|
-
|
|
327
|
-
import {
|
|
328
|
-
import {
|
|
228
|
+
function transportModuleSource(from) {
|
|
229
|
+
return `import { CLIENT_CONTRACT_HASH, PROTOCOL_VERSION } from ${from("../metadata")};
|
|
230
|
+
import { decodeResult, encodeArguments, serializeWireBody } from ${from("./codec")};
|
|
231
|
+
import { DECODE_TABLE } from ${from("./descriptor")};
|
|
329
232
|
import {
|
|
330
233
|
type Cause,
|
|
331
234
|
frameworkErrorOf,
|
|
@@ -336,20 +239,20 @@ import {
|
|
|
336
239
|
VALIDATION_CODES,
|
|
337
240
|
type ValidationCode,
|
|
338
241
|
type ValidationIssue,
|
|
339
|
-
} from "./errors
|
|
242
|
+
} from ${from("./errors")};
|
|
340
243
|
|
|
341
244
|
/**
|
|
342
|
-
* The transport of Aventara protocol version 1: one POST per operation
|
|
343
|
-
* sent with the protocol version and ClientContract hash
|
|
344
|
-
* read by
|
|
245
|
+
* The transport of Aventara protocol version 1: one POST per operation,
|
|
246
|
+
* sent with the protocol version and ClientContract hash, its response
|
|
247
|
+
* read by the protocol's rule — A1xxx resolves with the envelope's data, every other
|
|
345
248
|
* code throws its FrameworkError subclass, and anything that is not a framework
|
|
346
|
-
* envelope throws TransportError. No request is ever retried
|
|
249
|
+
* envelope throws TransportError. No request is ever retried.
|
|
347
250
|
*
|
|
348
|
-
* # A stale client
|
|
251
|
+
* # A stale client
|
|
349
252
|
*
|
|
350
253
|
* A client generated against an older ClientContract must fail hard and say to
|
|
351
254
|
* regenerate (cross-phase invariant 15). The server is what knows: it checks the
|
|
352
|
-
* identity this transport sends BEFORE it routes
|
|
255
|
+
* identity this transport sends BEFORE it routes, so a request
|
|
353
256
|
* under a stale hash — to an operation the deployment still advertises, or to
|
|
354
257
|
* one it no longer does — is answered 409 A2005, which throws
|
|
355
258
|
* ContractMismatchError naming the remedy. Nothing here guesses: a response that
|
|
@@ -358,17 +261,17 @@ import {
|
|
|
358
261
|
* client is stale.
|
|
359
262
|
*/
|
|
360
263
|
|
|
361
|
-
/** The wire prefix of the identity headers, in one place: its freeze
|
|
264
|
+
/** The wire prefix of the identity headers, in one place: its freeze is a one-line change. */
|
|
362
265
|
const WIRE_PREFIX = ${JSON.stringify(IDENTITY_HEADERS.prefix)};
|
|
363
266
|
|
|
364
|
-
/** The headers the framework owns on every operation request
|
|
267
|
+
/** The headers the framework owns on every operation request. */
|
|
365
268
|
const FRAMEWORK_HEADERS: Readonly<Record<string, string>> = {
|
|
366
269
|
"Content-Type": "application/json",
|
|
367
270
|
[WIRE_PREFIX + ${JSON.stringify(IDENTITY_HEADERS.protocolVersion)}]: String(PROTOCOL_VERSION),
|
|
368
271
|
[WIRE_PREFIX + ${JSON.stringify(IDENTITY_HEADERS.contractHash)}]: CLIENT_CONTRACT_HASH,
|
|
369
272
|
};
|
|
370
273
|
|
|
371
|
-
/** The request id header
|
|
274
|
+
/** The request id header: CallOptions.requestId sends it. */
|
|
372
275
|
const REQUEST_ID_HEADER = WIRE_PREFIX + ${JSON.stringify(IDENTITY_HEADERS.requestId)};
|
|
373
276
|
|
|
374
277
|
/** What the transport reads of an AbortSignal, where the consumer's lib declares none. */
|
|
@@ -394,7 +297,7 @@ export interface FetchResponse {
|
|
|
394
297
|
export type StructuralFetch = (url: string, init: FetchInit) => Promise<FetchResponse>;
|
|
395
298
|
|
|
396
299
|
/**
|
|
397
|
-
* The fetch this client calls
|
|
300
|
+
* The fetch this client calls: the consumer's own platform fetch type when
|
|
398
301
|
* their lib declares one — DOM's, Node's — so the platform fetch is accepted
|
|
399
302
|
* without a cast, and the structural fetch above where it declares none. Read
|
|
400
303
|
* through typeof globalThis, so this module names neither lib.
|
|
@@ -410,12 +313,12 @@ export type PlatformSignal = typeof globalThis extends {
|
|
|
410
313
|
|
|
411
314
|
/** Where operations go: the framework entrypoint, and the fetch that reaches it. */
|
|
412
315
|
export interface TransportConnection {
|
|
413
|
-
/** The framework entrypoint, an absolute URL without a trailing slash
|
|
316
|
+
/** The framework entrypoint, an absolute URL without a trailing slash. */
|
|
414
317
|
readonly entrypoint: string;
|
|
415
318
|
readonly fetch: Fetch;
|
|
416
319
|
}
|
|
417
320
|
|
|
418
|
-
/** Per-call options
|
|
321
|
+
/** Per-call options. Never serialized into the operation body. */
|
|
419
322
|
export interface CallOptions {
|
|
420
323
|
/** Aborts the request; the call rejects with what fetch rejected with. */
|
|
421
324
|
readonly signal?: PlatformSignal;
|
|
@@ -430,10 +333,15 @@ export interface CallOptions {
|
|
|
430
333
|
|
|
431
334
|
type SuccessCode = Exclude<OperationCode, OperationErrorCode>;
|
|
432
335
|
|
|
433
|
-
/**
|
|
336
|
+
/**
|
|
337
|
+
* A framework envelope, read and checked — told apart by \`outcome\`, a string
|
|
338
|
+
* discriminant, rather than by \`cause\` being null: without \`strictNullChecks\`
|
|
339
|
+
* (\`strict: false\`, which Next.js writes into a tsconfig it creates) null
|
|
340
|
+
* narrows nothing, and the client must type-check under the consumer's settings.
|
|
341
|
+
*/
|
|
434
342
|
type Envelope =
|
|
435
|
-
| { readonly
|
|
436
|
-
| { readonly
|
|
343
|
+
| { readonly outcome: "data"; readonly code: SuccessCode; readonly data: unknown }
|
|
344
|
+
| { readonly outcome: "failure"; readonly code: OperationErrorCode; readonly cause: Cause };
|
|
437
345
|
|
|
438
346
|
/**
|
|
439
347
|
* Runs one operation: POSTs its arguments and settles its envelope.
|
|
@@ -461,7 +369,7 @@ export async function execute(
|
|
|
461
369
|
}
|
|
462
370
|
|
|
463
371
|
/**
|
|
464
|
-
* Runs one transaction plan
|
|
372
|
+
* Runs one transaction plan: POSTs it to \`/_transactions\` and settles
|
|
465
373
|
* its envelope; a committed plan's data is one result per operation, each decoded
|
|
466
374
|
* by its own operation's Resource (\`resources\`, in plan order).
|
|
467
375
|
*
|
|
@@ -529,7 +437,7 @@ async function post(
|
|
|
529
437
|
|
|
530
438
|
/**
|
|
531
439
|
* The decoded data, or — a scalar in \`data\` that does not decode, inside an
|
|
532
|
-
* otherwise valid envelope — a TransportError
|
|
440
|
+
* otherwise valid envelope — a TransportError.
|
|
533
441
|
*/
|
|
534
442
|
function decoded<T>(operation: string, status: number, decode: () => T): T {
|
|
535
443
|
try {
|
|
@@ -544,9 +452,9 @@ function decoded<T>(operation: string, status: number, decode: () => T): T {
|
|
|
544
452
|
}
|
|
545
453
|
|
|
546
454
|
/**
|
|
547
|
-
* Settles one response by
|
|
548
|
-
* which the caller decodes by the decode table
|
|
549
|
-
* decided
|
|
455
|
+
* Settles one response by the protocol's rule: the envelope's data, still in wire form,
|
|
456
|
+
* which the caller decodes by the decode table. A decode failure's mode is
|
|
457
|
+
* decided:
|
|
550
458
|
* A scalar in \`data\` that fails to decode, inside an otherwise valid envelope, throws TransportError
|
|
551
459
|
* — not a FrameworkError, because the server reported no failure; the response is
|
|
552
460
|
* one this client cannot read, which is what TransportError means.
|
|
@@ -559,10 +467,10 @@ function settle(operation: string, status: number, text: string): unknown {
|
|
|
559
467
|
status,
|
|
560
468
|
);
|
|
561
469
|
}
|
|
562
|
-
if (envelope.
|
|
563
|
-
|
|
470
|
+
if (envelope.outcome === "failure") {
|
|
471
|
+
throw frameworkErrorOf(envelope.code, envelope.cause);
|
|
564
472
|
}
|
|
565
|
-
|
|
473
|
+
return envelope.data;
|
|
566
474
|
}
|
|
567
475
|
|
|
568
476
|
/** The parsed body, or undefined — which no envelope is — when it is not JSON. */
|
|
@@ -590,7 +498,7 @@ function isSuccessCode(code: OperationCode): code is SuccessCode {
|
|
|
590
498
|
return code.startsWith("A1");
|
|
591
499
|
}
|
|
592
500
|
|
|
593
|
-
/** The envelope in a parsed body, or undefined when the body is not one
|
|
501
|
+
/** The envelope in a parsed body, or undefined when the body is not one. */
|
|
594
502
|
function envelopeOf(body: unknown): Envelope | undefined {
|
|
595
503
|
if (!isRecord(body) || !Object.hasOwn(body, "data")) {
|
|
596
504
|
return undefined;
|
|
@@ -601,14 +509,14 @@ function envelopeOf(body: unknown): Envelope | undefined {
|
|
|
601
509
|
return undefined;
|
|
602
510
|
}
|
|
603
511
|
if (isSuccessCode(code)) {
|
|
604
|
-
return body["cause"] === null && (code !== "A1001" || data === null) ? {
|
|
512
|
+
return body["cause"] === null && (code !== "A1001" || data === null) ? { outcome: "data", code, data } : undefined;
|
|
605
513
|
}
|
|
606
514
|
const cause = causeOf(body["cause"]);
|
|
607
|
-
return data === null && cause !== undefined ? {
|
|
515
|
+
return data === null && cause !== undefined ? { outcome: "failure", code, cause } : undefined;
|
|
608
516
|
}
|
|
609
517
|
|
|
610
518
|
/**
|
|
611
|
-
* The cause rebuilt from the members
|
|
519
|
+
* The cause rebuilt from the members the protocol defines, or undefined when one of them
|
|
612
520
|
* is malformed. Every other member is dropped, so whatever else a server put in
|
|
613
521
|
* its cause — a stack, SQL, a file path — is never carried into a thrown error.
|
|
614
522
|
*/
|
|
@@ -672,10 +580,10 @@ function issueOf(value: unknown): ValidationIssue | undefined {
|
|
|
672
580
|
return Object.freeze({ path: Object.freeze(segments), code, message });
|
|
673
581
|
}
|
|
674
582
|
|
|
675
|
-
/** The arguments, refused unless they are an object: the body is always one
|
|
583
|
+
/** The arguments, refused unless they are an object: the body is always one. */
|
|
676
584
|
function argumentObject(args: unknown): unknown {
|
|
677
585
|
if (!isRecord(args)) {
|
|
678
|
-
throw new TypeError("An operation's arguments must be an object; send {} when there are none
|
|
586
|
+
throw new TypeError("An operation's arguments must be an object; send {} when there are none.");
|
|
679
587
|
}
|
|
680
588
|
return args;
|
|
681
589
|
}
|
|
@@ -683,14 +591,14 @@ function argumentObject(args: unknown): unknown {
|
|
|
683
591
|
/**
|
|
684
592
|
* The framework's headers plus the caller's, refusing a caller's that names one of
|
|
685
593
|
* the framework's; then requestId, which overwrites any request id header the
|
|
686
|
-
* caller set
|
|
594
|
+
* caller set.
|
|
687
595
|
*/
|
|
688
596
|
function requestHeaders(options: CallOptions): Record<string, string> {
|
|
689
597
|
const headers: Record<string, string> = { ...FRAMEWORK_HEADERS };
|
|
690
598
|
const owned = new Set(Object.keys(FRAMEWORK_HEADERS).map((name) => name.toLowerCase()));
|
|
691
599
|
for (const [name, value] of Object.entries(options.headers ?? {})) {
|
|
692
600
|
if (owned.has(name.toLowerCase())) {
|
|
693
|
-
throw new TypeError('The "' + name + '" header is the framework\\'s own; a request cannot set it
|
|
601
|
+
throw new TypeError('The "' + name + '" header is the framework\\'s own; a request cannot set it.');
|
|
694
602
|
}
|
|
695
603
|
headers[name] = value;
|
|
696
604
|
}
|
|
@@ -705,3 +613,4 @@ function requestHeaders(options: CallOptions): Record<string, string> {
|
|
|
705
613
|
return headers;
|
|
706
614
|
}
|
|
707
615
|
`;
|
|
616
|
+
}
|
|
@@ -1,51 +1,38 @@
|
|
|
1
1
|
import { AvProtocol } from "@aventara/core/protocol";
|
|
2
2
|
import type { EmittedModule } from "./emitted-tree.interface.js";
|
|
3
|
+
import { type ClientModuleStyle } from "./module-style.interface.js";
|
|
3
4
|
/**
|
|
4
|
-
* The scalar codecs of §6.2, as the generated client carries them in
|
|
5
|
-
* `runtime/codec.ts`.
|
|
6
|
-
*
|
|
7
5
|
* # Keyed on `BuiltInScalar` alone
|
|
8
6
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* position qualifier [M10]. So the codec reads no field, no capability and no
|
|
12
|
-
* operation descriptor — the caller names the scalar, and the codec converts one
|
|
13
|
-
* value. Walking a Resource's fields to find which scalar a value is belongs to
|
|
14
|
-
* the method surface, below Phase 12-partial's boundary.
|
|
7
|
+
* So the codec reads no field, no capability and no operation descriptor — the
|
|
8
|
+
* caller names the scalar, and the codec converts one value.
|
|
15
9
|
*
|
|
16
|
-
* One source for the scalar set: the emitted `BuiltInScalar` union and the
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
10
|
+
* One source for the scalar set: the emitted `BuiltInScalar` union and the emitted
|
|
11
|
+
* table both come from core's `BUILT_IN_SCALARS`, in its order, and {@link
|
|
12
|
+
* SCALAR_CODEC_SOURCES} is a mapped type over core's `BuiltInScalar` — a scalar
|
|
13
|
+
* the protocol adds is a compile error here until its codec is written.
|
|
20
14
|
*
|
|
21
15
|
* # Untyped on purpose
|
|
22
16
|
*
|
|
23
|
-
* Every codec is `unknown → unknown`, checked at runtime.
|
|
24
|
-
*
|
|
25
|
-
* maps (architect decision Q3 = 3a); declaring a second scalar→type map here
|
|
26
|
-
* would be a parallel source of that fact. When the boundary lifts, the methods
|
|
27
|
-
* that call these codecs carry the types.
|
|
17
|
+
* Every codec is `unknown → unknown`, checked at runtime. When the boundary lifts,
|
|
18
|
+
* the methods that call these codecs carry the types.
|
|
28
19
|
*
|
|
29
20
|
* # What the emitted codec promises
|
|
30
21
|
*
|
|
31
|
-
* - `encodeScalar`/`decodeScalar` convert between the generated runtime value
|
|
32
|
-
* and the JSON wire value of §6.2, and `decodeScalar(s, encodeScalar(s, v))`
|
|
33
|
-
* equals `v` for every scalar.
|
|
34
22
|
* - `null` passes through both: it is explicit, and whether a field admits it is
|
|
35
|
-
* the field's nullability, which the server validates
|
|
23
|
+
* the field's nullability, which the server validates.
|
|
36
24
|
* - `undefined` is never a value: an optional property is omitted, and
|
|
37
25
|
* `serializeWireBody` strips every `undefined` property before transport.
|
|
38
|
-
* - A value outside a scalar's runtime or wire form is refused with a
|
|
39
|
-
*
|
|
40
|
-
* `
|
|
41
|
-
*
|
|
42
|
-
* - Wire grammars are the server's, read from core — `AvProtocol.scalarFormats`
|
|
43
|
-
*
|
|
44
|
-
*
|
|
26
|
+
* - A value outside a scalar's runtime or wire form is refused with a `TypeError`
|
|
27
|
+
* naming the scalar — never coerced. A `json` value carrying a `BigInt`, `Date`,
|
|
28
|
+
* `Uint8Array`, `Decimal`, function, symbol or `undefined` is refused, with the
|
|
29
|
+
* path to the offending member.
|
|
30
|
+
* - Wire grammars are the server's, read from core — `AvProtocol.scalarFormats` —
|
|
31
|
+
* and emitted by value as regular-expression literals ({@link
|
|
32
|
+
* wireGrammarLiteral}): bigint `-?(0|[1-9]\d*)`, datetime exactly
|
|
45
33
|
* `toISOString()`'s form, bytes RFC 4648 base64 with the standard alphabet and
|
|
46
|
-
* padding, canonical
|
|
47
|
-
*
|
|
48
|
-
* nor a Node global (§15.7, U5).
|
|
34
|
+
* padding, canonical. Base64 is written out rather than delegated to
|
|
35
|
+
* `atob`/`btoa`, so the module needs neither a DOM nor a Node global.
|
|
49
36
|
*/
|
|
50
37
|
/**
|
|
51
38
|
* The regular-expression literal of one of core's wire grammars, as emitted
|
|
@@ -60,4 +47,4 @@ export declare function wireGrammarLiteral(scalar: keyof typeof AvProtocol.scala
|
|
|
60
47
|
* functions the generated transport calls. Nothing here is re-exported from the
|
|
61
48
|
* tree's `AvClient.ts`: the codec is the runtime's, not the consumer's.
|
|
62
49
|
*/
|
|
63
|
-
export declare function emitScalarCodecModule(): EmittedModule;
|
|
50
|
+
export declare function emitScalarCodecModule(style: ClientModuleStyle): EmittedModule;
|