@aventara/client 0.1.0-pilot.1 → 0.1.0-pilot.3

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 +44 -9
  2. package/dist/avclient.bin.js +0 -10
  3. package/dist/cli/command.parser.d.ts +15 -10
  4. package/dist/cli/command.parser.js +13 -19
  5. package/dist/cli/generate.command.js +0 -6
  6. package/dist/cli/generation-failure.renderer.js +0 -14
  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 +5 -25
  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 +34 -22
  17. package/dist/config/config.loader.js +49 -52
  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 +20 -34
  62. package/dist/generate.js +14 -22
  63. package/dist/index.js +0 -5
  64. package/dist/init/client-config.template.d.ts +6 -4
  65. package/dist/init/client-config.template.js +10 -13
  66. package/dist/init/client-init.errors.js +0 -3
  67. package/dist/init/client-init.orchestrator.js +8 -9
  68. package/dist/init/client-init.planner.d.ts +1 -9
  69. package/dist/init/client-init.planner.js +16 -24
  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 +49 -27
  76. package/dist/output/output.validator.js +113 -74
  77. package/dist/output/output.writer.d.ts +59 -52
  78. package/dist/output/output.writer.js +72 -134
  79. package/package.json +6 -4
@@ -1,20 +1,18 @@
1
1
  import type { ClientConfigInput } from "./client-config.interface.js";
2
2
  /**
3
- * Finds and evaluates the client config: `framework.client.mts` (or, for a setup
4
- * written before pilot.1, `framework.client.ts`), default-exporting
5
- * `defineClientConfig({ entrypoint, generateAt })` as §15.2 writes it. One file,
6
- * looked up in one directory — no search up the tree, no `package.json` key —
7
- * and when both spellings are present the run refuses rather than choose: two
8
- * files would be two sources of the same 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
- * Why `.mts` (pilot.1): `npm init -y` writes `"type": "commonjs"`, under which
11
- * Node reads a `.ts` file as CommonJS and the config's `import`/`export` stop
12
- * the run. A `.mts` file is an ES module in every project. A `.ts` config that
13
- * fails that way is told to rename itself.
14
- *
15
- * The file is TypeScript and is evaluated by Node itself (type stripping, on by
16
- * default in the Node this repository requires), so the generator needs no
17
- * 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
18
16
  * `defineClientConfig` and `env` — resolves from the consumer's project, as any
19
17
  * import of theirs would.
20
18
  *
@@ -22,20 +20,34 @@ import type { ClientConfigInput } from "./client-config.interface.js";
22
20
  * is trusted: the config is the consumer's code, and a typo there should be a
23
21
  * sentence naming the member, not a `TypeError` from deep inside resolution.
24
22
  */
25
- /** The config file `avclient init` writes: an ES module whatever the project's `type` (pilot.1). */
26
- export declare const CLIENT_CONFIG_FILE = "framework.client.mts";
27
- /** §15.2's spelling, still read for the setups written before pilot.1. */
28
- export declare const LEGACY_CLIENT_CONFIG_FILE = "framework.client.ts";
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. */
28
+ export declare const CLIENT_CONFIG_FILE = "framework.client.ts";
29
29
  export interface LoadedClientConfig {
30
30
  /** The config file's absolute path; `generateAt` resolves against its directory. */
31
31
  readonly file: string;
32
32
  readonly config: ClientConfigInput;
33
33
  }
34
34
  /**
35
- * Evaluates `<directory>/framework.client.mts`, or `framework.client.ts` when
36
- * only that one is there.
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`.
37
49
  *
38
- * @throws ClientConfigError, in one line, when neither or both are there, or
39
- * the file does not 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.
40
52
  */
41
53
  export declare function loadClientConfigFile(directory: string): Promise<LoadedClientConfig>;
