@neondatabase/config 0.5.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -1
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/define-config.d.ts +27 -4
- package/dist/lib/define-config.d.ts.map +1 -1
- package/dist/lib/define-config.js +36 -1
- package/dist/lib/define-config.js.map +1 -1
- package/dist/lib/diff.d.ts +22 -8
- package/dist/lib/diff.d.ts.map +1 -1
- package/dist/lib/diff.js +101 -19
- package/dist/lib/diff.js.map +1 -1
- package/dist/lib/duration.js.map +1 -1
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/loader.js.map +1 -1
- package/dist/lib/neon-api-real.d.ts +6 -0
- package/dist/lib/neon-api-real.d.ts.map +1 -1
- package/dist/lib/neon-api-real.js +136 -42
- package/dist/lib/neon-api-real.js.map +1 -1
- package/dist/lib/neon-api.d.ts +35 -14
- package/dist/lib/neon-api.d.ts.map +1 -1
- package/dist/lib/patterns.js.map +1 -1
- package/dist/lib/schema.d.ts +78 -1
- package/dist/lib/schema.d.ts.map +1 -1
- package/dist/lib/schema.js +73 -2
- package/dist/lib/schema.js.map +1 -1
- package/dist/lib/types.d.ts +136 -4
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +18 -1
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/wrap-neon-error.js.map +1 -1
- package/dist/v1.d.ts +69 -3
- package/dist/v1.d.ts.map +1 -1
- package/dist/v1.js +14 -5
- package/dist/v1.js.map +1 -1
- package/package.json +1 -1
package/dist/lib/duration.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"duration.js","names":[],"sources":["../../src/lib/duration.ts"],"sourcesContent":["import type { DurationString } from \"./types.js\";\n\n/**\n * Parse a duration value into whole seconds.\n *\n * Accepted formats:\n * - a positive finite **number** → interpreted as seconds (must be an integer)\n * - a **string** of the form `<integer><unit>` where unit is one of `s`, `m`, `h`, `d`, `w`\n * (e.g. `30s`, `5m`, `1h`, `7d`, `2w`)\n *\n * A **unit is required** on strings: a bare numeric string like `\"7\"` is rejected — pass a\n * `number` (`7`) for raw seconds instead. This removes the ambiguity where `\"7\"` silently\n * meant 7 seconds rather than, say, `\"7d\"`.\n *\n * Returns `{ seconds }` on success or `{ error }` on failure. Pure function — never throws.\n */\nexport function parseDuration(\n\tinput: string | number,\n): { seconds: number } | { error: string } {\n\tif (typeof input === \"number\") {\n\t\tif (!Number.isFinite(input))\n\t\t\treturn { error: `not a finite number: ${input}` };\n\t\tif (!Number.isInteger(input))\n\t\t\treturn {\n\t\t\t\terror: `must be an integer when passed as number: ${input}`,\n\t\t\t};\n\t\tif (input <= 0) return { error: `must be > 0, got ${input}` };\n\t\treturn { seconds: input };\n\t}\n\n\tconst trimmed = input.trim();\n\tif (trimmed === \"\") return { error: \"duration string is empty\" };\n\n\t// A bare numeric string is rejected on purpose: pass a number for raw seconds, or add a\n\t// unit (e.g. \"7d\"). Detected explicitly so we can give a targeted hint instead of the\n\t// generic \"invalid duration\" message.\n\tif (/^\\d+$/.test(trimmed)) {\n\t\treturn {\n\t\t\terror: `duration string \"${input}\" is missing a unit; add one of s, m, h, d, w (e.g. \"${trimmed}d\") or pass ${trimmed} as a number for seconds`,\n\t\t};\n\t}\n\n\tconst unitMatch = /^(\\d+)([smhdw])$/i.exec(trimmed);\n\tif (!unitMatch) {\n\t\treturn {\n\t\t\terror: `invalid duration \"${input}\"; expected an integer followed by one of: s, m, h, d, w (e.g. \"30s\", \"1h\", \"7d\")`,\n\t\t};\n\t}\n\n\tconst value = Number(unitMatch[1]);\n\tconst unit = unitMatch[2].toLowerCase() as \"s\" | \"m\" | \"h\" | \"d\" | \"w\";\n\tif (value <= 0) return { error: `must be > 0, got \"${trimmed}\"` };\n\n\tconst seconds = value * UNIT_SECONDS[unit];\n\treturn { seconds };\n}\n\nconst UNIT_SECONDS = {\n\ts: 1,\n\tm: 60,\n\th: 60 * 60,\n\td: 24 * 60 * 60,\n\tw: 7 * 24 * 60 * 60,\n} as const;\n\n/** Neon's branch-expiration ceiling: the API rejects an `expires_at` more than 30 days out. */\nexport const MAX_BRANCH_TTL_SECONDS = 30 * UNIT_SECONDS.d;\n\n/**\n * Parse a branch TTL into seconds, enforcing Neon's branch-expiration limit on top of the\n * shared {@link parseDuration} rules: the result must be `> 0` and at most 30 days\n * ({@link MAX_BRANCH_TTL_SECONDS}), since the API caps `expires_at` at 30 days from now.\n *\n * Returns `{ seconds }` on success or `{ error }` on failure. Pure function — never throws.\n */\nexport function parseBranchTtl(\n\tinput: string | number,\n): { seconds: number } | { error: string } {\n\tconst result = parseDuration(input);\n\tif (\"error\" in result) return result;\n\tif (result.seconds > MAX_BRANCH_TTL_SECONDS) {\n\t\treturn {\n\t\t\terror: `branch TTL must be at most 30 days (${MAX_BRANCH_TTL_SECONDS}s), got ${result.seconds}s`,\n\t\t};\n\t}\n\treturn result;\n}\n\n/**\n * Render a TTL in seconds back to the canonical \"<n><unit>\" form. Used for round-trip\n * serialization when {@link pullConfig} emits a TTL value (it always falls back to seconds\n * when no clean unit boundary matches). The output always carries a unit, so it is a valid\n * {@link DurationString}.\n */\nexport function formatDurationSeconds(totalSeconds: number): DurationString {\n\tif (!Number.isFinite(totalSeconds) || totalSeconds <= 0) {\n\t\tthrow new RangeError(\n\t\t\t`formatDurationSeconds expected a positive finite number, got ${totalSeconds}`,\n\t\t);\n\t}\n\tconst candidates = [\n\t\t[\"w\", UNIT_SECONDS.w],\n\t\t[\"d\", UNIT_SECONDS.d],\n\t\t[\"h\", UNIT_SECONDS.h],\n\t\t[\"m\", UNIT_SECONDS.m],\n\t] as const;\n\tfor (const [unit, perUnit] of candidates) {\n\t\tif (totalSeconds % perUnit === 0) {\n\t\t\treturn `${totalSeconds / perUnit}${unit}`;\n\t\t}\n\t}\n\treturn `${totalSeconds}s`;\n}\n\n/**\n * Parse a suspend timeout value into seconds for the Neon API.\n *\n * Accepted formats:\n * - `false` → -1 (never suspend)\n * - `undefined` → 0 (use platform default)\n * - duration string → parsed seconds (\"5m\", \"1h\", \"7d\")\n * - number → validated seconds (must be 60-604800 or -1/0)\n *\n * Returns `{ seconds }` on success or `{ error }` on failure. Pure function — never throws.\n */\nexport function parseSuspendTimeout(\n\tinput: false | string | number | undefined,\n): { seconds: number } | { error: string } {\n\t// false means \"never suspend\"\n\tif (input === false) return { seconds: -1 };\n\n\t// undefined means \"use platform default\"\n\tif (input === undefined) return { seconds: 0 };\n\n\t// If it's a number, validate the range\n\tif (typeof input === \"number\") {\n\t\tif (!Number.isFinite(input))\n\t\t\treturn { error: `not a finite number: ${input}` };\n\t\tif (!Number.isInteger(input))\n\t\t\treturn { error: `must be an integer: ${input}` };\n\n\t\t// Allow special values: -1 (never), 0 (default)\n\t\tif (input === -1 || input === 0) return { seconds: input };\n\n\t\t// Validate range for custom timeout: 60s (1 min) to 604800s (1 week)\n\t\tif (input < 60 || input > 604_800) {\n\t\t\treturn {\n\t\t\t\terror: `suspend timeout must be between 60 and 604800 seconds (1 minute to 1 week), got ${input}`,\n\t\t\t};\n\t\t}\n\t\treturn { seconds: input };\n\t}\n\n\t// Parse duration string\n\tconst result = parseDuration(input);\n\tif (\"error\" in result) return result;\n\n\t// Validate the parsed duration is in the valid range\n\tconst { seconds } = result;\n\tif (seconds < 60 || seconds > 604_800) {\n\t\treturn {\n\t\t\terror: `suspend timeout must be between 60 and 604800 seconds (1 minute to 1 week), \"${input}\" = ${seconds}s`,\n\t\t};\n\t}\n\n\treturn { seconds };\n}\n\n/**\n * Format a suspend timeout value from API seconds back to the user-facing type.\n * Returns `false` for -1 (never suspend), `undefined` for 0 (default), or a duration string.\n */\nexport function formatSuspendTimeout(\n\tseconds: number,\n): false | DurationString | undefined {\n\tif (seconds === -1) return false; // never suspend\n\tif (seconds === 0) return undefined; // platform default\n\treturn formatDurationSeconds(seconds);\n}\n"],"mappings":";;;;;;;;;;;;;;;AAgBA,SAAgB,cACf,OAC0C;CAC1C,IAAI,OAAO,UAAU,UAAU;EAC9B,IAAI,CAAC,OAAO,SAAS,KAAK,GACzB,OAAO,EAAE,OAAO,wBAAwB,QAAQ;EACjD,IAAI,CAAC,OAAO,UAAU,KAAK,GAC1B,OAAO,EACN,OAAO,6CAA6C,QACrD;EACD,IAAI,SAAS,GAAG,OAAO,EAAE,OAAO,oBAAoB,QAAQ;EAC5D,OAAO,EAAE,SAAS,MAAM;CACzB;CAEA,MAAM,UAAU,MAAM,KAAK;CAC3B,IAAI,YAAY,IAAI,OAAO,EAAE,OAAO,2BAA2B;CAK/D,IAAI,QAAQ,KAAK,OAAO,GACvB,OAAO,EACN,OAAO,oBAAoB,MAAM,uDAAuD,QAAQ,cAAc,QAAQ,0BACvH;CAGD,MAAM,YAAY,oBAAoB,KAAK,OAAO;CAClD,IAAI,CAAC,WACJ,OAAO,EACN,OAAO,qBAAqB,MAAM,mFACnC;CAGD,MAAM,QAAQ,OAAO,UAAU,EAAE;CACjC,MAAM,OAAO,UAAU,
|
|
1
|
+
{"version":3,"file":"duration.js","names":[],"sources":["../../src/lib/duration.ts"],"sourcesContent":["import type { DurationString } from \"./types.js\";\n\n/**\n * Parse a duration value into whole seconds.\n *\n * Accepted formats:\n * - a positive finite **number** → interpreted as seconds (must be an integer)\n * - a **string** of the form `<integer><unit>` where unit is one of `s`, `m`, `h`, `d`, `w`\n * (e.g. `30s`, `5m`, `1h`, `7d`, `2w`)\n *\n * A **unit is required** on strings: a bare numeric string like `\"7\"` is rejected — pass a\n * `number` (`7`) for raw seconds instead. This removes the ambiguity where `\"7\"` silently\n * meant 7 seconds rather than, say, `\"7d\"`.\n *\n * Returns `{ seconds }` on success or `{ error }` on failure. Pure function — never throws.\n */\nexport function parseDuration(\n\tinput: string | number,\n): { seconds: number } | { error: string } {\n\tif (typeof input === \"number\") {\n\t\tif (!Number.isFinite(input))\n\t\t\treturn { error: `not a finite number: ${input}` };\n\t\tif (!Number.isInteger(input))\n\t\t\treturn {\n\t\t\t\terror: `must be an integer when passed as number: ${input}`,\n\t\t\t};\n\t\tif (input <= 0) return { error: `must be > 0, got ${input}` };\n\t\treturn { seconds: input };\n\t}\n\n\tconst trimmed = input.trim();\n\tif (trimmed === \"\") return { error: \"duration string is empty\" };\n\n\t// A bare numeric string is rejected on purpose: pass a number for raw seconds, or add a\n\t// unit (e.g. \"7d\"). Detected explicitly so we can give a targeted hint instead of the\n\t// generic \"invalid duration\" message.\n\tif (/^\\d+$/.test(trimmed)) {\n\t\treturn {\n\t\t\terror: `duration string \"${input}\" is missing a unit; add one of s, m, h, d, w (e.g. \"${trimmed}d\") or pass ${trimmed} as a number for seconds`,\n\t\t};\n\t}\n\n\tconst unitMatch = /^(\\d+)([smhdw])$/i.exec(trimmed);\n\tif (!unitMatch) {\n\t\treturn {\n\t\t\terror: `invalid duration \"${input}\"; expected an integer followed by one of: s, m, h, d, w (e.g. \"30s\", \"1h\", \"7d\")`,\n\t\t};\n\t}\n\n\tconst value = Number(unitMatch[1]);\n\tconst unit = unitMatch[2].toLowerCase() as \"s\" | \"m\" | \"h\" | \"d\" | \"w\";\n\tif (value <= 0) return { error: `must be > 0, got \"${trimmed}\"` };\n\n\tconst seconds = value * UNIT_SECONDS[unit];\n\treturn { seconds };\n}\n\nconst UNIT_SECONDS = {\n\ts: 1,\n\tm: 60,\n\th: 60 * 60,\n\td: 24 * 60 * 60,\n\tw: 7 * 24 * 60 * 60,\n} as const;\n\n/** Neon's branch-expiration ceiling: the API rejects an `expires_at` more than 30 days out. */\nexport const MAX_BRANCH_TTL_SECONDS = 30 * UNIT_SECONDS.d;\n\n/**\n * Parse a branch TTL into seconds, enforcing Neon's branch-expiration limit on top of the\n * shared {@link parseDuration} rules: the result must be `> 0` and at most 30 days\n * ({@link MAX_BRANCH_TTL_SECONDS}), since the API caps `expires_at` at 30 days from now.\n *\n * Returns `{ seconds }` on success or `{ error }` on failure. Pure function — never throws.\n */\nexport function parseBranchTtl(\n\tinput: string | number,\n): { seconds: number } | { error: string } {\n\tconst result = parseDuration(input);\n\tif (\"error\" in result) return result;\n\tif (result.seconds > MAX_BRANCH_TTL_SECONDS) {\n\t\treturn {\n\t\t\terror: `branch TTL must be at most 30 days (${MAX_BRANCH_TTL_SECONDS}s), got ${result.seconds}s`,\n\t\t};\n\t}\n\treturn result;\n}\n\n/**\n * Render a TTL in seconds back to the canonical \"<n><unit>\" form. Used for round-trip\n * serialization when {@link pullConfig} emits a TTL value (it always falls back to seconds\n * when no clean unit boundary matches). The output always carries a unit, so it is a valid\n * {@link DurationString}.\n */\nexport function formatDurationSeconds(totalSeconds: number): DurationString {\n\tif (!Number.isFinite(totalSeconds) || totalSeconds <= 0) {\n\t\tthrow new RangeError(\n\t\t\t`formatDurationSeconds expected a positive finite number, got ${totalSeconds}`,\n\t\t);\n\t}\n\tconst candidates = [\n\t\t[\"w\", UNIT_SECONDS.w],\n\t\t[\"d\", UNIT_SECONDS.d],\n\t\t[\"h\", UNIT_SECONDS.h],\n\t\t[\"m\", UNIT_SECONDS.m],\n\t] as const;\n\tfor (const [unit, perUnit] of candidates) {\n\t\tif (totalSeconds % perUnit === 0) {\n\t\t\treturn `${totalSeconds / perUnit}${unit}`;\n\t\t}\n\t}\n\treturn `${totalSeconds}s`;\n}\n\n/**\n * Parse a suspend timeout value into seconds for the Neon API.\n *\n * Accepted formats:\n * - `false` → -1 (never suspend)\n * - `undefined` → 0 (use platform default)\n * - duration string → parsed seconds (\"5m\", \"1h\", \"7d\")\n * - number → validated seconds (must be 60-604800 or -1/0)\n *\n * Returns `{ seconds }` on success or `{ error }` on failure. Pure function — never throws.\n */\nexport function parseSuspendTimeout(\n\tinput: false | string | number | undefined,\n): { seconds: number } | { error: string } {\n\t// false means \"never suspend\"\n\tif (input === false) return { seconds: -1 };\n\n\t// undefined means \"use platform default\"\n\tif (input === undefined) return { seconds: 0 };\n\n\t// If it's a number, validate the range\n\tif (typeof input === \"number\") {\n\t\tif (!Number.isFinite(input))\n\t\t\treturn { error: `not a finite number: ${input}` };\n\t\tif (!Number.isInteger(input))\n\t\t\treturn { error: `must be an integer: ${input}` };\n\n\t\t// Allow special values: -1 (never), 0 (default)\n\t\tif (input === -1 || input === 0) return { seconds: input };\n\n\t\t// Validate range for custom timeout: 60s (1 min) to 604800s (1 week)\n\t\tif (input < 60 || input > 604_800) {\n\t\t\treturn {\n\t\t\t\terror: `suspend timeout must be between 60 and 604800 seconds (1 minute to 1 week), got ${input}`,\n\t\t\t};\n\t\t}\n\t\treturn { seconds: input };\n\t}\n\n\t// Parse duration string\n\tconst result = parseDuration(input);\n\tif (\"error\" in result) return result;\n\n\t// Validate the parsed duration is in the valid range\n\tconst { seconds } = result;\n\tif (seconds < 60 || seconds > 604_800) {\n\t\treturn {\n\t\t\terror: `suspend timeout must be between 60 and 604800 seconds (1 minute to 1 week), \"${input}\" = ${seconds}s`,\n\t\t};\n\t}\n\n\treturn { seconds };\n}\n\n/**\n * Format a suspend timeout value from API seconds back to the user-facing type.\n * Returns `false` for -1 (never suspend), `undefined` for 0 (default), or a duration string.\n */\nexport function formatSuspendTimeout(\n\tseconds: number,\n): false | DurationString | undefined {\n\tif (seconds === -1) return false; // never suspend\n\tif (seconds === 0) return undefined; // platform default\n\treturn formatDurationSeconds(seconds);\n}\n"],"mappings":"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"}
|
package/dist/lib/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","names":[],"sources":["../../src/lib/errors.ts"],"sourcesContent":["import type { ConflictReport } from \"./types.js\";\n\n/**\n * Every code a {@link PlatformError} can carry. Stable identifiers — consumers can rely on\n * these for `instanceof PlatformError && err.code === ErrorCode.…` style checks instead of\n * matching on free-text messages.\n *\n * Grouped by source:\n * - `PLATFORM_INVALID_CONFIG` — `defineConfig` / `configSchema` rejected the input.\n * - `PLATFORM_MISSING_CONTEXT` — no project / branch context could be resolved.\n * - `PLATFORM_PUSH_CONFLICT` — local config conflicts with remote and the caller did not\n * opt in to apply.\n * - `PLATFORM_CONFIG_LOAD_FAILED` — `neon.ts` could not be found or evaluated.\n * - `PLATFORM_MISSING_API_KEY` — no `NEON_API_KEY` and no explicit `apiKey` was provided.\n * - `PLATFORM_MISSING_PARENT_BRANCH` — push tried to create a child of a non-existent\n * branch.\n * - `PLATFORM_UNAUTHORIZED` / `PLATFORM_FORBIDDEN` / `PLATFORM_NOT_FOUND` /\n * `PLATFORM_CONFLICT` / `PLATFORM_RATE_LIMITED` / `PLATFORM_LOCKED` /\n * `PLATFORM_SERVER_ERROR` — wrappings of Neon HTTP failures.\n * - `PLATFORM_NETWORK_ERROR` — transport-level failure (no HTTP response at all).\n * - `PLATFORM_INTERNAL_ERROR` — invariant violations. Should never happen in production;\n * if you see one, please open an issue.\n */\nexport const ErrorCode = {\n\tInvalidConfig: \"PLATFORM_INVALID_CONFIG\",\n\tEnvNotInjected: \"PLATFORM_ENV_NOT_INJECTED\",\n\tMissingContext: \"PLATFORM_MISSING_CONTEXT\",\n\tPushConflict: \"PLATFORM_PUSH_CONFLICT\",\n\tPushAborted: \"PLATFORM_PUSH_ABORTED\",\n\tConfigLoadFailed: \"PLATFORM_CONFIG_LOAD_FAILED\",\n\tMissingApiKey: \"PLATFORM_MISSING_API_KEY\",\n\tAmbiguousBranchAuth: \"PLATFORM_AMBIGUOUS_BRANCH_AUTH\",\n\tBranchNotFound: \"PLATFORM_BRANCH_NOT_FOUND\",\n\tFeatureUnavailable: \"PLATFORM_FEATURE_UNAVAILABLE\",\n\tMissingParentBranch: \"PLATFORM_MISSING_PARENT_BRANCH\",\n\tUnauthorized: \"PLATFORM_UNAUTHORIZED\",\n\tForbidden: \"PLATFORM_FORBIDDEN\",\n\tNotFound: \"PLATFORM_NOT_FOUND\",\n\tConflict: \"PLATFORM_CONFLICT\",\n\tRateLimited: \"PLATFORM_RATE_LIMITED\",\n\tLocked: \"PLATFORM_LOCKED\",\n\tServerError: \"PLATFORM_SERVER_ERROR\",\n\tNetworkError: \"PLATFORM_NETWORK_ERROR\",\n\tInternalError: \"PLATFORM_INTERNAL_ERROR\",\n} as const;\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n\nconst ISSUE_URL = \"https://github.com/neondatabase/neon-pkgs/issues/new\";\n\n/**\n * Base class for all errors thrown by `@neondatabase/config`. Always extend this so callers\n * can catch every package-thrown error with a single `instanceof` check.\n *\n * Optional `details` carries structured context that the CLI prints under `--debug` and\n * that programmatic consumers can read (e.g. `details.status` for HTTP wrappings,\n * `details.requestId` for Neon API failures).\n */\nexport class PlatformError extends Error {\n\toverride readonly name: string = \"PlatformError\";\n\treadonly code: string;\n\treadonly details: Readonly<Record<string, unknown>>;\n\n\tconstructor(\n\t\tcode: string,\n\t\tmessage: string,\n\t\toptions?: { cause?: unknown; details?: Record<string, unknown> },\n\t) {\n\t\tsuper(message, options);\n\t\tthis.code = code;\n\t\tthis.details = Object.freeze({ ...(options?.details ?? {}) });\n\t}\n}\n\n/**\n * Structural check for a {@link PlatformError}.\n *\n * Prefer this over a bare `instanceof PlatformError` whenever the error may have crossed a\n * module-realm boundary. A `neon.ts` loaded through jiti imports its *own* copy of this\n * package, so a `PlatformError` thrown while evaluating it has a different class identity\n * than ours and fails `instanceof` — but its stable string `code` (always prefixed\n * `PLATFORM_`) survives the boundary intact, which is what we match on here.\n */\nexport function isPlatformError(value: unknown): value is PlatformError {\n\tif (value instanceof PlatformError) return true;\n\tif (typeof value !== \"object\" || value === null) return false;\n\tif (!(\"code\" in value)) return false;\n\tconst { code } = value;\n\treturn typeof code === \"string\" && code.startsWith(\"PLATFORM_\");\n}\n\n/**\n * Append a \"report-a-bug\" footer to an error message. Used only on truly unreachable\n * internal errors — never on user-facing validation / configuration errors where the user\n * is supposed to fix something on their end.\n */\nexport function bugReportFooter(): string {\n\treturn `\\nThis indicates a bug in @neondatabase/config. Please file an issue: ${ISSUE_URL}`;\n}\n\n/**\n * Thrown by {@link defineConfig} when the user-provided configuration object is invalid.\n *\n * The class collects every validation failure rather than throwing on the first one so that\n * users get a complete picture of what is wrong with their `neon.ts`.\n */\nexport class ConfigValidationError extends PlatformError {\n\toverride readonly name = \"ConfigValidationError\";\n\treadonly issues: readonly string[];\n\n\tconstructor(issues: readonly string[]) {\n\t\tsuper(\n\t\t\t\"PLATFORM_INVALID_CONFIG\",\n\t\t\t`Invalid Neon platform config:\\n - ${issues.join(\"\\n - \")}`,\n\t\t);\n\t\tthis.issues = issues;\n\t}\n}\n\n/**\n * Thrown when the package cannot resolve which Neon project to operate on.\n *\n * Per the package's read-only-filesystem contract, we never create a `.neon` context file;\n * callers must either pass `projectId`/`orgId` explicitly or rely on an existing context file\n * (`.neon/project.json` or neonctl's `.neon`).\n */\nexport class MissingContextError extends PlatformError {\n\toverride readonly name = \"MissingContextError\";\n\n\tconstructor(message: string) {\n\t\tsuper(\"PLATFORM_MISSING_CONTEXT\", message);\n\t}\n}\n\n/**\n * Thrown by {@link pushConfig} when it detects differences between the local config and\n * the remote project that the caller hasn't opted in to apply.\n *\n * The message lists every conflict with both the current and desired value plus a\n * per-conflict hint. Mutable branch drift is applied by passing `updateExisting: true`.\n */\nexport class PushConflictError extends PlatformError {\n\toverride readonly name = \"PushConflictError\";\n\treadonly conflicts: readonly ConflictReport[];\n\n\tconstructor(conflicts: readonly ConflictReport[]) {\n\t\tconst lines: string[] = [\n\t\t\t\"pushConfig refused to apply: local config conflicts with remote state.\",\n\t\t\t\"\",\n\t\t];\n\t\tfor (const c of conflicts) {\n\t\t\tlines.push(\n\t\t\t\t` - [${c.kind}:${c.identifier}] ${c.field}: ${c.reason}`,\n\t\t\t\t` current : ${formatValue(c.current)}`,\n\t\t\t\t` desired : ${formatValue(c.desired)}`,\n\t\t\t\t` fix : ${suggestFix(c)}`,\n\t\t\t);\n\t\t}\n\t\tconst hasMutable = conflicts.some((c) => !isImmutableConflict(c));\n\t\tlines.push(\"\");\n\t\tif (hasMutable) {\n\t\t\tlines.push(\n\t\t\t\t\"For mutable conflicts, pass `updateExisting: true` (SDK) / `--update-existing` (CLI) to apply.\",\n\t\t\t);\n\t\t}\n\n\t\tsuper(\"PLATFORM_PUSH_CONFLICT\", lines.join(\"\\n\"), {\n\t\t\tdetails: { conflicts: conflicts.map((c) => ({ ...c })) },\n\t\t});\n\t\tthis.conflicts = conflicts;\n\t}\n}\n\nfunction isImmutableConflict(_c: ConflictReport): boolean {\n\treturn false;\n}\n\nfunction suggestFix(c: ConflictReport): string {\n\tif (isImmutableConflict(c)) {\n\t\treturn \"immutable on Neon — recreate the project, or change your `neon.ts` to match the remote.\";\n\t}\n\tif (c.kind === \"branch\" && c.field === \"parent\") {\n\t\treturn \"create the parent branch on Neon first, or change the `parent` reference to an existing branch.\";\n\t}\n\treturn \"pass `updateExisting: true` (SDK) / `--update-existing` (CLI) to apply.\";\n}\n\n/**\n * Thrown by {@link pushConfig} when the caller-supplied `confirm` callback declines a\n * push that requires confirmation (protected branch and/or mutable drift overriding\n * existing remote settings).\n *\n * The CLI maps this to a non-zero exit so users see \"aborted\" rather than a stack trace.\n */\nexport class PushAbortedError extends PlatformError {\n\toverride readonly name = \"PushAbortedError\";\n\treadonly branchName: string;\n\treadonly reasons: readonly (\"protected-branch\" | \"override-updates\")[];\n\n\tconstructor(\n\t\tbranchName: string,\n\t\treasons: readonly (\"protected-branch\" | \"override-updates\")[],\n\t) {\n\t\tsuper(\n\t\t\t\"PLATFORM_PUSH_ABORTED\",\n\t\t\t[\n\t\t\t\t`Aborted push to branch ${JSON.stringify(branchName)}.`,\n\t\t\t\treasons.length > 0\n\t\t\t\t\t? `Reason${reasons.length === 1 ? \"\" : \"s\"}: ${reasons.join(\", \")}.`\n\t\t\t\t\t: undefined,\n\t\t\t\t\"Re-run with `--update-existing` (override existing settings) or `--allow-protected-branch` (push to a protected branch) to skip the prompt.\",\n\t\t\t]\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.join(\" \"),\n\t\t\t{ details: { branchName, reasons: [...reasons] } },\n\t\t);\n\t\tthis.branchName = branchName;\n\t\tthis.reasons = reasons;\n\t}\n}\n\n/**\n * Thrown when the SDK fails to find or load a `neon.ts` config file.\n */\nexport class ConfigLoadError extends PlatformError {\n\toverride readonly name = \"ConfigLoadError\";\n\n\tconstructor(message: string, options?: { cause?: unknown }) {\n\t\tsuper(\"PLATFORM_CONFIG_LOAD_FAILED\", message, options);\n\t}\n}\n\nfunction formatValue(value: unknown): string {\n\tif (value === undefined) return \"<unset>\";\n\tif (typeof value === \"string\") return JSON.stringify(value);\n\tif (typeof value === \"object\" && value !== null)\n\t\treturn JSON.stringify(value);\n\treturn String(value);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"errors.js","names":[],"sources":["../../src/lib/errors.ts"],"sourcesContent":["import type { ConflictReport } from \"./types.js\";\n\n/**\n * Every code a {@link PlatformError} can carry. Stable identifiers — consumers can rely on\n * these for `instanceof PlatformError && err.code === ErrorCode.…` style checks instead of\n * matching on free-text messages.\n *\n * Grouped by source:\n * - `PLATFORM_INVALID_CONFIG` — `defineConfig` / `configSchema` rejected the input.\n * - `PLATFORM_MISSING_CONTEXT` — no project / branch context could be resolved.\n * - `PLATFORM_PUSH_CONFLICT` — local config conflicts with remote and the caller did not\n * opt in to apply.\n * - `PLATFORM_CONFIG_LOAD_FAILED` — `neon.ts` could not be found or evaluated.\n * - `PLATFORM_MISSING_API_KEY` — no `NEON_API_KEY` and no explicit `apiKey` was provided.\n * - `PLATFORM_MISSING_PARENT_BRANCH` — push tried to create a child of a non-existent\n * branch.\n * - `PLATFORM_UNAUTHORIZED` / `PLATFORM_FORBIDDEN` / `PLATFORM_NOT_FOUND` /\n * `PLATFORM_CONFLICT` / `PLATFORM_RATE_LIMITED` / `PLATFORM_LOCKED` /\n * `PLATFORM_SERVER_ERROR` — wrappings of Neon HTTP failures.\n * - `PLATFORM_NETWORK_ERROR` — transport-level failure (no HTTP response at all).\n * - `PLATFORM_INTERNAL_ERROR` — invariant violations. Should never happen in production;\n * if you see one, please open an issue.\n */\nexport const ErrorCode = {\n\tInvalidConfig: \"PLATFORM_INVALID_CONFIG\",\n\tEnvNotInjected: \"PLATFORM_ENV_NOT_INJECTED\",\n\tMissingContext: \"PLATFORM_MISSING_CONTEXT\",\n\tPushConflict: \"PLATFORM_PUSH_CONFLICT\",\n\tPushAborted: \"PLATFORM_PUSH_ABORTED\",\n\tConfigLoadFailed: \"PLATFORM_CONFIG_LOAD_FAILED\",\n\tMissingApiKey: \"PLATFORM_MISSING_API_KEY\",\n\tAmbiguousBranchAuth: \"PLATFORM_AMBIGUOUS_BRANCH_AUTH\",\n\tBranchNotFound: \"PLATFORM_BRANCH_NOT_FOUND\",\n\tFeatureUnavailable: \"PLATFORM_FEATURE_UNAVAILABLE\",\n\tMissingParentBranch: \"PLATFORM_MISSING_PARENT_BRANCH\",\n\tUnauthorized: \"PLATFORM_UNAUTHORIZED\",\n\tForbidden: \"PLATFORM_FORBIDDEN\",\n\tNotFound: \"PLATFORM_NOT_FOUND\",\n\tConflict: \"PLATFORM_CONFLICT\",\n\tRateLimited: \"PLATFORM_RATE_LIMITED\",\n\tLocked: \"PLATFORM_LOCKED\",\n\tServerError: \"PLATFORM_SERVER_ERROR\",\n\tNetworkError: \"PLATFORM_NETWORK_ERROR\",\n\tInternalError: \"PLATFORM_INTERNAL_ERROR\",\n} as const;\nexport type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];\n\nconst ISSUE_URL = \"https://github.com/neondatabase/neon-pkgs/issues/new\";\n\n/**\n * Base class for all errors thrown by `@neondatabase/config`. Always extend this so callers\n * can catch every package-thrown error with a single `instanceof` check.\n *\n * Optional `details` carries structured context that the CLI prints under `--debug` and\n * that programmatic consumers can read (e.g. `details.status` for HTTP wrappings,\n * `details.requestId` for Neon API failures).\n */\nexport class PlatformError extends Error {\n\toverride readonly name: string = \"PlatformError\";\n\treadonly code: string;\n\treadonly details: Readonly<Record<string, unknown>>;\n\n\tconstructor(\n\t\tcode: string,\n\t\tmessage: string,\n\t\toptions?: { cause?: unknown; details?: Record<string, unknown> },\n\t) {\n\t\tsuper(message, options);\n\t\tthis.code = code;\n\t\tthis.details = Object.freeze({ ...(options?.details ?? {}) });\n\t}\n}\n\n/**\n * Structural check for a {@link PlatformError}.\n *\n * Prefer this over a bare `instanceof PlatformError` whenever the error may have crossed a\n * module-realm boundary. A `neon.ts` loaded through jiti imports its *own* copy of this\n * package, so a `PlatformError` thrown while evaluating it has a different class identity\n * than ours and fails `instanceof` — but its stable string `code` (always prefixed\n * `PLATFORM_`) survives the boundary intact, which is what we match on here.\n */\nexport function isPlatformError(value: unknown): value is PlatformError {\n\tif (value instanceof PlatformError) return true;\n\tif (typeof value !== \"object\" || value === null) return false;\n\tif (!(\"code\" in value)) return false;\n\tconst { code } = value;\n\treturn typeof code === \"string\" && code.startsWith(\"PLATFORM_\");\n}\n\n/**\n * Append a \"report-a-bug\" footer to an error message. Used only on truly unreachable\n * internal errors — never on user-facing validation / configuration errors where the user\n * is supposed to fix something on their end.\n */\nexport function bugReportFooter(): string {\n\treturn `\\nThis indicates a bug in @neondatabase/config. Please file an issue: ${ISSUE_URL}`;\n}\n\n/**\n * Thrown by {@link defineConfig} when the user-provided configuration object is invalid.\n *\n * The class collects every validation failure rather than throwing on the first one so that\n * users get a complete picture of what is wrong with their `neon.ts`.\n */\nexport class ConfigValidationError extends PlatformError {\n\toverride readonly name = \"ConfigValidationError\";\n\treadonly issues: readonly string[];\n\n\tconstructor(issues: readonly string[]) {\n\t\tsuper(\n\t\t\t\"PLATFORM_INVALID_CONFIG\",\n\t\t\t`Invalid Neon platform config:\\n - ${issues.join(\"\\n - \")}`,\n\t\t);\n\t\tthis.issues = issues;\n\t}\n}\n\n/**\n * Thrown when the package cannot resolve which Neon project to operate on.\n *\n * Per the package's read-only-filesystem contract, we never create a `.neon` context file;\n * callers must either pass `projectId`/`orgId` explicitly or rely on an existing context file\n * (`.neon/project.json` or neonctl's `.neon`).\n */\nexport class MissingContextError extends PlatformError {\n\toverride readonly name = \"MissingContextError\";\n\n\tconstructor(message: string) {\n\t\tsuper(\"PLATFORM_MISSING_CONTEXT\", message);\n\t}\n}\n\n/**\n * Thrown by {@link pushConfig} when it detects differences between the local config and\n * the remote project that the caller hasn't opted in to apply.\n *\n * The message lists every conflict with both the current and desired value plus a\n * per-conflict hint. Mutable branch drift is applied by passing `updateExisting: true`.\n */\nexport class PushConflictError extends PlatformError {\n\toverride readonly name = \"PushConflictError\";\n\treadonly conflicts: readonly ConflictReport[];\n\n\tconstructor(conflicts: readonly ConflictReport[]) {\n\t\tconst lines: string[] = [\n\t\t\t\"pushConfig refused to apply: local config conflicts with remote state.\",\n\t\t\t\"\",\n\t\t];\n\t\tfor (const c of conflicts) {\n\t\t\tlines.push(\n\t\t\t\t` - [${c.kind}:${c.identifier}] ${c.field}: ${c.reason}`,\n\t\t\t\t` current : ${formatValue(c.current)}`,\n\t\t\t\t` desired : ${formatValue(c.desired)}`,\n\t\t\t\t` fix : ${suggestFix(c)}`,\n\t\t\t);\n\t\t}\n\t\tconst hasMutable = conflicts.some((c) => !isImmutableConflict(c));\n\t\tlines.push(\"\");\n\t\tif (hasMutable) {\n\t\t\tlines.push(\n\t\t\t\t\"For mutable conflicts, pass `updateExisting: true` (SDK) / `--update-existing` (CLI) to apply.\",\n\t\t\t);\n\t\t}\n\n\t\tsuper(\"PLATFORM_PUSH_CONFLICT\", lines.join(\"\\n\"), {\n\t\t\tdetails: { conflicts: conflicts.map((c) => ({ ...c })) },\n\t\t});\n\t\tthis.conflicts = conflicts;\n\t}\n}\n\nfunction isImmutableConflict(_c: ConflictReport): boolean {\n\treturn false;\n}\n\nfunction suggestFix(c: ConflictReport): string {\n\tif (isImmutableConflict(c)) {\n\t\treturn \"immutable on Neon — recreate the project, or change your `neon.ts` to match the remote.\";\n\t}\n\tif (c.kind === \"branch\" && c.field === \"parent\") {\n\t\treturn \"create the parent branch on Neon first, or change the `parent` reference to an existing branch.\";\n\t}\n\treturn \"pass `updateExisting: true` (SDK) / `--update-existing` (CLI) to apply.\";\n}\n\n/**\n * Thrown by {@link pushConfig} when the caller-supplied `confirm` callback declines a\n * push that requires confirmation (protected branch and/or mutable drift overriding\n * existing remote settings).\n *\n * The CLI maps this to a non-zero exit so users see \"aborted\" rather than a stack trace.\n */\nexport class PushAbortedError extends PlatformError {\n\toverride readonly name = \"PushAbortedError\";\n\treadonly branchName: string;\n\treadonly reasons: readonly (\"protected-branch\" | \"override-updates\")[];\n\n\tconstructor(\n\t\tbranchName: string,\n\t\treasons: readonly (\"protected-branch\" | \"override-updates\")[],\n\t) {\n\t\tsuper(\n\t\t\t\"PLATFORM_PUSH_ABORTED\",\n\t\t\t[\n\t\t\t\t`Aborted push to branch ${JSON.stringify(branchName)}.`,\n\t\t\t\treasons.length > 0\n\t\t\t\t\t? `Reason${reasons.length === 1 ? \"\" : \"s\"}: ${reasons.join(\", \")}.`\n\t\t\t\t\t: undefined,\n\t\t\t\t\"Re-run with `--update-existing` (override existing settings) or `--allow-protected-branch` (push to a protected branch) to skip the prompt.\",\n\t\t\t]\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.join(\" \"),\n\t\t\t{ details: { branchName, reasons: [...reasons] } },\n\t\t);\n\t\tthis.branchName = branchName;\n\t\tthis.reasons = reasons;\n\t}\n}\n\n/**\n * Thrown when the SDK fails to find or load a `neon.ts` config file.\n */\nexport class ConfigLoadError extends PlatformError {\n\toverride readonly name = \"ConfigLoadError\";\n\n\tconstructor(message: string, options?: { cause?: unknown }) {\n\t\tsuper(\"PLATFORM_CONFIG_LOAD_FAILED\", message, options);\n\t}\n}\n\nfunction formatValue(value: unknown): string {\n\tif (value === undefined) return \"<unset>\";\n\tif (typeof value === \"string\") return JSON.stringify(value);\n\tif (typeof value === \"object\" && value !== null)\n\t\treturn JSON.stringify(value);\n\treturn String(value);\n}\n"],"mappings":"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"}
|
package/dist/lib/loader.js.map
CHANGED
|
@@ -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, 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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"}
|
|
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":";;;
|
|
1
|
+
{"version":3,"file":"neon-api-real.d.ts","names":[],"sources":["../../src/lib/neon-api-real.ts"],"mappings":";;;UAyOU,uBAAA;;EAAA,aAAA,CAAA,EAAA,MAAA;AAeV;AAyCC;AAeD;;;;AAES,iBA1DO,iBAAA,CA0DP,OAAA,EAAA;QACE,EAAA,MAAA;SAAR,CAAA,EAAA,MAAA;EAAO;AA84BV;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,EAtnC9B,OAsnC8B;UAxlCxB,WAAA,CAwlC8B;aAAW,EAAA,MAAA;EAAO,cAAA,EAAA,MAAA;;;;;;;;;;iBA3kCpC,2BACX,QAAQ,YACV,cACN,QAAQ;;;;;;;;;;;;iBA84BK,2BAAA;;;;;;;;;;;;iBAiFA,uBAAA;iBA6DA,uBAAA;;IAEZ;;;;;;;;;;;;iBAkBY,uBAAA,QAA+B,sBAAsB;;;;;;;;;;;iBAwB/C,YAAA,MAAkB,WAAW"}
|
|
@@ -12,6 +12,66 @@ const neonAuthResponseSchema = z.object({
|
|
|
12
12
|
jwks_url: z.string(),
|
|
13
13
|
base_url: z.string().optional()
|
|
14
14
|
});
|
|
15
|
+
/** Map our camelCase {@link DataApiSettings} onto the Neon API's snake_case `DataAPISettings`. */
|
|
16
|
+
function dataApiSettingsToApi(settings) {
|
|
17
|
+
const out = {};
|
|
18
|
+
if (settings.dbAggregatesEnabled !== void 0) out.db_aggregates_enabled = settings.dbAggregatesEnabled;
|
|
19
|
+
if (settings.dbAnonRole !== void 0) out.db_anon_role = settings.dbAnonRole;
|
|
20
|
+
if (settings.dbExtraSearchPath !== void 0) out.db_extra_search_path = settings.dbExtraSearchPath;
|
|
21
|
+
if (settings.dbMaxRows !== void 0) out.db_max_rows = settings.dbMaxRows;
|
|
22
|
+
if (settings.dbSchemas !== void 0) out.db_schemas = settings.dbSchemas;
|
|
23
|
+
if (settings.jwtRoleClaimKey !== void 0) out.jwt_role_claim_key = settings.jwtRoleClaimKey;
|
|
24
|
+
if (settings.jwtCacheMaxLifetime !== void 0) out.jwt_cache_max_lifetime = settings.jwtCacheMaxLifetime;
|
|
25
|
+
if (settings.openapiMode !== void 0) out.openapi_mode = settings.openapiMode;
|
|
26
|
+
if (settings.serverCorsAllowedOrigins !== void 0) out.server_cors_allowed_origins = settings.serverCorsAllowedOrigins;
|
|
27
|
+
if (settings.serverTimingEnabled !== void 0) out.server_timing_enabled = settings.serverTimingEnabled;
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
/** Narrow the API's free-form `openapi_mode` string to our literal union (else drop it). */
|
|
31
|
+
function normalizeOpenapiMode(value) {
|
|
32
|
+
return value === "ignore-privileges" || value === "disabled" ? value : void 0;
|
|
33
|
+
}
|
|
34
|
+
/** Map the Neon API's snake_case `DataAPISettings` back to our camelCase {@link DataApiSettings}. */
|
|
35
|
+
function dataApiSettingsFromApi(settings) {
|
|
36
|
+
if (!settings) return void 0;
|
|
37
|
+
const out = {};
|
|
38
|
+
if (settings.db_aggregates_enabled !== void 0) out.dbAggregatesEnabled = settings.db_aggregates_enabled;
|
|
39
|
+
if (settings.db_anon_role !== void 0) out.dbAnonRole = settings.db_anon_role;
|
|
40
|
+
if (settings.db_extra_search_path !== void 0) out.dbExtraSearchPath = settings.db_extra_search_path;
|
|
41
|
+
if (settings.db_max_rows !== void 0) out.dbMaxRows = settings.db_max_rows;
|
|
42
|
+
if (settings.db_schemas !== void 0) out.dbSchemas = settings.db_schemas;
|
|
43
|
+
if (settings.jwt_role_claim_key !== void 0) out.jwtRoleClaimKey = settings.jwt_role_claim_key;
|
|
44
|
+
if (settings.jwt_cache_max_lifetime !== void 0) out.jwtCacheMaxLifetime = settings.jwt_cache_max_lifetime;
|
|
45
|
+
if (settings.openapi_mode !== void 0) {
|
|
46
|
+
const mode = normalizeOpenapiMode(settings.openapi_mode);
|
|
47
|
+
if (mode !== void 0) out.openapiMode = mode;
|
|
48
|
+
}
|
|
49
|
+
if (settings.server_cors_allowed_origins !== void 0) out.serverCorsAllowedOrigins = settings.server_cors_allowed_origins;
|
|
50
|
+
if (settings.server_timing_enabled !== void 0) out.serverTimingEnabled = settings.server_timing_enabled;
|
|
51
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
52
|
+
}
|
|
53
|
+
/** Build the Neon API `DataAPICreateRequest` from our {@link EnableDataApiInput}. */
|
|
54
|
+
function dataApiCreateRequest(input) {
|
|
55
|
+
const req = {};
|
|
56
|
+
if (!input) return req;
|
|
57
|
+
if (input.authProvider !== void 0) req.auth_provider = input.authProvider === "neon" ? "neon_auth" : "external";
|
|
58
|
+
if (input.jwksUrl !== void 0) req.jwks_url = input.jwksUrl;
|
|
59
|
+
if (input.providerName !== void 0) req.provider_name = input.providerName;
|
|
60
|
+
if (input.jwtAudience !== void 0) req.jwt_audience = input.jwtAudience;
|
|
61
|
+
if (input.settings) {
|
|
62
|
+
const settings = dataApiSettingsToApi(input.settings);
|
|
63
|
+
if (Object.keys(settings).length > 0) req.settings = settings;
|
|
64
|
+
}
|
|
65
|
+
return req;
|
|
66
|
+
}
|
|
67
|
+
/** Map a `DataAPIReponse` (GET) onto our {@link NeonDataApiSnapshot}. */
|
|
68
|
+
function dataApiSnapshotFromResponse(data) {
|
|
69
|
+
const snapshot = { url: data.url };
|
|
70
|
+
if (data.status !== void 0) snapshot.status = data.status;
|
|
71
|
+
const settings = dataApiSettingsFromApi(data.settings);
|
|
72
|
+
if (settings) snapshot.settings = settings;
|
|
73
|
+
return snapshot;
|
|
74
|
+
}
|
|
15
75
|
const bucketSchema = z.object({
|
|
16
76
|
name: z.string(),
|
|
17
77
|
access_level: z.string().optional()
|
|
@@ -346,18 +406,16 @@ var RealNeonApi = class {
|
|
|
346
406
|
}
|
|
347
407
|
async getNeonDataApi(projectId, branchId, databaseName) {
|
|
348
408
|
try {
|
|
349
|
-
return await this.call(`getNeonDataApi(${projectId}/${branchId}/${databaseName})`, async () => {
|
|
350
|
-
return { url: (await this.client.getProjectBranchDataApi(projectId, branchId, databaseName)).data.url };
|
|
351
|
-
}, { projectId });
|
|
409
|
+
return await this.call(`getNeonDataApi(${projectId}/${branchId}/${databaseName})`, async () => dataApiSnapshotFromResponse(await this.client.getProjectBranchDataApi(projectId, branchId, databaseName).then((res) => res.data)), { projectId });
|
|
352
410
|
} catch (err) {
|
|
353
411
|
if (err instanceof PlatformError && err.code === ErrorCode.NotFound) return null;
|
|
354
412
|
throw err;
|
|
355
413
|
}
|
|
356
414
|
}
|
|
357
|
-
async enableProjectBranchDataApi(projectId, branchId, databaseName) {
|
|
415
|
+
async enableProjectBranchDataApi(projectId, branchId, databaseName, input) {
|
|
358
416
|
try {
|
|
359
417
|
return await this.call(`enableProjectBranchDataApi(${projectId}/${branchId}/${databaseName})`, async () => {
|
|
360
|
-
return { url: (await this.client.createProjectBranchDataApi(projectId, branchId, databaseName,
|
|
418
|
+
return { url: (await this.client.createProjectBranchDataApi(projectId, branchId, databaseName, dataApiCreateRequest(input))).data.url };
|
|
361
419
|
}, {
|
|
362
420
|
projectId,
|
|
363
421
|
mutating: true
|
|
@@ -370,6 +428,15 @@ var RealNeonApi = class {
|
|
|
370
428
|
throw err;
|
|
371
429
|
}
|
|
372
430
|
}
|
|
431
|
+
async updateProjectBranchDataApi(projectId, branchId, databaseName, settings) {
|
|
432
|
+
return await this.call(`updateProjectBranchDataApi(${projectId}/${branchId}/${databaseName})`, async () => {
|
|
433
|
+
await this.client.updateProjectBranchDataApi(projectId, branchId, databaseName, { settings: dataApiSettingsToApi(settings) });
|
|
434
|
+
return dataApiSnapshotFromResponse((await this.client.getProjectBranchDataApi(projectId, branchId, databaseName)).data);
|
|
435
|
+
}, {
|
|
436
|
+
projectId,
|
|
437
|
+
mutating: true
|
|
438
|
+
});
|
|
439
|
+
}
|
|
373
440
|
async listBranchBuckets(projectId, branchId) {
|
|
374
441
|
try {
|
|
375
442
|
return await this.call(`listBranchBuckets(${projectId}/${branchId})`, async () => {
|
|
@@ -443,33 +510,6 @@ var RealNeonApi = class {
|
|
|
443
510
|
mutating: true
|
|
444
511
|
});
|
|
445
512
|
}
|
|
446
|
-
async getAiGatewayEnabled(projectId, branchId) {
|
|
447
|
-
try {
|
|
448
|
-
return await this.call(`getAiGatewayEnabled(${projectId}/${branchId})`, async () => {
|
|
449
|
-
return aiGatewayEnabledFromResponse(await this.getJson(aiGatewayPath(projectId, branchId)));
|
|
450
|
-
}, { projectId });
|
|
451
|
-
} catch (err) {
|
|
452
|
-
if (isPreviewFeatureUnavailable(err)) throw previewUnavailableError(err, "AI Gateway");
|
|
453
|
-
if (err instanceof PlatformError && err.code === ErrorCode.NotFound) return false;
|
|
454
|
-
throw err;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
async enableAiGateway(projectId, branchId) {
|
|
458
|
-
await this.call(`enableAiGateway(${projectId}/${branchId})`, async () => {
|
|
459
|
-
await this.postJson(aiGatewayPath(projectId, branchId), { enabled: true });
|
|
460
|
-
}, {
|
|
461
|
-
projectId,
|
|
462
|
-
mutating: true
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
async disableAiGateway(projectId, branchId) {
|
|
466
|
-
await this.call(`disableAiGateway(${projectId}/${branchId})`, async () => {
|
|
467
|
-
await this.deleteJson(aiGatewayPath(projectId, branchId));
|
|
468
|
-
}, {
|
|
469
|
-
projectId,
|
|
470
|
-
mutating: true
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
513
|
async createCredential(projectId, branchId, input) {
|
|
474
514
|
try {
|
|
475
515
|
return await this.call(`createCredential(${projectId}/${branchId})`, async () => {
|
|
@@ -510,9 +550,6 @@ var RealNeonApi = class {
|
|
|
510
550
|
function branchPreviewPath(projectId, branchId, resource) {
|
|
511
551
|
return `/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/${resource}`;
|
|
512
552
|
}
|
|
513
|
-
function aiGatewayPath(projectId, branchId) {
|
|
514
|
-
return `/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/ai-gateway`;
|
|
515
|
-
}
|
|
516
553
|
function credentialsPath(projectId, branchId) {
|
|
517
554
|
return `/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/credentials`;
|
|
518
555
|
}
|
|
@@ -585,10 +622,6 @@ function normalizeDeploymentStatus(value) {
|
|
|
585
622
|
default: return "pending";
|
|
586
623
|
}
|
|
587
624
|
}
|
|
588
|
-
function aiGatewayEnabledFromResponse(data) {
|
|
589
|
-
if (data !== null && typeof data === "object" && "enabled" in data) return data.enabled === true;
|
|
590
|
-
return false;
|
|
591
|
-
}
|
|
592
625
|
/**
|
|
593
626
|
* Whether an error from a Preview-feature read means the feature simply isn't available
|
|
594
627
|
* for this project/branch/region (as opposed to a real, transient failure). Neon signals
|
|
@@ -607,16 +640,77 @@ function isPreviewFeatureUnavailable(err) {
|
|
|
607
640
|
return (message.includes("not available") || message.includes("does not exist") || message.includes("not enabled")) && (status === 503 || status === 404 || status === 501);
|
|
608
641
|
}
|
|
609
642
|
/**
|
|
643
|
+
* Reason phrase for the handful of HTTP statuses a Preview-feature read can surface as
|
|
644
|
+
* "unavailable". Used to print a short `HTTP <status> <reason>` line (not a stack trace),
|
|
645
|
+
* so the message reads like the API response the user would see in a tool like curl.
|
|
646
|
+
*/
|
|
647
|
+
const HTTP_STATUS_TEXT = {
|
|
648
|
+
401: "Unauthorized",
|
|
649
|
+
403: "Forbidden",
|
|
650
|
+
404: "Not Found",
|
|
651
|
+
500: "Internal Server Error",
|
|
652
|
+
501: "Not Implemented",
|
|
653
|
+
503: "Service Unavailable"
|
|
654
|
+
};
|
|
655
|
+
/**
|
|
656
|
+
* Per-status guidance for a Preview feature that came back "unavailable". A preview can be
|
|
657
|
+
* gated several different ways and the HTTP status is the best signal for which, so we tailor
|
|
658
|
+
* the next step instead of emitting one catch-all — valuable while these features are in
|
|
659
|
+
* preview and rolling out region by region:
|
|
660
|
+
*
|
|
661
|
+
* - 404 / 501 — the route isn't deployed for this project's region (or the account isn't in
|
|
662
|
+
* the private preview): create a project in a region where the preview is enabled, and
|
|
663
|
+
* confirm your account has preview access.
|
|
664
|
+
* - 503 — the route exists but is refusing right now: either the preview is still coming up,
|
|
665
|
+
* or Neon is having a transient incident. Retry; if it persists it's likely an incident.
|
|
666
|
+
* - anything else — generic "not enabled for your account/region; request access".
|
|
667
|
+
*
|
|
668
|
+
* Only statuses {@link isPreviewFeatureUnavailable} accepts (404/501/503) actually reach
|
|
669
|
+
* this, so there is intentionally no 401/403 branch — those never classify as "unavailable".
|
|
670
|
+
*/
|
|
671
|
+
function previewUnavailableHint(status) {
|
|
672
|
+
switch (status) {
|
|
673
|
+
case 404:
|
|
674
|
+
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.";
|
|
675
|
+
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.";
|
|
676
|
+
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.";
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
610
680
|
* Convert a Preview-feature error into a clear {@link PlatformError} when the feature is
|
|
611
681
|
* unavailable for the project; otherwise pass the original error through unchanged so a
|
|
612
682
|
* genuine failure (auth, transient 5xx, …) keeps its specific code and message.
|
|
683
|
+
*
|
|
684
|
+
* The message names the failing feature, summarizes the response in one short
|
|
685
|
+
* `HTTP <status> <reason>` line, includes the raw Neon API message + request id (valuable
|
|
686
|
+
* signal while the feature is in preview), gives status-specific guidance (see
|
|
687
|
+
* {@link previewUnavailableHint}), and offers removing the feature from the policy as an
|
|
688
|
+
* escape hatch. `status`/`requestId` are also kept on `details` for programmatic consumers.
|
|
613
689
|
*/
|
|
614
690
|
function previewUnavailableError(err, featureLabel) {
|
|
615
691
|
if (!isPreviewFeatureUnavailable(err)) return err;
|
|
616
|
-
const
|
|
617
|
-
|
|
692
|
+
const details = err instanceof PlatformError ? err.details : {};
|
|
693
|
+
const status = typeof details.status === "number" ? details.status : void 0;
|
|
694
|
+
const neonMessage = typeof details.neonMessage === "string" ? details.neonMessage : void 0;
|
|
695
|
+
const requestId = typeof details.requestId === "string" ? details.requestId : void 0;
|
|
696
|
+
const statusText = status ? HTTP_STATUS_TEXT[status] : void 0;
|
|
697
|
+
const apiParts = [
|
|
698
|
+
status ? `HTTP ${status}${statusText ? ` ${statusText}` : ""}` : void 0,
|
|
699
|
+
neonMessage ? `Neon API said: "${neonMessage}"` : void 0,
|
|
700
|
+
requestId ? `request id ${requestId}` : void 0
|
|
701
|
+
].filter((part) => part !== void 0);
|
|
702
|
+
const apiContext = apiParts.length > 0 ? ` (${apiParts.join("; ")})` : "";
|
|
703
|
+
return new PlatformError(ErrorCode.FeatureUnavailable, [
|
|
704
|
+
`${featureLabel} is a Preview feature and isn't available for this Neon project${apiContext}.`,
|
|
705
|
+
previewUnavailableHint(status),
|
|
706
|
+
"If you don't need it, remove the corresponding feature from the `preview` block of your neon.ts and re-run."
|
|
707
|
+
].join(" "), {
|
|
618
708
|
cause: err,
|
|
619
|
-
details: {
|
|
709
|
+
details: {
|
|
710
|
+
feature: featureLabel,
|
|
711
|
+
...status !== void 0 ? { status } : {},
|
|
712
|
+
...requestId !== void 0 ? { requestId } : {}
|
|
713
|
+
}
|
|
620
714
|
});
|
|
621
715
|
}
|
|
622
716
|
function neonAuthResponseToSnapshot(data) {
|