@neondatabase/config 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +178 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +8 -0
- package/dist/lib/auth.d.ts +63 -0
- package/dist/lib/auth.d.ts.map +1 -0
- package/dist/lib/auth.js +93 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/define-config.d.ts +43 -0
- package/dist/lib/define-config.d.ts.map +1 -0
- package/dist/lib/define-config.js +112 -0
- package/dist/lib/define-config.js.map +1 -0
- package/dist/lib/diff.d.ts +109 -0
- package/dist/lib/diff.d.ts.map +1 -0
- package/dist/lib/diff.js +205 -0
- package/dist/lib/diff.js.map +1 -0
- package/dist/lib/duration.d.ts +46 -0
- package/dist/lib/duration.d.ts.map +1 -0
- package/dist/lib/duration.js +96 -0
- package/dist/lib/duration.js.map +1 -0
- package/dist/lib/errors.d.ts +129 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +168 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/loader.d.ts +44 -0
- package/dist/lib/loader.d.ts.map +1 -0
- package/dist/lib/loader.js +119 -0
- package/dist/lib/loader.js.map +1 -0
- package/dist/lib/neon-api-real.d.ts +45 -0
- package/dist/lib/neon-api-real.d.ts.map +1 -0
- package/dist/lib/neon-api-real.js +582 -0
- package/dist/lib/neon-api-real.js.map +1 -0
- package/dist/lib/neon-api.d.ts +262 -0
- package/dist/lib/neon-api.d.ts.map +1 -0
- package/dist/lib/neon-api.js +1 -0
- package/dist/lib/patterns.d.ts +43 -0
- package/dist/lib/patterns.d.ts.map +1 -0
- package/dist/lib/patterns.js +76 -0
- package/dist/lib/patterns.js.map +1 -0
- package/dist/lib/schema.d.ts +130 -0
- package/dist/lib/schema.d.ts.map +1 -0
- package/dist/lib/schema.js +218 -0
- package/dist/lib/schema.js.map +1 -0
- package/dist/lib/types.d.ts +289 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +1 -0
- package/dist/lib/wrap-neon-error.d.ts +30 -0
- package/dist/lib/wrap-neon-error.d.ts.map +1 -0
- package/dist/lib/wrap-neon-error.js +139 -0
- package/dist/lib/wrap-neon-error.js.map +1 -0
- package/dist/v1.d.ts +153 -0
- package/dist/v1.d.ts.map +1 -0
- package/dist/v1.js +69 -0
- package/dist/v1.js.map +1 -0
- package/package.json +67 -17
- package/.env.example +0 -5
- package/e2e/errors.e2e.test.ts +0 -52
- package/e2e/helpers.ts +0 -205
- package/e2e/load-env.ts +0 -29
- package/e2e/setup.ts +0 -24
- package/src/index.ts +0 -5
- package/src/lib/auth.test.ts +0 -166
- package/src/lib/auth.ts +0 -124
- package/src/lib/define-config.test.ts +0 -161
- package/src/lib/define-config.ts +0 -152
- package/src/lib/diff.test.ts +0 -142
- package/src/lib/diff.ts +0 -391
- package/src/lib/duration.test.ts +0 -105
- package/src/lib/duration.ts +0 -147
- package/src/lib/errors.test.ts +0 -26
- package/src/lib/errors.ts +0 -220
- package/src/lib/fake-neon-api.ts +0 -782
- package/src/lib/loader.test.ts +0 -35
- package/src/lib/loader.ts +0 -215
- package/src/lib/neon-api-real.test.ts +0 -72
- package/src/lib/neon-api-real.ts +0 -1123
- package/src/lib/neon-api.ts +0 -356
- package/src/lib/patterns.test.ts +0 -80
- package/src/lib/patterns.ts +0 -98
- package/src/lib/schema.test.ts +0 -88
- package/src/lib/schema.ts +0 -252
- package/src/lib/test-utils.ts +0 -83
- package/src/lib/types.ts +0 -268
- package/src/lib/wrap-neon-error.test.ts +0 -145
- package/src/lib/wrap-neon-error.ts +0 -204
- package/src/v1.test.ts +0 -33
- package/src/v1.ts +0 -148
- package/tsconfig.json +0 -4
- package/tsdown.config.ts +0 -19
- package/vitest.config.ts +0 -19
- package/vitest.e2e.config.ts +0 -29
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
//#region src/lib/errors.ts
|
|
2
|
+
/**
|
|
3
|
+
* Every code a {@link PlatformError} can carry. Stable identifiers — consumers can rely on
|
|
4
|
+
* these for `instanceof PlatformError && err.code === ErrorCode.…` style checks instead of
|
|
5
|
+
* matching on free-text messages.
|
|
6
|
+
*
|
|
7
|
+
* Grouped by source:
|
|
8
|
+
* - `PLATFORM_INVALID_CONFIG` — `defineConfig` / `configSchema` rejected the input.
|
|
9
|
+
* - `PLATFORM_MISSING_CONTEXT` — no project / branch context could be resolved.
|
|
10
|
+
* - `PLATFORM_PUSH_CONFLICT` — local config conflicts with remote and the caller did not
|
|
11
|
+
* opt in to apply.
|
|
12
|
+
* - `PLATFORM_CONFIG_LOAD_FAILED` — `neon.ts` could not be found or evaluated.
|
|
13
|
+
* - `PLATFORM_MISSING_API_KEY` — no `NEON_API_KEY` and no explicit `apiKey` was provided.
|
|
14
|
+
* - `PLATFORM_MISSING_PARENT_BRANCH` — push tried to create a child of a non-existent
|
|
15
|
+
* branch.
|
|
16
|
+
* - `PLATFORM_UNAUTHORIZED` / `PLATFORM_FORBIDDEN` / `PLATFORM_NOT_FOUND` /
|
|
17
|
+
* `PLATFORM_CONFLICT` / `PLATFORM_RATE_LIMITED` / `PLATFORM_LOCKED` /
|
|
18
|
+
* `PLATFORM_SERVER_ERROR` — wrappings of Neon HTTP failures.
|
|
19
|
+
* - `PLATFORM_NETWORK_ERROR` — transport-level failure (no HTTP response at all).
|
|
20
|
+
* - `PLATFORM_INTERNAL_ERROR` — invariant violations. Should never happen in production;
|
|
21
|
+
* if you see one, please open an issue.
|
|
22
|
+
*/
|
|
23
|
+
const ErrorCode = {
|
|
24
|
+
InvalidConfig: "PLATFORM_INVALID_CONFIG",
|
|
25
|
+
EnvNotInjected: "PLATFORM_ENV_NOT_INJECTED",
|
|
26
|
+
MissingContext: "PLATFORM_MISSING_CONTEXT",
|
|
27
|
+
PushConflict: "PLATFORM_PUSH_CONFLICT",
|
|
28
|
+
PushAborted: "PLATFORM_PUSH_ABORTED",
|
|
29
|
+
ConfigLoadFailed: "PLATFORM_CONFIG_LOAD_FAILED",
|
|
30
|
+
MissingApiKey: "PLATFORM_MISSING_API_KEY",
|
|
31
|
+
AmbiguousBranchAuth: "PLATFORM_AMBIGUOUS_BRANCH_AUTH",
|
|
32
|
+
BranchNotFound: "PLATFORM_BRANCH_NOT_FOUND",
|
|
33
|
+
MissingParentBranch: "PLATFORM_MISSING_PARENT_BRANCH",
|
|
34
|
+
Unauthorized: "PLATFORM_UNAUTHORIZED",
|
|
35
|
+
Forbidden: "PLATFORM_FORBIDDEN",
|
|
36
|
+
NotFound: "PLATFORM_NOT_FOUND",
|
|
37
|
+
Conflict: "PLATFORM_CONFLICT",
|
|
38
|
+
RateLimited: "PLATFORM_RATE_LIMITED",
|
|
39
|
+
Locked: "PLATFORM_LOCKED",
|
|
40
|
+
ServerError: "PLATFORM_SERVER_ERROR",
|
|
41
|
+
NetworkError: "PLATFORM_NETWORK_ERROR",
|
|
42
|
+
InternalError: "PLATFORM_INTERNAL_ERROR"
|
|
43
|
+
};
|
|
44
|
+
const ISSUE_URL = "https://github.com/neondatabase/neon-pkgs/issues/new";
|
|
45
|
+
/**
|
|
46
|
+
* Base class for all errors thrown by `@neondatabase/config`. Always extend this so callers
|
|
47
|
+
* can catch every package-thrown error with a single `instanceof` check.
|
|
48
|
+
*
|
|
49
|
+
* Optional `details` carries structured context that the CLI prints under `--debug` and
|
|
50
|
+
* that programmatic consumers can read (e.g. `details.status` for HTTP wrappings,
|
|
51
|
+
* `details.requestId` for Neon API failures).
|
|
52
|
+
*/
|
|
53
|
+
var PlatformError = class extends Error {
|
|
54
|
+
name = "PlatformError";
|
|
55
|
+
code;
|
|
56
|
+
details;
|
|
57
|
+
constructor(code, message, options) {
|
|
58
|
+
super(message, options);
|
|
59
|
+
this.code = code;
|
|
60
|
+
this.details = Object.freeze({ ...options?.details ?? {} });
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Append a "report-a-bug" footer to an error message. Used only on truly unreachable
|
|
65
|
+
* internal errors — never on user-facing validation / configuration errors where the user
|
|
66
|
+
* is supposed to fix something on their end.
|
|
67
|
+
*/
|
|
68
|
+
function bugReportFooter() {
|
|
69
|
+
return `\nThis indicates a bug in @neondatabase/config. Please file an issue: ${ISSUE_URL}`;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Thrown by {@link defineConfig} when the user-provided configuration object is invalid.
|
|
73
|
+
*
|
|
74
|
+
* The class collects every validation failure rather than throwing on the first one so that
|
|
75
|
+
* users get a complete picture of what is wrong with their `neon.ts`.
|
|
76
|
+
*/
|
|
77
|
+
var ConfigValidationError = class extends PlatformError {
|
|
78
|
+
name = "ConfigValidationError";
|
|
79
|
+
issues;
|
|
80
|
+
constructor(issues) {
|
|
81
|
+
super("PLATFORM_INVALID_CONFIG", `Invalid Neon platform config:\n - ${issues.join("\n - ")}`);
|
|
82
|
+
this.issues = issues;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Thrown when the package cannot resolve which Neon project to operate on.
|
|
87
|
+
*
|
|
88
|
+
* Per the package's read-only-filesystem contract, we never create a `.neon` context file;
|
|
89
|
+
* callers must either pass `projectId`/`orgId` explicitly or rely on an existing context file
|
|
90
|
+
* (`.neon/project.json` or neonctl's `.neon`).
|
|
91
|
+
*/
|
|
92
|
+
var MissingContextError = class extends PlatformError {
|
|
93
|
+
name = "MissingContextError";
|
|
94
|
+
constructor(message) {
|
|
95
|
+
super("PLATFORM_MISSING_CONTEXT", message);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Thrown by {@link pushConfig} when it detects differences between the local config and
|
|
100
|
+
* the remote project that the caller hasn't opted in to apply.
|
|
101
|
+
*
|
|
102
|
+
* The message lists every conflict with both the current and desired value plus a
|
|
103
|
+
* per-conflict hint. Mutable branch drift is applied by passing `updateExisting: true`.
|
|
104
|
+
*/
|
|
105
|
+
var PushConflictError = class extends PlatformError {
|
|
106
|
+
name = "PushConflictError";
|
|
107
|
+
conflicts;
|
|
108
|
+
constructor(conflicts) {
|
|
109
|
+
const lines = ["pushConfig refused to apply: local config conflicts with remote state.", ""];
|
|
110
|
+
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)}`);
|
|
111
|
+
const hasMutable = conflicts.some((c) => !isImmutableConflict(c));
|
|
112
|
+
lines.push("");
|
|
113
|
+
if (hasMutable) lines.push("For mutable conflicts, pass `updateExisting: true` (SDK) / `--update-existing` (CLI) to apply.");
|
|
114
|
+
super("PLATFORM_PUSH_CONFLICT", lines.join("\n"), { details: { conflicts: conflicts.map((c) => ({ ...c })) } });
|
|
115
|
+
this.conflicts = conflicts;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
function isImmutableConflict(_c) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
function suggestFix(c) {
|
|
122
|
+
if (isImmutableConflict(c)) return "immutable on Neon — recreate the project, or change your `neon.ts` to match the remote.";
|
|
123
|
+
if (c.kind === "branch" && c.field === "parent") return "create the parent branch on Neon first, or change the `parent` reference to an existing branch.";
|
|
124
|
+
return "pass `updateExisting: true` (SDK) / `--update-existing` (CLI) to apply.";
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Thrown by {@link pushConfig} when the caller-supplied `confirm` callback declines a
|
|
128
|
+
* push that requires confirmation (protected branch and/or mutable drift overriding
|
|
129
|
+
* existing remote settings).
|
|
130
|
+
*
|
|
131
|
+
* The CLI maps this to a non-zero exit so users see "aborted" rather than a stack trace.
|
|
132
|
+
*/
|
|
133
|
+
var PushAbortedError = class extends PlatformError {
|
|
134
|
+
name = "PushAbortedError";
|
|
135
|
+
branchName;
|
|
136
|
+
reasons;
|
|
137
|
+
constructor(branchName, reasons) {
|
|
138
|
+
super("PLATFORM_PUSH_ABORTED", [
|
|
139
|
+
`Aborted push to branch ${JSON.stringify(branchName)}.`,
|
|
140
|
+
reasons.length > 0 ? `Reason${reasons.length === 1 ? "" : "s"}: ${reasons.join(", ")}.` : void 0,
|
|
141
|
+
"Re-run with `--update-existing` (override existing settings) or `--allow-protected-branch` (push to a protected branch) to skip the prompt."
|
|
142
|
+
].filter(Boolean).join(" "), { details: {
|
|
143
|
+
branchName,
|
|
144
|
+
reasons: [...reasons]
|
|
145
|
+
} });
|
|
146
|
+
this.branchName = branchName;
|
|
147
|
+
this.reasons = reasons;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
/**
|
|
151
|
+
* Thrown when the SDK fails to find or load a `neon.ts` config file.
|
|
152
|
+
*/
|
|
153
|
+
var ConfigLoadError = class extends PlatformError {
|
|
154
|
+
name = "ConfigLoadError";
|
|
155
|
+
constructor(message, options) {
|
|
156
|
+
super("PLATFORM_CONFIG_LOAD_FAILED", message, options);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
function formatValue(value) {
|
|
160
|
+
if (value === void 0) return "<unset>";
|
|
161
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
162
|
+
if (typeof value === "object" && value !== null) return JSON.stringify(value);
|
|
163
|
+
return String(value);
|
|
164
|
+
}
|
|
165
|
+
//#endregion
|
|
166
|
+
export { ConfigLoadError, ConfigValidationError, ErrorCode, MissingContextError, PlatformError, PushAbortedError, PushConflictError, bugReportFooter };
|
|
167
|
+
|
|
168
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +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\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 * 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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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Config } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/loader.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Default file names tried (in order) when {@link loadConfigFromFile} is called without an
|
|
7
|
+
* explicit path. We accept `.ts` first because that's the documented format; `.mjs` and `.js`
|
|
8
|
+
* fall out for free since jiti handles all of them.
|
|
9
|
+
*/
|
|
10
|
+
declare const DEFAULT_CONFIG_FILENAMES: readonly ["neon.ts", "neon.mts", "neon.js", "neon.mjs"];
|
|
11
|
+
interface LoadConfigOptions {
|
|
12
|
+
/** Explicit absolute or cwd-relative path to a config file. Takes precedence over the search. */
|
|
13
|
+
path?: string;
|
|
14
|
+
/** Starting directory for the upward search. Defaults to `process.cwd()`. */
|
|
15
|
+
cwd?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Hard ceiling for the upward walk — once `current === stopAt` the search returns
|
|
18
|
+
* `null` even if no `.git` boundary was hit. Defaults to the OS home directory so
|
|
19
|
+
* stray runs from outside any repo never leak into the user's `~` files.
|
|
20
|
+
*/
|
|
21
|
+
stopAt?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Load a `neon.ts` (or any other supported extension) and return the validated {@link Config}.
|
|
25
|
+
*
|
|
26
|
+
* Behavior:
|
|
27
|
+
* - When `path` is set, that file is loaded directly. The file must exist and must default-export
|
|
28
|
+
* a value produced by `defineConfig()`.
|
|
29
|
+
* - When `path` is omitted, we walk up from `cwd` picking the **closest** file matching
|
|
30
|
+
* {@link DEFAULT_CONFIG_FILENAMES}. The walk is monorepo-friendly: intermediate
|
|
31
|
+
* `package.json` files do **not** stop it, so a single `neon.ts` lifted to the workspace
|
|
32
|
+
* root keeps working when invoked from inside any sub-package. The walk terminates at the
|
|
33
|
+
* first directory containing `.git`, at `stopAt`, or at the filesystem root.
|
|
34
|
+
*
|
|
35
|
+
* jiti is loaded lazily so that callers who pass an already-resolved `Config` to `pushConfig`
|
|
36
|
+
* never pay the import cost.
|
|
37
|
+
*/
|
|
38
|
+
declare function loadConfigFromFile(options?: LoadConfigOptions): Promise<{
|
|
39
|
+
config: Config;
|
|
40
|
+
resolvedPath: string;
|
|
41
|
+
}>;
|
|
42
|
+
//#endregion
|
|
43
|
+
export { DEFAULT_CONFIG_FILENAMES, LoadConfigOptions, loadConfigFromFile };
|
|
44
|
+
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","names":[],"sources":["../../src/lib/loader.ts"],"mappings":";;;;;;AAaA;AAOA;AA4BA;AAAwC,cAnC3B,wBAmC2B,EAAA,SAAA,CAAA,SAAA,EAAA,UAAA,EAAA,SAAA,EAAA,UAAA,CAAA;AAC9B,UA7BO,iBAAA,CA6BP;;MACP,CAAA,EAAA,MAAA;EAAO;;;;;;;;;;;;;;;;;;;;;;;;iBAFY,kBAAA,WACZ,oBACP;UACM"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { ConfigLoadError } from "./errors.js";
|
|
2
|
+
import { defineConfig } from "./define-config.js";
|
|
3
|
+
import { existsSync, statSync } from "node:fs";
|
|
4
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { pathToFileURL } from "node:url";
|
|
7
|
+
//#region src/lib/loader.ts
|
|
8
|
+
/**
|
|
9
|
+
* Default file names tried (in order) when {@link loadConfigFromFile} is called without an
|
|
10
|
+
* explicit path. We accept `.ts` first because that's the documented format; `.mjs` and `.js`
|
|
11
|
+
* fall out for free since jiti handles all of them.
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_CONFIG_FILENAMES = [
|
|
14
|
+
"neon.ts",
|
|
15
|
+
"neon.mts",
|
|
16
|
+
"neon.js",
|
|
17
|
+
"neon.mjs"
|
|
18
|
+
];
|
|
19
|
+
/**
|
|
20
|
+
* Load a `neon.ts` (or any other supported extension) and return the validated {@link Config}.
|
|
21
|
+
*
|
|
22
|
+
* Behavior:
|
|
23
|
+
* - When `path` is set, that file is loaded directly. The file must exist and must default-export
|
|
24
|
+
* a value produced by `defineConfig()`.
|
|
25
|
+
* - When `path` is omitted, we walk up from `cwd` picking the **closest** file matching
|
|
26
|
+
* {@link DEFAULT_CONFIG_FILENAMES}. The walk is monorepo-friendly: intermediate
|
|
27
|
+
* `package.json` files do **not** stop it, so a single `neon.ts` lifted to the workspace
|
|
28
|
+
* root keeps working when invoked from inside any sub-package. The walk terminates at the
|
|
29
|
+
* first directory containing `.git`, at `stopAt`, or at the filesystem root.
|
|
30
|
+
*
|
|
31
|
+
* jiti is loaded lazily so that callers who pass an already-resolved `Config` to `pushConfig`
|
|
32
|
+
* never pay the import cost.
|
|
33
|
+
*/
|
|
34
|
+
async function loadConfigFromFile(options = {}) {
|
|
35
|
+
const resolvedPath = options.path ? resolveExplicitPath(options.path, options.cwd) : findDefaultConfig(options.cwd, options.stopAt);
|
|
36
|
+
if (!resolvedPath) throw new ConfigLoadError([
|
|
37
|
+
`Could not find a Neon config file while walking up from ${resolve(options.cwd ?? process.cwd())}.`,
|
|
38
|
+
`Looked for: ${DEFAULT_CONFIG_FILENAMES.join(", ")} (stopping at the first directory with a \`.git\`).`,
|
|
39
|
+
`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).`
|
|
40
|
+
].join("\n"));
|
|
41
|
+
let mod;
|
|
42
|
+
try {
|
|
43
|
+
mod = await importModule(resolvedPath);
|
|
44
|
+
} catch (cause) {
|
|
45
|
+
throw new ConfigLoadError([
|
|
46
|
+
`Failed to evaluate ${resolvedPath}.`,
|
|
47
|
+
`Underlying error: ${cause?.message ?? String(cause)}`,
|
|
48
|
+
"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."
|
|
49
|
+
].join("\n"), { cause });
|
|
50
|
+
}
|
|
51
|
+
const exported = extractDefaultExport(mod);
|
|
52
|
+
if (exported === void 0) throw new ConfigLoadError([`${resolvedPath} loaded successfully but did not default-export a config.`, "Add `export default defineConfig({ ... })` at the bottom of the file. (Named exports are ignored.)"].join("\n"));
|
|
53
|
+
return {
|
|
54
|
+
config: defineConfig(exported),
|
|
55
|
+
resolvedPath
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function resolveExplicitPath(input, cwd) {
|
|
59
|
+
const base = resolve(cwd ?? process.cwd());
|
|
60
|
+
const abs = isAbsolute(input) ? input : resolve(base, input);
|
|
61
|
+
if (!existsSync(abs)) throw new ConfigLoadError(`Config file not found at ${abs}. The path was resolved from \`${input}\` against ${base}.`);
|
|
62
|
+
if (!statSync(abs).isFile()) throw new ConfigLoadError(`Config path ${abs} is a directory, not a file. Pass a path to the file itself (e.g. ./neon.ts).`);
|
|
63
|
+
return abs;
|
|
64
|
+
}
|
|
65
|
+
function findDefaultConfig(cwd, stopAt) {
|
|
66
|
+
let current = resolve(cwd ?? process.cwd());
|
|
67
|
+
const stop = resolve(stopAt ?? homedir());
|
|
68
|
+
let lastSeen = null;
|
|
69
|
+
while (true) {
|
|
70
|
+
for (const name of DEFAULT_CONFIG_FILENAMES) {
|
|
71
|
+
const candidate = resolve(current, name);
|
|
72
|
+
if (existsSync(candidate) && safeIsFile(candidate)) return candidate;
|
|
73
|
+
}
|
|
74
|
+
if (existsSync(resolve(current, ".git"))) return null;
|
|
75
|
+
if (current === stop) return null;
|
|
76
|
+
const parent = dirname(current);
|
|
77
|
+
if (parent === current || parent === lastSeen) return null;
|
|
78
|
+
lastSeen = current;
|
|
79
|
+
current = parent;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function importModule(absPath) {
|
|
83
|
+
const lower = absPath.toLowerCase();
|
|
84
|
+
if (!(lower.endsWith(".ts") || lower.endsWith(".mts") || lower.endsWith(".cts"))) return import(pathToFileURL(absPath).href);
|
|
85
|
+
const createJiti = extractCreateJiti(await import("jiti"));
|
|
86
|
+
if (!createJiti) throw new ConfigLoadError(["jiti is required to load TypeScript config files but could not be initialised.", "Reinstall the package dependencies (`pnpm install` / `npm install`) — jiti is a runtime dependency of @neondatabase/config."].join(" "));
|
|
87
|
+
return createJiti(pathToFileURL(absPath).href, {
|
|
88
|
+
interopDefault: true,
|
|
89
|
+
moduleCache: false
|
|
90
|
+
}).import(absPath);
|
|
91
|
+
}
|
|
92
|
+
function extractCreateJiti(mod) {
|
|
93
|
+
if (mod === null || typeof mod !== "object") return null;
|
|
94
|
+
const obj = mod;
|
|
95
|
+
if (typeof obj.createJiti === "function") return obj.createJiti;
|
|
96
|
+
const def = obj.default;
|
|
97
|
+
if (def !== null && typeof def === "object") {
|
|
98
|
+
const defObj = def;
|
|
99
|
+
if (typeof defObj.createJiti === "function") return defObj.createJiti;
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
function extractDefaultExport(mod) {
|
|
104
|
+
if (mod === null || typeof mod !== "object") return mod;
|
|
105
|
+
const obj = mod;
|
|
106
|
+
if ("default" in obj && obj.default !== void 0) return obj.default;
|
|
107
|
+
if (typeof mod === "function") return mod;
|
|
108
|
+
}
|
|
109
|
+
function safeIsFile(path) {
|
|
110
|
+
try {
|
|
111
|
+
return statSync(path).isFile();
|
|
112
|
+
} catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
export { DEFAULT_CONFIG_FILENAMES, loadConfigFromFile };
|
|
118
|
+
|
|
119
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","names":[],"sources":["../../src/lib/loader.ts"],"sourcesContent":["import { existsSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, isAbsolute, resolve } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { defineConfig } from \"./define-config.js\";\nimport { ConfigLoadError } from \"./errors.js\";\nimport type { Config } from \"./types.js\";\n\n/**\n * Default file names tried (in order) when {@link loadConfigFromFile} is called without an\n * explicit path. We accept `.ts` first because that's the documented format; `.mjs` and `.js`\n * fall out for free since jiti handles all of them.\n */\nexport const DEFAULT_CONFIG_FILENAMES = [\n\t\"neon.ts\",\n\t\"neon.mts\",\n\t\"neon.js\",\n\t\"neon.mjs\",\n] as const;\n\nexport interface LoadConfigOptions {\n\t/** Explicit absolute or cwd-relative path to a config file. Takes precedence over the search. */\n\tpath?: string;\n\t/** Starting directory for the upward search. Defaults to `process.cwd()`. */\n\tcwd?: string;\n\t/**\n\t * Hard ceiling for the upward walk — once `current === stopAt` the search returns\n\t * `null` even if no `.git` boundary was hit. Defaults to the OS home directory so\n\t * stray runs from outside any repo never leak into the user's `~` files.\n\t */\n\tstopAt?: string;\n}\n\n/**\n * Load a `neon.ts` (or any other supported extension) and return the validated {@link Config}.\n *\n * Behavior:\n * - When `path` is set, that file is loaded directly. The file must exist and must default-export\n * a value produced by `defineConfig()`.\n * - When `path` is omitted, we walk up from `cwd` picking the **closest** file matching\n * {@link DEFAULT_CONFIG_FILENAMES}. The walk is monorepo-friendly: intermediate\n * `package.json` files do **not** stop it, so a single `neon.ts` lifted to the workspace\n * root keeps working when invoked from inside any sub-package. The walk terminates at the\n * first directory containing `.git`, at `stopAt`, or at the filesystem root.\n *\n * jiti is loaded lazily so that callers who pass an already-resolved `Config` to `pushConfig`\n * never pay the import cost.\n */\nexport async function loadConfigFromFile(\n\toptions: LoadConfigOptions = {},\n): Promise<{\n\tconfig: Config;\n\tresolvedPath: string;\n}> {\n\tconst resolvedPath = options.path\n\t\t? resolveExplicitPath(options.path, options.cwd)\n\t\t: findDefaultConfig(options.cwd, options.stopAt);\n\n\tif (!resolvedPath) {\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t`Could not find a Neon config file while walking up from ${resolve(options.cwd ?? process.cwd())}.`,\n\t\t\t\t`Looked for: ${DEFAULT_CONFIG_FILENAMES.join(\", \")} (stopping at the first directory with a \\`.git\\`).`,\n\t\t\t\t`Create one at your repository root (or anywhere on the path from cwd up to .git), or pass an explicit \\`configPath\\` (SDK) / \\`--config <path>\\` (CLI).`,\n\t\t\t].join(\"\\n\"),\n\t\t);\n\t}\n\n\tlet mod: unknown;\n\ttry {\n\t\tmod = await importModule(resolvedPath);\n\t} catch (cause) {\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t`Failed to evaluate ${resolvedPath}.`,\n\t\t\t\t`Underlying error: ${(cause as Error)?.message ?? String(cause)}`,\n\t\t\t\t\"This is usually a TypeScript syntax error, a missing dependency, or a runtime exception inside the config file. Run the file directly (e.g. `npx tsx neon.ts`) to reproduce.\",\n\t\t\t].join(\"\\n\"),\n\t\t\t{ cause },\n\t\t);\n\t}\n\n\tconst exported = extractDefaultExport(mod);\n\tif (exported === undefined) {\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t`${resolvedPath} loaded successfully but did not default-export a config.`,\n\t\t\t\t\"Add `export default defineConfig({ ... })` at the bottom of the file. (Named exports are ignored.)\",\n\t\t\t].join(\"\\n\"),\n\t\t);\n\t}\n\n\t// Run through defineConfig to validate any function the user might have constructed manually.\n\tconst config = defineConfig(exported as Config);\n\treturn { config, resolvedPath };\n}\n\nfunction resolveExplicitPath(input: string, cwd?: string): string {\n\tconst base = resolve(cwd ?? process.cwd());\n\tconst abs = isAbsolute(input) ? input : resolve(base, input);\n\tif (!existsSync(abs)) {\n\t\tthrow new ConfigLoadError(\n\t\t\t`Config file not found at ${abs}. The path was resolved from \\`${input}\\` against ${base}.`,\n\t\t);\n\t}\n\tconst s = statSync(abs);\n\tif (!s.isFile()) {\n\t\tthrow new ConfigLoadError(\n\t\t\t`Config path ${abs} is a directory, not a file. Pass a path to the file itself (e.g. ./neon.ts).`,\n\t\t);\n\t}\n\treturn abs;\n}\n\nfunction findDefaultConfig(\n\tcwd: string | undefined,\n\tstopAt: string | undefined,\n): string | null {\n\tlet current = resolve(cwd ?? process.cwd());\n\tconst stop = resolve(stopAt ?? homedir());\n\tlet lastSeen: string | null = null;\n\n\twhile (true) {\n\t\tfor (const name of DEFAULT_CONFIG_FILENAMES) {\n\t\t\tconst candidate = resolve(current, name);\n\t\t\tif (existsSync(candidate) && safeIsFile(candidate))\n\t\t\t\treturn candidate;\n\t\t}\n\n\t\t// `.git` is the canonical repo-root marker. `package.json` is deliberately *not*\n\t\t// a stop: monorepos lift `neon.ts` above sub-package package.jsons.\n\t\tif (existsSync(resolve(current, \".git\"))) return null;\n\t\tif (current === stop) return null;\n\n\t\tconst parent = dirname(current);\n\t\tif (parent === current || parent === lastSeen) return null;\n\t\tlastSeen = current;\n\t\tcurrent = parent;\n\t}\n}\n\nasync function importModule(absPath: string): Promise<unknown> {\n\tconst lower = absPath.toLowerCase();\n\tconst needsJiti =\n\t\tlower.endsWith(\".ts\") ||\n\t\tlower.endsWith(\".mts\") ||\n\t\tlower.endsWith(\".cts\");\n\n\tif (!needsJiti) {\n\t\treturn import(pathToFileURL(absPath).href);\n\t}\n\n\tconst jitiModule: unknown = await import(\"jiti\");\n\tconst createJiti = extractCreateJiti(jitiModule);\n\tif (!createJiti) {\n\t\tthrow new ConfigLoadError(\n\t\t\t[\n\t\t\t\t\"jiti is required to load TypeScript config files but could not be initialised.\",\n\t\t\t\t\"Reinstall the package dependencies (`pnpm install` / `npm install`) — jiti is a runtime dependency of @neondatabase/config.\",\n\t\t\t].join(\" \"),\n\t\t);\n\t}\n\tconst jiti = createJiti(pathToFileURL(absPath).href, {\n\t\tinteropDefault: true,\n\t\tmoduleCache: false,\n\t});\n\treturn jiti.import(absPath);\n}\n\nfunction extractCreateJiti(\n\tmod: unknown,\n): ((id: string, options?: unknown) => JitiInstance) | null {\n\tif (mod === null || typeof mod !== \"object\") return null;\n\tconst obj = mod as Record<string, unknown>;\n\tif (typeof obj.createJiti === \"function\") {\n\t\treturn obj.createJiti as (\n\t\t\tid: string,\n\t\t\toptions?: unknown,\n\t\t) => JitiInstance;\n\t}\n\tconst def = obj.default;\n\tif (def !== null && typeof def === \"object\") {\n\t\tconst defObj = def as Record<string, unknown>;\n\t\tif (typeof defObj.createJiti === \"function\") {\n\t\t\treturn defObj.createJiti as (\n\t\t\t\tid: string,\n\t\t\t\toptions?: unknown,\n\t\t\t) => JitiInstance;\n\t\t}\n\t}\n\treturn null;\n}\n\ninterface JitiInstance {\n\timport(id: string): Promise<unknown>;\n}\n\nfunction extractDefaultExport(mod: unknown): unknown {\n\tif (mod === null || typeof mod !== \"object\") return mod;\n\tconst obj = mod as Record<string, unknown>;\n\tif (\"default\" in obj && obj.default !== undefined) return obj.default;\n\t// No `default` export. If the module itself is a function, treat it as the config —\n\t// that lets tests and advanced users skip the wrapper.\n\t// Otherwise, return `undefined` so the caller surfaces a clear ConfigLoadError.\n\tif (typeof mod === \"function\") return mod;\n\treturn undefined;\n}\n\nfunction safeIsFile(path: string): boolean {\n\ttry {\n\t\treturn statSync(path).isFile();\n\t} catch {\n\t\treturn false;\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { NeonApi } from "./neon-api.js";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/neon-api-real.d.ts
|
|
4
|
+
interface CreateNeonAuthRestInput {
|
|
5
|
+
auth_provider: "better_auth";
|
|
6
|
+
database_name?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Adapt `@neondatabase/api-client` to the narrow {@link NeonApi} façade used by the rest of
|
|
10
|
+
* this package. Constructs are restricted to whole-object read/write of just the fields we
|
|
11
|
+
* model in {@link Config}; anything else stays untouched on the remote.
|
|
12
|
+
*/
|
|
13
|
+
declare function createRealNeonApi(options: {
|
|
14
|
+
apiKey: string;
|
|
15
|
+
baseUrl?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Tuning knob for the built-in 423 retry. Defaults: ~30s of total wait spread across
|
|
18
|
+
* 12 attempts with exponential backoff capped at 5s. Lowering this is mostly useful in
|
|
19
|
+
* tests; raising it is rarely needed because Neon operations are usually sub-second.
|
|
20
|
+
*/
|
|
21
|
+
retryOnLocked?: {
|
|
22
|
+
maxAttempts?: number;
|
|
23
|
+
initialDelayMs?: number;
|
|
24
|
+
maxDelayMs?: number;
|
|
25
|
+
};
|
|
26
|
+
}): NeonApi;
|
|
27
|
+
interface RetryConfig {
|
|
28
|
+
maxAttempts: number;
|
|
29
|
+
initialDelayMs: number;
|
|
30
|
+
maxDelayMs: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Retry a function whenever it throws an HTTP 423 (Locked) — Neon's signal that a prior
|
|
34
|
+
* mutation on the same resource is still in flight. Uses exponential backoff capped at
|
|
35
|
+
* `maxDelayMs`. Any other error (and the last attempt) propagates.
|
|
36
|
+
*
|
|
37
|
+
* Exported only for tests; production callers go through the wrapped {@link NeonApi}.
|
|
38
|
+
*/
|
|
39
|
+
declare function retryOnLocked<T>(fn: () => Promise<T>, config: RetryConfig): Promise<T>;
|
|
40
|
+
declare function createNeonAuthRestInput(input: {
|
|
41
|
+
databaseName?: string;
|
|
42
|
+
}): CreateNeonAuthRestInput;
|
|
43
|
+
//#endregion
|
|
44
|
+
export { createNeonAuthRestInput, createRealNeonApi, retryOnLocked };
|
|
45
|
+
//# sourceMappingURL=neon-api-real.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"neon-api-real.d.ts","names":[],"sources":["../../src/lib/neon-api-real.ts"],"mappings":";;;UAoFU,uBAAA;;EAAA,aAAA,CAAA,EAAA,MAAA;AAeV;AAyCC;AAeD;;;;AAES,iBA1DO,iBAAA,CA0DP,OAAA,EAAA;QACE,EAAA,MAAA;SAAR,CAAA,EAAA,MAAA;EAAO;AAwzBV;;;;;;;;;IAt2BI;UA8BM,WAAA;;;;;;;;;;;;iBAaY,2BACX,QAAQ,YACV,cACN,QAAQ;iBAwzBK,uBAAA;;IAEZ"}
|