@aventara/client 0.1.0-pilot.0 → 0.1.0-pilot.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +61 -61
  2. package/dist/avclient.bin.js +0 -10
  3. package/dist/cli/command.parser.d.ts +21 -9
  4. package/dist/cli/command.parser.js +51 -12
  5. package/dist/cli/generate.command.js +0 -6
  6. package/dist/cli/generation-failure.renderer.js +0 -16
  7. package/dist/cli/generation-success.renderer.d.ts +4 -1
  8. package/dist/cli/generation-success.renderer.js +0 -13
  9. package/dist/cli/terminal.prompter.d.ts +1 -2
  10. package/dist/cli/warning.renderer.d.ts +2 -2
  11. package/dist/cli/warning.renderer.js +0 -8
  12. package/dist/cli.d.ts +8 -16
  13. package/dist/cli.js +11 -27
  14. package/dist/config/client-config.interface.d.ts +18 -16
  15. package/dist/config/client-config.interface.js +0 -13
  16. package/dist/config/config.loader.d.ts +33 -13
  17. package/dist/config/config.loader.js +52 -40
  18. package/dist/config/config.resolver.d.ts +13 -19
  19. package/dist/config/config.resolver.js +9 -48
  20. package/dist/config/env.cascade.d.ts +12 -14
  21. package/dist/config/env.cascade.js +0 -19
  22. package/dist/config/module-style.resolver.d.ts +52 -0
  23. package/dist/config/module-style.resolver.js +75 -0
  24. package/dist/config/tsconfig.locator.d.ts +45 -0
  25. package/dist/config/tsconfig.locator.js +52 -0
  26. package/dist/contract/contract.acceptance.d.ts +12 -26
  27. package/dist/contract/contract.acceptance.js +0 -54
  28. package/dist/contract/contract.fetcher.d.ts +12 -17
  29. package/dist/contract/contract.fetcher.js +0 -24
  30. package/dist/contract/contract.loader.d.ts +4 -5
  31. package/dist/contract/contract.loader.js +0 -10
  32. package/dist/emit/banner.emitter.d.ts +11 -12
  33. package/dist/emit/banner.emitter.js +0 -26
  34. package/dist/emit/client-surface.emitter.d.ts +17 -21
  35. package/dist/emit/client-surface.emitter.js +29 -55
  36. package/dist/emit/client-tree.emitter.d.ts +11 -20
  37. package/dist/emit/client-tree.emitter.js +12 -54
  38. package/dist/emit/contract-carrier.emitter.d.ts +5 -6
  39. package/dist/emit/contract-carrier.emitter.js +0 -28
  40. package/dist/emit/derivation.emitter.d.ts +7 -7
  41. package/dist/emit/derivation.emitter.js +2 -161
  42. package/dist/emit/descriptor.emitter.js +2 -28
  43. package/dist/emit/emitted-tree.interface.d.ts +40 -17
  44. package/dist/emit/emitted-tree.interface.js +6 -16
  45. package/dist/emit/enum.emitter.d.ts +4 -4
  46. package/dist/emit/enum.emitter.js +0 -24
  47. package/dist/emit/module-specifier.scanner.d.ts +25 -0
  48. package/dist/emit/module-specifier.scanner.js +160 -0
  49. package/dist/emit/module-style.interface.d.ts +58 -0
  50. package/dist/emit/module-style.interface.js +8 -0
  51. package/dist/emit/name.deriver.d.ts +33 -61
  52. package/dist/emit/name.deriver.js +0 -134
  53. package/dist/emit/named-type.emitter.d.ts +14 -21
  54. package/dist/emit/named-type.emitter.js +3 -30
  55. package/dist/emit/runtime.emitter.d.ts +23 -50
  56. package/dist/emit/runtime.emitter.js +68 -159
  57. package/dist/emit/scalar.codec.d.ts +20 -33
  58. package/dist/emit/scalar.codec.js +13 -69
  59. package/dist/emit/transaction.emitter.d.ts +6 -14
  60. package/dist/emit/transaction.emitter.js +24 -33
  61. package/dist/generate.d.ts +17 -34
  62. package/dist/generate.js +14 -22
  63. package/dist/index.js +0 -5
  64. package/dist/init/client-config.template.d.ts +9 -2
  65. package/dist/init/client-config.template.js +12 -10
  66. package/dist/init/client-init.errors.js +0 -3
  67. package/dist/init/client-init.orchestrator.js +7 -11
  68. package/dist/init/client-init.planner.d.ts +3 -10
  69. package/dist/init/client-init.planner.js +5 -12
  70. package/dist/init/client-init.questions.d.ts +8 -12
  71. package/dist/init/client-init.questions.js +0 -11
  72. package/dist/init/client-project.inspector.d.ts +6 -0
  73. package/dist/init/client-project.inspector.js +2 -2
  74. package/dist/node-version.guard.js +0 -12
  75. package/dist/output/output.validator.d.ts +28 -29
  76. package/dist/output/output.validator.js +68 -73
  77. package/dist/output/output.writer.d.ts +56 -52
  78. package/dist/output/output.writer.js +71 -133
  79. package/package.json +7 -5
package/README.md CHANGED
@@ -11,7 +11,7 @@ server by construction, and that agreement is gated over both pilot schemas.
11
11
  ## Setting up a frontend: `avclient init`
12
12
 
13
13
  ```bash
14
- npx @aventara/client init # in your frontend project
14
+ npx @aventara/client@pilot init # in your frontend project; pnpm: pnpm dlx @aventara/client@pilot init
15
15
  ```