@@ -1,77 +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.mts` (or, for a setup
7
- * written before pilot.1, `framework.client.ts`), default-exporting
8
- * `defineClientConfig({ entrypoint, generateAt })` as §15.2 writes it. One file,
9
- * looked up in one directory — no search up the tree, no `package.json` key —
10
- * and when both spellings are present the run refuses rather than choose: two
11
- * files would be two sources of the same fact.
12
- *
13
- * Why `.mts` (pilot.1): `npm init -y` writes `"type": "commonjs"`, under which
14
- * Node reads a `.ts` file as CommonJS and the config's `import`/`export` stop
15
- * the run. A `.mts` file is an ES module in every project. A `.ts` config that
16
- * fails that way is told to rename itself.
17
- *
18
- * The file is TypeScript and is evaluated by Node itself (type stripping, on by
19
- * default in the Node this repository requires), so the generator needs no
20
- * compiler to read its own config. What it imports — `@aventara/client` for
21
- * `defineClientConfig` and `env` — resolves from the consumer's project, as any
22
- * import of theirs would.
23
- *
24
- * The evaluated value is checked against `ClientConfigInput`'s shape before it
25
- * is trusted: the config is the consumer's code, and a typo there should be a
26
- * sentence naming the member, not a `TypeError` from deep inside resolution.
27
- */
28
- /** The config file `avclient init` writes: an ES module whatever the project's `type` (pilot.1). */
29
- export const CLIENT_CONFIG_FILE = "framework.client.mts";
30
- /** §15.2's spelling, still read for the setups written before pilot.1. */
31
- export const LEGACY_CLIENT_CONFIG_FILE = "framework.client.ts";
32
- /**
33
- * Evaluates `<directory>/framework.client.mts`, or `framework.client.ts` when
34
- * only that one is there.
35
- *
36
- * @throws ClientConfigError, in one line, when neither or both are there, or
37
- * the file does not evaluate, or does not default-export a client config.
38
- */
39
- export async function loadClientConfigFile(directory) {
40
- const present = [CLIENT_CONFIG_FILE, LEGACY_CLIENT_CONFIG_FILE].filter((name) => existsSync(path.resolve(directory, name)));
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);
41
21
  if (present.length > 1) {
42
- throw new ClientConfigError(`both ${CLIENT_CONFIG_FILE} and ${LEGACY_CLIENT_CONFIG_FILE} are in ${path.resolve(directory)}, and only one may configure the generator; keep ${CLIENT_CONFIG_FILE} and delete ${LEGACY_CLIENT_CONFIG_FILE}.`);
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" : ""}.`);
43
24
  }
44
- const [name] = present;
25
+ return present[0];
26
+ }
27
+ export async function loadClientConfigFile(directory) {
28
+ const name = findClientConfigFile(directory);
45
29
  if (name === undefined) {
46
- throw new ClientConfigError(`no ${CLIENT_CONFIG_FILE} at ${path.resolve(directory, CLIENT_CONFIG_FILE)}; create it with ` +
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 ` +
47
31
  "`export default defineClientConfig({ entrypoint, generateAt })` (or run `avclient init`) and run the generator from its directory.");
48
32
  }
49
33
  const file = path.resolve(directory, name);
50
34
  let evaluated;
51
35
  try {
52
- 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;
53
53
  }
54
54
  catch (error) {
55
- throw new ClientConfigError(`${file} could not be evaluated: ${firstLineOf(error)}${name === LEGACY_CLIENT_CONFIG_FILE && error instanceof SyntaxError
56
- ? `; if this project's package.json says "type": "commonjs" (npm init -y writes it), Node reads a .ts file as CommonJS — rename it ${CLIENT_CONFIG_FILE}, which is an ES module in every project`
57
- : ""}`, { cause: error });
55
+ throw new ClientConfigError(`${file} could not be evaluated: ${firstLineOf(error)}`, { cause: error });
58
56
  }
59
57
  return { file, config: clientConfigOf(file, evaluated) };
60
58
  }
61
- /** The default export, checked member by member against `ClientConfigInput`. */
62
- function clientConfigOf(file, evaluated) {
59
+ function clientConfigOf(file, config) {
63
60
  const shape = "`export default defineClientConfig({ entrypoint, generateAt })`";
64
- if (!Object.hasOwn(evaluated, "default")) {
65
- throw new ClientConfigError(`${file} has no default export; it must be ${shape}.`);
66
- }
67
- const config = evaluated.default;
68
61
  if (typeof config !== "object" || config === null || Array.isArray(config)) {
69
62
  throw new ClientConfigError(`${file}'s default export is not an object; it must be ${shape}.`);
70
63
  }
71
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
+ }
72
69
  return {
73
70
  entrypoint: configValueOf(file, "entrypoint", members.entrypoint),
74
71
  generateAt: configValueOf(file, "generateAt", members.generateAt),
72
+ ...(tsconfigFile === undefined ? {} : { tsconfigFile }),
75
73
  };
76
74
  }
77
75
  function configValueOf(file, member, value) {
@@ -88,7 +86,6 @@ function configValueOf(file, member, value) {
88
86
  }
89
87
  throw new ClientConfigError(`${file}'s ${member} must be a string or env("NAME"), and it is ${value === undefined ? "missing" : "neither"}.`);
90
88
  }
