@neondatabase/config 0.4.2 → 0.7.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.
@@ -1 +1 @@
1
- {"version":3,"file":"loader.js","names":[],"sources":["../../src/lib/loader.ts"],"sourcesContent":["import { existsSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, isAbsolute, resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { defineConfig } from \"./define-config.js\";\nimport { ConfigLoadError } from \"./errors.js\";\nimport type { Config } from \"./types.js\";\n\n/**\n * Default file names tried (in order) when {@link loadConfigFromFile} is called without an\n * explicit path. We accept `.ts` first because that's the documented format; `.mjs` and `.js`\n * fall out for free since jiti handles all of them.\n */\nexport const DEFAULT_CONFIG_FILENAMES = [\n\t\"neon.ts\",\n\t\"neon.mts\",\n\t\"neon.js\",\n\t\"neon.mjs\",\n] as const;\n\nexport interface LoadConfigOptions {\n\t/** Explicit absolute or cwd-relative path to a config file. Takes precedence over the search. */\n\tpath?: string;\n\t/** Starting directory for the upward search. Defaults to `process.cwd()`. */\n\tcwd?: string;\n\t/**\n\t * Hard ceiling for the upward walk — once `current === stopAt` the search returns\n\t * `null` even if no `.git` boundary was hit. Defaults to the OS home directory so\n\t * stray runs from outside any repo never leak into the user's `~` files.\n\t */\n\tstopAt?: string;\n}\n\n/**\n * Load a `neon.ts` (or any other supported extension) and return the validated {@link Config}.\n *\n * Behavior:\n * - When `path` is set, that file is loaded directly. The file must exist and must default-export\n * a value produced by `defineConfig()`.\n * - When `path` is omitted, we walk up from `cwd` picking the **closest** file matching\n * {@link DEFAULT_CONFIG_FILENAMES}. The walk is monorepo-friendly: intermediate\n * `package.json` files do **not** stop it, so a single `neon.ts` lifted to the workspace\n * root keeps working when invoked from inside any sub-package. The walk terminates at the\n * first directory containing `.git`, at `stopAt`, or at the filesystem root.\n *\n * jiti is loaded lazily so that callers who pass an already-resolved `Config` to `pushConfig`\n * never pay the import cost.\n */\nexport async function loadConfigFromFile(\n\toptions: LoadConfigOptions = {},\n): Promise<{\n\tconfig: Config;\n\tresolvedPath: string;\n}> {\n\tconst resolvedPath = options.path\n\t\t? resolveExplicitPath(options.path, options.cwd)\n\t\t: findDefaultConfig(options.cwd, options.stopAt);\n\n\tif (!resolvedPath) {\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t`Could not find a Neon config file while walking up from ${resolve(options.cwd ?? process.cwd())}.`,\n\t\t\t\t`Looked for: ${DEFAULT_CONFIG_FILENAMES.join(\", \")} (stopping at the first directory with a \\`.git\\`).`,\n\t\t\t\t`Create one at your repository root (or anywhere on the path from cwd up to .git), or pass an explicit \\`configPath\\` (SDK) / \\`--config <path>\\` (CLI).`,\n\t\t\t].join(\"\\n\"),\n\t\t);\n\t}\n\n\tlet mod: unknown;\n\ttry {\n\t\tmod = await importModule(resolvedPath);\n\t} catch (cause) {\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t`Failed to evaluate ${resolvedPath}.`,\n\t\t\t\t`Underlying error: ${(cause as Error)?.message ?? String(cause)}`,\n\t\t\t\t\"This is usually a TypeScript syntax error, a missing dependency, or a runtime exception inside the config file. Run the file directly (e.g. `npx tsx neon.ts`) to reproduce.\",\n\t\t\t].join(\"\\n\"),\n\t\t\t{ cause },\n\t\t);\n\t}\n\n\tconst exported = extractDefaultExport(mod);\n\tif (exported === undefined) {\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t`${resolvedPath} loaded successfully but did not default-export a config.`,\n\t\t\t\t\"Add `export default defineConfig({ ... })` at the bottom of the file. (Named exports are ignored.)\",\n\t\t\t].join(\"\\n\"),\n\t\t);\n\t}\n\n\t// Run through defineConfig to validate any function the user might have constructed manually.\n\tconst config = defineConfig(exported as Config);\n\treturn { config, resolvedPath };\n}\n\nfunction resolveExplicitPath(input: string, cwd?: string): string {\n\tconst base = resolve(cwd ?? process.cwd());\n\tconst abs = isAbsolute(input) ? input : resolve(base, input);\n\tif (!existsSync(abs)) {\n\t\tthrow new ConfigLoadError(\n\t\t\t`Config file not found at ${abs}. The path was resolved from \\`${input}\\` against ${base}.`,\n\t\t);\n\t}\n\tconst s = statSync(abs);\n\tif (!s.isFile()) {\n\t\tthrow new ConfigLoadError(\n\t\t\t`Config path ${abs} is a directory, not a file. Pass a path to the file itself (e.g. ./neon.ts).`,\n\t\t);\n\t}\n\treturn abs;\n}\n\nfunction findDefaultConfig(\n\tcwd: string | undefined,\n\tstopAt: string | undefined,\n): string | null {\n\tlet current = resolve(cwd ?? process.cwd());\n\tconst stop = resolve(stopAt ?? homedir());\n\tlet lastSeen: string | null = null;\n\n\twhile (true) {\n\t\tfor (const name of DEFAULT_CONFIG_FILENAMES) {\n\t\t\tconst candidate = resolve(current, name);\n\t\t\tif (existsSync(candidate) && safeIsFile(candidate))\n\t\t\t\treturn candidate;\n\t\t}\n\n\t\t// `.git` is the canonical repo-root marker. `package.json` is deliberately *not*\n\t\t// a stop: monorepos lift `neon.ts` above sub-package package.jsons.\n\t\tif (existsSync(resolve(current, \".git\"))) return null;\n\t\tif (current === stop) return null;\n\n\t\tconst parent = dirname(current);\n\t\tif (parent === current || parent === lastSeen) return null;\n\t\tlastSeen = current;\n\t\tcurrent = parent;\n\t}\n}\n\nasync function importModule(absPath: string): Promise<unknown> {\n\tconst lower = absPath.toLowerCase();\n\tconst needsJiti =\n\t\tlower.endsWith(\".ts\") ||\n\t\tlower.endsWith(\".mts\") ||\n\t\tlower.endsWith(\".cts\");\n\n\tif (!needsJiti) {\n\t\treturn import(pathToFileURL(absPath).href);\n\t}\n\n\tconst jitiModule: unknown = await import(\"jiti\");\n\tconst createJiti = extractCreateJiti(jitiModule);\n\tif (!createJiti) {\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t\"jiti is required to load TypeScript config files but could not be initialised.\",\n\t\t\t\t\"Reinstall the package dependencies (`pnpm install` / `npm install`) — jiti is a runtime dependency of @neondatabase/config.\",\n\t\t\t].join(\" \"),\n\t\t);\n\t}\n\tconst jiti = createJiti(pathToFileURL(absPath).href, {\n\t\tinteropDefault: true,\n\t\tmoduleCache: false,\n\t});\n\treturn jiti.import(absPath);\n}\n\nfunction extractCreateJiti(\n\tmod: unknown,\n): ((id: string, options?: unknown) => JitiInstance) | null {\n\tif (mod === null || typeof mod !== \"object\") return null;\n\tconst obj = mod as Record<string, unknown>;\n\tif (typeof obj.createJiti === \"function\") {\n\t\treturn obj.createJiti as (\n\t\t\tid: string,\n\t\t\toptions?: unknown,\n\t\t) => JitiInstance;\n\t}\n\tconst def = obj.default;\n\tif (def !== null && typeof def === \"object\") {\n\t\tconst defObj = def as Record<string, unknown>;\n\t\tif (typeof defObj.createJiti === \"function\") {\n\t\t\treturn defObj.createJiti as (\n\t\t\t\tid: string,\n\t\t\t\toptions?: unknown,\n\t\t\t) => JitiInstance;\n\t\t}\n\t}\n\treturn null;\n}\n\ninterface JitiInstance {\n\timport(id: string): Promise<unknown>;\n}\n\nfunction extractDefaultExport(mod: unknown): unknown {\n\tif (mod === null || typeof mod !== \"object\") return mod;\n\tconst obj = mod as Record<string, unknown>;\n\tif (\"default\" in obj && obj.default !== undefined) return obj.default;\n\t// No `default` export. If the module itself is a function, treat it as the config —\n\t// that lets tests and advanced users skip the wrapper.\n\t// Otherwise, return `undefined` so the caller surfaces a clear ConfigLoadError.\n\tif (typeof mod === \"function\") return mod;\n\treturn undefined;\n}\n\nfunction safeIsFile(path: string): boolean {\n\ttry {\n\t\treturn statSync(path).isFile();\n\t} catch {\n\t\treturn false;\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"loader.js","names":[],"sources":["../../src/lib/loader.ts"],"sourcesContent":["import { existsSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, isAbsolute, resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { defineConfig } from \"./define-config.js\";\nimport { ConfigLoadError, isPlatformError } from \"./errors.js\";\nimport type { Config } from \"./types.js\";\n\n/**\n * Default file names tried (in order) when {@link loadConfigFromFile} is called without an\n * explicit path. We accept `.ts` first because that's the documented format; `.mjs` and `.js`\n * fall out for free since jiti handles all of them.\n */\nexport const DEFAULT_CONFIG_FILENAMES = [\n\t\"neon.ts\",\n\t\"neon.mts\",\n\t\"neon.js\",\n\t\"neon.mjs\",\n] as const;\n\nexport interface LoadConfigOptions {\n\t/** Explicit absolute or cwd-relative path to a config file. Takes precedence over the search. */\n\tpath?: string;\n\t/** Starting directory for the upward search. Defaults to `process.cwd()`. */\n\tcwd?: string;\n\t/**\n\t * Hard ceiling for the upward walk — once `current === stopAt` the search returns\n\t * `null` even if no `.git` boundary was hit. Defaults to the OS home directory so\n\t * stray runs from outside any repo never leak into the user's `~` files.\n\t */\n\tstopAt?: string;\n}\n\n/**\n * Load a `neon.ts` (or any other supported extension) and return the validated {@link Config}.\n *\n * Behavior:\n * - When `path` is set, that file is loaded directly. The file must exist and must default-export\n * a value produced by `defineConfig()`.\n * - When `path` is omitted, we walk up from `cwd` picking the **closest** file matching\n * {@link DEFAULT_CONFIG_FILENAMES}. The walk is monorepo-friendly: intermediate\n * `package.json` files do **not** stop it, so a single `neon.ts` lifted to the workspace\n * root keeps working when invoked from inside any sub-package. The walk terminates at the\n * first directory containing `.git`, at `stopAt`, or at the filesystem root.\n *\n * jiti is loaded lazily so that callers who pass an already-resolved `Config` to `pushConfig`\n * never pay the import cost.\n */\nexport async function loadConfigFromFile(\n\toptions: LoadConfigOptions = {},\n): Promise<{\n\tconfig: Config;\n\tresolvedPath: string;\n}> {\n\tconst resolvedPath = options.path\n\t\t? resolveExplicitPath(options.path, options.cwd)\n\t\t: findDefaultConfig(options.cwd, options.stopAt);\n\n\tif (!resolvedPath) {\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t`Could not find a Neon config file while walking up from ${resolve(options.cwd ?? process.cwd())}.`,\n\t\t\t\t`Looked for: ${DEFAULT_CONFIG_FILENAMES.join(\", \")} (stopping at the first directory with a \\`.git\\`).`,\n\t\t\t\t`Create one at your repository root (or anywhere on the path from cwd up to .git), or pass an explicit \\`configPath\\` (SDK) / \\`--config <path>\\` (CLI).`,\n\t\t\t].join(\"\\n\"),\n\t\t);\n\t}\n\n\tlet mod: unknown;\n\ttry {\n\t\tmod = await importModule(resolvedPath);\n\t} catch (cause) {\n\t\t// `defineConfig()` runs at module-eval time, so a config the user got *wrong*\n\t\t// (a bad function slug, an unknown key, an invalid duration, …) throws a\n\t\t// PlatformError from inside this import. That error already pinpoints the exact\n\t\t// field and reason, so surface it verbatim — burying it under the generic\n\t\t// \"this is usually a TypeScript syntax error\" hint sent users hunting for a\n\t\t// syntax bug that isn't there. The hint is reserved for genuine evaluation\n\t\t// failures (syntax errors, missing deps, thrown runtime exceptions).\n\t\tif (isPlatformError(cause)) {\n\t\t\tthrow cause;\n\t\t}\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t`Failed to evaluate ${resolvedPath}.`,\n\t\t\t\t`Underlying error: ${cause instanceof Error ? cause.message : String(cause)}`,\n\t\t\t\t\"This is usually a TypeScript syntax error, a missing dependency, or a runtime exception inside the config file. Run the file directly (e.g. `npx tsx neon.ts`) to reproduce.\",\n\t\t\t].join(\"\\n\"),\n\t\t\t{ cause },\n\t\t);\n\t}\n\n\tconst exported = extractDefaultExport(mod);\n\tif (exported === undefined) {\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t`${resolvedPath} loaded successfully but did not default-export a config.`,\n\t\t\t\t\"Add `export default defineConfig({ ... })` at the bottom of the file. (Named exports are ignored.)\",\n\t\t\t].join(\"\\n\"),\n\t\t);\n\t}\n\n\t// Run through defineConfig to validate any function the user might have constructed manually.\n\tconst config = defineConfig(exported as Config);\n\treturn { config, resolvedPath };\n}\n\nfunction resolveExplicitPath(input: string, cwd?: string): string {\n\tconst base = resolve(cwd ?? process.cwd());\n\tconst abs = isAbsolute(input) ? input : resolve(base, input);\n\tif (!existsSync(abs)) {\n\t\tthrow new ConfigLoadError(\n\t\t\t`Config file not found at ${abs}. The path was resolved from \\`${input}\\` against ${base}.`,\n\t\t);\n\t}\n\tconst s = statSync(abs);\n\tif (!s.isFile()) {\n\t\tthrow new ConfigLoadError(\n\t\t\t`Config path ${abs} is a directory, not a file. Pass a path to the file itself (e.g. ./neon.ts).`,\n\t\t);\n\t}\n\treturn abs;\n}\n\nfunction findDefaultConfig(\n\tcwd: string | undefined,\n\tstopAt: string | undefined,\n): string | null {\n\tlet current = resolve(cwd ?? process.cwd());\n\tconst stop = resolve(stopAt ?? homedir());\n\tlet lastSeen: string | null = null;\n\n\twhile (true) {\n\t\tfor (const name of DEFAULT_CONFIG_FILENAMES) {\n\t\t\tconst candidate = resolve(current, name);\n\t\t\tif (existsSync(candidate) && safeIsFile(candidate))\n\t\t\t\treturn candidate;\n\t\t}\n\n\t\t// `.git` is the canonical repo-root marker. `package.json` is deliberately *not*\n\t\t// a stop: monorepos lift `neon.ts` above sub-package package.jsons.\n\t\tif (existsSync(resolve(current, \".git\"))) return null;\n\t\tif (current === stop) return null;\n\n\t\tconst parent = dirname(current);\n\t\tif (parent === current || parent === lastSeen) return null;\n\t\tlastSeen = current;\n\t\tcurrent = parent;\n\t}\n}\n\nasync function importModule(absPath: string): Promise<unknown> {\n\tconst lower = absPath.toLowerCase();\n\tconst needsJiti =\n\t\tlower.endsWith(\".ts\") ||\n\t\tlower.endsWith(\".mts\") ||\n\t\tlower.endsWith(\".cts\");\n\n\tif (!needsJiti) {\n\t\treturn import(pathToFileURL(absPath).href);\n\t}\n\n\tconst jitiModule: unknown = await import(\"jiti\");\n\tconst createJiti = extractCreateJiti(jitiModule);\n\tif (!createJiti) {\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t\"jiti is required to load TypeScript config files but could not be initialised.\",\n\t\t\t\t\"Reinstall the package dependencies (`pnpm install` / `npm install`) — jiti is a runtime dependency of @neondatabase/config.\",\n\t\t\t].join(\" \"),\n\t\t);\n\t}\n\tconst jiti = createJiti(pathToFileURL(absPath).href, {\n\t\tinteropDefault: true,\n\t\tmoduleCache: false,\n\t});\n\treturn jiti.import(absPath);\n}\n\nfunction extractCreateJiti(\n\tmod: unknown,\n): ((id: string, options?: unknown) => JitiInstance) | null {\n\tif (mod === null || typeof mod !== \"object\") return null;\n\tconst obj = mod as Record<string, unknown>;\n\tif (typeof obj.createJiti === \"function\") {\n\t\treturn obj.createJiti as (\n\t\t\tid: string,\n\t\t\toptions?: unknown,\n\t\t) => JitiInstance;\n\t}\n\tconst def = obj.default;\n\tif (def !== null && typeof def === \"object\") {\n\t\tconst defObj = def as Record<string, unknown>;\n\t\tif (typeof defObj.createJiti === \"function\") {\n\t\t\treturn defObj.createJiti as (\n\t\t\t\tid: string,\n\t\t\t\toptions?: unknown,\n\t\t\t) => JitiInstance;\n\t\t}\n\t}\n\treturn null;\n}\n\ninterface JitiInstance {\n\timport(id: string): Promise<unknown>;\n}\n\nfunction extractDefaultExport(mod: unknown): unknown {\n\tif (mod === null || typeof mod !== \"object\") return mod;\n\tconst obj = mod as Record<string, unknown>;\n\tif (\"default\" in obj && obj.default !== undefined) return obj.default;\n\t// No `default` export. If the module itself is a function, treat it as the config —\n\t// that lets tests and advanced users skip the wrapper.\n\t// Otherwise, return `undefined` so the caller surfaces a clear ConfigLoadError.\n\tif (typeof mod === \"function\") return mod;\n\treturn undefined;\n}\n\nfunction safeIsFile(path: string): boolean {\n\ttry {\n\t\treturn statSync(path).isFile();\n\t} catch {\n\t\treturn false;\n\t}\n}\n"],"mappings":"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"}
@@ -53,6 +53,12 @@ declare function isPreviewFeatureUnavailable(err: unknown): boolean;
53
53
  * Convert a Preview-feature error into a clear {@link PlatformError} when the feature is
54
54
  * unavailable for the project; otherwise pass the original error through unchanged so a
55
55
  * genuine failure (auth, transient 5xx, …) keeps its specific code and message.
56
+ *
57
+ * The message names the failing feature, summarizes the response in one short
58
+ * `HTTP <status> <reason>` line, includes the raw Neon API message + request id (valuable
59
+ * signal while the feature is in preview), gives status-specific guidance (see
60
+ * {@link previewUnavailableHint}), and offers removing the feature from the policy as an
61
+ * escape hatch. `status`/`requestId` are also kept on `details` for programmatic consumers.
56
62
  */
57
63
  declare function previewUnavailableError(err: unknown, featureLabel: string): unknown;
58
64
  declare function createNeonAuthRestInput(input: {
@@ -1 +1 @@
1
- {"version":3,"file":"neon-api-real.d.ts","names":[],"sources":["../../src/lib/neon-api-real.ts"],"mappings":";;;UAoFU,uBAAA;;EAAA,aAAA,CAAA,EAAA,MAAA;AAeV;AAyCC;AAeD;;;;AAES,iBA1DO,iBAAA,CA0DP,OAAA,EAAA;QACE,EAAA,MAAA;SAAR,CAAA,EAAA,MAAA;EAAO;AAwzBV;AAsBA;AAmCA;AAoBA;EAAuC,aAAA,CAAA,EAAA;IAAQ,WAAA,CAAA,EAAA,MAAA;IAAsB,cAAA,CAAA,EAAA,MAAA;IAAQ,UAAA,CAAA,EAAA,MAAA;EAwBvD,CAAA;CAAY,CAAA,EA38B9B,OA28B8B;UA76BxB,WAAA,CA66B8B;aAAW,EAAA,MAAA;EAAO,cAAA,EAAA,MAAA;;;;;;;;;;iBAh6BpC,2BACX,QAAQ,YACV,cACN,QAAQ;;;;;;;;;;;;iBAwzBK,2BAAA;;;;;;iBAsBA,uBAAA;iBAmCA,uBAAA;;IAEZ;;;;;;;;;;;;iBAkBY,uBAAA,QAA+B,sBAAsB;;;;;;;;;;;iBAwB/C,YAAA,MAAkB,WAAW"}
1
+ {"version":3,"file":"neon-api-real.d.ts","names":[],"sources":["../../src/lib/neon-api-real.ts"],"mappings":";;;UAiIU,uBAAA;;EAAA,aAAA,CAAA,EAAA,MAAA;AAeV;AAyCC;AAeD;;;;AAES,iBA1DO,iBAAA,CA0DP,OAAA,EAAA;QACE,EAAA,MAAA;SAAR,CAAA,EAAA,MAAA;EAAO;AA+2BV;AAiFA;AA6DA;AAoBA;EAAuC,aAAA,CAAA,EAAA;IAAQ,WAAA,CAAA,EAAA,MAAA;IAAsB,cAAA,CAAA,EAAA,MAAA;IAAQ,UAAA,CAAA,EAAA,MAAA;EAwBvD,CAAA;CAAY,CAAA,EAvlC9B,OAulC8B;UAzjCxB,WAAA,CAyjC8B;aAAW,EAAA,MAAA;EAAO,cAAA,EAAA,MAAA;;;;;;;;;;iBA5iCpC,2BACX,QAAQ,YACV,cACN,QAAQ;;;;;;;;;;;;iBA+2BK,2BAAA;;;;;;;;;;;;iBAiFA,uBAAA;iBA6DA,uBAAA;;IAEZ;;;;;;;;;;;;iBAkBY,uBAAA,QAA+B,sBAAsB;;;;;;;;;;;iBAwB/C,YAAA,MAAkB,WAAW"}
@@ -18,6 +18,12 @@ const bucketSchema = z.object({
18
18
  });
19
19
  const bucketResponseSchema = z.object({ bucket: bucketSchema });
20
20
  const bucketsListResponseSchema = z.object({ buckets: z.array(bucketSchema) });
21
+ const branchStorageSchema = z.object({
22
+ enabled: z.boolean().optional(),
23
+ s3_endpoint: z.string(),
24
+ region: z.string(),
25
+ force_path_style: z.boolean()
26
+ });
21
27
  const functionDeploymentSchema = z.object({
22
28
  id: z.number(),
23
29
  status: z.string()
@@ -29,9 +35,39 @@ const neonFunctionSchema = z.object({
29
35
  invocation_url: z.string(),
30
36
  active_deployment: functionDeploymentSchema.optional()
31
37
  });
32
- const functionResponseSchema = z.object({ function: neonFunctionSchema });
33
38
  const functionsListResponseSchema = z.object({ functions: z.array(neonFunctionSchema) });
34
39
  const functionDeploymentResponseSchema = z.object({ deployment: functionDeploymentSchema });
40
+ const credentialScopeSchema = z.enum([
41
+ "storage:read",
42
+ "storage:write",
43
+ "ai_gateway:invoke",
44
+ "functions:invoke"
45
+ ]);
46
+ const createCredentialResponseSchema = z.object({
47
+ token_id: z.string(),
48
+ token_id_short: z.string(),
49
+ name: z.string().optional(),
50
+ api_token: z.string(),
51
+ s3_secret_access_key: z.string(),
52
+ scopes: z.array(credentialScopeSchema),
53
+ branch_id: z.string(),
54
+ created_at: z.string(),
55
+ expires_at: z.string().optional()
56
+ });
57
+ const credentialMetaSchema = z.object({
58
+ token_id: z.string(),
59
+ token_id_short: z.string(),
60
+ name: z.string().optional(),
61
+ scopes: z.array(credentialScopeSchema),
62
+ principal_type: z.enum(["user", "function"]),
63
+ function_id: z.string().optional(),
64
+ branch_id: z.string().optional(),
65
+ created_at: z.string(),
66
+ last_used_at: z.string().optional(),
67
+ revoked_at: z.string().optional(),
68
+ expires_at: z.string().optional()
69
+ });
70
+ const listCredentialsResponseSchema = z.object({ credentials: z.array(credentialMetaSchema) });
35
71
  /**
36
72
  * Adapt `@neondatabase/api-client` to the narrow {@link NeonApi} façade used by the rest of
37
73
  * this package. Constructs are restricted to whole-object read/write of just the fields we
@@ -364,6 +400,22 @@ var RealNeonApi = class {
364
400
  mutating: true
365
401
  });
366
402
  }
403
+ async getProjectBranchStorage(projectId, branchId) {
404
+ try {
405
+ return await this.call(`getProjectBranchStorage(${projectId}/${branchId})`, async () => {
406
+ const data = await this.getJson(`/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/storage`);
407
+ const parsed = branchStorageSchema.parse(data);
408
+ return {
409
+ s3Endpoint: parsed.s3_endpoint,
410
+ region: parsed.region,
411
+ forcePathStyle: parsed.force_path_style
412
+ };
413
+ }, { projectId });
414
+ } catch (err) {
415
+ if (err instanceof PlatformError && err.code === ErrorCode.NotFound) return null;
416
+ throw previewUnavailableError(err, "Object storage");
417
+ }
418
+ }
367
419
  async listBranchFunctions(projectId, branchId) {
368
420
  try {
369
421
  return await this.call(`listBranchFunctions(${projectId}/${branchId})`, async () => {
@@ -374,18 +426,6 @@ var RealNeonApi = class {
374
426
  throw previewUnavailableError(err, "Functions");
375
427
  }
376
428
  }
377
- async createBranchFunction(projectId, branchId, input) {
378
- return this.call(`createBranchFunction(${projectId}/${branchId}/${input.slug})`, async () => {
379
- const data = await this.postJson(branchPreviewPath(projectId, branchId, "functions"), {
380
- slug: input.slug,
381
- name: input.name
382
- });
383
- return functionToSnapshot(functionResponseSchema.parse(data).function);
384
- }, {
385
- projectId,
386
- mutating: true
387
- });
388
- }
389
429
  async deleteBranchFunction(projectId, branchId, slug) {
390
430
  await this.call(`deleteBranchFunction(${projectId}/${branchId}/${slug})`, async () => {
391
431
  await this.deleteJson(`${branchPreviewPath(projectId, branchId, "functions")}/${encodeURIComponent(slug)}`);
@@ -403,28 +443,37 @@ var RealNeonApi = class {
403
443
  mutating: true
404
444
  });
405
445
  }
406
- async getAiGatewayEnabled(projectId, branchId) {
446
+ async createCredential(projectId, branchId, input) {
407
447
  try {
408
- return await this.call(`getAiGatewayEnabled(${projectId}/${branchId})`, async () => {
409
- return aiGatewayEnabledFromResponse(await this.getJson(aiGatewayPath(projectId, branchId)));
410
- }, { projectId });
448
+ return await this.call(`createCredential(${projectId}/${branchId})`, async () => {
449
+ const data = await this.postJson(credentialsPath(projectId, branchId), {
450
+ scopes: input.scopes,
451
+ principal_type: input.principalType,
452
+ ...input.functionId ? { function_id: input.functionId } : {},
453
+ ...input.name ? { name: input.name } : {}
454
+ });
455
+ return createCredentialToSnapshot(createCredentialResponseSchema.parse(data));
456
+ }, {
457
+ projectId,
458
+ mutating: true
459
+ });
411
460
  } catch (err) {
412
- if (isPreviewFeatureUnavailable(err)) throw previewUnavailableError(err, "AI Gateway");
413
- if (err instanceof PlatformError && err.code === ErrorCode.NotFound) return false;
414
- throw err;
461
+ throw previewUnavailableError(err, "Branch credentials");
415
462
  }
416
463
  }
417
- async enableAiGateway(projectId, branchId) {
418
- await this.call(`enableAiGateway(${projectId}/${branchId})`, async () => {
419
- await this.postJson(aiGatewayPath(projectId, branchId), { enabled: true });
420
- }, {
421
- projectId,
422
- mutating: true
423
- });
464
+ async listCredentials(projectId, branchId) {
465
+ try {
466
+ return await this.call(`listCredentials(${projectId}/${branchId})`, async () => {
467
+ const data = await this.getJson(credentialsPath(projectId, branchId));
468
+ return listCredentialsResponseSchema.parse(data).credentials.map(credentialMetaToSnapshot);
469
+ }, { projectId });
470
+ } catch (err) {
471
+ throw previewUnavailableError(err, "Branch credentials");
472
+ }
424
473
  }
425
- async disableAiGateway(projectId, branchId) {
426
- await this.call(`disableAiGateway(${projectId}/${branchId})`, async () => {
427
- await this.deleteJson(aiGatewayPath(projectId, branchId));
474
+ async revokeCredential(projectId, branchId, tokenId) {
475
+ await this.call(`revokeCredential(${projectId}/${branchId}/${tokenId})`, async () => {
476
+ await this.deleteJson(`${credentialsPath(projectId, branchId)}/${encodeURIComponent(tokenId)}`);
428
477
  }, {
429
478
  projectId,
430
479
  mutating: true
@@ -434,8 +483,38 @@ var RealNeonApi = class {
434
483
  function branchPreviewPath(projectId, branchId, resource) {
435
484
  return `/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/${resource}`;
436
485
  }
437
- function aiGatewayPath(projectId, branchId) {
438
- return `/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/ai-gateway`;
486
+ function credentialsPath(projectId, branchId) {
487
+ return `/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/credentials`;
488
+ }
489
+ function createCredentialToSnapshot(data) {
490
+ const snapshot = {
491
+ tokenId: data.token_id,
492
+ tokenIdShort: data.token_id_short,
493
+ apiToken: data.api_token,
494
+ s3SecretAccessKey: data.s3_secret_access_key,
495
+ scopes: data.scopes,
496
+ branchId: data.branch_id,
497
+ createdAt: data.created_at
498
+ };
499
+ if (data.name !== void 0) snapshot.name = data.name;
500
+ if (data.expires_at !== void 0) snapshot.expiresAt = data.expires_at;
501
+ return snapshot;
502
+ }
503
+ function credentialMetaToSnapshot(data) {
504
+ const snapshot = {
505
+ tokenId: data.token_id,
506
+ tokenIdShort: data.token_id_short,
507
+ scopes: data.scopes,
508
+ principalType: data.principal_type,
509
+ createdAt: data.created_at
510
+ };
511
+ if (data.name !== void 0) snapshot.name = data.name;
512
+ if (data.function_id !== void 0) snapshot.functionId = data.function_id;
513
+ if (data.branch_id !== void 0) snapshot.branchId = data.branch_id;
514
+ if (data.last_used_at !== void 0) snapshot.lastUsedAt = data.last_used_at;
515
+ if (data.revoked_at !== void 0) snapshot.revokedAt = data.revoked_at;
516
+ if (data.expires_at !== void 0) snapshot.expiresAt = data.expires_at;
517
+ return snapshot;
439
518
  }
440
519
  function bucketToSnapshot(bucket) {
441
520
  return {
@@ -476,10 +555,6 @@ function normalizeDeploymentStatus(value) {
476
555
  default: return "pending";
477
556
  }
478
557
  }
479
- function aiGatewayEnabledFromResponse(data) {
480
- if (data !== null && typeof data === "object" && "enabled" in data) return data.enabled === true;
481
- return false;
482
- }
483
558
  /**
484
559
  * Whether an error from a Preview-feature read means the feature simply isn't available
485
560
  * for this project/branch/region (as opposed to a real, transient failure). Neon signals
@@ -498,16 +573,77 @@ function isPreviewFeatureUnavailable(err) {
498
573
  return (message.includes("not available") || message.includes("does not exist") || message.includes("not enabled")) && (status === 503 || status === 404 || status === 501);
499
574
  }
500
575
  /**
576
+ * Reason phrase for the handful of HTTP statuses a Preview-feature read can surface as
577
+ * "unavailable". Used to print a short `HTTP <status> <reason>` line (not a stack trace),
578
+ * so the message reads like the API response the user would see in a tool like curl.
579
+ */
580
+ const HTTP_STATUS_TEXT = {
581
+ 401: "Unauthorized",
582
+ 403: "Forbidden",
583
+ 404: "Not Found",
584
+ 500: "Internal Server Error",
585
+ 501: "Not Implemented",
586
+ 503: "Service Unavailable"
587
+ };
588
+ /**
589
+ * Per-status guidance for a Preview feature that came back "unavailable". A preview can be
590
+ * gated several different ways and the HTTP status is the best signal for which, so we tailor
591
+ * the next step instead of emitting one catch-all — valuable while these features are in
592
+ * preview and rolling out region by region:
593
+ *
594
+ * - 404 / 501 — the route isn't deployed for this project's region (or the account isn't in
595
+ * the private preview): create a project in a region where the preview is enabled, and
596
+ * confirm your account has preview access.
597
+ * - 503 — the route exists but is refusing right now: either the preview is still coming up,
598
+ * or Neon is having a transient incident. Retry; if it persists it's likely an incident.
599
+ * - anything else — generic "not enabled for your account/region; request access".
600
+ *
601
+ * Only statuses {@link isPreviewFeatureUnavailable} accepts (404/501/503) actually reach
602
+ * this, so there is intentionally no 401/403 branch — those never classify as "unavailable".
603
+ */
604
+ function previewUnavailableHint(status) {
605
+ switch (status) {
606
+ case 404:
607
+ case 501: return "This usually means the preview isn't available in your project's region yet, or your Neon account isn't in the private preview: create a project in a region where the preview is enabled, and make sure your account has access to the preview.";
608
+ case 503: return "The endpoint is reachable but refused the request — the preview may still be coming up, or Neon may be having a transient incident. Retry shortly; if it keeps failing, check https://neonstatus.com and report it to Neon support.";
609
+ default: return "This usually means the preview isn't enabled for your Neon account or the project's region. Request access to the preview, or use a project in a region where it's available.";
610
+ }
611
+ }
612
+ /**
501
613
  * Convert a Preview-feature error into a clear {@link PlatformError} when the feature is
502
614
  * unavailable for the project; otherwise pass the original error through unchanged so a
503
615
  * genuine failure (auth, transient 5xx, …) keeps its specific code and message.
616
+ *
617
+ * The message names the failing feature, summarizes the response in one short
618
+ * `HTTP <status> <reason>` line, includes the raw Neon API message + request id (valuable
619
+ * signal while the feature is in preview), gives status-specific guidance (see
620
+ * {@link previewUnavailableHint}), and offers removing the feature from the policy as an
621
+ * escape hatch. `status`/`requestId` are also kept on `details` for programmatic consumers.
504
622
  */
505
623
  function previewUnavailableError(err, featureLabel) {
506
624
  if (!isPreviewFeatureUnavailable(err)) return err;
507
- const neonMessage = err instanceof PlatformError && typeof err.details.neonMessage === "string" ? ` (Neon API said: "${err.details.neonMessage}")` : "";
508
- return new PlatformError(ErrorCode.FeatureUnavailable, `${featureLabel} is a Preview feature that is not available for this project or region${neonMessage}. Enable it for your Neon account/project first, then re-run.`, {
625
+ const details = err instanceof PlatformError ? err.details : {};
626
+ const status = typeof details.status === "number" ? details.status : void 0;
627
+ const neonMessage = typeof details.neonMessage === "string" ? details.neonMessage : void 0;
628
+ const requestId = typeof details.requestId === "string" ? details.requestId : void 0;
629
+ const statusText = status ? HTTP_STATUS_TEXT[status] : void 0;
630
+ const apiParts = [
631
+ status ? `HTTP ${status}${statusText ? ` ${statusText}` : ""}` : void 0,
632
+ neonMessage ? `Neon API said: "${neonMessage}"` : void 0,
633
+ requestId ? `request id ${requestId}` : void 0
634
+ ].filter((part) => part !== void 0);
635
+ const apiContext = apiParts.length > 0 ? ` (${apiParts.join("; ")})` : "";
636
+ return new PlatformError(ErrorCode.FeatureUnavailable, [
637
+ `${featureLabel} is a Preview feature and isn't available for this Neon project${apiContext}.`,
638
+ previewUnavailableHint(status),
639
+ "If you don't need it, remove the corresponding feature from the `preview` block of your neon.ts and re-run."
640
+ ].join(" "), {
509
641
  cause: err,
510
- details: { feature: featureLabel }
642
+ details: {
643
+ feature: featureLabel,
644
+ ...status !== void 0 ? { status } : {},
645
+ ...requestId !== void 0 ? { requestId } : {}
646
+ }
511
647
  });
512
648
  }
513
649
  function neonAuthResponseToSnapshot(data) {