16
16
 
17
17
  It asks — or takes from flags, or with `--yes` takes every default — four things, and writes the setup:
@@ -23,7 +23,7 @@ It asks — or takes from flags, or with `--yes` takes every default — four th
23
23
  | `--generate-at <dir>` | Where the client goes | `./src/api` |
24
24
  | `--package-manager <npm\|pnpm>` | | the lockfile's, else the launching one, else npm |
25
25
 
26
- It writes `framework.client.ts`, the variable into `.env` (only with a variable), `"avclient:generate": "avclient
26
+ It writes `framework.client.ts` (or reuses the config the project already has), the variable into `.env` (only with a variable), `"avclient:generate": "avclient
27
27
  generate"` into the scripts and `@aventara/client` as an **exact** devDependency at its own version; runs the install
28
28
  (`--skip-install` prints it instead); and generates the client right away (`--skip-generate` to skip; a server that does
29
29
  not answer leaves the files and names `avclient generate`). Existing content that differs is listed and replaced only
@@ -50,10 +50,34 @@ and run:
50
50
  npx avclient generate [--yes]
51
51
  ```
52
52
 
53
+ **The config file** is loaded the way Prisma 7 loads `prisma.config.ts`: `framework.client.ts` — or `.mts`, `.cts`,
54
+ `.js`, `.mjs`, `.cjs` — in ES module or CommonJS syntax, whatever your `package.json`'s `"type"`. A
55
+ `framework.client.mts` written by 0.1.0-pilot.1 keeps working, and `avclient init` reuses it. Two config files in one
56
+ directory are refused, naming both.
57
+
53
58
  - **`entrypoint`** is the deployment's framework entrypoint: origin plus mount path, one absolute `http(s)` URL. It
54
59
  may not carry credentials — the platform's `fetch` refuses such a URL, and the entrypoint ships inside the client.
55
60
  - **`generateAt`** is a directory you may share with your own files. The generator owns exactly two entries in it —
56
61
  `AvClient.ts` and `generated/` — and never reads, moves or removes anything else there.
62
+
63
+ ### Your tsconfig decides how the client is written
64
+
65
+ The client is TypeScript source that **your own toolchain compiles**, like your own files — `tsc`, Next.js (Turbopack or
66
+ webpack), Vite, `tsx`, Node's type stripping. Its modules import each other the way your project imports its files,
67
+ read from your `tsconfig.json` (the nearest one above `generateAt`, `extends` included) by Prisma 7's rules:
68
+
69
+ | Your tsconfig | The client's own imports | You import it as |
70
+ |---|---|---|
71
+ | `allowImportingTsExtensions` or `rewriteRelativeImportExtensions` (Vite's react-ts template, Node's type stripping) | `./generated/client.ts` | `./api/AvClient.ts` |
72
+ | `moduleResolution: "bundler"` (Next.js) or `module: "commonjs"` | `./generated/client` | `./api/AvClient` |
73
+ | anything else — `module: "nodenext"`/`"node16"` (NestJS 12) | `./generated/client.js` | `./api/AvClient.js` (ES module) or `./api/AvClient` (CommonJS) |
74
+
75
+ Under `nodenext`, the nearest `package.json`'s `"type"` decides whether the client is checked as an ES module or as
76
+ CommonJS. Vite's `tsconfig.json`, which only lists `references`, defers to the referenced project that includes the
77
+ client (`tsconfig.app.json`). The validate step type-checks the client in your project's resolution and module format.
78
+
79
+ A project with no `tsconfig.json` is refused: the generated client is TypeScript, and JavaScript projects are not
80
+ supported yet.
57
81
  - **The `.env` cascade** is read from the current directory before the config is evaluated, highest precedence first:
58
82
  the process environment, `.env.<mode>.local`, `.env.<mode>`, `.env.local`, `.env`; `mode` is `NODE_ENV`, or
59
83
  `development`. The config reads it through `env("NAME")`; `process.env` is never written.
@@ -67,9 +91,11 @@ npx avclient generate [--yes]
67
91
  - **A failed run leaves the previous output exactly as it was.** The whole tree is written to a staging directory inside
68
92
  `generateAt`, validated there as one program, and only then moved into place. A run killed part-way is repaired by the
69
93
  next run that writes, before it writes anything. A first run that fails removes the directories it created.
70
- - **The validate step is a real type check** when the optional peer `typescript` is installed, under a consumer's
71
- strictest plausible settings and with nothing outside the tree resolvable. Without it, the check degrades to a parse
72
- with Node's own TypeScript parser, and says so loudly — it is never skipped.
94
+ - **The validate step is a real type check** when the optional peer `typescript` (5.5 to 6) is installed, under a
95
+ consumer's strictest plausible settings and with nothing outside the tree resolvable. Without it — or under
96
+ TypeScript 7, which has no classic compiler API to check with — the check degrades to a parse with Node's own
97
+ TypeScript parser, and says so loudly in one warning; it is never skipped, and the client is still written. The peer
98
+ range has no upper bound, so a frontend on TypeScript 7 installs it; your own `tsc` checks the tree when it compiles.
73
99
  - **Determinism.** Two runs over one ClientContract write the same bytes; deleting the client and regenerating it gives
74
100
  the same bytes. Nothing volatile — no timestamp, generator version, host or path — is emitted.
75
101
  - **Failure is a sentence.** A refusal is one line and exit code 1, with no stack; anything the run warned about before
@@ -78,7 +104,7 @@ npx avclient generate [--yes]
78
104
  ## Calling the API
79
105
 
80
106
  ```ts
