deepline 0.1.243 → 0.1.245

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 (71) hide show
  1. package/README.md +1 -1
  2. package/dist/bundling-sources/sdk/src/plays/bundle-play-file.ts +5 -25
  3. package/dist/bundling-sources/sdk/src/release.ts +25 -26
  4. package/dist/bundling-sources/sdk/src/types.ts +1 -1
  5. package/dist/bundling-sources/shared_libs/play-runtime/backend.ts +2 -21
  6. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +5 -6
  7. package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +6 -13
  8. package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +9 -26
  9. package/dist/bundling-sources/shared_libs/play-runtime/internal-step-ids.ts +1 -2
  10. package/dist/bundling-sources/shared_libs/play-runtime/map-row-identity.ts +1 -1
  11. package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +191 -29
  12. package/dist/bundling-sources/shared_libs/play-runtime/play-latency-trace.ts +1 -1
  13. package/dist/bundling-sources/shared_libs/play-runtime/providers.ts +1 -36
  14. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +2 -3
  15. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/index.ts +0 -7
  16. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +1 -12
  17. package/dist/bundling-sources/shared_libs/play-runtime/runtime-pg-driver-pg.ts +2 -3
  18. package/dist/bundling-sources/shared_libs/play-runtime/runtime-pg-driver.ts +5 -15
  19. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +6 -21
  20. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +3 -4
  21. package/dist/bundling-sources/shared_libs/plays/artifact-kind-guard.ts +16 -29
  22. package/dist/bundling-sources/shared_libs/plays/artifact-types.ts +4 -8
  23. package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +21 -712
  24. package/dist/bundling-sources/shared_libs/plays/bundling/limits.ts +1 -18
  25. package/dist/cli/index.js +28 -71
  26. package/dist/cli/index.mjs +28 -71
  27. package/dist/{compiler-manifest-CYcwzSOJ.d.ts → compiler-manifest-DlE7dnRm.d.mts} +2 -8
  28. package/dist/{compiler-manifest-CYcwzSOJ.d.mts → compiler-manifest-DlE7dnRm.d.ts} +2 -8
  29. package/dist/index.d.mts +2 -2
  30. package/dist/index.d.ts +2 -2
  31. package/dist/index.js +23 -22
  32. package/dist/index.mjs +23 -22
  33. package/dist/plays/bundle-play-file.d.mts +7 -22
  34. package/dist/plays/bundle-play-file.d.ts +7 -22
  35. package/dist/plays/bundle-play-file.mjs +26 -614
  36. package/package.json +2 -2
  37. package/dist/bundling-sources/apps/play-runner-workers/src/coordinator-entry.ts +0 -4504
  38. package/dist/bundling-sources/apps/play-runner-workers/src/dedup-do.ts +0 -1386
  39. package/dist/bundling-sources/apps/play-runner-workers/src/durable-object-deploy-handoff.ts +0 -30
  40. package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +0 -8385
  41. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/README.md +0 -21
  42. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/batching.ts +0 -251
  43. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/coordinator-progress.ts +0 -289
  44. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/csv-rows.ts +0 -85
  45. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/customer-console.ts +0 -23
  46. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/dataset-handles.ts +0 -528
  47. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/harness-receipt-store.ts +0 -229
  48. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/ledger-event-batches.ts +0 -81
  49. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/live-progress.ts +0 -22
  50. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/map-chunk-plan.ts +0 -920
  51. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/map-latency-profile.ts +0 -90
  52. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/output-datasets.ts +0 -475
  53. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/receipts.ts +0 -570
  54. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/result-dataset-persistence.ts +0 -50
  55. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/row-isolation.ts +0 -5
  56. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/run-work-dispatcher.ts +0 -519
  57. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-batch.ts +0 -107
  58. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-dispatch.ts +0 -2541
  59. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-http-errors.ts +0 -7
  60. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-receipts.ts +0 -227
  61. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/work-budget.ts +0 -130
  62. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/worker-platform-budget.ts +0 -39
  63. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/workflow-preview.ts +0 -26
  64. package/dist/bundling-sources/apps/play-runner-workers/src/runtime-api-retry.ts +0 -23
  65. package/dist/bundling-sources/apps/play-runner-workers/src/workflow-instance-create.ts +0 -47
  66. package/dist/bundling-sources/apps/play-runner-workers/src/workflow-retry-state.ts +0 -167
  67. package/dist/bundling-sources/apps/play-runner-workers/src/workflow-retry.ts +0 -91
  68. package/dist/bundling-sources/sdk/src/plays/harness-stub.ts +0 -329
  69. package/dist/bundling-sources/sdk/src/worker-play-entry.ts +0 -240
  70. package/dist/bundling-sources/shared_libs/play-runtime/governor/coordinator-rate-state-backend.ts +0 -252
  71. package/dist/bundling-sources/shared_libs/play-runtime/runtime-pg-driver-neon-serverless.ts +0 -217
