@neondatabase/config 0.0.0 → 0.1.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 +111 -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 +109 -0
- package/dist/lib/schema.d.ts.map +1 -0
- package/dist/lib/schema.js +199 -0
- package/dist/lib/schema.js.map +1 -0
- package/dist/lib/types.d.ts +259 -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 +132 -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,199 @@
|
|
|
1
|
+
import { parseDuration, parseSuspendTimeout } from "./duration.js";
|
|
2
|
+
import { isWildcardPattern, validatePattern } from "./patterns.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
//#region src/lib/schema.ts
|
|
5
|
+
/**
|
|
6
|
+
* Zod schema for {@link import("./types.js").ComputeSettings}.
|
|
7
|
+
*
|
|
8
|
+
* - CU values must be one of: 0.25, 0.5, 1, 2, 4, 8
|
|
9
|
+
* - `suspendTimeout` can be:
|
|
10
|
+
* - `false` (never suspend)
|
|
11
|
+
* - duration string like "5m", "1h" (must be 60s-604800s when parsed)
|
|
12
|
+
* - number in seconds (60-604800, or -1/0 for special values)
|
|
13
|
+
* - `undefined` (use platform default)
|
|
14
|
+
*
|
|
15
|
+
* Cross-field invariants (min <= max) are enforced via `superRefine`.
|
|
16
|
+
*/
|
|
17
|
+
const computeSettingsSchema = z.strictObject({
|
|
18
|
+
autoscalingLimitMinCu: z.union([
|
|
19
|
+
z.literal(.25),
|
|
20
|
+
z.literal(.5),
|
|
21
|
+
z.literal(1),
|
|
22
|
+
z.literal(2),
|
|
23
|
+
z.literal(4),
|
|
24
|
+
z.literal(8)
|
|
25
|
+
]).optional(),
|
|
26
|
+
autoscalingLimitMaxCu: z.union([
|
|
27
|
+
z.literal(.25),
|
|
28
|
+
z.literal(.5),
|
|
29
|
+
z.literal(1),
|
|
30
|
+
z.literal(2),
|
|
31
|
+
z.literal(4),
|
|
32
|
+
z.literal(8)
|
|
33
|
+
]).optional(),
|
|
34
|
+
suspendTimeout: z.union([
|
|
35
|
+
z.literal(false),
|
|
36
|
+
z.string(),
|
|
37
|
+
z.number()
|
|
38
|
+
]).optional().superRefine((value, ctx) => {
|
|
39
|
+
if (value === void 0) return;
|
|
40
|
+
const result = parseSuspendTimeout(value);
|
|
41
|
+
if ("error" in result) ctx.addIssue({
|
|
42
|
+
code: "custom",
|
|
43
|
+
message: result.error
|
|
44
|
+
});
|
|
45
|
+
})
|
|
46
|
+
}).superRefine((settings, ctx) => {
|
|
47
|
+
const { autoscalingLimitMinCu: min, autoscalingLimitMaxCu: max } = settings;
|
|
48
|
+
if (min !== void 0 && max !== void 0 && min > max) ctx.addIssue({
|
|
49
|
+
code: "custom",
|
|
50
|
+
path: ["autoscalingLimitMinCu"],
|
|
51
|
+
message: `autoscalingLimitMinCu (${min}) must be <= autoscalingLimitMaxCu (${max})`
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
const serviceToggleSchema = z.strictObject({ enabled: z.boolean().optional() });
|
|
55
|
+
const postgresConfigSchema = z.strictObject({ computeSettings: computeSettingsSchema.optional() });
|
|
56
|
+
/**
|
|
57
|
+
* Branch-unique function slug. Mirrors the Neon Functions API path-segment rule
|
|
58
|
+
* (`platform/internal/platform/functions/name.go`): lowercase DNS label, 1–40 chars.
|
|
59
|
+
*/
|
|
60
|
+
const functionSlugSchema = z.string().regex(/^[a-z0-9]([a-z0-9-]{0,38}[a-z0-9])?$/, "function slug must be a lowercase DNS label (1-40 chars, letters/digits/hyphens, no leading/trailing hyphen)");
|
|
61
|
+
/**
|
|
62
|
+
* Per-function environment map. Every value must be a defined string: a `process.env.X`
|
|
63
|
+
* that is unset surfaces as `undefined` and is rejected here (rather than silently
|
|
64
|
+
* shipping `undefined` into the deployment).
|
|
65
|
+
*/
|
|
66
|
+
const functionEnvSchema = z.record(z.string(), z.string());
|
|
67
|
+
const functionConfigSchema = z.strictObject({
|
|
68
|
+
slug: functionSlugSchema,
|
|
69
|
+
name: z.string().min(1).max(255),
|
|
70
|
+
source: z.string().min(1),
|
|
71
|
+
env: functionEnvSchema.optional(),
|
|
72
|
+
runtime: z.literal("nodejs24").optional(),
|
|
73
|
+
memoryMib: z.union([
|
|
74
|
+
z.literal(256),
|
|
75
|
+
z.literal(512),
|
|
76
|
+
z.literal(1024),
|
|
77
|
+
z.literal(2048),
|
|
78
|
+
z.literal(4096),
|
|
79
|
+
z.literal(8192)
|
|
80
|
+
]).optional()
|
|
81
|
+
});
|
|
82
|
+
const bucketConfigSchema = z.strictObject({
|
|
83
|
+
name: z.string().min(1).max(255),
|
|
84
|
+
access: z.union([z.literal("private"), z.literal("public_read")]).optional()
|
|
85
|
+
});
|
|
86
|
+
const previewConfigSchema = z.strictObject({
|
|
87
|
+
functions: z.array(functionConfigSchema).optional(),
|
|
88
|
+
buckets: z.array(bucketConfigSchema).optional(),
|
|
89
|
+
aiGateway: serviceToggleSchema.optional()
|
|
90
|
+
}).superRefine((preview, ctx) => {
|
|
91
|
+
assertUnique({
|
|
92
|
+
ctx,
|
|
93
|
+
path: ["functions"],
|
|
94
|
+
items: preview.functions ?? [],
|
|
95
|
+
key: (fn) => fn.slug,
|
|
96
|
+
label: "function slug"
|
|
97
|
+
});
|
|
98
|
+
assertUnique({
|
|
99
|
+
ctx,
|
|
100
|
+
path: ["buckets"],
|
|
101
|
+
items: preview.buckets ?? [],
|
|
102
|
+
key: (bucket) => bucket.name,
|
|
103
|
+
label: "bucket name"
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
/**
|
|
107
|
+
* Flag duplicate keys within a Preview collection so a typo in two function slugs (or two
|
|
108
|
+
* buckets) surfaces as a config error rather than the second silently clobbering the first
|
|
109
|
+
* at apply time.
|
|
110
|
+
*/
|
|
111
|
+
function assertUnique(args) {
|
|
112
|
+
const { ctx, path, items, key, label } = args;
|
|
113
|
+
const seen = /* @__PURE__ */ new Set();
|
|
114
|
+
items.forEach((item, index) => {
|
|
115
|
+
const value = key(item);
|
|
116
|
+
if (seen.has(value)) ctx.addIssue({
|
|
117
|
+
code: "custom",
|
|
118
|
+
path: [...path, index],
|
|
119
|
+
message: `duplicate ${label}: ${JSON.stringify(value)}`
|
|
120
|
+
});
|
|
121
|
+
seen.add(value);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
const branchConfigSchema = z.strictObject({
|
|
125
|
+
parent: z.string().optional(),
|
|
126
|
+
protected: z.boolean().optional(),
|
|
127
|
+
ttl: z.union([z.string(), z.number()]).optional().superRefine((value, ctx) => {
|
|
128
|
+
if (value === void 0) return;
|
|
129
|
+
const result = parseDuration(value);
|
|
130
|
+
if ("error" in result) ctx.addIssue({
|
|
131
|
+
code: "custom",
|
|
132
|
+
message: result.error
|
|
133
|
+
});
|
|
134
|
+
}),
|
|
135
|
+
postgres: postgresConfigSchema.optional(),
|
|
136
|
+
auth: serviceToggleSchema.optional(),
|
|
137
|
+
dataApi: serviceToggleSchema.optional(),
|
|
138
|
+
preview: previewConfigSchema.optional()
|
|
139
|
+
}).superRefine((cfg, ctx) => {
|
|
140
|
+
validateParentReference({
|
|
141
|
+
ctx,
|
|
142
|
+
path: ["parent"],
|
|
143
|
+
parent: cfg.parent
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
function validateParentReference(args) {
|
|
147
|
+
const { ctx, path, parent } = args;
|
|
148
|
+
if (parent === void 0) return;
|
|
149
|
+
const patternCheck = validatePattern(parent);
|
|
150
|
+
if ("error" in patternCheck) ctx.addIssue({
|
|
151
|
+
code: "custom",
|
|
152
|
+
path,
|
|
153
|
+
message: patternCheck.error
|
|
154
|
+
});
|
|
155
|
+
else if (isWildcardPattern(parent)) ctx.addIssue({
|
|
156
|
+
code: "custom",
|
|
157
|
+
path,
|
|
158
|
+
message: `parent must be a concrete branch name (no wildcards), got "${parent}"`
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
const configSchema = z.function({
|
|
162
|
+
input: [z.unknown()],
|
|
163
|
+
output: z.unknown()
|
|
164
|
+
});
|
|
165
|
+
/**
|
|
166
|
+
* Convert the structured {@link z.ZodError} produced by `configSchema.safeParse` into the
|
|
167
|
+
* `string[]` shape used by {@link import("./errors.js").ConfigValidationError}.
|
|
168
|
+
*
|
|
169
|
+
* Issue paths are rendered as dot-separated property accesses (`postgres.computeSettings`)
|
|
170
|
+
* and unknown-key issues from `strictObject` are normalised so the message contains the
|
|
171
|
+
* substring "unknown key" — keeping pre-zod assertions in test suites and downstream tools
|
|
172
|
+
* stable.
|
|
173
|
+
*/
|
|
174
|
+
function formatZodIssues(error) {
|
|
175
|
+
return error.issues.map((issue) => {
|
|
176
|
+
const path = renderPath(issue.path);
|
|
177
|
+
const message = normaliseIssueMessage(issue);
|
|
178
|
+
return path ? `${path}: ${message}` : message;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
function renderPath(path) {
|
|
182
|
+
let out = "";
|
|
183
|
+
for (const segment of path) if (typeof segment === "number") out += `[${segment}]`;
|
|
184
|
+
else if (out === "") out += String(segment);
|
|
185
|
+
else out += `.${String(segment)}`;
|
|
186
|
+
return out;
|
|
187
|
+
}
|
|
188
|
+
function normaliseIssueMessage(issue) {
|
|
189
|
+
if (issue.code === "unrecognized_keys") {
|
|
190
|
+
const keys = issue.keys ?? [];
|
|
191
|
+
const formatted = keys.map((k) => JSON.stringify(k)).join(", ");
|
|
192
|
+
return `unknown key${keys.length === 1 ? "" : "s"}: ${formatted}`;
|
|
193
|
+
}
|
|
194
|
+
return issue.message;
|
|
195
|
+
}
|
|
196
|
+
//#endregion
|
|
197
|
+
export { branchConfigSchema, bucketConfigSchema, computeSettingsSchema, configSchema, formatZodIssues, functionConfigSchema, postgresConfigSchema, previewConfigSchema, serviceToggleSchema };
|
|
198
|
+
|
|
199
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","names":[],"sources":["../../src/lib/schema.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { parseDuration, parseSuspendTimeout } from \"./duration.js\";\nimport { isWildcardPattern, validatePattern } from \"./patterns.js\";\n\n/**\n * Zod schema for {@link import(\"./types.js\").ComputeSettings}.\n *\n * - CU values must be one of: 0.25, 0.5, 1, 2, 4, 8\n * - `suspendTimeout` can be:\n * - `false` (never suspend)\n * - duration string like \"5m\", \"1h\" (must be 60s-604800s when parsed)\n * - number in seconds (60-604800, or -1/0 for special values)\n * - `undefined` (use platform default)\n *\n * Cross-field invariants (min <= max) are enforced via `superRefine`.\n */\nexport const computeSettingsSchema = z\n\t.strictObject({\n\t\tautoscalingLimitMinCu: z\n\t\t\t.union([\n\t\t\t\tz.literal(0.25),\n\t\t\t\tz.literal(0.5),\n\t\t\t\tz.literal(1),\n\t\t\t\tz.literal(2),\n\t\t\t\tz.literal(4),\n\t\t\t\tz.literal(8),\n\t\t\t])\n\t\t\t.optional(),\n\t\tautoscalingLimitMaxCu: z\n\t\t\t.union([\n\t\t\t\tz.literal(0.25),\n\t\t\t\tz.literal(0.5),\n\t\t\t\tz.literal(1),\n\t\t\t\tz.literal(2),\n\t\t\t\tz.literal(4),\n\t\t\t\tz.literal(8),\n\t\t\t])\n\t\t\t.optional(),\n\t\tsuspendTimeout: z\n\t\t\t.union([z.literal(false), z.string(), z.number()])\n\t\t\t.optional()\n\t\t\t.superRefine((value, ctx) => {\n\t\t\t\tif (value === undefined) return; // undefined is valid (use platform default)\n\t\t\t\tconst result = parseSuspendTimeout(value);\n\t\t\t\tif (\"error\" in result) {\n\t\t\t\t\tctx.addIssue({\n\t\t\t\t\t\tcode: \"custom\",\n\t\t\t\t\t\tmessage: result.error,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}),\n\t})\n\t.superRefine((settings, ctx) => {\n\t\tconst { autoscalingLimitMinCu: min, autoscalingLimitMaxCu: max } =\n\t\t\tsettings;\n\t\tif (min !== undefined && max !== undefined && min > max) {\n\t\t\tctx.addIssue({\n\t\t\t\tcode: \"custom\",\n\t\t\t\tpath: [\"autoscalingLimitMinCu\"],\n\t\t\t\tmessage: `autoscalingLimitMinCu (${min}) must be <= autoscalingLimitMaxCu (${max})`,\n\t\t\t});\n\t\t}\n\t});\n\nexport const serviceToggleSchema = z.strictObject({\n\tenabled: z.boolean().optional(),\n});\n\nexport const postgresConfigSchema = z.strictObject({\n\tcomputeSettings: computeSettingsSchema.optional(),\n});\n\n/**\n * Branch-unique function slug. Mirrors the Neon Functions API path-segment rule\n * (`platform/internal/platform/functions/name.go`): lowercase DNS label, 1–40 chars.\n */\nconst functionSlugSchema = z\n\t.string()\n\t.regex(\n\t\t/^[a-z0-9]([a-z0-9-]{0,38}[a-z0-9])?$/,\n\t\t\"function slug must be a lowercase DNS label (1-40 chars, letters/digits/hyphens, no leading/trailing hyphen)\",\n\t);\n\n/**\n * Per-function environment map. Every value must be a defined string: a `process.env.X`\n * that is unset surfaces as `undefined` and is rejected here (rather than silently\n * shipping `undefined` into the deployment).\n */\nconst functionEnvSchema = z.record(z.string(), z.string());\n\nexport const functionConfigSchema = z.strictObject({\n\tslug: functionSlugSchema,\n\tname: z.string().min(1).max(255),\n\tsource: z.string().min(1),\n\tenv: functionEnvSchema.optional(),\n\truntime: z.literal(\"nodejs24\").optional(),\n\tmemoryMib: z\n\t\t.union([\n\t\t\tz.literal(256),\n\t\t\tz.literal(512),\n\t\t\tz.literal(1024),\n\t\t\tz.literal(2048),\n\t\t\tz.literal(4096),\n\t\t\tz.literal(8192),\n\t\t])\n\t\t.optional(),\n});\n\nexport const bucketConfigSchema = z.strictObject({\n\tname: z.string().min(1).max(255),\n\taccess: z\n\t\t.union([z.literal(\"private\"), z.literal(\"public_read\")])\n\t\t.optional(),\n});\n\nexport const previewConfigSchema = z\n\t.strictObject({\n\t\tfunctions: z.array(functionConfigSchema).optional(),\n\t\tbuckets: z.array(bucketConfigSchema).optional(),\n\t\taiGateway: serviceToggleSchema.optional(),\n\t})\n\t.superRefine((preview, ctx) => {\n\t\tassertUnique({\n\t\t\tctx,\n\t\t\tpath: [\"functions\"],\n\t\t\titems: preview.functions ?? [],\n\t\t\tkey: (fn) => fn.slug,\n\t\t\tlabel: \"function slug\",\n\t\t});\n\t\tassertUnique({\n\t\t\tctx,\n\t\t\tpath: [\"buckets\"],\n\t\t\titems: preview.buckets ?? [],\n\t\t\tkey: (bucket) => bucket.name,\n\t\t\tlabel: \"bucket name\",\n\t\t});\n\t});\n\n/**\n * Flag duplicate keys within a Preview collection so a typo in two function slugs (or two\n * buckets) surfaces as a config error rather than the second silently clobbering the first\n * at apply time.\n */\nfunction assertUnique<T>(args: {\n\tctx: z.RefinementCtx;\n\tpath: (string | number)[];\n\titems: T[];\n\tkey: (item: T) => string;\n\tlabel: string;\n}): void {\n\tconst { ctx, path, items, key, label } = args;\n\tconst seen = new Set<string>();\n\titems.forEach((item, index) => {\n\t\tconst value = key(item);\n\t\tif (seen.has(value)) {\n\t\t\tctx.addIssue({\n\t\t\t\tcode: \"custom\",\n\t\t\t\tpath: [...path, index],\n\t\t\t\tmessage: `duplicate ${label}: ${JSON.stringify(value)}`,\n\t\t\t});\n\t\t}\n\t\tseen.add(value);\n\t});\n}\n\nexport const branchConfigSchema = z\n\t.strictObject({\n\t\tparent: z.string().optional(),\n\t\tprotected: z.boolean().optional(),\n\t\tttl: z\n\t\t\t.union([z.string(), z.number()])\n\t\t\t.optional()\n\t\t\t.superRefine((value, ctx) => {\n\t\t\t\tif (value === undefined) return;\n\t\t\t\tconst result = parseDuration(value);\n\t\t\t\tif (\"error\" in result) {\n\t\t\t\t\tctx.addIssue({ code: \"custom\", message: result.error });\n\t\t\t\t}\n\t\t\t}),\n\t\tpostgres: postgresConfigSchema.optional(),\n\t\tauth: serviceToggleSchema.optional(),\n\t\tdataApi: serviceToggleSchema.optional(),\n\t\tpreview: previewConfigSchema.optional(),\n\t})\n\t.superRefine((cfg, ctx) => {\n\t\tvalidateParentReference({\n\t\t\tctx,\n\t\t\tpath: [\"parent\"],\n\t\t\tparent: cfg.parent,\n\t\t});\n\t});\n\nfunction validateParentReference(args: {\n\tctx: z.RefinementCtx;\n\tpath: (string | number)[];\n\tparent: string | undefined;\n}): void {\n\tconst { ctx, path, parent } = args;\n\tif (parent === undefined) return;\n\n\tconst patternCheck = validatePattern(parent);\n\tif (\"error\" in patternCheck) {\n\t\tctx.addIssue({ code: \"custom\", path, message: patternCheck.error });\n\t} else if (isWildcardPattern(parent)) {\n\t\tctx.addIssue({\n\t\t\tcode: \"custom\",\n\t\t\tpath,\n\t\t\tmessage: `parent must be a concrete branch name (no wildcards), got \"${parent}\"`,\n\t\t});\n\t}\n}\n\nexport const configSchema = z.function({\n\tinput: [z.unknown()],\n\toutput: z.unknown(),\n});\n\n/**\n * Convert the structured {@link z.ZodError} produced by `configSchema.safeParse` into the\n * `string[]` shape used by {@link import(\"./errors.js\").ConfigValidationError}.\n *\n * Issue paths are rendered as dot-separated property accesses (`postgres.computeSettings`)\n * and unknown-key issues from `strictObject` are normalised so the message contains the\n * substring \"unknown key\" — keeping pre-zod assertions in test suites and downstream tools\n * stable.\n */\nexport function formatZodIssues(error: z.ZodError): string[] {\n\treturn error.issues.map((issue) => {\n\t\tconst path = renderPath(issue.path);\n\t\tconst message = normaliseIssueMessage(issue);\n\t\treturn path ? `${path}: ${message}` : message;\n\t});\n}\n\nfunction renderPath(path: ReadonlyArray<PropertyKey>): string {\n\tlet out = \"\";\n\tfor (const segment of path) {\n\t\tif (typeof segment === \"number\") out += `[${segment}]`;\n\t\telse if (out === \"\") out += String(segment);\n\t\telse out += `.${String(segment)}`;\n\t}\n\treturn out;\n}\n\nfunction normaliseIssueMessage(issue: z.core.$ZodIssue): string {\n\tif (issue.code === \"unrecognized_keys\") {\n\t\tconst keys = (issue as z.core.$ZodIssueUnrecognizedKeys).keys ?? [];\n\t\tconst formatted = keys.map((k) => JSON.stringify(k)).join(\", \");\n\t\treturn `unknown key${keys.length === 1 ? \"\" : \"s\"}: ${formatted}`;\n\t}\n\treturn issue.message;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
//#region src/lib/types.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Valid Neon Compute Unit values.
|
|
4
|
+
* Most plans support 0.25, 0.5, 1, 2, 4, 8. Higher values may be available on Business plans.
|
|
5
|
+
*/
|
|
6
|
+
type ComputeUnit = 0.25 | 0.5 | 1 | 2 | 4 | 8;
|
|
7
|
+
/**
|
|
8
|
+
* Compute settings applied to the read/write endpoint of a branch.
|
|
9
|
+
*
|
|
10
|
+
* Mirrors the subset of {@link https://api-docs.neon.tech/reference/getting-started-with-neon-api Neon endpoint}
|
|
11
|
+
* fields that we expose as IaC primitives. Anything left undefined falls back to the project's
|
|
12
|
+
* `default_endpoint_settings` (which themselves fall back to Neon platform defaults).
|
|
13
|
+
*/
|
|
14
|
+
interface ComputeSettings {
|
|
15
|
+
/**
|
|
16
|
+
* Minimum number of Compute Units. Set to 0.25 for true scale-to-zero.
|
|
17
|
+
* @example 0.25 // scale-to-zero
|
|
18
|
+
* @example 1 // always-on with 1 CU minimum
|
|
19
|
+
*/
|
|
20
|
+
autoscalingLimitMinCu?: ComputeUnit;
|
|
21
|
+
/**
|
|
22
|
+
* Maximum number of Compute Units for autoscaling.
|
|
23
|
+
* @example 2
|
|
24
|
+
* @example 8
|
|
25
|
+
*/
|
|
26
|
+
autoscalingLimitMaxCu?: ComputeUnit;
|
|
27
|
+
/**
|
|
28
|
+
* How long to wait before suspending an idle compute.
|
|
29
|
+
*
|
|
30
|
+
* - `false` — never suspend (always-on compute)
|
|
31
|
+
* - `"5m"` — duration string (supports "30s", "5m", "1h", "7d", etc)
|
|
32
|
+
* - `300` — custom timeout in seconds (60-604800)
|
|
33
|
+
* - `undefined` — use Neon platform default (currently 300s / 5 minutes)
|
|
34
|
+
*
|
|
35
|
+
* @example false // never suspend
|
|
36
|
+
* @example "5m" // 5 minutes
|
|
37
|
+
* @example "1h" // 1 hour
|
|
38
|
+
* @example 300 // 5 minutes in seconds
|
|
39
|
+
*/
|
|
40
|
+
suspendTimeout?: false | "5m" | "1h" | string | number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Read-only descriptor of the branch a {@link Config} policy is being evaluated for — the
|
|
44
|
+
* `branch` argument passed to your `defineConfig((branch) => …)` callback. It describes
|
|
45
|
+
* **which** branch this invocation decides for; it is not a live branch handle and must not
|
|
46
|
+
* be mutated. Switch on its fields and return the desired {@link BranchConfig}.
|
|
47
|
+
*/
|
|
48
|
+
interface BranchTarget {
|
|
49
|
+
/** Branch name being evaluated. For `branch dev`, this is the generated branch name. */
|
|
50
|
+
name: string;
|
|
51
|
+
/** Neon branch id when the branch already exists. Undefined during pre-create eval. */
|
|
52
|
+
id?: string;
|
|
53
|
+
/** Whether this branch already exists on Neon. */
|
|
54
|
+
exists: boolean;
|
|
55
|
+
/** Parent branch id from Neon when known. */
|
|
56
|
+
parentId?: string;
|
|
57
|
+
/** Whether Neon marks this branch as the project default. */
|
|
58
|
+
isDefault?: boolean;
|
|
59
|
+
/** Whether Neon currently marks this branch protected. */
|
|
60
|
+
isProtected?: boolean;
|
|
61
|
+
/** Current expiration timestamp from Neon, when set. */
|
|
62
|
+
expiresAt?: string;
|
|
63
|
+
}
|
|
64
|
+
interface ServiceToggle {
|
|
65
|
+
/** Defaults to `true` when the service namespace is present. Set `false` to opt out. */
|
|
66
|
+
enabled?: boolean;
|
|
67
|
+
}
|
|
68
|
+
interface PostgresConfig {
|
|
69
|
+
computeSettings?: ComputeSettings;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Supported function runtimes. Mirrors the Neon Functions deploy API `runtime` enum.
|
|
73
|
+
* Only `nodejs24` exists today; kept as a union so adding runtimes later is a
|
|
74
|
+
* non-breaking, type-checked change.
|
|
75
|
+
*/
|
|
76
|
+
type FunctionRuntime = "nodejs24";
|
|
77
|
+
/**
|
|
78
|
+
* Memory sizes (MiB) accepted by the Neon Functions deploy API. Mirrors the
|
|
79
|
+
* `memory_mib` enum in the spec.
|
|
80
|
+
*/
|
|
81
|
+
type FunctionMemoryMib = 256 | 512 | 1024 | 2048 | 4096 | 8192;
|
|
82
|
+
/**
|
|
83
|
+
* A single Neon Function deployed to a branch (Preview feature).
|
|
84
|
+
*
|
|
85
|
+
* A function is invoked like a Cloudflare/Vercel handler — its source module
|
|
86
|
+
* `export default { fetch }` or `export async function handler(req): Response`. The
|
|
87
|
+
* `source` path is bundled (esbuild) and uploaded as a deployment; the newest
|
|
88
|
+
* deployment becomes active.
|
|
89
|
+
*/
|
|
90
|
+
interface FunctionConfig {
|
|
91
|
+
/**
|
|
92
|
+
* Branch-unique, lowercase DNS-label used as the path segment in the function's
|
|
93
|
+
* invocation URL. Immutable once created. 1–40 chars, `^[a-z0-9]([a-z0-9-]{0,38}[a-z0-9])?$`.
|
|
94
|
+
* @example "hello-world"
|
|
95
|
+
*/
|
|
96
|
+
slug: string;
|
|
97
|
+
/** Free-form display name. @example "Hello World" */
|
|
98
|
+
name: string;
|
|
99
|
+
/**
|
|
100
|
+
* Path to the function's entry module, **relative to `neon.ts`** (or absolute). The
|
|
101
|
+
* module's default export (`{ fetch }`) or `handler` export is the function entry. This
|
|
102
|
+
* path is resolved against the loaded `neon.ts` location and bundled with esbuild at
|
|
103
|
+
* deploy time.
|
|
104
|
+
*
|
|
105
|
+
* We require a string path rather than an imported handler because a JS function value
|
|
106
|
+
* carries no reference back to its source file, so esbuild has nothing to bundle from.
|
|
107
|
+
* @example "./functions/hello-world.ts"
|
|
108
|
+
*/
|
|
109
|
+
source: string;
|
|
110
|
+
/**
|
|
111
|
+
* Environment variables injected into the deployed function. Every value must be a
|
|
112
|
+
* defined string — a `process.env.X` that is `undefined` (unset) errors at validation
|
|
113
|
+
* time rather than silently shipping `undefined`.
|
|
114
|
+
* @example { RESEND_API_KEY: process.env.RESEND_API_KEY }
|
|
115
|
+
*/
|
|
116
|
+
env?: Record<string, string>;
|
|
117
|
+
/** Runtime to execute the function with. Defaults to `"nodejs24"`. */
|
|
118
|
+
runtime?: FunctionRuntime;
|
|
119
|
+
/** Memory allotted to each invocation, in MiB. Defaults to `512`. */
|
|
120
|
+
memoryMib?: FunctionMemoryMib;
|
|
121
|
+
}
|
|
122
|
+
/** Anonymous-access level for a branchable object-storage bucket. */
|
|
123
|
+
type BucketAccessLevel = "private" | "public_read";
|
|
124
|
+
/**
|
|
125
|
+
* A branchable object-storage bucket on a branch (Preview feature).
|
|
126
|
+
*/
|
|
127
|
+
interface BucketConfig {
|
|
128
|
+
/** Bucket name, unique within a branch. 1–255 chars. */
|
|
129
|
+
name: string;
|
|
130
|
+
/**
|
|
131
|
+
* Anonymous access level. `private` (default) requires authenticated reads/writes;
|
|
132
|
+
* `public_read` allows anonymous GetObject/HeadObject.
|
|
133
|
+
*/
|
|
134
|
+
access?: BucketAccessLevel;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Branch-scoped Preview features. Grouped under `preview` to signal they are backed by
|
|
138
|
+
* Neon `x-stability-level: beta` endpoints and may change before GA.
|
|
139
|
+
*/
|
|
140
|
+
interface PreviewConfig {
|
|
141
|
+
/** Functions to deploy on the branch. */
|
|
142
|
+
functions?: FunctionConfig[];
|
|
143
|
+
/** Object-storage buckets to create on the branch. */
|
|
144
|
+
buckets?: BucketConfig[];
|
|
145
|
+
/** Enable/disable the AI Gateway on the branch (toggle, like auth / dataApi). */
|
|
146
|
+
aiGateway?: ServiceToggle;
|
|
147
|
+
}
|
|
148
|
+
interface BranchConfigBase {
|
|
149
|
+
/** Parent branch name used when creating a new branch. Not a Postgres setting. */
|
|
150
|
+
parent?: string;
|
|
151
|
+
/** Time-to-live applied when creating a new branch, or reconciled on existing branches. */
|
|
152
|
+
ttl?: string | number;
|
|
153
|
+
/** Whether the selected branch should be protected. Undefined means "leave as-is". */
|
|
154
|
+
protected?: boolean;
|
|
155
|
+
postgres?: PostgresConfig;
|
|
156
|
+
/**
|
|
157
|
+
* Branch-scoped Preview features (functions, object-storage buckets, AI Gateway).
|
|
158
|
+
* Backed by Neon `x-stability-level: beta` endpoints — see {@link PreviewConfig}.
|
|
159
|
+
*/
|
|
160
|
+
preview?: PreviewConfig;
|
|
161
|
+
}
|
|
162
|
+
type BranchServiceConfig = {
|
|
163
|
+
auth?: never;
|
|
164
|
+
dataApi?: never;
|
|
165
|
+
} | {
|
|
166
|
+
auth: ServiceToggle;
|
|
167
|
+
dataApi?: never;
|
|
168
|
+
} | {
|
|
169
|
+
auth?: never;
|
|
170
|
+
dataApi: ServiceToggle;
|
|
171
|
+
} | {
|
|
172
|
+
auth: ServiceToggle;
|
|
173
|
+
dataApi: ServiceToggle;
|
|
174
|
+
};
|
|
175
|
+
type BranchConfig = BranchConfigBase & BranchServiceConfig;
|
|
176
|
+
type Config = (branch: BranchTarget) => BranchConfig;
|
|
177
|
+
/**
|
|
178
|
+
* A function with all deploy defaults applied. `resolveConfig` fills in `runtime` and
|
|
179
|
+
* `memoryMib` so downstream diff/apply never has to re-derive them.
|
|
180
|
+
*/
|
|
181
|
+
interface ResolvedFunctionConfig {
|
|
182
|
+
slug: string;
|
|
183
|
+
name: string;
|
|
184
|
+
source: string;
|
|
185
|
+
env: Record<string, string>;
|
|
186
|
+
runtime: FunctionRuntime;
|
|
187
|
+
memoryMib: FunctionMemoryMib;
|
|
188
|
+
}
|
|
189
|
+
/** A bucket with its access level defaulted to `private`. */
|
|
190
|
+
interface ResolvedBucketConfig {
|
|
191
|
+
name: string;
|
|
192
|
+
access: BucketAccessLevel;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Normalized {@link PreviewConfig}. Only present on {@link ResolvedBranchConfig} when the
|
|
196
|
+
* policy returned a `preview` block. `aiGatewayEnabled` follows the same
|
|
197
|
+
* "present-and-not-`false`" semantics as `authEnabled` / `dataApiEnabled`.
|
|
198
|
+
*/
|
|
199
|
+
interface ResolvedPreviewConfig {
|
|
200
|
+
functions: ResolvedFunctionConfig[];
|
|
201
|
+
buckets: ResolvedBucketConfig[];
|
|
202
|
+
aiGatewayEnabled: boolean;
|
|
203
|
+
}
|
|
204
|
+
interface ResolvedBranchConfig {
|
|
205
|
+
parent?: string;
|
|
206
|
+
ttlSeconds?: number;
|
|
207
|
+
protected?: boolean;
|
|
208
|
+
postgres?: PostgresConfig;
|
|
209
|
+
authEnabled: boolean;
|
|
210
|
+
dataApiEnabled: boolean;
|
|
211
|
+
preview?: ResolvedPreviewConfig;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* One concrete change `pushConfig` made (or, in dry-run, would make) on the remote.
|
|
215
|
+
*/
|
|
216
|
+
interface AppliedChange {
|
|
217
|
+
/**
|
|
218
|
+
* `service` covers branch-scoped integrations driven by the branch policy (e.g.
|
|
219
|
+
* Neon Auth, Data API).
|
|
220
|
+
*/
|
|
221
|
+
kind: "branch" | "service";
|
|
222
|
+
action: "create" | "update" | "noop";
|
|
223
|
+
identifier: string;
|
|
224
|
+
details?: Record<string, unknown>;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* A diff entry that conflicts with the desired config. `pushConfig` throws
|
|
228
|
+
* {@link PushConflictError} on the first call when conflicts exist; pass
|
|
229
|
+
* `updateExisting: true` to apply mutable drift (settings, `protected`, TTL, project
|
|
230
|
+
* rename). Immutable fields (region, Postgres major version) are always conflicts —
|
|
231
|
+
* recreate the project to change them.
|
|
232
|
+
*/
|
|
233
|
+
interface ConflictReport {
|
|
234
|
+
kind: "branch";
|
|
235
|
+
identifier: string;
|
|
236
|
+
field: string;
|
|
237
|
+
current: unknown;
|
|
238
|
+
desired: unknown;
|
|
239
|
+
reason: string;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Result of a `pushConfig` invocation.
|
|
243
|
+
*/
|
|
244
|
+
interface PushResult {
|
|
245
|
+
projectId: string;
|
|
246
|
+
orgId?: string;
|
|
247
|
+
branchId: string;
|
|
248
|
+
branchName: string;
|
|
249
|
+
/**
|
|
250
|
+
* `true` when `pushConfig` was called with `{ dryRun: true }`. `applied` then records
|
|
251
|
+
* what **would** be applied on a real push; no API mutations were performed.
|
|
252
|
+
*/
|
|
253
|
+
dryRun: boolean;
|
|
254
|
+
applied: AppliedChange[];
|
|
255
|
+
conflicts: ConflictReport[];
|
|
256
|
+
}
|
|
257
|
+
//#endregion
|
|
258
|
+
export { AppliedChange, BranchConfig, BranchTarget, BucketAccessLevel, BucketConfig, ComputeSettings, ComputeUnit, Config, ConflictReport, FunctionConfig, FunctionMemoryMib, FunctionRuntime, PostgresConfig, PreviewConfig, PushResult, ResolvedBranchConfig, ResolvedBucketConfig, ResolvedFunctionConfig, ResolvedPreviewConfig, ServiceToggle };
|
|
259
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/lib/types.ts"],"mappings":";;AAIA;AASA;;AAMyB,KAfb,WAAA,GAea,IAAA,GAAA,GAAA,GAAA,CAAA,GAAA,CAAA,GAAA,CAAA,GAAA,CAAA;;AAMW;AAuBpC;AAiBA;AAKA;AASA;AAMA;AAUiB,UAlFA,eAAA,CAkFc;EAAA;;;;AA8BD;EAIlB,qBAAiB,CAAA,EA9GJ,WA8GI;EAKZ;AAcjB;;;;uBAMa,CAAA,EAjIY,WAiIZ;EAAa;AACzB;;;;AAcuB;AAAA;;;;;;AAOwB;EAEpC,cAAA,CAAY,EAAA,KAAA,GAAA,IAAA,GAAA,IAAA,GAAA,MAAA,GAAA,MAAA;;;;AAAyC;AAEjE;;;AAA+C,UApI9B,YAAA,CAoI8B;EAAY;EAM1C,IAAA,EAAA,MAAA;EAAsB;KAIjC,EAAA,MAAA;;QAEM,EAAA,OAAA;EAAiB;EAIZ,QAAA,CAAA,EAAA,MAAA;EAUA;EAAqB,SAAA,CAAA,EAAA,OAAA;;aAE5B,CAAA,EAAA,OAAA;EAAoB;EAIb,SAAA,CAAA,EAAA,MAAA;;AAIL,UAvJK,aAAA,CAuJL;;EAGoB,OAAA,CAAA,EAAA,OAAA;AAMhC;AAkBiB,UA7KA,cAAA,CA6Kc;EAYd,eAAU,CAAA,EAxLR,eAwLQ;;;;AAWD;;;KA3Ld,eAAA;;;;;KAMA,iBAAA;;;;;;;;;UAUK,cAAA;;;;;;;;;;;;;;;;;;;;;;;;;;QA0BV;;YAEI;;cAEE;;;KAID,iBAAA;;;;UAKK,YAAA;;;;;;;WAOP;;;;;;UAOO,aAAA;;cAEJ;;YAEF;;cAEE;;UAGH,gBAAA;;;;;;;aAOE;;;;;YAKD;;KAGN,mBAAA;;;;QAEM;;;;WACiB;;QACjB;WAAwB;;KAEvB,YAAA,GAAe,mBAAmB;KAElC,MAAA,YAAkB,iBAAiB;;;;;UAM9B,sBAAA;;;;OAIX;WACI;aACE;;;UAIK,oBAAA;;UAER;;;;;;;UAQQ,qBAAA;aACL;WACF;;;UAIO,oBAAA;;;;aAIL;;;YAGD;;;;;UAMM,aAAA;;;;;;;;YAQN;;;;;;;;;UAUM,cAAA;;;;;;;;;;;UAYA,UAAA;;;;;;;;;;WAUP;aACE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { PlatformError } from "./errors.js";
|
|
2
|
+
|
|
3
|
+
//#region src/lib/wrap-neon-error.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Context the wrapper attaches to every PlatformError so consumers can debug without
|
|
7
|
+
* digging into the raw axios stack.
|
|
8
|
+
*/
|
|
9
|
+
interface NeonErrorContext {
|
|
10
|
+
/** Short label of the operation that failed, e.g. `getProject(proj-foo)` or `createBranch`. */
|
|
11
|
+
op: string;
|
|
12
|
+
/** Optional project id when the operation is project-scoped. */
|
|
13
|
+
projectId?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Turn a raw error from `@neondatabase/api-client` (axios under the hood) into a typed
|
|
17
|
+
* {@link PlatformError} whose message includes:
|
|
18
|
+
*
|
|
19
|
+
* 1. What operation was attempted (`op`, e.g. `getProject(proj-foo)`).
|
|
20
|
+
* 2. Why it failed in human terms (e.g. "API key is unauthorized").
|
|
21
|
+
* 3. The exact Neon API error message + request id (when present) for support tickets.
|
|
22
|
+
* 4. A concrete next action ("Generate a new key at …", "Pass `projectId`", …).
|
|
23
|
+
*
|
|
24
|
+
* Non-axios errors are passed through unchanged (a regular `Error` already has a useful
|
|
25
|
+
* stack trace; wrapping it would lose information without adding value).
|
|
26
|
+
*/
|
|
27
|
+
declare function wrapNeonError(err: unknown, context: NeonErrorContext): PlatformError | unknown;
|
|
28
|
+
//#endregion
|
|
29
|
+
export { NeonErrorContext, wrapNeonError };
|
|
30
|
+
//# sourceMappingURL=wrap-neon-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wrap-neon-error.d.ts","names":[],"sources":["../../src/lib/wrap-neon-error.ts"],"mappings":";;;;;;AAMA;AAmBA;AAA6B,UAnBZ,gBAAA,CAmBY;;MAG1B,MAAA;EAAa;;;;;;;;;;;;;;;iBAHA,aAAA,wBAEN,mBACP"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { ErrorCode, PlatformError } from "./errors.js";
|
|
2
|
+
//#region src/lib/wrap-neon-error.ts
|
|
3
|
+
/**
|
|
4
|
+
* Turn a raw error from `@neondatabase/api-client` (axios under the hood) into a typed
|
|
5
|
+
* {@link PlatformError} whose message includes:
|
|
6
|
+
*
|
|
7
|
+
* 1. What operation was attempted (`op`, e.g. `getProject(proj-foo)`).
|
|
8
|
+
* 2. Why it failed in human terms (e.g. "API key is unauthorized").
|
|
9
|
+
* 3. The exact Neon API error message + request id (when present) for support tickets.
|
|
10
|
+
* 4. A concrete next action ("Generate a new key at …", "Pass `projectId`", …).
|
|
11
|
+
*
|
|
12
|
+
* Non-axios errors are passed through unchanged (a regular `Error` already has a useful
|
|
13
|
+
* stack trace; wrapping it would lose information without adding value).
|
|
14
|
+
*/
|
|
15
|
+
function wrapNeonError(err, context) {
|
|
16
|
+
if (err instanceof PlatformError) return err;
|
|
17
|
+
const httpInfo = extractHttpInfo(err);
|
|
18
|
+
if (!httpInfo) {
|
|
19
|
+
const networkInfo = extractNetworkInfo(err);
|
|
20
|
+
if (networkInfo) return new PlatformError(ErrorCode.NetworkError, `Could not reach the Neon API while running ${context.op}: ${networkInfo.message}. Check your network connection and that https://console.neon.tech is reachable.`, {
|
|
21
|
+
cause: err,
|
|
22
|
+
details: {
|
|
23
|
+
op: context.op,
|
|
24
|
+
...networkInfo
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
return err;
|
|
28
|
+
}
|
|
29
|
+
const apiSummary = httpInfo.neonMessage ? `Neon API said: "${httpInfo.neonMessage}"` : `HTTP ${httpInfo.status}`;
|
|
30
|
+
const requestIdSuffix = httpInfo.requestId ? ` (request id ${httpInfo.requestId})` : "";
|
|
31
|
+
const apiSummaryWithRequestId = `${apiSummary}${requestIdSuffix}.`;
|
|
32
|
+
switch (httpInfo.status) {
|
|
33
|
+
case 401: return new PlatformError(ErrorCode.Unauthorized, [
|
|
34
|
+
`${context.op} failed: the Bearer token sent to the Neon API was rejected.`,
|
|
35
|
+
apiSummaryWithRequestId,
|
|
36
|
+
"Either (a) generate or rotate an API key at https://console.neon.tech/app/settings/api-keys and set NEON_API_KEY / pass --api-key, or (b) re-run `npx neonctl auth` to refresh the OAuth token in `~/.config/neonctl/credentials.json` (OAuth tokens expire)."
|
|
37
|
+
].join(" "), {
|
|
38
|
+
cause: err,
|
|
39
|
+
details: httpDetails(context, httpInfo)
|
|
40
|
+
});
|
|
41
|
+
case 403: return new PlatformError(ErrorCode.Forbidden, [
|
|
42
|
+
`${context.op} failed: this API key is not allowed to perform that operation.`,
|
|
43
|
+
apiSummaryWithRequestId,
|
|
44
|
+
"Project-scoped keys can only operate on their own project; switch to an organisation/user-scoped key or pass `projectId` for an operation that doesn't need listing."
|
|
45
|
+
].join(" "), {
|
|
46
|
+
cause: err,
|
|
47
|
+
details: httpDetails(context, httpInfo)
|
|
48
|
+
});
|
|
49
|
+
case 404: return new PlatformError(ErrorCode.NotFound, [
|
|
50
|
+
`${context.op} failed: resource not found on Neon.`,
|
|
51
|
+
apiSummaryWithRequestId,
|
|
52
|
+
context.projectId ? `Verify that project '${context.projectId}' exists in this account and that the API key has access to it.` : "Verify that the resource id is correct and that the API key has access to it."
|
|
53
|
+
].join(" "), {
|
|
54
|
+
cause: err,
|
|
55
|
+
details: httpDetails(context, httpInfo)
|
|
56
|
+
});
|
|
57
|
+
case 409: return new PlatformError(ErrorCode.Conflict, [
|
|
58
|
+
`${context.op} failed: a conflicting resource already exists on Neon.`,
|
|
59
|
+
apiSummaryWithRequestId,
|
|
60
|
+
"This is often a name collision (e.g. a branch with the same name already exists). Pull first to compare against the remote, or rename in your `neon.ts`."
|
|
61
|
+
].join(" "), {
|
|
62
|
+
cause: err,
|
|
63
|
+
details: httpDetails(context, httpInfo)
|
|
64
|
+
});
|
|
65
|
+
case 423: return new PlatformError(ErrorCode.Locked, [
|
|
66
|
+
`${context.op} failed: the resource is still being modified by a previous operation, and our built-in retries did not drain it in time.`,
|
|
67
|
+
apiSummaryWithRequestId,
|
|
68
|
+
"Wait a few seconds and re-run, or raise `retryOnLocked.maxAttempts` when constructing the real Neon adapter."
|
|
69
|
+
].join(" "), {
|
|
70
|
+
cause: err,
|
|
71
|
+
details: httpDetails(context, httpInfo)
|
|
72
|
+
});
|
|
73
|
+
case 429: return new PlatformError(ErrorCode.RateLimited, [
|
|
74
|
+
`${context.op} failed: rate-limited by the Neon API.`,
|
|
75
|
+
apiSummaryWithRequestId,
|
|
76
|
+
"Back off and retry; if this happens repeatedly, contact Neon support with the request id above."
|
|
77
|
+
].join(" "), {
|
|
78
|
+
cause: err,
|
|
79
|
+
details: httpDetails(context, httpInfo)
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
if (httpInfo.status >= 500) return new PlatformError(ErrorCode.ServerError, [
|
|
83
|
+
`${context.op} failed: the Neon API returned a server error (HTTP ${httpInfo.status}).`,
|
|
84
|
+
apiSummaryWithRequestId,
|
|
85
|
+
"This is most likely transient. Retry shortly; if it persists, file an issue with the request id above and check https://neonstatus.com."
|
|
86
|
+
].join(" "), {
|
|
87
|
+
cause: err,
|
|
88
|
+
details: httpDetails(context, httpInfo)
|
|
89
|
+
});
|
|
90
|
+
return new PlatformError(ErrorCode.ServerError, `${context.op} failed: HTTP ${httpInfo.status}. ${apiSummary}${requestIdSuffix}.`, {
|
|
91
|
+
cause: err,
|
|
92
|
+
details: httpDetails(context, httpInfo)
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function extractHttpInfo(err) {
|
|
96
|
+
if (err === null || typeof err !== "object") return null;
|
|
97
|
+
const response = err.response;
|
|
98
|
+
if (response === null || typeof response !== "object") return null;
|
|
99
|
+
const status = response.status;
|
|
100
|
+
if (typeof status !== "number") return null;
|
|
101
|
+
const data = response.data;
|
|
102
|
+
const out = { status };
|
|
103
|
+
if (data !== null && typeof data === "object") {
|
|
104
|
+
const dataObj = data;
|
|
105
|
+
if (typeof dataObj.message === "string" && dataObj.message !== "") out.neonMessage = dataObj.message;
|
|
106
|
+
if (typeof dataObj.code === "string" && dataObj.code !== "") out.neonCode = dataObj.code;
|
|
107
|
+
if (typeof dataObj.request_id === "string" && dataObj.request_id !== "") out.requestId = dataObj.request_id;
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
function extractNetworkInfo(err) {
|
|
112
|
+
if (err === null || typeof err !== "object") return null;
|
|
113
|
+
const code = err.code;
|
|
114
|
+
const message = err.message;
|
|
115
|
+
if (typeof code === "string" && /^(ECONNREFUSED|ECONNRESET|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|EPIPE|EHOSTUNREACH|ENETUNREACH)$/.test(code)) return {
|
|
116
|
+
message: typeof message === "string" ? message : code,
|
|
117
|
+
code
|
|
118
|
+
};
|
|
119
|
+
if (code === "ECONNABORTED") return {
|
|
120
|
+
message: typeof message === "string" ? message : "timeout",
|
|
121
|
+
code
|
|
122
|
+
};
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
function httpDetails(context, info) {
|
|
126
|
+
const out = {
|
|
127
|
+
op: context.op,
|
|
128
|
+
status: info.status
|
|
129
|
+
};
|
|
130
|
+
if (context.projectId) out.projectId = context.projectId;
|
|
131
|
+
if (info.neonMessage) out.neonMessage = info.neonMessage;
|
|
132
|
+
if (info.neonCode) out.neonCode = info.neonCode;
|
|
133
|
+
if (info.requestId) out.requestId = info.requestId;
|
|
134
|
+
return out;
|
|
135
|
+
}
|
|
136
|
+
//#endregion
|
|
137
|
+
export { wrapNeonError };
|
|
138
|
+
|
|
139
|
+
//# sourceMappingURL=wrap-neon-error.js.map
|