neon-testing 3.0.0-beta.0 โ†’ 3.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +1 -1
  2. package/dist/bun.d.ts +11 -0
  3. package/dist/bun.d.ts.map +1 -0
  4. package/dist/bun.js +10 -0
  5. package/dist/bun.js.map +1 -0
  6. package/dist/core.d.ts +148 -0
  7. package/dist/core.d.ts.map +1 -0
  8. package/dist/core.js +204 -0
  9. package/dist/core.js.map +1 -0
  10. package/dist/lib/barrier.d.ts +6 -0
  11. package/dist/lib/barrier.d.ts.map +1 -0
  12. package/dist/lib/barrier.js +19 -0
  13. package/dist/lib/barrier.js.map +1 -0
  14. package/dist/lib/cleared-database-url.d.ts +10 -0
  15. package/dist/lib/cleared-database-url.d.ts.map +1 -0
  16. package/dist/lib/cleared-database-url.js +10 -0
  17. package/dist/lib/cleared-database-url.js.map +1 -0
  18. package/dist/lib/expires-in.d.ts +5 -0
  19. package/dist/lib/expires-in.d.ts.map +1 -0
  20. package/dist/lib/expires-in.js +21 -0
  21. package/dist/lib/expires-in.js.map +1 -0
  22. package/dist/lib/singleton.d.ts +5 -0
  23. package/dist/lib/singleton.d.ts.map +1 -0
  24. package/dist/lib/singleton.js +11 -0
  25. package/dist/lib/singleton.js.map +1 -0
  26. package/dist/lib/ssl.d.ts +9 -0
  27. package/dist/lib/ssl.d.ts.map +1 -0
  28. package/dist/lib/ssl.js +21 -0
  29. package/dist/lib/ssl.js.map +1 -0
  30. package/dist/lib/with-retry.d.ts +12 -0
  31. package/dist/lib/with-retry.d.ts.map +1 -0
  32. package/dist/lib/with-retry.js +39 -0
  33. package/dist/lib/with-retry.js.map +1 -0
  34. package/dist/lib/ws-error.d.ts +6 -0
  35. package/dist/lib/ws-error.d.ts.map +1 -0
  36. package/dist/lib/ws-error.js +15 -0
  37. package/dist/lib/ws-error.js.map +1 -0
  38. package/dist/setup.d.ts +2 -0
  39. package/dist/setup.d.ts.map +1 -0
  40. package/dist/setup.js +9 -0
  41. package/dist/setup.js.map +1 -0
  42. package/dist/utils.d.ts +3 -0
  43. package/dist/utils.d.ts.map +1 -0
  44. package/dist/utils.js +3 -0
  45. package/dist/utils.js.map +1 -0
  46. package/dist/vitest.d.ts +11 -0
  47. package/dist/vitest.d.ts.map +1 -0
  48. package/dist/vitest.js +9 -0
  49. package/dist/vitest.js.map +1 -0
  50. package/package.json +32 -10
  51. package/src/bun.ts +3 -3
  52. package/src/core.ts +8 -5
  53. package/src/lib/cleared-database-url.ts +9 -0
  54. package/src/setup.ts +5 -3
  55. package/src/utils.ts +2 -2
  56. package/src/vitest.ts +3 -3
package/README.md CHANGED
@@ -31,7 +31,7 @@ Each test file runs against its own isolated PostgreSQL database (Neon branch),
31
31
  - ๐Ÿ”„ **Isolated test environments** - Each test file runs against its own Postgres database with your actual schema and constraints
32
32
  - ๐Ÿงน **Automatic cleanup** - Neon test branches are created and destroyed automatically
33
33
  - ๐Ÿ› **Debug friendly** - Option to preserve test branches for debugging failed tests
34
- - ๐Ÿ›ก๏ธ **TypeScript only** - Ships TypeScript source files
34
+ - ๐Ÿ›ก๏ธ **TypeScript native** - Ships compiled JavaScript with type declarations and source maps too
35
35
  - ๐ŸŽฏ **ESM only** - No CommonJS support
36
36
 
37
37
  ## How it works
