@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.
Files changed (79) hide show
  1. package/README.md +61 -61
  2. package/dist/avclient.bin.js +0 -10
  3. package/dist/cli/command.parser.d.ts +21 -9
  4. package/dist/cli/command.parser.js +51 -12
  5. package/dist/cli/generate.command.js +0 -6
  6. package/dist/cli/generation-failure.renderer.js +0 -16
  7. package/dist/cli/generation-success.renderer.d.ts +4 -1
  8. package/dist/cli/generation-success.renderer.js +0 -13
  9. package/dist/cli/terminal.prompter.d.ts +1 -2
  10. package/dist/cli/warning.renderer.d.ts +2 -2
  11. package/dist/cli/warning.renderer.js +0 -8
  12. package/dist/cli.d.ts +8 -16
  13. package/dist/cli.js +11 -27
  14. package/dist/config/client-config.interface.d.ts +18 -16
  15. package/dist/config/client-config.interface.js +0 -13
  16. package/dist/config/config.loader.d.ts +33 -13
  17. package/dist/config/config.loader.js +52 -40
  18. package/dist/config/config.resolver.d.ts +13 -19
  19. package/dist/config/config.resolver.js +9 -48
  20. package/dist/config/env.cascade.d.ts +12 -14
  21. package/dist/config/env.cascade.js +0 -19
  22. package/dist/config/module-style.resolver.d.ts +52 -0
  23. package/dist/config/module-style.resolver.js +75 -0
  24. package/dist/config/tsconfig.locator.d.ts +45 -0
  25. package/dist/config/tsconfig.locator.js +52 -0
  26. package/dist/contract/contract.acceptance.d.ts +12 -26
  27. package/dist/contract/contract.acceptance.js +0 -54
  28. package/dist/contract/contract.fetcher.d.ts +12 -17
  29. package/dist/contract/contract.fetcher.js +0 -24
  30. package/dist/contract/contract.loader.d.ts +4 -5
  31. package/dist/contract/contract.loader.js +0 -10
  32. package/dist/emit/banner.emitter.d.ts +11 -12
  33. package/dist/emit/banner.emitter.js +0 -26
  34. package/dist/emit/client-surface.emitter.d.ts +17 -21
  35. package/dist/emit/client-surface.emitter.js +29 -55
  36. package/dist/emit/client-tree.emitter.d.ts +11 -20
  37. package/dist/emit/client-tree.emitter.js +12 -54
  38. package/dist/emit/contract-carrier.emitter.d.ts +5 -6
  39. package/dist/emit/contract-carrier.emitter.js +0 -28
  40. package/dist/emit/derivation.emitter.d.ts +7 -7
  41. package/dist/emit/derivation.emitter.js +2 -161
  42. package/dist/emit/descriptor.emitter.js +2 -28
  43. package/dist/emit/emitted-tree.interface.d.ts +40 -17
  44. package/dist/emit/emitted-tree.interface.js +6 -16
  45. package/dist/emit/enum.emitter.d.ts +4 -4
  46. package/dist/emit/enum.emitter.js +0 -24
  47. package/dist/emit/module-specifier.scanner.d.ts +25 -0
  48. package/dist/emit/module-specifier.scanner.js +160 -0
  49. package/dist/emit/module-style.interface.d.ts +58 -0
  50. package/dist/emit/module-style.interface.js +8 -0
  51. package/dist/emit/name.deriver.d.ts +33 -61
  52. package/dist/emit/name.deriver.js +0 -134
  53. package/dist/emit/named-type.emitter.d.ts +14 -21
  54. package/dist/emit/named-type.emitter.js +3 -30
  55. package/dist/emit/runtime.emitter.d.ts +23 -50
  56. package/dist/emit/runtime.emitter.js +68 -159
  57. package/dist/emit/scalar.codec.d.ts +20 -33
  58. package/dist/emit/scalar.codec.js +13 -69
  59. package/dist/emit/transaction.emitter.d.ts +6 -14
  60. package/dist/emit/transaction.emitter.js +24 -33
  61. package/dist/generate.d.ts +17 -34
  62. package/dist/generate.js +14 -22
  63. package/dist/index.js +0 -5
  64. package/dist/init/client-config.template.d.ts +9 -2
  65. package/dist/init/client-config.template.js +12 -10
  66. package/dist/init/client-init.errors.js +0 -3
  67. package/dist/init/client-init.orchestrator.js +7 -11
  68. package/dist/init/client-init.planner.d.ts +3 -10
  69. package/dist/init/client-init.planner.js +5 -12
  70. package/dist/init/client-init.questions.d.ts +8 -12
  71. package/dist/init/client-init.questions.js +0 -11
  72. package/dist/init/client-project.inspector.d.ts +6 -0
  73. package/dist/init/client-project.inspector.js +2 -2
  74. package/dist/node-version.guard.js +0 -12
  75. package/dist/output/output.validator.d.ts +28 -29
  76. package/dist/output/output.validator.js +68 -73
  77. package/dist/output/output.writer.d.ts +56 -52
  78. package/dist/output/output.writer.js +71 -133
  79. package/package.json +7 -5
