@neondatabase/config 1.4.3 → 1.5.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.
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","names":[],"sources":["../../src/lib/errors.ts"],"mappings":";;;;;;AAyBA;AAuBA;AAAqB;AAAW;AAAwB;AAAS;AAYjE;AAA2B;AAGC;AAAT;AAKsB;AARN;AAAK;AAyBxC;AAaA;AAUA;AAoBA;AAeA;AAA+B;AAED;AAEG;AAJM,cAtH1B,SAsH0B,EAAA;EAAa,SAAA,aAAA,EAAA,yBAAA;EAqDvC,SAAA,cAAiB,EAAA,2BAAqB;EAsCtC,SAAA,cAAA,EAAA,0BAA8C;EAgC3C,SAAA,YAAA,EAAA,wBAEJ;EAiBC,SAAA,WAAgB,EAAA,uBAAqB;;;;;;;;;;;;;;;;;;KA7OtC,SAAA,WAAoB,wBAAwB;;;;;;;;;cAY3C,aAAA,SAAsB,KAAA;;;oBAGhB,SAAS;;;cAKa;;;;;;;;;;;;iBAiBzB,eAAA,2BAA0C;;;;;;iBAa1C,eAAA,CAAA;;;;;;;cAUH,qBAAA,SAA8B,aAAa;;;;;;;;;;;;cAoB3C,mBAAA,SAA4B,aAAa;;;;;;;;;;;cAezC,iBAAA,SAA0B,aAAA;;+BAET;kCAEG;;;;;;;;;cAiDpB,gBAAA,SAAyB,aAAa;;;;;;;;;;;;;;;;;cAsCtC,wBAAA,SAAiC,aAAa;;;;;;;;;;;;;;;;;iBAgC3C,0BAAA,2BAEJ;;;;cAiBC,eAAA,SAAwB,aAAa"}
1
+ {"version":3,"file":"errors.d.ts","names":[],"sources":["../../src/lib/errors.ts"],"mappings":";;;;;;AAyBA;AAuBA;AAAqB;AAAW;AAAwB;AAAS;AAYjE;AAA2B;AAGC;AAAT;AAKsB;AARN;AAAK;AAyBxC;AAaA;AAUA;AAoBA;AAeA;AAA+B;AAED;AAEG;AAJM,cAtH1B,SAsH0B,EAAA;EAAa,SAAA,aAAA,EAAA,yBAAA;EAmEvC,SAAA,cAAiB,EAAA,2BAAqB;EAsCtC,SAAA,cAAA,EAAA,0BAA8C;EAgC3C,SAAA,YAAA,EAAA,wBAEJ;EAiBC,SAAA,WAAgB,EAAA,uBAAqB;;;;;;;;;;;;;;;;;;KA3PtC,SAAA,WAAoB,wBAAwB;;;;;;;;;cAY3C,aAAA,SAAsB,KAAA;;;oBAGhB,SAAS;;;cAKa;;;;;;;;;;;;iBAiBzB,eAAA,2BAA0C;;;;;;iBAa1C,eAAA,CAAA;;;;;;;cAUH,qBAAA,SAA8B,aAAa;;;;;;;;;;;;cAoB3C,mBAAA,SAA4B,aAAa;;;;;;;;;;;cAezC,iBAAA,SAA0B,aAAA;;+BAET;kCAEG;;;;;;;;;cA+DpB,gBAAA,SAAyB,aAAa;;;;;;;;;;;;;;;;;cAsCtC,wBAAA,SAAiC,aAAa;;;;;;;;;;;;;;;;;iBAgC3C,0BAAA,2BAEJ;;;;cAiBC,eAAA,SAAwB,aAAa"}
@@ -128,9 +128,9 @@ var PushConflictError = class extends PlatformError {
128
128
  constructor(conflicts) {
129
129
  const lines = ["pushConfig refused to apply: local config conflicts with remote state.", ""];
130
130
  for (const c of conflicts) lines.push(` - [${c.kind}:${c.identifier}] ${c.field}: ${c.reason}`, ` current : ${formatValue(c.current)}`, ` desired : ${formatValue(c.desired)}`, ` fix : ${suggestFix(c)}`);
131
- const hasMutable = conflicts.some((c) => !isImmutableConflict(c));
131
+ const hasOverrideable = conflicts.some((c) => !isImmutableConflict(c) && (c.field !== "customDomain" || reasonAllowsUpdateExisting(c.reason)));
132
132
  lines.push("");
133
- if (hasMutable) lines.push("For mutable conflicts, pass `updateExisting: true` (SDK) / `--update-existing` (CLI) to apply.");
133
+ if (hasOverrideable) lines.push("For mutable conflicts, pass `updateExisting: true` (SDK) / `--update-existing` (CLI) to apply.");
134
134
  super("PLATFORM_PUSH_CONFLICT", lines.join("\n"), { details: { conflicts: conflicts.map((c) => ({ ...c })) } });
135
135
  this.conflicts = conflicts;
136
136
  }
@@ -138,7 +138,11 @@ var PushConflictError = class extends PlatformError {
138
138
  function isImmutableConflict(_c) {
139
139
  return false;
140
140
  }
141
+ function reasonAllowsUpdateExisting(reason) {
142
+ return reason.includes("Pass `updateExisting: true`");
143
+ }
141
144
  function suggestFix(c) {
145
+ if (c.field === "customDomain" && !reasonAllowsUpdateExisting(c.reason)) return "delete the domain with `neon function domains delete`, or stop declaring it in neon.ts.";
142
146
  if (isImmutableConflict(c)) return "immutable on Neon — recreate the project, or change your `neon.ts` to match the remote.";
143
147
  if (c.kind === "branch" && c.field === "parent") return "create the parent branch on Neon first, or change the `parent` reference to an existing branch.";
144
148
  return "pass `updateExisting: true` (SDK) / `--update-existing` (CLI) to apply.";
@@ -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_PARTIAL_BRANCH_CREATE` — `createBranch` created the branch but failed to\n * apply its policy, so the branch exists without its declared settings.\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\tPartialBranchCreate: \"PLATFORM_PARTIAL_BRANCH_CREATE\",\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 `@neon/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 @neon/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 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 by `createBranch` when the branch was created but pushing its `neon.ts` policy onto\n * it failed — e.g. the API rejected a declared compute setting, or a service could not be\n * provisioned. The branch is **real**: it exists with its creation-time `parent` but without\n * the rest of its declared settings.\n *\n * The created branch's id/name are carried on the error so callers can keep it usable (pin it,\n * report it) instead of leaving a branch behind that no one knows diverges from the policy.\n * Nothing re-applies a policy to an *existing* branch implicitly, so recovering takes an\n * explicit apply with `updateExisting: true` (SDK) / `--update-existing` (CLI).\n */\nexport class PartialBranchCreateError extends PlatformError {\n\toverride readonly name = \"PartialBranchCreateError\";\n\treadonly branchId: string;\n\treadonly branchName: string;\n\t/**\n\t * Why the policy failed, taken from the underlying error — what Neon actually rejected.\n\t * Exposed separately from {@link message} so a caller that has already reported the created\n\t * branch can show just the reason.\n\t */\n\treadonly reason: string;\n\n\tconstructor(branchId: string, branchName: string, cause: unknown) {\n\t\tconst reason = messageOf(cause);\n\t\tsuper(\n\t\t\t\"PLATFORM_PARTIAL_BRANCH_CREATE\",\n\t\t\t[\n\t\t\t\t`Created branch ${JSON.stringify(branchName)} (${branchId}), but applying the neon.ts policy to it failed: ${reason}`,\n\t\t\t\t\"The branch exists and is usable, but its settings do not match the policy. Fix the cause above, then apply the policy to the existing branch with `updateExisting: true` (SDK) / `--update-existing` (CLI).\",\n\t\t\t].join(\"\\n\"),\n\t\t\t{ cause, details: { branchId, branchName } },\n\t\t);\n\t\tthis.branchId = branchId;\n\t\tthis.branchName = branchName;\n\t\tthis.reason = reason;\n\t}\n}\n\n/**\n * Structural check for a {@link PartialBranchCreateError}, matching on the stable `code` and\n * the branch fields rather than class identity — same realm-crossing rationale as\n * {@link isPlatformError}.\n */\nexport function isPartialBranchCreateError(\n\tvalue: unknown,\n): value is PartialBranchCreateError {\n\tif (value instanceof PartialBranchCreateError) return true;\n\tif (!isPlatformError(value)) return false;\n\tif (value.code !== ErrorCode.PartialBranchCreate) return false;\n\treturn (\n\t\t\"branchId\" in value &&\n\t\ttypeof value.branchId === \"string\" &&\n\t\t\"branchName\" in value &&\n\t\ttypeof value.branchName === \"string\" &&\n\t\t\"reason\" in value &&\n\t\ttypeof value.reason === \"string\"\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 messageOf(cause: unknown): string {\n\treturn cause instanceof Error ? cause.message : String(cause);\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_PARTIAL_BRANCH_CREATE` — `createBranch` created the branch but failed to\n * apply its policy, so the branch exists without its declared settings.\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\tPartialBranchCreate: \"PLATFORM_PARTIAL_BRANCH_CREATE\",\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 `@neon/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 @neon/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 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 hasOverrideable = conflicts.some(\n\t\t\t(c) =>\n\t\t\t\t!isImmutableConflict(c) &&\n\t\t\t\t(c.field !== \"customDomain\" ||\n\t\t\t\t\treasonAllowsUpdateExisting(c.reason)),\n\t\t);\n\t\tlines.push(\"\");\n\t\tif (hasOverrideable) {\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\n// Hostnames are user-controlled; matching /updateExisting/i classified\n// updateexisting.example.com as overrideable.\nfunction reasonAllowsUpdateExisting(reason: string): boolean {\n\treturn reason.includes(\"Pass `updateExisting: true`\");\n}\n\nfunction suggestFix(c: ConflictReport): string {\n\tif (c.field === \"customDomain\" && !reasonAllowsUpdateExisting(c.reason)) {\n\t\treturn \"delete the domain with `neon function domains delete`, or stop declaring it in neon.ts.\";\n\t}\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 by `createBranch` when the branch was created but pushing its `neon.ts` policy onto\n * it failed — e.g. the API rejected a declared compute setting, or a service could not be\n * provisioned. The branch is **real**: it exists with its creation-time `parent` but without\n * the rest of its declared settings.\n *\n * The created branch's id/name are carried on the error so callers can keep it usable (pin it,\n * report it) instead of leaving a branch behind that no one knows diverges from the policy.\n * Nothing re-applies a policy to an *existing* branch implicitly, so recovering takes an\n * explicit apply with `updateExisting: true` (SDK) / `--update-existing` (CLI).\n */\nexport class PartialBranchCreateError extends PlatformError {\n\toverride readonly name = \"PartialBranchCreateError\";\n\treadonly branchId: string;\n\treadonly branchName: string;\n\t/**\n\t * Why the policy failed, taken from the underlying error — what Neon actually rejected.\n\t * Exposed separately from {@link message} so a caller that has already reported the created\n\t * branch can show just the reason.\n\t */\n\treadonly reason: string;\n\n\tconstructor(branchId: string, branchName: string, cause: unknown) {\n\t\tconst reason = messageOf(cause);\n\t\tsuper(\n\t\t\t\"PLATFORM_PARTIAL_BRANCH_CREATE\",\n\t\t\t[\n\t\t\t\t`Created branch ${JSON.stringify(branchName)} (${branchId}), but applying the neon.ts policy to it failed: ${reason}`,\n\t\t\t\t\"The branch exists and is usable, but its settings do not match the policy. Fix the cause above, then apply the policy to the existing branch with `updateExisting: true` (SDK) / `--update-existing` (CLI).\",\n\t\t\t].join(\"\\n\"),\n\t\t\t{ cause, details: { branchId, branchName } },\n\t\t);\n\t\tthis.branchId = branchId;\n\t\tthis.branchName = branchName;\n\t\tthis.reason = reason;\n\t}\n}\n\n/**\n * Structural check for a {@link PartialBranchCreateError}, matching on the stable `code` and\n * the branch fields rather than class identity — same realm-crossing rationale as\n * {@link isPlatformError}.\n */\nexport function isPartialBranchCreateError(\n\tvalue: unknown,\n): value is PartialBranchCreateError {\n\tif (value instanceof PartialBranchCreateError) return true;\n\tif (!isPlatformError(value)) return false;\n\tif (value.code !== ErrorCode.PartialBranchCreate) return false;\n\treturn (\n\t\t\"branchId\" in value &&\n\t\ttypeof value.branchId === \"string\" &&\n\t\t\"branchName\" in value &&\n\t\ttypeof value.branchName === \"string\" &&\n\t\t\"reason\" in value &&\n\t\ttypeof value.reason === \"string\"\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 messageOf(cause: unknown): string {\n\treturn cause instanceof Error ? cause.message : String(cause);\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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"}
@@ -37,6 +37,10 @@ interface RetryConfig {
37
37
  * Exported only for tests; production callers go through the wrapped {@link NeonApi}.
38
38
  */
39
39
  declare function retryOnLocked<T>(fn: () => Promise<T>, config: RetryConfig): Promise<T>;
40
+ declare function collectCursorPages<T>(fetchPage: (cursor: string | undefined) => Promise<{
41
+ items: T[];
42
+ next?: string;
43
+ }>): Promise<T[]>;
40
44
  /**
41
45
  * Whether an error from a Preview-feature read means the feature simply isn't available
42
46
  * for this project/branch/region (as opposed to a real, transient failure). Neon signals
@@ -49,6 +53,11 @@ declare function retryOnLocked<T>(fn: () => Promise<T>, config: RetryConfig): Pr
49
53
  * API will refuse to create.
50
54
  */
51
55
  declare function isPreviewFeatureUnavailable(err: unknown): boolean;
56
+ /**
57
+ * Custom-domain 404s in Functions-capable regions are project enablement, not a
58
+ * missing-region problem.
59
+ */
60
+ declare function customDomainsUnavailableError(err: unknown): unknown;
52
61
  /**
53
62
  * Convert a Preview-feature error into a clear {@link PlatformError} when the feature is
54
63
  * unavailable for the project; otherwise pass the original error through unchanged so a
@@ -88,5 +97,5 @@ declare function buildFunctionDeployForm(input: DeployFunctionInput): FormData;
88
97
  */
89
98
  declare function readJsonBody(res: Response): Promise<unknown>;
90
99
  //#endregion
91
- export { buildFunctionDeployForm, createNeonAuthRestInput, createRealNeonApi, isPreviewFeatureUnavailable, previewUnavailableError, readJsonBody, retryOnLocked };
100
+ export { buildFunctionDeployForm, collectCursorPages, createNeonAuthRestInput, createRealNeonApi, customDomainsUnavailableError, isPreviewFeatureUnavailable, previewUnavailableError, readJsonBody, retryOnLocked };
92
101
  //# sourceMappingURL=neon-api-real.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"neon-api-real.d.ts","names":[],"sources":["../../src/lib/neon-api-real.ts"],"mappings":";;;UA2SU,uBAAA;;EAAA,aAAA,CAAA,EAAA,MAAA;AAeV;AA4CC;AAeD;AAAmC;AAChB;AAAR;AACF,iBA7DO,iBAAA,CA6DP,OAAA,EAAA;EACE,MAAA,EAAA,MAAA;EAAR,OAAA,CAAA,EAAA,MAAA;EAAO;AAopCV;AAoGA;AA6DA;AAoBA;EAAuC,aAAA,CAAA,EAAA;IAAQ,WAAA,CAAA,EAAA,MAAA;IAAsB,cAAA,CAAA,EAAA,MAAA;IAAQ,UAAA,CAAA,EAAA,MAAA;EAwBvD,CAAA;AAAY,CAAA,CAAA,EAl5C9B,OAk5C8B;AAAM,UAj3C9B,WAAA,CAi3C8B;EAAW,WAAA,EAAA,MAAA;EAAO,cAAA,EAAA,MAAA;;;;;;;;;;iBAp2CpC,2BACX,QAAQ,YACV,cACN,QAAQ;;;;;;;;;;;;iBAopCK,2BAAA;;;;;;;;;;;;iBAoGA,uBAAA;iBA6DA,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":";;;UA4TU,uBAAA;;EAAA,aAAA,CAAA,EAAA,MAAA;AAeV;AA4CC;AAeD;AAAmC;AAChB;AAAR;AACF,iBA7DO,iBAAA,CA6DP,OAAA,EAAA;EACE,MAAA,EAAA,MAAA;EAAR,OAAA,CAAA,EAAA,MAAA;EAAO;AA6BV;AAAwC;AAGjB;AAAjB;EACK,aAAA,CAAA,EAAA;IAAR,WAAA,CAAA,EAAA,MAAA;IAAO,cAAA,CAAA,EAAA,MAAA;IA6wCM,UAAA,CAAA,EAAA,MAAA;EAgGA,CAAA;AAuDhB,CAAA,CAAA,EAt/CI,OAs/CY;AA6DhB,UAlhDU,WAAA,CAkhDM;EAoBA,WAAA,EAAA,MAAA;EAAuB,cAAA,EAAA,MAAA;EAAQ,UAAA,EAAA,MAAA;AAAsB;AAAQ;AAwB7E;AAAkC;AAAM;AAAW;AAAO;;iBAjjDpC,2BACX,QAAQ,YACV,cACN,QAAQ;iBA6BW,iEAGhB;SAAiB;;KACpB,QAAQ;;;;;;;;;;;;iBA6wCK,2BAAA;;;;;iBAgGA,6BAAA;;;;;;;;;;;;iBAuDA,uBAAA;iBA6DA,uBAAA;;IAEZ;;;;;;;;;;;;iBAkBY,uBAAA,QAA+B,sBAAsB;;;;;;;;;;;iBAwB/C,YAAA,MAAkB,WAAW"}
@@ -108,9 +108,11 @@ const neonFunctionSchema = z.object({
108
108
  slug: z.string(),
109
109
  name: z.string(),
110
110
  invocation_url: z.string(),
111
+ current_deployment: functionDeploymentSchema.optional(),
111
112
  active_deployment: functionDeploymentSchema.optional()
112
113
  });
113
114
  const functionsListResponseSchema = z.object({ functions: z.array(neonFunctionSchema) });
115
+ const functionGetResponseSchema = z.object({ function: neonFunctionSchema });
114
116
  const functionDeploymentResponseSchema = z.object({ deployment: functionDeploymentSchema });
115
117
  const scheduleTriggerSchema = z.object({
116
118
  type: z.literal("schedule"),
@@ -125,6 +127,17 @@ const scheduleTriggerSchema = z.object({
125
127
  });
126
128
  const triggerResponseSchema = z.object({ trigger: scheduleTriggerSchema });
127
129
  const triggersListResponseSchema = z.object({ triggers: z.array(scheduleTriggerSchema) });
130
+ const customDomainApiSchema = z.object({
131
+ domain: z.string(),
132
+ entity_type: z.string(),
133
+ entity_id: z.string(),
134
+ cname_target: z.string()
135
+ });
136
+ const customDomainsListResponseSchema = z.object({
137
+ custom_domains: z.array(customDomainApiSchema),
138
+ pagination: z.object({ next: z.string().optional() }).optional()
139
+ });
140
+ const CUSTOM_DOMAINS_LIST_LIMIT = 100;
128
141
  const credentialScopeSchema = z.enum([
129
142
  "storage:read",
130
143
  "storage:write",
@@ -212,6 +225,17 @@ function readHttpStatusFromError(err) {
212
225
  function sleep(ms) {
213
226
  return new Promise((resolve) => setTimeout(resolve, ms));
214
227
  }
228
+ async function collectCursorPages(fetchPage) {
229
+ const items = [];
230
+ let cursor;
231
+ for (;;) {
232
+ const page = await fetchPage(cursor);
233
+ items.push(...page.items);
234
+ if (!page.next || page.next === cursor) break;
235
+ cursor = page.next;
236
+ }
237
+ return items;
238
+ }
215
239
  var RealNeonApi = class {
216
240
  client;
217
241
  retryConfig;
@@ -473,8 +497,8 @@ var RealNeonApi = class {
473
497
  body: JSON.stringify(body)
474
498
  });
475
499
  }
476
- async getJson(path) {
477
- return this.request("GET", path);
500
+ async getJson(path, query) {
501
+ return this.request("GET", withQuery(path, query));
478
502
  }
479
503
  async deleteJson(path) {
480
504
  return this.request("DELETE", path);
@@ -660,6 +684,12 @@ var RealNeonApi = class {
660
684
  mutating: true
661
685
  });
662
686
  }
687
+ async getBranchFunction(projectId, branchId, slug) {
688
+ return this.call(`getBranchFunction(${projectId}/${branchId}/${slug})`, async () => {
689
+ const data = await this.getJson(`${branchPreviewPath(projectId, branchId, "functions")}/${encodeURIComponent(slug)}`);
690
+ return functionToSnapshot(functionGetResponseSchema.parse(data).function);
691
+ }, { projectId });
692
+ }
663
693
  async listBranchTriggers(projectId, branchId) {
664
694
  try {
665
695
  return await this.call(`listBranchTriggers(${projectId}/${branchId})`, async () => {
@@ -718,6 +748,52 @@ var RealNeonApi = class {
718
748
  mutating: true
719
749
  });
720
750
  }
751
+ async listBranchCustomDomains(projectId, branchId) {
752
+ try {
753
+ return await this.call(`listBranchCustomDomains(${projectId}/${branchId})`, async () => collectCursorPages(async (cursor) => {
754
+ const data = await this.getJson(customDomainsPath(projectId, branchId), {
755
+ limit: CUSTOM_DOMAINS_LIST_LIMIT,
756
+ ...cursor ? { cursor } : {}
757
+ });
758
+ const parsed = customDomainsListResponseSchema.parse(data);
759
+ return {
760
+ items: parsed.custom_domains.map(customDomainToSnapshot),
761
+ ...parsed.pagination?.next ? { next: parsed.pagination.next } : {}
762
+ };
763
+ }), { projectId });
764
+ } catch (err) {
765
+ throw customDomainsUnavailableError(err);
766
+ }
767
+ }
768
+ async registerBranchCustomDomain(projectId, branchId, input) {
769
+ try {
770
+ return await this.call(`registerBranchCustomDomain(${projectId}/${branchId}/${input.domain})`, async () => {
771
+ const data = await this.postJson(customDomainsPath(projectId, branchId), {
772
+ domain: input.domain,
773
+ entity_type: "function",
774
+ entity_id: input.functionSlug
775
+ });
776
+ return customDomainToSnapshot(customDomainApiSchema.parse(data));
777
+ }, {
778
+ projectId,
779
+ mutating: true
780
+ });
781
+ } catch (err) {
782
+ throw customDomainsUnavailableError(err);
783
+ }
784
+ }
785
+ async deleteBranchCustomDomain(projectId, branchId, domain) {
786
+ try {
787
+ await this.call(`deleteBranchCustomDomain(${projectId}/${branchId}/${domain})`, async () => {
788
+ await this.deleteJson(`${customDomainsPath(projectId, branchId)}/${encodeURIComponent(domain)}`);
789
+ }, {
790
+ projectId,
791
+ mutating: true
792
+ });
793
+ } catch (err) {
794
+ throw customDomainsUnavailableError(err);
795
+ }
796
+ }
721
797
  async createCredential(projectId, branchId, input) {
722
798
  try {
723
799
  return await this.call(`createCredential(${projectId}/${branchId})`, async () => {
@@ -782,6 +858,27 @@ function credentialsPath(projectId, branchId) {
782
858
  function triggersPath(projectId, branchId) {
783
859
  return `/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/triggers`;
784
860
  }
861
+ function customDomainsPath(projectId, branchId) {
862
+ return `/projects/${encodeURIComponent(projectId)}/branches/${encodeURIComponent(branchId)}/custom-domains`;
863
+ }
864
+ function withQuery(path, query) {
865
+ if (!query) return path;
866
+ const params = new URLSearchParams();
867
+ for (const [key, value] of Object.entries(query)) {
868
+ if (value === void 0) continue;
869
+ params.set(key, String(value));
870
+ }
871
+ const qs = params.toString();
872
+ return qs.length > 0 ? `${path}?${qs}` : path;
873
+ }
874
+ function customDomainToSnapshot(data) {
875
+ return {
876
+ domain: data.domain,
877
+ entityType: data.entity_type,
878
+ entityId: data.entity_id,
879
+ cnameTarget: data.cname_target
880
+ };
881
+ }
785
882
  function triggerToSnapshot(data) {
786
883
  return {
787
884
  triggerId: data.trigger_id,
@@ -846,6 +943,7 @@ function functionToSnapshot(fn) {
846
943
  invocationUrl: fn.invocation_url
847
944
  };
848
945
  if (fn.active_deployment) snapshot.activeDeploymentId = fn.active_deployment.id;
946
+ if (fn.current_deployment) snapshot.currentDeployment = deploymentToSnapshot(fn.current_deployment);
849
947
  return snapshot;
850
948
  }
851
949
  function deploymentToSnapshot(deployment) {
@@ -923,6 +1021,34 @@ function platformFeatureUnavailableHint(status, neonMessage) {
923
1021
  if (status === 503) return "The endpoint is reachable but refused the request — Neon may be having a transient incident. Retry shortly; if it keeps failing, check https://neonstatus.com and contact Neon support.";
924
1022
  return PLATFORM_BETA_REGION_GUIDANCE_SHORT;
925
1023
  }
1024
+ const CUSTOM_DOMAINS_UNAVAILABLE_HINT = "Custom domains are enabled per project; deploying functions does not enable them. Remove `customDomains` from the function in neon.ts to continue deploying functions.";
1025
+ /**
1026
+ * Custom-domain 404s in Functions-capable regions are project enablement, not a
1027
+ * missing-region problem.
1028
+ */
1029
+ function customDomainsUnavailableError(err) {
1030
+ if (!isPreviewFeatureUnavailable(err)) return err;
1031
+ const details = err instanceof PlatformError ? err.details : {};
1032
+ const status = typeof details.status === "number" ? details.status : void 0;
1033
+ const neonMessage = typeof details.neonMessage === "string" ? details.neonMessage : void 0;
1034
+ const requestId = typeof details.requestId === "string" ? details.requestId : void 0;
1035
+ const statusText = status ? HTTP_STATUS_TEXT[status] : void 0;
1036
+ const apiParts = [
1037
+ status ? `HTTP ${status}${statusText ? ` ${statusText}` : ""}` : void 0,
1038
+ neonMessage ? `Neon API said: "${neonMessage}"` : void 0,
1039
+ requestId ? `request id ${requestId}` : void 0
1040
+ ].filter((part) => part !== void 0);
1041
+ const apiContext = apiParts.length > 0 ? ` (${apiParts.join("; ")})` : "";
1042
+ const hint = status === 503 && !isRegionUnavailableNeonMessage(neonMessage) ? "The endpoint is reachable but refused the request — Neon may be having a transient incident. Retry shortly; if it keeps failing, check https://neonstatus.com and contact Neon support." : CUSTOM_DOMAINS_UNAVAILABLE_HINT;
1043
+ return new PlatformError(ErrorCode.FeatureUnavailable, [`Custom domains aren't available for this Neon project${apiContext}.`, hint].join(" "), {
1044
+ cause: err,
1045
+ details: {
1046
+ feature: "Custom domains",
1047
+ ...status !== void 0 ? { status } : {},
1048
+ ...requestId !== void 0 ? { requestId } : {}
1049
+ }
1050
+ });
1051
+ }
926
1052
  /**
927
1053
  * Convert a Preview-feature error into a clear {@link PlatformError} when the feature is
928
1054
  * unavailable for the project; otherwise pass the original error through unchanged so a
@@ -1093,6 +1219,6 @@ function defaultsToComputeSettings(defaults) {
1093
1219
  return Object.keys(out).length > 0 ? out : void 0;
1094
1220
  }
1095
1221
  //#endregion
1096
- export { buildFunctionDeployForm, createNeonAuthRestInput, createRealNeonApi, isPreviewFeatureUnavailable, previewUnavailableError, readJsonBody, retryOnLocked };
1222
+ export { buildFunctionDeployForm, collectCursorPages, createNeonAuthRestInput, createRealNeonApi, customDomainsUnavailableError, isPreviewFeatureUnavailable, previewUnavailableError, readJsonBody, retryOnLocked };
1097
1223
 
1098
1224
  //# sourceMappingURL=neon-api-real.js.map