81
- import avClient, { AvClient, type User, type UserWhere } from "./api/AvClient";
107
+ import avClient, { AvClient, type User, type UserWhere } from "./api/AvClient"; // as you import your own files
82
108
 
83
109
  const where: UserWhere = { email: { equals: "ada@example.com" } };
84
110
  const users = await avClient.User.find.many({ where, select: ["id", "email", { posts: { select: ["$count"] } }] });
@@ -165,21 +191,21 @@ chose. The `.d.ts` files are core's copied declarations under `generated/derivat
165
191
  - `generated/enums.ts` — each enum as a union type and a same-named `as const` object.
166
192
  - `generated/metadata.ts` — the ClientContract hash and protocol version the client is bound to, and the entrypoint it
167
193
  was generated from, its default deployment (an entrypoint carrying credentials is refused at resolution).
168
- - `generated/runtime/codec.ts` — the nine scalars' wire codecs (§6.2), and the request-body serializer.
194
+ - `generated/runtime/codec.ts` — the nine scalars' wire codecs, and the request-body serializer.
169
195
  - `generated/runtime/decimal.ts` — the framework's `Decimal`: a constructor, `toString` and `toJSON`.
170
196
  - `generated/runtime/descriptor.ts` — what the runtime reads of the contract: the result fields it revives and the
171
197
  relations it follows (the decode table), and the advertised operations.
172
198
  - `generated/runtime/errors.ts` — `FrameworkError`, its subclasses by code class (`AuthError`, `ConflictError`,
173
199
  `ContractMismatchError`, `InternalError`, `NotFoundError`, `ProtocolError`, `ValidationError`), `TransportError`, and
174
200
  the code unions, emitted from core's code arrays rather than retyped.
