@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 @@
|
|
|
1
|
+
{"version":3,"file":"wrap-neon-error.js","names":[],"sources":["../../src/lib/wrap-neon-error.ts"],"sourcesContent":["import { ErrorCode, PlatformError } from \"./errors.js\";\n\n/**\n * Context the wrapper attaches to every PlatformError so consumers can debug without\n * digging into the raw axios stack.\n */\nexport interface NeonErrorContext {\n\t/** Short label of the operation that failed, e.g. `getProject(proj-foo)` or `createBranch`. */\n\top: string;\n\t/** Optional project id when the operation is project-scoped. */\n\tprojectId?: string;\n}\n\n/**\n * Turn a raw error from `@neondatabase/api-client` (axios under the hood) into a typed\n * {@link PlatformError} whose message includes:\n *\n * 1. What operation was attempted (`op`, e.g. `getProject(proj-foo)`).\n * 2. Why it failed in human terms (e.g. \"API key is unauthorized\").\n * 3. The exact Neon API error message + request id (when present) for support tickets.\n * 4. A concrete next action (\"Generate a new key at …\", \"Pass `projectId`\", …).\n *\n * Non-axios errors are passed through unchanged (a regular `Error` already has a useful\n * stack trace; wrapping it would lose information without adding value).\n */\nexport function wrapNeonError(\n\terr: unknown,\n\tcontext: NeonErrorContext,\n): PlatformError | unknown {\n\tif (err instanceof PlatformError) return err;\n\tconst httpInfo = extractHttpInfo(err);\n\tif (!httpInfo) {\n\t\tconst networkInfo = extractNetworkInfo(err);\n\t\tif (networkInfo) {\n\t\t\treturn new PlatformError(\n\t\t\t\tErrorCode.NetworkError,\n\t\t\t\t`Could not reach the Neon API while running ${context.op}: ${networkInfo.message}. Check your network connection and that https://console.neon.tech is reachable.`,\n\t\t\t\t{ cause: err, details: { op: context.op, ...networkInfo } },\n\t\t\t);\n\t\t}\n\t\treturn err;\n\t}\n\n\tconst apiSummary = httpInfo.neonMessage\n\t\t? `Neon API said: \"${httpInfo.neonMessage}\"`\n\t\t: `HTTP ${httpInfo.status}`;\n\tconst requestIdSuffix = httpInfo.requestId\n\t\t? ` (request id ${httpInfo.requestId})`\n\t\t: \"\";\n\tconst apiSummaryWithRequestId = `${apiSummary}${requestIdSuffix}.`;\n\n\tswitch (httpInfo.status) {\n\t\tcase 401:\n\t\t\treturn new PlatformError(\n\t\t\t\tErrorCode.Unauthorized,\n\t\t\t\t[\n\t\t\t\t\t`${context.op} failed: the Bearer token sent to the Neon API was rejected.`,\n\t\t\t\t\tapiSummaryWithRequestId,\n\t\t\t\t\t\"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).\",\n\t\t\t\t].join(\" \"),\n\t\t\t\t{ cause: err, details: httpDetails(context, httpInfo) },\n\t\t\t);\n\t\tcase 403:\n\t\t\treturn new PlatformError(\n\t\t\t\tErrorCode.Forbidden,\n\t\t\t\t[\n\t\t\t\t\t`${context.op} failed: this API key is not allowed to perform that operation.`,\n\t\t\t\t\tapiSummaryWithRequestId,\n\t\t\t\t\t\"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.\",\n\t\t\t\t].join(\" \"),\n\t\t\t\t{ cause: err, details: httpDetails(context, httpInfo) },\n\t\t\t);\n\t\tcase 404:\n\t\t\treturn new PlatformError(\n\t\t\t\tErrorCode.NotFound,\n\t\t\t\t[\n\t\t\t\t\t`${context.op} failed: resource not found on Neon.`,\n\t\t\t\t\tapiSummaryWithRequestId,\n\t\t\t\t\tcontext.projectId\n\t\t\t\t\t\t? `Verify that project '${context.projectId}' exists in this account and that the API key has access to it.`\n\t\t\t\t\t\t: \"Verify that the resource id is correct and that the API key has access to it.\",\n\t\t\t\t].join(\" \"),\n\t\t\t\t{ cause: err, details: httpDetails(context, httpInfo) },\n\t\t\t);\n\t\tcase 409:\n\t\t\treturn new PlatformError(\n\t\t\t\tErrorCode.Conflict,\n\t\t\t\t[\n\t\t\t\t\t`${context.op} failed: a conflicting resource already exists on Neon.`,\n\t\t\t\t\tapiSummaryWithRequestId,\n\t\t\t\t\t\"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`.\",\n\t\t\t\t].join(\" \"),\n\t\t\t\t{ cause: err, details: httpDetails(context, httpInfo) },\n\t\t\t);\n\t\tcase 423:\n\t\t\treturn new PlatformError(\n\t\t\t\tErrorCode.Locked,\n\t\t\t\t[\n\t\t\t\t\t`${context.op} failed: the resource is still being modified by a previous operation, and our built-in retries did not drain it in time.`,\n\t\t\t\t\tapiSummaryWithRequestId,\n\t\t\t\t\t\"Wait a few seconds and re-run, or raise `retryOnLocked.maxAttempts` when constructing the real Neon adapter.\",\n\t\t\t\t].join(\" \"),\n\t\t\t\t{ cause: err, details: httpDetails(context, httpInfo) },\n\t\t\t);\n\t\tcase 429:\n\t\t\treturn new PlatformError(\n\t\t\t\tErrorCode.RateLimited,\n\t\t\t\t[\n\t\t\t\t\t`${context.op} failed: rate-limited by the Neon API.`,\n\t\t\t\t\tapiSummaryWithRequestId,\n\t\t\t\t\t\"Back off and retry; if this happens repeatedly, contact Neon support with the request id above.\",\n\t\t\t\t].join(\" \"),\n\t\t\t\t{ cause: err, details: httpDetails(context, httpInfo) },\n\t\t\t);\n\t}\n\n\tif (httpInfo.status >= 500) {\n\t\treturn new PlatformError(\n\t\t\tErrorCode.ServerError,\n\t\t\t[\n\t\t\t\t`${context.op} failed: the Neon API returned a server error (HTTP ${httpInfo.status}).`,\n\t\t\t\tapiSummaryWithRequestId,\n\t\t\t\t\"This is most likely transient. Retry shortly; if it persists, file an issue with the request id above and check https://neonstatus.com.\",\n\t\t\t].join(\" \"),\n\t\t\t{ cause: err, details: httpDetails(context, httpInfo) },\n\t\t);\n\t}\n\n\t// 4xx we don't have a dedicated code for. Surface what we know.\n\treturn new PlatformError(\n\t\tErrorCode.ServerError,\n\t\t`${context.op} failed: HTTP ${httpInfo.status}. ${apiSummary}${requestIdSuffix}.`,\n\t\t{ cause: err, details: httpDetails(context, httpInfo) },\n\t);\n}\n\ninterface HttpInfo {\n\tstatus: number;\n\tneonMessage?: string;\n\tneonCode?: string;\n\trequestId?: string;\n}\n\nfunction extractHttpInfo(err: unknown): HttpInfo | null {\n\tif (err === null || typeof err !== \"object\") return null;\n\tconst response = (err as { response?: unknown }).response;\n\tif (response === null || typeof response !== \"object\") return null;\n\tconst status = (response as { status?: unknown }).status;\n\tif (typeof status !== \"number\") return null;\n\tconst data = (response as { data?: unknown }).data;\n\tconst out: HttpInfo = { status };\n\tif (data !== null && typeof data === \"object\") {\n\t\tconst dataObj = data as Record<string, unknown>;\n\t\tif (typeof dataObj.message === \"string\" && dataObj.message !== \"\")\n\t\t\tout.neonMessage = dataObj.message;\n\t\tif (typeof dataObj.code === \"string\" && dataObj.code !== \"\")\n\t\t\tout.neonCode = dataObj.code;\n\t\tif (typeof dataObj.request_id === \"string\" && dataObj.request_id !== \"\")\n\t\t\tout.requestId = dataObj.request_id;\n\t}\n\treturn out;\n}\n\ninterface NetworkInfo {\n\tmessage: string;\n\tcode?: string;\n}\n\nfunction extractNetworkInfo(err: unknown): NetworkInfo | null {\n\tif (err === null || typeof err !== \"object\") return null;\n\tconst code = (err as { code?: unknown }).code;\n\tconst message = (err as { message?: unknown }).message;\n\tif (\n\t\ttypeof code === \"string\" &&\n\t\t/^(ECONNREFUSED|ECONNRESET|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|EPIPE|EHOSTUNREACH|ENETUNREACH)$/.test(\n\t\t\tcode,\n\t\t)\n\t) {\n\t\treturn { message: typeof message === \"string\" ? message : code, code };\n\t}\n\t// axios timeout reports `code: \"ECONNABORTED\"`.\n\tif (code === \"ECONNABORTED\") {\n\t\treturn {\n\t\t\tmessage: typeof message === \"string\" ? message : \"timeout\",\n\t\t\tcode,\n\t\t};\n\t}\n\treturn null;\n}\n\nfunction httpDetails(\n\tcontext: NeonErrorContext,\n\tinfo: HttpInfo,\n): Record<string, unknown> {\n\tconst out: Record<string, unknown> = {\n\t\top: context.op,\n\t\tstatus: info.status,\n\t};\n\tif (context.projectId) out.projectId = context.projectId;\n\tif (info.neonMessage) out.neonMessage = info.neonMessage;\n\tif (info.neonCode) out.neonCode = info.neonCode;\n\tif (info.requestId) out.requestId = info.requestId;\n\treturn out;\n}\n"],"mappings":"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"}
|
package/dist/v1.d.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { AppliedChange, BranchConfig, BranchTarget, BucketAccessLevel, BucketConfig, ComputeSettings, Config, ConflictReport, FunctionConfig, FunctionMemoryMib, FunctionRuntime, PostgresConfig, PreviewConfig, PushResult, ResolvedBranchConfig, ResolvedBucketConfig, ResolvedFunctionConfig, ResolvedPreviewConfig, ServiceToggle } from "./lib/types.js";
|
|
2
|
+
import { ConfigLoadError, ConfigValidationError, ErrorCode, MissingContextError, PlatformError, PushAbortedError, PushConflictError } from "./lib/errors.js";
|
|
3
|
+
import { CreateBranchInput, CreateBucketInput, CreateProjectInput, DeployFunctionInput, GetConnectionUriInput, NeonApi, NeonAuthSnapshot, NeonBranchSnapshot, NeonBucketSnapshot, NeonDataApiSnapshot, NeonDatabaseSnapshot, NeonEndpointSnapshot, NeonFunctionDeploymentSnapshot, NeonFunctionSnapshot, NeonProjectSnapshot, NeonRoleSnapshot, UpdateBranchInput } from "./lib/neon-api.js";
|
|
4
|
+
import { createNeonApiFromOptions, resolveApiKey } from "./lib/auth.js";
|
|
5
|
+
import { defineConfig, resolveConfig } from "./lib/define-config.js";
|
|
6
|
+
import { DiffOptions, DiffResult, PlanStep, RemotePreviewState, RemoteServiceState, RemoteState, diffConfig } from "./lib/diff.js";
|
|
7
|
+
import { LoadConfigOptions, loadConfigFromFile } from "./lib/loader.js";
|
|
8
|
+
import { createRealNeonApi } from "./lib/neon-api-real.js";
|
|
9
|
+
import * as zod0 from "zod";
|
|
10
|
+
import * as zod_v4_core0 from "zod/v4/core";
|
|
11
|
+
|
|
12
|
+
//#region src/v1.d.ts
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Specific `PlatformError` subclasses, grouped for `instanceof` / structured access.
|
|
16
|
+
* Also available as top-level exports.
|
|
17
|
+
*/
|
|
18
|
+
declare const errors: {
|
|
19
|
+
readonly ConfigLoadError: typeof ConfigLoadError;
|
|
20
|
+
readonly ConfigValidationError: typeof ConfigValidationError;
|
|
21
|
+
readonly ErrorCode: {
|
|
22
|
+
readonly InvalidConfig: "PLATFORM_INVALID_CONFIG";
|
|
23
|
+
readonly EnvNotInjected: "PLATFORM_ENV_NOT_INJECTED";
|
|
24
|
+
readonly MissingContext: "PLATFORM_MISSING_CONTEXT";
|
|
25
|
+
readonly PushConflict: "PLATFORM_PUSH_CONFLICT";
|
|
26
|
+
readonly PushAborted: "PLATFORM_PUSH_ABORTED";
|
|
27
|
+
readonly ConfigLoadFailed: "PLATFORM_CONFIG_LOAD_FAILED";
|
|
28
|
+
readonly MissingApiKey: "PLATFORM_MISSING_API_KEY";
|
|
29
|
+
readonly AmbiguousBranchAuth: "PLATFORM_AMBIGUOUS_BRANCH_AUTH";
|
|
30
|
+
readonly BranchNotFound: "PLATFORM_BRANCH_NOT_FOUND";
|
|
31
|
+
readonly MissingParentBranch: "PLATFORM_MISSING_PARENT_BRANCH";
|
|
32
|
+
readonly Unauthorized: "PLATFORM_UNAUTHORIZED";
|
|
33
|
+
readonly Forbidden: "PLATFORM_FORBIDDEN";
|
|
34
|
+
readonly NotFound: "PLATFORM_NOT_FOUND";
|
|
35
|
+
readonly Conflict: "PLATFORM_CONFLICT";
|
|
36
|
+
readonly RateLimited: "PLATFORM_RATE_LIMITED";
|
|
37
|
+
readonly Locked: "PLATFORM_LOCKED";
|
|
38
|
+
readonly ServerError: "PLATFORM_SERVER_ERROR";
|
|
39
|
+
readonly NetworkError: "PLATFORM_NETWORK_ERROR";
|
|
40
|
+
readonly InternalError: "PLATFORM_INTERNAL_ERROR";
|
|
41
|
+
};
|
|
42
|
+
readonly MissingContextError: typeof MissingContextError;
|
|
43
|
+
readonly PlatformError: typeof PlatformError;
|
|
44
|
+
readonly PushAbortedError: typeof PushAbortedError;
|
|
45
|
+
readonly PushConflictError: typeof PushConflictError;
|
|
46
|
+
};
|
|
47
|
+
/** The zod schemas underlying `defineConfig`, grouped under product-friendly names. */
|
|
48
|
+
declare const schemas: {
|
|
49
|
+
readonly branch: zod0.ZodObject<{
|
|
50
|
+
parent: zod0.ZodOptional<zod0.ZodString>;
|
|
51
|
+
protected: zod0.ZodOptional<zod0.ZodBoolean>;
|
|
52
|
+
ttl: zod0.ZodOptional<zod0.ZodUnion<readonly [zod0.ZodString, zod0.ZodNumber]>>;
|
|
53
|
+
postgres: zod0.ZodOptional<zod0.ZodObject<{
|
|
54
|
+
computeSettings: zod0.ZodOptional<zod0.ZodObject<{
|
|
55
|
+
autoscalingLimitMinCu: zod0.ZodOptional<zod0.ZodUnion<readonly [zod0.ZodLiteral<0.25>, zod0.ZodLiteral<0.5>, zod0.ZodLiteral<1>, zod0.ZodLiteral<2>, zod0.ZodLiteral<4>, zod0.ZodLiteral<8>]>>;
|
|
56
|
+
autoscalingLimitMaxCu: zod0.ZodOptional<zod0.ZodUnion<readonly [zod0.ZodLiteral<0.25>, zod0.ZodLiteral<0.5>, zod0.ZodLiteral<1>, zod0.ZodLiteral<2>, zod0.ZodLiteral<4>, zod0.ZodLiteral<8>]>>;
|
|
57
|
+
suspendTimeout: zod0.ZodOptional<zod0.ZodUnion<readonly [zod0.ZodLiteral<false>, zod0.ZodString, zod0.ZodNumber]>>;
|
|
58
|
+
}, zod_v4_core0.$strict>>;
|
|
59
|
+
}, zod_v4_core0.$strict>>;
|
|
60
|
+
auth: zod0.ZodOptional<zod0.ZodObject<{
|
|
61
|
+
enabled: zod0.ZodOptional<zod0.ZodBoolean>;
|
|
62
|
+
}, zod_v4_core0.$strict>>;
|
|
63
|
+
dataApi: zod0.ZodOptional<zod0.ZodObject<{
|
|
64
|
+
enabled: zod0.ZodOptional<zod0.ZodBoolean>;
|
|
65
|
+
}, zod_v4_core0.$strict>>;
|
|
66
|
+
preview: zod0.ZodOptional<zod0.ZodObject<{
|
|
67
|
+
functions: zod0.ZodOptional<zod0.ZodArray<zod0.ZodObject<{
|
|
68
|
+
slug: zod0.ZodString;
|
|
69
|
+
name: zod0.ZodString;
|
|
70
|
+
source: zod0.ZodString;
|
|
71
|
+
env: zod0.ZodOptional<zod0.ZodRecord<zod0.ZodString, zod0.ZodString>>;
|
|
72
|
+
runtime: zod0.ZodOptional<zod0.ZodLiteral<"nodejs24">>;
|
|
73
|
+
memoryMib: zod0.ZodOptional<zod0.ZodUnion<readonly [zod0.ZodLiteral<256>, zod0.ZodLiteral<512>, zod0.ZodLiteral<1024>, zod0.ZodLiteral<2048>, zod0.ZodLiteral<4096>, zod0.ZodLiteral<8192>]>>;
|
|
74
|
+
}, zod_v4_core0.$strict>>>;
|
|
75
|
+
buckets: zod0.ZodOptional<zod0.ZodArray<zod0.ZodObject<{
|
|
76
|
+
name: zod0.ZodString;
|
|
77
|
+
access: zod0.ZodOptional<zod0.ZodUnion<readonly [zod0.ZodLiteral<"private">, zod0.ZodLiteral<"public_read">]>>;
|
|
78
|
+
}, zod_v4_core0.$strict>>>;
|
|
79
|
+
aiGateway: zod0.ZodOptional<zod0.ZodObject<{
|
|
80
|
+
enabled: zod0.ZodOptional<zod0.ZodBoolean>;
|
|
81
|
+
}, zod_v4_core0.$strict>>;
|
|
82
|
+
}, zod_v4_core0.$strict>>;
|
|
83
|
+
}, zod_v4_core0.$strict>;
|
|
84
|
+
readonly bucket: zod0.ZodObject<{
|
|
85
|
+
name: zod0.ZodString;
|
|
86
|
+
access: zod0.ZodOptional<zod0.ZodUnion<readonly [zod0.ZodLiteral<"private">, zod0.ZodLiteral<"public_read">]>>;
|
|
87
|
+
}, zod_v4_core0.$strict>;
|
|
88
|
+
readonly computeSettings: zod0.ZodObject<{
|
|
89
|
+
autoscalingLimitMinCu: zod0.ZodOptional<zod0.ZodUnion<readonly [zod0.ZodLiteral<0.25>, zod0.ZodLiteral<0.5>, zod0.ZodLiteral<1>, zod0.ZodLiteral<2>, zod0.ZodLiteral<4>, zod0.ZodLiteral<8>]>>;
|
|
90
|
+
autoscalingLimitMaxCu: zod0.ZodOptional<zod0.ZodUnion<readonly [zod0.ZodLiteral<0.25>, zod0.ZodLiteral<0.5>, zod0.ZodLiteral<1>, zod0.ZodLiteral<2>, zod0.ZodLiteral<4>, zod0.ZodLiteral<8>]>>;
|
|
91
|
+
suspendTimeout: zod0.ZodOptional<zod0.ZodUnion<readonly [zod0.ZodLiteral<false>, zod0.ZodString, zod0.ZodNumber]>>;
|
|
92
|
+
}, zod_v4_core0.$strict>;
|
|
93
|
+
readonly config: zod0.ZodFunction<zod0.ZodTuple<readonly [zod0.ZodUnknown], null>, zod0.ZodUnknown>;
|
|
94
|
+
readonly function: zod0.ZodObject<{
|
|
95
|
+
slug: zod0.ZodString;
|
|
96
|
+
name: zod0.ZodString;
|
|
97
|
+
source: zod0.ZodString;
|
|
98
|
+
env: zod0.ZodOptional<zod0.ZodRecord<zod0.ZodString, zod0.ZodString>>;
|
|
99
|
+
runtime: zod0.ZodOptional<zod0.ZodLiteral<"nodejs24">>;
|
|
100
|
+
memoryMib: zod0.ZodOptional<zod0.ZodUnion<readonly [zod0.ZodLiteral<256>, zod0.ZodLiteral<512>, zod0.ZodLiteral<1024>, zod0.ZodLiteral<2048>, zod0.ZodLiteral<4096>, zod0.ZodLiteral<8192>]>>;
|
|
101
|
+
}, zod_v4_core0.$strict>;
|
|
102
|
+
readonly postgres: zod0.ZodObject<{
|
|
103
|
+
computeSettings: zod0.ZodOptional<zod0.ZodObject<{
|
|
104
|
+
autoscalingLimitMinCu: zod0.ZodOptional<zod0.ZodUnion<readonly [zod0.ZodLiteral<0.25>, zod0.ZodLiteral<0.5>, zod0.ZodLiteral<1>, zod0.ZodLiteral<2>, zod0.ZodLiteral<4>, zod0.ZodLiteral<8>]>>;
|
|
105
|
+
autoscalingLimitMaxCu: zod0.ZodOptional<zod0.ZodUnion<readonly [zod0.ZodLiteral<0.25>, zod0.ZodLiteral<0.5>, zod0.ZodLiteral<1>, zod0.ZodLiteral<2>, zod0.ZodLiteral<4>, zod0.ZodLiteral<8>]>>;
|
|
106
|
+
suspendTimeout: zod0.ZodOptional<zod0.ZodUnion<readonly [zod0.ZodLiteral<false>, zod0.ZodString, zod0.ZodNumber]>>;
|
|
107
|
+
}, zod_v4_core0.$strict>>;
|
|
108
|
+
}, zod_v4_core0.$strict>;
|
|
109
|
+
readonly preview: zod0.ZodObject<{
|
|
110
|
+
functions: zod0.ZodOptional<zod0.ZodArray<zod0.ZodObject<{
|
|
111
|
+
slug: zod0.ZodString;
|
|
112
|
+
name: zod0.ZodString;
|
|
113
|
+
source: zod0.ZodString;
|
|
114
|
+
env: zod0.ZodOptional<zod0.ZodRecord<zod0.ZodString, zod0.ZodString>>;
|
|
115
|
+
runtime: zod0.ZodOptional<zod0.ZodLiteral<"nodejs24">>;
|
|
116
|
+
memoryMib: zod0.ZodOptional<zod0.ZodUnion<readonly [zod0.ZodLiteral<256>, zod0.ZodLiteral<512>, zod0.ZodLiteral<1024>, zod0.ZodLiteral<2048>, zod0.ZodLiteral<4096>, zod0.ZodLiteral<8192>]>>;
|
|
117
|
+
}, zod_v4_core0.$strict>>>;
|
|
118
|
+
buckets: zod0.ZodOptional<zod0.ZodArray<zod0.ZodObject<{
|
|
119
|
+
name: zod0.ZodString;
|
|
120
|
+
access: zod0.ZodOptional<zod0.ZodUnion<readonly [zod0.ZodLiteral<"private">, zod0.ZodLiteral<"public_read">]>>;
|
|
121
|
+
}, zod_v4_core0.$strict>>>;
|
|
122
|
+
aiGateway: zod0.ZodOptional<zod0.ZodObject<{
|
|
123
|
+
enabled: zod0.ZodOptional<zod0.ZodBoolean>;
|
|
124
|
+
}, zod_v4_core0.$strict>>;
|
|
125
|
+
}, zod_v4_core0.$strict>;
|
|
126
|
+
readonly service: zod0.ZodObject<{
|
|
127
|
+
enabled: zod0.ZodOptional<zod0.ZodBoolean>;
|
|
128
|
+
}, zod_v4_core0.$strict>;
|
|
129
|
+
};
|
|
130
|
+
//#endregion
|
|
131
|
+
export { type AppliedChange, type BranchConfig, type BranchTarget, type BucketAccessLevel, type BucketConfig, type ComputeSettings, type Config, ConfigLoadError, ConfigValidationError, type ConflictReport, type CreateBranchInput, type CreateBucketInput, type CreateProjectInput, type DeployFunctionInput, type DiffOptions, type DiffResult, ErrorCode, type FunctionConfig, type FunctionMemoryMib, type FunctionRuntime, type GetConnectionUriInput, type LoadConfigOptions, MissingContextError, type NeonApi, type NeonAuthSnapshot, type NeonBranchSnapshot, type NeonBucketSnapshot, type NeonDataApiSnapshot, type NeonDatabaseSnapshot, type NeonEndpointSnapshot, type NeonFunctionDeploymentSnapshot, type NeonFunctionSnapshot, type NeonProjectSnapshot, type NeonRoleSnapshot, type PlanStep, PlatformError, type PostgresConfig, type PreviewConfig, PushAbortedError, PushConflictError, type PushResult, type RemotePreviewState, type RemoteServiceState, type RemoteState, type ResolvedBranchConfig, type ResolvedBucketConfig, type ResolvedFunctionConfig, type ResolvedPreviewConfig, type ServiceToggle, type UpdateBranchInput, createNeonApiFromOptions, createRealNeonApi, defineConfig, diffConfig, errors, loadConfigFromFile, resolveApiKey, resolveConfig, schemas };
|
|
132
|
+
//# sourceMappingURL=v1.d.ts.map
|
package/dist/v1.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v1.d.ts","names":[],"sources":["../src/v1.ts"],"mappings":";;;;;;;;;;;;;;;;;cA0Da;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAWA;;6BASH,IAAA,CAAA,SAAA"}
|
package/dist/v1.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { ConfigLoadError, ConfigValidationError, ErrorCode, MissingContextError, PlatformError, PushAbortedError, PushConflictError } from "./lib/errors.js";
|
|
2
|
+
import { branchConfigSchema, bucketConfigSchema, computeSettingsSchema, configSchema, functionConfigSchema, postgresConfigSchema, previewConfigSchema, serviceToggleSchema } from "./lib/schema.js";
|
|
3
|
+
import { createRealNeonApi } from "./lib/neon-api-real.js";
|
|
4
|
+
import { createNeonApiFromOptions, resolveApiKey } from "./lib/auth.js";
|
|
5
|
+
import { defineConfig, resolveConfig } from "./lib/define-config.js";
|
|
6
|
+
import { diffConfig } from "./lib/diff.js";
|
|
7
|
+
import { loadConfigFromFile } from "./lib/loader.js";
|
|
8
|
+
//#region src/v1.ts
|
|
9
|
+
/**
|
|
10
|
+
* `@neondatabase/config/v1` — the v1 public API for Config-as-Code on the Neon Platform.
|
|
11
|
+
*
|
|
12
|
+
* Usage in `neon.ts`:
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { defineConfig } from "@neondatabase/config/v1";
|
|
15
|
+
*
|
|
16
|
+
* export default defineConfig((branch) => {
|
|
17
|
+
* if (branch.name === "main") return { protected: true, auth: {} };
|
|
18
|
+
* return { parent: "main", ttl: "7d" };
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* This is the **authoring** surface — `defineConfig`, types, schemas, the pure diff engine,
|
|
23
|
+
* and the Neon API adapter. It is intentionally free of heavy/native dependencies so that
|
|
24
|
+
* importing it from `neon.ts` stays cheap and bundler-safe.
|
|
25
|
+
*
|
|
26
|
+
* The imperative operations (`inspect` / `plan` / `apply`, `pushConfig` / `pullConfig`) and
|
|
27
|
+
* function bundling/deploy live in **`@neondatabase/config-runtime`**, which depends on this
|
|
28
|
+
* package and pulls in `esbuild`. Import that from your CLI / CI, not from `neon.ts`:
|
|
29
|
+
* ```ts
|
|
30
|
+
* import config from "../neon";
|
|
31
|
+
* import { inspect, plan, apply } from "@neondatabase/config-runtime/v1";
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* Surface guidelines:
|
|
35
|
+
* - Top-level: `defineConfig` / `resolveConfig`, the pure `diffConfig` engine, the
|
|
36
|
+
* `createRealNeonApi` adapter + `NeonApi` types, the config loader, the `PlatformError`
|
|
37
|
+
* base class + `ErrorCode` enum, and the config types used in `neon.ts`.
|
|
38
|
+
* - `errors` namespace: specific `PlatformError` subclasses (`ConfigLoadError`,
|
|
39
|
+
* `PushConflictError`, …).
|
|
40
|
+
* - `schemas` namespace: the zod schemas underlying `defineConfig`.
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* Specific `PlatformError` subclasses, grouped for `instanceof` / structured access.
|
|
44
|
+
* Also available as top-level exports.
|
|
45
|
+
*/
|
|
46
|
+
const errors = {
|
|
47
|
+
ConfigLoadError,
|
|
48
|
+
ConfigValidationError,
|
|
49
|
+
ErrorCode,
|
|
50
|
+
MissingContextError,
|
|
51
|
+
PlatformError,
|
|
52
|
+
PushAbortedError,
|
|
53
|
+
PushConflictError
|
|
54
|
+
};
|
|
55
|
+
/** The zod schemas underlying `defineConfig`, grouped under product-friendly names. */
|
|
56
|
+
const schemas = {
|
|
57
|
+
branch: branchConfigSchema,
|
|
58
|
+
bucket: bucketConfigSchema,
|
|
59
|
+
computeSettings: computeSettingsSchema,
|
|
60
|
+
config: configSchema,
|
|
61
|
+
function: functionConfigSchema,
|
|
62
|
+
postgres: postgresConfigSchema,
|
|
63
|
+
preview: previewConfigSchema,
|
|
64
|
+
service: serviceToggleSchema
|
|
65
|
+
};
|
|
66
|
+
//#endregion
|
|
67
|
+
export { ConfigLoadError, ConfigValidationError, ErrorCode, MissingContextError, PlatformError, PushAbortedError, PushConflictError, createNeonApiFromOptions, createRealNeonApi, defineConfig, diffConfig, errors, loadConfigFromFile, resolveApiKey, resolveConfig, schemas };
|
|
68
|
+
|
|
69
|
+
//# sourceMappingURL=v1.js.map
|
package/dist/v1.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v1.js","names":[],"sources":["../src/v1.ts"],"sourcesContent":["/**\n * `@neondatabase/config/v1` — the v1 public API for Config-as-Code on the Neon Platform.\n *\n * Usage in `neon.ts`:\n * ```ts\n * import { defineConfig } from \"@neondatabase/config/v1\";\n *\n * export default defineConfig((branch) => {\n * if (branch.name === \"main\") return { protected: true, auth: {} };\n * return { parent: \"main\", ttl: \"7d\" };\n * });\n * ```\n *\n * This is the **authoring** surface — `defineConfig`, types, schemas, the pure diff engine,\n * and the Neon API adapter. It is intentionally free of heavy/native dependencies so that\n * importing it from `neon.ts` stays cheap and bundler-safe.\n *\n * The imperative operations (`inspect` / `plan` / `apply`, `pushConfig` / `pullConfig`) and\n * function bundling/deploy live in **`@neondatabase/config-runtime`**, which depends on this\n * package and pulls in `esbuild`. Import that from your CLI / CI, not from `neon.ts`:\n * ```ts\n * import config from \"../neon\";\n * import { inspect, plan, apply } from \"@neondatabase/config-runtime/v1\";\n * ```\n *\n * Surface guidelines:\n * - Top-level: `defineConfig` / `resolveConfig`, the pure `diffConfig` engine, the\n * `createRealNeonApi` adapter + `NeonApi` types, the config loader, the `PlatformError`\n * base class + `ErrorCode` enum, and the config types used in `neon.ts`.\n * - `errors` namespace: specific `PlatformError` subclasses (`ConfigLoadError`,\n * `PushConflictError`, …).\n * - `schemas` namespace: the zod schemas underlying `defineConfig`.\n */\n\nimport {\n\tConfigLoadError,\n\tConfigValidationError,\n\tErrorCode,\n\tMissingContextError,\n\tPlatformError,\n\tPushAbortedError,\n\tPushConflictError,\n} from \"./lib/errors.js\";\nimport {\n\tbranchConfigSchema,\n\tbucketConfigSchema,\n\tcomputeSettingsSchema,\n\tconfigSchema,\n\tfunctionConfigSchema,\n\tpostgresConfigSchema,\n\tpreviewConfigSchema,\n\tserviceToggleSchema,\n} from \"./lib/schema.js\";\n\n/**\n * Specific `PlatformError` subclasses, grouped for `instanceof` / structured access.\n * Also available as top-level exports.\n */\nexport const errors = {\n\tConfigLoadError,\n\tConfigValidationError,\n\tErrorCode,\n\tMissingContextError,\n\tPlatformError,\n\tPushAbortedError,\n\tPushConflictError,\n} as const;\n\n/** The zod schemas underlying `defineConfig`, grouped under product-friendly names. */\nexport const schemas = {\n\tbranch: branchConfigSchema,\n\tbucket: bucketConfigSchema,\n\tcomputeSettings: computeSettingsSchema,\n\tconfig: configSchema,\n\tfunction: functionConfigSchema,\n\tpostgres: postgresConfigSchema,\n\tpreview: previewConfigSchema,\n\tservice: serviceToggleSchema,\n} as const;\n\n// ─── Lower-level adapters ──────────────────────────────────────────────────────\nexport { createNeonApiFromOptions, resolveApiKey } from \"./lib/auth.js\";\nexport { defineConfig, resolveConfig } from \"./lib/define-config.js\";\n// ─── Diff engine (pure; consumed by @neondatabase/config-runtime) ─────────────\nexport type {\n\tDiffOptions,\n\tDiffResult,\n\tPlanStep,\n\tRemotePreviewState,\n\tRemoteServiceState,\n\tRemoteState,\n} from \"./lib/diff.js\";\nexport { diffConfig } from \"./lib/diff.js\";\n// ─── Errors ────────────────────────────────────────────────────────────────────\nexport {\n\tConfigLoadError,\n\tConfigValidationError,\n\tErrorCode,\n\tMissingContextError,\n\tPlatformError,\n\tPushAbortedError,\n\tPushConflictError,\n} from \"./lib/errors.js\";\nexport type { LoadConfigOptions } from \"./lib/loader.js\";\nexport { loadConfigFromFile } from \"./lib/loader.js\";\n// ─── NeonApi types (needed by callers implementing their own adapters) ────────\nexport type {\n\tCreateBranchInput,\n\tCreateBucketInput,\n\tCreateProjectInput,\n\tDeployFunctionInput,\n\tGetConnectionUriInput,\n\tNeonApi,\n\tNeonAuthSnapshot,\n\tNeonBranchSnapshot,\n\tNeonBucketSnapshot,\n\tNeonDataApiSnapshot,\n\tNeonDatabaseSnapshot,\n\tNeonEndpointSnapshot,\n\tNeonFunctionDeploymentSnapshot,\n\tNeonFunctionSnapshot,\n\tNeonProjectSnapshot,\n\tNeonRoleSnapshot,\n\tUpdateBranchInput,\n} from \"./lib/neon-api.js\";\nexport { createRealNeonApi } from \"./lib/neon-api-real.js\";\n// ─── Config types (used in neon.ts and in operation return values) ────────────\nexport type {\n\tAppliedChange,\n\tBranchConfig,\n\tBranchTarget,\n\tBucketAccessLevel,\n\tBucketConfig,\n\tComputeSettings,\n\tConfig,\n\tConflictReport,\n\tFunctionConfig,\n\tFunctionMemoryMib,\n\tFunctionRuntime,\n\tPostgresConfig,\n\tPreviewConfig,\n\tPushResult,\n\tResolvedBranchConfig,\n\tResolvedBucketConfig,\n\tResolvedFunctionConfig,\n\tResolvedPreviewConfig,\n\tServiceToggle,\n} from \"./lib/types.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0DA,MAAa,SAAS;CACrB;CACA;CACA;CACA;CACA;CACA;CACA;AACD;;AAGA,MAAa,UAAU;CACtB,QAAQ;CACR,QAAQ;CACR,iBAAiB;CACjB,QAAQ;CACR,UAAU;CACV,UAAU;CACV,SAAS;CACT,SAAS;AACV"}
|
package/package.json
CHANGED
|
@@ -1,18 +1,68 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
2
|
+
"name": "@neondatabase/config",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Config-as-Code for the Neon Platform. Define a `neon.ts` policy and inspect/diff/deploy it against the Neon API as plain TypeScript functions.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"neon",
|
|
7
|
+
"database",
|
|
8
|
+
"postgres",
|
|
9
|
+
"iac",
|
|
10
|
+
"config-as-code",
|
|
11
|
+
"platform"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/neondatabase/neon-pkgs.git"
|
|
16
|
+
},
|
|
17
|
+
"license": "Apache-2.0",
|
|
18
|
+
"author": {
|
|
19
|
+
"name": "Neon",
|
|
20
|
+
"url": "https://neon.com"
|
|
21
|
+
},
|
|
22
|
+
"type": "module",
|
|
23
|
+
"main": "dist/index.js",
|
|
24
|
+
"types": "dist/index.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js",
|
|
29
|
+
"default": "./dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./v1": {
|
|
32
|
+
"types": "./dist/v1.d.ts",
|
|
33
|
+
"import": "./dist/v1.js",
|
|
34
|
+
"default": "./dist/v1.js"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"README.md",
|
|
39
|
+
"dist/",
|
|
40
|
+
"package.json"
|
|
41
|
+
],
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^20.19.0",
|
|
44
|
+
"@vitest/coverage-v8": "3.0.9",
|
|
45
|
+
"console-fail-test": "0.5.0",
|
|
46
|
+
"tsdown": "^0.14.1",
|
|
47
|
+
"typescript": "^5.8.2",
|
|
48
|
+
"vitest": "^3.0.9"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@neondatabase/api-client": "^2.7.1",
|
|
52
|
+
"jiti": "^2.7.0",
|
|
53
|
+
"zod": "^4.4.3"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=22"
|
|
57
|
+
},
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"provenance": false
|
|
60
|
+
},
|
|
61
|
+
"scripts": {
|
|
62
|
+
"build": "tsc --noEmit && tsdown",
|
|
63
|
+
"test": "vitest --passWithNoTests",
|
|
64
|
+
"test:ci": "vitest run --passWithNoTests",
|
|
65
|
+
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
66
|
+
"tsc": "tsc"
|
|
67
|
+
}
|
|
68
|
+
}
|
package/.env.example
DELETED
package/e2e/errors.e2e.test.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { describe, expect } from "vitest";
|
|
2
|
-
import { createRealNeonApi, ErrorCode, type PlatformError } from "../src/v1.js";
|
|
3
|
-
import { detectApiKeyScope, e2eTest } from "./helpers.js";
|
|
4
|
-
|
|
5
|
-
describe("e2e — error wrapping against real Neon API", () => {
|
|
6
|
-
e2eTest(
|
|
7
|
-
"bad API key yields PLATFORM_UNAUTHORIZED with key-rotation guidance",
|
|
8
|
-
async () => {
|
|
9
|
-
const api = createRealNeonApi({
|
|
10
|
-
apiKey: "napi_definitely_not_a_real_key_xxxxxxxxxxxxxxxxxx",
|
|
11
|
-
});
|
|
12
|
-
await expect(api.listProjects({})).rejects.toMatchObject({
|
|
13
|
-
code: ErrorCode.Unauthorized,
|
|
14
|
-
});
|
|
15
|
-
try {
|
|
16
|
-
await api.listProjects({});
|
|
17
|
-
} catch (err) {
|
|
18
|
-
const p = err as PlatformError;
|
|
19
|
-
expect(p.message).toContain(
|
|
20
|
-
"Bearer token sent to the Neon API was rejected",
|
|
21
|
-
);
|
|
22
|
-
expect(p.message).toContain(
|
|
23
|
-
"https://console.neon.tech/app/settings/api-keys",
|
|
24
|
-
);
|
|
25
|
-
expect(p.message).toContain("neonctl auth");
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
e2eTest(
|
|
31
|
-
"getProject on a non-existent id yields PLATFORM_NOT_FOUND with the offending id in the message",
|
|
32
|
-
async () => {
|
|
33
|
-
const scope = await detectApiKeyScope();
|
|
34
|
-
if (scope.kind !== "org-or-user") return;
|
|
35
|
-
const api = createRealNeonApi({
|
|
36
|
-
apiKey: process.env.NEON_API_KEY ?? "",
|
|
37
|
-
});
|
|
38
|
-
await expect(
|
|
39
|
-
api.getProject("proj-definitely-not-real-12345"),
|
|
40
|
-
).rejects.toMatchObject({
|
|
41
|
-
code: ErrorCode.NotFound,
|
|
42
|
-
});
|
|
43
|
-
try {
|
|
44
|
-
await api.getProject("proj-definitely-not-real-12345");
|
|
45
|
-
} catch (err) {
|
|
46
|
-
const p = err as PlatformError;
|
|
47
|
-
expect(p.message).toContain("proj-definitely-not-real-12345");
|
|
48
|
-
expect(p.details.status).toBe(404);
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
);
|
|
52
|
-
});
|
package/e2e/helpers.ts
DELETED
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { createApiClient } from "@neondatabase/api-client";
|
|
3
|
-
import { test } from "vitest";
|
|
4
|
-
import type { NeonApi } from "../src/lib/neon-api.js";
|
|
5
|
-
import { createRealNeonApi } from "../src/lib/neon-api-real.js";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Every e2e-created project is named `neon-ts-e2e-<uuid>`. Tests can register
|
|
9
|
-
* `track(id)` to opt into the per-test cleanup hook. The suite-level
|
|
10
|
-
* {@link sweepOrphans} additionally deletes leftovers from a previous failed run.
|
|
11
|
-
*/
|
|
12
|
-
const PROJECT_PREFIX = "neon-ts-e2e-";
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Default Neon region used by every e2e test that creates a project. Override per-test
|
|
16
|
-
* by passing `region` to `defineConfig`.
|
|
17
|
-
*/
|
|
18
|
-
export const DEFAULT_REGION = "aws-us-east-2";
|
|
19
|
-
|
|
20
|
-
/** Generate a project name guaranteed not to collide with anything else in the org. */
|
|
21
|
-
export function uniqueProjectName(suffix?: string): string {
|
|
22
|
-
const id = randomUUID().slice(0, 8);
|
|
23
|
-
return suffix
|
|
24
|
-
? `${PROJECT_PREFIX}${id}-${suffix}`
|
|
25
|
-
: `${PROJECT_PREFIX}${id}`;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function requireApiKey(): string {
|
|
29
|
-
const key = process.env.NEON_API_KEY;
|
|
30
|
-
if (!key || key.trim() === "") {
|
|
31
|
-
throw new Error(
|
|
32
|
-
"NEON_API_KEY is not set. Create packages/config/.env (see .env.example) before running test:e2e.",
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
return key;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** The same real NeonApi adapter the SDK uses internally — exercised end-to-end. */
|
|
39
|
-
export function makeRealApi(): NeonApi {
|
|
40
|
-
return createRealNeonApi({ apiKey: requireApiKey() });
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Create a real Neon project via the NeonApi adapter directly. `pushConfig` no longer
|
|
45
|
-
* provisions projects (callers are expected to run `neonctl link` first), so every e2e
|
|
46
|
-
* test that needs a fresh project to push against goes through this helper instead.
|
|
47
|
-
*/
|
|
48
|
-
export async function bootstrapProject(
|
|
49
|
-
api: NeonApi,
|
|
50
|
-
args: { name: string; region: string },
|
|
51
|
-
): Promise<string> {
|
|
52
|
-
const created = await api.createProject({
|
|
53
|
-
name: args.name,
|
|
54
|
-
regionId: args.region,
|
|
55
|
-
});
|
|
56
|
-
return created.id;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** Lower-level Neon client. Used by cleanup and a few setup helpers. */
|
|
60
|
-
function makeRawClient(): ReturnType<typeof createApiClient> {
|
|
61
|
-
return createApiClient({ apiKey: requireApiKey() });
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Discriminates the key currently configured. Project-scoped keys can't list projects;
|
|
66
|
-
* org/user-scoped keys can.
|
|
67
|
-
*/
|
|
68
|
-
export type ApiKeyScope =
|
|
69
|
-
| { kind: "org-or-user"; canCreate: true }
|
|
70
|
-
| { kind: "project"; projectId: string; canCreate: false };
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Probe the configured API key to find out what it can do. Memoised because we only need
|
|
74
|
-
* to do this once per process.
|
|
75
|
-
*/
|
|
76
|
-
let cachedScope: ApiKeyScope | undefined;
|
|
77
|
-
export async function detectApiKeyScope(): Promise<ApiKeyScope> {
|
|
78
|
-
if (cachedScope) return cachedScope;
|
|
79
|
-
const client = makeRawClient();
|
|
80
|
-
try {
|
|
81
|
-
await client.listProjects({ limit: 1 });
|
|
82
|
-
cachedScope = { kind: "org-or-user", canCreate: true };
|
|
83
|
-
return cachedScope;
|
|
84
|
-
} catch (err) {
|
|
85
|
-
const status = (err as { response?: { status?: number } } | undefined)
|
|
86
|
-
?.response?.status;
|
|
87
|
-
if (status !== 401 && status !== 403) throw err;
|
|
88
|
-
}
|
|
89
|
-
const fixedProjectId = process.env.NEON_PROJECT_ID;
|
|
90
|
-
if (!fixedProjectId || fixedProjectId.trim() === "") {
|
|
91
|
-
throw new Error(
|
|
92
|
-
"API key cannot list projects (looks project-scoped) and NEON_PROJECT_ID is not set. " +
|
|
93
|
-
"Set NEON_PROJECT_ID in packages/config/.env to target a fixed project for the bounded e2e subset.",
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
cachedScope = {
|
|
97
|
-
kind: "project",
|
|
98
|
-
projectId: fixedProjectId,
|
|
99
|
-
canCreate: false,
|
|
100
|
-
};
|
|
101
|
-
return cachedScope;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Delete a project, ignoring "already gone" errors so cleanup is idempotent. Refuses to
|
|
106
|
-
* delete anything that isn't prefixed with {@link PROJECT_PREFIX} so a mis-typed id can
|
|
107
|
-
* never wipe an unrelated project.
|
|
108
|
-
*/
|
|
109
|
-
async function deleteProject(projectId: string): Promise<void> {
|
|
110
|
-
const client = makeRawClient();
|
|
111
|
-
const project = await client.getProject(projectId).catch((err) => {
|
|
112
|
-
const status = (err as { response?: { status?: number } } | undefined)
|
|
113
|
-
?.response?.status;
|
|
114
|
-
if (status === 404 || status === 410) return null;
|
|
115
|
-
throw err;
|
|
116
|
-
});
|
|
117
|
-
if (!project) return;
|
|
118
|
-
if (!project.data.project.name.startsWith(PROJECT_PREFIX)) {
|
|
119
|
-
throw new Error(
|
|
120
|
-
`Refusing to delete project ${projectId} ("${project.data.project.name}"): does not match the e2e prefix.`,
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
// Retry on 423 (locked while a previous mutation is in flight) so cleanup is robust.
|
|
124
|
-
const maxAttempts = 12;
|
|
125
|
-
let delay = 500;
|
|
126
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
127
|
-
try {
|
|
128
|
-
await client.deleteProject(projectId);
|
|
129
|
-
return;
|
|
130
|
-
} catch (err) {
|
|
131
|
-
const status = (
|
|
132
|
-
err as { response?: { status?: number } } | undefined
|
|
133
|
-
)?.response?.status;
|
|
134
|
-
if (status === 404 || status === 410) return;
|
|
135
|
-
if (status !== 423 || attempt === maxAttempts) throw err;
|
|
136
|
-
await sleep(delay);
|
|
137
|
-
delay = Math.min(delay * 2, 5_000);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* List every project whose name starts with {@link PROJECT_PREFIX} and delete them.
|
|
144
|
-
* Called once at suite start to mop up orphans from a previous failed run.
|
|
145
|
-
*/
|
|
146
|
-
export async function sweepOrphans(): Promise<{ swept: string[] }> {
|
|
147
|
-
const scope = await detectApiKeyScope();
|
|
148
|
-
if (scope.kind === "project") return { swept: [] };
|
|
149
|
-
const client = makeRawClient();
|
|
150
|
-
const swept: string[] = [];
|
|
151
|
-
let cursor: string | undefined;
|
|
152
|
-
while (true) {
|
|
153
|
-
const res = await client.listProjects({
|
|
154
|
-
limit: 100,
|
|
155
|
-
...(cursor ? { cursor } : {}),
|
|
156
|
-
});
|
|
157
|
-
for (const project of res.data.projects) {
|
|
158
|
-
if (project.name.startsWith(PROJECT_PREFIX)) {
|
|
159
|
-
await deleteProject(project.id);
|
|
160
|
-
swept.push(project.id);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
const next = (res.data as { pagination?: { next?: string } }).pagination
|
|
164
|
-
?.next;
|
|
165
|
-
if (!next || next === cursor) break;
|
|
166
|
-
cursor = next;
|
|
167
|
-
}
|
|
168
|
-
return { swept };
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* A vitest `test.extend` fixture that tracks every project id a test creates and deletes
|
|
173
|
-
* each one in the cleanup phase, even if the test failed mid-way. Use `track(id)` to
|
|
174
|
-
* register ids — cleanup runs regardless of outcome.
|
|
175
|
-
*/
|
|
176
|
-
export const e2eTest = test.extend<{
|
|
177
|
-
track: (projectId: string) => void;
|
|
178
|
-
}>({
|
|
179
|
-
// biome-ignore lint/correctness/noEmptyPattern: vitest's fixture API requires this exact shape.
|
|
180
|
-
track: async ({}, use) => {
|
|
181
|
-
const created: string[] = [];
|
|
182
|
-
await use((projectId: string) => {
|
|
183
|
-
created.push(projectId);
|
|
184
|
-
});
|
|
185
|
-
for (const projectId of created) {
|
|
186
|
-
try {
|
|
187
|
-
await deleteProject(projectId);
|
|
188
|
-
} catch (err) {
|
|
189
|
-
// Surface, but don't fail on cleanup errors — the orphan sweep on the next
|
|
190
|
-
// run will mop up anything we miss.
|
|
191
|
-
console.error(
|
|
192
|
-
`[e2e cleanup] failed to delete ${projectId}: ${(err as Error).message}`,
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
},
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Some Neon operations are eventually consistent (notably branch creation finishing
|
|
201
|
-
* `init` → `ready`). A small wait avoids racing on subsequent reads.
|
|
202
|
-
*/
|
|
203
|
-
function sleep(ms: number): Promise<void> {
|
|
204
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
205
|
-
}
|
package/e2e/load-env.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// Loaded by vitest.e2e.config.ts `setupFiles`; not imported statically.
|
|
2
|
-
// fallow-ignore-file unused-file
|
|
3
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
-
import { dirname, resolve } from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Vitest setup file — loads `packages/config/.env` into `process.env` so e2e tests can
|
|
9
|
-
* read `NEON_API_KEY` (and friends). Node 22 has `--env-file` but it's per-process; doing
|
|
10
|
-
* it here keeps the test command short (`pnpm test:e2e`).
|
|
11
|
-
*
|
|
12
|
-
* Lines starting with `#` are treated as comments; everything else is parsed as
|
|
13
|
-
* `KEY=value` (no quoting / interpolation — we keep this minimal on purpose).
|
|
14
|
-
*/
|
|
15
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
16
|
-
const envPath = resolve(here, "..", ".env");
|
|
17
|
-
|
|
18
|
-
if (existsSync(envPath)) {
|
|
19
|
-
const raw = readFileSync(envPath, "utf-8");
|
|
20
|
-
for (const rawLine of raw.split("\n")) {
|
|
21
|
-
const line = rawLine.trim();
|
|
22
|
-
if (line === "" || line.startsWith("#")) continue;
|
|
23
|
-
const eq = line.indexOf("=");
|
|
24
|
-
if (eq <= 0) continue;
|
|
25
|
-
const key = line.slice(0, eq).trim();
|
|
26
|
-
const value = line.slice(eq + 1).trim();
|
|
27
|
-
if (process.env[key] === undefined) process.env[key] = value;
|
|
28
|
-
}
|
|
29
|
-
}
|
package/e2e/setup.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
// Loaded by vitest.e2e.config.ts `setupFiles`; not imported statically.
|
|
2
|
-
// fallow-ignore-file unused-file
|
|
3
|
-
import { beforeAll } from "vitest";
|
|
4
|
-
import { detectApiKeyScope, sweepOrphans } from "./helpers.js";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Suite-level setup. Runs once before any e2e test:
|
|
8
|
-
* 1. Probes the configured API key to detect its scope. We do this here so a misconfigured
|
|
9
|
-
* environment fails fast with a clear message rather than surfacing as cryptic 403s
|
|
10
|
-
* inside individual tests.
|
|
11
|
-
* 2. When the key is org/user-scoped, sweep any orphaned `neon-ts-e2e-*` projects
|
|
12
|
-
* left over from a previous run.
|
|
13
|
-
*/
|
|
14
|
-
beforeAll(async () => {
|
|
15
|
-
const scope = await detectApiKeyScope();
|
|
16
|
-
if (scope.kind === "org-or-user") {
|
|
17
|
-
const { swept } = await sweepOrphans();
|
|
18
|
-
if (swept.length > 0) {
|
|
19
|
-
console.warn(
|
|
20
|
-
`[e2e setup] swept ${swept.length} orphaned project(s) from a previous run.`,
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
});
|