@@ -1,252 +0,0 @@
1
- import {
2
- noopPacingPermit,
3
- type PacingPermit,
4
- type PacingRule,
5
- type RateStateBackend,
6
- } from './rate-state-backend';
7
-
8
- /**
9
- * Distributed Rate State Backend for the `esm_workers` substrate.
10
- *
11
- * On Cloudflare a single play run fans child plays and map rows across many
12
- * V8 isolates, so the per-`(org, provider)` request window cannot be
13
- * process-local — each isolate would otherwise pace against its own private
14
- * counter and the org could blow past a provider's real limit by the number of
15
- * isolates. This backend makes the window GLOBAL by RPCing the coordinator
16
- * Durable Object addressed per bucket (`idFromName('rate:<orgId>:<provider>')`).
17
- * The DO is single-threaded, so it runs the same sliding-window algorithm as
18
- * `InMemoryRateStateBackend` correctly for all isolates at once.
19
- *
20
- * Latency: a full DO round-trip on every outbound tool call would tax the
21
- * hello-world latency baseline. Instead the backend LEASES SMALL PERMIT BLOCKS:
22
- * one `/rate-acquire` round-trip debits up to {@link LEASE_BLOCK_SIZE} permits
23
- * from the global window, and subsequent acquires draw from the local block
24
- * until it is exhausted or its rule-scoped TTL expires. This bounds round-trips
25
- * to roughly `calls / LEASE_BLOCK_SIZE` while keeping over-issuance bounded by
26
- * one block per isolate per window.
27
- *
28
- * Fail-open: if the coordinator is unreachable the backend logs once and
29
- * PROCEEDS (grants the permit) rather than stalling the run, matching the
30
- * semantics of `src/lib/redis/customer-rate-limiter.ts` — a degraded limiter
31
- * must never become an availability outage. The Governor's global
32
- * tool-concurrency semaphore remains the unconditional backstop.
33
- */
34
-
35
- /** Permits leased per round-trip. Tuned to amortize the DO hop, not to batch. */
36
- const LEASE_BLOCK_SIZE = 16;
37
- /**
38
- * Min/max age of a leased block. A leased permit debited the global window
39
- * already, but if it is held well past the provider window it could let an
40
- * isolate run ahead of a rolled-over window. The effective TTL is derived from
41
- * the shortest rule window so low-RPS pure-rate providers can draw already-
42
- * debited permits throughout a normal window instead of burning most blocks.
43
- */
44
- const LEASE_BLOCK_MIN_TTL_MS = 250;
45
- const LEASE_BLOCK_MAX_TTL_MS = 5_000;
46
- /** Cap one coordinator-provided sleep hint; total wait is bounded by run timeout. */
47
- const MAX_ACQUIRE_SLEEP_MS = 5_000;
48
-
49
- export interface CoordinatorRatePort {
50
- /**
51
- * Lease up to `requested` request-window permits for `bucketId` under all
52
- * `rules` from the coordinator DO. Returns how many were `granted` (0 when the
53
- * window is saturated) and a `waitMs` hint before retrying.
54
- */
55
- rateAcquire(input: {
56
- bucketId: string;
57
- rules: PacingRule[];
58
- requested: number;
59
- }): Promise<{ granted: number; waitMs: number }>;
60
- /** Feed a Retry-After cooldown back into the global bucket. */
61
- ratePenalize(input: { bucketId: string; cooldownMs: number }): Promise<void>;
62
- }
63
-
64
- interface LeasedBlock {
65
- remaining: number;
66
- expiresAt: number;
67
- /** Stable signature of the rules this block was leased under. */
68
- rulesKey: string;
69
- }
70
-
71
- interface Options {
72
- now?: () => number;
73
- sleep?: (ms: number) => Promise<void>;
74
- onDegraded?: (info: { bucketId: string; error: string }) => void;
75
- }
76
-
77
- function rulesSignature(rules: readonly PacingRule[]): string {
78
- return [...rules]
79
- .map(
80
- (rule) =>
81
- `${rule.ruleId}:${rule.requestsPerWindow}:${rule.windowMs}:${rule.maxConcurrency ?? ''}`,
82
- )
83
- .sort()
84
- .join('|');
85
- }
86
-
87
- export class CoordinatorRateStateBackend implements RateStateBackend {
88
- private readonly port: CoordinatorRatePort;
89
- private readonly now: () => number;
90
- private readonly sleep: (ms: number) => Promise<void>;
91
- private readonly onDegraded: (info: {
92
- bucketId: string;
93
- error: string;
94
- }) => void;
95
- private readonly blocks = new Map<string, LeasedBlock>();
96
- private degradedLogged = false;
97
-
98
- constructor(port: CoordinatorRatePort, options: Options = {}) {
99
- this.port = port;
100
- this.now = options.now ?? (() => Date.now());
101
- this.sleep =
102
- options.sleep ??
103
- ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms)));
104
- this.onDegraded =
105
- options.onDegraded ??
106
- ((info) => {
107
- if (this.degradedLogged) return;
108
- this.degradedLogged = true;
109
- console.warn('[coordinator-rate-state] acquire failed open', info);
110
- });
111
- }
112
-
113
- async acquire(input: {
114
- bucketId: string;
115
- rules: readonly PacingRule[];
116
- signal?: AbortSignal;
117
- }): Promise<PacingPermit> {
118
- const { bucketId, rules, signal } = input;
119
- if (rules.length === 0) {
120
- return noopPacingPermit();
121
- }
122
- const rulesKey = rulesSignature(rules);
123
-
124
- // Draw from a still-valid local block first — no round-trip.
125
- if (this.drawFromBlock(bucketId, rulesKey)) {
126
- return noopPacingPermit();
127
- }
128
-
129
- while (true) {
130
- if (signal?.aborted) {
131
- throw signal.reason instanceof Error
132
- ? signal.reason
133
- : new Error('Rate-state acquire aborted.');
134
- }
135
- let response: { granted: number; waitMs: number };
136
- try {
137
- response = await this.port.rateAcquire({
138
- bucketId,
139
- rules: [...rules],
140
- requested: LEASE_BLOCK_SIZE,
141
- });
142
- } catch (error) {
143
- // Fail open: a degraded coordinator must not stall the run.
144
- this.onDegraded({
145
- bucketId,
146
- error: error instanceof Error ? error.message : String(error),
147
- });
148
- return noopPacingPermit();
149
- }
150
- if (response.granted > 0) {
151
- // Consume one for this call; cache the rest as a short-lived block.
152
- const remaining = response.granted - 1;
153
- if (remaining > 0) {
154
- this.mergeBlock(bucketId, remaining, rulesKey, rules);
155
- }
156
- return noopPacingPermit();
157
- }
158
- // Window saturated. Park for the hint, then re-acquire. This is not a
159
- // degraded coordinator: the limiter answered and said "wait", so granting
160
- // here would violate the provider request-rate contract. Overall wait is
161
- // bounded by the play runtime deadline / abort signal.
162
- const waitMs = Math.max(
163
- 1,
164
- Math.min(response.waitMs, MAX_ACQUIRE_SLEEP_MS),
165
- );
166
- await this.sleep(waitMs);
167
- }
168
- }
169
-
170
- async penalize(input: {
171
- bucketId: string;
172
- cooldownMs: number;
173
- }): Promise<void> {
174
- if (input.cooldownMs <= 0) return;
175
- // Drop any cached block for this bucket so the cooldown takes effect on the
176
- // very next acquire instead of being masked by already-leased permits.
177
- this.blocks.delete(input.bucketId);
178
- try {
179
- await this.port.ratePenalize({
180
- bucketId: input.bucketId,
181
- cooldownMs: input.cooldownMs,
182
- });
183
- } catch (error) {
184
- this.onDegraded({
185
- bucketId: input.bucketId,
186
- error: error instanceof Error ? error.message : String(error),
187
- });
188
- throw error;
189
- }
190
- }
191
-
192
- /**
193
- * Add freshly-leased permits to the bucket's block instead of overwriting it.
194
- * Two concurrent acquires can both miss the local block and both round-trip;
195
- * each debited the global window, so the DO already issued both blocks'
196
- * permits. Overwriting would drop one set — under-issuance that wastes window
197
- * capacity and over-throttles. Merging preserves every debited permit:
198
- * - same rulesKey + still valid → sum remaining, keep the earlier expiry so
199
- * the merged block never outlives the older lease's sub-window bound.
200
- * - missing / stale / different rules → start fresh from this lease.
201
- */
202
- private mergeBlock(
203
- bucketId: string,
204
- remaining: number,
205
- rulesKey: string,
206
- rules: readonly PacingRule[],
207
- ): void {
208
- const freshExpiresAt = this.now() + leasedBlockTtlMs(rules);
209
- const existing = this.blocks.get(bucketId);
210
- if (
211
- existing &&
212
- existing.rulesKey === rulesKey &&
213
- existing.expiresAt > this.now()
214
- ) {
215
- existing.remaining += remaining;
216
- existing.expiresAt = Math.min(existing.expiresAt, freshExpiresAt);
217
- return;
218
- }
219
- this.blocks.set(bucketId, {
220
- remaining,
221
- expiresAt: freshExpiresAt,
222
- rulesKey,
223
- });
224
- }
225
-
226
- private drawFromBlock(bucketId: string, rulesKey: string): boolean {
227
- const block = this.blocks.get(bucketId);
228
- if (!block) return false;
229
- if (block.rulesKey !== rulesKey || block.expiresAt <= this.now()) {
230
- this.blocks.delete(bucketId);
231
- return false;
232
- }
233
- block.remaining -= 1;
234
- if (block.remaining <= 0) {
235
- this.blocks.delete(bucketId);
236
- }
237
- return true;
238
- }
239
- }
240
-
241
- function leasedBlockTtlMs(rules: readonly PacingRule[]): number {
242
- const shortestWindowMs = Math.min(
243
- ...rules
244
- .map((rule) => rule.windowMs)
245
- .filter((windowMs) => Number.isFinite(windowMs) && windowMs > 0),
246
- );
247
- if (!Number.isFinite(shortestWindowMs)) return LEASE_BLOCK_MIN_TTL_MS;
248
- return Math.max(
249
- LEASE_BLOCK_MIN_TTL_MS,
250
- Math.min(LEASE_BLOCK_MAX_TTL_MS, Math.floor(shortestWindowMs)),
251
- );
252
- }
@@ -1,217 +0,0 @@
1
- /**
2
- * `@neondatabase/serverless` Pool factory. Installed at boot by the
3
- * workers_edge dynamic Worker harness. The serverless driver speaks the
4
- * same Postgres wire protocol as `pg` but uses Neon's WebSocket proxy
5
- * (or HTTP endpoint) so it works inside the workerd V8 isolate where raw
6
- * TCP is unavailable.
7
- *
8
- * ## Lazy loading (the actual implementation)
9
- *
10
- * `@neondatabase/serverless` is ~143 KB minified — a non-trivial fraction
11
- * of every per-graphHash play Worker bundle. The harness's
12
- * `installNeonServerlessRuntimePoolDriver()` fires unconditionally at
13
- * isolate startup, but the heavy module is loaded ON FIRST `pool.connect()`
14
- * via dynamic `import('@neondatabase/serverless')`. Plays that never open
15
- * a Postgres pool (csv-only, computation-only, tool-only plays) skip the
16
- * import entirely, even though the driver registration ran at boot.
17
- *
18
- * Concretely:
19
- * - Bundle still contains the dynamic-import path → `@neondatabase/serverless`
20
- * is still in the per-play bundle (esbuild emits it as a deferred chunk
21
- * in `format: 'esm'`, but workerd inlines all chunks for a Worker —
22
- * so the bytes are still in the worker's compiled code).
23
- * - V8 PARSE / COMPILE for those bytes is also deferred until the first
24
- * `import()` call resolves; modern V8 does parse-on-demand for ESM
25
- * dynamic imports. So plays that never connect skip the parse cost.
26
- * - Plays that DO connect pay the import cost once per isolate, then
27
- * reuse the cached module + pool for the rest of the run.
28
- *
29
- * If you change this file, double-check by:
30
- * 1. Bundling a play that doesn't use `ctx.dataset` / postgres ops
31
- * (e.g. tests/v2-plays/plays/03-tool-basic.play.ts).
32
- * 2. Confirm the resulting bundle still references
33
- * `@neondatabase/serverless` (the dynamic import path is preserved
34
- * as a string), but no top-level `Pool` symbol is constructed.
35
- * 3. Confirm that creating an actual pool via `createRuntimePool` in
36
- * a test still resolves and returns a working client.
37
- *
38
- * Why we don't just `external: ['@neondatabase/serverless']` in
39
- * bundle-play-file.ts:
40
- * The `@neondatabase/serverless` package isn't provided by the
41
- * workerd runtime — marking it external would crash the Worker on
42
- * first import lookup. The lazy dynamic import is the right pattern:
43
- * esbuild bundles the module, but its CODE STARTUP COST (parse +
44
- * compile) is paid only when the import resolves.
45
- */
46
-
47
- import {
48
- registerRuntimePoolFactory,
49
- type RuntimePool,
50
- type RuntimePoolClient,
51
- } from './runtime-pg-driver';
52
-
53
- /**
54
- * Minimal local types for the Neon module's runtime surface.
55
- *
56
- * We INTENTIONALLY don't `import type { Pool, PoolClient } from
57
- * '@neondatabase/serverless'` here — that would force tsc to read the
58
- * package's full type tree on every typecheck, slowing the build, and
59
- * the package's overloaded `connect()` resolves through types in a way
60
- * that surfaces as `void` in some TS configurations.
61
- *
62
- * The minimal shape is just:
63
- * - Pool ctor takes a config bag
64
- * - Pool instance has connect() / end()
65
- * - PoolClient instance has query() / release()
66
- *
67
- * If Neon ever changes this surface, the runtime cast will fail loudly
68
- * the first time a play tries to use Postgres — which is exactly when
69
- * we want to find out.
70
- */
71
- interface NeonPoolClient {
72
- query<R = Record<string, unknown>>(
73
- text: string,
74
- params?: unknown[],
75
- ): Promise<{ rows: R[] }>;
76
- release(): void;
77
- }
78
-
79
- interface NeonPoolInstance {
80
- connect(): Promise<NeonPoolClient>;
81
- end(): Promise<void>;
82
- }
83
-
84
- interface NeonPoolConfig {
85
- connectionString: string;
86
- max?: number;
87
- idleTimeoutMillis?: number;
88
- connectionTimeoutMillis?: number;
89
- }
90
-
91
- interface NeonModule {
92
- Pool: new (config: NeonPoolConfig) => NeonPoolInstance;
93
- }
94
-
95
- function assertBindParams(params: unknown[] | undefined): void {
96
- if (params !== undefined && !Array.isArray(params)) {
97
- throw new Error(
98
- 'Neon runtime query bind parameters must be an array. Configure Neon client options at client construction, not on query().',
99
- );
100
- }
101
- }
102
-
103
- /**
104
- * Cached promise of the loaded Neon module. Once the first connect()
105
- * resolves, every subsequent connect on the same isolate hits this
106
- * cache instead of re-importing.
107
- *
108
- * We cache the PROMISE rather than the resolved module so concurrent
109
- * first-connect calls coalesce onto a single import — V8's module
110
- * loader is already idempotent for the same specifier, but caching at
111
- * this layer makes the contract obvious to readers.
112
- */
113
- let neonModulePromise: Promise<NeonModule> | null = null;
114
- function loadNeonModule(): Promise<NeonModule> {
115
- if (!neonModulePromise) {
116
- neonModulePromise =
117
- import('@neondatabase/serverless') as unknown as Promise<NeonModule>;
118
- }
119
- return neonModulePromise;
120
- }
121
-
122
- function wrapNeonClient(client: NeonPoolClient): RuntimePoolClient {
123
- return {
124
- query: <R extends Record<string, unknown> = Record<string, unknown>>(
125
- text: string,
126
- params?: unknown[],
127
- ) => {
128
- assertBindParams(params);
129
- return (
130
- client.query as unknown as (
131
- t: string,
132
- p?: unknown[],
133
- ) => Promise<{ rows: R[] }>
134
- )(text, params);
135
- },
136
- release: () => {
137
- client.release();
138
- },
139
- };
140
- }
141
-
142
- /**
143
- * Wrap a Neon `Pool` instance as our internal `RuntimePool` shape.
144
- * `connect()` and `end()` are async on both sides, so this is a thin
145
- * adapter — no logic, just type marshaling.
146
- */
147
- function wrapNeonPool(pool: NeonPoolInstance): RuntimePool {
148
- return {
149
- connect: async () =>
150
- wrapNeonClient((await pool.connect()) as unknown as NeonPoolClient),
151
- end: async () => {
152
- await pool.end();
153
- },
154
- };
155
- }
156
-
157
- /**
158
- * Install the Neon-backed pool factory. Called once at Worker isolate
159
- * boot — `registerRuntimePoolFactory` is idempotent and throws if a
160
- * different factory was already registered (build-config bug).
161
- *
162
- * The factory itself is SYNCHRONOUS (returns a `RuntimePool`) but the
163
- * underlying Neon Pool is created LAZILY: we capture the connection
164
- * config in a closure and instantiate the real `Pool` on first
165
- * `connect()`. Until then, no `@neondatabase/serverless` runtime code
166
- * runs in the isolate — V8 doesn't parse the dynamic-imported chunk
167
- * until the import() resolves.
168
- */
169
- export function installNeonServerlessRuntimePoolDriver(): void {
170
- registerRuntimePoolFactory(
171
- (input) => {
172
- // Captured lazily so a Pool is only constructed if the play actually
173
- // connects. Most plays that don't touch Postgres directly never even
174
- // reach this branch — the factory is registered but never invoked.
175
- let lazyPool: NeonPoolInstance | null = null;
176
-
177
- /**
178
- * Resolve the lazy Pool, creating it on first call.
179
- *
180
- * Race safety: workerd executes within a single JS event loop, so
181
- * two concurrent `connect()` calls on the SAME wrapper run their
182
- * "lazyPool == null" checks in turn — the first creates the Pool,
183
- * the second sees it. No locking needed.
184
- *
185
- * If pool creation fails (e.g. DNS, bad connection string), the
186
- * error propagates out of connect() to the caller. We don't cache
187
- * a failed pool so retries can succeed on transient failures.
188
- */
189
- async function ensurePool(): Promise<NeonPoolInstance> {
190
- if (lazyPool) return lazyPool;
191
- const neon = await loadNeonModule();
192
- const pool = new neon.Pool({
193
- connectionString: input.connectionString,
194
- max: input.maxConnections ?? 4,
195
- idleTimeoutMillis: input.idleTimeoutMs ?? 15_000,
196
- connectionTimeoutMillis: input.connectTimeoutMs ?? 10_000,
197
- });
198
- lazyPool = pool;
199
- return pool;
200
- }
201
-
202
- return {
203
- connect: async () => {
204
- const pool = await ensurePool();
205
- return wrapNeonClient(await pool.connect());
206
- },
207
- end: async () => {
208
- if (lazyPool) {
209
- await lazyPool.end();
210
- lazyPool = null;
211
- }
212
- },
213
- };
214
- },
215
- { canReuseAcrossRequests: false },
216
- );
217
- }