175
- - `generated/runtime/fingerprint.ts` — the operation fingerprint of §14.4 (RFC 8785 canonical JSON, SHA-256,
201
+ - `generated/runtime/fingerprint.ts` — the operation fingerprint (RFC 8785 canonical JSON, SHA-256,
176
202
  base64url), dependency-free and pinned to core's; emitted only when transactions are `interactive`.
177
203
  - `generated/runtime/transaction.ts` — `avClient.tx`'s deferred handles and `avClient.transaction`'s plan assembly,
178
204
  refusals and one POST to `/_transactions`; emitted only when transactions are `interactive`.
179
- - `generated/runtime/transport.ts` — one POST per operation with the identity headers, and the response read by §13.5's
180
- rule; arguments encoded by value, results revived by the decode table, the platform's own `fetch` type accepted.
205
+ - `generated/runtime/transport.ts` — one POST per operation with the identity headers, and the response read by the
206
+ protocol's rule; arguments encoded by value, results revived by the decode table, the platform's own `fetch` type accepted.
181
207
  Its `execute` primitive is module-private: no public file re-exports it; `AvClient`'s methods wrap it.
182
- - `generated/types.d.ts` — §15.6's named Resource types, as a declaration file (`User`, `UserWhere`,
208
+ - `generated/types.d.ts` — the named Resource types, as a declaration file (`User`, `UserWhere`,
183
209
  `UserUniqueWhere`, `UserOrderBy`, `UserCreateData`, `UserUpdateData`, `UserSelect`, `UserInclude`), each only when
184
210
  its operation is advertised.
185
211
 
@@ -198,10 +224,9 @@ A deployment whose ClientContract differs refuses the request with `A2005`, whic
198
224
 
199
225
  The hash covers everything the deployment advertises, not only its schema: two deployments of one schema can advertise
200
226
  different ClientContracts, and a client generated against one is refused by the other. Whatever the cause, the remedy
201
- is the same — **generate against the deployment the client will call, and regenerate when it changes.** (This closes
202
- the generator's half of follow-up row F-801, by the architect's decision to state the binding rather than its causes.)
227
+ is the same — **generate against the deployment the client will call, and regenerate when it changes.**
203
228
 
204
- That holds for an operation the deployment **no longer advertises**, too (F-716, closed): the server checks the client's
229
+ That holds for an operation the deployment **no longer advertises**, too: the server checks the client's
205
230
  identity before it routes, so a stale client calling a removed operation also hears `A2005` and throws
206
231
  `ContractMismatchError`. A response that is not a framework envelope at all — a host's own 404 for a path it does not
207
232
  mount — is a `TransportError`, which names no remedy: nothing in it says the client is stale.
@@ -209,59 +234,34 @@ mount — is a `TransportError`, which names no remedy: nothing in it says the c
209
234
  The embedded entrypoint is a **default**: `new AvClient({ entrypoint })` may point a client at another deployment, and
210
235
  doing so re-binds nothing — the ClientContract hash still decides whether that deployment accepts the client.
211
236
 
212
- ## Phase 12's deliverables
213
-
214
- | Phase 12 deliverable | Status |
215
- |---|---|
216
- | `avclient generate` CLI (bin `avclient`, R3) | Delivered, with `--yes` and the foreign-content question. |
217
- | `.env` loading | Delivered: the five-file cascade, no dependency. |
218
- | Deployed `/_contract` fetch | Delivered: conditional on the output it finds (`If-None-Match`, `304` → "up to date" only when re-emission is byte-identical). |
219
- | Protocol compatibility validation | Delivered: the supported-version set, and the hash recomputed with core's function and compared — never trusted as delivered. |
220
- | ClientContract structure validation | Delivered, by core's `validateClientContractStructure`. |
221
- | Deterministic named type generation | Delivered: §15.6's named types as aliases over core's derivation, the rename ladder keeping every derived name free. |
222
- | Resource operation methods, the client class and the default singleton | Delivered: `AvClient` and `avClient`, one method per advertised operation (P4: the runtime's list equals the type grammar's keys over both pilots). |
223
- | Projection inference | Delivered: core's own call grammar — the caller's `select`/`include` literal inferred. Facts the capability model cannot state yet are unstated on both sides alike: **F-809**, **F-812**, **F-813**, **F-818** no longer block the client. |
224
- | Deferred transaction builder | Delivered, iff the ClientContract advertises `interactive`; core's Contract-derived `Operation<T>` (**F-701**, closed) types each handle. |
225
- | Transport runtime emitted into output | Delivered, including the stale-client mechanism (**F-716**, closed: server-side). |
226
- | Scalar codecs | Delivered: all nine scalars of §6.2, encoded by value, revived by a decode table. |
227
- | Typed framework and transport errors | Delivered. |
228
- | Contract version and hash embedded into output | Delivered (`generated/metadata.ts`), with the resolved entrypoint as the default (§15.2). |
229
- | Atomic temp → validate → replace | Delivered. |
230
- | Generated-file ownership banners | Delivered. |
231
- | No runtime import from `@aventara/client` | Delivered, and gated: nothing outside the tree, `@aventara/core` included. |
232
- | Availability readers | Read through core's `isOperationVariantAvailable` (**F-820**, closed). Not hand-rolled here. |
233
-
234
- ### Phase 12's exit gate
235
-
236
- - *"Delete the generated client, regenerate it from the same contract, and obtain equivalent output"* — **passed, and
237
- strengthened to byte-identical**, through the packed bin against a real HTTP server, with the callable surface, core's
238
- derivation and the transaction runtime in the tree.
239
- - *"A generated test application compiles without depending on server source types"* — **passed, and run**: a
240
- throwaway project where neither `@aventara/core` nor `@aventara/client` can resolve generates its client from a
241
- running host, compiles a program against it with `tsc`, and runs it — scalars revived, an enum, a `$count` envelope, a
242
- transaction with a `$ref`, a `NotFoundError`, an abort, and `ContractMismatchError` after the host's contract changes.
243
-
244
- ### Type-checking cost in a consumer
237
+ ## Type-checking cost in a consumer
245
238
 
246
239
  A consumer pays for the calls it type-checks, not for the schema — under `skipLibCheck: true` (the common default),
247
240
  where the named types (`generated/types.d.ts`) and core's derivation are declaration files checked only where read:
248
241
  about 33,000 instantiations for a 50-Resource schema and seven typed calls. Under `skipLibCheck: false` every named type
249
- is resolved where it is declared, about 2,900 instantiations per Resource (**F-839**): about 204,000 for the same
242
+ is resolved where it is declared, about 2,900 instantiations per Resource: about 204,000 for the same
250
243
  program, the 500,000 line near 150 Resources.
251
244
 
252
- ### Known seams, to be closed when their owners land
253
-
254
- - **The envelope check** — "did a framework envelope arrive?" — is the emitted transport's own, pinned to core's
255
- `AvProtocol.isOperationResponse` over every corpus row.
256
- - **Specification edits owed to the specification owner** are listed in follow-up row F-838: the names (`avClient`,
257
- `AvClient`, `CallOptions`), the emitted tree's layout, the forms map, credentials, "up to date".
258
- - **The end-to-end and ownership gates read a gitignored file by path:** the SQLite pilot's discovery artifact,
259
- `packages/prisma7-adapter/generated/artifact/sqlite.artifact.ts`, which exists only after the adapter's generate step
260
- has run (`pnpm typecheck` runs it). This package takes no dependency on the adapter.
261
- - **The `Aventara` wire prefix** of the identity headers must be frozen before the first external release (§12.4). It is
262
- one constant in `generated/runtime/transport.ts`, so the freeze is a one-line change.
263
- - **A tarball from `npm pack` is not a releasable artifact** while `@aventara/core` is spelled `workspace:*`, and does
264
- not install outside this workspace (F-808, Phase 13). Do not validate a pilot against one.
245
+ ## Before 1.0
246
+
247
+ - **The `Aventara` wire prefix** of the protocol's headers (`Aventara-Protocol-Version`, `Aventara-Contract-Hash`,
248
+ `Aventara-Request-Id`) may still change before the first stable release. It is one constant in the generated
249
+ `generated/runtime/transport.ts`; regenerating picks up a change.
250
+
251
+ ## Known issues
252
+
253
+ - **The generator read the wrong `tsconfig.json`.** In a monorepo or a non-standard layout, the nearest
254
+ `tsconfig.json` above `generateAt` may not be the one your project compiles the client with, and the client's
255
+ imports or module format then do not match your compiler (`Cannot find module './generated/client.js'`, an
256
+ extension your bundler will not resolve). Name the right one in `framework.client.ts`, relative to the config file:
257
+
258
+ ```ts
259
+ export default defineClientConfig({
260
+ entrypoint: env("AVENTARA_API_URL"),
261
+ generateAt: "./src/api",
262
+ tsconfigFile: "./tsconfig.app.json",
263
+ });
264
+ ```
265
265
 
266
266
  ## License
267
267
 