package/dist/bun.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import type { MakeNeonTestingOptions } from "./core.js";
2
+ export type { MakeNeonTestingOptions, NeonTestingOptions } from "./core.js";
3
+ /**
4
+ * Create a Neon test-branch factory wired to Bun's lifecycle hooks.
5
+ */
6
+ export declare function makeNeonTesting(options: MakeNeonTestingOptions): {
7
+ (overrides?: import("./core.js").NeonTestingOptions): () => import("@neondatabase/api-client").Branch | undefined;
8
+ deleteAllTestBranches: () => Promise<void>;
9
+ api: import("@neondatabase/api-client").Api<unknown>;
10
+ };
11
+ //# sourceMappingURL=bun.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bun.d.ts","sourceRoot":"","sources":["../src/bun.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAExD,YAAY,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAE5E;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB;;;;EAE9D"}
package/dist/bun.js ADDED
@@ -0,0 +1,10 @@
1
+ /// <reference types="bun" />
2
+ import { beforeAll, afterAll } from "bun:test";
3
+ import { makeNeonTestingCore } from "./core.js";
4
+ /**
5
+ * Create a Neon test-branch factory wired to Bun's lifecycle hooks.
6
+ */
7
+ export function makeNeonTesting(options) {
8
+ return makeNeonTestingCore({ ...options, hooks: { beforeAll, afterAll } });
9
+ }
10
+ //# sourceMappingURL=bun.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bun.js","sourceRoot":"","sources":["../src/bun.ts"],"names":[],"mappings":"AAAA,6BAA6B;AAC7B,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAKhD;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,OAA+B;IAC7D,OAAO,mBAAmB,CAAC,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;AAC7E,CAAC"}
package/dist/core.d.ts ADDED
@@ -0,0 +1,148 @@
1
+ /**
2
+ * https://neon.com/docs/reference/typescript-sdk
3
+ */
4
+ import { type Branch } from "@neondatabase/api-client";
5
+ /**
6
+ * Test-runner lifecycle hooks
7
+ *
8
+ * Injected so the core stays runner-agnostic. Most users don't build these by
9
+ * hand โ€” import the pre-wired factory from `neon-testing/vitest` or
10
+ * `neon-testing/bun`, which supply them automatically.
11
+ */
12
+ export interface NeonTestingHooks {
13
+ beforeAll: (fn: () => void | Promise<void>) => void;
14
+ afterAll: (fn: () => void | Promise<void>) => void;
15
+ }
16
+ /**
17
+ * Configuration for the Neon test-branch factory.
18
+ *
19
+ * `hooks` are injected by the per-runner entry (`neon-testing/vitest` or
20
+ * `neon-testing/bun`); the remaining fields control how each test branch is
21
+ * created and torn down.
22
+ */
23
+ export interface MakeNeonTestingCoreOptions {
24
+ /**
25
+ * Test-runner lifecycle hooks (supplied by `neon-testing/vitest` or
26
+ * `neon-testing/bun`)
27
+ */
28
+ hooks: NeonTestingHooks;
29
+ /**
30
+ * The Neon API key, this is used to create and teardown test branches (required)
31
+ *
32
+ * https://neon.com/docs/manage/api-keys#creating-api-keys
33
+ */
34
+ apiKey: string;
35
+ /**
36
+ * The Neon project ID to operate on (required)
37
+ *
38
+ * https://console.neon.tech/app/projects
39
+ */
40
+ projectId: string;
41
+ /**
42
+ * The parent branch ID for the new branch (default: undefined)
43
+ *
44
+ * If omitted or undefined, test branches will be created from the project's
45
+ * default branch.
46
+ */
47
+ parentBranchId?: string;
48
+ /**
49
+ * Whether to create a schema-only branch (default: false)
50
+ */
51
+ schemaOnly?: boolean;
52
+ /**
53
+ * The type of connection to create (default: "pooler")
54
+ */
55
+ endpoint?: "pooler" | "direct";
56
+ /**
57
+ * Delete the test branch in afterAll (default: true)
58
+ *
59
+ * Disabling this will leave each test branch in the Neon project after the
60
+ * test suite runs
61
+ */
62
+ deleteBranch?: boolean;
63
+ /**
64
+ * Automatically close Neon WebSocket connections opened during tests before
65
+ * deleting the branch (default: false)
66
+ *
67
+ * Suppresses the specific Neon WebSocket "Connection terminated unexpectedly"
68
+ * error that may surface when deleting a branch with open WebSocket
69
+ * connections
70
+ *
71
+ * Vitest only โ€” under Bun the uncaughtException suppression does not
72
+ * intercept, so close connections explicitly there instead.
73
+ */
74
+ autoCloseWebSockets?: boolean;
75
+ /**
76
+ * Time in seconds until the branch expires and is automatically deleted
77
+ * (default: 600 = 10 minutes)
78
+ *
79
+ * This provides automatic cleanup for dangling branches from interrupted or
80
+ * failed test runs. Set to `null` to disable automatic expiration.
81
+ *
82
+ * Must be a positive integer. Maximum 30 days (2,592,000 seconds).
83
+ *
84
+ * https://neon.com/docs/guides/branch-expiration
85
+ */
86
+ expiresIn?: number | null;
87
+ /**
88
+ * The database role to connect as (default: project owner role)
89
+ *
90
+ * The role must exist in the parent branch. Roles are automatically
91
+ * copied to test branches when branching.
92
+ */
93
+ roleName?: string;
94
+ /**
95
+ * The database to connect to (default: project default database)
96
+ */
97
+ databaseName?: string;
98
+ /**
99
+ * Override the `sslmode` query param on the connection URI (default:
100
+ * undefined โ€” URI is passed through unchanged)
101
+ *
102
+ * Neon's API returns URIs with `sslmode=require`. In pg v9 the meaning of
103
+ * `require` changes to libpq semantics (encrypt, but don't verify the CA).
104
+ *
105
+ * - `"verify-full"` โ€” strict CA verification (silences the pg v9 warning)
106
+ * - `"require"` โ€” preserves today's effective behavior under pg v9 by also
107
+ * setting `uselibpqcompat=true`
108
+ *
109
+ * Only affects drivers that parse `sslmode` (e.g. `pg`). The Neon
110
+ * serverless driver ignores it.
111
+ */
112
+ sslMode?: "verify-full" | "require";
113
+ }
114
+ /** Options for the `makeNeonTesting` factories โ€” core options minus the injected hooks */
115
+ export type MakeNeonTestingOptions = Omit<MakeNeonTestingCoreOptions, "hooks">;
116
+ /** Per-file overrides accepted by the returned `neonTesting()` function */
117
+ export type NeonTestingOptions = Partial<Omit<MakeNeonTestingOptions, "apiKey">>;
118
+ /**
119
+ * Low-level factory that creates a Neon test-branch setup/teardown function.
120
+ * Runner-agnostic โ€” you inject the lifecycle hooks via `options.hooks`.
121
+ *
122
+ * Most users want a pre-wired entry instead: `neon-testing/vitest` or
123
+ * `neon-testing/bun`. Reach for this core factory only for other runners
124
+ * (jest, node:test, โ€ฆ).
125
+ *
126
+ * @param factoryOptions - see {@link MakeNeonTestingCoreOptions}
127
+ * @returns A setup/teardown function with attached utilities:
128
+ * - `deleteAllTestBranches()` - delete all test branches
129
+ * - `api` - the Neon API client
130
+ *
131
+ * @example
132
+ * ```ts
133
+ * import { beforeAll, afterAll } from "vitest"; // or any runner
134
+ * import { makeNeonTestingCore } from "neon-testing/core";
135
+ *
136
+ * export const neonTesting = makeNeonTestingCore({
137
+ * apiKey: process.env.NEON_API_KEY!,
138
+ * projectId: process.env.NEON_PROJECT_ID!,
139
+ * hooks: { beforeAll, afterAll },
140
+ * });
141
+ * ```
142
+ */
143
+ export declare function makeNeonTestingCore(factoryOptions: MakeNeonTestingCoreOptions): {
144
+ (overrides?: NeonTestingOptions): () => Branch | undefined;
145
+ deleteAllTestBranches: () => Promise<void>;
146
+ api: import("@neondatabase/api-client").Api<unknown>;
147
+ };
148
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAGL,KAAK,MAAM,EACZ,MAAM,0BAA0B,CAAC;AAOlC;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;IACpD,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;CACpD;AAED;;;;;;GAMG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;OAGG;IACH,KAAK,EAAE,gBAAgB,CAAC;IACxB;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,QAAQ,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC/B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;;;;;;;OAUG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;CACrC;AAED,0FAA0F;AAC1F,MAAM,MAAM,sBAAsB,GAAG,IAAI,CAAC,0BAA0B,EAAE,OAAO,CAAC,CAAC;AAE/E,2EAA2E;AAC3E,MAAM,MAAM,kBAAkB,GAAG,OAAO,CACtC,IAAI,CAAC,sBAAsB,EAAE,QAAQ,CAAC,CACvC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,mBAAmB,CACjC,cAAc,EAAE,0BAA0B;iBA0C5B,kBAAkB;;;EA6KjC"}
package/dist/core.js ADDED
@@ -0,0 +1,204 @@
1
+ /**
2
+ * https://neon.com/docs/reference/typescript-sdk
3
+ */
4
+ import { createApiClient, EndpointType, } from "@neondatabase/api-client";
5
+ import { applySslMode } from "./lib/ssl.js";
6
+ import { validateExpiresIn } from "./lib/expires-in.js";
7
+ import { neonWsErrorHandler } from "./lib/ws-error.js";
8
+ import { withRetry } from "./lib/with-retry.js";
9
+ import { CLEARED_DATABASE_URL } from "./lib/cleared-database-url.js";
10
+ /**
11
+ * Low-level factory that creates a Neon test-branch setup/teardown function.
12
+ * Runner-agnostic โ€” you inject the lifecycle hooks via `options.hooks`.
13
+ *
14
+ * Most users want a pre-wired entry instead: `neon-testing/vitest` or
15
+ * `neon-testing/bun`. Reach for this core factory only for other runners
16
+ * (jest, node:test, โ€ฆ).
17
+ *
18
+ * @param factoryOptions - see {@link MakeNeonTestingCoreOptions}
19
+ * @returns A setup/teardown function with attached utilities:
20
+ * - `deleteAllTestBranches()` - delete all test branches
21
+ * - `api` - the Neon API client
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * import { beforeAll, afterAll } from "vitest"; // or any runner
26
+ * import { makeNeonTestingCore } from "neon-testing/core";
27
+ *
28
+ * export const neonTesting = makeNeonTestingCore({
29
+ * apiKey: process.env.NEON_API_KEY!,
30
+ * projectId: process.env.NEON_PROJECT_ID!,
31
+ * hooks: { beforeAll, afterAll },
32
+ * });
33
+ * ```
34
+ */
35
+ export function makeNeonTestingCore(factoryOptions) {
36
+ // Validate factory options
37
+ validateExpiresIn(factoryOptions.expiresIn);
38
+ const apiClient = createApiClient({ apiKey: factoryOptions.apiKey });
39
+ /**
40
+ * Delete all test branches (branches with the "integration-test: true" annotation)
41
+ */
42
+ async function deleteAllTestBranches() {
43
+ const { data } = await apiClient.listProjectBranches({
44
+ projectId: factoryOptions.projectId,
45
+ });
46
+ for (const branch of data.branches) {
47
+ const isTestBranch = data.annotations[branch.id]?.value["integration-test"] === "true";
48
+ if (isTestBranch) {
49
+ await apiClient.deleteProjectBranch(factoryOptions.projectId, branch.id);
50
+ }
51
+ }
52
+ }
53
+ /**
54
+ * Setup/teardown function for a test file.
55
+ *
56
+ * Registers lifecycle hooks (via the injected `hooks`) that:
57
+ * - Create an isolated test branch from your parent branch
58
+ * - Set `DATABASE_URL` environment variable to the test branch connection URI
59
+ * - Automatically delete the test branch after tests complete (unless `deleteBranch: false`)
60
+ * - Automatically expire branches after 10 minutes for cleanup (unless `expiresIn: null`)
61
+ *
62
+ * @param overrides - Optional per-file overrides for the factory options
63
+ * @returns A function that provides access to the current Neon branch object
64
+ */
65
+ const neonTesting = (
66
+ /** Override any factory options except apiKey */
67
+ overrides) => {
68
+ // Validate overrides
69
+ if (overrides?.expiresIn !== undefined) {
70
+ validateExpiresIn(overrides.expiresIn);
71
+ }
72
+ // Merge factory options with overrides
73
+ const options = { ...factoryOptions, ...overrides };
74
+ // Each test file gets its own branch and database client
75
+ let branch;
76
+ // List of tracked Neon WebSocket connections
77
+ // Lazily initialized when autoCloseWebSockets is enabled (avoids
78
+ // referencing the WebSocket global on runtimes that lack it, e.g. Node 20)
79
+ let neonSockets;
80
+ /**
81
+ * Create a new test branch
82
+ *
83
+ * @returns The connection URI for the new branch
84
+ */
85
+ async function createBranch() {
86
+ // Calculate expiration timestamp if expiresIn is set
87
+ const expiresIn = options.expiresIn === undefined ? 600 : options.expiresIn; // Default: 10 minutes
88
+ const expiresAt = expiresIn !== null
89
+ ? new Date(Date.now() + expiresIn * 1000).toISOString()
90
+ : undefined;
91
+ const { data } = await apiClient.createProjectBranch(options.projectId, {
92
+ branch: {
93
+ name: `test/${crypto.randomUUID()}`,
94
+ ...(options.parentBranchId
95
+ ? { parent_id: options.parentBranchId }
96
+ : {}),
97
+ ...(options.schemaOnly ? { init_source: "schema-only" } : {}),
98
+ ...(expiresAt ? { expires_at: expiresAt } : {}),
99
+ },
100
+ endpoints: [{ type: EndpointType.ReadWrite }],
101
+ annotation_value: {
102
+ "integration-test": "true",
103
+ },
104
+ });
105
+ branch = data.branch;
106
+ // Determine role and database (handles multi-role/database projects)
107
+ const targetRole = options.roleName ??
108
+ data.roles?.find((r) => r.name === "neondb_owner")?.name ??
109
+ data.roles?.[0]?.name;
110
+ const targetDatabase = options.databaseName ??
111
+ data.databases?.find((d) => d.name === "neondb")?.name ??
112
+ data.databases?.[0]?.name;
113
+ if (!targetRole) {
114
+ throw new Error("No role available in branch");
115
+ }
116
+ if (!targetDatabase) {
117
+ throw new Error("No database available in branch");
118
+ }
119
+ // Validate specified role exists
120
+ if (options.roleName &&
121
+ !data.roles?.some((r) => r.name === options.roleName)) {
122
+ throw new Error(`Role not found: ${options.roleName}`);
123
+ }
124
+ // Validate specified database exists
125
+ if (options.databaseName &&
126
+ !data.databases?.some((d) => d.name === options.databaseName)) {
127
+ throw new Error(`Database not found: ${options.databaseName}`);
128
+ }
129
+ // Use getConnectionUri API (works for all cases, including multi-role projects)
130
+ const { data: uriData } = await apiClient.getConnectionUri({
131
+ projectId: options.projectId,
132
+ branch_id: branch.id,
133
+ role_name: targetRole,
134
+ database_name: targetDatabase,
135
+ pooled: options.endpoint !== "direct",
136
+ });
137
+ return applySslMode(uriData.uri, options.sslMode);
138
+ }
139
+ /**
140
+ * Delete the test branch
141
+ */
142
+ async function deleteBranch() {
143
+ if (!branch?.id) {
144
+ throw new Error("No branch to delete");
145
+ }
146
+ await apiClient.deleteProjectBranch(options.projectId, branch.id);
147
+ branch = undefined;
148
+ }
149
+ factoryOptions.hooks.beforeAll(async () => {
150
+ process.env.DATABASE_URL = await withRetry(createBranch, {
151
+ maxRetries: 8,
152
+ baseDelayMs: 1000,
153
+ });
154
+ if (options.autoCloseWebSockets) {
155
+ neonSockets = new Set();
156
+ // Custom WebSocket constructor that tracks Neon WebSocket connections
157
+ class TrackingWebSocket extends WebSocket {
158
+ constructor(url) {
159
+ super(url);
160
+ // Only track Neon WebSocket connections
161
+ if (!url.includes(".neon.tech/"))
162
+ return;
163
+ neonSockets.add(this);
164
+ }
165
+ }
166
+ // Install a custom WebSocket constructor that tracks Neon WebSocket
167
+ // connections and closes them before deleting the branch
168
+ try {
169
+ const { neonConfig } = await import("@neondatabase/serverless");
170
+ neonConfig.webSocketConstructor = TrackingWebSocket;
171
+ }
172
+ catch {
173
+ throw new Error("autoCloseWebSockets requires @neondatabase/serverless to be installed. Run: npm install @neondatabase/serverless");
174
+ }
175
+ }
176
+ });
177
+ factoryOptions.hooks.afterAll(async () => {
178
+ // Overwrite (not `delete`) so DATABASE_URL no longer points at the test
179
+ // branch; see CLEARED_DATABASE_URL for why a sentinel rather than delete.
180
+ process.env.DATABASE_URL = CLEARED_DATABASE_URL;
181
+ // Close all tracked Neon WebSocket connections before deleting the branch
182
+ if (options.autoCloseWebSockets && neonSockets) {
183
+ // Suppress Neon WebSocket "Connection terminated unexpectedly" error
184
+ process.prependListener("uncaughtException", neonWsErrorHandler);
185
+ // Close tracked Neon WebSocket connections before deleting the branch
186
+ neonSockets.forEach((ws) => ws.close());
187
+ }
188
+ if (options.deleteBranch !== false) {
189
+ await deleteBranch();
190
+ }
191
+ });
192
+ /**
193
+ * Return the Neon branch object
194
+ *
195
+ * @returns The Neon branch object
196
+ */
197
+ return () => branch;
198
+ };
199
+ // Attach utilities
200
+ neonTesting.deleteAllTestBranches = deleteAllTestBranches;
201
+ neonTesting.api = apiClient;
202
+ return neonTesting;
203
+ }
204
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Creates a synchronization barrier that blocks until `count` callers have
3
+ * arrived, then releases all of them simultaneously
4
+ */
5
+ export declare function createBarrier(count: number): () => Promise<void>;
6
+ //# sourceMappingURL=barrier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"barrier.d.ts","sourceRoot":"","sources":["../../src/lib/barrier.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,uBAiB1C"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Creates a synchronization barrier that blocks until `count` callers have
3
+ * arrived, then releases all of them simultaneously
4
+ */
5
+ export function createBarrier(count) {
6
+ let arrived = 0;
7
+ let release;
8
+ const barrier = new Promise((resolve) => {
9
+ release = resolve;
10
+ });
11
+ return async () => {
12
+ arrived++;
13
+ if (arrived === count) {
14
+ release();
15
+ }
16
+ await barrier;
17
+ };
18
+ }
19
+ //# sourceMappingURL=barrier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"barrier.js","sourceRoot":"","sources":["../../src/lib/barrier.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,OAAmB,CAAC;IAExB,MAAM,OAAO,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QAC5C,OAAO,GAAG,OAAO,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,OAAO,KAAK,IAAI,EAAE;QAChB,OAAO,EAAE,CAAC;QAEV,IAAI,OAAO,KAAK,KAAK,EAAE,CAAC;YACtB,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,OAAO,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Sentinel written to `DATABASE_URL` to clear it, instead of `delete`.
3
+ *
4
+ * Keeps the value a string for consumers that type `DATABASE_URL` as required
5
+ * (so the library never has to fight their env typing), and is an invalid
6
+ * connection string so a test that forgot `neonTesting()` fails loudly rather
7
+ * than silently reaching a real database.
8
+ */
9
+ export declare const CLEARED_DATABASE_URL = "neon-testing: DATABASE_URL cleared, call neonTesting()";
10
+ //# sourceMappingURL=cleared-database-url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cleared-database-url.d.ts","sourceRoot":"","sources":["../../src/lib/cleared-database-url.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,2DAA2D,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Sentinel written to `DATABASE_URL` to clear it, instead of `delete`.
3
+ *
4
+ * Keeps the value a string for consumers that type `DATABASE_URL` as required
5
+ * (so the library never has to fight their env typing), and is an invalid
6
+ * connection string so a test that forgot `neonTesting()` fails loudly rather
7
+ * than silently reaching a real database.
8
+ */
9
+ export const CLEARED_DATABASE_URL = `neon-testing: DATABASE_URL cleared, call neonTesting()`;
10
+ //# sourceMappingURL=cleared-database-url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cleared-database-url.js","sourceRoot":"","sources":["../../src/lib/cleared-database-url.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,wDAAwD,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Validates the expiresIn option
3
+ */
4
+ export declare function validateExpiresIn(expiresIn: number | null | undefined): void;
5
+ //# sourceMappingURL=expires-in.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expires-in.d.ts","sourceRoot":"","sources":["../../src/lib/expires-in.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,QAoBrE"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Validates the expiresIn option
3
+ */
4
+ export function validateExpiresIn(expiresIn) {
5
+ if (expiresIn === null) {
6
+ return;
7
+ }
8
+ if (expiresIn === undefined) {
9
+ return;
10
+ }
11
+ if (!Number.isInteger(expiresIn)) {
12
+ throw new Error("expiresIn must be an integer");
13
+ }
14
+ if (expiresIn <= 0) {
15
+ throw new Error("expiresIn must be a positive integer");
16
+ }
17
+ if (expiresIn > 2592000) {
18
+ throw new Error("expiresIn must not exceed 30 days (2,592,000 seconds)");
19
+ }
20
+ }
21
+ //# sourceMappingURL=expires-in.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expires-in.js","sourceRoot":"","sources":["../../src/lib/expires-in.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAoC;IACpE,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;QACvB,OAAO;IACT,CAAC;IAED,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,OAAO;IACT,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAClD,CAAC;IAED,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IAED,IAAI,SAAS,GAAG,OAAO,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;IAC3E,CAAC;AACH,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Create a lazy singleton from a factory function
3
+ */
4
+ export declare function lazySingleton<T>(factory: () => T): () => T;
5
+ //# sourceMappingURL=singleton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"singleton.d.ts","sourceRoot":"","sources":["../../src/lib/singleton.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,CAM1D"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Create a lazy singleton from a factory function
3
+ */
4
+ export function lazySingleton(factory) {
5
+ let instance;
6
+ return () => {
7
+ instance ??= factory();
8
+ return instance;
9
+ };
10
+ }
11
+ //# sourceMappingURL=singleton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"singleton.js","sourceRoot":"","sources":["../../src/lib/singleton.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,UAAU,aAAa,CAAI,OAAgB;IAC/C,IAAI,QAAuB,CAAC;IAC5B,OAAO,GAAG,EAAE;QACV,QAAQ,KAAK,OAAO,EAAE,CAAC;QACvB,OAAO,QAAQ,CAAC;IAClB,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Rewrite the `sslmode` (and related) query params on a Neon connection URI.
3
+ *
4
+ * Neon's API returns URIs with `sslmode=require`. In pg v9 /
5
+ * pg-connection-string v3 this mode will adopt libpq semantics (encrypt
6
+ * without CA verification) instead of today's effective `verify-full`.
7
+ */
8
+ export declare function applySslMode(uri: string, mode: "verify-full" | "require" | undefined): string;
9
+ //# sourceMappingURL=ssl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssl.d.ts","sourceRoot":"","sources":["../../src/lib/ssl.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,aAAa,GAAG,SAAS,GAAG,SAAS,GAC1C,MAAM,CAaR"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Rewrite the `sslmode` (and related) query params on a Neon connection URI.
3
+ *
4
+ * Neon's API returns URIs with `sslmode=require`. In pg v9 /
5
+ * pg-connection-string v3 this mode will adopt libpq semantics (encrypt
6
+ * without CA verification) instead of today's effective `verify-full`.
7
+ */
8
+ export function applySslMode(uri, mode) {
9
+ if (mode === undefined)
10
+ return uri;
11
+ const url = new URL(uri);
12
+ url.searchParams.set("sslmode", mode);
13
+ if (mode === "require") {
14
+ url.searchParams.set("uselibpqcompat", "true");
15
+ }
16
+ else {
17
+ url.searchParams.delete("uselibpqcompat");
18
+ }
19
+ return url.toString();
20
+ }
21
+ //# sourceMappingURL=ssl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssl.js","sourceRoot":"","sources":["../../src/lib/ssl.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAC1B,GAAW,EACX,IAA2C;IAE3C,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,GAAG,CAAC;IAEnC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACzB,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAEtC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;IACjD,CAAC;SAAM,CAAC;QACN,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IAC5C,CAAC;IAED,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;AACxB,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Reusable API call wrapper with automatic retry on 423 errors with exponential
3
+ * backoff
4
+ *
5
+ * https://neon.com/docs/reference/typescript-sdk#error-handling
6
+ * https://neon.com/docs/changelog/2022-07-20
7
+ */
8
+ export declare function withRetry<T>(fn: () => Promise<T>, options: {
9
+ maxRetries: number;
10
+ baseDelayMs: number;
11
+ }): Promise<T>;
12
+ //# sourceMappingURL=with-retry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"with-retry.d.ts","sourceRoot":"","sources":["../../src/lib/with-retry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAC/B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,EAAE;IACP,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB,GACA,OAAO,CAAC,CAAC,CAAC,CAyCZ"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Reusable API call wrapper with automatic retry on 423 errors with exponential
3
+ * backoff
4
+ *
5
+ * https://neon.com/docs/reference/typescript-sdk#error-handling
6
+ * https://neon.com/docs/changelog/2022-07-20
7
+ */
8
+ export async function withRetry(fn, options) {
9
+ if (!Number.isInteger(options.maxRetries) || options.maxRetries <= 0) {
10
+ throw new Error("maxRetries must be a positive integer");
11
+ }
12
+ if (!Number.isInteger(options.baseDelayMs) || options.baseDelayMs <= 0) {
13
+ throw new Error("baseDelayMs must be a positive integer");
14
+ }
15
+ for (let attempt = 1; attempt <= options.maxRetries; attempt++) {
16
+ try {
17
+ return await fn();
18
+ }
19
+ catch (error) {
20
+ const status = error?.response?.status;
21
+ // Retry on 423 (resource locked) with exponential backoff
22
+ if (status === 423 && attempt < options.maxRetries) {
23
+ const delay = options.baseDelayMs * Math.pow(2, attempt - 1);
24
+ console.log(`API call failed with 423, retrying in ${delay}ms (attempt ${attempt}/${options.maxRetries})`);
25
+ await new Promise((resolve) => setTimeout(resolve, delay));
26
+ continue;
27
+ }
28
+ // Surface Neon API error details that are otherwise buried in the Axios error
29
+ if (error?.response?.data?.code) {
30
+ const { code, message } = error.response.data;
31
+ throw new Error(`Neon API error - HTTP ${status} - ${code} - ${message}`, { cause: error });
32
+ }
33
+ // Non-API errors (network, timeouts, etc.) pass through as-is
34
+ throw error;
35
+ }
36
+ }
37
+ throw new Error("apiCallWithRetry reached unexpected end");
38
+ }
39
+ //# sourceMappingURL=with-retry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"with-retry.js","sourceRoot":"","sources":["../../src/lib/with-retry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,EAAoB,EACpB,OAGC;IAED,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC,UAAU,IAAI,CAAC,EAAE,CAAC;QACrE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC3D,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC,WAAW,IAAI,CAAC,EAAE,CAAC;QACvE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IAC5D,CAAC;IAED,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,OAAO,CAAC,UAAU,EAAE,OAAO,EAAE,EAAE,CAAC;QAC/D,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,EAAE,CAAC;QACpB,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,MAAM,MAAM,GAAG,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC;YAEvC,0DAA0D;YAC1D,IAAI,MAAM,KAAK,GAAG,IAAI,OAAO,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;gBACnD,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC;gBAE7D,OAAO,CAAC,GAAG,CACT,yCAAyC,KAAK,eAAe,OAAO,IAAI,OAAO,CAAC,UAAU,GAAG,CAC9F,CAAC;gBACF,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;gBAE3D,SAAS;YACX,CAAC;YAED,8EAA8E;YAC9E,IAAI,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;gBAChC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAC9C,MAAM,IAAI,KAAK,CACb,yBAAyB,MAAM,MAAM,IAAI,MAAM,OAAO,EAAE,EACxD,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAC;YACJ,CAAC;YAED,8DAA8D;YAC9D,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;AAC7D,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Error handler: Suppress Neon WebSocket "Connection terminated unexpectedly"
3
+ * error
4
+ */
5
+ export declare const neonWsErrorHandler: (error: Error) => void;
6
+ //# sourceMappingURL=ws-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws-error.d.ts","sourceRoot":"","sources":["../../src/lib/ws-error.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAO,KAAK,SAY9C,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Error handler: Suppress Neon WebSocket "Connection terminated unexpectedly"
3
+ * error
4
+ */
5
+ export const neonWsErrorHandler = (error) => {
6
+ const isNeonWsClose = error.message.includes("Connection terminated unexpectedly") &&
7
+ error.stack?.includes("@neondatabase/serverless");
8
+ if (isNeonWsClose) {
9
+ // Swallow this specific Neon WS termination error
10
+ return;
11
+ }
12
+ // For any other error, detach and rethrow
13
+ throw error;
14
+ };
15
+ //# sourceMappingURL=ws-error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws-error.js","sourceRoot":"","sources":["../../src/lib/ws-error.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,KAAY,EAAE,EAAE;IACjD,MAAM,aAAa,GACjB,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,oCAAoC,CAAC;QAC5D,KAAK,CAAC,KAAK,EAAE,QAAQ,CAAC,0BAA0B,CAAC,CAAC;IAEpD,IAAI,aAAa,EAAE,CAAC;QAClB,kDAAkD;QAClD,OAAO;IACT,CAAC;IAED,0CAA0C;IAC1C,MAAM,KAAK,CAAC;AACd,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":""}
package/dist/setup.js ADDED
@@ -0,0 +1,9 @@
1
+ import { CLEARED_DATABASE_URL } from "./lib/cleared-database-url.js";
2
+ // Overwrites any existing DATABASE_URL so a test file that forgot to call
3
+ // `neonTesting()` can't reach the pre-configured database. Loaded as a
4
+ // test-runner preload (Vitest `setupFiles` / Bun `bunfig` `preload`).
5
+ if (process.env.DATABASE_URL && process.env.NEON_TESTING_DEBUG === "true") {
6
+ console.debug("[neon-testing] Overwriting existing DATABASE_URL");
7
+ }
8
+ process.env.DATABASE_URL = CLEARED_DATABASE_URL;
9
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAErE,0EAA0E;AAC1E,uEAAuE;AACvE,sEAAsE;AACtE,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,KAAK,MAAM,EAAE,CAAC;IAC1E,OAAO,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAAC;AACpE,CAAC;AAED,OAAO,CAAC,GAAG,CAAC,YAAY,GAAG,oBAAoB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { lazySingleton } from "./lib/singleton.js";
2
+ export { createBarrier } from "./lib/barrier.js";
3
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/utils.js ADDED
@@ -0,0 +1,3 @@
1
+ export { lazySingleton } from "./lib/singleton.js";
2
+ export { createBarrier } from "./lib/barrier.js";
3
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { MakeNeonTestingOptions } from "./core.js";
2
+ export type { MakeNeonTestingOptions, NeonTestingOptions } from "./core.js";
3
+ /**
4
+ * Create a Neon test-branch factory wired to Vitest's lifecycle hooks.
5
+ */
6
+ export declare function makeNeonTesting(options: MakeNeonTestingOptions): {
7
+ (overrides?: import("./core.js").NeonTestingOptions): () => import("@neondatabase/api-client").Branch | undefined;
8
+ deleteAllTestBranches: () => Promise<void>;
9
+ api: import("@neondatabase/api-client").Api<unknown>;
10
+ };
11
+ //# sourceMappingURL=vitest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vitest.d.ts","sourceRoot":"","sources":["../src/vitest.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAExD,YAAY,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAE5E;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB;;;;EAE9D"}
package/dist/vitest.js ADDED
@@ -0,0 +1,9 @@
1
+ import { beforeAll, afterAll } from "vitest";
2
+ import { makeNeonTestingCore } from "./core.js";
3
+ /**
4
+ * Create a Neon test-branch factory wired to Vitest's lifecycle hooks.
5
+ */
6
+ export function makeNeonTesting(options) {
7
+ return makeNeonTestingCore({ ...options, hooks: { beforeAll, afterAll } });
8
+ }
9
+ //# sourceMappingURL=vitest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vitest.js","sourceRoot":"","sources":["../src/vitest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAKhD;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,OAA+B;IAC7D,OAAO,mBAAmB,CAAC,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;AAC7E,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neon-testing",
3
- "version": "3.0.0-beta.0",
3
+ "version": "3.0.0-beta.1",
4
4
  "description": "A testing utility for seamless integration tests with Neon Postgres, for Vitest and Bun Test.",
