@aventara/client 0.0.0-stage → 0.1.0-pilot.1

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 (84) hide show
  1. package/LICENSE +91 -0
  2. package/LICENSE-ADDITIONAL-PERMISSION.md +9 -0
  3. package/README.md +234 -2
  4. package/dist/avclient.bin.d.ts +2 -0
  5. package/dist/avclient.bin.js +15 -0
  6. package/dist/cli/command.parser.d.ts +37 -0
  7. package/dist/cli/command.parser.js +177 -0
  8. package/dist/cli/generate.command.d.ts +24 -0
  9. package/dist/cli/generate.command.js +41 -0
  10. package/dist/cli/generation-failure.renderer.d.ts +6 -0
  11. package/dist/cli/generation-failure.renderer.js +52 -0
  12. package/dist/cli/generation-success.renderer.d.ts +32 -0
  13. package/dist/cli/generation-success.renderer.js +47 -0
  14. package/dist/cli/terminal.prompter.d.ts +13 -0
  15. package/dist/cli/terminal.prompter.js +53 -0
  16. package/dist/cli/warning.renderer.d.ts +10 -0
  17. package/dist/cli/warning.renderer.js +14 -0
  18. package/dist/cli.d.ts +29 -0
  19. package/dist/cli.js +75 -0
  20. package/dist/config/client-config.interface.d.ts +62 -0
  21. package/dist/config/client-config.interface.js +14 -0
  22. package/dist/config/config.loader.d.ts +41 -0
  23. package/dist/config/config.loader.js +95 -0
  24. package/dist/config/config.resolver.d.ts +50 -0
  25. package/dist/config/config.resolver.js +126 -0
  26. package/dist/config/env.cascade.d.ts +84 -0
  27. package/dist/config/env.cascade.js +126 -0
  28. package/dist/contract/contract.acceptance.d.ts +77 -0
  29. package/dist/contract/contract.acceptance.js +124 -0
  30. package/dist/contract/contract.fetcher.d.ts +64 -0
  31. package/dist/contract/contract.fetcher.js +85 -0
  32. package/dist/contract/contract.loader.d.ts +32 -0
  33. package/dist/contract/contract.loader.js +32 -0
  34. package/dist/emit/banner.emitter.d.ts +31 -0
  35. package/dist/emit/banner.emitter.js +42 -0
  36. package/dist/emit/client-surface.emitter.d.ts +32 -0
  37. package/dist/emit/client-surface.emitter.js +236 -0
  38. package/dist/emit/client-tree.emitter.d.ts +37 -0
  39. package/dist/emit/client-tree.emitter.js +103 -0
  40. package/dist/emit/contract-carrier.emitter.d.ts +13 -0
  41. package/dist/emit/contract-carrier.emitter.js +60 -0
  42. package/dist/emit/derivation.emitter.d.ts +45 -0
  43. package/dist/emit/derivation.emitter.js +233 -0
  44. package/dist/emit/descriptor.emitter.d.ts +4 -0
  45. package/dist/emit/descriptor.emitter.js +97 -0
  46. package/dist/emit/emitted-tree.interface.d.ts +61 -0
  47. package/dist/emit/emitted-tree.interface.js +18 -0
  48. package/dist/emit/enum.emitter.d.ts +24 -0
  49. package/dist/emit/enum.emitter.js +42 -0
  50. package/dist/emit/name.deriver.d.ts +153 -0
  51. package/dist/emit/name.deriver.js +411 -0
  52. package/dist/emit/named-type.emitter.d.ts +32 -0
  53. package/dist/emit/named-type.emitter.js +50 -0
  54. package/dist/emit/runtime.emitter.d.ts +87 -0
  55. package/dist/emit/runtime.emitter.js +707 -0
  56. package/dist/emit/scalar.codec.d.ts +63 -0
  57. package/dist/emit/scalar.codec.js +498 -0
  58. package/dist/emit/transaction.emitter.d.ts +17 -0
  59. package/dist/emit/transaction.emitter.js +438 -0
  60. package/dist/generate.d.ts +123 -0
  61. package/dist/generate.js +98 -0
  62. package/dist/index.d.ts +8 -0
  63. package/dist/index.js +8 -0
  64. package/dist/init/client-config.template.d.ts +11 -0
  65. package/dist/init/client-config.template.js +27 -0
  66. package/dist/init/client-init.errors.d.ts +9 -0
  67. package/dist/init/client-init.errors.js +9 -0
  68. package/dist/init/client-init.orchestrator.d.ts +3 -0
  69. package/dist/init/client-init.orchestrator.js +86 -0
  70. package/dist/init/client-init.planner.d.ts +27 -0
  71. package/dist/init/client-init.planner.js +99 -0
  72. package/dist/init/client-init.questions.d.ts +52 -0
  73. package/dist/init/client-init.questions.js +124 -0
  74. package/dist/init/client-project.inspector.d.ts +15 -0
  75. package/dist/init/client-project.inspector.js +32 -0
  76. package/dist/init/command.runner.d.ts +8 -0
  77. package/dist/init/command.runner.js +17 -0
  78. package/dist/node-version.guard.d.ts +8 -0
  79. package/dist/node-version.guard.js +59 -0
  80. package/dist/output/output.validator.d.ts +75 -0
  81. package/dist/output/output.validator.js +262 -0
  82. package/dist/output/output.writer.d.ts +162 -0
  83. package/dist/output/output.writer.js +499 -0
  84. package/package.json +47 -3
