@aventara/client 1.0.0-rc.1 → 1.0.0-rc.11
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 +247 -0
- package/README.md +147 -11
- package/dist/cli/command.parser.d.ts +4 -3
- package/dist/cli/command.parser.js +3 -3
- package/dist/cli/generation-failure.renderer.d.ts +2 -1
- 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 +17 -0
- package/dist/config/config.loader.d.ts +8 -3
- package/dist/config/config.loader.js +20 -2
- package/dist/config/config.resolver.d.ts +10 -3
- package/dist/config/config.resolver.js +28 -0
- package/dist/config/env.cascade.d.ts +2 -6
- package/dist/config/env.cascade.js +3 -3
- package/dist/config/module-style.resolver.d.ts +3 -6
- package/dist/config/module-style.resolver.js +2 -2
- package/dist/config/tsconfig.locator.d.ts +2 -1
- package/dist/config/tsconfig.locator.js +14 -2
- package/dist/contract/contract.acceptance.d.ts +0 -1
- package/dist/contract/contract.acceptance.js +1 -1
- package/dist/contract/contract.fetcher.d.ts +2 -1
- package/dist/contract/contract.loader.d.ts +3 -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/cached-read.emitter.d.ts +3 -0
- package/dist/emit/cached-read.emitter.js +158 -0
- package/dist/emit/client-surface.emitter.js +130 -13
- package/dist/emit/client-tree.emitter.d.ts +9 -2
- package/dist/emit/client-tree.emitter.js +51 -15
- package/dist/emit/derivation.emitter.d.ts +3 -3
- package/dist/emit/derivation.emitter.js +6 -3
- package/dist/emit/descriptor.emitter.d.ts +2 -0
- package/dist/emit/descriptor.emitter.js +28 -1
- package/dist/emit/emitted-tree.interface.d.ts +1 -1
- package/dist/emit/field-check.emitter.d.ts +2 -1
- package/dist/emit/field-check.emitter.js +2 -13
- package/dist/emit/module-specifier.scanner.d.ts +2 -1
- package/dist/emit/name.deriver.d.ts +2 -3
- package/dist/emit/name.deriver.js +6 -1
- package/dist/emit/named-type.emitter.js +12 -2
- package/dist/emit/operation-set.emitter.d.ts +3 -0
- package/dist/emit/operation-set.emitter.js +604 -0
- package/dist/emit/runtime.emitter.d.ts +5 -4
- package/dist/emit/runtime.emitter.js +484 -42
- package/dist/emit/scalar.codec.js +223 -6
- package/dist/emit/transaction.emitter.js +7 -3
- package/dist/generate.d.ts +3 -2
- package/dist/generate.js +1 -1
- package/dist/hydration/hydration-tag.format.d.ts +11 -0
- package/dist/hydration/hydration-tag.format.js +6 -0
- package/dist/init/client-init.planner.d.ts +3 -2
- package/dist/init/client-init.questions.d.ts +1 -12
- package/dist/init/client-init.questions.js +2 -2
- 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 +3 -2
- package/package.json +9 -5
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { canonicalJson } from "@aventara/core/internal";
|
|
2
|
+
import { moduleSpecifierWriter, } from "./module-style.interface.js";
|
|
3
|
+
export function emitCachedReadModules(style) {
|
|
4
|
+
return [
|
|
5
|
+
{ path: "runtime/canonical-json.ts", source: CANONICAL_JSON_MODULE },
|
|
6
|
+
{
|
|
7
|
+
path: "runtime/cached-read.ts",
|
|
8
|
+
source: cachedReadModuleSource(moduleSpecifierWriter(style)),
|
|
9
|
+
},
|
|
10
|
+
];
|
|
11
|
+
}
|
|
12
|
+
const MAX_READ_URL_LENGTH = 4096;
|
|
13
|
+
const CANONICAL_JSON_MODULE = `// @ts-nocheck
|
|
14
|
+
/**
|
|
15
|
+
* The canonical JSON (RFC 8785) of a cached read's arguments: @aventara/core's
|
|
16
|
+
* own writer, its compiled source copied as it is, so one read is one URL.
|
|
17
|
+
*/
|
|
18
|
+
export ${canonicalJson.toString()}
|
|
19
|
+
`;
|
|
20
|
+
function cachedReadModuleSource(from) {
|
|
21
|
+
return `import { CLIENT_CONTRACT_HASH } from ${from("../metadata")};
|
|
22
|
+
import { canonicalJson as canonicalJsonOf } from ${from("./canonical-json")};
|
|
23
|
+
import { CACHED_READS } from ${from("./descriptor")};
|
|
24
|
+
import {
|
|
25
|
+
type PlatformSignal,
|
|
26
|
+
type PreparedOperation,
|
|
27
|
+
routeOf,
|
|
28
|
+
sendOperation,
|
|
29
|
+
sendRead,
|
|
30
|
+
type TransportConnection,
|
|
31
|
+
} from ${from("./transport")};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Cached reads: a read the ClientContract marks cache is sent as
|
|
35
|
+
* GET <entrypoint>/_resources/<resource>/find/<variant>?q=<arguments>&v=<hash>,
|
|
36
|
+
* alone, so a browser or a CDN may keep its answer. q is the arguments'
|
|
37
|
+
* canonical JSON, percent-encoded, or deflate-raw and base64url when that is
|
|
38
|
+
* shorter. A URL longer than MAX_READ_URL_LENGTH is POSTed instead.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/** The longest URL, in characters, a cached read is sent as a GET; a longer one is POSTed. */
|
|
42
|
+
export const MAX_READ_URL_LENGTH = ${MAX_READ_URL_LENGTH};
|
|
43
|
+
|
|
44
|
+
/** The canonical JSON of a JSON value; throws a TypeError for anything else. */
|
|
45
|
+
const canonicalJson: (value: unknown) => string = canonicalJsonOf;
|
|
46
|
+
|
|
47
|
+
/** Whether the ClientContract marks this read cache: it is sent as a GET, never grouped. */
|
|
48
|
+
export function isCachedRead(operation: PreparedOperation): boolean {
|
|
49
|
+
return (
|
|
50
|
+
operation.family === "find" &&
|
|
51
|
+
Object.hasOwn(CACHED_READS, operation.resource) &&
|
|
52
|
+
(CACHED_READS[operation.resource] ?? []).includes(operation.variant)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Sends one cached read: a GET with its q and v, or a POST to the same route
|
|
58
|
+
* when its URL would pass MAX_READ_URL_LENGTH or canonical JSON cannot write its
|
|
59
|
+
* arguments. Settles as every call does.
|
|
60
|
+
*/
|
|
61
|
+
export async function sendCachedRead(
|
|
62
|
+
connection: TransportConnection,
|
|
63
|
+
operation: PreparedOperation,
|
|
64
|
+
signal?: PlatformSignal,
|
|
65
|
+
): Promise<unknown> {
|
|
66
|
+
let json: string;
|
|
67
|
+
try {
|
|
68
|
+
json = canonicalJson(JSON.parse(operation.body));
|
|
69
|
+
} catch {
|
|
70
|
+
return sendOperation(connection, operation, signal);
|
|
71
|
+
}
|
|
72
|
+
const query = "q=" + (await queryArgument(json)) + "&v=" + encodeURIComponent(CLIENT_CONTRACT_HASH);
|
|
73
|
+
return (connection.entrypoint + routeOf(operation) + "?" + query).length > MAX_READ_URL_LENGTH
|
|
74
|
+
? sendOperation(connection, operation, signal)
|
|
75
|
+
: sendRead(connection, operation, query, signal);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** q: the JSON percent-encoded, or deflate-raw and base64url when that is shorter. */
|
|
79
|
+
async function queryArgument(json: string): Promise<string> {
|
|
80
|
+
const plain = encodeURIComponent(json);
|
|
81
|
+
const compressed = await deflated(json);
|
|
82
|
+
return compressed !== undefined && compressed.length < plain.length ? compressed : plain;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** What the client reads of a CompressionStream, where the consumer's lib declares none. */
|
|
86
|
+
interface Compression {
|
|
87
|
+
readonly writable: {
|
|
88
|
+
getWriter(): { write(chunk: Uint8Array): Promise<void>; close(): Promise<void> };
|
|
89
|
+
};
|
|
90
|
+
readonly readable: {
|
|
91
|
+
getReader(): { read(): Promise<{ readonly done: boolean; readonly value?: Uint8Array | undefined }> };
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** The JSON's UTF-8 bytes deflate-raw'd, in base64url; undefined where the runtime cannot. */
|
|
96
|
+
async function deflated(json: string): Promise<string | undefined> {
|
|
97
|
+
const platform = globalThis as unknown as {
|
|
98
|
+
readonly CompressionStream?: new (format: string) => Compression;
|
|
99
|
+
readonly TextEncoder?: new () => { encode(text: string): Uint8Array };
|
|
100
|
+
};
|
|
101
|
+
const Stream = platform.CompressionStream;
|
|
102
|
+
const Encoder = platform.TextEncoder;
|
|
103
|
+
if (typeof Stream !== "function" || typeof Encoder !== "function") {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const stream = new Stream("deflate-raw");
|
|
108
|
+
const writer = stream.writable.getWriter();
|
|
109
|
+
const written = writer.write(new Encoder().encode(json)).then(() => writer.close());
|
|
110
|
+
written.catch((): void => undefined);
|
|
111
|
+
const reader = stream.readable.getReader();
|
|
112
|
+
const chunks: Uint8Array[] = [];
|
|
113
|
+
let total = 0;
|
|
114
|
+
for (;;) {
|
|
115
|
+
const chunk = await reader.read();
|
|
116
|
+
if (chunk.done) {
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
if (chunk.value !== undefined) {
|
|
120
|
+
chunks.push(chunk.value);
|
|
121
|
+
total += chunk.value.length;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
await written;
|
|
125
|
+
const bytes = new Uint8Array(total);
|
|
126
|
+
let offset = 0;
|
|
127
|
+
for (const chunk of chunks) {
|
|
128
|
+
bytes.set(chunk, offset);
|
|
129
|
+
offset += chunk.length;
|
|
130
|
+
}
|
|
131
|
+
return base64Url(bytes);
|
|
132
|
+
} catch {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const BASE64URL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
138
|
+
|
|
139
|
+
/** Base64url without padding. */
|
|
140
|
+
function base64Url(bytes: Uint8Array): string {
|
|
141
|
+
let text = "";
|
|
142
|
+
for (let index = 0; index < bytes.length; index += 3) {
|
|
143
|
+
const first = bytes[index] ?? 0;
|
|
144
|
+
const second = bytes[index + 1];
|
|
145
|
+
const third = bytes[index + 2];
|
|
146
|
+
const group = (first << 16) | ((second ?? 0) << 8) | (third ?? 0);
|
|
147
|
+
text += BASE64URL.charAt((group >> 18) & 63) + BASE64URL.charAt((group >> 12) & 63);
|
|
148
|
+
if (second !== undefined) {
|
|
149
|
+
text += BASE64URL.charAt((group >> 6) & 63);
|
|
150
|
+
}
|
|
151
|
+
if (third !== undefined) {
|
|
152
|
+
text += BASE64URL.charAt(group & 63);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return text;
|
|
156
|
+
}
|
|
157
|
+
`;
|
|
158
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { ACCESS_VERB_NAMES } from "@aventara/core/internal";
|
|
2
|
+
import { accessVerbShape, accessVerbsOf, } from "./access.emitter.js";
|
|
1
3
|
import { contractHasFieldChecks } from "./field-check.emitter.js";
|
|
2
|
-
import { shapedContractType } from "./json-shape.emitter.js";
|
|
4
|
+
import { jsonSchemaType, shapedContractType } from "./json-shape.emitter.js";
|
|
3
5
|
import { importSpecifier, } from "./module-style.interface.js";
|
|
4
6
|
import { ownPropertyKey } from "./name.deriver.js";
|
|
5
7
|
export function emitClientSurfaceModule(contract, names, style) {
|
|
@@ -18,6 +20,58 @@ export function emitClientSurfaceModule(contract, names, style) {
|
|
|
18
20
|
const renamedEntries = renamed.length === 0
|
|
19
21
|
? ""
|
|
20
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\tOmit<AvCallOptions, "grouping">\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";
|
|
21
75
|
const surface = (name, call) => `type ${name} = {\n` +
|
|
22
76
|
`\treadonly [${resourceKey}]: {\n` +
|
|
23
77
|
"\t\treadonly [F in keyof Grammar[R]]: {\n" +
|
|
@@ -32,23 +86,37 @@ export function emitClientSurfaceModule(contract, names, style) {
|
|
|
32
86
|
? ""
|
|
33
87
|
: `import type { JsonFieldType } from ${from("./derivation/contracts/json-shape")};\n`) +
|
|
34
88
|
`import type { AvApplicationScalarForms } from ${from("./derivation/contracts/scalar-value-type")};\n` +
|
|
35
|
-
|
|
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`) +
|
|
36
92
|
`import type { ${interactive ? "AvDeferredOperationCall, " : ""}AvOperationCall, AvOperationGrammar } from ${from("./derivation/operations/operation-call")};\n` +
|
|
37
93
|
`import type { AvOperationFamily, AvOperationIdentity } from ${from("./derivation/operations/operation-identity")};\n` +
|
|
38
94
|
`import type { AdmittedOperationResult } from ${from("./derivation/operations/operation-result")};\n` +
|
|
39
95
|
(interactive
|
|
40
96
|
? `import type { AvOperation, AvTransactionResults } from ${from("./derivation/transactions/deferred-operation")};\n`
|
|
41
97
|
: "") +
|
|
98
|
+
(access === undefined
|
|
99
|
+
? ""
|
|
100
|
+
: `import type { AvAccessVerbs } from ${from("./derivation/policy/access-verbs")};\n`) +
|
|
42
101
|
`import { DEFAULT_ENTRYPOINT } from ${from("./metadata")};\n` +
|
|
102
|
+
(verbs.length === 0
|
|
103
|
+
? ""
|
|
104
|
+
: `import { callAccess } from ${from("./runtime/access")};\n`) +
|
|
43
105
|
`import type { AvDecimal } from ${from("./runtime/decimal")};\n` +
|
|
44
106
|
`import { ADVERTISED_OPERATIONS } from ${from("./runtime/descriptor")};\n` +
|
|
45
107
|
(checked
|
|
46
108
|
? `import { ${interactive ? "checkOperation, checkPlan" : "checkOperation"} } from ${from("./runtime/field-validators")};\n`
|
|
47
109
|
: "") +
|
|
110
|
+
`import { all, CallQueue${interactive ? ", withSignal" : ""} } from ${from("./runtime/operation-set")};\n` +
|
|
48
111
|
(interactive
|
|
49
112
|
? `import { deferOperation, runTransaction } from ${from("./runtime/transaction")};\n`
|
|
50
113
|
: "") +
|
|
51
|
-
|
|
114
|
+
(access === undefined
|
|
115
|
+
? `import type { AvCallOptions, AvFetch, PlatformSignal, TransportConnection } from ${from("./runtime/transport")};\n`
|
|
116
|
+
: `import { askCan, type AvCallOptions, type AvFetch, type PlatformSignal, type TransportConnection } from ${from("./runtime/transport")};\n`) +
|
|
117
|
+
(verbs.includes("me")
|
|
118
|
+
? `import type { AvAccessMe } from ${from("./types")};\n`
|
|
119
|
+
: "") +
|
|
52
120
|
"\n" +
|
|
53
121
|
`export type { AvCallOptions } from ${from("./runtime/transport")};\n` +
|
|
54
122
|
"\n" +
|
|
@@ -66,6 +134,11 @@ export function emitClientSurfaceModule(contract, names, style) {
|
|
|
66
134
|
"\treadonly entrypoint?: string;\n" +
|
|
67
135
|
"\t/** A custom fetch, for authentication or tests; the platform's otherwise. */\n" +
|
|
68
136
|
"\treadonly fetch?: AvFetch;\n" +
|
|
137
|
+
"\t/**\n" +
|
|
138
|
+
"\t * false sends every call to its own route. Otherwise the calls made\n" +
|
|
139
|
+
"\t * together (in one tick) go out as one Operation Set, POST /_all.\n" +
|
|
140
|
+
"\t */\n" +
|
|
141
|
+
"\treadonly grouping?: boolean;\n" +
|
|
69
142
|
"}\n" +
|
|
70
143
|
"\n" +
|
|
71
144
|
(shaped === undefined
|
|
@@ -99,9 +172,30 @@ export function emitClientSurfaceModule(contract, names, style) {
|
|
|
99
172
|
"/** Sends the handles as one plan and resolves their results as a typed tuple. */\n" +
|
|
100
173
|
"type TransactionCall = <const Steps extends readonly AvOperation<unknown>[]>(\n" +
|
|
101
174
|
"\tsteps: Steps,\n" +
|
|
102
|
-
|
|
175
|
+
'\toptions?: Omit<AvCallOptions, "grouping">,\n' +
|
|
103
176
|
") => Promise<AvTransactionResults<Steps>>;\n"
|
|
104
177
|
: "") +
|
|
178
|
+
accessTypes +
|
|
179
|
+
"\n" +
|
|
180
|
+
"/** How one call of `all(…, { settle: true })` settled. */\n" +
|
|
181
|
+
"type AllSettledEntry<T> =\n" +
|
|
182
|
+
'\t| { readonly status: "fulfilled"; readonly value: T }\n' +
|
|
183
|
+
'\t| { readonly status: "rejected"; readonly reason: unknown };\n' +
|
|
184
|
+
"\n" +
|
|
185
|
+
"/** Awaits an object of calls together and resolves one value per key, or with `settle: true` one settled entry per key. */\n" +
|
|
186
|
+
"type AllCall = <E extends { readonly [key: string]: PromiseLike<unknown> }, S extends boolean = false>(\n" +
|
|
187
|
+
"\tentries: E,\n" +
|
|
188
|
+
"\toptions?: {\n" +
|
|
189
|
+
"\t\t/** Aborts every call of this `all`, and no other. */\n" +
|
|
190
|
+
"\t\treadonly signal?: PlatformSignal;\n" +
|
|
191
|
+
"\t\t/** true settles every key on its own and never rejects for one; strict otherwise. */\n" +
|
|
192
|
+
"\t\treadonly settle?: S;\n" +
|
|
193
|
+
"\t},\n" +
|
|
194
|
+
") => Promise<\n" +
|
|
195
|
+
"\tS extends true\n" +
|
|
196
|
+
"\t\t? { -readonly [K in keyof E]: AllSettledEntry<Awaited<E[K]>> }\n" +
|
|
197
|
+
"\t\t: { -readonly [K in keyof E]: Awaited<E[K]> }\n" +
|
|
198
|
+
">;\n" +
|
|
105
199
|
"\n" +
|
|
106
200
|
"/** The generated client: one property per Resource this deployment's ClientContract advertises. */\n" +
|
|
107
201
|
"export interface AvClient extends AvClientSurface {}\n" +
|
|
@@ -120,6 +214,19 @@ export function emitClientSurfaceModule(contract, names, style) {
|
|
|
120
214
|
" */\n"
|
|
121
215
|
: "/** The generated client. */\n") +
|
|
122
216
|
"export class AvClient {\n" +
|
|
217
|
+
"\t/**\n" +
|
|
218
|
+
"\t * Awaits an object of calls together, so they go out as one Operation Set,\n" +
|
|
219
|
+
"\t * and resolves one value per key. Strict: the first failure rejects it and\n" +
|
|
220
|
+
"\t * aborts its other calls (fail-fast, not atomic: what already ran stays\n" +
|
|
221
|
+
"\t * done). With `settle: true` every key settles as `{ status, value }` or\n" +
|
|
222
|
+
"\t * `{ status, reason }`. Its `signal` aborts each of its calls and no other.\n" +
|
|
223
|
+
"\t */\n" +
|
|
224
|
+
"\tdeclare readonly all: AllCall;\n" +
|
|
225
|
+
(access === undefined
|
|
226
|
+
? ""
|
|
227
|
+
: "\t/** The policy's answers for the UI (`can`), and the Aventara Access verbs this deployment offers. */\n" +
|
|
228
|
+
"\tdeclare readonly $access: AccessSurface;\n" +
|
|
229
|
+
(interactive ? "" : "\n")) +
|
|
123
230
|
(interactive
|
|
124
231
|
? "\t/** Deferred operations, for `transaction`. */\n" +
|
|
125
232
|
"\tdeclare readonly tx: AvTxSurface;\n" +
|
|
@@ -131,15 +238,14 @@ export function emitClientSurfaceModule(contract, names, style) {
|
|
|
131
238
|
'\t\tconst entrypoint = (options.entrypoint ?? DEFAULT_ENTRYPOINT).replace(/\\/+$/, "");\n' +
|
|
132
239
|
"\t\tconst given = options.fetch;\n" +
|
|
133
240
|
"\t\tconst connect = (): TransportConnection => ({ entrypoint, fetch: platformFetch(given) });\n" +
|
|
241
|
+
"\t\tconst queue = new CallQueue(connect, options.grouping !== false);\n" +
|
|
134
242
|
"\t\tfor (const [resource, families] of operationTree(\n" +
|
|
135
243
|
"\t\t\t(resource, family, variant): OperationMethod =>\n" +
|
|
136
244
|
(checked
|
|
137
|
-
? "\t\t\t\
|
|
138
|
-
"\t\t\t\t\
|
|
139
|
-
|
|
140
|
-
"\t\t\t\t
|
|
141
|
-
: "\t\t\t\tasync (args = {}, callOptions = {}) =>\n" +
|
|
142
|
-
"\t\t\t\t\texecute(connect(), resource, family, variant, args, callOptions),\n") +
|
|
245
|
+
? "\t\t\t\t(args = {}, callOptions = {}) =>\n" +
|
|
246
|
+
"\t\t\t\t\tqueue.operation(resource, family, variant, args, callOptions, checkOperation),\n"
|
|
247
|
+
: "\t\t\t\t(args = {}, callOptions = {}) =>\n" +
|
|
248
|
+
"\t\t\t\t\tqueue.operation(resource, family, variant, args, callOptions),\n") +
|
|
143
249
|
"\t\t)) {\n" +
|
|
144
250
|
"\t\t\tObject.defineProperty(this, propertyOf(resource), { value: families, enumerable: true });\n" +
|
|
145
251
|
"\t\t}\n" +
|
|
@@ -154,12 +260,23 @@ export function emitClientSurfaceModule(contract, names, style) {
|
|
|
154
260
|
"\t\t}\n" +
|
|
155
261
|
'\t\tObject.defineProperty(this, "tx", { value: Object.freeze(deferred) });\n' +
|
|
156
262
|
'\t\tObject.defineProperty(this, "transaction", {\n' +
|
|
157
|
-
"\t\t\tvalue:
|
|
263
|
+
"\t\t\tvalue: (steps: unknown, callOptions: AvCallOptions = {}) => {\n" +
|
|
264
|
+
"\t\t\t\tconst plan = Array.isArray(steps) ? [...(steps as readonly unknown[])] : steps;\n" +
|
|
265
|
+
"\t\t\t\treturn queue.alone(\n" +
|
|
266
|
+
"\t\t\t\t\t(signal) =>\n" +
|
|
158
267
|
(checked
|
|
159
|
-
? "\t\t\t\trunTransaction(connect(),
|
|
160
|
-
: "\t\t\t\trunTransaction(connect(),
|
|
268
|
+
? "\t\t\t\t\t\trunTransaction(connect(), plan, withSignal(callOptions, signal), checkPlan),\n"
|
|
269
|
+
: "\t\t\t\t\t\trunTransaction(connect(), plan, withSignal(callOptions, signal)),\n") +
|
|
270
|
+
"\t\t\t\t\tcallOptions.signal,\n" +
|
|
271
|
+
"\t\t\t\t);\n" +
|
|
272
|
+
"\t\t\t},\n" +
|
|
161
273
|
"\t\t});\n"
|
|
162
274
|
: "") +
|
|
275
|
+
'\t\tObject.defineProperty(this, "all", {\n' +
|
|
276
|
+
"\t\t\tvalue: (entries: unknown, allOptions?: { readonly signal?: PlatformSignal; readonly settle?: boolean }) =>\n" +
|
|
277
|
+
"\t\t\t\tall(entries, allOptions),\n" +
|
|
278
|
+
"\t\t});\n" +
|
|
279
|
+
accessRuntime +
|
|
163
280
|
"\t}\n" +
|
|
164
281
|
"}\n" +
|
|
165
282
|
"\n" +
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import type { AvContractClient } from "@aventara/core";
|
|
2
|
-
import type {
|
|
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
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: AvContractClient, entrypoint:
|
|
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";
|
|
4
|
+
import { emitCachedReadModules } from "./cached-read.emitter.js";
|
|
2
5
|
import { emitClientSurfaceModule } from "./client-surface.emitter.js";
|
|
3
6
|
import { emitContractCarrierModule } from "./contract-carrier.emitter.js";
|
|
4
|
-
import { emitDerivationModules } from "./derivation.emitter.js";
|
|
5
|
-
import { emitDescriptorModule } from "./descriptor.emitter.js";
|
|
7
|
+
import { ACCESS_DERIVATION_ROOTS, DERIVATION_ROOTS, emitDerivationModules, } from "./derivation.emitter.js";
|
|
8
|
+
import { contractHasCachedReads, emitDescriptorModule, } from "./descriptor.emitter.js";
|
|
6
9
|
import { CLIENT_ENTRY_FILE, GENERATED_DIRECTORY, } from "./emitted-tree.interface.js";
|
|
7
10
|
import { emitEnumsModule } from "./enum.emitter.js";
|
|
8
11
|
import { emitFieldCheckModules } from "./field-check.emitter.js";
|
|
9
12
|
import { moduleSpecifierWriter, } from "./module-style.interface.js";
|
|
10
13
|
import { deriveEmittedNames } from "./name.deriver.js";
|
|
11
14
|
import { emitNamedTypesModule } from "./named-type.emitter.js";
|
|
15
|
+
import { emitOperationSetModule } from "./operation-set.emitter.js";
|
|
12
16
|
import { emitDecimalModule, emitErrorsModule, emitMetadataModule, emitTransportModule, errorsModuleExports, } from "./runtime.emitter.js";
|
|
13
17
|
import { emitScalarCodecModule } from "./scalar.codec.js";
|
|
14
18
|
import { emitTransactionModules } from "./transaction.emitter.js";
|
|
15
|
-
export function emitClientTree(contract, entrypoint, style) {
|
|
19
|
+
export function emitClientTree(contract, entrypoint, style, regenerate = AVCLIENT_REGENERATE, accessOrigins = []) {
|
|
20
|
+
refuseForeignAccessOrigin(contract, entrypoint, accessOrigins);
|
|
16
21
|
const names = deriveEmittedNames(contract);
|
|
17
|
-
const derivation = emitDerivationModules(
|
|
22
|
+
const derivation = emitDerivationModules(undefined, contract.access === undefined
|
|
23
|
+
? DERIVATION_ROOTS
|
|
24
|
+
: [...DERIVATION_ROOTS, ...ACCESS_DERIVATION_ROOTS]);
|
|
18
25
|
const generated = [
|
|
19
26
|
...derivation,
|
|
20
27
|
emitClientSurfaceModule(contract, names, style),
|
|
@@ -24,8 +31,11 @@ export function emitClientTree(contract, entrypoint, style) {
|
|
|
24
31
|
emitMetadataModule(contract.protocol, entrypoint),
|
|
25
32
|
emitScalarCodecModule(style),
|
|
26
33
|
emitDecimalModule(),
|
|
27
|
-
emitErrorsModule(),
|
|
28
|
-
emitTransportModule(style),
|
|
34
|
+
emitErrorsModule(regenerate),
|
|
35
|
+
emitTransportModule(style, contract.access !== undefined),
|
|
36
|
+
emitOperationSetModule(style, contractHasCachedReads(contract)),
|
|
37
|
+
...(contractHasCachedReads(contract) ? emitCachedReadModules(style) : []),
|
|
38
|
+
...emitAccessModules(contract, style),
|
|
29
39
|
emitNamedTypesModule(contract, names, style),
|
|
30
40
|
...(contract.transactions === "interactive"
|
|
31
41
|
? emitTransactionModules(style)
|
|
@@ -33,7 +43,10 @@ export function emitClientTree(contract, entrypoint, style) {
|
|
|
33
43
|
...emitFieldCheckModules(contract, style, new Set(derivation.map((module) => module.path))),
|
|
34
44
|
];
|
|
35
45
|
const files = [
|
|
36
|
-
{
|
|
46
|
+
{
|
|
47
|
+
path: CLIENT_ENTRY_FILE,
|
|
48
|
+
source: clientEntrySource(style, accessVerbsOf(contract).length > 0),
|
|
49
|
+
},
|
|
37
50
|
...generated.map((module) => ({
|
|
38
51
|
path: `${GENERATED_DIRECTORY}/${module.path}`,
|
|
39
52
|
source: module.source,
|
|
@@ -44,21 +57,44 @@ export function emitClientTree(contract, entrypoint, style) {
|
|
|
44
57
|
.sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0)
|
|
45
58
|
.map((file) => ({
|
|
46
59
|
path: file.path,
|
|
47
|
-
bytes: encoder.encode(withGeneratedBanner(file.source)),
|
|
60
|
+
bytes: encoder.encode(withGeneratedBanner(file.source, regenerate)),
|
|
48
61
|
}));
|
|
49
62
|
return { tree, style, warnings: names.warnings };
|
|
50
63
|
}
|
|
51
|
-
function clientEntrySource(style) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
64
|
+
function clientEntrySource(style, accessVerbs) {
|
|
65
|
+
return (`export { default } from ${generatedModuleSpecifier(style, "client.ts")};\n` +
|
|
66
|
+
clientEntryReExports(style, "value", accessVerbs));
|
|
67
|
+
}
|
|
68
|
+
function generatedModuleSpecifier(style, module) {
|
|
69
|
+
return moduleSpecifierWriter(style)(`./${GENERATED_DIRECTORY}/${module.replace(/\.d\.ts$|\.ts$/, "")}`);
|
|
70
|
+
}
|
|
71
|
+
export function clientEntryReExports(style, clientClass = "value", accessVerbs = false) {
|
|
72
|
+
const from = (module) => generatedModuleSpecifier(style, module);
|
|
73
|
+
return ((clientClass === "value"
|
|
74
|
+
? `export {\n\tAvClient,\n\ttype AvClientOptions,\n\ttype AvCallOptions,\n} from ${from("client.ts")};\n`
|
|
75
|
+
: `export type { AvClient, AvClientOptions, AvCallOptions } from ${from("client.ts")};\n`) +
|
|
56
76
|
`export type { AvFetch } from ${from("runtime/transport.ts")};\n` +
|
|
57
77
|
`export type { AvOperation } from ${from("derivation/transactions/deferred-operation.d.ts")};\n` +
|
|
58
78
|
`export type * from ${from("types.d.ts")};\n` +
|
|
59
79
|
`export * from ${from("enums.ts")};\n` +
|
|
60
80
|
`export { AvDecimal } from ${from("runtime/decimal.ts")};\n` +
|
|
81
|
+
`export { avLazy } from ${from("runtime/operation-set.ts")};\n` +
|
|
61
82
|
`export {\n${errorsModuleExports()
|
|
62
83
|
.map((name) => `\t${name},\n`)
|
|
63
|
-
.join("")}} from ${from("runtime/errors.ts")};\n`
|
|
84
|
+
.join("")}} from ${from("runtime/errors.ts")};\n` +
|
|
85
|
+
(accessVerbs
|
|
86
|
+
? `export { AvAccessError } from ${from("runtime/access.ts")};\n`
|
|
87
|
+
: ""));
|
|
88
|
+
}
|
|
89
|
+
function refuseForeignAccessOrigin(contract, entrypoint, accessOrigins) {
|
|
90
|
+
const url = contract.access?.url;
|
|
91
|
+
if (url === undefined || url.startsWith("/")) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const origin = new URL(url).origin;
|
|
95
|
+
if (origin === entrypoint.deployment?.origin ||
|
|
96
|
+
accessOrigins.includes(origin)) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
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).`);
|
|
64
100
|
}
|
|
@@ -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[];
|
|
@@ -13,7 +13,7 @@ export const DERIVATION_ROOTS = [
|
|
|
13
13
|
"transactions/deferred-operation",
|
|
14
14
|
"transactions/transaction-reference",
|
|
15
15
|
];
|
|
16
|
-
|
|
16
|
+
function publishedCoreDeclarations() {
|
|
17
17
|
const dist = path.dirname(fileURLToPath(import.meta.resolve("@aventara/core")));
|
|
18
18
|
return (relative) => {
|
|
19
19
|
try {
|
|
@@ -27,9 +27,12 @@ export function publishedCoreDeclarations() {
|
|
|
27
27
|
}
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
-
export
|
|
30
|
+
export const ACCESS_DERIVATION_ROOTS = [
|
|
31
|
+
"policy/access-verbs",
|
|
32
|
+
];
|
|
33
|
+
export function emitDerivationModules(read = publishedCoreDeclarations(), roots = DERIVATION_ROOTS) {
|
|
31
34
|
const copied = new Map();
|
|
32
|
-
const queue =
|
|
35
|
+
const queue = roots.map((root) => `${root}.d.ts`);
|
|
33
36
|
while (queue.length > 0) {
|
|
34
37
|
const file = queue.shift();
|
|
35
38
|
if (copied.has(file)) {
|
|
@@ -2,3 +2,5 @@ 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
4
|
export declare function emitDescriptorModule(contract: AvContractClient): EmittedModule;
|
|
5
|
+
/** Whether the ClientContract marks any read `cache`: a client that sends GETs. */
|
|
6
|
+
export declare function contractHasCachedReads(contract: AvContractClient): boolean;
|
|
@@ -51,9 +51,36 @@ export function emitDescriptorModule(contract) {
|
|
|
51
51
|
"\tresource: string,\n" +
|
|
52
52
|
"\tfamily: string,\n" +
|
|
53
53
|
"\tvariant: string,\n" +
|
|
54
|
-
`])[] = [${advertised.length === 0 ? "" : `\n${advertised.join("")}`}];\n
|
|
54
|
+
`])[] = [${advertised.length === 0 ? "" : `\n${advertised.join("")}`}];\n` +
|
|
55
|
+
"\n" +
|
|
56
|
+
"/** The ClientContract's limits a group of calls is split by: entries per Operation Set, and request bytes. */\n" +
|
|
57
|
+
"export const OPERATION_SET_LIMITS: { readonly maxAll: number; readonly maxRequestBytes: number } = {\n" +
|
|
58
|
+
`\tmaxAll: ${JSON.stringify(contract.limits.maxAll)},\n` +
|
|
59
|
+
`\tmaxRequestBytes: ${JSON.stringify(contract.limits.maxRequestBytes)},\n` +
|
|
60
|
+
"};\n" +
|
|
61
|
+
cachedReadsSource(contract),
|
|
55
62
|
};
|
|
56
63
|
}
|
|
64
|
+
function cachedReadsSource(contract) {
|
|
65
|
+
const entries = codeUnitOrder(Object.keys(contract.resources)).flatMap((name) => {
|
|
66
|
+
const variants = codeUnitOrder(Object.keys(contract.resources[name]?.cache ?? {}));
|
|
67
|
+
return variants.length === 0
|
|
68
|
+
? []
|
|
69
|
+
: [
|
|
70
|
+
`\t${ownPropertyKey(name)}: [${variants.map((variant) => JSON.stringify(variant)).join(", ")}],\n`,
|
|
71
|
+
];
|
|
72
|
+
});
|
|
73
|
+
return entries.length === 0
|
|
74
|
+
? ""
|
|
75
|
+
: "\n" +
|
|
76
|
+
"/** Per Resource, the find variants served as a cached read: sent as a GET. */\n" +
|
|
77
|
+
"export const CACHED_READS: { readonly [resource: string]: readonly string[] } = {\n" +
|
|
78
|
+
entries.join("") +
|
|
79
|
+
"};\n";
|
|
80
|
+
}
|
|
81
|
+
export function contractHasCachedReads(contract) {
|
|
82
|
+
return Object.values(contract.resources).some((resource) => Object.keys(resource.cache ?? {}).length > 0);
|
|
83
|
+
}
|
|
57
84
|
function decodingOf(field) {
|
|
58
85
|
if (field === undefined) {
|
|
59
86
|
return undefined;
|
|
@@ -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/field-check.evaluator.ts" | "runtime/field-validators.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/cached-read.ts" | "runtime/canonical-json.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/operation-set.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}`;
|
|
@@ -4,7 +4,7 @@ import { type ClientModuleStyle } from "./module-style.interface.js";
|
|
|
4
4
|
/** Where `@aventara/core` publishes the evaluator's source, relative to its package root. */
|
|
5
5
|
export declare const EVALUATOR_SOURCE = "src/runtime/field-check.evaluator.ts";
|
|
6
6
|
/** Reads the evaluator's published source; `undefined` when core publishes none. */
|
|
7
|
-
|
|
7
|
+
type EvaluatorSourceReader = () => string | undefined;
|
|
8
8
|
/** The evaluator's source as the `@aventara/core` this package resolves publishes it. */
|
|
9
9
|
export declare function publishedCoreEvaluator(): EvaluatorSourceReader;
|
|
10
10
|
/** Whether the ClientContract carries any check: a field's validators or a Json Shape node's. */
|
|
@@ -19,3 +19,4 @@ export declare function contractHasFieldChecks(contract: AvContractClient): bool
|
|
|
19
19
|
* `@aventara/core`, never of the consumer's input.
|
|
20
20
|
*/
|
|
21
21
|
export declare function emitFieldCheckModules(contract: AvContractClient, style: ClientModuleStyle, derivation: ReadonlySet<string>, read?: EvaluatorSourceReader): readonly EmittedModule[];
|
|
22
|
+
export {};
|