@@ -1,15 +1,11 @@
1
1
  /**
2
- * The generator's configuration, in its two states (plan §7, group 1).
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
- * an `EnvReference`, a NAME to look up, so the file can be evaluated without the
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 (§15.2). */
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 (architect, 2026-10-04). It is SHARED: the generator owns only
27
- * `AvClient.ts` and `generated/` in it, and never touches anything else there.
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
- * A mount path in F-822's canonical form: `""` at the root, otherwise
38
- * `/seg(/seg)*` — one leading slash, no trailing slash, no empty segment. Root is
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 (F-822). Joining a route onto it is concatenation.
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: `framework.client.ts`, default-exporting
4
- * `defineClientConfig({ entrypoint, generateAt })`, exactly as §15.2 writes it. One
5
- * file name, looked up in one directory — no search up the tree, no alternative
6
- * spellings, no `package.json` key: the specification names one file, and a
7
- * second way to configure the generator would be a second source of the same
8
- * fact.
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
- * The file is TypeScript and is evaluated by Node itself (type stripping, on by
11
- * default in the Node this repository requires), so the generator needs no
12
- * compiler to read its own config. What it imports — `@aventara/client` for
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
- /** The config file §15.2 names. */
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
- * Evaluates `<directory>/framework.client.ts`.
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 the file is missing, does not
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 { existsSync } from "node:fs";
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
- * Finds and evaluates the client config: `framework.client.ts`, default-exporting
7
- * `defineClientConfig({ entrypoint, generateAt })`, exactly as §15.2 writes it. One
8
- * file name, looked up in one directory — no search up the tree, no alternative
9
- * spellings, no `package.json` key: the specification names one file, and a
10
- * second way to configure the generator would be a second source of the same
11
- * fact.
12
- *
13
- * The file is TypeScript and is evaluated by Node itself (type stripping, on by
14
- * default in the Node this repository requires), so the generator needs no
15
- * compiler to read its own config. What it imports — `@aventara/client` for
16
- * `defineClientConfig` and `env` — resolves from the consumer's project, as any
17
- * import of theirs would.
18
- *
19
- * The evaluated value is checked against `ClientConfigInput`'s shape before it
20
- * is trusted: the config is the consumer's code, and a typo there should be a
21
- * sentence naming the member, not a `TypeError` from deep inside resolution.
22
- */
23
- /** The config file §15.2 names. */
24
- export const CLIENT_CONFIG_FILE = "framework.client.ts";
25
- /**
26
- * Evaluates `<directory>/framework.client.ts`.
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 file = path.resolve(directory, CLIENT_CONFIG_FILE);
33
- if (!existsSync(file)) {
34
- throw new ClientConfigError(`no ${CLIENT_CONFIG_FILE} at ${file}; create it with ` +
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
- evaluated = (await import(pathToFileURL(file).href));
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
- /** The default export, checked member by member against `ClientConfigInput`. */
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 (§15.2). */
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 (§15.2). */
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 (F-822, architect's
27
- * decision 2026-10-04; Q15). The URL half is this function's: whitespace (which
28
- * the URL parser would trim or drop unseen), a value that is not an absolute
29
- * URL, a scheme other than http(s), credentials (Q5, architect 2026-10-05), a
30
- * query or fragment (even an empty one, which the parser forgets), and a `..`
31
- * segment (which the parser would resolve away) are refused here, reading the
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
- * The mount-path half is core's: the path AS WRITTEN — before `URL` has turned
35
- * a backslash into a slash, resolved a `.` segment away or percent-encoded a
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
- * emitted transport joins its routes to (§12.1), and the default a generated
47
- * client embeds (§15.2, Q5: `generated/metadata.ts`'s `DEFAULT_ENTRYPOINT`).
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
- * §15.2's `.env` cascade, resolved into a returned record.
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
- * passes alone and fails in sequence is exactly that leak.
16
- * 2. It reports an unreadable file (`EACCES`) as `ENOENT`. Catching `ENOENT`
17
- * from it would treat a `.env` the user cannot read as a `.env` that is not
18
- * there — a silent wrong answer.
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
- * parsed with `util.parseEnv` (the same parser `loadEnvFile` uses — pinned by a
24
- * parity test), checked for lines the parser would discard, and folded
25
- * first-write-wins over the candidates in priority order. Nothing global is
26
- * read or written: the process environment is an INPUT.
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;