5
5
  "keywords": [
6
6
  "neon",
@@ -17,13 +17,29 @@
17
17
  "bugs": "https://github.com/starmode-base/neon-testing/issues",
18
18
  "type": "module",
19
19
  "exports": {
20
- "./core": "./src/core.ts",
21
- "./vitest": "./src/vitest.ts",
22
- "./bun": "./src/bun.ts",
23
- "./utils": "./src/utils.ts",
24
- "./setup": "./src/setup.ts"
20
+ "./core": {
21
+ "types": "./dist/core.d.ts",
22
+ "import": "./dist/core.js"
23
+ },
24
+ "./vitest": {
25
+ "types": "./dist/vitest.d.ts",
26
+ "import": "./dist/vitest.js"
27
+ },
28
+ "./bun": {
29
+ "types": "./dist/bun.d.ts",
30
+ "import": "./dist/bun.js"
31
+ },
32
+ "./utils": {
33
+ "types": "./dist/utils.d.ts",
34
+ "import": "./dist/utils.js"
35
+ },
36
+ "./setup": {
37
+ "types": "./dist/setup.d.ts",
38
+ "import": "./dist/setup.js"
39
+ }
25
40
  },
26
41
  "files": [
42
+ "dist",
27
43
  "src",
28
44
  "!src/**/*.test.ts"
29
45
  ],
@@ -31,8 +47,10 @@
31
47
  "node": ">=22"
32
48
  },
