@aventara/client 0.1.0-pilot.0 → 0.1.0-pilot.2
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/README.md +61 -61
- package/dist/avclient.bin.js +0 -10
- package/dist/cli/command.parser.d.ts +21 -9
- package/dist/cli/command.parser.js +51 -12
- package/dist/cli/generate.command.js +0 -6
- package/dist/cli/generation-failure.renderer.js +0 -16
- package/dist/cli/generation-success.renderer.d.ts +4 -1
- package/dist/cli/generation-success.renderer.js +0 -13
- package/dist/cli/terminal.prompter.d.ts +1 -2
- package/dist/cli/warning.renderer.d.ts +2 -2
- package/dist/cli/warning.renderer.js +0 -8
- package/dist/cli.d.ts +8 -16
- package/dist/cli.js +11 -27
- package/dist/config/client-config.interface.d.ts +18 -16
- package/dist/config/client-config.interface.js +0 -13
- package/dist/config/config.loader.d.ts +33 -13
- package/dist/config/config.loader.js +52 -40
- package/dist/config/config.resolver.d.ts +13 -19
- package/dist/config/config.resolver.js +9 -48
- package/dist/config/env.cascade.d.ts +12 -14
- package/dist/config/env.cascade.js +0 -19
- package/dist/config/module-style.resolver.d.ts +52 -0
- package/dist/config/module-style.resolver.js +75 -0
- package/dist/config/tsconfig.locator.d.ts +45 -0
- package/dist/config/tsconfig.locator.js +52 -0
- package/dist/contract/contract.acceptance.d.ts +12 -26
- package/dist/contract/contract.acceptance.js +0 -54
- package/dist/contract/contract.fetcher.d.ts +12 -17
- package/dist/contract/contract.fetcher.js +0 -24
- package/dist/contract/contract.loader.d.ts +4 -5
- package/dist/contract/contract.loader.js +0 -10
- package/dist/emit/banner.emitter.d.ts +11 -12
- package/dist/emit/banner.emitter.js +0 -26
- package/dist/emit/client-surface.emitter.d.ts +17 -21
- package/dist/emit/client-surface.emitter.js +29 -55
- package/dist/emit/client-tree.emitter.d.ts +11 -20
- package/dist/emit/client-tree.emitter.js +12 -54
- package/dist/emit/contract-carrier.emitter.d.ts +5 -6
- package/dist/emit/contract-carrier.emitter.js +0 -28
- package/dist/emit/derivation.emitter.d.ts +7 -7
- package/dist/emit/derivation.emitter.js +2 -161
- package/dist/emit/descriptor.emitter.js +2 -28
- package/dist/emit/emitted-tree.interface.d.ts +40 -17
- package/dist/emit/emitted-tree.interface.js +6 -16
- package/dist/emit/enum.emitter.d.ts +4 -4
- package/dist/emit/enum.emitter.js +0 -24
- package/dist/emit/module-specifier.scanner.d.ts +25 -0
- package/dist/emit/module-specifier.scanner.js +160 -0
- package/dist/emit/module-style.interface.d.ts +58 -0
- package/dist/emit/module-style.interface.js +8 -0
- package/dist/emit/name.deriver.d.ts +33 -61
- package/dist/emit/name.deriver.js +0 -134
- package/dist/emit/named-type.emitter.d.ts +14 -21
- package/dist/emit/named-type.emitter.js +3 -30
- package/dist/emit/runtime.emitter.d.ts +23 -50
- package/dist/emit/runtime.emitter.js +68 -159
- package/dist/emit/scalar.codec.d.ts +20 -33
- package/dist/emit/scalar.codec.js +13 -69
- package/dist/emit/transaction.emitter.d.ts +6 -14
- package/dist/emit/transaction.emitter.js +24 -33
- package/dist/generate.d.ts +17 -34
- package/dist/generate.js +14 -22
- package/dist/index.js +0 -5
- package/dist/init/client-config.template.d.ts +9 -2
- package/dist/init/client-config.template.js +12 -10
- package/dist/init/client-init.errors.js +0 -3
- package/dist/init/client-init.orchestrator.js +7 -11
- package/dist/init/client-init.planner.d.ts +3 -10
- package/dist/init/client-init.planner.js +5 -12
- package/dist/init/client-init.questions.d.ts +8 -12
- package/dist/init/client-init.questions.js +0 -11
- package/dist/init/client-project.inspector.d.ts +6 -0
- package/dist/init/client-project.inspector.js +2 -2
- package/dist/node-version.guard.js +0 -12
- package/dist/output/output.validator.d.ts +28 -29
- package/dist/output/output.validator.js +68 -73
- package/dist/output/output.writer.d.ts +56 -52
- package/dist/output/output.writer.js +71 -133
- package/package.json +7 -5
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The generator's configuration, in its two states
|
|
2
|
+
* The generator's configuration, in its two states.
|
|
3
3
|
*
|
|
4
4
|
* `ClientConfigInput` is what a `framework.client.ts` default-exports through
|
|
5
|
-
* `defineClientConfig`. It is passive data: an environment variable appears as
|
|
6
|
-
*
|
|
5
|
+
* `defineClientConfig`. It is passive data: an environment variable appears as an
|
|
6
|
+
* `EnvReference`, a NAME to look up, so the file can be evaluated without the
|
|
7
7
|
* environment having been loaded into the global and resolution stays a pure
|
|
8
8
|
* function of the input and an `EnvRecord`.
|
|
9
|
-
*
|
|
10
|
-
* `ResolvedClientConfig` is what later slices consume: the one §15.2 entrypoint
|
|
11
|
-
* split into the deployment it addresses and its canonical mount path,
|
|
12
|
-
* `generateAt` made absolute, and the mode the cascade resolved under.
|
|
13
9
|
*/
|
|
14
10
|
/** A deferred read of one environment variable from the resolved cascade. */
|
|
15
11
|
export interface EnvReference {
|
|
@@ -19,32 +15,36 @@ export interface EnvReference {
|
|
|
19
15
|
/** A value written literally, or read from the resolved environment. */
|
|
20
16
|
export type ConfigValue = string | EnvReference;
|
|
21
17
|
export interface ClientConfigInput {
|
|
22
|
-
/** The full deployed framework entrypoint: origin plus mount path
|
|
18
|
+
/** The full deployed framework entrypoint: origin plus mount path. */
|
|
23
19
|
readonly entrypoint: ConfigValue;
|
|
24
20
|
/**
|
|
25
21
|
* The directory the client is generated into, relative to the config file's
|
|
26
|
-
* directory
|
|
27
|
-
*
|
|
28
|
-
* Named `generateAt`, not §15.2's `output`, by the architect's decision.
|
|
22
|
+
* directory. It is SHARED: the generator owns only `AvClient.ts` and `generated/`
|
|
23
|
+
* in it, and never touches anything else there.
|
|
29
24
|
*/
|
|
30
25
|
readonly generateAt: ConfigValue;
|
|
26
|
+
/**
|
|
27
|
+
* The `tsconfig.json` the generated client is written for, relative to the
|
|
28
|
+
* config file's directory — only for a layout the discovery gets wrong (a
|
|
29
|
+
* monorepo, a non-standard name). Absent, the nearest `tsconfig.json` above
|
|
30
|
+
* `generateAt` (`tsconfig.locator.ts`).
|
|
31
|
+
*/
|
|
32
|
+
readonly tsconfigFile?: string;
|
|
31
33
|
}
|
|
32
34
|
/** A filesystem path known to be absolute. */
|
|
33
35
|
export type AbsolutePath = string & {
|
|
34
36
|
readonly __absolutePath: true;
|
|
35
37
|
};
|
|
36
38
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* the empty string, not `"/"`, so every route is `path + "/<route>"` with no
|
|
40
|
-
* special case. Only resolution makes one.
|
|
39
|
+
* Root is the empty string, not `"/"`, so every route is `path + "/<route>"` with
|
|
40
|
+
* no special case. Only resolution makes one.
|
|
41
41
|
*/
|
|
42
42
|
export type EntrypointPath = ("" | `/${string}`) & {
|
|
43
43
|
readonly __entrypointPath: true;
|
|
44
44
|
};
|
|
45
45
|
/**
|
|
46
46
|
* The deployed entrypoint, normalised once at resolution so every consumer reads
|
|
47
|
-
* one canonical form
|
|
47
|
+
* one canonical form. Joining a route onto it is concatenation.
|
|
48
48
|
*/
|
|
49
49
|
export interface ClientEntrypoint {
|
|
50
50
|
/**
|
|
@@ -58,5 +58,7 @@ export interface ResolvedClientConfig {
|
|
|
58
58
|
readonly entrypoint: ClientEntrypoint;
|
|
59
59
|
/** `generateAt`, made absolute. */
|
|
60
60
|
readonly generateAt: AbsolutePath;
|
|
61
|
+
/** `tsconfigFile`, made absolute; absent when the config names none. */
|
|
62
|
+
readonly tsconfigFile?: AbsolutePath;
|
|
61
63
|
readonly mode: string;
|
|
62
64
|
}
|
|
@@ -1,14 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The generator's configuration, in its two states (plan §7, group 1).
|
|
3
|
-
*
|
|
4
|
-
* `ClientConfigInput` is what a `framework.client.ts` default-exports through
|
|
5
|
-
* `defineClientConfig`. It is passive data: an environment variable appears as
|
|
6
|
-
* an `EnvReference`, a NAME to look up, so the file can be evaluated without the
|
|
7
|
-
* environment having been loaded into the global and resolution stays a pure
|
|
8
|
-
* function of the input and an `EnvRecord`.
|
|
9
|
-
*
|
|
10
|
-
* `ResolvedClientConfig` is what later slices consume: the one §15.2 entrypoint
|
|
11
|
-
* split into the deployment it addresses and its canonical mount path,
|
|
12
|
-
* `generateAt` made absolute, and the mode the cascade resolved under.
|
|
13
|
-
*/
|
|
14
1
|
export {};
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import type { ClientConfigInput } from "./client-config.interface.js";
|
|
2
2
|
/**
|
|
3
|
-
* Finds and evaluates the client config
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
3
|
+
* Finds and evaluates the client config, `framework.client.<ext>`, the way
|
|
4
|
+
* Prisma 7 loads `prisma.config.ts` (`@prisma/config` 7.10's
|
|
5
|
+
* `loadConfigFromFile`): through `c12`, which evaluates it with `jiti`
|
|
6
|
+
* (`interopDefault`, no module cache), with no `.env` loading, no rc file, no
|
|
7
|
+
* remote or `extends` layers and no `package.json` key. So any of
|
|
8
|
+
* {@link CLIENT_CONFIG_FILE_EXTENSIONS} — Prisma's own list — in ES module or
|
|
9
|
+
* CommonJS syntax, whatever the project's `"type"`, TypeScript included, with
|
|
10
|
+
* no compiler of the project's needed (pilot.1's `.mts` rule is gone: jiti, not
|
|
11
|
+
* Node, decides how the file is read).
|
|
9
12
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
+
* One file, looked up in one directory — no search up the tree — and when more
|
|
14
|
+
* than one is present the run refuses rather than choose: two files would be
|
|
15
|
+
* two sources of the same fact. What the file imports — `@aventara/client` for
|
|
13
16
|
* `defineClientConfig` and `env` — resolves from the consumer's project, as any
|
|
14
17
|
* import of theirs would.
|
|
15
18
|
*
|
|
@@ -17,7 +20,11 @@ import type { ClientConfigInput } from "./client-config.interface.js";
|
|
|
17
20
|
* is trusted: the config is the consumer's code, and a typo there should be a
|
|
18
21
|
* sentence naming the member, not a `TypeError` from deep inside resolution.
|
|
19
22
|
*/
|
|
20
|
-
/**
|
|
23
|
+
/** Prisma's `SUPPORTED_EXTENSIONS`, in its order. */
|
|
24
|
+
export declare const CLIENT_CONFIG_FILE_EXTENSIONS: readonly [".js", ".ts", ".mjs", ".cjs", ".mts", ".cts"];
|
|
25
|
+
/** The config's name, before its extension. */
|
|
26
|
+
export declare const CLIENT_CONFIG_BASENAME = "framework.client";
|
|
27
|
+
/** The config file `avclient init` writes when the project has none. */
|
|
21
28
|
export declare const CLIENT_CONFIG_FILE = "framework.client.ts";
|
|
22
29
|
export interface LoadedClientConfig {
|
|
23
30
|
/** The config file's absolute path; `generateAt` resolves against its directory. */
|
|
@@ -25,9 +32,22 @@ export interface LoadedClientConfig {
|
|
|
25
32
|
readonly config: ClientConfigInput;
|
|
26
33
|
}
|
|
27
34
|
/**
|
|
28
|
-
*
|
|
35
|
+
* The config files present in `directory`, by name, in
|
|
36
|
+
* {@link CLIENT_CONFIG_FILE_EXTENSIONS} order: none, one, or — refused by
|
|
37
|
+
* {@link findClientConfigFile} — more.
|
|
38
|
+
*/
|
|
39
|
+
export declare function clientConfigFilesIn(directory: string): readonly string[];
|
|
40
|
+
/**
|
|
41
|
+
* The one config file in `directory`, by name, or `undefined` when there is
|
|
42
|
+
* none.
|
|
43
|
+
*
|
|
44
|
+
* @throws ClientConfigError when there is more than one, naming each.
|
|
45
|
+
*/
|
|
46
|
+
export declare function findClientConfigFile(directory: string): string | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Evaluates the one `framework.client.<ext>` in `directory`.
|
|
29
49
|
*
|
|
30
|
-
* @throws ClientConfigError, in one line, when
|
|
31
|
-
* evaluate, or does not default-export a client config.
|
|
50
|
+
* @throws ClientConfigError, in one line, when there is none or more than one,
|
|
51
|
+
* or the file does not evaluate, or does not default-export a client config.
|
|
32
52
|
*/
|
|
33
53
|
export declare function loadClientConfigFile(directory: string): Promise<LoadedClientConfig>;
|
|
@@ -1,62 +1,75 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { statSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { pathToFileURL } from "node:url";
|
|
4
3
|
import { ClientConfigError } from "./config.resolver.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
* @throws ClientConfigError, in one line, when the file is missing, does not
|
|
29
|
-
* evaluate, or does not default-export a client config.
|
|
30
|
-
*/
|
|
4
|
+
export const CLIENT_CONFIG_FILE_EXTENSIONS = [
|
|
5
|
+
".js",
|
|
6
|
+
".ts",
|
|
7
|
+
".mjs",
|
|
8
|
+
".cjs",
|
|
9
|
+
".mts",
|
|
10
|
+
".cts",
|
|
11
|
+
];
|
|
12
|
+
export const CLIENT_CONFIG_BASENAME = "framework.client";
|
|
13
|
+
export const CLIENT_CONFIG_FILE = `${CLIENT_CONFIG_BASENAME}.ts`;
|
|
14
|
+
export function clientConfigFilesIn(directory) {
|
|
15
|
+
return CLIENT_CONFIG_FILE_EXTENSIONS.map((extension) => `${CLIENT_CONFIG_BASENAME}${extension}`).filter((name) => statSync(path.resolve(directory, name), {
|
|
16
|
+
throwIfNoEntry: false,
|
|
17
|
+
})?.isFile() === true);
|
|
18
|
+
}
|
|
19
|
+
export function findClientConfigFile(directory) {
|
|
20
|
+
const present = clientConfigFilesIn(directory);
|
|
21
|
+
if (present.length > 1) {
|
|
22
|
+
const named = `${present.slice(0, -1).join(", ")} and ${present.at(-1)}`;
|
|
23
|
+
throw new ClientConfigError(`${named} are ${present.length === 2 ? "both" : "all"} in ${path.resolve(directory)}, and only one may configure the generator; keep one and delete the other${present.length > 2 ? "s" : ""}.`);
|
|
24
|
+
}
|
|
25
|
+
return present[0];
|
|
26
|
+
}
|
|
31
27
|
export async function loadClientConfigFile(directory) {
|
|
32
|
-
const
|
|
33
|
-
if (
|
|
34
|
-
throw new ClientConfigError(`no ${
|
|
35
|
-
"`export default defineClientConfig({ entrypoint, generateAt })` and run the generator from its directory.");
|
|
28
|
+
const name = findClientConfigFile(directory);
|
|
29
|
+
if (name === undefined) {
|
|
30
|
+
throw new ClientConfigError(`no ${CLIENT_CONFIG_BASENAME}.{${CLIENT_CONFIG_FILE_EXTENSIONS.map((extension) => extension.slice(1)).join(",")}} in ${path.resolve(directory)}; create ${CLIENT_CONFIG_FILE} with ` +
|
|
31
|
+
"`export default defineClientConfig({ entrypoint, generateAt })` (or run `avclient init`) and run the generator from its directory.");
|
|
36
32
|
}
|
|
33
|
+
const file = path.resolve(directory, name);
|
|
37
34
|
let evaluated;
|
|
38
35
|
try {
|
|
39
|
-
|
|
36
|
+
const { loadConfig } = await import("c12");
|
|
37
|
+
const loaded = await loadConfig({
|
|
38
|
+
cwd: path.dirname(file),
|
|
39
|
+
name: CLIENT_CONFIG_BASENAME,
|
|
40
|
+
configFile: file,
|
|
41
|
+
dotenv: false,
|
|
42
|
+
rcFile: false,
|
|
43
|
+
giget: false,
|
|
44
|
+
extend: false,
|
|
45
|
+
packageJson: false,
|
|
46
|
+
jitiOptions: {
|
|
47
|
+
interopDefault: true,
|
|
48
|
+
moduleCache: false,
|
|
49
|
+
extensions: [...CLIENT_CONFIG_FILE_EXTENSIONS],
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
evaluated = loaded.layers?.find((layer) => layer.configFile === file)?.config;
|
|
40
53
|
}
|
|
41
54
|
catch (error) {
|
|
42
55
|
throw new ClientConfigError(`${file} could not be evaluated: ${firstLineOf(error)}`, { cause: error });
|
|
43
56
|
}
|
|
44
57
|
return { file, config: clientConfigOf(file, evaluated) };
|
|
45
58
|
}
|
|
46
|
-
|
|
47
|
-
function clientConfigOf(file, evaluated) {
|
|
59
|
+
function clientConfigOf(file, config) {
|
|
48
60
|
const shape = "`export default defineClientConfig({ entrypoint, generateAt })`";
|
|
49
|
-
if (!Object.hasOwn(evaluated, "default")) {
|
|
50
|
-
throw new ClientConfigError(`${file} has no default export; it must be ${shape}.`);
|
|
51
|
-
}
|
|
52
|
-
const config = evaluated.default;
|
|
53
61
|
if (typeof config !== "object" || config === null || Array.isArray(config)) {
|
|
54
62
|
throw new ClientConfigError(`${file}'s default export is not an object; it must be ${shape}.`);
|
|
55
63
|
}
|
|
56
64
|
const members = config;
|
|
65
|
+
const tsconfigFile = members.tsconfigFile;
|
|
66
|
+
if (tsconfigFile !== undefined && typeof tsconfigFile !== "string") {
|
|
67
|
+
throw new ClientConfigError(`${file}'s tsconfigFile must be a string, a path relative to ${path.dirname(file)}.`);
|
|
68
|
+
}
|
|
57
69
|
return {
|
|
58
70
|
entrypoint: configValueOf(file, "entrypoint", members.entrypoint),
|
|
59
71
|
generateAt: configValueOf(file, "generateAt", members.generateAt),
|
|
72
|
+
...(tsconfigFile === undefined ? {} : { tsconfigFile }),
|
|
60
73
|
};
|
|
61
74
|
}
|
|
62
75
|
function configValueOf(file, member, value) {
|
|
@@ -73,7 +86,6 @@ function configValueOf(file, member, value) {
|
|
|
73
86
|
}
|
|
74
87
|
throw new ClientConfigError(`${file}'s ${member} must be a string or env("NAME"), and it is ${value === undefined ? "missing" : "neither"}.`);
|
|
75
88
|
}
|
|
76
|
-
/** An error's message, cut at its first line break: the refusal is one line. */
|
|
77
89
|
function firstLineOf(error) {
|
|
78
90
|
const message = error instanceof Error ? error.message : String(error);
|
|
79
91
|
return message.split("\n", 1)[0] ?? "";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ClientConfigInput, ClientEntrypoint, EnvReference, ResolvedClientConfig } from "./client-config.interface.js";
|
|
2
2
|
import type { EnvCascadeResolution } from "./env.cascade.js";
|
|
3
|
-
/** The identity function that gives a `framework.client.ts` its type
|
|
3
|
+
/** The identity function that gives a `framework.client.ts` its type. */
|
|
4
4
|
export declare function defineClientConfig(config: ClientConfigInput): ClientConfigInput;
|
|
5
|
-
/** Names an environment variable to be read from the resolved cascade
|
|
5
|
+
/** Names an environment variable to be read from the resolved cascade. */
|
|
6
6
|
export declare function env(name: string): EnvReference;
|
|
7
7
|
/**
|
|
8
8
|
* The configuration cannot be resolved. The message is the whole diagnosis — the
|
|
@@ -23,28 +23,22 @@ export interface ClientConfigResolutionInput {
|
|
|
23
23
|
*/
|
|
24
24
|
export declare function resolveClientConfig(input: ClientConfigResolutionInput): ResolvedClientConfig;
|
|
25
25
|
/**
|
|
26
|
-
* An entrypoint value resolved to its canonical form
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* value as written.
|
|
26
|
+
* An entrypoint value resolved to its canonical form. The URL half is this
|
|
27
|
+
* function's: whitespace (which the URL parser would trim or drop unseen), a value
|
|
28
|
+
* that is not an absolute URL, a scheme other than http(s), credentials, a query
|
|
29
|
+
* or fragment (even an empty one, which the parser forgets), and a `..` segment
|
|
30
|
+
* (which the parser would resolve away) are refused here, reading the value as
|
|
31
|
+
* written.
|
|
33
32
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* character — goes to `AvProtocol.normalizeEntrypoint`, the rule the server's
|
|
37
|
-
* config resolution applies, so the two halves of F-822 cannot disagree
|
|
38
|
-
* (`entrypoint.parity.spec.ts`). Only its slash spelling is coerced — `api`,
|
|
39
|
-
* `/api/` and `//api//` are one intent, `/api` — and root is `""`.
|
|
33
|
+
* Only its slash spelling is coerced — `api`, `/api/` and `//api//` are one
|
|
34
|
+
* intent, `/api` — and root is `""`.
|
|
40
35
|
*
|
|
41
36
|
* @throws ClientConfigError in one sentence that does not echo the value.
|
|
42
37
|
*/
|
|
43
38
|
export declare function resolveEntrypoint(value: string): ClientEntrypoint;
|
|
44
39
|
/**
|
|
45
|
-
* The entrypoint as one absolute URL with no trailing slash — the form the
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* Root is the bare origin.
|
|
40
|
+
* The entrypoint as one absolute URL with no trailing slash — the form the emitted
|
|
41
|
+
* transport joins its routes to, and the default a generated client embeds. Root
|
|
42
|
+
* is the bare origin.
|
|
49
43
|
*/
|
|
50
44
|
export declare function entrypointHref(entrypoint: ClientEntrypoint): string;
|
|
@@ -1,30 +1,29 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { AvProtocol } from "@aventara/core/protocol";
|
|
3
|
-
/** The identity function that gives a `framework.client.ts` its type (§15.2). */
|
|
4
3
|
export function defineClientConfig(config) {
|
|
5
4
|
return config;
|
|
6
5
|
}
|
|
7
|
-
/** Names an environment variable to be read from the resolved cascade (§15.2). */
|
|
8
6
|
export function env(name) {
|
|
9
7
|
return { kind: "env", name };
|
|
10
8
|
}
|
|
11
|
-
/**
|
|
12
|
-
* The configuration cannot be resolved. The message is the whole diagnosis — the
|
|
13
|
-
* CLI prints it and exits non-zero; no stack is owed for a user's mistake.
|
|
14
|
-
*/
|
|
15
9
|
export class ClientConfigError extends Error {
|
|
16
10
|
name = "ClientConfigError";
|
|
17
11
|
}
|
|
18
|
-
/**
|
|
19
|
-
* Resolves a config against an already-resolved cascade. Pure: it reads no
|
|
20
|
-
* file and no global, so it is tested against literal inputs.
|
|
21
|
-
*/
|
|
22
12
|
export function resolveClientConfig(input) {
|
|
23
13
|
const entrypoint = resolveValue("entrypoint", input.config.entrypoint, input.cascade);
|
|
24
14
|
const generateAt = resolveValue("generateAt", input.config.generateAt, input.cascade);
|
|
15
|
+
const tsconfigFile = input.config.tsconfigFile;
|
|
16
|
+
if (tsconfigFile === "") {
|
|
17
|
+
throw new ClientConfigError("tsconfigFile is empty.");
|
|
18
|
+
}
|
|
25
19
|
return {
|
|
26
20
|
entrypoint: resolveEntrypoint(entrypoint),
|
|
27
21
|
generateAt: path.resolve(input.configDirectory, generateAt),
|
|
22
|
+
...(tsconfigFile === undefined
|
|
23
|
+
? {}
|
|
24
|
+
: {
|
|
25
|
+
tsconfigFile: path.resolve(input.configDirectory, tsconfigFile),
|
|
26
|
+
}),
|
|
28
27
|
mode: input.cascade.mode,
|
|
29
28
|
};
|
|
30
29
|
}
|
|
@@ -41,29 +40,7 @@ function resolveValue(field, value, cascade) {
|
|
|
41
40
|
}
|
|
42
41
|
return resolved;
|
|
43
42
|
}
|
|
44
|
-
/**
|
|
45
|
-
* The refusal's closing clause: what an entrypoint is. Never the value itself —
|
|
46
|
-
* an entrypoint may carry credentials.
|
|
47
|
-
*/
|
|
48
43
|
const ENTRYPOINT_IS = "it must be the origin plus a mount path";
|
|
49
|
-
/**
|
|
50
|
-
* An entrypoint value resolved to its canonical form (F-822, architect's
|
|
51
|
-
* decision 2026-10-04; Q15). The URL half is this function's: whitespace (which
|
|
52
|
-
* the URL parser would trim or drop unseen), a value that is not an absolute
|
|
53
|
-
* URL, a scheme other than http(s), credentials (Q5, architect 2026-10-05), a
|
|
54
|
-
* query or fragment (even an empty one, which the parser forgets), and a `..`
|
|
55
|
-
* segment (which the parser would resolve away) are refused here, reading the
|
|
56
|
-
* value as written.
|
|
57
|
-
*
|
|
58
|
-
* The mount-path half is core's: the path AS WRITTEN — before `URL` has turned
|
|
59
|
-
* a backslash into a slash, resolved a `.` segment away or percent-encoded a
|
|
60
|
-
* character — goes to `AvProtocol.normalizeEntrypoint`, the rule the server's
|
|
61
|
-
* config resolution applies, so the two halves of F-822 cannot disagree
|
|
62
|
-
* (`entrypoint.parity.spec.ts`). Only its slash spelling is coerced — `api`,
|
|
63
|
-
* `/api/` and `//api//` are one intent, `/api` — and root is `""`.
|
|
64
|
-
*
|
|
65
|
-
* @throws ClientConfigError in one sentence that does not echo the value.
|
|
66
|
-
*/
|
|
67
44
|
export function resolveEntrypoint(value) {
|
|
68
45
|
if (/\s/u.test(value)) {
|
|
69
46
|
throw new ClientConfigError(`entrypoint contains whitespace; ${ENTRYPOINT_IS}, written without spaces, tabs or line breaks.`);
|
|
@@ -86,8 +63,6 @@ export function resolveEntrypoint(value) {
|
|
|
86
63
|
if (value.includes("?") || value.includes("#")) {
|
|
87
64
|
throw new ClientConfigError("entrypoint must not carry a query or fragment; it is the origin plus mount path only.");
|
|
88
65
|
}
|
|
89
|
-
// Over the whole URL, authority included — wider than the mount-path rule,
|
|
90
|
-
// which refuses a `..` too — so a `..` anywhere keeps its shipped message.
|
|
91
66
|
if (value.split(/[/\\]/u).some(isDotDotSegment)) {
|
|
92
67
|
throw new ClientConfigError(`entrypoint's mount path has a .. segment; ${ENTRYPOINT_IS}, with no dot segments.`);
|
|
93
68
|
}
|
|
@@ -99,28 +74,14 @@ export function resolveEntrypoint(value) {
|
|
|
99
74
|
deployment.pathname = "/";
|
|
100
75
|
return { deployment, path: normalized.path };
|
|
101
76
|
}
|
|
102
|
-
/**
|
|
103
|
-
* The mount path of an http(s) URL exactly as written: everything after the
|
|
104
|
-
* scheme, the slashes that follow it, and the authority. The authority ends
|
|
105
|
-
* where WHATWG URL parsing ends it for a special scheme — at the first `/`,
|
|
106
|
-
* `\`, `?` or `#` — so this is the text the parser turns into `pathname`,
|
|
107
|
-
* before it does.
|
|
108
|
-
*/
|
|
109
77
|
function writtenMountPath(value) {
|
|
110
78
|
return value.replace(/^[A-Za-z][A-Za-z0-9+.-]*:[/\\]*[^/\\?#]*/u, "");
|
|
111
79
|
}
|
|
112
|
-
/**
|
|
113
|
-
* The entrypoint as one absolute URL with no trailing slash — the form the
|
|
114
|
-
* emitted transport joins its routes to (§12.1), and the default a generated
|
|
115
|
-
* client embeds (§15.2, Q5: `generated/metadata.ts`'s `DEFAULT_ENTRYPOINT`).
|
|
116
|
-
* Root is the bare origin.
|
|
117
|
-
*/
|
|
118
80
|
export function entrypointHref(entrypoint) {
|
|
119
81
|
const url = new URL(entrypoint.deployment.href);
|
|
120
82
|
url.pathname = entrypoint.path === "" ? "/" : entrypoint.path;
|
|
121
83
|
return url.href.replace(/\/$/, "");
|
|
122
84
|
}
|
|
123
|
-
/** `..`, spelled as WHATWG URL resolution recognises it: `.` or `%2e`, any case. */
|
|
124
85
|
function isDotDotSegment(segment) {
|
|
125
86
|
return /^(?:\.|%2e){2}$/iu.test(segment);
|
|
126
87
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Precedence, highest first: the existing process environment
|
|
5
|
-
* > `.env.<mode>.local` > `.env.<mode>` > `.env.local` > `.env`.
|
|
2
|
+
* Precedence, highest first: the existing process environment >
|
|
3
|
+
* `.env.<mode>.local` > `.env.<mode>` > `.env.local` > `.env`.
|
|
6
4
|
*
|
|
7
5
|
* # Why a returned record, and not `process.loadEnvFile`
|
|
8
6
|
*
|
|
@@ -11,19 +9,19 @@
|
|
|
11
9
|
* mechanism here, for three reasons that were measured rather than argued:
|
|
12
10
|
*
|
|
13
11
|
* 1. It writes into the live `process.env`. A resolution that mutates the global
|
|
14
|
-
* leaks across tests and across invocations in one process; a suite that
|
|
15
|
-
*
|
|
16
|
-
* 2. It reports an unreadable file (`EACCES`) as `ENOENT`. Catching `ENOENT`
|
|
17
|
-
*
|
|
18
|
-
*
|
|
12
|
+
* leaks across tests and across invocations in one process; a suite that passes
|
|
13
|
+
* alone and fails in sequence is exactly that leak.
|
|
14
|
+
* 2. It reports an unreadable file (`EACCES`) as `ENOENT`. Catching `ENOENT` from
|
|
15
|
+
* it would treat a `.env` the user cannot read as a `.env` that is not there —
|
|
16
|
+
* a silent wrong answer.
|
|
19
17
|
* 3. It silently drops lines that are not assignments. So does `util.parseEnv`;
|
|
20
18
|
* neither has a notion of a malformed file.
|
|
21
19
|
*
|
|
22
|
-
* So each file is read with `readFileSync` (whose error code is the truth),
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
20
|
+
* So each file is read with `readFileSync` (whose error code is the truth), parsed
|
|
21
|
+
* with `util.parseEnv` (the same parser `loadEnvFile` uses — pinned by a parity
|
|
22
|
+
* test), checked for lines the parser would discard, and folded first-write-wins
|
|
23
|
+
* over the candidates in priority order. Nothing global is read or written: the
|
|
24
|
+
* process environment is an INPUT.
|
|
27
25
|
*/
|
|
28
26
|
/** A resolved environment: names to values, never `undefined`. */
|
|
29
27
|
export type EnvRecord = Readonly<Record<string, string>>;
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { parseEnv } from "node:util";
|
|
4
|
-
/** The mode used when neither an explicit mode nor `NODE_ENV` supplies one. */
|
|
5
4
|
export const DEFAULT_ENV_MODE = "development";
|
|
6
|
-
/** A candidate exists but could not be read, or contains a discarded line. */
|
|
7
5
|
export class EnvFileError extends Error {
|
|
8
6
|
filePath;
|
|
9
7
|
reason;
|
|
@@ -14,7 +12,6 @@ export class EnvFileError extends Error {
|
|
|
14
12
|
this.reason = reason;
|
|
15
13
|
}
|
|
16
14
|
}
|
|
17
|
-
/** The four file candidates for a mode, highest precedence first. */
|
|
18
15
|
export function envCascadeCandidates(mode) {
|
|
19
16
|
return [`.env.${mode}.local`, `.env.${mode}`, ".env.local", ".env"];
|
|
20
17
|
}
|
|
@@ -34,8 +31,6 @@ export function resolveEnvCascade(input) {
|
|
|
34
31
|
env[name] = value;
|
|
35
32
|
}
|
|
36
33
|
const loaded = [];
|
|
37
|
-
// Highest precedence first: under first-write-wins, the first source to
|
|
38
|
-
// define a name owns it. Reversing this loop inverts the precedence.
|
|
39
34
|
for (const candidate of candidates) {
|
|
40
35
|
const filePath = path.join(input.directory, candidate);
|
|
41
36
|
const content = read(filePath);
|
|
@@ -49,7 +44,6 @@ export function resolveEnvCascade(input) {
|
|
|
49
44
|
}
|
|
50
45
|
return { mode, env: Object.freeze(env), candidates, loaded };
|
|
51
46
|
}
|
|
52
|
-
/** Reads a candidate from disk; `ENOENT` alone means "not there". */
|
|
53
47
|
export function readEnvFileFromDisk(filePath) {
|
|
54
48
|
try {
|
|
55
49
|
return readFileSync(filePath, "utf8");
|
|
@@ -61,12 +55,6 @@ export function readEnvFileFromDisk(filePath) {
|
|
|
61
55
|
throw new EnvFileError(filePath, "unreadable", `exists but cannot be read (${code ?? "unknown error"})`);
|
|
62
56
|
}
|
|
63
57
|
}
|
|
64
|
-
/**
|
|
65
|
-
* Parses one file's content. A file whose content holds a line the parser would
|
|
66
|
-
* silently discard is malformed: the user wrote something they expect to take
|
|
67
|
-
* effect, and it would not. The error names the line number, never its content
|
|
68
|
-
* — a `.env` line is as likely as not to be a secret.
|
|
69
|
-
*/
|
|
70
58
|
export function parseEnvFile(filePath, content) {
|
|
71
59
|
const source = content.startsWith("") ? content.slice(1) : content;
|
|
72
60
|
const discarded = firstDiscardedLine(source);
|
|
@@ -76,13 +64,6 @@ export function parseEnvFile(filePath, content) {
|
|
|
76
64
|
return parseEnv(source);
|
|
77
65
|
}
|
|
78
66
|
const QUOTES = new Set(['"', "'", "`"]);
|
|
79
|
-
/**
|
|
80
|
-
* The 1-based number of the first line `util.parseEnv` would drop, or
|
|
81
|
-
* `undefined`. Mirrors the parser's line model: blank and `#` lines are
|
|
82
|
-
* skipped, an optional `export ` prefix is allowed, and a value opening with a
|
|
83
|
-
* quote runs to the next matching quote anywhere later in the content —
|
|
84
|
-
* spanning lines — or, when there is none, to the end of its own line.
|
|
85
|
-
*/
|
|
86
67
|
export function firstDiscardedLine(content) {
|
|
87
68
|
let offset = 0;
|
|
88
69
|
while (offset < content.length) {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { TsConfigJsonResolved } from "get-tsconfig";
|
|
2
|
+
import type { ClientModuleStyle, ImportFileExtension, ModuleFormat } from "../emit/module-style.interface.js";
|
|
3
|
+
import { type LocatedTsconfig } from "./tsconfig.locator.js";
|
|
4
|
+
/**
|
|
5
|
+
* How the generated client is spelled for the project that compiles it: inferred
|
|
6
|
+
* from the project's `tsconfig.json` (`tsconfig.locator.ts`) and, under
|
|
7
|
+
* `node16`/`nodenext`, the nearest `package.json`, by Prisma 7.10's
|
|
8
|
+
* `prisma-client` rules, mirrored from its CLI with the generated extension fixed
|
|
9
|
+
* at `ts`:
|
|
10
|
+
*
|
|
11
|
+
* importFileExtension
|
|
12
|
+
* 1. `allowImportingTsExtensions` or `rewriteRelativeImportExtensions` → `ts`;
|
|
13
|
+
* 2. `module` is `commonjs`, or `moduleResolution` is `bundler` (either case) →
|
|
14
|
+
* `""`;
|
|
15
|
+
* 3. otherwise → `js` (Prisma's `ts → js`).
|
|
16
|
+
*
|
|
17
|
+
* moduleFormat
|
|
18
|
+
* 1. `module` is `commonjs` → `cjs`;
|
|
19
|
+
* 2. `module` is `node16` or `nodenext` → the nearest `package.json` above
|
|
20
|
+
* `generateAt`: `"type": "module"` → `esm`; no `package.json`, an unparseable
|
|
21
|
+
* one, or any other `type` → `cjs`;
|
|
22
|
+
* 3. any other `module` → `esm`; no `module` → `esm` (Prisma's fallback, whose
|
|
23
|
+
* `cjs` arm needs a generated `.cts`).
|
|
24
|
+
*
|
|
25
|
+
* Prisma falls back to the generated extension when there is no tsconfig; this
|
|
26
|
+
* generator refuses instead: its client is TypeScript, and a project without a
|
|
27
|
+
* tsconfig is a JavaScript project, not supported yet.
|
|
28
|
+
*/
|
|
29
|
+
/** What the run follows, and where it read it from. */
|
|
30
|
+
export interface ResolvedModuleStyle extends ClientModuleStyle {
|
|
31
|
+
/** The tsconfig the style was inferred from. */
|
|
32
|
+
readonly tsconfig: string;
|
|
33
|
+
}
|
|
34
|
+
export interface ModuleStyleInput {
|
|
35
|
+
/** The absolute `generateAt`. */
|
|
36
|
+
readonly generateAt: string;
|
|
37
|
+
/** The generated entry file, absolute: the file the project must include. */
|
|
38
|
+
readonly entryFile: string;
|
|
39
|
+
/** The config's `tsconfigFile`, already absolute. */
|
|
40
|
+
readonly tsconfigFile?: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The style the project's tsconfig asks for.
|
|
44
|
+
*
|
|
45
|
+
* @throws ClientConfigError when there is no tsconfig to follow, or the named
|
|
46
|
+
* `tsconfigFile` cannot be read.
|
|
47
|
+
*/
|
|
48
|
+
export declare function resolveModuleStyle(input: ModuleStyleInput): ResolvedModuleStyle;
|
|
49
|
+
/** The refusal of a project with no tsconfig: a JavaScript project. */
|
|
50
|
+
export declare function noTsconfigRefusal(generateAt: string): string;
|
|
51
|
+
export declare function importFileExtensionOf(config: TsConfigJsonResolved): ImportFileExtension;
|
|
52
|
+
export declare function moduleFormatOf(tsconfig: LocatedTsconfig, generateAt: string): ModuleFormat;
|