@@ -1,15 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  import { refuseUnsupportedNode } from "./node-version.guard.js";
3
- /**
4
- * `avclient`'s entry, what the manifest's `bin` names (F-855). It always runs — no
5
- * `import.meta.main` — and checks this Node against the package's
6
- * `engines.node` before it loads anything else: the program is imported only
7
- * once the guard admits this Node, so an older Node meets one sentence and exit
8
- * 1, never a silent exit 0 or a parse error from a module it cannot run.
9
- *
10
- * A promise chain rather than a top-level `await`, which Node 12 cannot parse:
11
- * this file is read by the Nodes the package does not support.
12
- */
13
3
  if (!refuseUnsupportedNode("avclient", new URL("../package.json", import.meta.url))) {
14
4
  void import("./cli.js").then((program) => program.runFromProcess());
15
5
  }
@@ -1,12 +1,16 @@
1
1
  /**
2
- * The `avclient` command line: one command, its one flag, and help. Everything
3
- * the generator needs is in `framework.client.ts` and the `.env` cascade (§15.2),
4
- * so `generate` takes no flag that would duplicate a config member — a second
5
- * source of the same fact. `--yes` is not one: it answers the one question the
6
- * generator asks (architect, 2026-10-04).
2
+ * The `avclient` command line: one command, its one flag, and help. Everything the
3
+ * generator needs is in `framework.client.ts` and the `.env` cascade, so
4
+ * `generate` takes no flag that would duplicate a config member — a second source
5
+ * of the same fact. `--yes` is not one: it answers the one question the generator
6
+ * asks.
7
7
  */
