@aventara/client 0.0.0-stage → 0.1.0-pilot.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +91 -0
- package/LICENSE-ADDITIONAL-PERMISSION.md +9 -0
- package/README.md +234 -2
- package/dist/avclient.bin.d.ts +2 -0
- package/dist/avclient.bin.js +15 -0
- package/dist/cli/command.parser.d.ts +37 -0
- package/dist/cli/command.parser.js +177 -0
- package/dist/cli/generate.command.d.ts +24 -0
- package/dist/cli/generate.command.js +41 -0
- package/dist/cli/generation-failure.renderer.d.ts +6 -0
- package/dist/cli/generation-failure.renderer.js +52 -0
- package/dist/cli/generation-success.renderer.d.ts +32 -0
- package/dist/cli/generation-success.renderer.js +47 -0
- package/dist/cli/terminal.prompter.d.ts +13 -0
- package/dist/cli/terminal.prompter.js +53 -0
- package/dist/cli/warning.renderer.d.ts +10 -0
- package/dist/cli/warning.renderer.js +14 -0
- package/dist/cli.d.ts +29 -0
- package/dist/cli.js +75 -0
- package/dist/config/client-config.interface.d.ts +62 -0
- package/dist/config/client-config.interface.js +14 -0
- package/dist/config/config.loader.d.ts +41 -0
- package/dist/config/config.loader.js +95 -0
- package/dist/config/config.resolver.d.ts +50 -0
- package/dist/config/config.resolver.js +126 -0
- package/dist/config/env.cascade.d.ts +84 -0
- package/dist/config/env.cascade.js +126 -0
- package/dist/contract/contract.acceptance.d.ts +77 -0
- package/dist/contract/contract.acceptance.js +124 -0
- package/dist/contract/contract.fetcher.d.ts +64 -0
- package/dist/contract/contract.fetcher.js +85 -0
- package/dist/contract/contract.loader.d.ts +32 -0
- package/dist/contract/contract.loader.js +32 -0
- package/dist/emit/banner.emitter.d.ts +31 -0
- package/dist/emit/banner.emitter.js +42 -0
- package/dist/emit/client-surface.emitter.d.ts +32 -0
- package/dist/emit/client-surface.emitter.js +236 -0
- package/dist/emit/client-tree.emitter.d.ts +37 -0
- package/dist/emit/client-tree.emitter.js +103 -0
- package/dist/emit/contract-carrier.emitter.d.ts +13 -0
- package/dist/emit/contract-carrier.emitter.js +60 -0
- package/dist/emit/derivation.emitter.d.ts +45 -0
- package/dist/emit/derivation.emitter.js +233 -0
- package/dist/emit/descriptor.emitter.d.ts +4 -0
- package/dist/emit/descriptor.emitter.js +97 -0
- package/dist/emit/emitted-tree.interface.d.ts +61 -0
- package/dist/emit/emitted-tree.interface.js +18 -0
- package/dist/emit/enum.emitter.d.ts +24 -0
- package/dist/emit/enum.emitter.js +42 -0
- package/dist/emit/name.deriver.d.ts +153 -0
- package/dist/emit/name.deriver.js +411 -0
- package/dist/emit/named-type.emitter.d.ts +32 -0
- package/dist/emit/named-type.emitter.js +50 -0
- package/dist/emit/runtime.emitter.d.ts +87 -0
- package/dist/emit/runtime.emitter.js +707 -0
- package/dist/emit/scalar.codec.d.ts +63 -0
- package/dist/emit/scalar.codec.js +498 -0
- package/dist/emit/transaction.emitter.d.ts +17 -0
- package/dist/emit/transaction.emitter.js +438 -0
- package/dist/generate.d.ts +123 -0
- package/dist/generate.js +98 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/init/client-config.template.d.ts +11 -0
- package/dist/init/client-config.template.js +27 -0
- package/dist/init/client-init.errors.d.ts +9 -0
- package/dist/init/client-init.errors.js +9 -0
- package/dist/init/client-init.orchestrator.d.ts +3 -0
- package/dist/init/client-init.orchestrator.js +86 -0
- package/dist/init/client-init.planner.d.ts +27 -0
- package/dist/init/client-init.planner.js +99 -0
- package/dist/init/client-init.questions.d.ts +52 -0
- package/dist/init/client-init.questions.js +124 -0
- package/dist/init/client-project.inspector.d.ts +15 -0
- package/dist/init/client-project.inspector.js +32 -0
- package/dist/init/command.runner.d.ts +8 -0
- package/dist/init/command.runner.js +17 -0
- package/dist/node-version.guard.d.ts +8 -0
- package/dist/node-version.guard.js +59 -0
- package/dist/output/output.validator.d.ts +75 -0
- package/dist/output/output.validator.js +262 -0
- package/dist/output/output.writer.d.ts +162 -0
- package/dist/output/output.writer.js +499 -0
- package/package.json +47 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
Required Notice: Copyright 2026 Mohamed Ragheb
|
|
2
|
+
|
|
3
|
+
# PolyForm Shield License 1.0.0
|
|
4
|
+
|
|
5
|
+
<https://polyformproject.org/licenses/shield/1.0.0>
|
|
6
|
+
|
|
7
|
+
## Acceptance
|
|
8
|
+
|
|
9
|
+
In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.
|
|
10
|
+
|
|
11
|
+
## Copyright License
|
|
12
|
+
|
|
13
|
+
The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright in it for any permitted purpose. However, you may only distribute the software according to [Distribution License](#distribution-license) and make changes or new works based on the software according to [Changes and New Works License](#changes-and-new-works-license).
|
|
14
|
+
|
|
15
|
+
## Distribution License
|
|
16
|
+
|
|
17
|
+
The licensor grants you an additional copyright license to distribute copies of the software. Your license to distribute covers distributing the software with changes and new works permitted by [Changes and New Works License](#changes-and-new-works-license).
|
|
18
|
+
|
|
19
|
+
## Notices
|
|
20
|
+
|
|
21
|
+
You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms or the URL for them above, as well as copies of any plain-text lines beginning with `Required Notice:` that the licensor provided with the software. For example:
|
|
22
|
+
|
|
23
|
+
> Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
|
|
24
|
+
|
|
25
|
+
## Changes and New Works License
|
|
26
|
+
|
|
27
|
+
The licensor grants you an additional copyright license to make changes and new works based on the software for any permitted purpose.
|
|
28
|
+
|
|
29
|
+
## Patent License
|
|
30
|
+
|
|
31
|
+
The licensor grants you a patent license for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.
|
|
32
|
+
|
|
33
|
+
## Noncompete
|
|
34
|
+
|
|
35
|
+
Any purpose is a permitted purpose, except for providing any product that competes with the software or any product the licensor or any of its affiliates provides using the software.
|
|
36
|
+
|
|
37
|
+
## Competition
|
|
38
|
+
|
|
39
|
+
Goods and services compete even when they provide functionality through different kinds of interfaces or for different technical platforms. Applications can compete with services, libraries with plugins, frameworks with development tools, and so on, even if they're written in different programming languages or for different computer architectures. Goods and services compete even when provided free of charge. If you market a product as a practical substitute for the software or another product, it definitely competes.
|
|
40
|
+
|
|
41
|
+
## New Products
|
|
42
|
+
|
|
43
|
+
If you are using the software to provide a product that does not compete, but the licensor or any of its affiliates brings your product into competition by providing a new version of the software or another product using the software, you may continue using versions of the software available under these terms beforehand to provide your competing product, but not any later versions.
|
|
44
|
+
|
|
45
|
+
## Discontinued Products
|
|
46
|
+
|
|
47
|
+
You may begin using the software to compete with a product or service that the licensor or any of its affiliates has stopped providing, unless the licensor includes a plain-text line beginning with `Licensor Line of Business:` with the software that mentions that line of business. For example:
|
|
48
|
+
|
|
49
|
+
> Licensor Line of Business: YoyodyneCMS Content Management System (http://example.com/cms)
|
|
50
|
+
|
|
51
|
+
## Sales of Business
|
|
52
|
+
|
|
53
|
+
If the licensor or any of its affiliates sells a line of business developing the software or using the software to provide a product, the buyer can also enforce [Noncompete](#noncompete) for that product.
|
|
54
|
+
|
|
55
|
+
## Fair Use
|
|
56
|
+
|
|
57
|
+
You may have "fair use" rights for the software under the law. These terms do not limit them.
|
|
58
|
+
|
|
59
|
+
## No Other Rights
|
|
60
|
+
|
|
61
|
+
These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses.
|
|
62
|
+
|
|
63
|
+
## Patent Defense
|
|
64
|
+
|
|
65
|
+
If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
|
|
66
|
+
|
|
67
|
+
## Violations
|
|
68
|
+
|
|
69
|
+
The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 32 days of receiving notice. Otherwise, all your licenses end immediately.
|
|
70
|
+
|
|
71
|
+
## No Liability
|
|
72
|
+
|
|
73
|
+
***As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.***
|
|
74
|
+
|
|
75
|
+
## Definitions
|
|
76
|
+
|
|
77
|
+
The **licensor** is the individual or entity offering these terms, and the **software** is the software the licensor makes available under these terms.
|
|
78
|
+
|
|
79
|
+
A **product** can be a good or service, or a combination of them.
|
|
80
|
+
|
|
81
|
+
**You** refers to the individual or entity agreeing to these terms.
|
|
82
|
+
|
|
83
|
+
**Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all its affiliates.
|
|
84
|
+
|
|
85
|
+
**Affiliates** means the other organizations than an organization has control over, is under the control of, or is under common control with.
|
|
86
|
+
|
|
87
|
+
**Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
|
|
88
|
+
|
|
89
|
+
**Your licenses** are all the licenses granted to you for the software under these terms.
|
|
90
|
+
|
|
91
|
+
**Use** means anything you do with the software requiring one of your licenses.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Additional Permission — Aventara
|
|
2
|
+
|
|
3
|
+
Copyright 2026 Mohamed Ragheb
|
|
4
|
+
|
|
5
|
+
In addition to the permissions of the PolyForm Shield License 1.0.0 in `LICENSE`, the licensor grants the following additional permission:
|
|
6
|
+
|
|
7
|
+
The Noncompete section applies only to providing a product that competes with the software itself — the Aventara framework, its packages and its tooling. Providing a product that competes with any other product the licensor or its affiliates provide using the software is a permitted purpose.
|
|
8
|
+
|
|
9
|
+
This additional permission only adds to your permissions; it does not restrict or replace any term of `LICENSE`.
|
package/README.md
CHANGED
|
@@ -1,3 +1,235 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@aventara/client`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The development-time generator for Aventara typed remote clients. It fetches a deployed framework's `ClientContract`
|
|
4
|
+
from `<entrypoint>/_contract`, verifies it, and writes a standalone TypeScript client into your project. The generated
|
|
5
|
+
client is the product: it imports nothing from this package, or from `@aventara/core`, at runtime.
|
|
6
|
+
|
|
7
|
+
Every argument and result type in the generated client is core's own derivation — `@aventara/core`'s published
|
|
8
|
+
declarations, copied into the tree and instantiated over the deployment's ClientContract — so the client agrees with the
|
|
9
|
+
server by construction, and that agreement is gated over both pilot schemas.
|
|
10
|
+
|
|
11
|
+
## Setting up a frontend: `avclient init`
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @aventara/client@pilot init # in your frontend project; pnpm: pnpm dlx @aventara/client@pilot init
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
It asks — or takes from flags, or with `--yes` takes every default — four things, and writes the setup:
|
|
18
|
+
|
|
19
|
+
| Flag | Question | Default |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| `--entrypoint <url>` | The server's framework entrypoint | `http://localhost:3000/api` |
|
|
22
|
+
| `--env-var <NAME>` / `--no-env-var` | Read it from this variable, or write it as a literal | `AVENTARA_API_URL` |
|
|
23
|
+
| `--generate-at <dir>` | Where the client goes | `./src/api` |
|
|
24
|
+
| `--package-manager <npm\|pnpm>` | | the lockfile's, else the launching one, else npm |
|
|
25
|
+
|
|
26
|
+
It writes `framework.client.mts`, the variable into `.env` (only with a variable), `"avclient:generate": "avclient
|
|
27
|
+
generate"` into the scripts and `@aventara/client` as an **exact** devDependency at its own version; runs the install
|
|
28
|
+
(`--skip-install` prints it instead); and generates the client right away (`--skip-generate` to skip; a server that does
|
|
29
|
+
not answer leaves the files and names `avclient generate`). Existing content that differs is listed and replaced only
|
|
30
|
+
when you confirm, or with `--yes`; where nobody can be asked, nothing is touched. The generated tree is meant to be
|
|
31
|
+
committed: regenerating it needs a running server.
|
|
32
|
+
|
|
33
|
+
## Generating a client
|
|
34
|
+
|
|
35
|
+
Install it as a dev dependency (or let `avclient init` do it), then add `framework.client.mts` to the directory you run
|
|
36
|
+
the generator from:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { defineClientConfig, env } from "@aventara/client";
|
|
40
|
+
|
|
41
|
+
export default defineClientConfig({
|
|
42
|
+
entrypoint: env("AVENTARA_API_URL"),
|
|
43
|
+
generateAt: "./src/api",
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
and run:
|
|
48
|
+
|
|
49
|
+
```text
|
|
50
|
+
npx avclient generate [--yes]
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Why `.mts`.** `npm init -y` writes `"type": "commonjs"`, and under it Node reads a `.ts` file as CommonJS — the
|
|
54
|
+
config's `import` would stop the run. A `.mts` file is an ES module in every project. A `framework.client.ts` written
|
|
55
|
+
before 0.1.0-pilot.1 is still read; with both files present the generator refuses, and `avclient init` moves the old one
|
|
56
|
+
to `framework.client.mts` (asking first if you edited it).
|
|
57
|
+
|
|
58
|
+
- **`entrypoint`** is the deployment's framework entrypoint: origin plus mount path, one absolute `http(s)` URL. It
|
|
59
|
+
may not carry credentials — the platform's `fetch` refuses such a URL, and the entrypoint ships inside the client.
|
|
60
|
+
- **`generateAt`** is a directory you may share with your own files. The generator owns exactly two entries in it —
|
|
61
|
+
`AvClient.ts` and `generated/` — and never reads, moves or removes anything else there.
|
|
62
|
+
- **The `.env` cascade** is read from the current directory before the config is evaluated, highest precedence first:
|
|
63
|
+
the process environment, `.env.<mode>.local`, `.env.<mode>`, `.env.local`, `.env`; `mode` is `NODE_ENV`, or
|
|
64
|
+
`development`. The config reads it through `env("NAME")`; `process.env` is never written.
|
|
65
|
+
- **Content the generator did not produce** in `AvClient.ts` or `generated/` is listed and you are asked before it is
|
|
66
|
+
overwritten or removed — including what a killed run left in the `generated/` it moved aside. `--yes` answers yes.
|
|
67
|
+
Where nobody can answer — stdin is not a terminal, as in CI — the run is refused and nothing is touched. A cancelled
|
|
68
|
+
or refused run still prints every warning it raised.
|
|
69
|
+
|
|
70
|
+
### What a run guarantees
|
|
71
|
+
|
|
72
|
+
- **A failed run leaves the previous output exactly as it was.** The whole tree is written to a staging directory inside
|
|
73
|
+
`generateAt`, validated there as one program, and only then moved into place. A run killed part-way is repaired by the
|
|
74
|
+
next run that writes, before it writes anything. A first run that fails removes the directories it created.
|
|
75
|
+
- **The validate step is a real type check** when the optional peer `typescript` (5.5 to 6) is installed, under a
|
|
76
|
+
consumer's strictest plausible settings and with nothing outside the tree resolvable. Without it — or under
|
|
77
|
+
TypeScript 7, which has no classic compiler API to check with — the check degrades to a parse with Node's own
|
|
78
|
+
TypeScript parser, and says so loudly in one warning; it is never skipped, and the client is still written. The peer
|
|
79
|
+
range has no upper bound, so a frontend on TypeScript 7 installs it; your own `tsc` checks the tree when it compiles.
|
|
80
|
+
- **Determinism.** Two runs over one ClientContract write the same bytes; deleting the client and regenerating it gives
|
|
81
|
+
the same bytes. Nothing volatile — no timestamp, generator version, host or path — is emitted.
|
|
82
|
+
- **Failure is a sentence.** A refusal is one line and exit code 1, with no stack; anything the run warned about before
|
|
83
|
+
it stopped is printed first. A stack means a defect in the generator.
|
|
84
|
+
|
|
85
|
+
## Calling the API
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import avClient, { AvClient, type User, type UserWhere } from "./api/AvClient";
|
|
89
|
+
|
|
90
|
+
const where: UserWhere = { email: { equals: "ada@example.com" } };
|
|
91
|
+
const users = await avClient.User.find.many({ where, select: ["id", "email", { posts: { select: ["$count"] } }] });
|
|
92
|
+
const one: User = await avClient.User.find.unique({ where: { id } });
|
|
93
|
+
const maybe = await avClient.User.find.first({ where }); // User | null — a miss is null
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- **`avClient`** is the default export: the client of the deployment the tree was generated from.
|
|
97
|
+
`new AvClient({ entrypoint, fetch })` makes another — `entrypoint` for a deployment serving **exactly the same**
|
|
98
|
+
ClientContract (tests, SSR), `fetch` for a custom fetch.
|
|
99
|
+
- **One property per Resource**, one per family under it, one per advertised variant: only what the ClientContract
|
|
100
|
+
advertises exists, in the type and at runtime. A Resource named like one of the client's own members (`tx`,
|
|
101
|
+
`transaction`, `then`, `constructor`, `Object.prototype`'s names) is reached as `<name>Model`, with one warning; its
|
|
102
|
+
wire name is unchanged.
|
|
103
|
+
- **A call resolves to the data**: the record, a list, a count; a first-style miss (`A1001`) is `null`. Every other
|
|
104
|
+
outcome throws — the `FrameworkError` subclass of its code (`NotFoundError` for a strict-unique miss,
|
|
105
|
+
`ContractMismatchError` for a stale client, …), or `TransportError` when no framework answer arrived.
|
|
106
|
+
- **Values are application values**: `bigint`, `Date`, `Uint8Array`, the client's `Decimal`, JSON values as they are.
|
|
107
|
+
Arguments are encoded and results revived for you; a value that cannot be read throws `TransportError`.
|
|
108
|
+
- **Per-call options** (`CallOptions`), never sent in the body: `signal` (an abort rejects with what `fetch` rejected
|
|
109
|
+
with), `headers` (the framework's own identity headers are refused), and `requestId`, sent as `Aventara-Request-Id`
|
|
110
|
+
and winning over that header in `headers`.
|
|
111
|
+
- **Named types** (`User`, `UserWhere`, `UserUniqueWhere`, `UserOrderBy`, `UserCreateData`, `UserUpdateData`,
|
|
112
|
+
`UserSelect`, `UserInclude`) are named exports, each only when the operation it reads is advertised. A name that would
|
|
113
|
+
clash walks the rename ladder (`User` beside a Resource `UserWhere` becomes `UserModel`), one warning each.
|
|
114
|
+
|
|
115
|
+
## Transactions
|
|
116
|
+
|
|
117
|
+
When the ClientContract advertises `interactive` transactions, the client has `avClient.tx` and `avClient.transaction`
|
|
118
|
+
(otherwise neither exists):
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const created = avClient.tx.User.create.one({ data: { email: "ada@example.com" }, select: ["id"] });
|
|
122
|
+
const profile = avClient.tx.Profile.create.one({ data: { bio: "…", userId: created.$ref("id") } });
|
|
123
|
+
const [user, row] = await avClient.transaction([created, profile]);
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`avClient.tx.…` builds a handle and sends nothing. `avClient.transaction([...])` sends the plan once and resolves one
|
|
127
|
+
result per handle, typed, in order; a failing step throws its error with `cause.operation` naming the step. `$ref` binds
|
|
128
|
+
to the handle's position in the list it runs with. Two mistakes are refused before anything is sent, as core refuses
|
|
129
|
+
them in process: one handle twice (`ValidationError`, `A2004`/`V1001`), and a `$ref` to a handle not in the list
|
|
130
|
+
(`ValidationError`, `A2007`/`V1010`). A handle is plain data: any client of the same generated tree may run it.
|
|
131
|
+
|
|
132
|
+
## Custom fetch and authentication
|
|
133
|
+
|
|
134
|
+
`fetch` is typed as **your platform's own `fetch`** when your `lib` declares one (DOM, `@types/node`), so the platform
|
|
135
|
+
`fetch` is accepted without a cast; with neither, a structural fetch type stands in. The client never adds headers of its
|
|
136
|
+
own beyond the protocol's; authentication is a custom fetch:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
const client = new AvClient({
|
|
140
|
+
fetch: (input, init) => fetch(input, { ...init, headers: { ...init?.headers, Authorization: `Bearer ${token}` } }),
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The platform `fetch` is read when a call is made, not when the client is imported, so importing the client never fails
|
|
145
|
+
where there is none — a call does, saying why. An entrypoint carrying credentials is refused when the config is
|
|
146
|
+
resolved.
|
|
147
|
+
|
|
148
|
+
## Up to date
|
|
149
|
+
|
|
150
|
+
A rerun asks the deployment conditionally (`If-None-Match`, the ClientContract hash of the output it
|
|
151
|
+
finds — only when that output is intact and its carrier re-verifies). On `304 Not Modified` it re-emits from that
|
|
152
|
+
stored ClientContract with **this** generator and **this** entrypoint: identical bytes print
|
|
153
|
+
`avclient: up to date: … nothing was written.` and exit 0; otherwise the output is replaced, and the line says
|
|
154
|
+
`the ClientContract is unchanged; the generator or the entrypoint changed`.
|
|
155
|
+
|
|
156
|
+
## What is emitted
|
|
157
|
+
|
|
158
|
+
Every file opens with a banner stating that it is generated and must not be edited, and with the Biome suppressions a
|
|
159
|
+
generated file needs — emitted, not configured, so the output stays correct in a repository whose formatter nobody here
|
|
160
|
+
chose. The `.d.ts` files are core's copied declarations under `generated/derivation/` and the named types in
|
|
161
|
+
`generated/types.d.ts`; the `.ts` sources carry their own types.
|
|
162
|
+
|
|
163
|
+
- `AvClient.ts` — the entry point you import: `avClient` (the default export), `AvClient`, `AvClientOptions`,
|
|
164
|
+
`CallOptions`, `Fetch`, `Operation`, the named Resource types, the enums, `Decimal`, `FrameworkError` and its
|
|
165
|
+
subclasses, `TransportError`, and the types `Cause`, `ValidationIssue`, `OperationCode` and `ValidationCode`.
|
|
166
|
+
- `generated/client.ts` — the typed client: the class `AvClient`, `AvClientOptions`, `CallOptions` and the ready
|
|
167
|
+
instance `avClient`; every argument and result type an instantiation of core's own derivation.
|
|
168
|
+
- `generated/contract.ts` — the ClientContract the client was generated against: its served canonical bytes, verbatim,
|
|
169
|
+
as the type `ClientContractShape` (no runtime byte).
|
|
170
|
+
- `generated/derivation/` — `@aventara/core`'s published declarations of the argument/result derivation and the
|
|
171
|
+
call grammar, copied byte for byte (bannered, source-map trailer dropped): the generated types are core's own.
|
|
172
|
+
- `generated/enums.ts` — each enum as a union type and a same-named `as const` object.
|
|
173
|
+
- `generated/metadata.ts` — the ClientContract hash and protocol version the client is bound to, and the entrypoint it
|
|
174
|
+
was generated from, its default deployment (an entrypoint carrying credentials is refused at resolution).
|
|
175
|
+
- `generated/runtime/codec.ts` — the nine scalars' wire codecs, and the request-body serializer.
|
|
176
|
+
- `generated/runtime/decimal.ts` — the framework's `Decimal`: a constructor, `toString` and `toJSON`.
|
|
177
|
+
- `generated/runtime/descriptor.ts` — what the runtime reads of the contract: the result fields it revives and the
|
|
178
|
+
relations it follows (the decode table), and the advertised operations.
|
|
179
|
+
- `generated/runtime/errors.ts` — `FrameworkError`, its subclasses by code class (`AuthError`, `ConflictError`,
|
|
180
|
+
`ContractMismatchError`, `InternalError`, `NotFoundError`, `ProtocolError`, `ValidationError`), `TransportError`, and
|
|
181
|
+
the code unions, emitted from core's code arrays rather than retyped.
|
|
182
|
+
- `generated/runtime/fingerprint.ts` — the operation fingerprint (RFC 8785 canonical JSON, SHA-256,
|
|
183
|
+
base64url), dependency-free and pinned to core's; emitted only when transactions are `interactive`.
|
|
184
|
+
- `generated/runtime/transaction.ts` — `avClient.tx`'s deferred handles and `avClient.transaction`'s plan assembly,
|
|
185
|
+
refusals and one POST to `/_transactions`; emitted only when transactions are `interactive`.
|
|
186
|
+
- `generated/runtime/transport.ts` — one POST per operation with the identity headers, and the response read by the
|
|
187
|
+
protocol's rule; arguments encoded by value, results revived by the decode table, the platform's own `fetch` type accepted.
|
|
188
|
+
Its `execute` primitive is module-private: no public file re-exports it; `AvClient`'s methods wrap it.
|
|
189
|
+
- `generated/types.d.ts` — the named Resource types, as a declaration file (`User`, `UserWhere`,
|
|
190
|
+
`UserUniqueWhere`, `UserOrderBy`, `UserCreateData`, `UserUpdateData`, `UserSelect`, `UserInclude`), each only when
|
|
191
|
+
its operation is advertised.
|
|
192
|
+
|
|
193
|
+
The tree is self-contained: every import in it names a file in it. That is gated twice in this package's tests — once
|
|
194
|
+
lexically, once by compiling the tree alone where nothing outside it can resolve — and each gate catches a planted
|
|
195
|
+
import of `@aventara/core` or `@aventara/client` on its own. Names a TypeScript declaration cannot carry as-is (a
|
|
196
|
+
reserved word, a name the runtime owns, an enum sharing a Resource's name) are renamed, with one warning each; the wire
|
|
197
|
+
name is kept.
|
|
198
|
+
|
|
199
|
+
## What a generated client is bound to
|
|
200
|
+
|
|
201
|
+
A generated client is bound to the **exact ClientContract hash and protocol version** it was generated from. Both are
|
|
202
|
+
in `generated/metadata.ts` and both are sent on every request (`Aventara-Protocol-Version`, `Aventara-Contract-Hash`).
|
|
203
|
+
A deployment whose ClientContract differs refuses the request with `A2005`, which the client throws as
|
|
204
|
+
`ContractMismatchError`.
|
|
205
|
+
|
|
206
|
+
The hash covers everything the deployment advertises, not only its schema: two deployments of one schema can advertise
|
|
207
|
+
different ClientContracts, and a client generated against one is refused by the other. Whatever the cause, the remedy
|
|
208
|
+
is the same — **generate against the deployment the client will call, and regenerate when it changes.**
|
|
209
|
+
|
|
210
|
+
That holds for an operation the deployment **no longer advertises**, too: the server checks the client's
|
|
211
|
+
identity before it routes, so a stale client calling a removed operation also hears `A2005` and throws
|
|
212
|
+
`ContractMismatchError`. A response that is not a framework envelope at all — a host's own 404 for a path it does not
|
|
213
|
+
mount — is a `TransportError`, which names no remedy: nothing in it says the client is stale.
|
|
214
|
+
|
|
215
|
+
The embedded entrypoint is a **default**: `new AvClient({ entrypoint })` may point a client at another deployment, and
|
|
216
|
+
doing so re-binds nothing — the ClientContract hash still decides whether that deployment accepts the client.
|
|
217
|
+
|
|
218
|
+
## Type-checking cost in a consumer
|
|
219
|
+
|
|
220
|
+
A consumer pays for the calls it type-checks, not for the schema — under `skipLibCheck: true` (the common default),
|
|
221
|
+
where the named types (`generated/types.d.ts`) and core's derivation are declaration files checked only where read:
|
|
222
|
+
about 33,000 instantiations for a 50-Resource schema and seven typed calls. Under `skipLibCheck: false` every named type
|
|
223
|
+
is resolved where it is declared, about 2,900 instantiations per Resource: about 204,000 for the same
|
|
224
|
+
program, the 500,000 line near 150 Resources.
|
|
225
|
+
|
|
226
|
+
## Before 1.0
|
|
227
|
+
|
|
228
|
+
- **The `Aventara` wire prefix** of the protocol's headers (`Aventara-Protocol-Version`, `Aventara-Contract-Hash`,
|
|
229
|
+
`Aventara-Request-Id`) may still change before the first stable release. It is one constant in the generated
|
|
230
|
+
`generated/runtime/transport.ts`; regenerating picks up a change.
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
PolyForm Shield 1.0.0 with an additional permission — free to use, including in commercial applications; you may not
|
|
235
|
+
use it to build a product that competes with Aventara itself. See `LICENSE` and `LICENSE-ADDITIONAL-PERMISSION.md`.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
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
|
+
if (!refuseUnsupportedNode("avclient", new URL("../package.json", import.meta.url))) {
|
|
14
|
+
void import("./cli.js").then((program) => program.runFromProcess());
|
|
15
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
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).
|
|
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.mts (or framework.client.ts) in the\n current directory, fetch <entrypoint>/_contract, and\n write AvClient.ts and generated/ into its generateAt\n directory.\n avclient init [options] Set up this frontend: write framework.client.mts, 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.mts 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\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.mts 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.mts (or framework.client.ts) in the current directory, fetch\n<entrypoint>/_contract, and write AvClient.ts and generated/ into its generateAt\ndirectory.\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.mts, 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.mts 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 (R4, Q16 rows 12–15). */
|
|
14
|
+
export type ClientInitCommand = {
|
|
15
|
+
readonly command: "init";
|
|
16
|
+
readonly given: Readonly<Partial<Record<"entrypoint" | "envVar" | "generateAt" | "packageManager", string>>>;
|
|
17
|
+
readonly noEnvVar: boolean;
|
|
18
|
+
readonly skipInstall: boolean;
|
|
19
|
+
readonly skipGenerate: boolean;
|
|
20
|
+
readonly yes: boolean;
|
|
21
|
+
};
|
|
22
|
+
/** What the command line asked for. */
|
|
23
|
+
export type CliCommand =
|
|
24
|
+
/** `--help`; with `topic`, `avclient <topic> --help` (pilot.1). */
|
|
25
|
+
{
|
|
26
|
+
readonly command: "help";
|
|
27
|
+
readonly topic?: "generate" | "init";
|
|
28
|
+
} | {
|
|
29
|
+
readonly command: "generate";
|
|
30
|
+
readonly yes: boolean;
|
|
31
|
+
} | ClientInitCommand;
|
|
32
|
+
/** The command line cannot be understood. A refusal: one sentence, exit 1. */
|
|
33
|
+
export declare class CliCommandError extends Error {
|
|
34
|
+
readonly name = "CliCommandError";
|
|
35
|
+
}
|
|
36
|
+
/** @throws CliCommandError when `argv` is not `generate [--yes|-y]`, `--help` or `-h`. */
|
|
37
|
+
export declare function parseCliCommand(argv: readonly string[]): CliCommand;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { CLIENT_CONFIG_FILE, LEGACY_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
|
+
export const USAGE = `avclient — generate a typed Aventara client from a deployed ClientContract.
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
avclient generate [--yes] Read ${CLIENT_CONFIG_FILE} (or ${LEGACY_CLIENT_CONFIG_FILE}) in the
|
|
13
|
+
current directory, fetch <entrypoint>/_contract, and
|
|
14
|
+
write AvClient.ts and generated/ into its generateAt
|
|
15
|
+
directory.
|
|
16
|
+
avclient init [options] Set up this frontend: write ${CLIENT_CONFIG_FILE}, the
|
|
17
|
+
.env entry, the avclient:generate script and the
|
|
18
|
+
@aventara/client devDependency; install; generate.
|
|
19
|
+
--entrypoint <url> The server's entrypoint [http://localhost:3000/api].
|
|
20
|
+
--env-var <NAME> Read it from this variable [AVENTARA_API_URL].
|
|
21
|
+
--no-env-var Write it into ${CLIENT_CONFIG_FILE} as a literal.
|
|
22
|
+
--generate-at <dir> Where the client goes [./src/api].
|
|
23
|
+
--package-manager <npm|pnpm> [the lockfile's, else the launching one, else npm]
|
|
24
|
+
--skip-install Print the install instead of running it.
|
|
25
|
+
--skip-generate Do not generate now.
|
|
26
|
+
-y, --yes Accept every default, and replace differing content.
|
|
27
|
+
avclient --help Print this and exit 0.
|
|
28
|
+
avclient <command> --help Print that command's usage and exit 0.
|
|
29
|
+
|
|
30
|
+
Options:
|
|
31
|
+
-y, --yes Overwrite or remove content in AvClient.ts and generated/ that the
|
|
32
|
+
generator did not produce, without asking. Without it, such content
|
|
33
|
+
is listed and you are asked; where nobody can answer (stdin is not a
|
|
34
|
+
terminal, as in CI), the run is refused and nothing is touched.
|
|
35
|
+
|
|
36
|
+
The generator owns AvClient.ts and generated/ in generateAt, and nothing else
|
|
37
|
+
there: your own files beside them are never read, moved or removed. Generated
|
|
38
|
+
files are replaced on every run; do not edit them.
|
|
39
|
+
|
|
40
|
+
Before ${CLIENT_CONFIG_FILE} is evaluated, the .env cascade is read from the
|
|
41
|
+
current directory, highest precedence first: the process environment,
|
|
42
|
+
.env.<mode>.local, .env.<mode>, .env.local, .env — where mode is NODE_ENV, or
|
|
43
|
+
"development".
|
|
44
|
+
`;
|
|
45
|
+
/** `avclient generate --help` (pilot.1): that command's usage alone. */
|
|
46
|
+
export const GENERATE_USAGE = `Usage: avclient generate [--yes]
|
|
47
|
+
|
|
48
|
+
Read ${CLIENT_CONFIG_FILE} (or ${LEGACY_CLIENT_CONFIG_FILE}) in the current directory, fetch
|
|
49
|
+
<entrypoint>/_contract, and write AvClient.ts and generated/ into its generateAt
|
|
50
|
+
directory.
|
|
51
|
+
|
|
52
|
+
Options:
|
|
53
|
+
-y, --yes Overwrite or remove content in AvClient.ts and generated/ that the
|
|
54
|
+
generator did not produce, without asking. Without it, such content
|
|
55
|
+
is listed and you are asked; where nobody can answer (stdin is not a
|
|
56
|
+
terminal, as in CI), the run is refused and nothing is touched.
|
|
57
|
+
|
|
58
|
+
The generator owns AvClient.ts and generated/ in generateAt, and nothing else
|
|
59
|
+
there: your own files beside them are never read, moved or removed.
|
|
60
|
+
|
|
61
|
+
Before the config is evaluated, the .env cascade is read from the current
|
|
62
|
+
directory, highest precedence first: the process environment, .env.<mode>.local,
|
|
63
|
+
.env.<mode>, .env.local, .env — where mode is NODE_ENV, or "development".
|
|
64
|
+
`;
|
|
65
|
+
/** `avclient init --help` (pilot.1): that command's usage alone. */
|
|
66
|
+
export const INIT_USAGE = `Usage: avclient init [options]
|
|
67
|
+
|
|
68
|
+
Set up this frontend: write ${CLIENT_CONFIG_FILE}, the .env entry, the avclient:generate
|
|
69
|
+
script and the @aventara/client devDependency; install; generate.
|
|
70
|
+
|
|
71
|
+
Options:
|
|
72
|
+
--entrypoint <url> The server's entrypoint [http://localhost:3000/api].
|
|
73
|
+
--env-var <NAME> Read it from this variable [AVENTARA_API_URL].
|
|
74
|
+
--no-env-var Write it into ${CLIENT_CONFIG_FILE} as a literal.
|
|
75
|
+
--generate-at <dir> Where the client goes [./src/api].
|
|
76
|
+
--package-manager <npm|pnpm> [the lockfile's, else the launching one, else npm]
|
|
77
|
+
--skip-install Print the install instead of running it.
|
|
78
|
+
--skip-generate Do not generate now.
|
|
79
|
+
-y, --yes Accept every default, and replace differing content.
|
|
80
|
+
|
|
81
|
+
On a terminal every unanswered question is asked; anywhere else, pass its flag
|
|
82
|
+
or --yes, or the run stops before writing anything.
|
|
83
|
+
`;
|
|
84
|
+
/** The command line cannot be understood. A refusal: one sentence, exit 1. */
|
|
85
|
+
export class CliCommandError extends Error {
|
|
86
|
+
name = "CliCommandError";
|
|
87
|
+
}
|
|
88
|
+
/** @throws CliCommandError when `argv` is not `generate [--yes|-y]`, `--help` or `-h`. */
|
|
89
|
+
export function parseCliCommand(argv) {
|
|
90
|
+
const [command, ...rest] = argv;
|
|
91
|
+
const help = "run `avclient --help` for usage.";
|
|
92
|
+
if (command === undefined) {
|
|
93
|
+
throw new CliCommandError(`a command is required; ${help}`);
|
|
94
|
+
}
|
|
95
|
+
if (command === "--help" || command === "-h") {
|
|
96
|
+
return { command: "help" };
|
|
97
|
+
}
|
|
98
|
+
if ((command === "init" || command === "generate") &&
|
|
99
|
+
(rest.includes("--help") || rest.includes("-h"))) {
|
|
100
|
+
return { command: "help", topic: command };
|
|
101
|
+
}
|
|
102
|
+
if (command === "init") {
|
|
103
|
+
return parseInit(rest, help);
|
|
104
|
+
}
|
|
105
|
+
if (command !== "generate") {
|
|
106
|
+
throw new CliCommandError(`unknown command ${JSON.stringify(command)}; ${help}`);
|
|
107
|
+
}
|
|
108
|
+
let yes = false;
|
|
109
|
+
for (const argument of rest) {
|
|
110
|
+
if ((argument === "--yes" || argument === "-y") && !yes) {
|
|
111
|
+
yes = true;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
throw new CliCommandError(`unexpected argument ${JSON.stringify(argument)}: generate takes only --yes, ` +
|
|
115
|
+
`everything else it needs is in ${CLIENT_CONFIG_FILE}; ${help}`);
|
|
116
|
+
}
|
|
117
|
+
return { command: "generate", yes };
|
|
118
|
+
}
|
|
119
|
+
const INIT_VALUES = {
|
|
120
|
+
"--entrypoint": "entrypoint",
|
|
121
|
+
"--env-var": "envVar",
|
|
122
|
+
"--generate-at": "generateAt",
|
|
123
|
+
"--package-manager": "packageManager",
|
|
124
|
+
};
|
|
125
|
+
const INIT_SWITCHES = {
|
|
126
|
+
"--no-env-var": "noEnvVar",
|
|
127
|
+
"--skip-install": "skipInstall",
|
|
128
|
+
"--skip-generate": "skipGenerate",
|
|
129
|
+
"--yes": "yes",
|
|
130
|
+
"-y": "yes",
|
|
131
|
+
};
|
|
132
|
+
function parseInit(argv, help) {
|
|
133
|
+
const given = {};
|
|
134
|
+
const switches = {
|
|
135
|
+
noEnvVar: false,
|
|
136
|
+
skipInstall: false,
|
|
137
|
+
skipGenerate: false,
|
|
138
|
+
yes: false,
|
|
139
|
+
};
|
|
140
|
+
const seen = new Set();
|
|
141
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
142
|
+
const argument = argv[index];
|
|
143
|
+
const equals = argument.indexOf("=");
|
|
144
|
+
const flag = equals === -1 ? argument : argument.slice(0, equals);
|
|
145
|
+
const inline = equals === -1 ? undefined : argument.slice(equals + 1);
|
|
146
|
+
const value = INIT_VALUES[flag];
|
|
147
|
+
const toggle = INIT_SWITCHES[flag];
|
|
148
|
+
if (value === undefined && toggle === undefined) {
|
|
149
|
+
throw new CliCommandError(`unexpected argument ${JSON.stringify(argument)} for init; ${help}`);
|
|
150
|
+
}
|
|
151
|
+
const canonical = toggle === "yes" ? "--yes" : flag;
|
|
152
|
+
if (seen.has(canonical)) {
|
|
153
|
+
throw new CliCommandError(`${canonical} was given twice; ${help}`);
|
|
154
|
+
}
|
|
155
|
+
seen.add(canonical);
|
|
156
|
+
if (toggle !== undefined) {
|
|
157
|
+
if (inline !== undefined) {
|
|
158
|
+
throw new CliCommandError(`${canonical} takes no value; ${help}`);
|
|
159
|
+
}
|
|
160
|
+
switches[toggle] = true;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const taken = inline ?? argv[index + 1];
|
|
164
|
+
if (taken === undefined ||
|
|
165
|
+
(inline === undefined && taken.startsWith("-"))) {
|
|
166
|
+
throw new CliCommandError(`${canonical} takes a value; ${help}`);
|
|
167
|
+
}
|
|
168
|
+
if (inline === undefined) {
|
|
169
|
+
index += 1;
|
|
170
|
+
}
|
|
171
|
+
given[value] = taken;
|
|
172
|
+
}
|
|
173
|
+
if (switches.noEnvVar && given.envVar !== undefined) {
|
|
174
|
+
throw new CliCommandError(`--env-var and --no-env-var contradict each other; ${help}`);
|
|
175
|
+
}
|
|
176
|
+
return { command: "init", given, ...switches };
|
|
177
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ProcessEnvInput } from "../config/env.cascade.js";
|
|
2
|
+
import type { CommandRunner } from "../init/command.runner.js";
|
|
3
|
+
/** What `runCli` reads and writes instead of the process globals. */
|
|
4
|
+
export interface CliIo {
|
|
5
|
+
readonly cwd: string;
|
|
6
|
+
readonly env: ProcessEnvInput;
|
|
7
|
+
readonly stdout: (text: string) => void;
|
|
8
|
+
readonly stderr: (text: string) => void;
|
|
9
|
+
/** Whether someone can answer a question: stdin is a terminal. */
|
|
10
|
+
readonly interactive: boolean;
|
|
11
|
+
/** Asks `question`; resolves true for yes. Called only when `interactive`. */
|
|
12
|
+
readonly confirm: (question: string) => Promise<boolean>;
|
|
13
|
+
/** Asks for a line; resolves with what was typed. Called only when `interactive` (`init`). */
|
|
14
|
+
readonly ask?: (prompt: string) => Promise<string>;
|
|
15
|
+
/** Runs a command with stdout piped (`init`'s install); a child process by default. */
|
|
16
|
+
readonly run?: CommandRunner;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* `avclient generate` — the generation, the generateAt rule's question, and the
|
|
20
|
+
* report. Shared by `init`, which generates once the project is set up.
|
|
21
|
+
*
|
|
22
|
+
* @throws whatever the generation raises; the caller renders it.
|
|
23
|
+
*/
|
|
24
|
+
export declare function runGenerate(io: CliIo, yes: boolean): Promise<number>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { generateClient } from "../generate.js";
|
|
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
|
+
export async function runGenerate(io, yes) {
|
|
10
|
+
const outcome = await generateClient({
|
|
11
|
+
directory: io.cwd,
|
|
12
|
+
processEnv: io.env,
|
|
13
|
+
overrideForeign: yes,
|
|
14
|
+
});
|
|
15
|
+
if (outcome.kind === "up-to-date") {
|
|
16
|
+
const report = renderUpToDate(outcome);
|
|
17
|
+
io.stderr(report.stderr);
|
|
18
|
+
io.stdout(report.stdout);
|
|
19
|
+
return 0;
|
|
20
|
+
}
|
|
21
|
+
let result;
|
|
22
|
+
if (outcome.kind === "foreign-content") {
|
|
23
|
+
if (!io.interactive) {
|
|
24
|
+
io.stderr(renderForeignContentCancellation(outcome, false));
|
|
25
|
+
return 1;
|
|
26
|
+
}
|
|
27
|
+
io.stderr(renderForeignContentWarning(outcome));
|
|
28
|
+
if (!(await io.confirm(FOREIGN_CONTENT_QUESTION))) {
|
|
29
|
+
io.stderr(renderForeignContentCancellation(outcome, true));
|
|
30
|
+
return 1;
|
|
31
|
+
}
|
|
32
|
+
result = await outcome.proceed();
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
result = outcome;
|
|
36
|
+
}
|
|
37
|
+
const report = renderGenerationSuccess(result);
|
|
38
|
+
io.stderr(report.stderr);
|
|
39
|
+
io.stdout(report.stdout);
|
|
40
|
+
return 0;
|
|
41
|
+
}
|