33
49
  "scripts": {
34
- "dev": "tsc --watch",
35
- "check": "prettier --check . && tsc && bun run check:drift && bun run test",
50
+ "dev": "tsc -p tsconfig.build.json --watch",
51
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
52
+ "prepublishOnly": "bun run build",
53
+ "check": "prettier --check . && bun run build && tsc && bun run check:drift && bun run test",
36
54
  "check:drift": "diff -r -x neon-testing.ts tests-bun tests-vitest",
37
55
  "format": "prettier --write .",
38
56
  "test": "vitest run && bun test",
@@ -45,8 +63,8 @@
45
63
  "test:bun": "bun test",
46
64
  "test:bun:watch": "bun test --watch",
47
65
  "skills:update": "bunx skills add neondatabase/agent-skills",
48
- "clean:soft": "rm -rf node_modules && bun install",
49
- "clean:hard": "rm -rf node_modules bun.lock && bun install",
66
+ "clean:soft": "rm -rf node_modules dist && bun install",
67
+ "clean:hard": "rm -rf node_modules dist bun.lock && bun install",
50
68
  "release:patch": "bun pm version patch --message 'release: v%s'",
51
69
  "release:minor": "bun pm version minor --message 'release: v%s'",
52
70
  "release:major": "bun pm version major --message 'release: v%s'",
@@ -61,9 +79,13 @@
61
79
  "@neondatabase/api-client": "2.7.1"
62
80
  },