8
- export declare const USAGE = "avclient \u2014 generate a typed Aventara client from a deployed ClientContract.\n\nUsage:\n avclient generate [--yes] Read framework.client.ts in the current directory,\n fetch <entrypoint>/_contract, and write AvClient.ts\n and generated/ into its generateAt directory.\n avclient init [options] Set up this frontend: write framework.client.ts, the\n .env entry, the avclient:generate script and the\n @aventara/client devDependency; install; generate.\n --entrypoint <url> The server's entrypoint [http://localhost:3000/api].\n --env-var <NAME> Read it from this variable [AVENTARA_API_URL].\n --no-env-var Write it into framework.client.ts as a literal.\n --generate-at <dir> Where the client goes [./src/api].\n --package-manager <npm|pnpm> [the lockfile's, else the launching one, else npm]\n --skip-install Print the install instead of running it.\n --skip-generate Do not generate now.\n -y, --yes Accept every default, and replace differing content.\n avclient --help Print this and exit 0.\n\nOptions:\n -y, --yes Overwrite or remove content in AvClient.ts and generated/ that the\n generator did not produce, without asking. Without it, such content\n is listed and you are asked; where nobody can answer (stdin is not a\n terminal, as in CI), the run is refused and nothing is touched.\n\nThe generator owns AvClient.ts and generated/ in generateAt, and nothing else\nthere: your own files beside them are never read, moved or removed. Generated\nfiles are replaced on every run; do not edit them.\n\nBefore framework.client.ts is evaluated, the .env cascade is read from the\ncurrent directory, highest precedence first: the process environment,\n.env.<mode>.local, .env.<mode>, .env.local, .env \u2014 where mode is NODE_ENV, or\n\"development\".\n";
9
- /** `avclient init`'s command line (R4, Q16 rows 12–15). */
8
+ export declare const USAGE = "avclient \u2014 generate a typed Aventara client from a deployed ClientContract.\n\nUsage:\n avclient generate [--yes] Read framework.client.ts (or .js, .mjs, .cjs, .mts,\n .cts) in the current directory, fetch\n <entrypoint>/_contract, and write AvClient.ts and\n generated/ into its generateAt directory, its imports\n spelled as the project's tsconfig.json says.\n avclient init [options] Set up this frontend: write framework.client.ts, the\n .env entry, the avclient:generate script and the\n @aventara/client devDependency; install; generate.\n --entrypoint <url> The server's entrypoint [http://localhost:3000/api].\n --env-var <NAME> Read it from this variable [AVENTARA_API_URL].\n --no-env-var Write it into framework.client.ts as a literal.\n --generate-at <dir> Where the client goes [./src/api].\n --package-manager <npm|pnpm> [the lockfile's, else the launching one, else npm]\n --skip-install Print the install instead of running it.\n --skip-generate Do not generate now.\n -y, --yes Accept every default, and replace differing content.\n avclient --help Print this and exit 0.\n avclient <command> --help Print that command's usage and exit 0.\n avclient --version, -v Print this generator's version and exit 0.\n\nOptions:\n -y, --yes Overwrite or remove content in AvClient.ts and generated/ that the\n generator did not produce, without asking. Without it, such content\n is listed and you are asked; where nobody can answer (stdin is not a\n terminal, as in CI), the run is refused and nothing is touched.\n\nThe generator owns AvClient.ts and generated/ in generateAt, and nothing else\nthere: your own files beside them are never read, moved or removed. Generated\nfiles are replaced on every run; do not edit them.\n\nBefore framework.client.ts is evaluated, the .env cascade is read from the\ncurrent directory, highest precedence first: the process environment,\n.env.<mode>.local, .env.<mode>, .env.local, .env \u2014 where mode is NODE_ENV, or\n\"development\".\n";
9
+ /** `avclient generate --help` (pilot.1): that command's usage alone. */
10
+ export declare const GENERATE_USAGE = "Usage: avclient generate [--yes]\n\nRead framework.client.ts (or .js, .mjs, .cjs, .mts, .cts) in the current directory,\nfetch <entrypoint>/_contract, and write AvClient.ts and generated/ into its\ngenerateAt directory, its imports spelled as the project's tsconfig.json says.\n\nOptions:\n -y, --yes Overwrite or remove content in AvClient.ts and generated/ that the\n generator did not produce, without asking. Without it, such content\n is listed and you are asked; where nobody can answer (stdin is not a\n terminal, as in CI), the run is refused and nothing is touched.\n\nThe generator owns AvClient.ts and generated/ in generateAt, and nothing else\nthere: your own files beside them are never read, moved or removed.\n\nBefore the config is evaluated, the .env cascade is read from the current\ndirectory, highest precedence first: the process environment, .env.<mode>.local,\n.env.<mode>, .env.local, .env \u2014 where mode is NODE_ENV, or \"development\".\n";
11
+ /** `avclient init --help` (pilot.1): that command's usage alone. */
12
+ export declare const INIT_USAGE = "Usage: avclient init [options]\n\nSet up this frontend: write framework.client.ts, the .env entry, the avclient:generate\nscript and the @aventara/client devDependency; install; generate.\n\nOptions:\n --entrypoint <url> The server's entrypoint [http://localhost:3000/api].\n --env-var <NAME> Read it from this variable [AVENTARA_API_URL].\n --no-env-var Write it into framework.client.ts as a literal.\n --generate-at <dir> Where the client goes [./src/api].\n --package-manager <npm|pnpm> [the lockfile's, else the launching one, else npm]\n --skip-install Print the install instead of running it.\n --skip-generate Do not generate now.\n -y, --yes Accept every default, and replace differing content.\n\nOn a terminal every unanswered question is asked; anywhere else, pass its flag\nor --yes, or the run stops before writing anything.\n";
13
+ /** `avclient init`'s command line. */
10
14
  export type ClientInitCommand = {
11
15
  readonly command: "init";
12
16
  readonly given: Readonly<Partial<Record<"entrypoint" | "envVar" | "generateAt" | "packageManager", string>>>;
@@ -16,8 +20,13 @@ export type ClientInitCommand = {
16
20
  readonly yes: boolean;
17
21
  };
18
22
  /** What the command line asked for. */
19
- export type CliCommand = {
23
+ export type CliCommand =
24
+ /** `--help`; with `topic`, `avclient <topic> --help` (pilot.1). */
25
+ {
20
26
  readonly command: "help";
27
+ readonly topic?: "generate" | "init";
28
+ } | {
29
+ readonly command: "version";
21
30
  } | {
22
31
  readonly command: "generate";
23
32
  readonly yes: boolean;
@@ -26,5 +35,8 @@ export type CliCommand = {
26
35
  export declare class CliCommandError extends Error {
27
36
  readonly name = "CliCommandError";
28
37
  }
29
- /** @throws CliCommandError when `argv` is not `generate [--yes|-y]`, `--help` or `-h`. */
38
+ /**
39
+ * @throws CliCommandError when `argv` is not `generate [--yes|-y]`, `init …`,
40
+ * `--help`/`-h` or `--version`/`-v`.
41
+ */
30
42
  export declare function parseCliCommand(argv: readonly string[]): CliCommand;
@@ -1,17 +1,12 @@
1
1
  import { CLIENT_CONFIG_FILE } from "../config/config.loader.js";
2
- /**
3
- * The `avclient` command line: one command, its one flag, and help. Everything
4
- * the generator needs is in `framework.client.ts` and the `.env` cascade (§15.2),
5
- * so `generate` takes no flag that would duplicate a config member — a second
6
- * source of the same fact. `--yes` is not one: it answers the one question the
7
- * generator asks (architect, 2026-10-04).
8
- */
9
2
  export const USAGE = `avclient — generate a typed Aventara client from a deployed ClientContract.
10
3
 
11
4
  Usage:
12
- avclient generate [--yes] Read ${CLIENT_CONFIG_FILE} in the current directory,
13
- fetch <entrypoint>/_contract, and write AvClient.ts
14
- and generated/ into its generateAt directory.
5
+ avclient generate [--yes] Read ${CLIENT_CONFIG_FILE} (or .js, .mjs, .cjs, .mts,
6
+ .cts) in the current directory, fetch
7
+ <entrypoint>/_contract, and write AvClient.ts and
8
+ generated/ into its generateAt directory, its imports
9
+ spelled as the project's tsconfig.json says.
15
10
  avclient init [options] Set up this frontend: write ${CLIENT_CONFIG_FILE}, the
16
11
  .env entry, the avclient:generate script and the
17
12
  @aventara/client devDependency; install; generate.
@@ -24,6 +19,8 @@ Usage:
24
19
  --skip-generate Do not generate now.
25
20
  -y, --yes Accept every default, and replace differing content.
26
21
  avclient --help Print this and exit 0.
22
+ avclient <command> --help Print that command's usage and exit 0.
23
+ avclient --version, -v Print this generator's version and exit 0.
27
24
 
28
25
  Options:
29
26
  -y, --yes Overwrite or remove content in AvClient.ts and generated/ that the
@@ -40,11 +37,46 @@ current directory, highest precedence first: the process environment,
40
37
  .env.<mode>.local, .env.<mode>, .env.local, .env — where mode is NODE_ENV, or
41
38
  "development".
42
39
  `;
43
- /** The command line cannot be understood. A refusal: one sentence, exit 1. */
40
+ export const GENERATE_USAGE = `Usage: avclient generate [--yes]
41
+
42
+ Read ${CLIENT_CONFIG_FILE} (or .js, .mjs, .cjs, .mts, .cts) in the current directory,
43
+ fetch <entrypoint>/_contract, and write AvClient.ts and generated/ into its
44
+ generateAt directory, its imports spelled as the project's tsconfig.json says.
45
+
46
+ Options:
47
+ -y, --yes Overwrite or remove content in AvClient.ts and generated/ that the
48
+ generator did not produce, without asking. Without it, such content
49
+ is listed and you are asked; where nobody can answer (stdin is not a
50
+ terminal, as in CI), the run is refused and nothing is touched.
51
+
52
+ The generator owns AvClient.ts and generated/ in generateAt, and nothing else
53
+ there: your own files beside them are never read, moved or removed.
54
+
55
+ Before the config is evaluated, the .env cascade is read from the current
56
+ directory, highest precedence first: the process environment, .env.<mode>.local,
57
+ .env.<mode>, .env.local, .env — where mode is NODE_ENV, or "development".
58
+ `;
59
+ export const INIT_USAGE = `Usage: avclient init [options]
60
+
61
+ Set up this frontend: write ${CLIENT_CONFIG_FILE}, the .env entry, the avclient:generate
62
+ script and the @aventara/client devDependency; install; generate.
63
+
64
+ Options:
65
+ --entrypoint <url> The server's entrypoint [http://localhost:3000/api].
66
+ --env-var <NAME> Read it from this variable [AVENTARA_API_URL].
67
+ --no-env-var Write it into ${CLIENT_CONFIG_FILE} as a literal.
68
+ --generate-at <dir> Where the client goes [./src/api].
69
+ --package-manager <npm|pnpm> [the lockfile's, else the launching one, else npm]
70
+ --skip-install Print the install instead of running it.
71
+ --skip-generate Do not generate now.
72
+ -y, --yes Accept every default, and replace differing content.
73
+
74
+ On a terminal every unanswered question is asked; anywhere else, pass its flag
75
+ or --yes, or the run stops before writing anything.
76
+ `;
44
77
  export class CliCommandError extends Error {
45
78
  name = "CliCommandError";
46
79
  }
47
- /** @throws CliCommandError when `argv` is not `generate [--yes|-y]`, `--help` or `-h`. */
48
80
  export function parseCliCommand(argv) {
49
81
  const [command, ...rest] = argv;
50
82
  const help = "run `avclient --help` for usage.";
@@ -54,6 +86,13 @@ export function parseCliCommand(argv) {
54
86
  if (command === "--help" || command === "-h") {
55
87
  return { command: "help" };
56
88
  }
89
+ if (command === "--version" || command === "-v") {
90
+ return { command: "version" };
91
+ }
92
+ if ((command === "init" || command === "generate") &&
93
+ (rest.includes("--help") || rest.includes("-h"))) {
94
+ return { command: "help", topic: command };
95
+ }
57
96
  if (command === "init") {
58
97
  return parseInit(rest, help);
59
98
  }
@@ -1,11 +1,5 @@
1
1
  import { generateClient } from "../generate.js";
2
2
  import { FOREIGN_CONTENT_QUESTION, renderForeignContentCancellation, renderForeignContentWarning, renderGenerationSuccess, renderUpToDate, } from "./generation-success.renderer.js";
3
- /**
4
- * `avclient generate` — the generation, the generateAt rule's question, and the
5
- * report. Shared by `init`, which generates once the project is set up.
6
- *
7
- * @throws whatever the generation raises; the caller renders it.
8
- */
9
3
  export async function runGenerate(io, yes) {
10
4
  const outcome = await generateClient({
11
5
  directory: io.cwd,
@@ -6,22 +6,9 @@ import { GeneratedNameError } from "../emit/name.deriver.js";
6
6
  import { ClientInitNotConfirmedError, ClientInitStepError, } from "../init/client-init.errors.js";
7
7
  import { ClientInitAnswerError, ClientInitUnansweredError, } from "../init/client-init.questions.js";
8
8
  import { ClientProjectRefusedError } from "../init/client-project.inspector.js";
9
- import { UnsupportedTypeScriptError } from "../output/output.validator.js";
10
9
  import { OutputWriteError, warningsRaisedBeforeDefect, } from "../output/output.writer.js";
11
10
  import { CliCommandError } from "./command.parser.js";
12
11
  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
12
  const REFUSALS = [
26
13
  CliCommandError,
27
14
  ClientConfigError,
@@ -30,7 +17,6 @@ const REFUSALS = [
30
17
  ContractProtocolError,
31
18
  GeneratedNameError,
32
19
  OutputWriteError,
33
- UnsupportedTypeScriptError,
34
20
  ClientInitAnswerError,
35
21
  ClientInitUnansweredError,
36
22
  ClientProjectRefusedError,
@@ -41,8 +27,6 @@ export function renderGenerationFailure(error) {
41
27
  if (REFUSALS.some((refusal) => error instanceof refusal)) {
42
28
  const carried = error instanceof OutputWriteError ? error.warnings : [];
43
29
  return {
44
- // A refused contract carries its own (plan §7); the other refusals are 1,
45
- // the adapter CLI's precedent (M3).
46
30
  exitCode: error instanceof ContractProtocolError ? error.exitCode : 1,
47
31
  text: `${renderWarningLines(carried)}avclient: ${error.message}\n`,
48
32
  };
@@ -13,7 +13,10 @@ export interface GenerationSuccessReport {
13
13
  readonly stderr: string;
14
14
  }
15
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. */
16
+ /**
17
+ * Nothing written: the deployment's ClientContract and these bytes are what is
18
+ * there.
19
+ */
17
20
  export declare function renderUpToDate(result: ClientUpToDate): GenerationSuccessReport;
18
21
  /**
19
22
  * What the person running the generator is told before being asked: every path
@@ -10,32 +10,19 @@ export function renderGenerationSuccess(result) {
10
10
  stderr: renderWarningLines(result.warnings),
11
11
  };
12
12
  }
13
- /** Nothing written (Phase 12-rest Q6): the deployment's ClientContract and these bytes are what is there. */
14
13
  export function renderUpToDate(result) {
15
14
  return {
16
15
  stdout: `avclient: up to date: ${result.generateAt} already holds this deployment's client; nothing was written.\n`,
17
16
  stderr: renderWarningLines(result.warnings),
18
17
  };
19
18
  }
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
19
  export function renderForeignContentWarning(found) {
26
20
  return (`${WARNING_LINE_PREFIX}${found.foreign.join(", ")} in ${found.generateAt} ` +
27
21
  `${found.foreign.length === 1 ? "was" : "were"} not generated by @aventara/client, ` +
28
22
  "and generating will overwrite or remove " +
29
23
  `${found.foreign.length === 1 ? "it" : "them"}.\n`);
30
24
  }
31
- /** The question; answered yes, generation proceeds. */
32
25
  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
26
  export function renderForeignContentCancellation(found, asked) {
40
27
  const them = found.foreign.length === 1 ? "it" : "them";
41
28
  return `${renderWarningLines(found.warnings)}${asked
@@ -2,8 +2,7 @@
2
2
  * The terminal's questions — `generate`'s yes/no and `init`'s wizard — over one
3
3
  * `node:readline` interface whose lines are **queued**: a line typed (or piped
4
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).
5
+ * interface per question, or `readline/promises`' `question`, drops it.
7
6
  */
8
7
  export type TerminalPrompter = {
9
8
  readonly ask: (prompt: string) => Promise<string>;
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * How one warning reaches the person running the generator: its own stderr line,
3
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.
4
+ * says "warning" once. Shared by a success and a refusal, so a warning reads the
5
+ * same either way.
6
6
  */
7
7
  /** The prefix every warning line opens with. */
8
8
  export declare const WARNING_LINE_PREFIX = "avclient: warning: ";
@@ -1,12 +1,4 @@
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
1
  export const WARNING_LINE_PREFIX = "avclient: warning: ";
9
- /** One newline-terminated line per warning, in the order given; empty for none. */
10
2
  export function renderWarningLines(warnings) {
11
3
  return warnings
12
4
  .map((warning) => `${WARNING_LINE_PREFIX}${warning}\n`)
package/dist/cli.d.ts CHANGED
@@ -1,22 +1,14 @@
1
1
  import { type CliIo } from "./cli/generate.command.js";
2
2
  export type { CliIo } from "./cli/generate.command.js";
3
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.
4
+ * It is also the one place that ASKS: when content the generator did not produce
5
+ * stands in `AvClient.ts` or `generated/`, the paths are listed and the person is
6
+ * asked whether to overwrite them. `--yes` answers for them. Where nobody can
7
+ * answer — stdin is not a terminal — it never asks and never overrides: it
8
+ * refuses, naming `--yes`. Cancelled or refused, nothing was touched, the run's
9
+ * warnings are printed before the sentence, and the exit code is 1. A killed run's
10
+ * leftovers are looked at before the question, so the answer — and `--yes` —
11
+ * covers what it hid as well.
20
12
  */
21
13
  /** Runs one command line; resolves to the exit code. Never throws. */
22
14
  export declare function runCli(argv: readonly string[], io: CliIo): Promise<number>;
package/dist/cli.js CHANGED
@@ -1,33 +1,23 @@
1
- import { parseCliCommand, USAGE } from "./cli/command.parser.js";
1
+ import { GENERATE_USAGE, INIT_USAGE, parseCliCommand, USAGE, } from "./cli/command.parser.js";
2
2
  import { runGenerate } from "./cli/generate.command.js";
3
3
  import { renderGenerationFailure } from "./cli/generation-failure.renderer.js";
4
4
  import { createTerminalPrompter } from "./cli/terminal.prompter.js";
5
+ import { AVENTARA_CLIENT_GENERATOR_VERSION } from "./index.js";
5
6
  import { runClientInit } from "./init/client-init.orchestrator.js";
6
7
  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
8
  export async function runCli(argv, io) {
27
9
  try {
28
10
  const command = parseCliCommand(argv);
29
11
  if (command.command === "help") {
30
- io.stdout(USAGE);
12
+ io.stdout(command.topic === undefined
13
+ ? USAGE
14
+ : command.topic === "init"
15
+ ? INIT_USAGE
16
+ : GENERATE_USAGE);
17
+ return 0;
18
+ }
19
+ if (command.command === "version") {
20
+ io.stdout(`${AVENTARA_CLIENT_GENERATOR_VERSION}\n`);
31
21
  return 0;
32
22
  }
33
23
  if (command.command === "init") {
@@ -41,12 +31,6 @@ export async function runCli(argv, io) {
41
31
  return failure.exitCode;
42
32
  }
43
33
  }
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
34
  export async function runFromProcess() {
51
35
  const terminal = createTerminalPrompter();
52
36
  try {