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

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 +268 -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 +30 -0
  7. package/dist/cli/command.parser.js +132 -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 +54 -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 +71 -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 +33 -0
  23. package/dist/config/config.loader.js +80 -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 +6 -0
  65. package/dist/init/client-config.template.js +22 -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 +82 -0
  70. package/dist/init/client-init.planner.d.ts +26 -0
  71. package/dist/init/client-init.planner.js +88 -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 +76 -0
  81. package/dist/output/output.validator.js +254 -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,60 @@
1
+ import { canonicalizeContract } from "@aventara/core";
2
+ import { acceptClientContract } from "../contract/contract.acceptance.js";
3
+ import { withGeneratedBanner } from "./banner.emitter.js";
4
+ /**
5
+ * The carrier (Q4 = A): `generated/contract.ts` holds the ClientContract the
6
+ * client was generated against, as its served canonical bytes (RFC 8785, §19.2)
7
+ * verbatim — which are a valid TypeScript type literal (plan M4):
8
+ *
9
+ * ```ts
10
+ * export type ClientContractShape = {"enums":{},…};
11
+ * ```
12
+ *
13
+ * One artefact, three readers: the derivation's `C` (S4); the generator on a
14
+ * `304` (Q6, S7), which parses it back with {@link parseContractCarrier}; and a
15
+ * person auditing what the client was generated against. It costs no runtime
16
+ * byte — nothing imports it as a value.
17
+ *
18
+ * The bytes are `canonicalizeContract`'s over the ACCEPTED contract: the body a
19
+ * deployment serves is those bytes (`_contract` serves `canonicalizeContract`'s
20
+ * output, and acceptance verified the hash over the same canonical form), and a
21
+ * contract that arrives in another key order still emits them (C-843).
22
+ */
23
+ const CARRIER_PREFIX = "export type ClientContractShape = ";
24
+ const CARRIER_SUFFIX = ";\n";
25
+ /** `generated/contract.ts`, before the banner. */
26
+ export function emitContractCarrierModule(contract) {
27
+ return {
28
+ path: "contract.ts",
29
+ source: `${CARRIER_PREFIX}${canonicalizeContract(contract)}${CARRIER_SUFFIX}`,
30
+ };
31
+ }
32
+ /**
33
+ * The ClientContract a carrier file holds — its whole text, banner included —
34
+ * or `undefined` when it is not one this generator wrote over a contract that
35
+ * still verifies: a foreign prefix or suffix, bytes that are not JSON, a body
36
+ * that is not a ClientContract or whose hash does not re-verify, or bytes that
37
+ * are not that contract's canonical form. Never trusted otherwise (Q6: a carrier
38
+ * that does not re-hash is treated as absent).
39
+ */
40
+ export async function parseContractCarrier(text) {
41
+ const opening = withGeneratedBanner(CARRIER_PREFIX);
42
+ if (!text.startsWith(opening) || !text.endsWith(CARRIER_SUFFIX)) {
43
+ return undefined;
44
+ }
45
+ const bytes = text.slice(opening.length, text.length - CARRIER_SUFFIX.length);
46
+ let body;
47
+ try {
48
+ body = JSON.parse(bytes);
49
+ }
50
+ catch {
51
+ return undefined;
52
+ }
53
+ const acceptance = await acceptClientContract(body);
54
+ if (!acceptance.accepted) {
55
+ return undefined;
56
+ }
57
+ return canonicalizeContract(acceptance.contract) === bytes
58
+ ? acceptance.contract
59
+ : undefined;
60
+ }
@@ -0,0 +1,45 @@
1
+ import type { EmittedModule } from "./emitted-tree.interface.js";
2
+ /**
3
+ * The derivation, transported (Phase 12-rest S2, Q1 = A). A generated client's
4
+ * argument and result types are core's own `OperationArgumentsFor`,
5
+ * `OperationResultFor` and the call grammar (`OperationCall`, …) — never a second
6
+ * spelling. They reach the emitted tree as core's PUBLISHED DECLARATIONS, copied
7
+ * under `generated/derivation/` at their path relative to core's `dist`:
8
+ *
9
+ * - declarations, not sources: the sources' closure carries value imports (the
10
+ * canonicaliser, the wire grammars), the declarations' carries none (plan M1);
11
+ * a `.d.ts` cannot carry runtime, so a bundler never sees one;
12
+ * - under their own directory, so core's `runtime/decimal.d.ts` cannot shadow the
13
+ * emitted `generated/runtime/decimal.ts`;
14
+ * - byte for byte, except the trailing `//# sourceMappingURL=…` line, which names
15
+ * a map the tree does not hold.
16
+ *
17
+ * The closure is walked here, at generation time, from {@link DERIVATION_ROOTS}
18
+ * over whichever `@aventara/core` this package resolves — so the output is a
19
+ * function of the core version the generator depends on, as the rest of it is a
20
+ * function of the generator. A closure that names a package, carries a
21
+ * triple-slash reference, or leaves core's declarations is refused: it cannot be
22
+ * transported, and core's own gate (`emittable-closure.gate.spec.ts`) exists so
23
+ * that it never is.
24
+ */
25
+ /**
26
+ * The modules whose declarations the generated client copies — the roots of core's
27
+ * `emittable-closure.gate.spec.ts`, mirrored (core cannot export a test constant,
28
+ * and this package cannot read core's tests).
29
+ */
30
+ export declare const DERIVATION_ROOTS: readonly string[];
31
+ /**
32
+ * Reads one declaration file by its path relative to core's `dist`
33
+ * (POSIX-separated); `undefined` when there is none.
34
+ */
35
+ export type DeclarationReader = (relative: string) => string | undefined;
36
+ /** The published declarations of the `@aventara/core` this package resolves. */
37
+ export declare function publishedCoreDeclarations(): DeclarationReader;
38
+ /**
39
+ * Core's declaration closure from {@link DERIVATION_ROOTS}, as modules under
40
+ * `derivation/`, in UTF-16 code-unit order of their path.
41
+ *
42
+ * @throws Error when the closure cannot be transported — a defect of the
43
+ * installed `@aventara/core`, never of the consumer's input.
44
+ */
45
+ export declare function emitDerivationModules(read?: DeclarationReader): readonly EmittedModule[];
@@ -0,0 +1,233 @@
1
+ import { readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ /**
5
+ * The derivation, transported (Phase 12-rest S2, Q1 = A). A generated client's
6
+ * argument and result types are core's own `OperationArgumentsFor`,
7
+ * `OperationResultFor` and the call grammar (`OperationCall`, …) — never a second
8
+ * spelling. They reach the emitted tree as core's PUBLISHED DECLARATIONS, copied
9
+ * under `generated/derivation/` at their path relative to core's `dist`:
10
+ *
11
+ * - declarations, not sources: the sources' closure carries value imports (the
12
+ * canonicaliser, the wire grammars), the declarations' carries none (plan M1);
13
+ * a `.d.ts` cannot carry runtime, so a bundler never sees one;
14
+ * - under their own directory, so core's `runtime/decimal.d.ts` cannot shadow the
15
+ * emitted `generated/runtime/decimal.ts`;
16
+ * - byte for byte, except the trailing `//# sourceMappingURL=…` line, which names
17
+ * a map the tree does not hold.
18
+ *
19
+ * The closure is walked here, at generation time, from {@link DERIVATION_ROOTS}
20
+ * over whichever `@aventara/core` this package resolves — so the output is a
21
+ * function of the core version the generator depends on, as the rest of it is a
22
+ * function of the generator. A closure that names a package, carries a
23
+ * triple-slash reference, or leaves core's declarations is refused: it cannot be
24
+ * transported, and core's own gate (`emittable-closure.gate.spec.ts`) exists so
25
+ * that it never is.
26
+ */
27
+ /**
28
+ * The modules whose declarations the generated client copies — the roots of core's
29
+ * `emittable-closure.gate.spec.ts`, mirrored (core cannot export a test constant,
30
+ * and this package cannot read core's tests).
31
+ */
32
+ export const DERIVATION_ROOTS = [
33
+ "contracts/contract",
34
+ "contracts/scalar-value-type",
35
+ "operations/operation-arguments",
36
+ "operations/operation-call",
37
+ "operations/operation-identity",
38
+ "operations/operation-result",
39
+ "transactions/deferred-operation",
40
+ "transactions/transaction-reference",
41
+ ];
42
+ /** The published declarations of the `@aventara/core` this package resolves. */
43
+ export function publishedCoreDeclarations() {
44
+ const dist = path.dirname(fileURLToPath(import.meta.resolve("@aventara/core")));
45
+ return (relative) => {
46
+ try {
47
+ return readFileSync(path.join(dist, relative), "utf8");
48
+ }
49
+ catch (error) {
50
+ if (error.code === "ENOENT") {
51
+ return undefined;
52
+ }
53
+ throw error;
54
+ }
55
+ };
56
+ }
57
+ /**
58
+ * Core's declaration closure from {@link DERIVATION_ROOTS}, as modules under
59
+ * `derivation/`, in UTF-16 code-unit order of their path.
60
+ *
61
+ * @throws Error when the closure cannot be transported — a defect of the
62
+ * installed `@aventara/core`, never of the consumer's input.
63
+ */
64
+ export function emitDerivationModules(read = publishedCoreDeclarations()) {
65
+ const copied = new Map();
66
+ const queue = DERIVATION_ROOTS.map((root) => `${root}.d.ts`);
67
+ while (queue.length > 0) {
68
+ const file = queue.shift();
69
+ if (copied.has(file)) {
70
+ continue;
71
+ }
72
+ const text = read(file);
73
+ if (text === undefined) {
74
+ throw new Error(`@aventara/core's declaration closure names ${file}, which it does not publish.`);
75
+ }
76
+ const scanned = scanDeclaration(text);
77
+ if (scanned.references) {
78
+ throw untransportable(`${file} carries a triple-slash reference, which the emitted tree cannot satisfy`);
79
+ }
80
+ for (const specifier of scanned.specifiers) {
81
+ queue.push(declarationTarget(file, specifier));
82
+ }
83
+ copied.set(file, withoutSourceMapTrailer(text));
84
+ }
85
+ return [...copied.keys()]
86
+ .sort((left, right) => (left < right ? -1 : left > right ? 1 : 0))
87
+ .map((file) => ({
88
+ path: `derivation/${file}`,
89
+ source: copied.get(file),
90
+ }));
91
+ }
92
+ function untransportable(reason) {
93
+ return new Error(`@aventara/core's declarations cannot be copied into the generated client: ${reason}.`);
94
+ }
95
+ /**
96
+ * The declaration file a specifier names, relative to core's `dist`: a relative
97
+ * `.js` specifier inside it, as NodeNext resolves an emitted declaration's import.
98
+ */
99
+ function declarationTarget(file, specifier) {
100
+ const leaves = () => untransportable(`${file} names ${JSON.stringify(specifier)}, which is not a declaration file beside it`);
101
+ if (!(specifier.startsWith("./") || specifier.startsWith("../")) ||
102
+ !specifier.endsWith(".js")) {
103
+ throw leaves();
104
+ }
105
+ const target = path.posix.normalize(path.posix.join(path.posix.dirname(file), specifier));
106
+ if (target.startsWith("../")) {
107
+ throw leaves();
108
+ }
109
+ return target.replace(/\.js$/, ".d.ts");
110
+ }
111
+ /** `text` without its final `//# sourceMappingURL=…` line, when it ends with one. */
112
+ function withoutSourceMapTrailer(text) {
113
+ return text.replace(/(^|\n)\/\/# sourceMappingURL=[^\n]*\n?$/, "$1");
114
+ }
115
+ /**
116
+ * A word followed directly by a string literal names a module. A pattern, not two
117
+ * string comparisons: the packaging gate reads shipped JavaScript lexically, and
118
+ * a keyword spelled as a quoted literal would read to it as an import.
119
+ */
120
+ const SPECIFIER_KEYWORD = /^(?:from|import)$/;
121
+ /**
122
+ * The specifiers one declaration file names, read by a small lexer rather than
123
+ * by `typescript` (an optional peer, absent from a consumer's install): comments,
124
+ * string literals and template literals are skipped as units, so text inside them
125
+ * is never mistaken for an import. Agrees with TypeScript's own pre-processor
126
+ * over core's closure (`derivation.emitter.spec.ts`).
127
+ */
128
+ function scanDeclaration(text) {
129
+ const tokens = [];
130
+ let references = false;
131
+ let index = 0;
132
+ const readQuoted = (quote) => {
133
+ let value = "";
134
+ index += 1;
135
+ while (index < text.length && text[index] !== quote) {
136
+ if (text[index] === "\\") {
137
+ value += text[index + 1] ?? "";
138
+ index += 2;
139
+ continue;
140
+ }
141
+ value += text[index];
142
+ index += 1;
143
+ }
144
+ index += 1;
145
+ return value;
146
+ };
147
+ /** Scans code until the end, or until the `}` closing a `${` when `inPlaceholder`. */
148
+ const scanCode = (inPlaceholder) => {
149
+ let depth = 0;
150
+ while (index < text.length) {
151
+ const character = text[index];
152
+ const next = text[index + 1];
153
+ if (character === "/" && next === "/") {
154
+ const end = text.indexOf("\n", index);
155
+ const line = text.slice(index, end === -1 ? text.length : end);
156
+ if (/^\/\/\/\s*<reference\b/.test(line)) {
157
+ references = true;
158
+ }
159
+ index = end === -1 ? text.length : end + 1;
160
+ }
161
+ else if (character === "/" && next === "*") {
162
+ const end = text.indexOf("*/", index + 2);
163
+ index = end === -1 ? text.length : end + 2;
164
+ }
165
+ else if (character === '"' || character === "'") {
166
+ tokens.push({ kind: "string", value: readQuoted(character) });
167
+ }
168
+ else if (character === "`") {
169
+ scanTemplate();
170
+ }
171
+ else if (/[A-Za-z_$]/.test(character)) {
172
+ const start = index;
173
+ while (index < text.length && /[\w$]/.test(text[index])) {
174
+ index += 1;
175
+ }
176
+ tokens.push({ kind: "word", value: text.slice(start, index) });
177
+ }
178
+ else if (/\s/.test(character)) {
179
+ index += 1;
180
+ }
181
+ else {
182
+ if (inPlaceholder && character === "{") {
183
+ depth += 1;
184
+ }
185
+ else if (inPlaceholder && character === "}") {
186
+ if (depth === 0) {
187
+ index += 1;
188
+ return;
189
+ }
190
+ depth -= 1;
191
+ }
192
+ tokens.push({ kind: "punctuation", value: character });
193
+ index += 1;
194
+ }
195
+ }
196
+ };
197
+ const scanTemplate = () => {
198
+ index += 1;
199
+ while (index < text.length && text[index] !== "`") {
200
+ if (text[index] === "\\") {
201
+ index += 2;
202
+ }
203
+ else if (text[index] === "$" && text[index + 1] === "{") {
204
+ index += 2;
205
+ scanCode(true);
206
+ }
207
+ else {
208
+ index += 1;
209
+ }
210
+ }
211
+ index += 1;
212
+ // A template is one opaque token: nothing before it pairs with it.
213
+ tokens.push({ kind: "punctuation", value: "`" });
214
+ };
215
+ scanCode(false);
216
+ const specifiers = [];
217
+ tokens.forEach((token, at) => {
218
+ const following = tokens[at + 1];
219
+ if (token.kind !== "word") {
220
+ return;
221
+ }
222
+ if (SPECIFIER_KEYWORD.test(token.value) && following?.kind === "string") {
223
+ specifiers.push(following.value);
224
+ }
225
+ else if (token.value === "import" &&
226
+ following?.kind === "punctuation" &&
227
+ following.value === "(" &&
228
+ tokens[at + 2]?.kind === "string") {
229
+ specifiers.push(tokens[at + 2].value);
230
+ }
231
+ });
232
+ return { specifiers, references };
233
+ }
@@ -0,0 +1,4 @@
1
+ import { type ClientContract } from "@aventara/core";
2
+ import type { EmittedModule } from "./emitted-tree.interface.js";
3
+ /** `generated/runtime/descriptor.ts`, before the banner. */
4
+ export declare function emitDescriptorModule(contract: ClientContract): EmittedModule;
@@ -0,0 +1,97 @@
1
+ import { isOperationVariantAvailable, } from "@aventara/core";
2
+ import { ownPropertyKey } from "./name.deriver.js";
3
+ /**
4
+ * `generated/runtime/descriptor.ts` — what the RUNTIME needs of the ClientContract,
5
+ * as data (P1, P4). A projection of the same accepted contract the carrier
6
+ * (`generated/contract.ts`) holds, derived in the same run, so it is not a second
7
+ * source: `descriptor.emitter.spec.ts` regenerates it from the parsed carrier and
8
+ * compares.
9
+ *
10
+ * - **The decode table (P1).** Encoding is decidable by a value's runtime class;
11
+ * decoding is not — a wire `"12"` is a `string`, a `bigint` or a `decimal`
12
+ * depending only on the field (M13). So per Resource the table names each
13
+ * result field whose wire string is revived (`bigint`, `decimal`, `datetime`,
14
+ * `bytes`, §6.2) and each relation with its target and whether it is to-many,
15
+ * which the decode walk recurses into (S5 added `many`: a to-one record may
16
+ * itself hold a `data` and a `count` field, so the shape alone cannot say). Every other key passes through untouched; `json` is never
17
+ * revived.
18
+ * - **The advertised operations (P4).** Read here through core's
19
+ * `isOperationVariantAvailable`, emitted as `[resource, family, variant]`; the
20
+ * type grammar reads the carrier's `operations` keys. They agree while present
21
+ * ⇔ advertised (Phase 10 A3), and a gate asserts it over both pilots.
22
+ *
23
+ * Every registry is walked in UTF-16 code-unit order, so the key order a
24
+ * contract arrives in never reaches the bytes (C-843).
25
+ */
26
+ /** The scalars whose wire form a decoder revives into another runtime class (§6.2). */
27
+ const REVIVED_SCALARS = new Set([
28
+ "bigint",
29
+ "bytes",
30
+ "datetime",
31
+ "decimal",
32
+ ]);
33
+ /** `generated/runtime/descriptor.ts`, before the banner. */
34
+ export function emitDescriptorModule(contract) {
35
+ const resources = codeUnitOrder(Object.keys(contract.resources));
36
+ const table = resources.map((name) => {
37
+ const fields = contract.resources[name]?.fields ?? {};
38
+ const entries = codeUnitOrder(Object.keys(fields)).flatMap((field) => {
39
+ const decoding = decodingOf(fields[field]);
40
+ return decoding === undefined
41
+ ? []
42
+ : [`${ownPropertyKey(field)}: ${decoding}`];
43
+ });
44
+ return `\t${ownPropertyKey(name)}: {${entries.length === 0 ? "" : ` ${entries.join(", ")} `}},\n`;
45
+ });
46
+ const advertised = resources.flatMap((name) => {
47
+ const operations = contract.resources[name]?.operations ?? {};
48
+ return codeUnitOrder(Object.keys(operations)).flatMap((family) => {
49
+ const variants = (operations[family] ?? {});
50
+ return codeUnitOrder(Object.keys(variants))
51
+ .filter((variant) => isOperationVariantAvailable(variants[variant]))
52
+ .map((variant) => `\t[${[name, family, variant].map((part) => JSON.stringify(part)).join(", ")}],\n`);
53
+ });
54
+ });
55
+ return {
56
+ path: "runtime/descriptor.ts",
57
+ source: "/**\n" +
58
+ " * A result field the decoder revives from its wire string (§6.2), or a relation\n" +
59
+ " * it recurses into, read as the relation's target Resource — `many` saying the\n" +
60
+ " * value is a list, `{ data, count }` or `{ count }` rather than one record (P1).\n" +
61
+ " */\n" +
62
+ "export type FieldDecoding =\n" +
63
+ '\t| "bigint"\n' +
64
+ '\t| "bytes"\n' +
65
+ '\t| "datetime"\n' +
66
+ '\t| "decimal"\n' +
67
+ "\t| { readonly relation: string; readonly many: boolean };\n" +
68
+ "\n" +
69
+ "/** Per Resource, the result fields that need decoding; any other key passes through. */\n" +
70
+ "export const DECODE_TABLE: {\n" +
71
+ "\treadonly [resource: string]: { readonly [field: string]: FieldDecoding };\n" +
72
+ `} = {${table.length === 0 ? "" : `\n${table.join("")}`}};\n` +
73
+ "\n" +
74
+ "/** The operations the ClientContract advertises, as `[resource, family, variant]`. */\n" +
75
+ "export const ADVERTISED_OPERATIONS: readonly (readonly [\n" +
76
+ "\tresource: string,\n" +
77
+ "\tfamily: string,\n" +
78
+ "\tvariant: string,\n" +
79
+ `])[] = [${advertised.length === 0 ? "" : `\n${advertised.join("")}`}];\n`,
80
+ };
81
+ }
82
+ /** The table entry a field needs, as source, or `undefined` when it passes through. */
83
+ function decodingOf(field) {
84
+ if (field === undefined) {
85
+ return undefined;
86
+ }
87
+ if (field.kind === "relation") {
88
+ return `{ relation: ${JSON.stringify(field.target)}, many: ${field.cardinality === "many"} }`;
89
+ }
90
+ return "scalar" in field.type &&
91
+ REVIVED_SCALARS.has(field.type.scalar)
92
+ ? JSON.stringify(field.type.scalar)
93
+ : undefined;
94
+ }
95
+ function codeUnitOrder(values) {
96
+ return [...values].sort((left, right) => left < right ? -1 : left > right ? 1 : 0);
97
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The generator's output as a VALUE before it is a filesystem effect (plan §7,
3
+ * group 3). Byte-equality is therefore testable without touching disk, and
4
+ * temp → validate → replace (S7) is a property of the writer alone rather than of
5
+ * every emitter.
6
+ */
7
+ /**
8
+ * The output layout (architect, 2026-10-04): the consumer names a directory,
9
+ * `generateAt`, which is SHARED — they may keep their own files there. The
10
+ * generator owns exactly two entries in it:
11
+ *
12
+ * - `AvClient.ts`, the entry point a consumer imports;
13
+ * - `generated/`, every other module, owned and replaced whole.
14
+ */
15
+ /** The entry point, at the root of `generateAt`. */
16
+ export declare const CLIENT_ENTRY_FILE = "AvClient.ts";
17
+ /** The directory holding every other emitted module, under `generateAt`. */
18
+ export declare const GENERATED_DIRECTORY = "generated";
19
+ /**
20
+ * One of core's published declaration files, copied under `generated/derivation/`
21
+ * at its path relative to core's `dist` (Q1 = A, `derivation.emitter.ts`).
22
+ */
23
+ export type DerivationModulePath = `derivation/${string}.d.ts`;
24
+ /**
25
+ * The modules under `generated/`: the emitted sources, the carrier
26
+ * (`contract.ts`, Q4), the typed surface (`client.ts`) and named types
27
+ * (`types.d.ts`, S4 — a declaration file, architect 2026-10-05), and core's declarations under `derivation/`. §15.4's
28
+ * `runtime/transaction.ts` and `runtime/fingerprint.ts` exist iff transactions
29
+ * are interactive (S6, P3); `resources/` and `runtime/projection.ts` are not
30
+ * emitted (plan §7).
31
+ *
32
+ * Relative to `generated/`, POSIX-separated.
33
+ */
34
+ export type GeneratedModulePath = DerivationModulePath | "client.ts" | "contract.ts" | "enums.ts" | "metadata.ts" | "runtime/codec.ts" | "runtime/decimal.ts" | "runtime/descriptor.ts" | "runtime/errors.ts" | "runtime/fingerprint.ts" | "runtime/transaction.ts" | "runtime/transport.ts" | "types.d.ts";
35
+ /** A path in the emitted tree: relative to `generateAt`, POSIX-separated. */
36
+ export type EmittedFilePath = typeof CLIENT_ENTRY_FILE | `${typeof GENERATED_DIRECTORY}/${GeneratedModulePath}`;
37
+ /** One module's TypeScript source under `generated/`, before the banner and before encoding. */
38
+ export interface EmittedModule {
39
+ readonly path: GeneratedModulePath;
40
+ readonly source: string;
41
+ }
42
+ /** One emitted file: its path and its exact bytes, banner included, UTF-8. */
43
+ export interface EmittedFile {
44
+ readonly path: EmittedFilePath;
45
+ readonly bytes: Uint8Array;
46
+ }
47
+ /**
48
+ * Every file one generation emits, in UTF-16 code-unit order of `path`, each path
49
+ * once — `AvClient.ts` first, then `generated/**`. Replaced, never merged
50
+ * (§15.3): `generated/` whole, `AvClient.ts` as one file.
51
+ */
52
+ export type EmittedTree = readonly EmittedFile[];
53
+ /**
54
+ * What emission yields: the tree, and the diagnostics a run raised without
55
+ * failing — today, one line per renamed name (`name.deriver.ts`). Returned rather
56
+ * than printed, so the code that owns the terminal decides where they go.
57
+ */
58
+ export interface ClientEmission {
59
+ readonly tree: EmittedTree;
60
+ readonly warnings: readonly string[];
61
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The generator's output as a VALUE before it is a filesystem effect (plan §7,
3
+ * group 3). Byte-equality is therefore testable without touching disk, and
4
+ * temp → validate → replace (S7) is a property of the writer alone rather than of
5
+ * every emitter.
6
+ */
7
+ /**
8
+ * The output layout (architect, 2026-10-04): the consumer names a directory,
9
+ * `generateAt`, which is SHARED — they may keep their own files there. The
10
+ * generator owns exactly two entries in it:
11
+ *
12
+ * - `AvClient.ts`, the entry point a consumer imports;
13
+ * - `generated/`, every other module, owned and replaced whole.
14
+ */
15
+ /** The entry point, at the root of `generateAt`. */
16
+ export const CLIENT_ENTRY_FILE = "AvClient.ts";
17
+ /** The directory holding every other emitted module, under `generateAt`. */
18
+ export const GENERATED_DIRECTORY = "generated";
@@ -0,0 +1,24 @@
1
+ import type { ClientContract } from "@aventara/core";
2
+ import type { EmittedModule } from "./emitted-tree.interface.js";
3
+ import { type EmittedNames } from "./name.deriver.js";
4
+ /**
5
+ * `enums.ts`: per enum, a string-literal union type and a same-named `as const`
6
+ * object (§15.4's "enum types"; architect decision, 2026-10-04):
7
+ *
8
+ * ```ts
9
+ * export type Role = "ADMIN" | "USER";
10
+ * export const Role = { ADMIN: "ADMIN", USER: "USER" } as const;
11
+ * ```
12
+ *
13
+ * The type is what every later emitter references; the object gives a consumer a
14
+ * value to name a member by and to enumerate. Both are declared under the enum's
15
+ * emitted identifier, which differs from its contract name only when the name was
16
+ * renamed (`name.deriver.ts`). The values are the wire values and are never
17
+ * renamed.
18
+ *
19
+ * Enums come in the order `names` gives them — UTF-16 code units of the contract
20
+ * name — so the key order a contract arrives in never reaches the bytes (C-843).
21
+ * An enum's VALUES keep their declared order, in the type and in the object: that
22
+ * order is meaning, and canonical form keeps it too.
23
+ */
24
+ export declare function emitEnumsModule(contract: Pick<ClientContract, "enums">, names: EmittedNames): EmittedModule;
@@ -0,0 +1,42 @@
1
+ import { ownPropertyKey } from "./name.deriver.js";
2
+ /**
3
+ * `enums.ts`: per enum, a string-literal union type and a same-named `as const`
4
+ * object (§15.4's "enum types"; architect decision, 2026-10-04):
5
+ *
6
+ * ```ts
7
+ * export type Role = "ADMIN" | "USER";
8
+ * export const Role = { ADMIN: "ADMIN", USER: "USER" } as const;
9
+ * ```
10
+ *
11
+ * The type is what every later emitter references; the object gives a consumer a
12
+ * value to name a member by and to enumerate. Both are declared under the enum's
13
+ * emitted identifier, which differs from its contract name only when the name was
14
+ * renamed (`name.deriver.ts`). The values are the wire values and are never
15
+ * renamed.
16
+ *
17
+ * Enums come in the order `names` gives them — UTF-16 code units of the contract
18
+ * name — so the key order a contract arrives in never reaches the bytes (C-843).
19
+ * An enum's VALUES keep their declared order, in the type and in the object: that
20
+ * order is meaning, and canonical form keeps it too.
21
+ */
22
+ export function emitEnumsModule(contract, names) {
23
+ const declarations = names.enums.map(({ contractName, identifier }) => {
24
+ const values = contract.enums[contractName]?.values ?? [];
25
+ const union = values.length === 0
26
+ ? "never"
27
+ : values.map((value) => JSON.stringify(value)).join(" | ");
28
+ const members = values.length === 0
29
+ ? "{}"
30
+ : `{ ${values.map((value) => `${ownPropertyKey(value)}: ${JSON.stringify(value)}`).join(", ")} }`;
31
+ return (`export type ${identifier} = ${union};\n` +
32
+ `export const ${identifier} = ${members} as const;\n`);
33
+ });
34
+ return {
35
+ path: "enums.ts",
36
+ // Without an import or export, a consumer whose config treats such a file
37
+ // as a script (`moduleResolution: "bundler"` outside a `"type": "module"`
38
+ // package, measured) fails `AvClient.ts`'s `export *` with TS2306 — so a
39
+ // contract without enums still emits a module.
40
+ source: declarations.length === 0 ? "export {};\n" : declarations.join("\n"),
41
+ };
42
+ }