@sponson/adapters 0.0.0-stage → 0.4.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/dist/http.d.ts ADDED
@@ -0,0 +1,120 @@
1
+ import { SponsonError, type ErrorCode } from "@sponson/core";
2
+ /** Longest response excerpt an error carries. Error pages can be megabytes; receipts and PR comments must not. */
3
+ export declare const HTTP_ERROR_BODY_LIMIT = 500;
4
+ /** Per-request timeout unless SPONSON_HTTP_TIMEOUT_MS says otherwise. */
5
+ export declare const DEFAULT_TIMEOUT_MS = 30000;
6
+ /** Retries of a retryable failure unless SPONSON_HTTP_RETRIES says otherwise. */
7
+ export declare const DEFAULT_RETRIES = 4;
8
+ /** First backoff step (doubling, jittered) unless SPONSON_HTTP_RETRY_BASE_MS says otherwise. */
9
+ export declare const DEFAULT_RETRY_BASE_MS = 500;
10
+ /** No single wait is longer than this, whatever Retry-After says. */
11
+ export declare const MAX_RETRY_WAIT_MS = 30000;
12
+ /** The error codes the HTTP client classifies provider failures into; match on them with `isProviderError`. */
13
+ export type ProviderErrorCode = Extract<ErrorCode, `PROVIDER_${string}`>;
14
+ /** Details carried by every provider error. Never the request body. */
15
+ export interface ProviderErrorDetails {
16
+ adapter: string;
17
+ method: string;
18
+ path: string;
19
+ /** HTTP status, when a response arrived. */
20
+ status?: number;
21
+ [k: string]: unknown;
22
+ }
23
+ /**
24
+ * True when `e` is a provider failure, optionally of the given code(s). Adapters use it to branch on "already
25
+ * exists" or "not found" instead of reading HTTP statuses. Stable authoring API.
26
+ */
27
+ export declare function isProviderError(e: unknown, code?: ProviderErrorCode | ProviderErrorCode[]): e is SponsonError & {
28
+ details: ProviderErrorDetails;
29
+ };
30
+ /** Errors that say "not now" rather than "no": a later poll or run may succeed. */
31
+ export declare function isTransient(e: unknown): boolean;
32
+ /**
33
+ * Options for `apiClient`. Adapters get one through `clientFor`, which fills `redact` and `env` from the adapter
34
+ * context.
35
+ */
36
+ export interface ApiClientOptions {
37
+ /** Adapter name, for messages and error details. */
38
+ adapter: string;
39
+ baseUrl: string;
40
+ token: string;
41
+ /** `bearer` sends `Authorization: Bearer <token>`; `header:<name>` sends the token in a custom header. */
42
+ authHeader?: "bearer" | `header:${string}`;
43
+ /** Masks known secrets; applied to provider text before it is truncated into an error message. */
44
+ redact: (text: string) => string;
45
+ /** Source of SPONSON_HTTP_TIMEOUT_MS / SPONSON_HTTP_RETRIES / SPONSON_HTTP_RETRY_BASE_MS. */
46
+ env?: NodeJS.ProcessEnv;
47
+ }
48
+ /**
49
+ * Validates a parsed response body and returns it typed. Throw `ShapeError` (or return via the `shape` helpers)
50
+ * when it is not what the adapter consumes; the client turns that into PROVIDER_RESPONSE naming the endpoint.
51
+ * Stable authoring API.
52
+ */
53
+ export type Shape<T> = (body: unknown) => T;
54
+ /**
55
+ * Thrown by a `Shape` when a response body is not what the adapter expects; the client reports it as
56
+ * PROVIDER_RESPONSE. Stable authoring API.
57
+ */
58
+ export declare class ShapeError extends Error {
59
+ constructor(message: string);
60
+ }
61
+ /**
62
+ * A JSON client bound to one provider: retries, timeouts and error classification are built in. Get one from
63
+ * `clientFor`. Stable authoring API.
64
+ */
65
+ export interface ApiClient {
66
+ readonly adapter: string;
67
+ get<T = unknown>(path: string, shape?: Shape<T>): Promise<T>;
68
+ post<T = unknown>(path: string, body?: unknown, shape?: Shape<T>, opts?: WriteOptions): Promise<T>;
69
+ /** PUT replaces the target, so it is retried after a 502/503/504 or a dropped connection like GET. */
70
+ put<T = unknown>(path: string, body?: unknown, shape?: Shape<T>): Promise<T>;
71
+ patch<T = unknown>(path: string, body?: unknown, shape?: Shape<T>, opts?: WriteOptions): Promise<T>;
72
+ delete<T = unknown>(path: string, shape?: Shape<T>): Promise<T>;
73
+ }
74
+ /** Options for a POST or PATCH. Stable authoring API. */
75
+ export interface WriteOptions {
76
+ /**
77
+ * The request may safely be sent twice: it sets state rather than adding to it (a PATCH that replaces a whole
78
+ * list, a POST with an idempotency key). Then a 502/503/504 or a dropped connection is retried as for GET;
79
+ * otherwise only refusals (429, 423) are, since the first attempt may have taken effect.
80
+ */
81
+ idempotent?: boolean;
82
+ }
83
+ /** Redact first, then flatten and truncate: truncating first could cut a secret so the redactor no longer sees it. */
84
+ export declare function excerptOf(body: string, redact: (s: string) => string): string;
85
+ /** Retry-After as milliseconds: delta-seconds or an HTTP date. Undefined when absent or unparseable. */
86
+ export declare function retryAfterMs(header: string | null, now?: number): number | undefined;
87
+ /** Exponential backoff with jitter: attempt 1 waits base·[0.5, 1), attempt 2 base·[1, 2), … */
88
+ export declare function backoffMs(attempt: number, baseMs: number, random?: () => number): number;
89
+ /**
90
+ * The PROVIDER_* code for a non-2xx response (status and body). Exported for adapters that talk to a provider
91
+ * without `apiClient`.
92
+ */
93
+ export declare function classifyStatus(status: number, body: string): ProviderErrorCode;
94
+ /**
95
+ * Build an `ApiClient`. Adapters should call `clientFor(actx, ...)` instead, which wires redaction and the
96
+ * environment.
97
+ */
98
+ export declare function apiClient(opts: ApiClientOptions): ApiClient;
99
+ /** True for a plain JSON object (not null, not an array). For hand-written `Shape`s. Stable authoring API. */
100
+ export declare function isObject(v: unknown): v is Record<string, unknown>;
101
+ /** `v` must be an object; returns it. Stable authoring API. */
102
+ export declare function obj(v: unknown, what: string): Record<string, unknown>;
103
+ /** `v` must be an array of objects each having the string fields named; returns them typed. Stable authoring API. */
104
+ export declare function records<K extends string>(v: unknown, what: string, fields: readonly K[]): Array<Record<K, string> & Record<string, unknown>>;
105
+ /** One page of a list, as the `page` callback of `listAll` describes it. Stable authoring API. */
106
+ export interface Page<T> {
107
+ items: T[];
108
+ /** Query parameters for the next page, or null when this was the last one. */
109
+ next: Record<string, string> | null;
110
+ }
111
+ /** Hard stop against a provider that keeps returning a cursor. */
112
+ export declare const MAX_PAGES = 1000;
113
+ /**
114
+ * Fetch every page of a list. `page` validates one response body and says how to ask for the next page.
115
+ * Stops on a null cursor, an empty page, or a cursor seen before (a provider that repeats the last cursor).
116
+ * Stable authoring API.
117
+ */
118
+ export declare function listAll<T>(api: ApiClient, path: string, page: (body: unknown, pageNo: number) => Page<T>): Promise<T[]>;
119
+ /** `path` with `extra` appended as query parameters (after any it already has). Stable authoring API. */
120
+ export declare function withQuery(path: string, extra: Record<string, string>): string;
package/dist/http.js ADDED
@@ -0,0 +1,291 @@
1
+ /**
2
+ * The one HTTP layer every adapter uses: timeout, retry policy, failure classification, response-shape checks and
3
+ * pagination. Adapters never see a raw fetch error or a non-2xx status; they see a SponsonError with a PROVIDER_*
4
+ * code, so "already exists" (→ re-read and claim), "gone" (→ fine on delete) and "try later" are decided here once.
5
+ *
6
+ * Part of the stable adapter authoring API (see packages/adapters/src/index.ts): ApiClient, Page, Shape,
7
+ * ShapeError, isObject, obj, records, listAll, withQuery, isProviderError. The rest may change.
8
+ */
9
+ import { setTimeout as sleep } from "node:timers/promises";
10
+ import { SponsonError, isSponsonError } from "@sponson/core";
11
+ /** Longest response excerpt an error carries. Error pages can be megabytes; receipts and PR comments must not. */
12
+ export const HTTP_ERROR_BODY_LIMIT = 500;
13
+ /** Per-request timeout unless SPONSON_HTTP_TIMEOUT_MS says otherwise. */
14
+ export const DEFAULT_TIMEOUT_MS = 30_000;
15
+ /** Retries of a retryable failure unless SPONSON_HTTP_RETRIES says otherwise. */
16
+ export const DEFAULT_RETRIES = 4;
17
+ /** First backoff step (doubling, jittered) unless SPONSON_HTTP_RETRY_BASE_MS says otherwise. */
18
+ export const DEFAULT_RETRY_BASE_MS = 500;
19
+ /** No single wait is longer than this, whatever Retry-After says. */
20
+ export const MAX_RETRY_WAIT_MS = 30_000;
21
+ /**
22
+ * True when `e` is a provider failure, optionally of the given code(s). Adapters use it to branch on "already
23
+ * exists" or "not found" instead of reading HTTP statuses. Stable authoring API.
24
+ */
25
+ export function isProviderError(e, code) {
26
+ if (!isSponsonError(e) || !e.code.startsWith("PROVIDER_"))
27
+ return false;
28
+ if (code === undefined)
29
+ return true;
30
+ return Array.isArray(code) ? code.includes(e.code) : e.code === code;
31
+ }
32
+ /** Errors that say "not now" rather than "no": a later poll or run may succeed. */
33
+ export function isTransient(e) {
34
+ return isProviderError(e, ["PROVIDER_TRANSIENT", "PROVIDER_TIMEOUT"]);
35
+ }
36
+ /**
37
+ * Thrown by a `Shape` when a response body is not what the adapter expects; the client reports it as
38
+ * PROVIDER_RESPONSE. Stable authoring API.
39
+ */
40
+ export class ShapeError extends Error {
41
+ constructor(message) {
42
+ super(message);
43
+ this.name = "ShapeError";
44
+ }
45
+ }
46
+ /** Methods whose repetition is harmless, so 5xx and network errors are retried. */
47
+ const IDEMPOTENT = new Set(["GET", "HEAD", "DELETE", "PUT"]);
48
+ /** Retried for any method: the provider refused before doing anything (rate limit, Neon's "operation running"). */
49
+ const REFUSED_STATUSES = new Set([429, 423]);
50
+ /** Retried only for idempotent methods: the request may or may not have been executed. */
51
+ const UNAVAILABLE_STATUSES = new Set([502, 503, 504]);
52
+ function policyFrom(env = {}) {
53
+ const num = (name, fallback, min) => {
54
+ const raw = env[name];
55
+ if (raw === undefined || raw === "")
56
+ return fallback;
57
+ const n = Number(raw);
58
+ return Number.isFinite(n) && n >= min ? n : fallback;
59
+ };
60
+ return {
61
+ timeoutMs: num("SPONSON_HTTP_TIMEOUT_MS", DEFAULT_TIMEOUT_MS, 1),
62
+ retries: Math.floor(num("SPONSON_HTTP_RETRIES", DEFAULT_RETRIES, 0)),
63
+ baseMs: num("SPONSON_HTTP_RETRY_BASE_MS", DEFAULT_RETRY_BASE_MS, 0),
64
+ };
65
+ }
66
+ /** Redact first, then flatten and truncate: truncating first could cut a secret so the redactor no longer sees it. */
67
+ export function excerptOf(body, redact) {
68
+ const flat = redact(body).replace(/\s+/g, " ").trim();
69
+ return flat.length > HTTP_ERROR_BODY_LIMIT ? `${flat.slice(0, HTTP_ERROR_BODY_LIMIT)}… (${flat.length} chars)` : flat;
70
+ }
71
+ /** Retry-After as milliseconds: delta-seconds or an HTTP date. Undefined when absent or unparseable. */
72
+ export function retryAfterMs(header, now = Date.now()) {
73
+ if (!header)
74
+ return undefined;
75
+ const t = header.trim();
76
+ if (/^\d+(\.\d+)?$/.test(t))
77
+ return Number(t) * 1000;
78
+ const at = Date.parse(t);
79
+ return Number.isNaN(at) ? undefined : Math.max(0, at - now);
80
+ }
81
+ /** Exponential backoff with jitter: attempt 1 waits base·[0.5, 1), attempt 2 base·[1, 2), … */
82
+ export function backoffMs(attempt, baseMs, random = Math.random) {
83
+ const ceiling = baseMs * 2 ** (attempt - 1);
84
+ return Math.min(MAX_RETRY_WAIT_MS, ceiling / 2 + random() * (ceiling / 2));
85
+ }
86
+ /** "Already exists"-type answers. 409 always; 400/422 when the body says so (Clerk, Vercel use those statuses). */
87
+ const CONFLICT_BODY = /already[ _-]?exists|duplicate|ENV_CONFLICT|_exists\b/i;
88
+ /**
89
+ * The PROVIDER_* code for a non-2xx response (status and body). Exported for adapters that talk to a provider
90
+ * without `apiClient`.
91
+ */
92
+ export function classifyStatus(status, body) {
93
+ if (status === 401 || status === 403)
94
+ return "PROVIDER_AUTH";
95
+ if (status === 404)
96
+ return "PROVIDER_NOT_FOUND";
97
+ if (status === 409)
98
+ return "PROVIDER_CONFLICT";
99
+ if ((status === 400 || status === 422) && CONFLICT_BODY.test(body))
100
+ return "PROVIDER_CONFLICT";
101
+ if (REFUSED_STATUSES.has(status) || status >= 500)
102
+ return "PROVIDER_TRANSIENT";
103
+ return "PROVIDER_INVALID";
104
+ }
105
+ const DESCRIBE = {
106
+ PROVIDER_TRANSIENT: "temporarily unavailable",
107
+ PROVIDER_CONFLICT: "conflict",
108
+ PROVIDER_NOT_FOUND: "not found",
109
+ PROVIDER_AUTH: "not authorised",
110
+ PROVIDER_INVALID: "rejected the request",
111
+ PROVIDER_TIMEOUT: "timed out",
112
+ PROVIDER_RESPONSE: "unexpected response",
113
+ };
114
+ class Attempt {
115
+ code;
116
+ retryable;
117
+ message;
118
+ status;
119
+ waitMs;
120
+ constructor(code, retryable, message, status, waitMs) {
121
+ this.code = code;
122
+ this.retryable = retryable;
123
+ this.message = message;
124
+ this.status = status;
125
+ this.waitMs = waitMs;
126
+ }
127
+ }
128
+ /**
129
+ * A 2xx response body as `shape` wants it (parsed JSON when there is no shape; undefined for an empty body with no
130
+ * shape), or what is wrong with it.
131
+ */
132
+ function decode(text, shape, redact) {
133
+ if (!text)
134
+ return shape ? { problem: "empty response body" } : { value: undefined };
135
+ let parsed;
136
+ try {
137
+ parsed = JSON.parse(text);
138
+ }
139
+ catch {
140
+ return { problem: `response is not JSON: ${excerptOf(text, redact)}` };
141
+ }
142
+ if (!shape)
143
+ return { value: parsed };
144
+ try {
145
+ return { value: shape(parsed) };
146
+ }
147
+ catch (e) {
148
+ if (e instanceof ShapeError)
149
+ return { problem: e.message };
150
+ throw e;
151
+ }
152
+ }
153
+ /**
154
+ * Build an `ApiClient`. Adapters should call `clientFor(actx, ...)` instead, which wires redaction and the
155
+ * environment.
156
+ */
157
+ export function apiClient(opts) {
158
+ let base = opts.baseUrl;
159
+ while (base.endsWith("/"))
160
+ base = base.slice(0, -1);
161
+ const auth = opts.authHeader ?? "bearer";
162
+ const policy = policyFrom(opts.env);
163
+ const headers = { accept: "application/json" };
164
+ if (auth === "bearer")
165
+ headers.authorization = `Bearer ${opts.token}`;
166
+ else
167
+ headers[auth.slice("header:".length)] = opts.token;
168
+ const fail = (code, method, path, detail, status, extra = {}) => new SponsonError(code, `${opts.adapter}: ${method} ${path} → ${status ?? DESCRIBE[code]}${detail ? `: ${detail}` : ""}`, {
169
+ adapter: opts.adapter,
170
+ method,
171
+ path,
172
+ ...(status !== undefined ? { status } : {}),
173
+ ...extra,
174
+ });
175
+ async function once(method, path, body, idempotent) {
176
+ const controller = new AbortController();
177
+ const init = body === undefined
178
+ ? { method, headers: { ...headers }, signal: controller.signal }
179
+ : { method, headers: { ...headers, "content-type": "application/json" }, body: JSON.stringify(body), signal: controller.signal };
180
+ const timer = setTimeout(() => controller.abort(), policy.timeoutMs);
181
+ try {
182
+ const res = await fetch(base + path, init);
183
+ const text = await res.text(); // the timer also bounds a body that never finishes
184
+ if (res.ok)
185
+ return { status: res.status, text };
186
+ const code = classifyStatus(res.status, text);
187
+ const retryable = REFUSED_STATUSES.has(res.status) || (idempotent && UNAVAILABLE_STATUSES.has(res.status));
188
+ return new Attempt(code, retryable, excerptOf(text, opts.redact), res.status, retryAfterMs(res.headers.get("retry-after")));
189
+ }
190
+ catch (e) {
191
+ if (controller.signal.aborted)
192
+ return new Attempt("PROVIDER_TIMEOUT", false, `no response within ${policy.timeoutMs}ms (SPONSON_HTTP_TIMEOUT_MS)`);
193
+ // `fetch failed` alone says nothing; the cause (ECONNREFUSED, ECONNRESET, ENOTFOUND) does.
194
+ const cause = e.cause?.message ?? e.message;
195
+ // A write whose connection dropped may have happened; only idempotent requests are repeated.
196
+ return new Attempt("PROVIDER_TRANSIENT", idempotent, `${base}${path}: ${opts.redact(cause)}`);
197
+ }
198
+ finally {
199
+ clearTimeout(timer);
200
+ }
201
+ }
202
+ async function call(method, path, body, shape, write) {
203
+ const idempotent = write?.idempotent ?? IDEMPOTENT.has(method);
204
+ for (let attempt = 1;; attempt++) {
205
+ const r = await once(method, path, body, idempotent);
206
+ if (r instanceof Attempt) {
207
+ if (r.retryable && attempt <= policy.retries) {
208
+ await sleep(Math.min(MAX_RETRY_WAIT_MS, r.waitMs ?? backoffMs(attempt, policy.baseMs)));
209
+ continue;
210
+ }
211
+ const note = r.retryable ? ` (after ${attempt} attempts)` : "";
212
+ throw fail(r.code, method, path, r.message + note, r.status, r.retryable ? { attempts: attempt } : {});
213
+ }
214
+ const d = decode(r.text, shape, opts.redact);
215
+ if ("problem" in d)
216
+ throw fail("PROVIDER_RESPONSE", method, path, d.problem, r.status);
217
+ return d.value;
218
+ }
219
+ }
220
+ return {
221
+ adapter: opts.adapter,
222
+ get: (path, shape) => call("GET", path, undefined, shape),
223
+ post: (path, body, shape, write) => call("POST", path, body, shape, write),
224
+ put: (path, body, shape) => call("PUT", path, body, shape),
225
+ patch: (path, body, shape, write) => call("PATCH", path, body, shape, write),
226
+ delete: (path, shape) => call("DELETE", path, undefined, shape),
227
+ };
228
+ }
229
+ // ---------------------------------------------------------------------------
230
+ // Shape helpers: small checks with messages that name the field, instead of a TypeError three frames later.
231
+ // ---------------------------------------------------------------------------
232
+ /** True for a plain JSON object (not null, not an array). For hand-written `Shape`s. Stable authoring API. */
233
+ export function isObject(v) {
234
+ return typeof v === "object" && v !== null && !Array.isArray(v);
235
+ }
236
+ /** `v` must be an object; returns it. Stable authoring API. */
237
+ export function obj(v, what) {
238
+ if (!isObject(v))
239
+ throw new ShapeError(`expected ${what} to be an object, got ${kindOf(v)}`);
240
+ return v;
241
+ }
242
+ /** `v` must be an array of objects each having the string fields named; returns them typed. Stable authoring API. */
243
+ export function records(v, what, fields) {
244
+ if (!Array.isArray(v))
245
+ throw new ShapeError(`expected ${what} to be an array, got ${kindOf(v)}`);
246
+ return v.map((item, i) => {
247
+ const o = obj(item, `${what}[${i}]`);
248
+ for (const f of fields)
249
+ if (typeof o[f] !== "string")
250
+ throw new ShapeError(`expected ${what}[${i}].${f} to be a string, got ${kindOf(o[f])}`);
251
+ return o;
252
+ });
253
+ }
254
+ function kindOf(v) {
255
+ return v === null ? "null" : Array.isArray(v) ? "an array" : typeof v;
256
+ }
257
+ /** Hard stop against a provider that keeps returning a cursor. */
258
+ export const MAX_PAGES = 1000;
259
+ /**
260
+ * Fetch every page of a list. `page` validates one response body and says how to ask for the next page.
261
+ * Stops on a null cursor, an empty page, or a cursor seen before (a provider that repeats the last cursor).
262
+ * Stable authoring API.
263
+ */
264
+ export async function listAll(api, path, page) {
265
+ const out = [];
266
+ const seen = new Set();
267
+ let query = {};
268
+ for (let n = 0;; n++) {
269
+ if (n >= MAX_PAGES)
270
+ throw new SponsonError("PROVIDER_RESPONSE", `${api.adapter}: GET ${path} returned more than ${MAX_PAGES} pages`, { adapter: api.adapter, method: "GET", path });
271
+ const p = await api.get(withQuery(path, query), (b) => page(b, n));
272
+ out.push(...p.items);
273
+ if (!p.next || p.items.length === 0)
274
+ break;
275
+ const key = JSON.stringify(p.next);
276
+ if (seen.has(key))
277
+ break;
278
+ seen.add(key);
279
+ query = p.next;
280
+ }
281
+ return out;
282
+ }
283
+ /** `path` with `extra` appended as query parameters (after any it already has). Stable authoring API. */
284
+ export function withQuery(path, extra) {
285
+ const entries = Object.entries(extra);
286
+ if (entries.length === 0)
287
+ return path;
288
+ const q = new URLSearchParams(entries).toString();
289
+ return path.includes("?") ? `${path}&${q}` : `${path}?${q}`;
290
+ }
291
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { Registry } from "@sponson/core";
2
+ /**
3
+ * The stable adapter authoring API: what templates/adapter (`pnpm new:adapter`) uses, and what an out-of-tree
4
+ * adapter may rely on across minor releases. From common.ts: the helpers below (see the comment at its top).
5
+ * From http.ts: `ApiClient`, `Page`, `Shape`, `ShapeError`, `isObject`, `obj`, `records`, `listAll`,
6
+ * `withQuery` and `isProviderError`; the rest of http.ts is exported for the built-in adapters and tests.
7
+ * scenarios/tooling/new-adapter.test.ts fails if the template imports a helper that is not exported here.
8
+ */
9
+ export { clientFor, requireEnv, optionalEnv, requireProvider, diffValue, desiredSide, assertNoPending, deleteIgnoringNotFound, paramError, stringParam } from "./common.js";
10
+ export * from "./http.js";
11
+ export * from "./neon.js";
12
+ export * from "./vercel.js";
13
+ export * from "./clerk.js";
14
+ export * from "./secrets.js";
15
+ /** Every built-in adapter and secret source. The CLI uses this; tests build narrower registries. */
16
+ export declare function createRegistry(): Registry;
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ import { Registry } from "@sponson/core";
2
+ import { clerkAdapter } from "./clerk.js";
3
+ import { neonAdapter } from "./neon.js";
4
+ import { awsSecretsManagerSource, dopplerSecretSource, envSecretSource, gcpSecretManagerSource, opSecretSource } from "./secrets.js";
5
+ import { vercelAdapter } from "./vercel.js";
6
+ /**
7
+ * The stable adapter authoring API: what templates/adapter (`pnpm new:adapter`) uses, and what an out-of-tree
8
+ * adapter may rely on across minor releases. From common.ts: the helpers below (see the comment at its top).
9
+ * From http.ts: `ApiClient`, `Page`, `Shape`, `ShapeError`, `isObject`, `obj`, `records`, `listAll`,
10
+ * `withQuery` and `isProviderError`; the rest of http.ts is exported for the built-in adapters and tests.
11
+ * scenarios/tooling/new-adapter.test.ts fails if the template imports a helper that is not exported here.
12
+ */
13
+ export { clientFor, requireEnv, optionalEnv, requireProvider, diffValue, desiredSide, assertNoPending, deleteIgnoringNotFound, paramError, stringParam } from "./common.js";
14
+ export * from "./http.js";
15
+ export * from "./neon.js";
16
+ export * from "./vercel.js";
17
+ export * from "./clerk.js";
18
+ export * from "./secrets.js";
19
+ /** Every built-in adapter and secret source. The CLI uses this; tests build narrower registries. */
20
+ export function createRegistry() {
21
+ return new Registry().addAdapter(neonAdapter).addAdapter(vercelAdapter).addAdapter(clerkAdapter).addSecretSource(envSecretSource).addSecretSource(dopplerSecretSource()).addSecretSource(opSecretSource()).addSecretSource(awsSecretsManagerSource()).addSecretSource(gcpSecretManagerSource());
22
+ }
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,eAAe,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACrI,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C;;;;;;GAMG;AACH,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,sBAAsB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC5K,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAE7B,oGAAoG;AACpG,MAAM,UAAU,cAAc;IAC5B,OAAO,IAAI,QAAQ,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,eAAe,CAAC,eAAe,CAAC,CAAC,eAAe,CAAC,mBAAmB,EAAE,CAAC,CAAC,eAAe,CAAC,cAAc,EAAE,CAAC,CAAC,eAAe,CAAC,uBAAuB,EAAE,CAAC,CAAC,eAAe,CAAC,sBAAsB,EAAE,CAAC,CAAC;AAClS,CAAC"}
package/dist/neon.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { type ResourceAdapter } from "@sponson/core";
2
+ /** Neon's API base URL; `NEON_API_URL` overrides it (the sim and tests use that). */
3
+ export declare const NEON_DEFAULT_API_URL = "https://console.neon.tech/api/v2";
4
+ /**
5
+ * Neon: op `branch` creates a database branch per scope and outputs its connection string (sensitive). Needs
6
+ * `NEON_API_KEY` and `providers.neon.project`.
7
+ */
8
+ export declare const neonAdapter: ResourceAdapter;
package/dist/neon.js ADDED
@@ -0,0 +1,167 @@
1
+ import { canonicalJson, isPendingMarker, sha256 } from "@sponson/core";
2
+ import { ABSENT, assertNoPending, clientFor, deleteIgnoringNotFound, desiredSide, optionalEnv, paramError, requireEnv, requireProvider, stringParam } from "./common.js";
3
+ import { ShapeError, isProviderError, listAll, obj, records } from "./http.js";
4
+ /** The environment neon reads; declared once, used by the code below and by the generated docs. */
5
+ const ABOUT = { credentialEnv: "NEON_API_KEY", baseUrlEnv: "NEON_API_URL" };
6
+ /** Neon's API base URL; `NEON_API_URL` overrides it (the sim and tests use that). */
7
+ export const NEON_DEFAULT_API_URL = "https://console.neon.tech/api/v2";
8
+ function client(actx) {
9
+ const token = requireEnv(actx.env, ABOUT.credentialEnv, "neon");
10
+ const project = requireProvider(actx, "project", "neon");
11
+ return { api: clientFor(actx, "neon", { baseUrl: optionalEnv(actx.env, ABOUT.baseUrlEnv) ?? NEON_DEFAULT_API_URL, token }), project };
12
+ }
13
+ function branchKey(name) {
14
+ return `${BRANCH_PREFIX}${name}`;
15
+ }
16
+ const BRANCH_PREFIX = "branch:";
17
+ /** Identity hash: name and parent, the two things that define a branch for us. A replacement shows as a new provider id. */
18
+ function branchHash(b) {
19
+ return sha256(canonicalJson({ name: b.name, parent_id: b.parent_id }));
20
+ }
21
+ function record(b) {
22
+ return { key: branchKey(b.name), id: b.id, hash: branchHash(b), label: `Neon branch ${b.name}` };
23
+ }
24
+ function parseBranches(v, what) {
25
+ return records(v, what, ["id", "name"]).map((b, i) => {
26
+ if (b.parent_id !== undefined && b.parent_id !== null && typeof b.parent_id !== "string")
27
+ throw new ShapeError(`expected ${what}[${i}].parent_id to be a string or null`);
28
+ return { id: b.id, name: b.name, parent_id: b.parent_id ?? null, default: b.default === true, primary: b.primary === true };
29
+ });
30
+ }
31
+ /** `{ branches, pagination: { next } }`; the next page is `?cursor=<next>`. Older responses call it `cursor`. */
32
+ function branchPage(body) {
33
+ const o = obj(body, "the branch list");
34
+ const items = parseBranches(o.branches, "`branches`");
35
+ const p = o.pagination;
36
+ const cursor = p && typeof p === "object" ? (p.next ?? p.cursor) : undefined;
37
+ return { items, next: typeof cursor === "string" && cursor !== "" ? { cursor } : null };
38
+ }
39
+ async function listBranches(c) {
40
+ return listAll(c.api, `/projects/${c.project}/branches`, branchPage);
41
+ }
42
+ /** The database a branch's connection string points at: `neondb` (a new project's default) if present, else the first one. */
43
+ const DEFAULT_DATABASE = "neondb";
44
+ /**
45
+ * The branch's endpoint host and connection string. GET /projects/:id/connection_uri requires `database_name` and
46
+ * `role_name`; both come from the branch's own database list (the database's `owner_name` is the role), so a
47
+ * project whose database or role is not named like a new project's defaults still works.
48
+ */
49
+ async function outputsFor(c, b) {
50
+ const [host, db] = await Promise.all([
51
+ c.api.get(`/projects/${c.project}/branches/${b.id}/endpoints`, (body) => {
52
+ const eps = records(obj(body, "the endpoint list").endpoints, "`endpoints`", ["id", "host"]);
53
+ const h = (eps.find((e) => e.type === "read_write") ?? eps[0])?.host;
54
+ if (!h)
55
+ throw new ShapeError(`branch ${b.name} has no endpoint`);
56
+ return h;
57
+ }),
58
+ c.api.get(`/projects/${c.project}/branches/${b.id}/databases`, (body) => {
59
+ const dbs = records(obj(body, "the database list").databases, "`databases`", ["name", "owner_name"]);
60
+ const d = dbs.find((x) => x.name === DEFAULT_DATABASE) ?? dbs[0];
61
+ if (!d)
62
+ throw new ShapeError(`branch ${b.name} has no database`);
63
+ return d;
64
+ }),
65
+ ]);
66
+ const q = new URLSearchParams({ branch_id: b.id, database_name: db.name, role_name: db.owner_name });
67
+ const uri = await c.api.get(`/projects/${c.project}/connection_uri?${q}`, (body) => {
68
+ const o = obj(body, "the connection URI");
69
+ if (typeof o.uri !== "string")
70
+ throw new ShapeError("expected `uri` to be a string");
71
+ return o.uri;
72
+ });
73
+ return { branch_id: b.id, connection_string: uri, host };
74
+ }
75
+ function parseCreated(body) {
76
+ const o = obj(body, "the create-branch response");
77
+ const branch = parseBranches([o.branch], "`branch`")[0];
78
+ const endpoints = Array.isArray(o.endpoints) ? records(o.endpoints, "`endpoints`", ["host"]) : [];
79
+ const uris = Array.isArray(o.connection_uris) ? records(o.connection_uris, "`connection_uris`", ["connection_uri"]) : [];
80
+ return { branch, ...(endpoints[0] ? { host: endpoints[0].host } : {}), ...(uris[0] ? { uri: uris[0].connection_uri } : {}) };
81
+ }
82
+ const branch = {
83
+ outputs: {
84
+ branch_id: { available: "immediate" },
85
+ connection_string: { available: "immediate", sensitive: true },
86
+ host: { available: "immediate" },
87
+ },
88
+ defaults(params, ctx) {
89
+ return { parent: "main", name: `sponson/${ctx.env}/${ctx.scope}`, ...params };
90
+ },
91
+ async read(actx, params) {
92
+ if (isPendingMarker(params.name))
93
+ return null;
94
+ const c = client(actx);
95
+ const name = stringParam(params, "name", "neon");
96
+ const found = (await listBranches(c)).find((b) => b.name === name);
97
+ if (!found)
98
+ return null;
99
+ return { resources: [record(found)], outputs: await outputsFor(c, found) };
100
+ },
101
+ diff(live, params) {
102
+ const name = params.name;
103
+ if (isPendingMarker(name))
104
+ return [{ key: "branch:(pending)", kind: "create", label: "Neon branch", before: ABSENT, after: desiredSide(name, false) }];
105
+ const key = branchKey(String(name));
106
+ const current = live?.resources.find((r) => r.key === key);
107
+ // A branch's identity is its name; re-parenting is not supported, so an existing branch is unchanged whatever `parent` says.
108
+ if (current)
109
+ return [{ key, kind: "unchanged", label: current.label ?? key }];
110
+ return [{ key, kind: "create", label: `Neon branch ${String(name)}`, before: ABSENT, after: desiredSide(name, false) }];
111
+ },
112
+ async apply(actx, params, live) {
113
+ assertNoPending(params, "neon");
114
+ const c = client(actx);
115
+ const name = stringParam(params, "name", "neon");
116
+ const key = branchKey(name);
117
+ if (live?.resources.some((r) => r.key === key))
118
+ return { resources: live.resources, outputs: live.outputs, created: [] };
119
+ const parentName = stringParam(params, "parent", "neon", "main");
120
+ const branches = await listBranches(c);
121
+ const parent = branches.find((b) => b.name === parentName || b.id === parentName);
122
+ if (!parent)
123
+ throw paramError("neon", `parent branch \`${parentName}\` not found in project ${c.project}`, "parent");
124
+ await actx.intend([key]);
125
+ actx.log(`create branch ${name} from ${parent.name}`);
126
+ let r;
127
+ try {
128
+ r = await c.api.post(`/projects/${c.project}/branches`, { branch: { name, parent_id: parent.id }, endpoints: [{ type: "read_write" }] }, parseCreated);
129
+ }
130
+ catch (e) {
131
+ if (!isProviderError(e, "PROVIDER_CONFLICT"))
132
+ throw e;
133
+ // It exists already (a lost response of ours, or someone else's). Not created by this call; the engine
134
+ // claims it when an earlier intent of ours named it.
135
+ const found = (await listBranches(c)).find((b) => b.name === name);
136
+ if (!found)
137
+ throw e;
138
+ return { resources: [record(found)], outputs: await outputsFor(c, found), created: [] };
139
+ }
140
+ // Trust the write's answer: listing right after a create may not show it yet.
141
+ const outputs = r.host && r.uri ? { branch_id: r.branch.id, connection_string: r.uri, host: r.host } : await outputsFor(c, r.branch);
142
+ return { resources: [record(r.branch)], outputs, created: [key] };
143
+ },
144
+ async destroy(actx, resources) {
145
+ const c = client(actx);
146
+ for (const r of resources)
147
+ await deleteIgnoringNotFound(c.api, `/projects/${c.project}/branches/${encodeURIComponent(r.id)}`);
148
+ },
149
+ /** Every branch but the project's root: anything else may be a preview branch some scope (or nobody) manages. */
150
+ async listScope(actx) {
151
+ const c = client(actx);
152
+ return (await listBranches(c)).filter((b) => !b.default && !b.primary).map(record);
153
+ },
154
+ /** One line per branch, naming it: the line then manages exactly that branch. */
155
+ adopt(resources) {
156
+ return resources.map((r) => {
157
+ const name = r.key.slice(BRANCH_PREFIX.length);
158
+ return { id: `db-${name}`, params: { name }, keys: [r.key] };
159
+ });
160
+ },
161
+ };
162
+ /**
163
+ * Neon: op `branch` creates a database branch per scope and outputs its connection string (sensitive). Needs
164
+ * `NEON_API_KEY` and `providers.neon.project`.
165
+ */
166
+ export const neonAdapter = { name: "neon", ops: { branch }, about: ABOUT };
167
+ //# sourceMappingURL=neon.js.map