@@ -0,0 +1,63 @@
1
+ import { AvProtocol } from "@aventara/core/protocol";
2
+ import type { EmittedModule } from "./emitted-tree.interface.js";
3
+ /**
4
+ * The scalar codecs of §6.2, as the generated client carries them in
5
+ * `runtime/codec.ts`.
6
+ *
7
+ * # Keyed on `BuiltInScalar` alone
8
+ *
9
+ * §6.2's table is fixed by PROTOCOL VERSION, not carried in the Contract: the
10
+ * scalar registry is a key registry (`{ builtin: true }`) and scalars carry no
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.
15
+ *
16
+ * One source for the scalar set: the emitted `BuiltInScalar` union and the
17
+ * emitted table both come from core's `BUILT_IN_SCALARS`, in its order, and
18
+ * {@link SCALAR_CODEC_SOURCES} is a mapped type over core's `BuiltInScalar` — a
19
+ * scalar the protocol adds is a compile error here until its codec is written.
20
+ *
21
+ * # Untyped on purpose
22
+ *
23
+ * Every codec is `unknown → unknown`, checked at runtime. The TYPE of a scalar's
24
+ * application form and wire form is Phase 9's `ScalarValueType` and its `Forms`
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.
28
+ *
29
+ * # What the emitted codec promises
30
+ *
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
+ * - `null` passes through both: it is explicit, and whether a field admits it is
35
+ * the field's nullability, which the server validates (§6.2).
36
+ * - `undefined` is never a value: an optional property is omitted, and
37
+ * `serializeWireBody` strips every `undefined` property before transport.
38
+ * - A value outside a scalar's runtime or wire form is refused with a
39
+ * `TypeError` naming the scalar — never coerced. A `json` value carrying a
40
+ * `BigInt`, `Date`, `Uint8Array`, `Decimal`, function, symbol or `undefined`
41
+ * is refused (§6.2), with the path to the offending member.
42
+ * - Wire grammars are the server's, read from core — `AvProtocol.scalarFormats`
43
+ * (C2, R7) — and emitted by value as regular-expression literals
44
+ * ({@link wireGrammarLiteral}): bigint `-?(0|[1-9]\d*)`, datetime exactly
45
+ * `toISOString()`'s form, bytes RFC 4648 base64 with the standard alphabet and
46
+ * padding, canonical (zero pad bits, like the server's — C1). Base64 is written
47
+ * out rather than delegated to `atob`/`btoa`, so the module needs neither a DOM
48
+ * nor a Node global (§15.7, U5).
49
+ */
50
+ /**
51
+ * The regular-expression literal of one of core's wire grammars, as emitted
52
+ * source: `/<source>/`, the grammar's own text. The sources are anchored,
53
+ * flagless and carry a `/` only inside a character class, which core's
54
+ * `av-protocol.spec.ts` pins, so the literal is the grammar itself.
55
+ */
56
+ export declare function wireGrammarLiteral(scalar: keyof typeof AvProtocol.scalarFormats): string;
57
+ /**
58
+ * `runtime/codec.ts`: the `BuiltInScalar` union and the codec table in core's
59
+ * `BUILT_IN_SCALARS` order, the helpers the codecs share, and the three
60
+ * functions the generated transport calls. Nothing here is re-exported from the
61
+ * tree's `AvClient.ts`: the codec is the runtime's, not the consumer's.
62
+ */
63
+ export declare function emitScalarCodecModule(): EmittedModule;
@@ -0,0 +1,498 @@
1
+ import { BUILT_IN_SCALARS } from "@aventara/core";
2
+ import { AvProtocol } from "@aventara/core/protocol";
3
+ /**
4
+ * The scalar codecs of §6.2, as the generated client carries them in
5
+ * `runtime/codec.ts`.
6
+ *
7
+ * # Keyed on `BuiltInScalar` alone
8
+ *
9
+ * §6.2's table is fixed by PROTOCOL VERSION, not carried in the Contract: the
10
+ * scalar registry is a key registry (`{ builtin: true }`) and scalars carry no
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.
15
+ *
16
+ * One source for the scalar set: the emitted `BuiltInScalar` union and the
17
+ * emitted table both come from core's `BUILT_IN_SCALARS`, in its order, and
18
+ * {@link SCALAR_CODEC_SOURCES} is a mapped type over core's `BuiltInScalar` — a
19
+ * scalar the protocol adds is a compile error here until its codec is written.
20
+ *
21
+ * # Untyped on purpose
22
+ *
23
+ * Every codec is `unknown → unknown`, checked at runtime. The TYPE of a scalar's
24
+ * application form and wire form is Phase 9's `ScalarValueType` and its `Forms`
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.
28
+ *
29
+ * # What the emitted codec promises
30
+ *
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
+ * - `null` passes through both: it is explicit, and whether a field admits it is
35
+ * the field's nullability, which the server validates (§6.2).
36
+ * - `undefined` is never a value: an optional property is omitted, and
37
+ * `serializeWireBody` strips every `undefined` property before transport.
38
+ * - A value outside a scalar's runtime or wire form is refused with a
39
+ * `TypeError` naming the scalar — never coerced. A `json` value carrying a
40
+ * `BigInt`, `Date`, `Uint8Array`, `Decimal`, function, symbol or `undefined`
41
+ * is refused (§6.2), with the path to the offending member.
42
+ * - Wire grammars are the server's, read from core — `AvProtocol.scalarFormats`
43
+ * (C2, R7) — and emitted by value as regular-expression literals
44
+ * ({@link wireGrammarLiteral}): bigint `-?(0|[1-9]\d*)`, datetime exactly
45
+ * `toISOString()`'s form, bytes RFC 4648 base64 with the standard alphabet and
46
+ * padding, canonical (zero pad bits, like the server's — C1). Base64 is written
47
+ * out rather than delegated to `atob`/`btoa`, so the module needs neither a DOM
48
+ * nor a Node global (§15.7, U5).
49
+ */
50
+ /**
51
+ * The regular-expression literal of one of core's wire grammars, as emitted
52
+ * source: `/<source>/`, the grammar's own text. The sources are anchored,
53
+ * flagless and carry a `/` only inside a character class, which core's
54
+ * `av-protocol.spec.ts` pins, so the literal is the grammar itself.
55
+ */
56
+ export function wireGrammarLiteral(scalar) {
57
+ return `/${AvProtocol.scalarFormats[scalar]}/`;
58
+ }
59
+ const SCALAR_CODEC_SOURCES = {
60
+ string: {
61
+ encode: `(value) => (typeof value === "string" ? value : refuse("string", "runtime", "a string"))`,
62
+ decode: `(wire) => (typeof wire === "string" ? wire : refuse("string", "wire", "a string"))`,
63
+ },
64
+ boolean: {
65
+ encode: `(value) => (typeof value === "boolean" ? value : refuse("boolean", "runtime", "a boolean"))`,
66
+ decode: `(wire) => (typeof wire === "boolean" ? wire : refuse("boolean", "wire", "a boolean"))`,
67
+ },
68
+ int: {
69
+ encode: `(value) => (isInt32(value) ? value : refuse("int", "runtime", "a 32-bit integer"))`,
70
+ decode: `(wire) => (isInt32(wire) ? wire : refuse("int", "wire", "a 32-bit integer"))`,
71
+ },
72
+ float: {
73
+ encode: `(value) => (isFiniteNumber(value) ? value : refuse("float", "runtime", "a finite number"))`,
74
+ decode: `(wire) => (isFiniteNumber(wire) ? wire : refuse("float", "wire", "a finite number"))`,
75
+ },
76
+ bigint: {
77
+ encode: `(value) => (typeof value === "bigint" ? value.toString(10) : refuse("bigint", "runtime", "a bigint"))`,
78
+ decode: `(wire) =>
79
+ typeof wire === "string" && BIGINT_WIRE.test(wire)
80
+ ? BigInt(wire)
81
+ : refuse("bigint", "wire", "a base-10 integer string")`,
82
+ },
83
+ decimal: {
84
+ encode: `(value) => (value instanceof Decimal ? value.toString() : refuse("decimal", "runtime", "a Decimal"))`,
85
+ decode: "decodeDecimal",
86
+ },
87
+ datetime: {
88
+ encode: "encodeDatetime",
89
+ decode: "decodeDatetime",
90
+ },
91
+ bytes: {
92
+ encode: `(value) => (value instanceof Uint8Array ? encodeBase64(value) : refuse("bytes", "runtime", "a Uint8Array"))`,
93
+ decode: `(wire) =>
94
+ (typeof wire === "string" ? decodeBase64(wire) : undefined) ??
95
+ refuse("bytes", "wire", "an RFC 4648 base64 string")`,
96
+ },
97
+ json: {
98
+ encode: `(value) => copyJson("runtime", value, [], new Set())`,
99
+ decode: `(wire) => copyJson("wire", wire, [], new Set())`,
100
+ },
101
+ };
102
+ const CODEC_MODULE_HEAD = `import { Decimal } from "./decimal.js";
103
+ import type { FieldDecoding } from "./descriptor.js";
104
+
105
+ /**
106
+ * The scalar codecs of Aventara protocol version 1 (§6.2): one per built-in
107
+ * scalar, converting between the generated runtime value and the JSON wire value.
108
+ * A value is never wrapped per field — the Contract names the scalar, so the
109
+ * caller says which scalar a value is.
110
+ *
111
+ * null is explicit in both directions and passes through; whether a field admits
112
+ * it is the field's nullability. undefined is never a value: an optional
113
+ * property is omitted (serializeWireBody). Anything outside a scalar's runtime or
114
+ * wire form is refused with a TypeError, never coerced.
115
+ */
116
+ `;
117
+ const CODEC_MODULE_BODY = `
118
+ interface ScalarCodec {
119
+ /** The generated runtime value to its JSON wire value. */
120
+ readonly encode: (value: unknown) => unknown;
121
+ /** The JSON wire value to the generated runtime value. */
122
+ readonly decode: (wire: unknown) => unknown;
123
+ }
124
+
125
+ type Side = "runtime" | "wire";
126
+ type Path = readonly (string | number)[];
127
+
128
+ const INT32_MIN = -(2 ** 31);
129
+ const INT32_MAX = 2 ** 31 - 1;
130
+ const BIGINT_WIRE = ${wireGrammarLiteral("bigint")};
131
+ const DATETIME_WIRE = ${wireGrammarLiteral("datetime")};
132
+ const BASE64_WIRE = ${wireGrammarLiteral("bytes")};
133
+ const BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
134
+
135
+ function refuse(scalar: BuiltInScalar, side: Side, expected: string): never {
136
+ throw new TypeError('A "' + scalar + '" ' + side + " value must be " + expected + ".");
137
+ }
138
+
139
+ function isInt32(value: unknown): value is number {
140
+ return typeof value === "number" && Number.isInteger(value) && value >= INT32_MIN && value <= INT32_MAX;
141
+ }
142
+
143
+ function isFiniteNumber(value: unknown): value is number {
144
+ return typeof value === "number" && Number.isFinite(value);
145
+ }
146
+
147
+ function decodeDecimal(wire: unknown): Decimal {
148
+ if (typeof wire === "string") {
149
+ try {
150
+ return new Decimal(wire);
151
+ } catch {
152
+ // Refused below, naming the scalar.
153
+ }
154
+ }
155
+ return refuse("decimal", "wire", "a decimal string");
156
+ }
157
+
158
+ function encodeDatetime(value: unknown): string {
159
+ if (!(value instanceof Date) || Number.isNaN(value.getTime())) {
160
+ return refuse("datetime", "runtime", "a valid Date");
161
+ }
162
+ const wire = value.toISOString();
163
+ return DATETIME_WIRE.test(wire) ? wire : refuse("datetime", "runtime", "a Date in the years 0000 to 9999");
164
+ }
165
+
166
+ function decodeDatetime(wire: unknown): Date {
167
+ if (typeof wire === "string" && DATETIME_WIRE.test(wire)) {
168
+ const date = new Date(wire);
169
+ if (!Number.isNaN(date.getTime()) && date.toISOString() === wire) {
170
+ return date;
171
+ }
172
+ }
173
+ return refuse("datetime", "wire", "a canonical ISO-8601 UTC string");
174
+ }
175
+
176
+ function encodeBase64(bytes: Uint8Array): string {
177
+ let text = "";
178
+ for (let index = 0; index < bytes.length; index += 3) {
179
+ const remaining = bytes.length - index;
180
+ const group = ((bytes[index] ?? 0) << 16) | ((bytes[index + 1] ?? 0) << 8) | (bytes[index + 2] ?? 0);
181
+ text += sextet(group, 18) + sextet(group, 12);
182
+ text += remaining > 1 ? sextet(group, 6) : "=";
183
+ text += remaining > 2 ? sextet(group, 0) : "=";
184
+ }
185
+ return text;
186
+ }
187
+
188
+ function sextet(group: number, shift: number): string {
189
+ return BASE64_ALPHABET.charAt((group >> shift) & 63);
190
+ }
191
+
192
+ /** The bytes of a canonical RFC 4648 base64 string; undefined for any other string. */
193
+ function decodeBase64(wire: string): Uint8Array | undefined {
194
+ if (!BASE64_WIRE.test(wire)) {
195
+ return undefined;
196
+ }
197
+ const padding = wire.endsWith("==") ? 2 : wire.endsWith("=") ? 1 : 0;
198
+ const bytes = new Uint8Array((wire.length / 4) * 3 - padding);
199
+ let written = 0;
200
+ for (let index = 0; index < wire.length; index += 4) {
201
+ let group = 0;
202
+ for (let offset = 0; offset < 4; offset += 1) {
203
+ const symbol = wire.charAt(index + offset);
204
+ group = (group << 6) | (symbol === "=" ? 0 : BASE64_ALPHABET.indexOf(symbol));
205
+ }
206
+ const count = index + 4 === wire.length ? 3 - padding : 3;
207
+ // Bits a padded group does not carry must be zero, or two strings would
208
+ // name one byte sequence.
209
+ if ((group & ((1 << (8 * (3 - count))) - 1)) !== 0) {
210
+ return undefined;
211
+ }
212
+ for (let byte = 0; byte < count; byte += 1) {
213
+ bytes[written] = (group >> (16 - 8 * byte)) & 255;
214
+ written += 1;
215
+ }
216
+ }
217
+ return bytes;
218
+ }
219
+
220
+ function isPlainRecord(value: object): boolean {
221
+ const prototype: unknown = Object.getPrototypeOf(value);
222
+ return prototype === Object.prototype || prototype === null;
223
+ }
224
+
225
+ /** What a value that is not JSON-safe is, for a refusal. */
226
+ function kindOf(value: unknown): string {
227
+ if (value === undefined) {
228
+ return "undefined";
229
+ }
230
+ if (typeof value === "bigint") {
231
+ return "a bigint";
232
+ }
233
+ if (typeof value === "number") {
234
+ return "a non-finite number";
235
+ }
236
+ if (typeof value === "function") {
237
+ return "a function";
238
+ }
239
+ if (typeof value === "symbol") {
240
+ return "a symbol";
241
+ }
242
+ if (value instanceof Date) {
243
+ return "a Date";
244
+ }
245
+ if (value instanceof Uint8Array) {
246
+ return "a Uint8Array";
247
+ }
248
+ if (value instanceof Decimal) {
249
+ return "a Decimal";
250
+ }
251
+ if (typeof value === "object" && value !== null && isPlainRecord(value)) {
252
+ return "a symbol-keyed property";
253
+ }
254
+ return "a non-plain object";
255
+ }
256
+
257
+ /** A JSON Pointer (RFC 6901) to a member, or "the root". */
258
+ function pointerOf(path: Path): string {
259
+ return path.length === 0
260
+ ? "the root"
261
+ : path.map((segment) => "/" + String(segment).replaceAll("~", "~0").replaceAll("/", "~1")).join("");
262
+ }
263
+
264
+ function isJsonContainer(value: object): boolean {
265
+ return Array.isArray(value) || (isPlainRecord(value) && Object.getOwnPropertySymbols(value).length === 0);
266
+ }
267
+
268
+ /** A copy of a JSON-safe value, or a refusal naming the first member that is not. */
269
+ function copyJson(side: Side, value: unknown, path: Path, ancestors: Set<object>): unknown {
270
+ if (value === null || typeof value === "string" || typeof value === "boolean" || isFiniteNumber(value)) {
271
+ return value;
272
+ }
273
+ if (typeof value !== "object" || !isJsonContainer(value)) {
274
+ return refuse("json", side, "JSON-safe, and " + kindOf(value) + " at " + pointerOf(path) + " is not");
275
+ }
276
+ if (ancestors.has(value)) {
277
+ return refuse("json", side, "acyclic, and " + pointerOf(path) + " repeats an ancestor");
278
+ }
279
+ ancestors.add(value);
280
+ let copy: unknown;
281
+ if (Array.isArray(value)) {
282
+ const items: unknown[] = [];
283
+ for (let index = 0; index < value.length; index += 1) {
284
+ items.push(copyJson(side, value[index], [...path, index], ancestors));
285
+ }
286
+ copy = items;
287
+ } else {
288
+ // fromEntries defines each key, so "__proto__" stays an own key.
289
+ copy = Object.fromEntries(
290
+ Object.entries(value).map(([key, member]) => [key, copyJson(side, member, [...path, key], ancestors)]),
291
+ );
292
+ }
293
+ ancestors.delete(value);
294
+ return copy;
295
+ }
296
+
297
+ function refuseBody(path: Path, what: string): never {
298
+ throw new TypeError(
299
+ "A request body must be JSON-safe once its scalars are encoded, and " + what + " at " + pointerOf(path) + " is not.",
300
+ );
301
+ }
302
+
303
+ function writeWireJson(value: unknown, path: Path, ancestors: Set<object>): string {
304
+ if (value === null) {
305
+ return "null";
306
+ }
307
+ if (typeof value === "string" || isFiniteNumber(value)) {
308
+ return JSON.stringify(value);
309
+ }
310
+ if (typeof value === "boolean") {
311
+ return value ? "true" : "false";
312
+ }
313
+ if (typeof value !== "object" || !isJsonContainer(value)) {
314
+ return refuseBody(path, kindOf(value));
315
+ }
316
+ if (ancestors.has(value)) {
317
+ return refuseBody(path, "a cycle");
318
+ }
319
+ ancestors.add(value);
320
+ let text: string;
321
+ if (Array.isArray(value)) {
322
+ const items: string[] = [];
323
+ for (let index = 0; index < value.length; index += 1) {
324
+ // An undefined element is refused, not stripped: JSON would make it null.
325
+ items.push(writeWireJson(value[index], [...path, index], ancestors));
326
+ }
327
+ text = "[" + items.join(",") + "]";
328
+ } else {
329
+ const members: string[] = [];
330
+ for (const [key, member] of Object.entries(value)) {
331
+ if (member !== undefined) {
332
+ members.push(JSON.stringify(key) + ":" + writeWireJson(member, [...path, key], ancestors));
333
+ }
334
+ }
335
+ text = "{" + members.join(",") + "}";
336
+ }
337
+ ancestors.delete(value);
338
+ return text;
339
+ }
340
+
341
+ function codecOf(scalar: BuiltInScalar): ScalarCodec {
342
+ if (!Object.hasOwn(SCALAR_CODECS, scalar)) {
343
+ throw new TypeError('"' + String(scalar) + '" is not a built-in scalar of this protocol version.');
344
+ }
345
+ return SCALAR_CODECS[scalar];
346
+ }
347
+
348
+ /** The JSON wire value of a scalar's runtime value; null passes through. */
349
+ export function encodeScalar(scalar: BuiltInScalar, value: unknown): unknown {
350
+ const codec = codecOf(scalar);
351
+ return value === null ? null : codec.encode(value);
352
+ }
353
+
354
+ /** The runtime value of a scalar's JSON wire value; null passes through. */
355
+ export function decodeScalar(scalar: BuiltInScalar, wire: unknown): unknown {
356
+ const codec = codecOf(scalar);
357
+ return wire === null ? null : codec.decode(wire);
358
+ }
359
+
360
+ /**
361
+ * The JSON text of a request body whose scalars are already encoded. Every
362
+ * undefined property is stripped, at every depth — an optional property is
363
+ * omitted, never sent — and null is kept as the explicit value it is (§6.2).
364
+ * Anything else that is not JSON-safe is refused rather than coerced: it means a
365
+ * value escaped its scalar codec.
366
+ */
367
+ export function serializeWireBody(body: unknown): string {
368
+ return writeWireJson(body, [], new Set());
369
+ }
370
+
371
+ /**
372
+ * An operation's arguments with every scalar in its wire form, read off each
373
+ * value's runtime class (Phase 12-rest Q15 = a, the server's encode mirrored): a
374
+ * bigint, a Date, a Decimal or a Uint8Array is encoded wherever it stands — a
375
+ * filter operand, a data value, a cursor — and plain objects and arrays are
376
+ * walked. Every other value is left for serializeWireBody, which refuses what is
377
+ * not JSON-safe. A json value cannot hold one of the four classes (the types
378
+ * exclude them), so nothing inside one is ever re-read as a scalar.
379
+ */
380
+ export function encodeArguments(value: unknown): unknown {
381
+ return encodeValue(value, [], new Set());
382
+ }
383
+
384
+ function encodeValue(value: unknown, path: Path, ancestors: Set<object>): unknown {
385
+ if (typeof value === "bigint") {
386
+ return encodeScalar("bigint", value);
387
+ }
388
+ if (value instanceof Date) {
389
+ return encodeScalar("datetime", value);
390
+ }
391
+ if (value instanceof Decimal) {
392
+ return encodeScalar("decimal", value);
393
+ }
394
+ if (value instanceof Uint8Array) {
395
+ return encodeScalar("bytes", value);
396
+ }
397
+ if (typeof value !== "object" || value === null || !isJsonContainer(value)) {
398
+ return value;
399
+ }
400
+ if (ancestors.has(value)) {
401
+ return refuseBody(path, "a cycle");
402
+ }
403
+ ancestors.add(value);
404
+ const copy = Array.isArray(value)
405
+ ? value.map((item, index) => encodeValue(item, [...path, index], ancestors))
406
+ : Object.fromEntries(
407
+ Object.entries(value).map(([key, member]) => [key, encodeValue(member, [...path, key], ancestors)]),
408
+ );
409
+ ancestors.delete(value);
410
+ return copy;
411
+ }
412
+
413
+ /** Which result fields of each Resource decode, and how (generated/runtime/descriptor.ts). */
414
+ export type DecodeTable = {
415
+ readonly [resource: string]: { readonly [field: string]: FieldDecoding };
416
+ };
417
+
418
+ /**
419
+ * An operation's result with every scalar in its runtime form, read off the decode
420
+ * table (P1): a number (a count), null, and the data of a Resource the table
421
+ * does not name pass through; a list is a list of
422
+ * records, anything else is one record of \`resource\`. In a record, a field the
423
+ * table names is revived — each item of a list field — a relation recurses with
424
+ * its target, and every other key passes through untouched: json is never
425
+ * revived. A to-many relation's value is a list, \`{ data, count }\` or
426
+ * \`{ count }\` — the three shapes the derivation gives it.
427
+ *
428
+ * @throws TypeError when a value is not the wire form its field's scalar takes —
429
+ * which the transport answers as a TransportError (architect, 2026-10-04).
430
+ */
431
+ export function decodeResult(table: DecodeTable, resource: string, value: unknown): unknown {
432
+ // A Resource the table does not name has nothing to revive: its data passes.
433
+ if (value === null || typeof value === "number" || !Object.hasOwn(table, resource)) {
434
+ return value;
435
+ }
436
+ return Array.isArray(value)
437
+ ? value.map((record) => decodeRecord(table, resource, record))
438
+ : decodeRecord(table, resource, value);
439
+ }
440
+
441
+ function decodeRecord(table: DecodeTable, resource: string, record: unknown): unknown {
442
+ if (record === null) {
443
+ return null;
444
+ }
445
+ if (typeof record !== "object" || Array.isArray(record)) {
446
+ throw new TypeError('A "' + resource + '" record must be an object.');
447
+ }
448
+ const fields = Object.hasOwn(table, resource) ? table[resource] : undefined;
449
+ return Object.fromEntries(
450
+ Object.entries(record).map(([key, member]) => {
451
+ const decoding = fields !== undefined && Object.hasOwn(fields, key) ? fields[key] : undefined;
452
+ return [key, decoding === undefined ? member : decodeField(table, decoding, member)];
453
+ }),
454
+ );
455
+ }
456
+
457
+ function decodeField(table: DecodeTable, decoding: FieldDecoding, value: unknown): unknown {
458
+ if (typeof decoding === "string") {
459
+ return Array.isArray(value) ? value.map((item) => decodeScalar(decoding, item)) : decodeScalar(decoding, value);
460
+ }
461
+ if (value === null || !decoding.many) {
462
+ return decodeRecord(table, decoding.relation, value);
463
+ }
464
+ if (Array.isArray(value)) {
465
+ return value.map((record) => decodeRecord(table, decoding.relation, record));
466
+ }
467
+ if (typeof value === "object" && Object.hasOwn(value, "data")) {
468
+ const envelope = value as { readonly data: unknown };
469
+ if (!Array.isArray(envelope.data)) {
470
+ throw new TypeError('A "' + decoding.relation + '" list must be an array.');
471
+ }
472
+ return { ...value, data: envelope.data.map((record) => decodeRecord(table, decoding.relation, record)) };
473
+ }
474
+ return value;
475
+ }
476
+ `;
477
+ /**
478
+ * `runtime/codec.ts`: the `BuiltInScalar` union and the codec table in core's
479
+ * `BUILT_IN_SCALARS` order, the helpers the codecs share, and the three
480
+ * functions the generated transport calls. Nothing here is re-exported from the
481
+ * tree's `AvClient.ts`: the codec is the runtime's, not the consumer's.
482
+ */
483
+ export function emitScalarCodecModule() {
484
+ const union = BUILT_IN_SCALARS.map((scalar) => `\t| ${JSON.stringify(scalar)}`).join("\n");
485
+ const table = BUILT_IN_SCALARS.map((scalar) => {
486
+ const { encode, decode } = SCALAR_CODEC_SOURCES[scalar];
487
+ return `\t${scalar}: {\n\t\tencode: ${encode},\n\t\tdecode: ${decode},\n\t},`;
488
+ }).join("\n");
489
+ return {
490
+ path: "runtime/codec.ts",
491
+ source: CODEC_MODULE_HEAD +
492
+ "\n/** The built-in scalars of the protocol (§6.1). */\n" +
493
+ `export type BuiltInScalar =\n${union};\n` +
494
+ CODEC_MODULE_BODY +
495
+ "\nconst SCALAR_CODECS: { readonly [S in BuiltInScalar]: ScalarCodec } = {\n" +
496
+ `${table}\n};\n`,
497
+ };
498
+ }
@@ -0,0 +1,17 @@
1
+ import type { EmittedModule } from "./emitted-tree.interface.js";
2
+ /**
3
+ * The transaction builder and runner (§14; Phase 12-rest S6, Q13, Q14, P3):
4
+ * `runtime/fingerprint.ts` and `runtime/transaction.ts`, emitted iff the
5
+ * ClientContract advertises `interactive` transactions (P3) — `client.ts` wires
6
+ * them to `avClient.tx` and `avClient.transaction`.
7
+ *
8
+ * Both are fixed by protocol version, not by the Contract, and mirror core's own
9
+ * seams: the plan is assembled as `transactions/transaction-plan-assembler.ts`
10
+ * assembles it — handles as pure data, a `$ref` bound lazily to its source
11
+ * handle's position in the list it runs with, the two refusals core answers in
12
+ * process (Q13) — and each node's fingerprint is core's
13
+ * `computeOperationFingerprint` over the node's WIRE arguments (§14.4), computed
14
+ * here by an emitted JCS and SHA-256 (Q14: `crypto.subtle` is absent from a
15
+ * browser page served over plain http from a non-localhost host).
16
+ */
17
+ export declare function emitTransactionModules(): readonly EmittedModule[];