@aventara/client 1.0.0-rc.0 → 1.0.0-rc.10
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/CHANGELOG.md +651 -0
- package/README.md +195 -141
- package/dist/cli/command.parser.d.ts +7 -6
- package/dist/cli/command.parser.js +13 -13
- package/dist/cli/generation-failure.renderer.d.ts +2 -1
- package/dist/cli/generation-failure.renderer.js +24 -24
- package/dist/cli/generation-success.renderer.d.ts +2 -1
- package/dist/cli/step.reporter.d.ts +2 -1
- package/dist/cli/terminal.prompter.d.ts +2 -1
- package/dist/config/client-config.interface.d.ts +24 -7
- package/dist/config/config.loader.d.ts +12 -7
- package/dist/config/config.loader.js +26 -8
- package/dist/config/config.resolver.d.ts +16 -9
- package/dist/config/config.resolver.js +40 -12
- package/dist/config/env.cascade.d.ts +4 -8
- package/dist/config/env.cascade.js +7 -7
- package/dist/config/module-style.resolver.d.ts +4 -7
- package/dist/config/module-style.resolver.js +4 -4
- package/dist/config/tsconfig.locator.d.ts +3 -2
- package/dist/config/tsconfig.locator.js +16 -4
- package/dist/contract/contract.acceptance.d.ts +4 -5
- package/dist/contract/contract.acceptance.js +5 -5
- package/dist/contract/contract.fetcher.d.ts +5 -4
- package/dist/contract/contract.fetcher.js +6 -6
- package/dist/contract/contract.loader.d.ts +9 -8
- package/dist/contract/contract.loader.js +2 -2
- package/dist/emit/access.emitter.d.ts +12 -0
- package/dist/emit/access.emitter.js +263 -0
- package/dist/emit/banner.emitter.d.ts +6 -1
- package/dist/emit/banner.emitter.js +12 -8
- package/dist/emit/client-surface.emitter.d.ts +2 -2
- package/dist/emit/client-surface.emitter.js +136 -41
- package/dist/emit/client-tree.emitter.d.ts +11 -4
- package/dist/emit/client-tree.emitter.js +51 -17
- package/dist/emit/contract-carrier.emitter.d.ts +3 -3
- package/dist/emit/derivation.emitter.d.ts +3 -3
- package/dist/emit/derivation.emitter.js +7 -3
- package/dist/emit/descriptor.emitter.d.ts +2 -2
- package/dist/emit/descriptor.emitter.js +1 -1
- package/dist/emit/emitted-tree.interface.d.ts +1 -1
- package/dist/emit/enum.emitter.d.ts +2 -2
- package/dist/emit/field-check.emitter.d.ts +22 -0
- package/dist/emit/field-check.emitter.js +592 -0
- package/dist/emit/json-shape.emitter.d.ts +2 -2
- package/dist/emit/module-specifier.scanner.d.ts +2 -1
- package/dist/emit/name.deriver.d.ts +8 -9
- package/dist/emit/name.deriver.js +18 -15
- package/dist/emit/named-type.emitter.d.ts +2 -2
- package/dist/emit/named-type.emitter.js +12 -2
- package/dist/emit/runtime.emitter.d.ts +6 -5
- package/dist/emit/runtime.emitter.js +215 -116
- package/dist/emit/scalar.codec.d.ts +1 -1
- package/dist/emit/scalar.codec.js +246 -29
- package/dist/emit/transaction.emitter.js +30 -14
- package/dist/generate.d.ts +5 -4
- package/dist/generate.js +3 -3
- package/dist/hydration/hydration-tag.format.d.ts +11 -0
- package/dist/hydration/hydration-tag.format.js +6 -0
- package/dist/index.d.ts +1 -1
- package/dist/init/client-init.errors.d.ts +4 -4
- package/dist/init/client-init.errors.js +4 -4
- package/dist/init/client-init.orchestrator.js +7 -7
- package/dist/init/client-init.planner.d.ts +3 -2
- package/dist/init/client-init.questions.d.ts +5 -16
- package/dist/init/client-init.questions.js +12 -12
- package/dist/init/client-project.inspector.d.ts +2 -2
- package/dist/init/client-project.inspector.js +3 -3
- package/dist/internal.d.ts +25 -0
- package/dist/internal.js +11 -0
- package/dist/output/output.validator.d.ts +3 -4
- package/dist/output/output.validator.js +2 -2
- package/dist/output/output.writer.d.ts +8 -7
- package/dist/output/output.writer.js +12 -12
- package/package.json +9 -5
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ACCESS_VERB_NAMES } from "@aventara/core/internal";
|
|
2
|
+
import { accessVerbShape, accessVerbsOf, } from "./access.emitter.js";
|
|
3
|
+
import { contractHasFieldChecks } from "./field-check.emitter.js";
|
|
4
|
+
import { jsonSchemaType, shapedContractType } from "./json-shape.emitter.js";
|
|
2
5
|
import { importSpecifier, } from "./module-style.interface.js";
|
|
3
6
|
import { ownPropertyKey } from "./name.deriver.js";
|
|
4
7
|
export function emitClientSurfaceModule(contract, names, style) {
|
|
@@ -11,11 +14,64 @@ export function emitClientSurfaceModule(contract, names, style) {
|
|
|
11
14
|
.join("")}R`;
|
|
12
15
|
const resourceKey = renamed.length === 0 ? "R in Resource" : `R in Resource as ${propertyOf}`;
|
|
13
16
|
const interactive = contract.transactions === "interactive";
|
|
17
|
+
const checked = contractHasFieldChecks(contract);
|
|
14
18
|
const shaped = shapedContractType(contract);
|
|
15
19
|
const C = shaped === undefined ? "ClientContractShape" : "ShapedContract";
|
|
16
20
|
const renamedEntries = renamed.length === 0
|
|
17
21
|
? ""
|
|
18
22
|
: ` ${renamed.map((property) => `${ownPropertyKey(property.contractName)}: ${JSON.stringify(property.property)}`).join(", ")} `;
|
|
23
|
+
const access = contract.access;
|
|
24
|
+
const verbs = accessVerbsOf(contract);
|
|
25
|
+
const argsType = (name) => {
|
|
26
|
+
const schema = access?.verbs?.[name]?.args;
|
|
27
|
+
return schema === undefined
|
|
28
|
+
? "undefined"
|
|
29
|
+
: jsonSchemaType(schema);
|
|
30
|
+
};
|
|
31
|
+
const argsMembers = ACCESS_VERB_NAMES.filter((name) => accessVerbShape(name).args)
|
|
32
|
+
.map((name) => `readonly ${name}: ${argsType(name)}`)
|
|
33
|
+
.join("; ");
|
|
34
|
+
const accessTypes = access === undefined
|
|
35
|
+
? ""
|
|
36
|
+
: "\n" +
|
|
37
|
+
"/** One question `$access.can` takes: an action on a Resource, or on the one row its unique `where` names. */\n" +
|
|
38
|
+
"type AccessQuestion = {\n" +
|
|
39
|
+
"\treadonly [R in Resource]: {\n" +
|
|
40
|
+
"\t\treadonly resource: R;\n" +
|
|
41
|
+
'\t\treadonly action: "read" | "create" | "update" | "delete";\n' +
|
|
42
|
+
`\t\treadonly where?: AvUniqueWhereInput<${C}, R, ClientScalarForms>;\n` +
|
|
43
|
+
"\t};\n" +
|
|
44
|
+
"}[Resource];\n" +
|
|
45
|
+
"\n" +
|
|
46
|
+
"/** `avClient.$access`: `can`, and the Aventara Access verbs this deployment offers. */\n" +
|
|
47
|
+
"type AccessSurface = Pick<\n" +
|
|
48
|
+
"\tAvAccessVerbs<\n" +
|
|
49
|
+
"\t\tAccessQuestion,\n" +
|
|
50
|
+
`\t\t${verbs.includes("me") ? "AvAccessMe" : "never"},\n` +
|
|
51
|
+
`\t\t{ ${argsMembers} },\n` +
|
|
52
|
+
"\t\tAvCallOptions\n" +
|
|
53
|
+
"\t>,\n" +
|
|
54
|
+
`\t${["can", ...verbs].map((name) => JSON.stringify(name)).join(" | ")}\n` +
|
|
55
|
+
">;\n";
|
|
56
|
+
const accessMember = (name) => {
|
|
57
|
+
const shape = accessVerbShape(name);
|
|
58
|
+
const quoted = JSON.stringify(name);
|
|
59
|
+
const parameters = shape.args
|
|
60
|
+
? "args: unknown, callOptions: AvCallOptions = {}"
|
|
61
|
+
: "callOptions: AvCallOptions = {}";
|
|
62
|
+
const args = shape.args ? "args" : "undefined";
|
|
63
|
+
return shape.resolves === "nothing"
|
|
64
|
+
? `\t\t\t\t${name}: async (${parameters}): Promise<void> => {\n\t\t\t\t\tawait callAccess(connect(), ${quoted}, ${args}, callOptions);\n\t\t\t\t},\n`
|
|
65
|
+
: `\t\t\t\t${name}: async (${parameters}) => callAccess(connect(), ${quoted}, ${args}, callOptions),\n`;
|
|
66
|
+
};
|
|
67
|
+
const accessRuntime = access === undefined
|
|
68
|
+
? ""
|
|
69
|
+
: '\t\tObject.defineProperty(this, "$access", {\n' +
|
|
70
|
+
"\t\t\tvalue: Object.freeze({\n" +
|
|
71
|
+
"\t\t\t\tcan: async (questions: readonly unknown[], callOptions: AvCallOptions = {}) => askCan(connect(), questions, callOptions),\n" +
|
|
72
|
+
verbs.map(accessMember).join("") +
|
|
73
|
+
"\t\t\t}),\n" +
|
|
74
|
+
"\t\t});\n";
|
|
19
75
|
const surface = (name, call) => `type ${name} = {\n` +
|
|
20
76
|
`\treadonly [${resourceKey}]: {\n` +
|
|
21
77
|
"\t\treadonly [F in keyof Grammar[R]]: {\n" +
|
|
@@ -29,38 +85,54 @@ export function emitClientSurfaceModule(contract, names, style) {
|
|
|
29
85
|
(shaped === undefined
|
|
30
86
|
? ""
|
|
31
87
|
: `import type { JsonFieldType } from ${from("./derivation/contracts/json-shape")};\n`) +
|
|
32
|
-
`import type {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
88
|
+
`import type { AvApplicationScalarForms } from ${from("./derivation/contracts/scalar-value-type")};\n` +
|
|
89
|
+
(access === undefined
|
|
90
|
+
? `import type { AvOperationArgumentsFor, AvResourceKey } from ${from("./derivation/operations/operation-arguments")};\n`
|
|
91
|
+
: `import type { AvOperationArgumentsFor, AvResourceKey, AvUniqueWhereInput } from ${from("./derivation/operations/operation-arguments")};\n`) +
|
|
92
|
+
`import type { ${interactive ? "AvDeferredOperationCall, " : ""}AvOperationCall, AvOperationGrammar } from ${from("./derivation/operations/operation-call")};\n` +
|
|
93
|
+
`import type { AvOperationFamily, AvOperationIdentity } from ${from("./derivation/operations/operation-identity")};\n` +
|
|
36
94
|
`import type { AdmittedOperationResult } from ${from("./derivation/operations/operation-result")};\n` +
|
|
37
95
|
(interactive
|
|
38
|
-
? `import type {
|
|
96
|
+
? `import type { AvOperation, AvTransactionResults } from ${from("./derivation/transactions/deferred-operation")};\n`
|
|
39
97
|
: "") +
|
|
98
|
+
(access === undefined
|
|
99
|
+
? ""
|
|
100
|
+
: `import type { AvAccessVerbs } from ${from("./derivation/policy/access-verbs")};\n`) +
|
|
40
101
|
`import { DEFAULT_ENTRYPOINT } from ${from("./metadata")};\n` +
|
|
41
|
-
|
|
102
|
+
(verbs.length === 0
|
|
103
|
+
? ""
|
|
104
|
+
: `import { callAccess } from ${from("./runtime/access")};\n`) +
|
|
105
|
+
`import type { AvDecimal } from ${from("./runtime/decimal")};\n` +
|
|
42
106
|
`import { ADVERTISED_OPERATIONS } from ${from("./runtime/descriptor")};\n` +
|
|
107
|
+
(checked
|
|
108
|
+
? `import { ${interactive ? "checkOperation, checkPlan" : "checkOperation"} } from ${from("./runtime/field-validators")};\n`
|
|
109
|
+
: "") +
|
|
43
110
|
(interactive
|
|
44
111
|
? `import { deferOperation, runTransaction } from ${from("./runtime/transaction")};\n`
|
|
45
112
|
: "") +
|
|
46
|
-
|
|
113
|
+
(access === undefined
|
|
114
|
+
? `import { type AvCallOptions, execute, type AvFetch, type TransportConnection } from ${from("./runtime/transport")};\n`
|
|
115
|
+
: `import { askCan, type AvCallOptions, execute, type AvFetch, type TransportConnection } from ${from("./runtime/transport")};\n`) +
|
|
116
|
+
(verbs.includes("me")
|
|
117
|
+
? `import type { AvAccessMe } from ${from("./types")};\n`
|
|
118
|
+
: "") +
|
|
47
119
|
"\n" +
|
|
48
|
-
`export type {
|
|
120
|
+
`export type { AvCallOptions } from ${from("./runtime/transport")};\n` +
|
|
49
121
|
"\n" +
|
|
50
122
|
"/**\n" +
|
|
51
|
-
" * The forms this client reads and writes: core's application forms
|
|
52
|
-
" * `bigint`, `Date`, `Uint8Array`
|
|
123
|
+
" * The forms this client reads and writes: core's application forms\n" +
|
|
124
|
+
" * (`bigint`, `Date`, `Uint8Array`) with this client's own `AvDecimal`.\n" +
|
|
53
125
|
" */\n" +
|
|
54
|
-
'export type ClientScalarForms = Omit<
|
|
55
|
-
"\treadonly decimal:
|
|
126
|
+
'export type ClientScalarForms = Omit<AvApplicationScalarForms, "decimal"> & {\n' +
|
|
127
|
+
"\treadonly decimal: AvDecimal;\n" +
|
|
56
128
|
"};\n" +
|
|
57
129
|
"\n" +
|
|
58
130
|
"/** How an `AvClient` reaches its deployment. */\n" +
|
|
59
131
|
"export interface AvClientOptions {\n" +
|
|
60
132
|
"\t/** Another deployment serving exactly the same ClientContract; the generated default otherwise. */\n" +
|
|
61
133
|
"\treadonly entrypoint?: string;\n" +
|
|
62
|
-
"\t/** A custom fetch
|
|
63
|
-
"\treadonly fetch?:
|
|
134
|
+
"\t/** A custom fetch, for authentication or tests; the platform's otherwise. */\n" +
|
|
135
|
+
"\treadonly fetch?: AvFetch;\n" +
|
|
64
136
|
"}\n" +
|
|
65
137
|
"\n" +
|
|
66
138
|
(shaped === undefined
|
|
@@ -68,42 +140,59 @@ export function emitClientSurfaceModule(contract, names, style) {
|
|
|
68
140
|
: "/** The ClientContract, with each shaped Json field's own type. */\n" +
|
|
69
141
|
`type ShapedContract = ${shaped};\n` +
|
|
70
142
|
"\n") +
|
|
71
|
-
`type Grammar =
|
|
72
|
-
`type Resource =
|
|
143
|
+
`type Grammar = AvOperationGrammar<${C}>;\n` +
|
|
144
|
+
`type Resource = AvResourceKey<${C}>;\n` +
|
|
73
145
|
"\n" +
|
|
74
146
|
"/** `avClient.<resource>.<family>.<variant>(args, options?)`: one call per advertised operation. */\n" +
|
|
75
|
-
surface("AvClientSurface", "
|
|
147
|
+
surface("AvClientSurface", "AvOperationCall<\n" +
|
|
76
148
|
`\t\t\t\t${C},\n` +
|
|
77
149
|
"\t\t\t\tR,\n" +
|
|
78
|
-
"\t\t\t\tGrammar[R][F][V] &
|
|
150
|
+
"\t\t\t\tGrammar[R][F][V] & AvOperationIdentity,\n" +
|
|
79
151
|
"\t\t\t\tClientScalarForms,\n" +
|
|
80
152
|
"\t\t\t\tClientScalarForms,\n" +
|
|
81
|
-
|
|
82
|
-
"\t\t\t\tCallOptions\n" +
|
|
153
|
+
"\t\t\t\tAvCallOptions\n" +
|
|
83
154
|
"\t\t\t>") +
|
|
84
155
|
(interactive
|
|
85
156
|
? "\n" +
|
|
86
157
|
"/** `avClient.tx.<resource>.<family>.<variant>(args)`: a deferred handle; no request. */\n" +
|
|
87
|
-
surface("AvTxSurface", "
|
|
158
|
+
surface("AvTxSurface", "AvDeferredOperationCall<\n" +
|
|
88
159
|
`\t\t\t\t${C},\n` +
|
|
89
160
|
"\t\t\t\tR,\n" +
|
|
90
|
-
"\t\t\t\tGrammar[R][F][V] &
|
|
161
|
+
"\t\t\t\tGrammar[R][F][V] & AvOperationIdentity,\n" +
|
|
91
162
|
"\t\t\t\tClientScalarForms,\n" +
|
|
92
163
|
"\t\t\t\tClientScalarForms\n" +
|
|
93
164
|
"\t\t\t>") +
|
|
94
165
|
"\n" +
|
|
95
166
|
"/** Sends the handles as one plan and resolves their results as a typed tuple. */\n" +
|
|
96
|
-
"type TransactionCall = <const Steps extends readonly
|
|
167
|
+
"type TransactionCall = <const Steps extends readonly AvOperation<unknown>[]>(\n" +
|
|
97
168
|
"\tsteps: Steps,\n" +
|
|
98
|
-
"\toptions?:
|
|
99
|
-
") => Promise<
|
|
169
|
+
"\toptions?: AvCallOptions,\n" +
|
|
170
|
+
") => Promise<AvTransactionResults<Steps>>;\n"
|
|
100
171
|
: "") +
|
|
172
|
+
accessTypes +
|
|
101
173
|
"\n" +
|
|
102
174
|
"/** The generated client: one property per Resource this deployment's ClientContract advertises. */\n" +
|
|
103
175
|
"export interface AvClient extends AvClientSurface {}\n" +
|
|
104
176
|
"\n" +
|
|
105
|
-
|
|
177
|
+
(checked
|
|
178
|
+
? "/**\n" +
|
|
179
|
+
" * The generated client.\n" +
|
|
180
|
+
" *\n" +
|
|
181
|
+
" * A call runs the ClientContract's field validators before it is sent: one\n" +
|
|
182
|
+
" * whose values break a validator, or a check of a shaped Json field, is\n" +
|
|
183
|
+
" * refused with the AvValidationError the server would answer (A2004, with\n" +
|
|
184
|
+
" * the same issues), and nothing is sent. The client checks each value as\n" +
|
|
185
|
+
" * written; the server checks it again after its field pipes, so a field pipe\n" +
|
|
186
|
+
" * that normalizes a value must not turn one the client accepted into one the\n" +
|
|
187
|
+
" * server refuses. A $ref value in a transaction is the server's to check.\n" +
|
|
188
|
+
" */\n"
|
|
189
|
+
: "/** The generated client. */\n") +
|
|
106
190
|
"export class AvClient {\n" +
|
|
191
|
+
(access === undefined
|
|
192
|
+
? ""
|
|
193
|
+
: "\t/** The policy's answers for the UI (`can`), and the Aventara Access verbs this deployment offers. */\n" +
|
|
194
|
+
"\tdeclare readonly $access: AccessSurface;\n" +
|
|
195
|
+
(interactive ? "" : "\n")) +
|
|
107
196
|
(interactive
|
|
108
197
|
? "\t/** Deferred operations, for `transaction`. */\n" +
|
|
109
198
|
"\tdeclare readonly tx: AvTxSurface;\n" +
|
|
@@ -117,8 +206,11 @@ export function emitClientSurfaceModule(contract, names, style) {
|
|
|
117
206
|
"\t\tconst connect = (): TransportConnection => ({ entrypoint, fetch: platformFetch(given) });\n" +
|
|
118
207
|
"\t\tfor (const [resource, families] of operationTree(\n" +
|
|
119
208
|
"\t\t\t(resource, family, variant): OperationMethod =>\n" +
|
|
120
|
-
|
|
121
|
-
|
|
209
|
+
(checked
|
|
210
|
+
? "\t\t\t\tasync (args = {}, callOptions = {}) =>\n" +
|
|
211
|
+
"\t\t\t\t\texecute(connect(), resource, family, variant, args, callOptions, checkOperation),\n"
|
|
212
|
+
: "\t\t\t\tasync (args = {}, callOptions = {}) =>\n" +
|
|
213
|
+
"\t\t\t\t\texecute(connect(), resource, family, variant, args, callOptions),\n") +
|
|
122
214
|
"\t\t)) {\n" +
|
|
123
215
|
"\t\t\tObject.defineProperty(this, propertyOf(resource), { value: families, enumerable: true });\n" +
|
|
124
216
|
"\t\t}\n" +
|
|
@@ -133,16 +225,19 @@ export function emitClientSurfaceModule(contract, names, style) {
|
|
|
133
225
|
"\t\t}\n" +
|
|
134
226
|
'\t\tObject.defineProperty(this, "tx", { value: Object.freeze(deferred) });\n' +
|
|
135
227
|
'\t\tObject.defineProperty(this, "transaction", {\n' +
|
|
136
|
-
"\t\t\tvalue: async (steps: unknown, callOptions:
|
|
137
|
-
|
|
228
|
+
"\t\t\tvalue: async (steps: unknown, callOptions: AvCallOptions = {}) =>\n" +
|
|
229
|
+
(checked
|
|
230
|
+
? "\t\t\t\trunTransaction(connect(), steps, callOptions, checkPlan),\n"
|
|
231
|
+
: "\t\t\t\trunTransaction(connect(), steps, callOptions),\n") +
|
|
138
232
|
"\t\t});\n"
|
|
139
233
|
: "") +
|
|
234
|
+
accessRuntime +
|
|
140
235
|
"\t}\n" +
|
|
141
236
|
"}\n" +
|
|
142
237
|
"\n" +
|
|
143
238
|
"/**\n" +
|
|
144
239
|
" * One frozen object per Resource, one per family under it, one member per\n" +
|
|
145
|
-
" * advertised variant
|
|
240
|
+
" * advertised variant, by wire name; the caller places each Resource.\n" +
|
|
146
241
|
" */\n" +
|
|
147
242
|
"function operationTree<M>(member: (resource: string, family: string, variant: string) => M): Map<string, object> {\n" +
|
|
148
243
|
"\tconst resources = new Map<string, Map<string, Record<string, M>>>();\n" +
|
|
@@ -162,7 +257,7 @@ export function emitClientSurfaceModule(contract, names, style) {
|
|
|
162
257
|
"}\n" +
|
|
163
258
|
"\n" +
|
|
164
259
|
"/** One advertised operation, as the runtime calls it: the types are the surface's. */\n" +
|
|
165
|
-
"type OperationMethod = (args?: Readonly<Record<string, unknown>>, options?:
|
|
260
|
+
"type OperationMethod = (args?: Readonly<Record<string, unknown>>, options?: AvCallOptions) => Promise<unknown>;\n" +
|
|
166
261
|
"\n" +
|
|
167
262
|
"/** The Resources reached by a property other than their wire name. */\n" +
|
|
168
263
|
`const RENAMED_PROPERTIES: Readonly<Record<string, string>> = {${renamedEntries}};\n` +
|
|
@@ -174,11 +269,11 @@ export function emitClientSurfaceModule(contract, names, style) {
|
|
|
174
269
|
"\n" +
|
|
175
270
|
"/**\n" +
|
|
176
271
|
" * The fetch a call uses: the one the client was given, else the\n" +
|
|
177
|
-
" * platform's, read when the call is made
|
|
272
|
+
" * platform's, read when the call is made. Importing the client never\n" +
|
|
178
273
|
" * fails where there is no fetch, and a call does, saying why.\n" +
|
|
179
274
|
" */\n" +
|
|
180
|
-
"function platformFetch(given:
|
|
181
|
-
"\tconst found = given ?? (globalThis as { readonly fetch?:
|
|
275
|
+
"function platformFetch(given: AvFetch | undefined): AvFetch {\n" +
|
|
276
|
+
"\tconst found = given ?? (globalThis as { readonly fetch?: AvFetch }).fetch;\n" +
|
|
182
277
|
'\tif (typeof found !== "function") {\n' +
|
|
183
278
|
'\t\tthrow new TypeError("No fetch is available here; pass one: new AvClient({ fetch }).");\n' +
|
|
184
279
|
"\t}\n" +
|
|
@@ -189,12 +284,12 @@ export function emitClientSurfaceModule(contract, names, style) {
|
|
|
189
284
|
"const avClient: AvClient = new AvClient();\n" +
|
|
190
285
|
"export default avClient;\n" +
|
|
191
286
|
"\n" +
|
|
192
|
-
"type Identity<F extends
|
|
193
|
-
"\
|
|
287
|
+
"type Identity<F extends AvOperationFamily, V extends string> = Extract<\n" +
|
|
288
|
+
"\tAvOperationIdentity,\n" +
|
|
194
289
|
"\t{ readonly family: F; readonly variant: V }\n" +
|
|
195
290
|
">;\n" +
|
|
196
291
|
"\n" +
|
|
197
|
-
"/** A Resource's default record
|
|
292
|
+
"/** A Resource's default record: `find.unique` with no projection. */\n" +
|
|
198
293
|
"export type ResourceRecord<R extends Resource> = AdmittedOperationResult<\n" +
|
|
199
294
|
`\t${C},\n` +
|
|
200
295
|
"\tR,\n" +
|
|
@@ -206,11 +301,11 @@ export function emitClientSurfaceModule(contract, names, style) {
|
|
|
206
301
|
"/** One argument of one of a Resource's operations, present. */\n" +
|
|
207
302
|
"export type ResourceArgument<\n" +
|
|
208
303
|
"\tR extends Resource,\n" +
|
|
209
|
-
"\tF extends
|
|
304
|
+
"\tF extends AvOperationFamily,\n" +
|
|
210
305
|
"\tV extends string,\n" +
|
|
211
306
|
"\tK extends string,\n" +
|
|
212
307
|
"> = ArgumentOf<\n" +
|
|
213
|
-
`\
|
|
308
|
+
`\tAvOperationArgumentsFor<${C}, R, Identity<F, V>, ClientScalarForms>,\n` +
|
|
214
309
|
"\tK\n" +
|
|
215
310
|
">;\n" +
|
|
216
311
|
"\n" +
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { AvContractClient } from "@aventara/core";
|
|
2
|
+
import type { EmittedEntrypoint } from "../config/client-config.interface.js";
|
|
3
|
+
import { type RegenerateCommand } from "./banner.emitter.js";
|
|
3
4
|
import { type ClientEmission } from "./emitted-tree.interface.js";
|
|
4
5
|
import { type ClientModuleStyle } from "./module-style.interface.js";
|
|
5
6
|
/**
|
|
6
|
-
* @throws
|
|
7
|
+
* @throws AvGeneratedNameError when a name cannot be emitted, even renamed.
|
|
8
|
+
* @throws AvClientConfigError when the ClientContract's Access URL is on an
|
|
9
|
+
* origin other than the entrypoint's that `accessOrigins` does not list.
|
|
7
10
|
*/
|
|
8
|
-
export declare function emitClientTree(contract:
|
|
11
|
+
export declare function emitClientTree(contract: AvContractClient, entrypoint: EmittedEntrypoint, style: ClientModuleStyle, regenerate?: RegenerateCommand, accessOrigins?: readonly string[]): ClientEmission;
|
|
12
|
+
/** How an entry re-exports `AvClient`: the class itself, or its type alone. */
|
|
13
|
+
export type ClientClassExport = "value" | "type";
|
|
14
|
+
/** The entry's re-exports, its default aside, as source: one statement per module. */
|
|
15
|
+
export declare function clientEntryReExports(style: ClientModuleStyle, clientClass?: ClientClassExport, accessVerbs?: boolean): string;
|
|
@@ -1,20 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AvClientConfigError } from "../config/config.resolver.js";
|
|
2
|
+
import { accessVerbsOf, emitAccessModules } from "./access.emitter.js";
|
|
3
|
+
import { AVCLIENT_REGENERATE, withGeneratedBanner, } from "./banner.emitter.js";
|
|
2
4
|
import { emitClientSurfaceModule } from "./client-surface.emitter.js";
|
|
3
5
|
import { emitContractCarrierModule } from "./contract-carrier.emitter.js";
|
|
4
|
-
import { emitDerivationModules } from "./derivation.emitter.js";
|
|
6
|
+
import { ACCESS_DERIVATION_ROOTS, DERIVATION_ROOTS, emitDerivationModules, } from "./derivation.emitter.js";
|
|
5
7
|
import { emitDescriptorModule } from "./descriptor.emitter.js";
|
|
6
8
|
import { CLIENT_ENTRY_FILE, GENERATED_DIRECTORY, } from "./emitted-tree.interface.js";
|
|
7
9
|
import { emitEnumsModule } from "./enum.emitter.js";
|
|
10
|
+
import { emitFieldCheckModules } from "./field-check.emitter.js";
|
|
8
11
|
import { moduleSpecifierWriter, } from "./module-style.interface.js";
|
|
9
12
|
import { deriveEmittedNames } from "./name.deriver.js";
|
|
10
13
|
import { emitNamedTypesModule } from "./named-type.emitter.js";
|
|
11
14
|
import { emitDecimalModule, emitErrorsModule, emitMetadataModule, emitTransportModule, errorsModuleExports, } from "./runtime.emitter.js";
|
|
12
15
|
import { emitScalarCodecModule } from "./scalar.codec.js";
|
|
13
16
|
import { emitTransactionModules } from "./transaction.emitter.js";
|
|
14
|
-
export function emitClientTree(contract, entrypoint, style) {
|
|
17
|
+
export function emitClientTree(contract, entrypoint, style, regenerate = AVCLIENT_REGENERATE, accessOrigins = []) {
|
|
18
|
+
refuseForeignAccessOrigin(contract, entrypoint, accessOrigins);
|
|
15
19
|
const names = deriveEmittedNames(contract);
|
|
20
|
+
const derivation = emitDerivationModules(undefined, contract.access === undefined
|
|
21
|
+
? DERIVATION_ROOTS
|
|
22
|
+
: [...DERIVATION_ROOTS, ...ACCESS_DERIVATION_ROOTS]);
|
|
16
23
|
const generated = [
|
|
17
|
-
...
|
|
24
|
+
...derivation,
|
|
18
25
|
emitClientSurfaceModule(contract, names, style),
|
|
19
26
|
emitContractCarrierModule(contract),
|
|
20
27
|
emitDescriptorModule(contract),
|
|
@@ -22,15 +29,20 @@ export function emitClientTree(contract, entrypoint, style) {
|
|
|
22
29
|
emitMetadataModule(contract.protocol, entrypoint),
|
|
23
30
|
emitScalarCodecModule(style),
|
|
24
31
|
emitDecimalModule(),
|
|
25
|
-
emitErrorsModule(),
|
|
26
|
-
emitTransportModule(style),
|
|
32
|
+
emitErrorsModule(regenerate),
|
|
33
|
+
emitTransportModule(style, contract.access !== undefined),
|
|
34
|
+
...emitAccessModules(contract, style),
|
|
27
35
|
emitNamedTypesModule(contract, names, style),
|
|
28
36
|
...(contract.transactions === "interactive"
|
|
29
37
|
? emitTransactionModules(style)
|
|
30
38
|
: []),
|
|
39
|
+
...emitFieldCheckModules(contract, style, new Set(derivation.map((module) => module.path))),
|
|
31
40
|
];
|
|
32
41
|
const files = [
|
|
33
|
-
{
|
|
42
|
+
{
|
|
43
|
+
path: CLIENT_ENTRY_FILE,
|
|
44
|
+
source: clientEntrySource(style, accessVerbsOf(contract).length > 0),
|
|
45
|
+
},
|
|
34
46
|
...generated.map((module) => ({
|
|
35
47
|
path: `${GENERATED_DIRECTORY}/${module.path}`,
|
|
36
48
|
source: module.source,
|
|
@@ -41,21 +53,43 @@ export function emitClientTree(contract, entrypoint, style) {
|
|
|
41
53
|
.sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0)
|
|
42
54
|
.map((file) => ({
|
|
43
55
|
path: file.path,
|
|
44
|
-
bytes: encoder.encode(withGeneratedBanner(file.source)),
|
|
56
|
+
bytes: encoder.encode(withGeneratedBanner(file.source, regenerate)),
|
|
45
57
|
}));
|
|
46
58
|
return { tree, style, warnings: names.warnings };
|
|
47
59
|
}
|
|
48
|
-
function clientEntrySource(style) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
60
|
+
function clientEntrySource(style, accessVerbs) {
|
|
61
|
+
return (`export { default } from ${generatedModuleSpecifier(style, "client.ts")};\n` +
|
|
62
|
+
clientEntryReExports(style, "value", accessVerbs));
|
|
63
|
+
}
|
|
64
|
+
function generatedModuleSpecifier(style, module) {
|
|
65
|
+
return moduleSpecifierWriter(style)(`./${GENERATED_DIRECTORY}/${module.replace(/\.d\.ts$|\.ts$/, "")}`);
|
|
66
|
+
}
|
|
67
|
+
export function clientEntryReExports(style, clientClass = "value", accessVerbs = false) {
|
|
68
|
+
const from = (module) => generatedModuleSpecifier(style, module);
|
|
69
|
+
return ((clientClass === "value"
|
|
70
|
+
? `export {\n\tAvClient,\n\ttype AvClientOptions,\n\ttype AvCallOptions,\n} from ${from("client.ts")};\n`
|
|
71
|
+
: `export type { AvClient, AvClientOptions, AvCallOptions } from ${from("client.ts")};\n`) +
|
|
72
|
+
`export type { AvFetch } from ${from("runtime/transport.ts")};\n` +
|
|
73
|
+
`export type { AvOperation } from ${from("derivation/transactions/deferred-operation.d.ts")};\n` +
|
|
55
74
|
`export type * from ${from("types.d.ts")};\n` +
|
|
56
75
|
`export * from ${from("enums.ts")};\n` +
|
|
57
|
-
`export {
|
|
76
|
+
`export { AvDecimal } from ${from("runtime/decimal.ts")};\n` +
|
|
58
77
|
`export {\n${errorsModuleExports()
|
|
59
78
|
.map((name) => `\t${name},\n`)
|
|
60
|
-
.join("")}} from ${from("runtime/errors.ts")};\n`
|
|
79
|
+
.join("")}} from ${from("runtime/errors.ts")};\n` +
|
|
80
|
+
(accessVerbs
|
|
81
|
+
? `export { AvAccessError } from ${from("runtime/access.ts")};\n`
|
|
82
|
+
: ""));
|
|
83
|
+
}
|
|
84
|
+
function refuseForeignAccessOrigin(contract, entrypoint, accessOrigins) {
|
|
85
|
+
const url = contract.access?.url;
|
|
86
|
+
if (url === undefined || url.startsWith("/")) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const origin = new URL(url).origin;
|
|
90
|
+
if (origin === entrypoint.deployment?.origin ||
|
|
91
|
+
accessOrigins.includes(origin)) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
throw new AvClientConfigError(`the ClientContract's Aventara Access URL is on ${origin}, not on the entrypoint's origin; $access sends sign-in credentials there, so it is emitted only when avclient's framework.client.ts lists that origin in accessOrigins (a same-origin client from aventara-build --client takes none: mount Access on the app's own origin).`);
|
|
61
95
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AvContractClient } from "@aventara/core";
|
|
2
2
|
import type { EmittedModule } from "./emitted-tree.interface.js";
|
|
3
3
|
/** `generated/contract.ts`, before the banner. */
|
|
4
|
-
export declare function emitContractCarrierModule(contract:
|
|
5
|
-
export declare function parseContractCarrier(text: string): Promise<
|
|
4
|
+
export declare function emitContractCarrierModule(contract: AvContractClient): EmittedModule;
|
|
5
|
+
export declare function parseContractCarrier(text: string): Promise<AvContractClient | undefined>;
|
|
@@ -5,8 +5,6 @@ export declare const DERIVATION_ROOTS: readonly string[];
|
|
|
5
5
|
* (POSIX-separated); `undefined` when there is none.
|
|
6
6
|
*/
|
|
7
7
|
export type DeclarationReader = (relative: string) => string | undefined;
|
|
8
|
-
/** The published declarations of the `@aventara/core` this package resolves. */
|
|
9
|
-
export declare function publishedCoreDeclarations(): DeclarationReader;
|
|
10
8
|
/**
|
|
11
9
|
* Core's declaration closure from {@link DERIVATION_ROOTS}, as modules under
|
|
12
10
|
* `derivation/`, in UTF-16 code-unit order of their path.
|
|
@@ -14,4 +12,6 @@ export declare function publishedCoreDeclarations(): DeclarationReader;
|
|
|
14
12
|
* @throws Error when the closure cannot be transported — a defect of the
|
|
15
13
|
* installed `@aventara/core`, never of the consumer's input.
|
|
16
14
|
*/
|
|
17
|
-
|
|
15
|
+
/** The roots a client with the `access` section copies besides {@link DERIVATION_ROOTS}. */
|
|
16
|
+
export declare const ACCESS_DERIVATION_ROOTS: readonly string[];
|
|
17
|
+
export declare function emitDerivationModules(read?: DeclarationReader, roots?: readonly string[]): readonly EmittedModule[];
|
|
@@ -9,10 +9,11 @@ export const DERIVATION_ROOTS = [
|
|
|
9
9
|
"operations/operation-call",
|
|
10
10
|
"operations/operation-identity",
|
|
11
11
|
"operations/operation-result",
|
|
12
|
+
"operations/validation-code",
|
|
12
13
|
"transactions/deferred-operation",
|
|
13
14
|
"transactions/transaction-reference",
|
|
14
15
|
];
|
|
15
|
-
|
|
16
|
+
function publishedCoreDeclarations() {
|
|
16
17
|
const dist = path.dirname(fileURLToPath(import.meta.resolve("@aventara/core")));
|
|
17
18
|
return (relative) => {
|
|
18
19
|
try {
|
|
@@ -26,9 +27,12 @@ export function publishedCoreDeclarations() {
|
|
|
26
27
|
}
|
|
27
28
|
};
|
|
28
29
|
}
|
|
29
|
-
export
|
|
30
|
+
export const ACCESS_DERIVATION_ROOTS = [
|
|
31
|
+
"policy/access-verbs",
|
|
32
|
+
];
|
|
33
|
+
export function emitDerivationModules(read = publishedCoreDeclarations(), roots = DERIVATION_ROOTS) {
|
|
30
34
|
const copied = new Map();
|
|
31
|
-
const queue =
|
|
35
|
+
const queue = roots.map((root) => `${root}.d.ts`);
|
|
32
36
|
while (queue.length > 0) {
|
|
33
37
|
const file = queue.shift();
|
|
34
38
|
if (copied.has(file)) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AvContractClient } from "@aventara/core";
|
|
2
2
|
import type { EmittedModule } from "./emitted-tree.interface.js";
|
|
3
3
|
/** `generated/runtime/descriptor.ts`, before the banner. */
|
|
4
|
-
export declare function emitDescriptorModule(contract:
|
|
4
|
+
export declare function emitDescriptorModule(contract: AvContractClient): EmittedModule;
|
|
@@ -31,7 +31,7 @@ export function emitDescriptorModule(contract) {
|
|
|
31
31
|
path: "runtime/descriptor.ts",
|
|
32
32
|
source: "/**\n" +
|
|
33
33
|
" * A result field the decoder revives from its wire string, or a relation\n" +
|
|
34
|
-
" * it recurses into, read as the relation's target Resource
|
|
34
|
+
" * it recurses into, read as the relation's target Resource; `many` says the\n" +
|
|
35
35
|
" * value is a list, `{ data, count }` or `{ count }` rather than one record.\n" +
|
|
36
36
|
" */\n" +
|
|
37
37
|
"export type FieldDecoding =\n" +
|
|
@@ -8,7 +8,7 @@ export declare const LEGACY_CLIENT_ENTRY_FILES: readonly string[];
|
|
|
8
8
|
/** The directory holding every other emitted module, under `generateAt`. */
|
|
9
9
|
export declare const GENERATED_DIRECTORY = "generated";
|
|
10
10
|
export type DerivationModulePath = `derivation/${string}.d.ts`;
|
|
11
|
-
export type GeneratedModulePath = DerivationModulePath | "client.ts" | "contract.ts" | "enums.ts" | "metadata.ts" | "runtime/codec.ts" | "runtime/decimal.ts" | "runtime/descriptor.ts" | "runtime/errors.ts" | "runtime/fingerprint.ts" | "runtime/transaction.ts" | "runtime/transport.ts" | "types.d.ts";
|
|
11
|
+
export type GeneratedModulePath = DerivationModulePath | "client.ts" | "contract.ts" | "enums.ts" | "metadata.ts" | "runtime/access.ts" | "runtime/codec.ts" | "runtime/decimal.ts" | "runtime/descriptor.ts" | "runtime/errors.ts" | "runtime/field-check.evaluator.ts" | "runtime/field-validators.ts" | "runtime/fingerprint.ts" | "runtime/transaction.ts" | "runtime/transport.ts" | "types.d.ts";
|
|
12
12
|
export declare const CONTRACT_CARRIER_MODULE = "contract.ts";
|
|
13
13
|
/** A path in the emitted tree: relative to `generateAt`, POSIX-separated. */
|
|
14
14
|
export type EmittedFilePath = typeof CLIENT_ENTRY_FILE | `${typeof GENERATED_DIRECTORY}/${GeneratedModulePath}`;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AvContractClient } from "@aventara/core";
|
|
2
2
|
import type { EmittedModule } from "./emitted-tree.interface.js";
|
|
3
3
|
import { type EmittedNames } from "./name.deriver.js";
|
|
4
|
-
export declare function emitEnumsModule(contract: Pick<
|
|
4
|
+
export declare function emitEnumsModule(contract: Pick<AvContractClient, "enums">, names: EmittedNames): EmittedModule;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AvContractClient } from "@aventara/core";
|
|
2
|
+
import type { EmittedModule } from "./emitted-tree.interface.js";
|
|
3
|
+
import { type ClientModuleStyle } from "./module-style.interface.js";
|
|
4
|
+
/** Where `@aventara/core` publishes the evaluator's source, relative to its package root. */
|
|
5
|
+
export declare const EVALUATOR_SOURCE = "src/runtime/field-check.evaluator.ts";
|
|
6
|
+
/** Reads the evaluator's published source; `undefined` when core publishes none. */
|
|
7
|
+
type EvaluatorSourceReader = () => string | undefined;
|
|
8
|
+
/** The evaluator's source as the `@aventara/core` this package resolves publishes it. */
|
|
9
|
+
export declare function publishedCoreEvaluator(): EvaluatorSourceReader;
|
|
10
|
+
/** Whether the ClientContract carries any check: a field's validators or a Json Shape node's. */
|
|
11
|
+
export declare function contractHasFieldChecks(contract: AvContractClient): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* The two field check modules, or none when the contract carries no check.
|
|
14
|
+
*
|
|
15
|
+
* @param derivation the declaration paths the tree copies under `derivation/`
|
|
16
|
+
* (`derivation/contracts/field-check.d.ts`, …): the evaluator's type imports
|
|
17
|
+
* must name one of them.
|
|
18
|
+
* @throws Error when the evaluator cannot be copied — a defect of the installed
|
|
19
|
+
* `@aventara/core`, never of the consumer's input.
|
|
20
|
+
*/
|
|
21
|
+
export declare function emitFieldCheckModules(contract: AvContractClient, style: ClientModuleStyle, derivation: ReadonlySet<string>, read?: EvaluatorSourceReader): readonly EmittedModule[];
|
|
22
|
+
export {};
|