@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,54 @@
1
+ import { ClientConfigError } from "../config/config.resolver.js";
2
+ import { EnvFileError } from "../config/env.cascade.js";
3
+ import { ContractProtocolError } from "../contract/contract.acceptance.js";
4
+ import { ContractTransportError } from "../contract/contract.fetcher.js";
5
+ import { GeneratedNameError } from "../emit/name.deriver.js";
6
+ import { ClientInitNotConfirmedError, ClientInitStepError, } from "../init/client-init.errors.js";
7
+ import { ClientInitAnswerError, ClientInitUnansweredError, } from "../init/client-init.questions.js";
8
+ import { ClientProjectRefusedError } from "../init/client-project.inspector.js";
9
+ import { UnsupportedTypeScriptError } from "../output/output.validator.js";
10
+ import { OutputWriteError, warningsRaisedBeforeDefect, } from "../output/output.writer.js";
11
+ import { CliCommandError } from "./command.parser.js";
12
+ import { renderWarningLines } from "./warning.renderer.js";
13
+ /**
14
+ * How a failed generation reaches the person running it (M3, the adapter CLI's
15
+ * precedent): a refusal is a sentence and a non-zero exit code, never a stack — a
16
+ * stack printed over it buries the sentence that says what to do. Anything else is
17
+ * a defect and keeps its stack.
18
+ *
19
+ * A refusal that stopped a run part-way carries what the run said before it —
20
+ * a crash recovered, a name renamed — and those lines come first, in the order a
21
+ * success prints them, so nothing the run did goes unreported because it failed.
22
+ * A defect's come first too, then its stack, whole.
23
+ */
24
+ /** Every error the generator raises on purpose. One list, read by `instanceof`. */
25
+ const REFUSALS = [
26
+ CliCommandError,
27
+ ClientConfigError,
28
+ EnvFileError,
29
+ ContractTransportError,
30
+ ContractProtocolError,
31
+ GeneratedNameError,
32
+ OutputWriteError,
33
+ UnsupportedTypeScriptError,
34
+ ClientInitAnswerError,
35
+ ClientInitUnansweredError,
36
+ ClientProjectRefusedError,
37
+ ClientInitNotConfirmedError,
38
+ ClientInitStepError,
39
+ ];
40
+ export function renderGenerationFailure(error) {
41
+ if (REFUSALS.some((refusal) => error instanceof refusal)) {
42
+ const carried = error instanceof OutputWriteError ? error.warnings : [];
43
+ return {
44
+ // A refused contract carries its own (plan §7); the other refusals are 1,
45
+ // the adapter CLI's precedent (M3).
46
+ exitCode: error instanceof ContractProtocolError ? error.exitCode : 1,
47
+ text: `${renderWarningLines(carried)}avclient: ${error.message}\n`,
48
+ };
49
+ }
50
+ return {
51
+ exitCode: 1,
52
+ text: `${renderWarningLines(warningsRaisedBeforeDefect(error))}${String(error instanceof Error ? error.stack : error)}\n`,
53
+ };
54
+ }
@@ -0,0 +1,32 @@
1
+ import type { ClientGenerated, ClientUpToDate, ForeignOutputContent } from "../generate.js";
2
+ /**
3
+ * How a generation that succeeded reaches the person running it: one line on
4
+ * stdout saying what was written where and how deeply it was checked, and every
5
+ * warning the run raised on its own stderr line, in the order the run returned
6
+ * them — renames, the degraded check, crash recovery — so the same run prints the
7
+ * same lines.
8
+ */
9
+ export interface GenerationSuccessReport {
10
+ /** What goes to stdout, newline-terminated. */
11
+ readonly stdout: string;
12
+ /** What goes to stderr: one line per warning, or nothing. */
13
+ readonly stderr: string;
14
+ }
15
+ export declare function renderGenerationSuccess(result: ClientGenerated): GenerationSuccessReport;
16
+ /** Nothing written (Phase 12-rest Q6): the deployment's ClientContract and these bytes are what is there. */
17
+ export declare function renderUpToDate(result: ClientUpToDate): GenerationSuccessReport;
18
+ /**
19
+ * What the person running the generator is told before being asked: every path
20
+ * in `AvClient.ts` and `generated/` that the generator did not produce, which
21
+ * proceeding overwrites or removes.
22
+ */
23
+ export declare function renderForeignContentWarning(found: ForeignOutputContent): string;
24
+ /** The question; answered yes, generation proceeds. */
25
+ export declare const FOREIGN_CONTENT_QUESTION = "avclient: overwrite them and generate? [y/N] ";
26
+ /**
27
+ * Nobody confirmed: nothing was touched. One sentence naming what to sort out —
28
+ * and, where nobody could have been asked, the flag that answers yes — after
29
+ * every warning the run raised before it stopped, so a cancelled run's renames
30
+ * are reported as a refused one's are.
31
+ */
32
+ export declare function renderForeignContentCancellation(found: ForeignOutputContent, asked: boolean): string;
@@ -0,0 +1,47 @@
1
+ import { renderWarningLines, WARNING_LINE_PREFIX } from "./warning.renderer.js";
2
+ export function renderGenerationSuccess(result) {
3
+ const count = result.files.length;
4
+ return {
5
+ stdout: `avclient: ${result.contractUnchanged ? "regenerated" : "generated"} ${count} ${count === 1 ? "file" : "files"} into ` +
6
+ `${result.generateAt} (checked: ${result.checked})` +
7
+ (result.contractUnchanged
8
+ ? ": the ClientContract is unchanged; the generator or the entrypoint changed.\n"
9
+ : ".\n"),
10
+ stderr: renderWarningLines(result.warnings),
11
+ };
12
+ }
13
+ /** Nothing written (Phase 12-rest Q6): the deployment's ClientContract and these bytes are what is there. */
14
+ export function renderUpToDate(result) {
15
+ return {
16
+ stdout: `avclient: up to date: ${result.generateAt} already holds this deployment's client; nothing was written.\n`,
17
+ stderr: renderWarningLines(result.warnings),
18
+ };
19
+ }
20
+ /**
21
+ * What the person running the generator is told before being asked: every path
22
+ * in `AvClient.ts` and `generated/` that the generator did not produce, which
23
+ * proceeding overwrites or removes.
24
+ */
25
+ export function renderForeignContentWarning(found) {
26
+ return (`${WARNING_LINE_PREFIX}${found.foreign.join(", ")} in ${found.generateAt} ` +
27
+ `${found.foreign.length === 1 ? "was" : "were"} not generated by @aventara/client, ` +
28
+ "and generating will overwrite or remove " +
29
+ `${found.foreign.length === 1 ? "it" : "them"}.\n`);
30
+ }
31
+ /** The question; answered yes, generation proceeds. */
32
+ export const FOREIGN_CONTENT_QUESTION = "avclient: overwrite them and generate? [y/N] ";
33
+ /**
34
+ * Nobody confirmed: nothing was touched. One sentence naming what to sort out —
35
+ * and, where nobody could have been asked, the flag that answers yes — after
36
+ * every warning the run raised before it stopped, so a cancelled run's renames
37
+ * are reported as a refused one's are.
38
+ */
39
+ export function renderForeignContentCancellation(found, asked) {
40
+ const them = found.foreign.length === 1 ? "it" : "them";
41
+ return `${renderWarningLines(found.warnings)}${asked
42
+ ? `avclient: cancelled, and nothing was touched; move ${found.foreign.join(", ")} out of ${found.generateAt}, ` +
43
+ `or run again and answer yes to overwrite ${them}.\n`
44
+ : `avclient: not generating, and nothing was touched: ${found.foreign.join(", ")} in ${found.generateAt} ` +
45
+ `${found.foreign.length === 1 ? "was" : "were"} not generated by @aventara/client and nobody can be asked ` +
46
+ `(stdin is not a terminal); move ${them} out, or run \`avclient generate --yes\` to overwrite ${them}.\n`}`;
47
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The terminal's questions — `generate`'s yes/no and `init`'s wizard — over one
3
+ * `node:readline` interface whose lines are **queued**: a line typed (or piped
4
+ * into a pseudo-terminal) before its question is kept for it, where a fresh
5
+ * interface per question, or `readline/promises`' `question`, drops it (measured
6
+ * in `@aventara/cli`'s S5; the same rule there, P7).
7
+ */
8
+ export type TerminalPrompter = {
9
+ readonly ask: (prompt: string) => Promise<string>;
10
+ readonly confirm: (question: string) => Promise<boolean>;
11
+ readonly close: () => void;
12
+ };
13
+ export declare function createTerminalPrompter(): TerminalPrompter;
@@ -0,0 +1,53 @@
1
+ import { createInterface } from "node:readline";
2
+ export function createTerminalPrompter() {
3
+ let session;
4
+ let ended = false;
5
+ const lines = [];
6
+ const waiting = [];
7
+ const open = () => {
8
+ if (session === undefined) {
9
+ session = createInterface({
10
+ input: process.stdin,
11
+ output: process.stderr,
12
+ });
13
+ session.on("line", (line) => {
14
+ const next = waiting.shift();
15
+ if (next === undefined) {
16
+ lines.push(line);
17
+ }
18
+ else {
19
+ next.resolve(line);
20
+ }
21
+ });
22
+ session.once("close", () => {
23
+ ended = true;
24
+ for (const next of waiting.splice(0)) {
25
+ next.reject(new Error("the terminal was closed before the question was answered"));
26
+ }
27
+ });
28
+ }
29
+ return session;
30
+ };
31
+ const ask = (prompt) => {
32
+ const readline = open();
33
+ readline.setPrompt(prompt);
34
+ readline.prompt();
35
+ const typed = lines.shift();
36
+ if (typed !== undefined) {
37
+ return Promise.resolve(typed);
38
+ }
39
+ if (ended) {
40
+ return Promise.resolve("");
41
+ }
42
+ return new Promise((resolve, reject) => {
43
+ waiting.push({ resolve, reject });
44
+ });
45
+ };
46
+ return {
47
+ ask,
48
+ confirm: async (question) => /^y(?:es)?$/i.test((await ask(question)).trim()),
49
+ close: () => {
50
+ session?.close();
51
+ },
52
+ };
53
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * How one warning reaches the person running the generator: its own stderr line,
3
+ * under the CLI's one prefix. Warnings carry no prefix of their own, so the line
4
+ * says "warning" once (S7b's report: the degraded check used to say it twice).
5
+ * Shared by a success and a refusal, so a warning reads the same either way.
6
+ */
7
+ /** The prefix every warning line opens with. */
8
+ export declare const WARNING_LINE_PREFIX = "avclient: warning: ";
9
+ /** One newline-terminated line per warning, in the order given; empty for none. */
10
+ export declare function renderWarningLines(warnings: readonly string[]): string;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * How one warning reaches the person running the generator: its own stderr line,
3
+ * under the CLI's one prefix. Warnings carry no prefix of their own, so the line
4
+ * says "warning" once (S7b's report: the degraded check used to say it twice).
5
+ * Shared by a success and a refusal, so a warning reads the same either way.
6
+ */
7
+ /** The prefix every warning line opens with. */
8
+ export const WARNING_LINE_PREFIX = "avclient: warning: ";
9
+ /** One newline-terminated line per warning, in the order given; empty for none. */
10
+ export function renderWarningLines(warnings) {
11
+ return warnings
12
+ .map((warning) => `${WARNING_LINE_PREFIX}${warning}\n`)
13
+ .join("");
14
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ import { type CliIo } from "./cli/generate.command.js";
2
+ export type { CliIo } from "./cli/generate.command.js";
3
+ /**
4
+ * `avclient` — the generator's bin (`avclient generate`), S7b.
5
+ *
6
+ * It parses the command line, runs §15.3's pipeline (`generate.ts`) in the
7
+ * current directory, and owns the terminal: the success line goes to stdout,
8
+ * every warning the run returned to stderr in the run's order, and a failure
9
+ * through the refusal renderer — a refusal is one sentence and exit 1 with no
10
+ * stack (M3), anything else is a defect and keeps its stack.
11
+ *
12
+ * It is also the one place that ASKS (architect, 2026-10-04): when content the
13
+ * generator did not produce stands in `AvClient.ts` or `generated/`, the paths
14
+ * are listed and the person is asked whether to overwrite them. `--yes` answers
15
+ * for them. Where nobody can answer — stdin is not a terminal — it never asks
16
+ * and never overrides: it refuses, naming `--yes`. Cancelled or refused, nothing
17
+ * was touched, the run's warnings are printed before the sentence, and the exit
18
+ * code is 1. A killed run's leftovers are looked at before the question, so the
19
+ * answer — and `--yes` — covers what it hid as well.
20
+ */
21
+ /** Runs one command line; resolves to the exit code. Never throws. */
22
+ export declare function runCli(argv: readonly string[], io: CliIo): Promise<number>;
23
+ /**
24
+ * Runs `avclient` over this process — its arguments, its terminal, its exit
25
+ * code. Called by the bin's entry (`avclient.bin.ts`) once the Node guard has
26
+ * admitted this Node; importing this module runs nothing, which is what lets
27
+ * `runCli` be tested in process.
28
+ */
29
+ export declare function runFromProcess(): Promise<void>;
package/dist/cli.js ADDED
@@ -0,0 +1,71 @@
1
+ import { parseCliCommand, USAGE } from "./cli/command.parser.js";
2
+ import { runGenerate } from "./cli/generate.command.js";
3
+ import { renderGenerationFailure } from "./cli/generation-failure.renderer.js";
4
+ import { createTerminalPrompter } from "./cli/terminal.prompter.js";
5
+ import { runClientInit } from "./init/client-init.orchestrator.js";
6
+ import { runCommand } from "./init/command.runner.js";
7
+ /**
8
+ * `avclient` — the generator's bin (`avclient generate`), S7b.
9
+ *
10
+ * It parses the command line, runs §15.3's pipeline (`generate.ts`) in the
11
+ * current directory, and owns the terminal: the success line goes to stdout,
12
+ * every warning the run returned to stderr in the run's order, and a failure
13
+ * through the refusal renderer — a refusal is one sentence and exit 1 with no
14
+ * stack (M3), anything else is a defect and keeps its stack.
15
+ *
16
+ * It is also the one place that ASKS (architect, 2026-10-04): when content the
17
+ * generator did not produce stands in `AvClient.ts` or `generated/`, the paths
18
+ * are listed and the person is asked whether to overwrite them. `--yes` answers
19
+ * for them. Where nobody can answer — stdin is not a terminal — it never asks
20
+ * and never overrides: it refuses, naming `--yes`. Cancelled or refused, nothing
21
+ * was touched, the run's warnings are printed before the sentence, and the exit
22
+ * code is 1. A killed run's leftovers are looked at before the question, so the
23
+ * answer — and `--yes` — covers what it hid as well.
24
+ */
25
+ /** Runs one command line; resolves to the exit code. Never throws. */
26
+ export async function runCli(argv, io) {
27
+ try {
28
+ const command = parseCliCommand(argv);
29
+ if (command.command === "help") {
30
+ io.stdout(USAGE);
31
+ return 0;
32
+ }
33
+ if (command.command === "init") {
34
+ return await runClientInit(command, io);
35
+ }
36
+ return await runGenerate(io, command.yes);
37
+ }
38
+ catch (error) {
39
+ const failure = renderGenerationFailure(error);
40
+ io.stderr(failure.text);
41
+ return failure.exitCode;
42
+ }
43
+ }
44
+ /**
45
+ * Runs `avclient` over this process — its arguments, its terminal, its exit
46
+ * code. Called by the bin's entry (`avclient.bin.ts`) once the Node guard has
47
+ * admitted this Node; importing this module runs nothing, which is what lets
48
+ * `runCli` be tested in process.
49
+ */
50
+ export async function runFromProcess() {
51
+ const terminal = createTerminalPrompter();
52
+ try {
53
+ process.exitCode = await runCli(process.argv.slice(2), {
54
+ cwd: process.cwd(),
55
+ env: { ...process.env },
56
+ stdout: (text) => {
57
+ process.stdout.write(text);
58
+ },
59
+ stderr: (text) => {
60
+ process.stderr.write(text);
61
+ },
62
+ interactive: process.stdin.isTTY === true,
63
+ confirm: terminal.confirm,
64
+ ask: terminal.ask,
65
+ run: runCommand,
66
+ });
67
+ }
68
+ finally {
69
+ terminal.close();
70
+ }
71
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The generator's configuration, in its two states (plan §7, group 1).
3
+ *
4
+ * `ClientConfigInput` is what a `framework.client.ts` default-exports through
5
+ * `defineClientConfig`. It is passive data: an environment variable appears as
6
+ * an `EnvReference`, a NAME to look up, so the file can be evaluated without the
7
+ * environment having been loaded into the global and resolution stays a pure
8
+ * function of the input and an `EnvRecord`.
9
+ *
10
+ * `ResolvedClientConfig` is what later slices consume: the one §15.2 entrypoint
11
+ * split into the deployment it addresses and its canonical mount path,
12
+ * `generateAt` made absolute, and the mode the cascade resolved under.
13
+ */
14
+ /** A deferred read of one environment variable from the resolved cascade. */
15
+ export interface EnvReference {
16
+ readonly kind: "env";
17
+ readonly name: string;
18
+ }
19
+ /** A value written literally, or read from the resolved environment. */
20
+ export type ConfigValue = string | EnvReference;
21
+ export interface ClientConfigInput {
22
+ /** The full deployed framework entrypoint: origin plus mount path (§15.2). */
23
+ readonly entrypoint: ConfigValue;
24
+ /**
25
+ * The directory the client is generated into, relative to the config file's
26
+ * directory (architect, 2026-10-04). It is SHARED: the generator owns only
27
+ * `AvClient.ts` and `generated/` in it, and never touches anything else there.
28
+ * Named `generateAt`, not §15.2's `output`, by the architect's decision.
29
+ */
30
+ readonly generateAt: ConfigValue;
31
+ }
32
+ /** A filesystem path known to be absolute. */
33
+ export type AbsolutePath = string & {
34
+ readonly __absolutePath: true;
35
+ };
36
+ /**
37
+ * A mount path in F-822's canonical form: `""` at the root, otherwise
38
+ * `/seg(/seg)*` — one leading slash, no trailing slash, no empty segment. Root is
39
+ * the empty string, not `"/"`, so every route is `path + "/<route>"` with no
40
+ * special case. Only resolution makes one.
41
+ */
42
+ export type EntrypointPath = ("" | `/${string}`) & {
43
+ readonly __entrypointPath: true;
44
+ };
45
+ /**
46
+ * The deployed entrypoint, normalised once at resolution so every consumer reads
47
+ * one canonical form (F-822). Joining a route onto it is concatenation.
48
+ */
49
+ export interface ClientEntrypoint {
50
+ /**
51
+ * The deployment the entrypoint addresses: scheme, credentials, host and port.
52
+ * Its own path is the root and carries nothing; the mount path is `path`.
53
+ */
54
+ readonly deployment: URL;
55
+ readonly path: EntrypointPath;
56
+ }
57
+ export interface ResolvedClientConfig {
58
+ readonly entrypoint: ClientEntrypoint;
59
+ /** `generateAt`, made absolute. */
60
+ readonly generateAt: AbsolutePath;
61
+ readonly mode: string;
62
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The generator's configuration, in its two states (plan §7, group 1).
3
+ *
4
+ * `ClientConfigInput` is what a `framework.client.ts` default-exports through
5
+ * `defineClientConfig`. It is passive data: an environment variable appears as
6
+ * an `EnvReference`, a NAME to look up, so the file can be evaluated without the
7
+ * environment having been loaded into the global and resolution stays a pure
8
+ * function of the input and an `EnvRecord`.
9
+ *
10
+ * `ResolvedClientConfig` is what later slices consume: the one §15.2 entrypoint
11
+ * split into the deployment it addresses and its canonical mount path,
12
+ * `generateAt` made absolute, and the mode the cascade resolved under.
13
+ */
14
+ export {};
@@ -0,0 +1,33 @@
1
+ import type { ClientConfigInput } from "./client-config.interface.js";
2
+ /**
3
+ * Finds and evaluates the client config: `framework.client.ts`, default-exporting
4
+ * `defineClientConfig({ entrypoint, generateAt })`, exactly as §15.2 writes it. One
5
+ * file name, looked up in one directory — no search up the tree, no alternative
6
+ * spellings, no `package.json` key: the specification names one file, and a
7
+ * second way to configure the generator would be a second source of the same
8
+ * fact.
9
+ *
10
+ * The file is TypeScript and is evaluated by Node itself (type stripping, on by
11
+ * default in the Node this repository requires), so the generator needs no
12
+ * compiler to read its own config. What it imports — `@aventara/client` for
13
+ * `defineClientConfig` and `env` — resolves from the consumer's project, as any
14
+ * import of theirs would.
15
+ *
16
+ * The evaluated value is checked against `ClientConfigInput`'s shape before it
17
+ * is trusted: the config is the consumer's code, and a typo there should be a
18
+ * sentence naming the member, not a `TypeError` from deep inside resolution.
19
+ */
20
+ /** The config file §15.2 names. */
21
+ export declare const CLIENT_CONFIG_FILE = "framework.client.ts";
22
+ export interface LoadedClientConfig {
23
+ /** The config file's absolute path; `generateAt` resolves against its directory. */
24
+ readonly file: string;
25
+ readonly config: ClientConfigInput;
26
+ }
27
+ /**
28
+ * Evaluates `<directory>/framework.client.ts`.
29
+ *
30
+ * @throws ClientConfigError, in one line, when the file is missing, does not
31
+ * evaluate, or does not default-export a client config.
32
+ */
33
+ export declare function loadClientConfigFile(directory: string): Promise<LoadedClientConfig>;
@@ -0,0 +1,80 @@
1
+ import { existsSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { ClientConfigError } from "./config.resolver.js";
5
+ /**
6
+ * Finds and evaluates the client config: `framework.client.ts`, default-exporting
7
+ * `defineClientConfig({ entrypoint, generateAt })`, exactly as §15.2 writes it. One
8
+ * file name, looked up in one directory — no search up the tree, no alternative
9
+ * spellings, no `package.json` key: the specification names one file, and a
10
+ * second way to configure the generator would be a second source of the same
11
+ * fact.
12
+ *
13
+ * The file is TypeScript and is evaluated by Node itself (type stripping, on by
14
+ * default in the Node this repository requires), so the generator needs no
15
+ * compiler to read its own config. What it imports — `@aventara/client` for
16
+ * `defineClientConfig` and `env` — resolves from the consumer's project, as any
17
+ * import of theirs would.
18
+ *
19
+ * The evaluated value is checked against `ClientConfigInput`'s shape before it
20
+ * is trusted: the config is the consumer's code, and a typo there should be a
21
+ * sentence naming the member, not a `TypeError` from deep inside resolution.
22
+ */
23
+ /** The config file §15.2 names. */
24
+ export const CLIENT_CONFIG_FILE = "framework.client.ts";
25
+ /**
26
+ * Evaluates `<directory>/framework.client.ts`.
27
+ *
28
+ * @throws ClientConfigError, in one line, when the file is missing, does not
29
+ * evaluate, or does not default-export a client config.
30
+ */
31
+ export async function loadClientConfigFile(directory) {
32
+ const file = path.resolve(directory, CLIENT_CONFIG_FILE);
33
+ if (!existsSync(file)) {
34
+ throw new ClientConfigError(`no ${CLIENT_CONFIG_FILE} at ${file}; create it with ` +
35
+ "`export default defineClientConfig({ entrypoint, generateAt })` and run the generator from its directory.");
36
+ }
37
+ let evaluated;
38
+ try {
39
+ evaluated = (await import(pathToFileURL(file).href));
40
+ }
41
+ catch (error) {
42
+ throw new ClientConfigError(`${file} could not be evaluated: ${firstLineOf(error)}`, { cause: error });
43
+ }
44
+ return { file, config: clientConfigOf(file, evaluated) };
45
+ }
46
+ /** The default export, checked member by member against `ClientConfigInput`. */
47
+ function clientConfigOf(file, evaluated) {
48
+ const shape = "`export default defineClientConfig({ entrypoint, generateAt })`";
49
+ if (!Object.hasOwn(evaluated, "default")) {
50
+ throw new ClientConfigError(`${file} has no default export; it must be ${shape}.`);
51
+ }
52
+ const config = evaluated.default;
53
+ if (typeof config !== "object" || config === null || Array.isArray(config)) {
54
+ throw new ClientConfigError(`${file}'s default export is not an object; it must be ${shape}.`);
55
+ }
56
+ const members = config;
57
+ return {
58
+ entrypoint: configValueOf(file, "entrypoint", members.entrypoint),
59
+ generateAt: configValueOf(file, "generateAt", members.generateAt),
60
+ };
61
+ }
62
+ function configValueOf(file, member, value) {
63
+ if (typeof value === "string") {
64
+ return value;
65
+ }
66
+ if (typeof value === "object" &&
67
+ value !== null &&
68
+ value.kind === "env") {
69
+ const name = value.name;
70
+ if (typeof name === "string" && name !== "") {
71
+ return { kind: "env", name };
72
+ }
73
+ }
74
+ throw new ClientConfigError(`${file}'s ${member} must be a string or env("NAME"), and it is ${value === undefined ? "missing" : "neither"}.`);
75
+ }
76
+ /** An error's message, cut at its first line break: the refusal is one line. */
77
+ function firstLineOf(error) {
78
+ const message = error instanceof Error ? error.message : String(error);
79
+ return message.split("\n", 1)[0] ?? "";
80
+ }
@@ -0,0 +1,50 @@
1
+ import type { ClientConfigInput, ClientEntrypoint, EnvReference, ResolvedClientConfig } from "./client-config.interface.js";
2
+ import type { EnvCascadeResolution } from "./env.cascade.js";
3
+ /** The identity function that gives a `framework.client.ts` its type (§15.2). */
4
+ export declare function defineClientConfig(config: ClientConfigInput): ClientConfigInput;
5
+ /** Names an environment variable to be read from the resolved cascade (§15.2). */
6
+ export declare function env(name: string): EnvReference;
7
+ /**
8
+ * The configuration cannot be resolved. The message is the whole diagnosis — the
9
+ * CLI prints it and exits non-zero; no stack is owed for a user's mistake.
10
+ */
11
+ export declare class ClientConfigError extends Error {
12
+ readonly name = "ClientConfigError";
13
+ }
14
+ export interface ClientConfigResolutionInput {
15
+ readonly config: ClientConfigInput;
16
+ readonly cascade: EnvCascadeResolution;
17
+ /** The directory holding the config file; `generateAt` resolves against it. */
18
+ readonly configDirectory: string;
19
+ }
20
+ /**
21
+ * Resolves a config against an already-resolved cascade. Pure: it reads no
22
+ * file and no global, so it is tested against literal inputs.
23
+ */
24
+ export declare function resolveClientConfig(input: ClientConfigResolutionInput): ResolvedClientConfig;
25
+ /**
26
+ * An entrypoint value resolved to its canonical form (F-822, architect's
27
+ * decision 2026-10-04; Q15). The URL half is this function's: whitespace (which
28
+ * the URL parser would trim or drop unseen), a value that is not an absolute
29
+ * URL, a scheme other than http(s), credentials (Q5, architect 2026-10-05), a
30
+ * query or fragment (even an empty one, which the parser forgets), and a `..`
31
+ * segment (which the parser would resolve away) are refused here, reading the
32
+ * value as written.
33
+ *
34
+ * The mount-path half is core's: the path AS WRITTEN — before `URL` has turned
35
+ * a backslash into a slash, resolved a `.` segment away or percent-encoded a
36
+ * character — goes to `AvProtocol.normalizeEntrypoint`, the rule the server's
37
+ * config resolution applies, so the two halves of F-822 cannot disagree
38
+ * (`entrypoint.parity.spec.ts`). Only its slash spelling is coerced — `api`,
39
+ * `/api/` and `//api//` are one intent, `/api` — and root is `""`.
40
+ *
41
+ * @throws ClientConfigError in one sentence that does not echo the value.
42
+ */
43
+ export declare function resolveEntrypoint(value: string): ClientEntrypoint;
44
+ /**
45
+ * The entrypoint as one absolute URL with no trailing slash — the form the
46
+ * emitted transport joins its routes to (§12.1), and the default a generated
47
+ * client embeds (§15.2, Q5: `generated/metadata.ts`'s `DEFAULT_ENTRYPOINT`).
48
+ * Root is the bare origin.
49
+ */
50
+ export declare function entrypointHref(entrypoint: ClientEntrypoint): string;