91
- /** An error's message, cut at its first line break: the refusal is one line. */
92
89
  function firstLineOf(error) {
93
90
  const message = error instanceof Error ? error.message : String(error);
94
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;
@@ -0,0 +1,75 @@
1
+ import { readFileSync, statSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { ClientConfigError } from "./config.resolver.js";
4
+ import { locateTsconfig } from "./tsconfig.locator.js";
5
+ export function resolveModuleStyle(input) {
6
+ const located = locateTsconfig(input);
7
+ if (located === undefined) {
8
+ throw new ClientConfigError(noTsconfigRefusal(input.generateAt));
9
+ }
10
+ return {
11
+ tsconfig: located.path,
12
+ importFileExtension: importFileExtensionOf(located.config),
13
+ moduleFormat: moduleFormatOf(located, input.generateAt),
14
+ };
15
+ }
16
+ export function noTsconfigRefusal(generateAt) {
17
+ return (`no tsconfig.json was found in ${generateAt} or any directory above it. ` +
18
+ "Aventara's generated client is TypeScript, compiled by your project's own toolchain and written the way " +
19
+ "its tsconfig.json says; JavaScript projects are not supported yet. Add a tsconfig.json to the project " +
20
+ "(`npx tsc --init` writes one), or, if it has one this search does not find, name it with `tsconfigFile` " +
21
+ "in your framework.client config. Nothing was written.");
22
+ }
23
+ export function importFileExtensionOf(config) {
24
+ const options = config.compilerOptions;
25
+ if (options?.allowImportingTsExtensions ||
26
+ options?.rewriteRelativeImportExtensions) {
27
+ return "ts";
28
+ }
29
+ const moduleResolution = options?.moduleResolution?.toLowerCase();
30
+ return options?.module?.toLowerCase() === "commonjs" ||
31
+ moduleResolution === "bundler"
32
+ ? ""
33
+ : "js";
34
+ }
35
+ export function moduleFormatOf(tsconfig, generateAt) {
36
+ const module = tsconfig.config.compilerOptions?.module?.toLowerCase();
37
+ if (module === undefined || module === "") {
38
+ return "esm";
39
+ }
40
+ if (module === "commonjs") {
41
+ return "cjs";
42
+ }
43
+ if (module === "node16" || module === "nodenext") {
44
+ return packageTypeFormat(generateAt);
45
+ }
46
+ return "esm";
47
+ }
48
+ function packageTypeFormat(directory) {
49
+ const manifest = nearestPackageJson(directory);
50
+ if (manifest === undefined) {
51
+ return "cjs";
52
+ }
53
+ try {
54
+ return JSON.parse(readFileSync(manifest, "utf8"))
55
+ .type === "module"
56
+ ? "esm"
57
+ : "cjs";
58
+ }
59
+ catch {
60
+ return "cjs";
61
+ }
62
+ }
63
+ function nearestPackageJson(directory) {
64
+ for (let current = path.resolve(directory);;) {
65
+ const candidate = path.join(current, "package.json");
66
+ if (statSync(candidate, { throwIfNoEntry: false })?.isFile() === true) {
67
+ return candidate;
68
+ }
69
+ const parent = path.dirname(current);
70
+ if (parent === current) {
71
+ return undefined;
72
+ }
73
+ current = parent;
74
+ }
75
+ }
@@ -0,0 +1,45 @@
1
+ import { type TsConfigJsonResolved } from "get-tsconfig";
2
+ /**
3
+ * Which `tsconfig.json` the generated client is spelled for
4
+ * (`module-style.resolver.ts`): the project's own, found the way Prisma 7's
5
+ * `prisma-client` generator finds it — `get-tsconfig`'s `getTsconfig`, the very
6
+ * library and version Prisma 7.10 bundles, searching from the output directory
7
+ * up, `extends` chains merged — with one step Prisma does not take.
8
+ *
9
+ * # A solution-style root (Vite's layout)
10
+ *
11
+ * `npm create vite` writes a `tsconfig.json` that compiles nothing — `"files":
12
+ * []` and `references` to `tsconfig.app.json` and `tsconfig.node.json` — so the
13
+ * nearest config names no compiler option at all. When the config found does
14
+ * not include the generated entry file and has `references`, the referenced
15
+ * project that does include it is the one used, the first in `references`
16
+ * order — how TypeScript's own language service picks a file's project. With
17
+ * none including it, the root stays the answer, as it is Prisma's.
18
+ *
19
+ * # `tsconfigFile`
20
+ *
21
+ * The escape hatch for a layout this discovery gets wrong (a monorepo, a
22
+ * non-standard name): the config's `tsconfigFile`, resolved against the config
23
+ * file's directory, used as named — no search, no reference step. A missing or
24
+ * unreadable one is refused, naming the path it resolved to.
25
+ */
26
+ /** The tsconfig a run follows: its path and its resolved content (`extends` merged). */
27
+ export interface LocatedTsconfig {
28
+ readonly path: string;
29
+ readonly config: TsConfigJsonResolved;
30
+ }
31
+ export interface TsconfigLocation {
32
+ /** The absolute `generateAt`: the search starts there. */
33
+ readonly generateAt: string;
34
+ /** The generated entry file the project must include, absolute. */
35
+ readonly entryFile: string;
36
+ /** The config's `tsconfigFile`, already absolute; the search otherwise. */
37
+ readonly tsconfigFile?: string;
38
+ }
39
+ /**
40
+ * The tsconfig `location` names, or `undefined` when no `tsconfig.json` is in
41
+ * `generateAt` or any directory above it (and none was named).
42
+ *
43
+ * @throws ClientConfigError when the named `tsconfigFile` is missing or unreadable.
44
+ */
45
+ export declare function locateTsconfig(location: TsconfigLocation): LocatedTsconfig | undefined;