@prisma/compute-sdk 0.21.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +3 -1
  2. package/dist/api-client.d.ts +105 -0
  3. package/dist/api-client.d.ts.map +1 -1
  4. package/dist/api-client.js +39 -0
  5. package/dist/api-client.js.map +1 -1
  6. package/dist/compute-client.d.ts.map +1 -1
  7. package/dist/compute-client.js +69 -12
  8. package/dist/compute-client.js.map +1 -1
  9. package/dist/config/frameworks.d.ts +46 -0
  10. package/dist/config/frameworks.d.ts.map +1 -0
  11. package/dist/config/frameworks.js +129 -0
  12. package/dist/config/frameworks.js.map +1 -0
  13. package/dist/config/index.d.ts +15 -0
  14. package/dist/config/index.d.ts.map +1 -0
  15. package/dist/config/index.js +15 -0
  16. package/dist/config/index.js.map +1 -0
  17. package/dist/config/load.d.ts +48 -0
  18. package/dist/config/load.d.ts.map +1 -0
  19. package/dist/config/load.js +141 -0
  20. package/dist/config/load.js.map +1 -0
  21. package/dist/config/normalize.d.ts +77 -0
  22. package/dist/config/normalize.d.ts.map +1 -0
  23. package/dist/config/normalize.js +366 -0
  24. package/dist/config/normalize.js.map +1 -0
  25. package/dist/config/serialize.d.ts +11 -0
  26. package/dist/config/serialize.d.ts.map +1 -0
  27. package/dist/config/serialize.js +55 -0
  28. package/dist/config/serialize.js.map +1 -0
  29. package/dist/config/source-root.d.ts +10 -0
  30. package/dist/config/source-root.d.ts.map +1 -0
  31. package/dist/config/source-root.js +72 -0
  32. package/dist/config/source-root.js.map +1 -0
  33. package/dist/config/types.d.ts +67 -0
  34. package/dist/config/types.d.ts.map +1 -0
  35. package/dist/config/types.js +30 -0
  36. package/dist/config/types.js.map +1 -0
  37. package/dist/errors.d.ts +1 -1
  38. package/dist/errors.d.ts.map +1 -1
  39. package/package.json +11 -3
  40. package/src/api-client.ts +89 -0
  41. package/src/compute-client.ts +138 -13
  42. package/src/config/frameworks.ts +187 -0
  43. package/src/config/index.ts +59 -0
  44. package/src/config/load.ts +201 -0
  45. package/src/config/normalize.ts +569 -0
  46. package/src/config/serialize.ts +76 -0
  47. package/src/config/source-root.ts +91 -0
  48. package/src/config/types.ts +78 -0
  49. package/src/errors.ts +1 -0