63
81
  "peerDependencies": {
82
+ "@neondatabase/serverless": "^1",
64
83
  "vitest": "^3 || ^4"
65
84
  },
66
85
  "peerDependenciesMeta": {
86
+ "@neondatabase/serverless": {
87
+ "optional": true
88
+ },
67
89
  "vitest": {
68
90
  "optional": true
69
91
  }
package/src/bun.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  /// <reference types="bun" />
2
2
  import { beforeAll, afterAll } from "bun:test";
3
- import { makeNeonTestingCore } from "./core";
4
- import type { MakeNeonTestingOptions } from "./core";
3
+ import { makeNeonTestingCore } from "./core.js";
4
+ import type { MakeNeonTestingOptions } from "./core.js";
5
5
 
6
- export type { MakeNeonTestingOptions, NeonTestingOptions } from "./core";
6
+ export type { MakeNeonTestingOptions, NeonTestingOptions } from "./core.js";
7
7
 
8
8
  /**
9
9
  * Create a Neon test-branch factory wired to Bun's lifecycle hooks.
package/src/core.ts CHANGED
@@ -6,10 +6,11 @@ import {
6
6
  EndpointType,
7
7
  type Branch,
8
8
  } from "@neondatabase/api-client";
9
- import { applySslMode } from "./lib/ssl";
10
- import { validateExpiresIn } from "./lib/expires-in";
11
- import { neonWsErrorHandler } from "./lib/ws-error";
12
- import { withRetry } from "./lib/with-retry";
9
+ import { applySslMode } from "./lib/ssl.js";
10
+ import { validateExpiresIn } from "./lib/expires-in.js";
11
+ import { neonWsErrorHandler } from "./lib/ws-error.js";
12
+ import { withRetry } from "./lib/with-retry.js";
13
+ import { CLEARED_DATABASE_URL } from "./lib/cleared-database-url.js";
13
14
 
14
15
  /**
15
16
  * Test-runner lifecycle hooks
@@ -340,7 +341,9 @@ export function makeNeonTestingCore(
340
341
  });
341
342
 
342
343
  factoryOptions.hooks.afterAll(async () => {
343
- delete process.env.DATABASE_URL;
344
+ // Overwrite (not `delete`) so DATABASE_URL no longer points at the test
345
+ // branch; see CLEARED_DATABASE_URL for why a sentinel rather than delete.
346
+ process.env.DATABASE_URL = CLEARED_DATABASE_URL;
344
347
 
345
348
  // Close all tracked Neon WebSocket connections before deleting the branch
346
349
  if (options.autoCloseWebSockets && neonSockets) {
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Sentinel written to `DATABASE_URL` to clear it, instead of `delete`.
3
+ *
4
+ * Keeps the value a string for consumers that type `DATABASE_URL` as required
5
+ * (so the library never has to fight their env typing), and is an invalid
6
+ * connection string so a test that forgot `neonTesting()` fails loudly rather
7
+ * than silently reaching a real database.
8
+ */
9
+ export const CLEARED_DATABASE_URL = `neon-testing: DATABASE_URL cleared, call neonTesting()`;
package/src/setup.ts CHANGED
@@ -1,8 +1,10 @@
1
- // Removes any existing DATABASE_URL so a test file that forgot to call
1
+ import { CLEARED_DATABASE_URL } from "./lib/cleared-database-url.js";
2
+
3
+ // Overwrites any existing DATABASE_URL so a test file that forgot to call
2
4
  // `neonTesting()` can't reach the pre-configured database. Loaded as a
3
5
  // test-runner preload (Vitest `setupFiles` / Bun `bunfig` `preload`).
4
6
  if (process.env.DATABASE_URL && process.env.NEON_TESTING_DEBUG === "true") {
5
- console.debug("[neon-testing] Clearing existing DATABASE_URL");
7
+ console.debug("[neon-testing] Overwriting existing DATABASE_URL");
6
8
  }
7
9
 
8
- delete process.env.DATABASE_URL;
10
+ process.env.DATABASE_URL = CLEARED_DATABASE_URL;
package/src/utils.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { lazySingleton } from "./lib/singleton";
2
- export { createBarrier } from "./lib/barrier";
1
+ export { lazySingleton } from "./lib/singleton.js";
2
+ export { createBarrier } from "./lib/barrier.js";
package/src/vitest.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { beforeAll, afterAll } from "vitest";
2
- import { makeNeonTestingCore } from "./core";
3
- import type { MakeNeonTestingOptions } from "./core";
2
+ import { makeNeonTestingCore } from "./core.js";
3
+ import type { MakeNeonTestingOptions } from "./core.js";
4
4
 
5
- export type { MakeNeonTestingOptions, NeonTestingOptions } from "./core";
5
+ export type { MakeNeonTestingOptions, NeonTestingOptions } from "./core.js";
6
6
 
7
7
  /**
8
8
  * Create a Neon test-branch factory wired to Vitest's lifecycle hooks.