@aventara/client 0.1.0-pilot.3 → 1.0.0-rc.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/CHANGELOG.md +222 -0
- package/README.md +29 -14
- package/dist/cli/command.parser.d.ts +4 -14
- package/dist/cli/command.parser.js +12 -1
- package/dist/cli/generate.command.d.ts +6 -1
- package/dist/cli/generate.command.js +12 -1
- package/dist/cli/generation-success.renderer.d.ts +0 -4
- package/dist/cli/step.reporter.d.ts +31 -0
- package/dist/cli/step.reporter.js +87 -0
- package/dist/cli/terminal.prompter.d.ts +0 -6
- package/dist/cli/warning.renderer.d.ts +0 -6
- package/dist/cli.d.ts +0 -16
- package/dist/cli.js +2 -0
- package/dist/config/client-config.interface.d.ts +10 -20
- package/dist/config/config.loader.d.ts +0 -21
- package/dist/config/config.resolver.d.ts +2 -17
- package/dist/config/env.cascade.d.ts +0 -30
- package/dist/config/module-style.resolver.d.ts +0 -25
- package/dist/config/tsconfig.locator.d.ts +0 -24
- package/dist/contract/contract.acceptance.d.ts +1 -40
- package/dist/contract/contract.acceptance.js +1 -1
- package/dist/contract/contract.fetcher.d.ts +0 -29
- package/dist/contract/contract.loader.d.ts +0 -7
- package/dist/emit/banner.emitter.d.ts +0 -18
- package/dist/emit/banner.emitter.js +1 -1
- package/dist/emit/client-surface.emitter.d.ts +0 -23
- package/dist/emit/client-surface.emitter.js +17 -6
- package/dist/emit/client-tree.emitter.d.ts +0 -20
- package/dist/emit/contract-carrier.emitter.d.ts +1 -8
- package/dist/emit/contract-carrier.emitter.js +1 -1
- package/dist/emit/derivation.emitter.d.ts +0 -28
- package/dist/emit/descriptor.emitter.d.ts +1 -1
- package/dist/emit/descriptor.emitter.js +1 -1
- package/dist/emit/emitted-tree.interface.d.ts +0 -53
- package/dist/emit/enum.emitter.d.ts +0 -20
- package/dist/emit/json-shape.emitter.d.ts +16 -0
- package/dist/emit/json-shape.emitter.js +88 -0
- package/dist/emit/module-specifier.scanner.d.ts +0 -15
- package/dist/emit/module-style.interface.d.ts +0 -15
- package/dist/emit/name.deriver.d.ts +1 -72
- package/dist/emit/name.deriver.js +1 -1
- package/dist/emit/named-type.emitter.d.ts +0 -20
- package/dist/emit/runtime.emitter.d.ts +1 -51
- package/dist/emit/runtime.emitter.js +15 -22
- package/dist/emit/scalar.codec.d.ts +0 -39
- package/dist/emit/scalar.codec.js +56 -13
- package/dist/emit/transaction.emitter.d.ts +0 -6
- package/dist/emit/transaction.emitter.js +112 -78
- package/dist/generate.d.ts +8 -36
- package/dist/generate.js +38 -24
- package/dist/index.d.ts +1 -5
- package/dist/init/client-config.template.d.ts +0 -8
- package/dist/init/client-init.orchestrator.js +31 -16
- package/dist/init/client-init.planner.d.ts +0 -1
- package/dist/init/client-init.questions.d.ts +0 -8
- package/dist/init/command.runner.d.ts +2 -0
- package/dist/init/command.runner.js +4 -1
- package/dist/output/output.validator.d.ts +0 -39
- package/dist/output/output.writer.d.ts +1 -110
- package/package.json +6 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BUILT_IN_SCALARS } from "@aventara/core";
|
|
1
|
+
import { BUILT_IN_SCALARS } from "@aventara/core/internal";
|
|
2
2
|
import { AvProtocol } from "@aventara/core/protocol";
|
|
3
3
|
import { moduleSpecifierWriter, } from "./module-style.interface.js";
|
|
4
4
|
export function wireGrammarLiteral(scalar) {
|
|
@@ -331,31 +331,74 @@ export function encodeArguments(value: unknown): unknown {
|
|
|
331
331
|
return encodeValue(value, [], new Set());
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
334
|
+
/**
|
|
335
|
+
* One read of a caller's arguments. Every native read on them happens through it,
|
|
336
|
+
* here and in the transaction builder, so a value that cannot be read — a revoked
|
|
337
|
+
* Proxy (an Immer draft after produce), a throwing getter or trap — is refused at its
|
|
338
|
+
* path with a TypeError before anything is sent, never with the engine's own error.
|
|
339
|
+
*/
|
|
340
|
+
export function readArgument<T>(path: Path, read: () => T): T {
|
|
341
|
+
try {
|
|
342
|
+
return read();
|
|
343
|
+
} catch {
|
|
344
|
+
throw new TypeError(
|
|
345
|
+
"An operation's arguments must be readable plain data, and the value at " + pointerOf(path) + " could not be read.",
|
|
346
|
+
);
|
|
337
347
|
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** How encodeValue treats an object: a scalar's runtime class, a container, or neither. */
|
|
351
|
+
function objectKind(value: object): "datetime" | "decimal" | "bytes" | "container" | "other" {
|
|
338
352
|
if (value instanceof Date) {
|
|
339
|
-
return
|
|
353
|
+
return "datetime";
|
|
340
354
|
}
|
|
341
355
|
if (value instanceof Decimal) {
|
|
342
|
-
return
|
|
356
|
+
return "decimal";
|
|
343
357
|
}
|
|
344
358
|
if (value instanceof Uint8Array) {
|
|
345
|
-
return
|
|
359
|
+
return "bytes";
|
|
346
360
|
}
|
|
347
|
-
|
|
361
|
+
return isJsonContainer(value) ? "container" : "other";
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function encodeValue(value: unknown, path: Path, ancestors: Set<object>): unknown {
|
|
365
|
+
if (typeof value === "bigint") {
|
|
366
|
+
return encodeScalar("bigint", value);
|
|
367
|
+
}
|
|
368
|
+
if (typeof value !== "object" || value === null) {
|
|
369
|
+
return value;
|
|
370
|
+
}
|
|
371
|
+
const kind = readArgument(path, () => objectKind(value));
|
|
372
|
+
if (kind === "other") {
|
|
348
373
|
return value;
|
|
349
374
|
}
|
|
375
|
+
if (kind !== "container") {
|
|
376
|
+
return encodeScalar(kind, value);
|
|
377
|
+
}
|
|
350
378
|
if (ancestors.has(value)) {
|
|
351
379
|
return refuseBody(path, "a cycle");
|
|
352
380
|
}
|
|
353
381
|
ancestors.add(value);
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
382
|
+
let copy: unknown;
|
|
383
|
+
if (readArgument(path, () => Array.isArray(value))) {
|
|
384
|
+
const items = value as readonly unknown[];
|
|
385
|
+
const length = readArgument(path, () => items.length);
|
|
386
|
+
const list: unknown[] = [];
|
|
387
|
+
for (let index = 0; index < length; index += 1) {
|
|
388
|
+
const itemPath = [...path, index];
|
|
389
|
+
list.push(encodeValue(readArgument(itemPath, () => items[index]), itemPath, ancestors));
|
|
390
|
+
}
|
|
391
|
+
copy = list;
|
|
392
|
+
} else {
|
|
393
|
+
const record = value as Readonly<Record<string, unknown>>;
|
|
394
|
+
const members: [string, unknown][] = [];
|
|
395
|
+
for (const key of readArgument(path, () => Object.keys(record))) {
|
|
396
|
+
const memberPath = [...path, key];
|
|
397
|
+
members.push([key, encodeValue(readArgument(memberPath, () => record[key]), memberPath, ancestors)]);
|
|
398
|
+
}
|
|
399
|
+
// fromEntries defines each key, so "__proto__" stays an own key.
|
|
400
|
+
copy = Object.fromEntries(members);
|
|
401
|
+
}
|
|
359
402
|
ancestors.delete(value);
|
|
360
403
|
return copy;
|
|
361
404
|
}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
1
|
import type { EmittedModule } from "./emitted-tree.interface.js";
|
|
2
2
|
import { type ClientModuleStyle } from "./module-style.interface.js";
|
|
3
|
-
/**
|
|
4
|
-
* The transaction builder and runner: `runtime/fingerprint.ts` and
|
|
5
|
-
* `runtime/transaction.ts`, emitted iff the ClientContract advertises
|
|
6
|
-
* `interactive` transactions — `client.ts` wires them to `avClient.tx` and
|
|
7
|
-
* `avClient.transaction`.
|
|
8
|
-
*/
|
|
9
3
|
export declare function emitTransactionModules(style: ClientModuleStyle): readonly EmittedModule[];
|
|
@@ -12,8 +12,7 @@ const FINGERPRINT_MODULE = `/**
|
|
|
12
12
|
* The v1 operation fingerprint: RFC 8785 canonical JSON of
|
|
13
13
|
* { resource, family, variant, args } over the WIRE-form arguments, its UTF-8
|
|
14
14
|
* bytes hashed with SHA-256, the first 16 bytes in base64url, prefixed "fp1:".
|
|
15
|
-
* Dependency-free
|
|
16
|
-
* computeOperationFingerprint.
|
|
15
|
+
* Dependency-free; the same bytes the server computes.
|
|
17
16
|
*/
|
|
18
17
|
|
|
19
18
|
/** One plan node, its arguments already in wire form. */
|
|
@@ -210,26 +209,29 @@ function base64Url(bytes: Uint8Array): string {
|
|
|
210
209
|
}
|
|
211
210
|
`;
|
|
212
211
|
function transactionModuleSource(from) {
|
|
213
|
-
return `import { encodeArguments, serializeWireBody } from ${from("./codec")};
|
|
212
|
+
return `import { encodeArguments, readArgument, serializeWireBody } from ${from("./codec")};
|
|
214
213
|
import { type Cause, type FrameworkError, frameworkErrorOf } from ${from("./errors")};
|
|
215
214
|
import { fingerprintOf } from ${from("./fingerprint")};
|
|
216
215
|
import { type CallOptions, executePlan, type TransportConnection } from ${from("./transport")};
|
|
217
216
|
|
|
218
217
|
/**
|
|
219
|
-
* The deferred transaction builder and runner
|
|
220
|
-
* in process (transaction-plan-assembler.ts): \`avClient.tx\` defers an operation
|
|
218
|
+
* The deferred transaction builder and runner: \`avClient.tx\` defers an operation
|
|
221
219
|
* into a handle — pure data, no request — and \`avClient.transaction\` binds a
|
|
222
220
|
* list of handles into one transaction plan, sends it once, and resolves one
|
|
223
221
|
* result per handle in list order. A handle has no connection, so any client of
|
|
224
222
|
* this generated tree may run it.
|
|
225
223
|
*/
|
|
226
224
|
|
|
227
|
-
/**
|
|
225
|
+
/**
|
|
226
|
+
* What a handle defers — or why its arguments could not be read: the builder
|
|
227
|
+
* never throws into app code, and running the handle refuses it instead.
|
|
228
|
+
*/
|
|
228
229
|
interface DeferredRecord {
|
|
229
230
|
readonly resource: string;
|
|
230
231
|
readonly family: string;
|
|
231
232
|
readonly variant: string;
|
|
232
233
|
readonly args: unknown;
|
|
234
|
+
readonly unreadable?: TypeError;
|
|
233
235
|
}
|
|
234
236
|
|
|
235
237
|
/** What a \`$ref\` placeholder stands for until the plan is assembled. */
|
|
@@ -238,8 +240,6 @@ interface Placeholder {
|
|
|
238
240
|
readonly field: string;
|
|
239
241
|
}
|
|
240
242
|
|
|
241
|
-
type MutableRecord = Record<string, unknown>;
|
|
242
|
-
|
|
243
243
|
/** One wire node of a transaction plan, its arguments in wire form. */
|
|
244
244
|
interface PlanNode {
|
|
245
245
|
readonly resource: string;
|
|
@@ -249,10 +249,11 @@ interface PlanNode {
|
|
|
249
249
|
readonly fingerprint: string | null;
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
/** One
|
|
253
|
-
interface
|
|
252
|
+
/** One placeholder an argument tree holds: where, the object itself, and its meaning. */
|
|
253
|
+
interface PlaceholderSite {
|
|
254
254
|
readonly path: readonly (string | number)[];
|
|
255
|
-
readonly
|
|
255
|
+
readonly object: object;
|
|
256
|
+
readonly placeholder: Placeholder;
|
|
256
257
|
}
|
|
257
258
|
|
|
258
259
|
const HANDLES = new WeakMap<object, DeferredRecord>();
|
|
@@ -266,45 +267,73 @@ function isPlainRecord(value: unknown): value is Readonly<Record<string, unknown
|
|
|
266
267
|
return prototype === Object.prototype || prototype === null;
|
|
267
268
|
}
|
|
268
269
|
|
|
269
|
-
|
|
270
|
-
function referenceKeys(family: string): readonly string[] {
|
|
271
|
-
return family === "create" || family === "update" ? ["data"] : family === "upsert" ? ["create", "update"] : [];
|
|
272
|
-
}
|
|
270
|
+
const KEEP: unique symbol = Symbol("keep");
|
|
273
271
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
272
|
+
/**
|
|
273
|
+
* Rebuilds an argument tree: every plain record and array is copied, every other
|
|
274
|
+
* value kept, and \`leaf\` may replace any value first (a placeholder, by identity).
|
|
275
|
+
* A placeholder is bound wherever the arguments hold one; the server answers a
|
|
276
|
+
* position that takes no reference. A container met again on its own ancestry is
|
|
277
|
+
* kept as it is; the codec refuses it.
|
|
278
|
+
*/
|
|
279
|
+
function rebuild(
|
|
280
|
+
value: unknown,
|
|
281
|
+
path: readonly (string | number)[],
|
|
282
|
+
leaf: (value: unknown, path: readonly (string | number)[]) => unknown,
|
|
283
|
+
ancestors: Set<object> = new Set(),
|
|
284
|
+
): unknown {
|
|
285
|
+
const replaced = leaf(value, path);
|
|
286
|
+
if (replaced !== KEEP || typeof value !== "object" || value === null) {
|
|
287
|
+
return replaced === KEEP ? value : replaced;
|
|
277
288
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
289
|
+
const array = readArgument(path, () => Array.isArray(value));
|
|
290
|
+
if ((!array && !readArgument(path, () => isPlainRecord(value))) || ancestors.has(value)) {
|
|
291
|
+
return value;
|
|
292
|
+
}
|
|
293
|
+
ancestors.add(value);
|
|
294
|
+
let copy: unknown;
|
|
295
|
+
if (array) {
|
|
296
|
+
const items = value as readonly unknown[];
|
|
297
|
+
const length = readArgument(path, () => items.length);
|
|
298
|
+
const list: unknown[] = [];
|
|
299
|
+
for (let index = 0; index < length; index += 1) {
|
|
300
|
+
const itemPath = [...path, index];
|
|
301
|
+
list.push(rebuild(readArgument(itemPath, () => items[index]), itemPath, leaf, ancestors));
|
|
282
302
|
}
|
|
283
|
-
|
|
284
|
-
}
|
|
303
|
+
copy = list;
|
|
304
|
+
} else {
|
|
305
|
+
const record = value as Readonly<Record<string, unknown>>;
|
|
306
|
+
const members: [string, unknown][] = [];
|
|
307
|
+
for (const key of readArgument(path, () => Object.keys(record))) {
|
|
308
|
+
const memberPath = [...path, key];
|
|
309
|
+
members.push([key, rebuild(readArgument(memberPath, () => record[key]), memberPath, leaf, ancestors)]);
|
|
310
|
+
}
|
|
311
|
+
copy = Object.fromEntries(members);
|
|
312
|
+
}
|
|
313
|
+
ancestors.delete(value);
|
|
314
|
+
return copy;
|
|
285
315
|
}
|
|
286
316
|
|
|
287
|
-
/** \`args\` with
|
|
288
|
-
function
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
const value = args[key];
|
|
295
|
-
if (Array.isArray(value)) {
|
|
296
|
-
result[key] = value.map((entry: unknown, index) => (isPlainRecord(entry) ? rebuild({ path: [key, index], record: entry }) : entry));
|
|
297
|
-
} else if (isPlainRecord(value)) {
|
|
298
|
-
result[key] = rebuild({ path: [key], record: value });
|
|
317
|
+
/** Every placeholder \`args\` holds, with its path inside the arguments. */
|
|
318
|
+
function placeholderSites(args: unknown): readonly PlaceholderSite[] {
|
|
319
|
+
const sites: PlaceholderSite[] = [];
|
|
320
|
+
rebuild(args, [], (value, path) => {
|
|
321
|
+
const placeholder = typeof value === "object" && value !== null ? PLACEHOLDERS.get(value) : undefined;
|
|
322
|
+
if (placeholder === undefined) {
|
|
323
|
+
return KEEP;
|
|
299
324
|
}
|
|
300
|
-
|
|
301
|
-
|
|
325
|
+
sites.push({ path, object: value as object, placeholder });
|
|
326
|
+
return value;
|
|
327
|
+
});
|
|
328
|
+
return sites;
|
|
302
329
|
}
|
|
303
330
|
|
|
304
331
|
/**
|
|
305
|
-
* Defers one operation into a handle: nothing is sent
|
|
306
|
-
*
|
|
307
|
-
* a reference — and a handle can reference only handles built before it.
|
|
332
|
+
* Defers one operation into a handle: nothing is sent, and nothing is thrown.
|
|
333
|
+
* Its arguments are copied now, so a later change to the caller's data cannot
|
|
334
|
+
* move a reference — and a handle can reference only handles built before it.
|
|
335
|
+
* Arguments that cannot be read are kept as their refusal, which running the
|
|
336
|
+
* handle rejects with.
|
|
308
337
|
*/
|
|
309
338
|
export function deferOperation(resource: string, family: string, variant: string, args: unknown): object {
|
|
310
339
|
const handle: object = Object.freeze({
|
|
@@ -314,12 +343,19 @@ export function deferOperation(resource: string, family: string, variant: string
|
|
|
314
343
|
return placeholder;
|
|
315
344
|
},
|
|
316
345
|
});
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
346
|
+
let record: DeferredRecord;
|
|
347
|
+
try {
|
|
348
|
+
record = {
|
|
349
|
+
resource,
|
|
350
|
+
family,
|
|
351
|
+
variant,
|
|
352
|
+
args: rebuild(args, [], (value) => (typeof value === "object" && value !== null && PLACEHOLDERS.has(value) ? value : KEEP)),
|
|
353
|
+
};
|
|
354
|
+
} catch (error) {
|
|
355
|
+
// Only readArgument throws here: an argument that could not be read.
|
|
356
|
+
record = { resource, family, variant, args: {}, unreadable: error as TypeError };
|
|
357
|
+
}
|
|
358
|
+
HANDLES.set(handle, record);
|
|
323
359
|
return handle;
|
|
324
360
|
}
|
|
325
361
|
|
|
@@ -327,7 +363,7 @@ export function deferOperation(resource: string, family: string, variant: string
|
|
|
327
363
|
function entryRefusal(operation: number, message: string): FrameworkError {
|
|
328
364
|
const cause: Cause = {
|
|
329
365
|
message: "Transaction plan failed framework validation.",
|
|
330
|
-
issues: [{ path: ["operation
|
|
366
|
+
issues: [{ path: ["operations", operation], code: "V1001", message }],
|
|
331
367
|
operation,
|
|
332
368
|
};
|
|
333
369
|
return frameworkErrorOf("A2004", cause);
|
|
@@ -338,8 +374,9 @@ function entryRefusal(operation: number, message: string): FrameworkError {
|
|
|
338
374
|
* step. Refused before anything is sent, with what core answers in
|
|
339
375
|
* process: a list that is not one, an entry that is not a handle of this
|
|
340
376
|
* generated client, one handle twice (ValidationError A2004 / V1001 at
|
|
341
|
-
* ["
|
|
342
|
-
* V1010 at its \`$ref\`)
|
|
377
|
+
* ["operations", i]: the entry itself); a reference to a handle not in the list (ValidationError A2007 /
|
|
378
|
+
* V1010 at its \`$ref\`); a handle whose arguments could not be read when it was
|
|
379
|
+
* built (the TypeError naming where).
|
|
343
380
|
*/
|
|
344
381
|
export async function runTransaction(
|
|
345
382
|
connection: TransportConnection,
|
|
@@ -366,27 +403,27 @@ export async function runTransaction(
|
|
|
366
403
|
records.push(record);
|
|
367
404
|
}
|
|
368
405
|
for (const [index, record] of records.entries()) {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
}
|
|
406
|
+
if (record.unreadable !== undefined) {
|
|
407
|
+
throw record.unreadable;
|
|
408
|
+
}
|
|
409
|
+
for (const { path, placeholder } of placeholderSites(record.args)) {
|
|
410
|
+
if (!positions.has(placeholder.source)) {
|
|
411
|
+
throw frameworkErrorOf("A2007", {
|
|
412
|
+
message: "Operation arguments failed framework validation.",
|
|
413
|
+
issues: [
|
|
414
|
+
{
|
|
415
|
+
path: ["operations", index, "args", ...path, "$ref"],
|
|
416
|
+
code: "V1010",
|
|
417
|
+
message: "Transaction reference names an operation that is not in this transaction.",
|
|
418
|
+
},
|
|
419
|
+
],
|
|
420
|
+
operation: index,
|
|
421
|
+
});
|
|
385
422
|
}
|
|
386
423
|
}
|
|
387
424
|
}
|
|
388
425
|
|
|
389
|
-
// Bound lazily
|
|
426
|
+
// Bound lazily: a node's references need their source's
|
|
390
427
|
// fingerprint, so each node is built once, on first need, wherever its source
|
|
391
428
|
// stands in the list — the server, not this client, refuses a later one.
|
|
392
429
|
const nodes = new Map<number, PlanNode>();
|
|
@@ -396,17 +433,14 @@ export async function runTransaction(
|
|
|
396
433
|
return built;
|
|
397
434
|
}
|
|
398
435
|
const record = records[index] as DeferredRecord;
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
}
|
|
408
|
-
return copy;
|
|
409
|
-
});
|
|
436
|
+
const references = new Map<object, unknown>();
|
|
437
|
+
for (const { object, placeholder } of placeholderSites(record.args)) {
|
|
438
|
+
const operation = positions.get(placeholder.source) as number;
|
|
439
|
+
references.set(object, { $ref: { operation, fingerprint: nodeAt(operation).fingerprint, path: [placeholder.field] } });
|
|
440
|
+
}
|
|
441
|
+
const bound = rebuild(record.args, [], (value) =>
|
|
442
|
+
typeof value === "object" && value !== null && references.has(value) ? references.get(value) : KEEP,
|
|
443
|
+
);
|
|
410
444
|
const args: unknown = JSON.parse(serializeWireBody(encodeArguments(bound)));
|
|
411
445
|
const node: PlanNode = {
|
|
412
446
|
resource: record.resource,
|
package/dist/generate.d.ts
CHANGED
|
@@ -1,23 +1,8 @@
|
|
|
1
1
|
import type { AbsolutePath } from "./config/client-config.interface.js";
|
|
2
2
|
import { type ProcessEnvInput } from "./config/env.cascade.js";
|
|
3
|
-
import type
|
|
3
|
+
import { type ContractFetch } from "./contract/contract.fetcher.js";
|
|
4
4
|
import { type EmittedFilePath } from "./emit/emitted-tree.interface.js";
|
|
5
5
|
import type { OutputCheck, TypeScriptResolver } from "./output/output.validator.js";
|
|
6
|
-
/**
|
|
7
|
-
* The cascade is a returned record, never written into `process.env`: the config
|
|
8
|
-
* reaches it through `env("NAME")`.
|
|
9
|
-
*
|
|
10
|
-
* Output goes to `generateAt`: `AvClient.ts` and `generated/`, nothing else there
|
|
11
|
-
* touched. Content in those two the generator did not produce is never overwritten
|
|
12
|
-
* unasked: the run returns `ForeignOutputContent` instead, and the caller decides.
|
|
13
|
-
*
|
|
14
|
-
* Every failure is thrown, every diagnostic that is not a failure is returned:
|
|
15
|
-
* this function prints nothing and asks nothing. `cli.ts` owns the terminal. A
|
|
16
|
-
* refusal raised after a warning (`OutputWriteError`) carries the warnings raised
|
|
17
|
-
* before it, and so does `ForeignOutputContent`; a defect is rethrown as itself,
|
|
18
|
-
* its warnings kept beside it (`warningsRaisedBeforeDefect`). No run's warnings
|
|
19
|
-
* are lost because it stopped.
|
|
20
|
-
*/
|
|
21
6
|
export interface ClientGenerationInput {
|
|
22
7
|
/** The project directory: where `framework.client.ts` and the `.env` files are. */
|
|
23
8
|
readonly directory: string;
|
|
@@ -30,14 +15,17 @@ export interface ClientGenerationInput {
|
|
|
30
15
|
* written.
|
|
31
16
|
*/
|
|
32
17
|
readonly overrideForeign?: boolean;
|
|
33
|
-
/** Injected for tests; the platform `fetch` otherwise. */
|
|
34
18
|
readonly fetch?: ContractFetch;
|
|
19
|
+
readonly resolveTypeScript?: TypeScriptResolver;
|
|
35
20
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
21
|
+
* Runs each of the run's four phases — the config, the contract, the
|
|
22
|
+
* emission, the write — under a title, so whoever owns the terminal can show
|
|
23
|
+
* them (`cli/step.reporter.ts`); each phase simply runs when omitted.
|
|
38
24
|
*/
|
|
39
|
-
readonly
|
|
25
|
+
readonly step?: GenerationStep;
|
|
40
26
|
}
|
|
27
|
+
/** Runs one phase of a generation under its title, resolving to what it returns. */
|
|
28
|
+
export type GenerationStep = <T>(title: string, body: () => Promise<T> | T) => Promise<T>;
|
|
41
29
|
export interface ClientGenerated {
|
|
42
30
|
readonly kind: "generated";
|
|
43
31
|
/** The shared directory the client was generated into. */
|
|
@@ -46,11 +34,6 @@ export interface ClientGenerated {
|
|
|
46
34
|
readonly files: readonly EmittedFilePath[];
|
|
47
35
|
/** How deeply the output was validated before it replaced the previous one. */
|
|
48
36
|
readonly checked: OutputCheck;
|
|
49
|
-
/**
|
|
50
|
-
* The deployment answered `304` — the ClientContract is the one the previous
|
|
51
|
-
* output was generated against — and the output was still replaced, because this
|
|
52
|
-
* generator or this entrypoint emits other bytes.
|
|
53
|
-
*/
|
|
54
37
|
readonly contractUnchanged: boolean;
|
|
55
38
|
/**
|
|
56
39
|
* Everything the run has to say without failing, in a fixed order: the
|
|
@@ -59,12 +42,6 @@ export interface ClientGenerated {
|
|
|
59
42
|
*/
|
|
60
43
|
readonly warnings: readonly string[];
|
|
61
44
|
}
|
|
62
|
-
/**
|
|
63
|
-
* The run found content in `AvClient.ts` or `generated/` the generator did not
|
|
64
|
-
* produce, and stopped before writing anything. Whoever owns the terminal asks;
|
|
65
|
-
* `proceed` writes the emission already made — no second fetch — overwriting or
|
|
66
|
-
* removing exactly `foreign`, through the same temp → validate → replace path.
|
|
67
|
-
*/
|
|
68
45
|
export interface ForeignOutputContent {
|
|
69
46
|
readonly kind: "foreign-content";
|
|
70
47
|
readonly generateAt: AbsolutePath;
|
|
@@ -82,11 +59,6 @@ export interface ForeignOutputContent {
|
|
|
82
59
|
readonly warnings: readonly string[];
|
|
83
60
|
proceed(): Promise<ClientGenerated>;
|
|
84
61
|
}
|
|
85
|
-
/**
|
|
86
|
-
* The deployment serves the ClientContract the output was generated against, and
|
|
87
|
-
* this generator, with this entrypoint, emits exactly the bytes already there:
|
|
88
|
-
* nothing was written.
|
|
89
|
-
*/
|
|
90
62
|
export interface ClientUpToDate {
|
|
91
63
|
readonly kind: "up-to-date";
|
|
92
64
|
readonly generateAt: AbsolutePath;
|
package/dist/generate.js
CHANGED
|
@@ -4,11 +4,13 @@ import { loadClientConfigFile } from "./config/config.loader.js";
|
|
|
4
4
|
import { resolveClientConfig } from "./config/config.resolver.js";
|
|
5
5
|
import { resolveEnvCascade, } from "./config/env.cascade.js";
|
|
6
6
|
import { resolveModuleStyle } from "./config/module-style.resolver.js";
|
|
7
|
+
import { contractUrlOf, displayUrl, } from "./contract/contract.fetcher.js";
|
|
7
8
|
import { loadClientContractSince } from "./contract/contract.loader.js";
|
|
8
9
|
import { emitClientTree } from "./emit/client-tree.emitter.js";
|
|
9
10
|
import { parseContractCarrier } from "./emit/contract-carrier.emitter.js";
|
|
10
11
|
import { CLIENT_ENTRY_FILE, CONTRACT_CARRIER_MODULE, GENERATED_DIRECTORY, } from "./emit/emitted-tree.interface.js";
|
|
11
12
|
import { findForeignOutputContent, OutputWriteError, ownedOutputMatches, precedeDefect, writeClientOutput, } from "./output/output.writer.js";
|
|
13
|
+
const runPhase = async (_title, body) => await body();
|
|
12
14
|
async function storedContract(generateAt) {
|
|
13
15
|
try {
|
|
14
16
|
if ((await findForeignOutputContent(generateAt)).length > 0) {
|
|
@@ -21,32 +23,42 @@ async function storedContract(generateAt) {
|
|
|
21
23
|
}
|
|
22
24
|
}
|
|
23
25
|
export async function generateClient(input) {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
26
|
+
const step = input.step ?? runPhase;
|
|
27
|
+
const { config, style } = await step("Loading the client config", async () => {
|
|
28
|
+
const cascade = resolveEnvCascade({
|
|
29
|
+
directory: input.directory,
|
|
30
|
+
processEnv: input.processEnv,
|
|
31
|
+
});
|
|
32
|
+
const loaded = await loadClientConfigFile(input.directory);
|
|
33
|
+
const config = resolveClientConfig({
|
|
34
|
+
config: loaded.config,
|
|
35
|
+
cascade,
|
|
36
|
+
configDirectory: path.dirname(loaded.file),
|
|
37
|
+
});
|
|
38
|
+
const style = resolveModuleStyle({
|
|
39
|
+
generateAt: config.generateAt,
|
|
40
|
+
entryFile: path.join(config.generateAt, CLIENT_ENTRY_FILE),
|
|
41
|
+
...(config.tsconfigFile === undefined
|
|
42
|
+
? {}
|
|
43
|
+
: { tsconfigFile: config.tsconfigFile }),
|
|
44
|
+
});
|
|
45
|
+
return { config, style };
|
|
40
46
|
});
|
|
41
|
-
const { contract, notModified } = await loadClientContractSince(input.fetch === undefined
|
|
47
|
+
const { contract, notModified } = await step(`Fetching the ClientContract (${displayUrl(contractUrlOf(config.entrypoint))})`, async () => loadClientContractSince(input.fetch === undefined
|
|
42
48
|
? { entrypoint: config.entrypoint }
|
|
43
|
-
: { entrypoint: config.entrypoint, fetch: input.fetch }, await storedContract(config.generateAt));
|
|
44
|
-
const emission =
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
: { entrypoint: config.entrypoint, fetch: input.fetch }, await storedContract(config.generateAt)));
|
|
50
|
+
const { emission, upToDate } = await step("Emitting the client", async () => {
|
|
51
|
+
const emission = emitClientTree(contract, config.entrypoint, {
|
|
52
|
+
importFileExtension: style.importFileExtension,
|
|
53
|
+
moduleFormat: style.moduleFormat,
|
|
54
|
+
});
|
|
55
|
+
return {
|
|
56
|
+
emission,
|
|
57
|
+
upToDate: notModified &&
|
|
58
|
+
(await ownedOutputMatches(config.generateAt, emission.tree)),
|
|
59
|
+
};
|
|
47
60
|
});
|
|
48
|
-
if (
|
|
49
|
-
(await ownedOutputMatches(config.generateAt, emission.tree))) {
|
|
61
|
+
if (upToDate) {
|
|
50
62
|
return {
|
|
51
63
|
kind: "up-to-date",
|
|
52
64
|
generateAt: config.generateAt,
|
|
@@ -54,7 +66,7 @@ export async function generateClient(input) {
|
|
|
54
66
|
warnings: emission.warnings,
|
|
55
67
|
};
|
|
56
68
|
}
|
|
57
|
-
const
|
|
69
|
+
const writeInto = async (overrideForeign) => {
|
|
58
70
|
const written = await writeClientOutput(input.resolveTypeScript === undefined
|
|
59
71
|
? { emission, generateAt: config.generateAt, overrideForeign }
|
|
60
72
|
: {
|
|
@@ -72,6 +84,8 @@ export async function generateClient(input) {
|
|
|
72
84
|
warnings: written.warnings,
|
|
73
85
|
};
|
|
74
86
|
};
|
|
87
|
+
const into = path.relative(input.directory, config.generateAt) || ".";
|
|
88
|
+
const write = (overrideForeign) => step(`Checking and writing the client into ${into}`, () => writeInto(overrideForeign));
|
|
75
89
|
const foreign = await findForeignOutputContent(config.generateAt).catch((error) => {
|
|
76
90
|
throw error instanceof OutputWriteError
|
|
77
91
|
? error.precededBy(emission.warnings)
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This package's version, read from its own manifest — the `package.json` every
|
|
3
|
-
* tarball carries beside `dist/` — so a release's `changeset version` is the one
|
|
4
|
-
* statement of it.
|
|
5
|
-
*/
|
|
1
|
+
/** The version of `@aventara/client`. */
|
|
6
2
|
export declare const AVENTARA_CLIENT_GENERATOR_VERSION: string;
|
|
7
3
|
export type { ClientConfigInput, ConfigValue, EnvReference, } from "./config/client-config.interface.js";
|
|
8
4
|
export { defineClientConfig, env } from "./config/config.resolver.js";
|
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `file` names it in its own comment line: the `framework.client.ts` it writes, or
|
|
3
|
-
* the project's own config of another extension, which init reuses rather than add
|
|
4
|
-
* a second (two configs make `avclient generate` refuse). A `.cjs` one is written
|
|
5
|
-
* in CommonJS — `require` and `module.exports` — the syntax its extension
|
|
6
|
-
* promises; every other one in ES module syntax, which the loader reads in any of
|
|
7
|
-
* them.
|
|
8
|
-
*/
|
|
9
1
|
export declare function clientConfigSource(input: {
|
|
10
2
|
readonly entrypoint: string;
|
|
11
3
|
readonly envVar: string | undefined;
|