@aventara/client 0.0.0-stage → 0.1.0-pilot.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +91 -0
- package/LICENSE-ADDITIONAL-PERMISSION.md +9 -0
- package/README.md +268 -2
- package/dist/avclient.bin.d.ts +2 -0
- package/dist/avclient.bin.js +15 -0
- package/dist/cli/command.parser.d.ts +30 -0
- package/dist/cli/command.parser.js +132 -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 +54 -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 +71 -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 +33 -0
- package/dist/config/config.loader.js +80 -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 +6 -0
- package/dist/init/client-config.template.js +22 -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 +82 -0
- package/dist/init/client-init.planner.d.ts +26 -0
- package/dist/init/client-init.planner.js +88 -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 +76 -0
- package/dist/output/output.validator.js +254 -0
- package/dist/output/output.writer.d.ts +162 -0
- package/dist/output/output.writer.js +499 -0
- package/package.json +47 -3
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { type ClientContract, type OperationFamily } from "@aventara/core";
|
|
2
|
+
/**
|
|
3
|
+
* Name derivation and the rename ladder for the emitted tree.
|
|
4
|
+
*
|
|
5
|
+
* # Renamed, not refused (architect decision, 2026-10-04)
|
|
6
|
+
*
|
|
7
|
+
* S4 shipped "verbatim, or refused — never renamed". The architect overturned it:
|
|
8
|
+
* a Contract name the emitted TypeScript cannot declare as-is is RENAMED, and the
|
|
9
|
+
* rename is the TypeScript identifier's alone. The name the server uses — the
|
|
10
|
+
* wire name a Resource is addressed by — is kept beside it unchanged
|
|
11
|
+
* (`EmittedName.contractName`), so a later emitter that sends a request reads that,
|
|
12
|
+
* never the identifier. Every rename is one warning line on the result; generation
|
|
13
|
+
* does not print.
|
|
14
|
+
*
|
|
15
|
+
* A name is unusable as-is when it is not an identifier, is a reserved word, is a
|
|
16
|
+
* predefined type's name, is a name the generated runtime owns, or — an enum only
|
|
17
|
+
* — is also a Resource's name. Its ladder is tried in order and the first rung
|
|
18
|
+
* that is usable AND free of every other emitted name (verbatim or renamed) wins:
|
|
19
|
+
*
|
|
20
|
+
* - Resource `X`: `XModel` → `ResourceX` → `X_` → `_X` → `_X_` → refused;
|
|
21
|
+
* - enum `X`: `XEnum` → `EnumX` → `X_` → refused.
|
|
22
|
+
*
|
|
23
|
+
* Only a name whose whole ladder is unusable or taken is refused, in one sentence —
|
|
24
|
+
* and a name no rename can repair, which is refused without trying a rung: the
|
|
25
|
+
* empty name. Every rung of its ladder is a bare affix (`Model`, `Enum`, `_`) that
|
|
26
|
+
* names nothing on the server, so "renaming" it would invent an identifier rather
|
|
27
|
+
* than repair one (architect's rule, 2026-10-04: refuse when unrepairable).
|
|
28
|
+
*
|
|
29
|
+
* # Determinism
|
|
30
|
+
*
|
|
31
|
+
* The same Contract always yields the same names, whatever order its keys arrive
|
|
32
|
+
* in: every name usable as-is is claimed first (so a usable name is never displaced
|
|
33
|
+
* by another's rename), then Resources are renamed, then enums, each registry in
|
|
34
|
+
* UTF-16 code-unit order of its contract names. An enum sharing a Resource's name
|
|
35
|
+
* walks its ladder and the Resource keeps the name.
|
|
36
|
+
*
|
|
37
|
+
* # One namespace
|
|
38
|
+
*
|
|
39
|
+
* Enum and Resource identifiers share the generated client's root namespace with
|
|
40
|
+
* each other and with the runtime's own exports. §15.6 names a Resource's type by
|
|
41
|
+
* the Resource's name (`Spell`), so that namespace is claimed now, before the
|
|
42
|
+
* Resource types are emitted. Names are case-sensitive.
|
|
43
|
+
*
|
|
44
|
+
* # Derived names (Phase 12-rest Q10; the ladder rule, a plan ruling)
|
|
45
|
+
*
|
|
46
|
+
* §15.6 derives named types from a Resource (`SpellWhere`, `SpellOrderBy`, …;
|
|
47
|
+
* {@link RESOURCE_NAMED_TYPES}), each only when the operation it reads is
|
|
48
|
+
* advertised. A Resource's base name is taken at the first rung — its own name
|
|
49
|
+
* first, when usable — where the base AND every name derived from it are free of
|
|
50
|
+
* every other emitted name: `User` beside a Resource `UserWhere` walks to
|
|
51
|
+
* `UserModel`, one warning. Derived names a Resource claims are claimed for good,
|
|
52
|
+
* so a later Resource or enum never takes one. The generated modules that declare
|
|
53
|
+
* these names use no global type a Resource could shadow (`types.d.ts` imports two
|
|
54
|
+
* helpers, which are reserved).
|
|
55
|
+
*
|
|
56
|
+
* # Properties (Phase 12-rest Q12)
|
|
57
|
+
*
|
|
58
|
+
* A Resource is reached as a property of the client, named by its contract name.
|
|
59
|
+
* A contract name equal to one of the client's own members
|
|
60
|
+
* ({@link CLIENT_MEMBER_NAMES}) walks the Resource ladder for its PROPERTY only,
|
|
61
|
+
* one warning; the wire name, and the TypeScript type names, are unaffected.
|
|
62
|
+
*
|
|
63
|
+
* Reads registry KEYS and which operations are advertised. No capability member.
|
|
64
|
+
*/
|
|
65
|
+
/**
|
|
66
|
+
* The names the generated client declares in the root namespace: those §15.4
|
|
67
|
+
* and §13.4 give it, under the architect's names (Phase 12-rest Q7–Q9) — the
|
|
68
|
+
* `avClient` singleton, `AvClient`, `AvClientOptions`, `CallOptions`, `Fetch`,
|
|
69
|
+
* `Decimal`, `FrameworkError`, `NotFoundError`, `TransportError`,
|
|
70
|
+
* `OperationCode`/`ValidationCode` and `Operation<T>`, plus `Cause` and
|
|
71
|
+
* `ValidationIssue` (public by the architect's decision of 2026-10-04) — every
|
|
72
|
+
* `FrameworkError` subclass Q2 adds, read from the runtime emitter that declares
|
|
73
|
+
* them, and the two helpers `types.d.ts` imports to declare the named types
|
|
74
|
+
* (`ResourceArgument`, `ResourceRecord`). A Contract name equal to one of them
|
|
75
|
+
* walks its rename ladder. In UTF-16 code-unit order.
|
|
76
|
+
*/
|
|
77
|
+
export declare const RESERVED_EMITTED_NAMES: readonly string[];
|
|
78
|
+
/**
|
|
79
|
+
* One of §15.6's named Resource types (Q10 = a): the suffix it adds to the
|
|
80
|
+
* Resource's name, the operation it reads — it exists only when that operation is
|
|
81
|
+
* advertised — and the argument it names, or none for the default record.
|
|
82
|
+
*/
|
|
83
|
+
export interface ResourceNamedType {
|
|
84
|
+
readonly suffix: string;
|
|
85
|
+
readonly family: OperationFamily;
|
|
86
|
+
readonly variant: string;
|
|
87
|
+
/** The argument it names; absent for `X`, the default record. */
|
|
88
|
+
readonly argument?: "where" | "orderBy" | "data" | "select" | "include";
|
|
89
|
+
}
|
|
90
|
+
/** §15.6's set exactly (Q10 = a), in §15.6's order. */
|
|
91
|
+
export declare const RESOURCE_NAMED_TYPES: readonly ResourceNamedType[];
|
|
92
|
+
/** The named types a Resource with `operations` gets: those whose operation it advertises. */
|
|
93
|
+
export declare function namedTypesOf(operations: unknown): readonly ResourceNamedType[];
|
|
94
|
+
/**
|
|
95
|
+
* The client's own members, which a Resource property must not shadow (Q12):
|
|
96
|
+
* `tx` and `transaction` (reserved whether or not the contract advertises
|
|
97
|
+
* transactions, so a property never moves with `transactions`), `then` — a client
|
|
98
|
+
* with a `then` is a thenable, and `await` would call it — and every name
|
|
99
|
+
* `Object.prototype` carries in ES2022, `constructor` among them. Fixed here, not
|
|
100
|
+
* read from the running Node, so the output never moves with the generator's
|
|
101
|
+
* runtime. In UTF-16 code-unit order.
|
|
102
|
+
*/
|
|
103
|
+
export declare const CLIENT_MEMBER_NAMES: readonly string[];
|
|
104
|
+
/** One Contract name and the TypeScript identifier the tree declares it under. */
|
|
105
|
+
export interface EmittedName {
|
|
106
|
+
/** The name the server uses — what a request addresses. Never renamed. */
|
|
107
|
+
readonly contractName: string;
|
|
108
|
+
/** The identifier the emitted TypeScript declares; `contractName` unless renamed. */
|
|
109
|
+
readonly identifier: string;
|
|
110
|
+
}
|
|
111
|
+
/** A Resource's property on the client: its contract name unless a member owns it (Q12). */
|
|
112
|
+
export interface EmittedProperty {
|
|
113
|
+
/** The name the server uses. Never renamed. */
|
|
114
|
+
readonly contractName: string;
|
|
115
|
+
/** The client's property for it; `contractName` unless it is a client member's. */
|
|
116
|
+
readonly property: string;
|
|
117
|
+
}
|
|
118
|
+
/** The emitted names, and one warning per rename. */
|
|
119
|
+
export interface EmittedNames {
|
|
120
|
+
/** In UTF-16 code-unit order of `contractName` — RFC 8785's order. */
|
|
121
|
+
readonly enums: readonly EmittedName[];
|
|
122
|
+
/** In UTF-16 code-unit order of `contractName`. */
|
|
123
|
+
readonly resources: readonly EmittedName[];
|
|
124
|
+
/** Each Resource's client property, in UTF-16 code-unit order of `contractName`. */
|
|
125
|
+
readonly properties: readonly EmittedProperty[];
|
|
126
|
+
/**
|
|
127
|
+
* One line per renamed name, in UTF-16 code-unit order of the contract name, a
|
|
128
|
+
* Resource before an enum of the same name. Returned, never printed: the caller
|
|
129
|
+
* that owns the terminal decides where diagnostics go.
|
|
130
|
+
*/
|
|
131
|
+
readonly warnings: readonly string[];
|
|
132
|
+
}
|
|
133
|
+
/** Whether `text` is an IdentifierName — the rule above, for every emitter. */
|
|
134
|
+
export declare function isIdentifierName(text: string): boolean;
|
|
135
|
+
/**
|
|
136
|
+
* The emitted names of a Contract's enums and Resources, renamed where a name
|
|
137
|
+
* cannot be declared as-is.
|
|
138
|
+
*
|
|
139
|
+
* @throws GeneratedNameError naming every name no rung of its ladder can repair.
|
|
140
|
+
*/
|
|
141
|
+
export declare function deriveEmittedNames(contract: Pick<ClientContract, "enums" | "resources">): EmittedNames;
|
|
142
|
+
/** The refusal that stops generation over names no rename can repair. */
|
|
143
|
+
export declare class GeneratedNameError extends Error {
|
|
144
|
+
readonly name = "GeneratedNameError";
|
|
145
|
+
constructor(problems: readonly string[]);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* `value` as an object-literal key that makes it an OWN property: bare when it is
|
|
149
|
+
* an IdentifierName (reserved words included — a property name may be one), a
|
|
150
|
+
* string literal otherwise — except `__proto__`, which bare or quoted sets the
|
|
151
|
+
* prototype instead (measured on Node 24), so it is computed.
|
|
152
|
+
*/
|
|
153
|
+
export declare function ownPropertyKey(value: string): string;
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import { isOperationVariantAvailable, } from "@aventara/core";
|
|
2
|
+
import { FRAMEWORK_ERROR_CLASS_NAMES } from "./runtime.emitter.js";
|
|
3
|
+
/**
|
|
4
|
+
* Name derivation and the rename ladder for the emitted tree.
|
|
5
|
+
*
|
|
6
|
+
* # Renamed, not refused (architect decision, 2026-10-04)
|
|
7
|
+
*
|
|
8
|
+
* S4 shipped "verbatim, or refused — never renamed". The architect overturned it:
|
|
9
|
+
* a Contract name the emitted TypeScript cannot declare as-is is RENAMED, and the
|
|
10
|
+
* rename is the TypeScript identifier's alone. The name the server uses — the
|
|
11
|
+
* wire name a Resource is addressed by — is kept beside it unchanged
|
|
12
|
+
* (`EmittedName.contractName`), so a later emitter that sends a request reads that,
|
|
13
|
+
* never the identifier. Every rename is one warning line on the result; generation
|
|
14
|
+
* does not print.
|
|
15
|
+
*
|
|
16
|
+
* A name is unusable as-is when it is not an identifier, is a reserved word, is a
|
|
17
|
+
* predefined type's name, is a name the generated runtime owns, or — an enum only
|
|
18
|
+
* — is also a Resource's name. Its ladder is tried in order and the first rung
|
|
19
|
+
* that is usable AND free of every other emitted name (verbatim or renamed) wins:
|
|
20
|
+
*
|
|
21
|
+
* - Resource `X`: `XModel` → `ResourceX` → `X_` → `_X` → `_X_` → refused;
|
|
22
|
+
* - enum `X`: `XEnum` → `EnumX` → `X_` → refused.
|
|
23
|
+
*
|
|
24
|
+
* Only a name whose whole ladder is unusable or taken is refused, in one sentence —
|
|
25
|
+
* and a name no rename can repair, which is refused without trying a rung: the
|
|
26
|
+
* empty name. Every rung of its ladder is a bare affix (`Model`, `Enum`, `_`) that
|
|
27
|
+
* names nothing on the server, so "renaming" it would invent an identifier rather
|
|
28
|
+
* than repair one (architect's rule, 2026-10-04: refuse when unrepairable).
|
|
29
|
+
*
|
|
30
|
+
* # Determinism
|
|
31
|
+
*
|
|
32
|
+
* The same Contract always yields the same names, whatever order its keys arrive
|
|
33
|
+
* in: every name usable as-is is claimed first (so a usable name is never displaced
|
|
34
|
+
* by another's rename), then Resources are renamed, then enums, each registry in
|
|
35
|
+
* UTF-16 code-unit order of its contract names. An enum sharing a Resource's name
|
|
36
|
+
* walks its ladder and the Resource keeps the name.
|
|
37
|
+
*
|
|
38
|
+
* # One namespace
|
|
39
|
+
*
|
|
40
|
+
* Enum and Resource identifiers share the generated client's root namespace with
|
|
41
|
+
* each other and with the runtime's own exports. §15.6 names a Resource's type by
|
|
42
|
+
* the Resource's name (`Spell`), so that namespace is claimed now, before the
|
|
43
|
+
* Resource types are emitted. Names are case-sensitive.
|
|
44
|
+
*
|
|
45
|
+
* # Derived names (Phase 12-rest Q10; the ladder rule, a plan ruling)
|
|
46
|
+
*
|
|
47
|
+
* §15.6 derives named types from a Resource (`SpellWhere`, `SpellOrderBy`, …;
|
|
48
|
+
* {@link RESOURCE_NAMED_TYPES}), each only when the operation it reads is
|
|
49
|
+
* advertised. A Resource's base name is taken at the first rung — its own name
|
|
50
|
+
* first, when usable — where the base AND every name derived from it are free of
|
|
51
|
+
* every other emitted name: `User` beside a Resource `UserWhere` walks to
|
|
52
|
+
* `UserModel`, one warning. Derived names a Resource claims are claimed for good,
|
|
53
|
+
* so a later Resource or enum never takes one. The generated modules that declare
|
|
54
|
+
* these names use no global type a Resource could shadow (`types.d.ts` imports two
|
|
55
|
+
* helpers, which are reserved).
|
|
56
|
+
*
|
|
57
|
+
* # Properties (Phase 12-rest Q12)
|
|
58
|
+
*
|
|
59
|
+
* A Resource is reached as a property of the client, named by its contract name.
|
|
60
|
+
* A contract name equal to one of the client's own members
|
|
61
|
+
* ({@link CLIENT_MEMBER_NAMES}) walks the Resource ladder for its PROPERTY only,
|
|
62
|
+
* one warning; the wire name, and the TypeScript type names, are unaffected.
|
|
63
|
+
*
|
|
64
|
+
* Reads registry KEYS and which operations are advertised. No capability member.
|
|
65
|
+
*/
|
|
66
|
+
/**
|
|
67
|
+
* The names the generated client declares in the root namespace: those §15.4
|
|
68
|
+
* and §13.4 give it, under the architect's names (Phase 12-rest Q7–Q9) — the
|
|
69
|
+
* `avClient` singleton, `AvClient`, `AvClientOptions`, `CallOptions`, `Fetch`,
|
|
70
|
+
* `Decimal`, `FrameworkError`, `NotFoundError`, `TransportError`,
|
|
71
|
+
* `OperationCode`/`ValidationCode` and `Operation<T>`, plus `Cause` and
|
|
72
|
+
* `ValidationIssue` (public by the architect's decision of 2026-10-04) — every
|
|
73
|
+
* `FrameworkError` subclass Q2 adds, read from the runtime emitter that declares
|
|
74
|
+
* them, and the two helpers `types.d.ts` imports to declare the named types
|
|
75
|
+
* (`ResourceArgument`, `ResourceRecord`). A Contract name equal to one of them
|
|
76
|
+
* walks its rename ladder. In UTF-16 code-unit order.
|
|
77
|
+
*/
|
|
78
|
+
export const RESERVED_EMITTED_NAMES = [
|
|
79
|
+
...new Set([
|
|
80
|
+
"AvClient",
|
|
81
|
+
"AvClientOptions",
|
|
82
|
+
"CallOptions",
|
|
83
|
+
"Cause",
|
|
84
|
+
"Decimal",
|
|
85
|
+
"Fetch",
|
|
86
|
+
"FrameworkError",
|
|
87
|
+
"NotFoundError",
|
|
88
|
+
"Operation",
|
|
89
|
+
"OperationCode",
|
|
90
|
+
"ResourceArgument",
|
|
91
|
+
"ResourceRecord",
|
|
92
|
+
"TransportError",
|
|
93
|
+
"ValidationCode",
|
|
94
|
+
"ValidationIssue",
|
|
95
|
+
"avClient",
|
|
96
|
+
...FRAMEWORK_ERROR_CLASS_NAMES,
|
|
97
|
+
]),
|
|
98
|
+
].sort();
|
|
99
|
+
/** §15.6's set exactly (Q10 = a), in §15.6's order. */
|
|
100
|
+
export const RESOURCE_NAMED_TYPES = [
|
|
101
|
+
{ suffix: "", family: "find", variant: "unique" },
|
|
102
|
+
{ suffix: "Where", family: "find", variant: "many", argument: "where" },
|
|
103
|
+
{
|
|
104
|
+
suffix: "UniqueWhere",
|
|
105
|
+
family: "find",
|
|
106
|
+
variant: "unique",
|
|
107
|
+
argument: "where",
|
|
108
|
+
},
|
|
109
|
+
{ suffix: "OrderBy", family: "find", variant: "many", argument: "orderBy" },
|
|
110
|
+
{ suffix: "CreateData", family: "create", variant: "one", argument: "data" },
|
|
111
|
+
{
|
|
112
|
+
suffix: "UpdateData",
|
|
113
|
+
family: "update",
|
|
114
|
+
variant: "unique",
|
|
115
|
+
argument: "data",
|
|
116
|
+
},
|
|
117
|
+
{ suffix: "Select", family: "find", variant: "many", argument: "select" },
|
|
118
|
+
{ suffix: "Include", family: "find", variant: "many", argument: "include" },
|
|
119
|
+
];
|
|
120
|
+
/** The named types a Resource with `operations` gets: those whose operation it advertises. */
|
|
121
|
+
export function namedTypesOf(operations) {
|
|
122
|
+
const families = (operations ?? {});
|
|
123
|
+
return RESOURCE_NAMED_TYPES.filter((named) => isOperationVariantAvailable(families[named.family]?.[named.variant]));
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The client's own members, which a Resource property must not shadow (Q12):
|
|
127
|
+
* `tx` and `transaction` (reserved whether or not the contract advertises
|
|
128
|
+
* transactions, so a property never moves with `transactions`), `then` — a client
|
|
129
|
+
* with a `then` is a thenable, and `await` would call it — and every name
|
|
130
|
+
* `Object.prototype` carries in ES2022, `constructor` among them. Fixed here, not
|
|
131
|
+
* read from the running Node, so the output never moves with the generator's
|
|
132
|
+
* runtime. In UTF-16 code-unit order.
|
|
133
|
+
*/
|
|
134
|
+
export const CLIENT_MEMBER_NAMES = [
|
|
135
|
+
"__defineGetter__",
|
|
136
|
+
"__defineSetter__",
|
|
137
|
+
"__lookupGetter__",
|
|
138
|
+
"__lookupSetter__",
|
|
139
|
+
"__proto__",
|
|
140
|
+
"constructor",
|
|
141
|
+
"hasOwnProperty",
|
|
142
|
+
"isPrototypeOf",
|
|
143
|
+
"propertyIsEnumerable",
|
|
144
|
+
"then",
|
|
145
|
+
"toLocaleString",
|
|
146
|
+
"toString",
|
|
147
|
+
"transaction",
|
|
148
|
+
"tx",
|
|
149
|
+
"valueOf",
|
|
150
|
+
];
|
|
151
|
+
/**
|
|
152
|
+
* A TypeScript identifier: ID_Start, `$` or `_`, then ID_Continue, `$`, ZWNJ, ZWJ —
|
|
153
|
+
* ECMAScript's IdentifierName. ZWNJ and ZWJ are ID_Continue since Unicode 15.1, so
|
|
154
|
+
* on this repository's Node (Unicode 17.0, measured) their explicit escapes are
|
|
155
|
+
* redundant; they stay so the rule does not depend on the runtime's tables.
|
|
156
|
+
*/
|
|
157
|
+
const IDENTIFIER = /^[\p{ID_Start}$_][\p{ID_Continue}$\u200C\u200D]*$/u;
|
|
158
|
+
/** Whether `text` is an IdentifierName — the rule above, for every emitter. */
|
|
159
|
+
export function isIdentifierName(text) {
|
|
160
|
+
return IDENTIFIER.test(text);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Words `export type <name> = …` or `export const <name> = …` does not accept in a
|
|
164
|
+
* module, measured against this repository's `tsc` (2026-10-04, NodeNext strict):
|
|
165
|
+
* ECMAScript's reserved words, the strict-mode reserved words (a module is
|
|
166
|
+
* strict), `await` (a module may await at top level), `as`, which is not reserved
|
|
167
|
+
* but does not parse after `type`, and `arguments`/`eval`, which strict code
|
|
168
|
+
* cannot bind (TS1215) — measured again when enums gained their const.
|
|
169
|
+
*/
|
|
170
|
+
const RESERVED_WORDS = new Set([
|
|
171
|
+
"arguments",
|
|
172
|
+
"as",
|
|
173
|
+
"await",
|
|
174
|
+
"break",
|
|
175
|
+
"case",
|
|
176
|
+
"catch",
|
|
177
|
+
"class",
|
|
178
|
+
"const",
|
|
179
|
+
"continue",
|
|
180
|
+
"debugger",
|
|
181
|
+
"default",
|
|
182
|
+
"delete",
|
|
183
|
+
"do",
|
|
184
|
+
"else",
|
|
185
|
+
"enum",
|
|
186
|
+
"eval",
|
|
187
|
+
"export",
|
|
188
|
+
"extends",
|
|
189
|
+
"false",
|
|
190
|
+
"finally",
|
|
191
|
+
"for",
|
|
192
|
+
"function",
|
|
193
|
+
"if",
|
|
194
|
+
"implements",
|
|
195
|
+
"import",
|
|
196
|
+
"in",
|
|
197
|
+
"instanceof",
|
|
198
|
+
"interface",
|
|
199
|
+
"let",
|
|
200
|
+
"new",
|
|
201
|
+
"null",
|
|
202
|
+
"package",
|
|
203
|
+
"private",
|
|
204
|
+
"protected",
|
|
205
|
+
"public",
|
|
206
|
+
"return",
|
|
207
|
+
"static",
|
|
208
|
+
"super",
|
|
209
|
+
"switch",
|
|
210
|
+
"this",
|
|
211
|
+
"throw",
|
|
212
|
+
"true",
|
|
213
|
+
"try",
|
|
214
|
+
"typeof",
|
|
215
|
+
"var",
|
|
216
|
+
"void",
|
|
217
|
+
"while",
|
|
218
|
+
"with",
|
|
219
|
+
"yield",
|
|
220
|
+
]);
|
|
221
|
+
/** TS2457: a type alias cannot take a predefined type's name. */
|
|
222
|
+
const PREDEFINED_TYPES = new Set([
|
|
223
|
+
"any",
|
|
224
|
+
"bigint",
|
|
225
|
+
"boolean",
|
|
226
|
+
"never",
|
|
227
|
+
"number",
|
|
228
|
+
"object",
|
|
229
|
+
"string",
|
|
230
|
+
"symbol",
|
|
231
|
+
"undefined",
|
|
232
|
+
"unknown",
|
|
233
|
+
]);
|
|
234
|
+
const RESERVED_EMITTED = new Set(RESERVED_EMITTED_NAMES);
|
|
235
|
+
/** The rungs a name of `kind` tries, in order (architect decision, 2026-10-04). */
|
|
236
|
+
const LADDERS = {
|
|
237
|
+
Resource: (name) => [
|
|
238
|
+
`${name}Model`,
|
|
239
|
+
`Resource${name}`,
|
|
240
|
+
`${name}_`,
|
|
241
|
+
`_${name}`,
|
|
242
|
+
`_${name}_`,
|
|
243
|
+
],
|
|
244
|
+
enum: (name) => [`${name}Enum`, `Enum${name}`, `${name}_`],
|
|
245
|
+
};
|
|
246
|
+
/**
|
|
247
|
+
* The emitted names of a Contract's enums and Resources, renamed where a name
|
|
248
|
+
* cannot be declared as-is.
|
|
249
|
+
*
|
|
250
|
+
* @throws GeneratedNameError naming every name no rung of its ladder can repair.
|
|
251
|
+
*/
|
|
252
|
+
export function deriveEmittedNames(contract) {
|
|
253
|
+
const resourceNames = Object.keys(contract.resources).sort();
|
|
254
|
+
const enumNames = Object.keys(contract.enums).sort();
|
|
255
|
+
const resourceSet = new Set(resourceNames);
|
|
256
|
+
const reasonOf = (kind, name) => unusableBecause(name) ??
|
|
257
|
+
(kind === "enum" && resourceSet.has(name)
|
|
258
|
+
? "shares its name with a Resource"
|
|
259
|
+
: undefined);
|
|
260
|
+
// Every name usable as-is is claimed before any rename picks a rung, so a
|
|
261
|
+
// usable name is never displaced by another's rename.
|
|
262
|
+
const taken = new Set([
|
|
263
|
+
...resourceNames.filter((name) => reasonOf("Resource", name) === undefined),
|
|
264
|
+
...enumNames.filter((name) => reasonOf("enum", name) === undefined),
|
|
265
|
+
]);
|
|
266
|
+
// The names Resources derive (Q10), claimed as each Resource settles.
|
|
267
|
+
const derivedTaken = new Set();
|
|
268
|
+
/** `base`'s derived names for Resource `name`, or the first that is not free. */
|
|
269
|
+
const derivedNames = (name, base) => namedTypesOf(contract.resources[name]?.operations)
|
|
270
|
+
.filter((named) => named.suffix !== "")
|
|
271
|
+
.map((named) => `${base}${named.suffix}`);
|
|
272
|
+
const takenDerived = (name, base) => derivedNames(name, base).find((derived) => taken.has(derived) || derivedTaken.has(derived));
|
|
273
|
+
const warnings = [];
|
|
274
|
+
const refusals = [];
|
|
275
|
+
const resolve = (kind, name) => {
|
|
276
|
+
if (name === "") {
|
|
277
|
+
refusals.push({
|
|
278
|
+
name,
|
|
279
|
+
text: `${kind} "" is empty, which no rename repairs`,
|
|
280
|
+
});
|
|
281
|
+
return { contractName: name, identifier: name };
|
|
282
|
+
}
|
|
283
|
+
const settle = (identifier) => {
|
|
284
|
+
if (kind === "Resource") {
|
|
285
|
+
for (const derived of derivedNames(name, identifier)) {
|
|
286
|
+
derivedTaken.add(derived);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return { contractName: name, identifier };
|
|
290
|
+
};
|
|
291
|
+
const usable = reasonOf(kind, name);
|
|
292
|
+
const blockedBy = usable !== undefined || kind === "enum"
|
|
293
|
+
? undefined
|
|
294
|
+
: derivedTaken.has(name)
|
|
295
|
+
? name
|
|
296
|
+
: takenDerived(name, name);
|
|
297
|
+
const reason = usable ??
|
|
298
|
+
(blockedBy === undefined
|
|
299
|
+
? undefined
|
|
300
|
+
: blockedBy === name
|
|
301
|
+
? "is a name another Resource's type takes"
|
|
302
|
+
: `would derive ${JSON.stringify(blockedBy)}, which another emitted name takes`);
|
|
303
|
+
if (reason === undefined) {
|
|
304
|
+
return settle(name);
|
|
305
|
+
}
|
|
306
|
+
const rungs = LADDERS[kind](name);
|
|
307
|
+
const identifier = rungs.find((rung) => unusableBecause(rung) === undefined &&
|
|
308
|
+
!taken.has(rung) &&
|
|
309
|
+
!derivedTaken.has(rung) &&
|
|
310
|
+
(kind === "enum" || takenDerived(name, rung) === undefined));
|
|
311
|
+
const subject = `${kind} ${JSON.stringify(name)} ${reason}`;
|
|
312
|
+
if (identifier === undefined) {
|
|
313
|
+
refusals.push({
|
|
314
|
+
name,
|
|
315
|
+
text: `${subject}, and none of its renames (${rungs.map((rung) => JSON.stringify(rung)).join(", ")}) is free`,
|
|
316
|
+
});
|
|
317
|
+
return { contractName: name, identifier: name };
|
|
318
|
+
}
|
|
319
|
+
taken.add(identifier);
|
|
320
|
+
warnings.push({
|
|
321
|
+
name,
|
|
322
|
+
text: `${subject}, so it is emitted as ${JSON.stringify(identifier)}`,
|
|
323
|
+
});
|
|
324
|
+
return settle(identifier);
|
|
325
|
+
};
|
|
326
|
+
// Resources first: in a clash the Resource keeps the name, and a Resource's
|
|
327
|
+
// rename is settled before any enum's.
|
|
328
|
+
const resources = resourceNames.map((name) => resolve("Resource", name));
|
|
329
|
+
const enums = enumNames.map((name) => resolve("enum", name));
|
|
330
|
+
const properties = resourceProperties(resourceNames, warnings, refusals);
|
|
331
|
+
if (refusals.length > 0) {
|
|
332
|
+
throw new GeneratedNameError(byName(refusals));
|
|
333
|
+
}
|
|
334
|
+
return { enums, resources, properties, warnings: byName(warnings) };
|
|
335
|
+
}
|
|
336
|
+
const CLIENT_MEMBERS = new Set(CLIENT_MEMBER_NAMES);
|
|
337
|
+
/**
|
|
338
|
+
* Each Resource's property on the client (Q12): its contract name, unless that is
|
|
339
|
+
* one of the client's members — then the first rung of the Resource ladder that is
|
|
340
|
+
* neither a member nor another Resource's property. A property need not be an
|
|
341
|
+
* identifier (it is a key, quoted where needed), so only those two tests apply.
|
|
342
|
+
*/
|
|
343
|
+
function resourceProperties(resourceNames, warnings, refusals) {
|
|
344
|
+
const claimed = new Set(resourceNames.filter((name) => !CLIENT_MEMBERS.has(name)));
|
|
345
|
+
return resourceNames.map((name) => {
|
|
346
|
+
if (!CLIENT_MEMBERS.has(name)) {
|
|
347
|
+
return { contractName: name, property: name };
|
|
348
|
+
}
|
|
349
|
+
const rungs = LADDERS.Resource(name);
|
|
350
|
+
const property = rungs.find((rung) => !CLIENT_MEMBERS.has(rung) && !claimed.has(rung));
|
|
351
|
+
const subject = `Resource ${JSON.stringify(name)} is the name of the client's own member ${JSON.stringify(name)}`;
|
|
352
|
+
if (property === undefined) {
|
|
353
|
+
refusals.push({
|
|
354
|
+
name,
|
|
355
|
+
text: `${subject}, and none of its property renames (${rungs.map((rung) => JSON.stringify(rung)).join(", ")}) is free`,
|
|
356
|
+
});
|
|
357
|
+
return { contractName: name, property: name };
|
|
358
|
+
}
|
|
359
|
+
claimed.add(property);
|
|
360
|
+
warnings.push({
|
|
361
|
+
name,
|
|
362
|
+
text: `${subject}, so the client reaches it as ${JSON.stringify(property)}; its wire name is kept`,
|
|
363
|
+
});
|
|
364
|
+
return { contractName: name, property };
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
/** Why `name` cannot be declared as-is in any position, or `undefined`. */
|
|
368
|
+
function unusableBecause(name) {
|
|
369
|
+
return !isIdentifierName(name)
|
|
370
|
+
? "is not a TypeScript identifier"
|
|
371
|
+
: RESERVED_WORDS.has(name)
|
|
372
|
+
? "is a reserved word"
|
|
373
|
+
: PREDEFINED_TYPES.has(name)
|
|
374
|
+
? "is the name of a predefined TypeScript type"
|
|
375
|
+
: RESERVED_EMITTED.has(name)
|
|
376
|
+
? "is the generated client's own name for its runtime"
|
|
377
|
+
: undefined;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* The lines' texts by name in UTF-16 code units. The sort is stable and lines are
|
|
381
|
+
* collected Resources first, so a Resource precedes an enum of the same name.
|
|
382
|
+
*/
|
|
383
|
+
function byName(lines) {
|
|
384
|
+
return [...lines]
|
|
385
|
+
.sort((left, right) => compareCodeUnits(left.name, right.name))
|
|
386
|
+
.map((line) => line.text);
|
|
387
|
+
}
|
|
388
|
+
/** `Array.prototype.sort`'s default order, stated: UTF-16 code units. */
|
|
389
|
+
function compareCodeUnits(left, right) {
|
|
390
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
391
|
+
}
|
|
392
|
+
/** The refusal that stops generation over names no rename can repair. */
|
|
393
|
+
export class GeneratedNameError extends Error {
|
|
394
|
+
name = "GeneratedNameError";
|
|
395
|
+
constructor(problems) {
|
|
396
|
+
super(`the ClientContract's names cannot all be emitted: ${problems.join("; ")}. ` +
|
|
397
|
+
`Rename ${problems.length === 1 ? "it" : "them"} on the server, then generate again.`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* `value` as an object-literal key that makes it an OWN property: bare when it is
|
|
402
|
+
* an IdentifierName (reserved words included — a property name may be one), a
|
|
403
|
+
* string literal otherwise — except `__proto__`, which bare or quoted sets the
|
|
404
|
+
* prototype instead (measured on Node 24), so it is computed.
|
|
405
|
+
*/
|
|
406
|
+
export function ownPropertyKey(value) {
|
|
407
|
+
if (value === "__proto__") {
|
|
408
|
+
return '["__proto__"]';
|
|
409
|
+
}
|
|
410
|
+
return isIdentifierName(value) ? value : JSON.stringify(value);
|
|
411
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ClientContract } from "@aventara/core";
|
|
2
|
+
import type { EmittedModule } from "./emitted-tree.interface.js";
|
|
3
|
+
import { type EmittedNames } from "./name.deriver.js";
|
|
4
|
+
/**
|
|
5
|
+
* `generated/types.d.ts` — §15.6's named Resource types (Q10 = a): `X`, `XWhere`,
|
|
6
|
+
* `XUniqueWhere`, `XOrderBy`, `XCreateData`, `XUpdateData`, `XSelect`,
|
|
7
|
+
* `XInclude`, each only when the operation it reads is advertised, `X` being the
|
|
8
|
+
* Resource's emitted name (`name.deriver.ts`'s ladder, which keeps every derived
|
|
9
|
+
* name free).
|
|
10
|
+
*
|
|
11
|
+
* Each is an ALIAS over the copied derivation — `ResourceRecord` /
|
|
12
|
+
* `ResourceArgument`, declared in `client.ts` — never a walked-out literal type:
|
|
13
|
+
* the derivation stays one. A Resource is addressed by its contract name, a
|
|
14
|
+
* string literal type, so nothing here depends on how it was renamed.
|
|
15
|
+
*
|
|
16
|
+
* The module declares no name but these, and uses no global type a Resource
|
|
17
|
+
* named like one could shadow.
|
|
18
|
+
*
|
|
19
|
+
* # A declaration file (architect, 2026-10-05)
|
|
20
|
+
*
|
|
21
|
+
* TypeScript resolves a type alias where it is DECLARED, so a consumer pays for
|
|
22
|
+
* every Resource's named types whether it uses them or not — measured ≈2,800
|
|
23
|
+
* instantiations per Resource (plan §21). Emitted as `types.d.ts`, they cost
|
|
24
|
+
* nothing under `skipLibCheck: true` (the common default) until used, and the
|
|
25
|
+
* same as a `.ts` under `skipLibCheck: false`. It is live, not a dead
|
|
26
|
+
* declaration: nothing else declares these names, and no `types.ts` sits beside
|
|
27
|
+
* it to shadow it (`client-tree.emitter.spec.ts`). Only types are declared here,
|
|
28
|
+
* so nothing a bundler needs lives in it; `AvClient.ts` re-exports it by
|
|
29
|
+
* `export type *` (TypeScript 5.0, inside the 5.5 peer floor) — an `export *`
|
|
30
|
+
* would survive to the JavaScript and import a `types.js` that does not exist.
|
|
31
|
+
*/
|
|
32
|
+
export declare function emitNamedTypesModule(contract: ClientContract, names: EmittedNames): EmittedModule;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { namedTypesOf } from "./name.deriver.js";
|
|
2
|
+
/**
|
|
3
|
+
* `generated/types.d.ts` — §15.6's named Resource types (Q10 = a): `X`, `XWhere`,
|
|
4
|
+
* `XUniqueWhere`, `XOrderBy`, `XCreateData`, `XUpdateData`, `XSelect`,
|
|
5
|
+
* `XInclude`, each only when the operation it reads is advertised, `X` being the
|
|
6
|
+
* Resource's emitted name (`name.deriver.ts`'s ladder, which keeps every derived
|
|
7
|
+
* name free).
|
|
8
|
+
*
|
|
9
|
+
* Each is an ALIAS over the copied derivation — `ResourceRecord` /
|
|
10
|
+
* `ResourceArgument`, declared in `client.ts` — never a walked-out literal type:
|
|
11
|
+
* the derivation stays one. A Resource is addressed by its contract name, a
|
|
12
|
+
* string literal type, so nothing here depends on how it was renamed.
|
|
13
|
+
*
|
|
14
|
+
* The module declares no name but these, and uses no global type a Resource
|
|
15
|
+
* named like one could shadow.
|
|
16
|
+
*
|
|
17
|
+
* # A declaration file (architect, 2026-10-05)
|
|
18
|
+
*
|
|
19
|
+
* TypeScript resolves a type alias where it is DECLARED, so a consumer pays for
|
|
20
|
+
* every Resource's named types whether it uses them or not — measured ≈2,800
|
|
21
|
+
* instantiations per Resource (plan §21). Emitted as `types.d.ts`, they cost
|
|
22
|
+
* nothing under `skipLibCheck: true` (the common default) until used, and the
|
|
23
|
+
* same as a `.ts` under `skipLibCheck: false`. It is live, not a dead
|
|
24
|
+
* declaration: nothing else declares these names, and no `types.ts` sits beside
|
|
25
|
+
* it to shadow it (`client-tree.emitter.spec.ts`). Only types are declared here,
|
|
26
|
+
* so nothing a bundler needs lives in it; `AvClient.ts` re-exports it by
|
|
27
|
+
* `export type *` (TypeScript 5.0, inside the 5.5 peer floor) — an `export *`
|
|
28
|
+
* would survive to the JavaScript and import a `types.js` that does not exist.
|
|
29
|
+
*/
|
|
30
|
+
export function emitNamedTypesModule(contract, names) {
|
|
31
|
+
const declarations = names.resources.flatMap(({ contractName, identifier }) => {
|
|
32
|
+
const resource = JSON.stringify(contractName);
|
|
33
|
+
return namedTypesOf(contract.resources[contractName]?.operations).map((named) => {
|
|
34
|
+
const operation = `${named.family}.${named.variant}`;
|
|
35
|
+
return named.argument === undefined
|
|
36
|
+
? `/** \`${contractName}\`'s default record: \`${operation}\` with no projection. */\n` +
|
|
37
|
+
`export type ${identifier} = ResourceRecord<${resource}>;\n`
|
|
38
|
+
: `/** \`${contractName}\`'s \`${operation}\` \`${named.argument}\`. */\n` +
|
|
39
|
+
`export type ${identifier}${named.suffix} = ResourceArgument<${resource}, ${JSON.stringify(named.family)}, ${JSON.stringify(named.variant)}, ${JSON.stringify(named.argument)}>;\n`;
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
const uses = (helper) => declarations.some((declaration) => declaration.includes(` = ${helper}<`));
|
|
43
|
+
const imported = ["ResourceArgument", "ResourceRecord"].filter(uses);
|
|
44
|
+
return {
|
|
45
|
+
path: "types.d.ts",
|
|
46
|
+
source: declarations.length === 0
|
|
47
|
+
? "export {};\n"
|
|
48
|
+
: `import type { ${imported.join(", ")} } from "./client.js";\n\n${declarations.join("")}`,
|
|
49
|
+
};
|
|
50
|
+
}
|