@@ -0,0 +1,201 @@
1
+ import { existsSync } from "node:fs";
2
+ import { access } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ import { Result, TaggedError } from "better-result";
7
+ import { createJiti } from "jiti";
8
+
9
+ import {
10
+ type ComputeConfigInvalidError,
11
+ type LoadedComputeConfig,
12
+ normalizeComputeConfig,
13
+ } from "./normalize.ts";
14
+ import { sourceRootLineage } from "./source-root.ts";
15
+
16
+ export const COMPUTE_CONFIG_FILENAME = "prisma.compute.ts";
17
+
18
+ // Highest priority first. TypeScript is the canonical format; the rest exist
19
+ // so plain JavaScript projects are not forced into TypeScript.
20
+ export const COMPUTE_CONFIG_FILENAMES = [
21
+ "prisma.compute.ts",
22
+ "prisma.compute.mts",
23
+ "prisma.compute.js",
24
+ "prisma.compute.mjs",
25
+ "prisma.compute.cjs",
26
+ ] as const;
27
+
28
+ // Config files may import the typed helper through either package; both
29
+ // resolve to this module so configs load without a local install.
30
+ const CONFIG_MODULE_SPECIFIERS = [
31
+ "@prisma/compute-sdk/config",
32
+ "@prisma/cli/config",
33
+ ] as const;
34
+
35
+ export class ComputeConfigAmbiguousError extends TaggedError(
36
+ "ComputeConfigAmbiguousError",
37
+ )<{
38
+ message: string;
39
+ configPaths: string[];
40
+ }>() {
41
+ constructor(configPaths: string[]) {
42
+ super({
43
+ message: `Multiple compute config files exist: ${configPaths.map((configPath) => path.basename(configPath)).join(", ")}. Keep exactly one.`,
44
+ configPaths,
45
+ });
46
+ }
47
+ }
48
+
49
+ export class ComputeConfigLoadError extends TaggedError(
50
+ "ComputeConfigLoadError",
51
+ )<{
52
+ message: string;
53
+ cause: unknown;
54
+ configPath: string;
55
+ }>() {
56
+ constructor(configPath: string, cause: unknown) {
57
+ super({
58
+ message: `Could not load ${path.basename(configPath)}: ${cause instanceof Error ? cause.message : String(cause)}`,
59
+ cause,
60
+ configPath,
61
+ });
62
+ }
63
+ }
64
+
65
+ export type ComputeConfigError =
66
+ | ComputeConfigAmbiguousError
67
+ | ComputeConfigLoadError
68
+ | ComputeConfigInvalidError;
69
+
70
+ /**
71
+ * Compute config files present in one directory, in filename priority order.
72
+ */
73
+ export async function findComputeConfigCandidates(
74
+ directory: string,
75
+ signal?: AbortSignal,
76
+ ): Promise<string[]> {
77
+ const candidates: string[] = [];
78
+ for (const filename of COMPUTE_CONFIG_FILENAMES) {
79
+ const configPath = path.join(directory, filename);
80
+ signal?.throwIfAborted();
81
+ try {
82
+ await access(configPath);
83
+ candidates.push(configPath);
84
+ } catch (error) {
85
+ if (signal?.aborted) throw error;
86
+ }
87
+ }
88
+ signal?.throwIfAborted();
89
+
90
+ return candidates;
91
+ }
92
+
93
+ /**
94
+ * Locates the nearest directory holding a compute config file, searching from
95
+ * `cwd` up to the source root. This is location-only discovery — the config
96
+ * is not loaded or validated — so it is safe to run in hot paths.
97
+ * Returns null when no config exists inside the repository boundary.
98
+ */
99
+ export async function findComputeConfigDir(
100
+ cwd: string,
101
+ signal?: AbortSignal,
102
+ ): Promise<string | null> {
103
+ for (const directory of await sourceRootLineage(cwd, signal)) {
104
+ const candidates = await findComputeConfigCandidates(directory, signal);
105
+ if (candidates.length > 0) {
106
+ return directory;
107
+ }
108
+ }
109
+
110
+ return null;
111
+ }
112
+
113
+ /**
114
+ * Loads the nearest compute config, searching from `cwd` up to the source
115
+ * root (repository or workspace boundary). Without such a boundary only
116
+ * `cwd` itself is checked, so discovery never escapes into unrelated
117
+ * directories.
118
+ */
119
+ export async function loadComputeConfig(
120
+ cwd: string,
121
+ options?: {
122
+ signal?: AbortSignal;
123
+ /**
124
+ * Module path the config-helper import specifiers alias to. Defaults to
125
+ * this SDK's own config module; pass a path when the consumer ships its
126
+ * own copy of the contract (e.g. a bundled CLI).
127
+ */
128
+ configModuleAlias?: string;
129
+ },
130
+ ): Promise<Result<LoadedComputeConfig | null, ComputeConfigError>> {
131
+ const signal = options?.signal;
132
+ for (const directory of await sourceRootLineage(cwd, signal)) {
133
+ const candidates = await findComputeConfigCandidates(directory, signal);
134
+ if (candidates.length === 0) {
135
+ continue;
136
+ }
137
+ if (candidates.length > 1) {
138
+ return Result.err(new ComputeConfigAmbiguousError(candidates));
139
+ }
140
+
141
+ const configPath = candidates[0] as string;
142
+ signal?.throwIfAborted();
143
+ const imported = await importComputeConfigModule(
144
+ configPath,
145
+ options?.configModuleAlias,
146
+ );
147
+ if (imported.isErr()) {
148
+ return Result.err(imported.error);
149
+ }
150
+ return normalizeComputeConfig(imported.value, configPath);
151
+ }
152
+
153
+ return Result.ok(null);
154
+ }
155
+
156
+ async function importComputeConfigModule(
157
+ configPath: string,
158
+ configModuleAlias: string | undefined,
159
+ ): Promise<Result<unknown, ComputeConfigLoadError>> {
160
+ return Result.tryPromise({
161
+ try: async () => {
162
+ const aliasTarget = configModuleAlias ?? resolveOwnConfigModulePath();
163
+ const jiti = createJiti(import.meta.url, {
164
+ // Keep Node's standard interop so `.default` exists only for a real
165
+ // default export (ESM) or module.exports (CJS).
166
+ interopDefault: false,
167
+ // Re-import fresh so repeated loads in one process observe file edits.
168
+ moduleCache: false,
169
+ ...(aliasTarget
170
+ ? {
171
+ alias: Object.fromEntries(
172
+ CONFIG_MODULE_SPECIFIERS.map((specifier) => [
173
+ specifier,
174
+ aliasTarget,
175
+ ]),
176
+ ),
177
+ }
178
+ : {}),
179
+ });
180
+ const moduleNamespace =
181
+ await jiti.import<Record<string, unknown>>(configPath);
182
+ // Require an explicit default export instead of jiti's namespace
183
+ // interop so named-export configs fail with a clear message.
184
+ return moduleNamespace?.default;
185
+ },
186
+ catch: (cause) => new ComputeConfigLoadError(configPath, cause),
187
+ });
188
+ }
189
+
190
+ function resolveOwnConfigModulePath(): string | null {
191
+ // dist layout: dist/config/load.js -> dist/config/index.js
192
+ // src layout (bun/tests): src/config/load.ts -> src/config/index.ts
193
+ for (const candidate of ["./index.js", "./index.ts"]) {
194
+ const candidatePath = fileURLToPath(new URL(candidate, import.meta.url));
195
+ if (existsSync(candidatePath)) {
196
+ return candidatePath;
197
+ }
198
+ }
199
+
200
+ return null;
201
+ }