deepline 0.0.1 → 0.1.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.
- package/README.md +324 -0
- package/dist/cli/index.js +6750 -503
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +6735 -512
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.d.mts +2349 -32
- package/dist/index.d.ts +2349 -32
- package/dist/index.js +1631 -82
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1617 -83
- package/dist/index.mjs.map +1 -1
- package/dist/repo/apps/play-runner-workers/src/coordinator-entry.ts +3256 -0
- package/dist/repo/apps/play-runner-workers/src/dedup-do.ts +710 -0
- package/dist/repo/apps/play-runner-workers/src/entry.ts +5070 -0
- package/dist/repo/apps/play-runner-workers/src/runtime/README.md +21 -0
- package/dist/repo/apps/play-runner-workers/src/runtime/batching.ts +177 -0
- package/dist/repo/apps/play-runner-workers/src/runtime/execution-plan.ts +52 -0
- package/dist/repo/apps/play-runner-workers/src/runtime/tool-batch.ts +100 -0
- package/dist/repo/apps/play-runner-workers/src/runtime/tool-result.ts +184 -0
- package/dist/repo/sdk/src/cli/commands/auth.ts +482 -0
- package/dist/repo/sdk/src/cli/commands/billing.ts +188 -0
- package/dist/repo/sdk/src/cli/commands/csv.ts +123 -0
- package/dist/repo/sdk/src/cli/commands/db.ts +119 -0
- package/dist/repo/sdk/src/cli/commands/feedback.ts +40 -0
- package/dist/repo/sdk/src/cli/commands/org.ts +117 -0
- package/dist/repo/sdk/src/cli/commands/play.ts +3200 -0
- package/dist/repo/sdk/src/cli/commands/tools.ts +687 -0
- package/dist/repo/sdk/src/cli/dataset-stats.ts +341 -0
- package/dist/repo/sdk/src/cli/index.ts +138 -0
- package/dist/repo/sdk/src/cli/progress.ts +135 -0
- package/dist/repo/sdk/src/cli/trace.ts +61 -0
- package/dist/repo/sdk/src/cli/utils.ts +145 -0
- package/dist/repo/sdk/src/client.ts +1188 -0
- package/dist/repo/sdk/src/compat.ts +77 -0
- package/dist/repo/sdk/src/config.ts +285 -0
- package/dist/repo/sdk/src/errors.ts +125 -0
- package/dist/repo/sdk/src/http.ts +391 -0
- package/dist/repo/sdk/src/index.ts +139 -0
- package/dist/repo/sdk/src/play.ts +1330 -0
- package/dist/repo/sdk/src/plays/bundle-play-file.ts +133 -0
- package/dist/repo/sdk/src/plays/harness-stub.ts +210 -0
- package/dist/repo/sdk/src/plays/local-file-discovery.ts +326 -0
- package/dist/repo/sdk/src/tool-output.ts +489 -0
- package/dist/repo/sdk/src/types.ts +669 -0
- package/dist/repo/sdk/src/version.ts +2 -0
- package/dist/repo/sdk/src/worker-play-entry.ts +286 -0
- package/dist/repo/shared_libs/observability/node-tracing.ts +129 -0
- package/dist/repo/shared_libs/observability/tracing.ts +98 -0
- package/dist/repo/shared_libs/play-runtime/backend.ts +139 -0
- package/dist/repo/shared_libs/play-runtime/batch-runtime.ts +182 -0
- package/dist/repo/shared_libs/play-runtime/batching-types.ts +91 -0
- package/dist/repo/shared_libs/play-runtime/context.ts +3999 -0
- package/dist/repo/shared_libs/play-runtime/coordinator-headers.ts +78 -0
- package/dist/repo/shared_libs/play-runtime/ctx-contract.ts +250 -0
- package/dist/repo/shared_libs/play-runtime/ctx-types.ts +713 -0
- package/dist/repo/shared_libs/play-runtime/dataset-id.ts +10 -0
- package/dist/repo/shared_libs/play-runtime/db-session-crypto.ts +304 -0
- package/dist/repo/shared_libs/play-runtime/db-session.ts +462 -0
- package/dist/repo/shared_libs/play-runtime/dedup-backend.ts +0 -0
- package/dist/repo/shared_libs/play-runtime/default-batch-strategies.ts +124 -0
- package/dist/repo/shared_libs/play-runtime/execution-plan.ts +262 -0
- package/dist/repo/shared_libs/play-runtime/live-events.ts +214 -0
- package/dist/repo/shared_libs/play-runtime/live-state-contract.ts +50 -0
- package/dist/repo/shared_libs/play-runtime/map-execution-frame.ts +114 -0
- package/dist/repo/shared_libs/play-runtime/map-row-identity.ts +158 -0
- package/dist/repo/shared_libs/play-runtime/profiles.ts +90 -0
- package/dist/repo/shared_libs/play-runtime/progress-emitter.ts +172 -0
- package/dist/repo/shared_libs/play-runtime/protocol.ts +121 -0
- package/dist/repo/shared_libs/play-runtime/public-play-contract.ts +42 -0
- package/dist/repo/shared_libs/play-runtime/result-normalization.ts +33 -0
- package/dist/repo/shared_libs/play-runtime/runtime-actions.ts +208 -0
- package/dist/repo/shared_libs/play-runtime/runtime-api.ts +1873 -0
- package/dist/repo/shared_libs/play-runtime/runtime-constraints.ts +2 -0
- package/dist/repo/shared_libs/play-runtime/runtime-pg-driver-neon-serverless.ts +201 -0
- package/dist/repo/shared_libs/play-runtime/runtime-pg-driver-pg.ts +48 -0
- package/dist/repo/shared_libs/play-runtime/runtime-pg-driver.ts +84 -0
- package/dist/repo/shared_libs/play-runtime/scheduler-backend.ts +174 -0
- package/dist/repo/shared_libs/play-runtime/static-pipeline-types.ts +147 -0
- package/dist/repo/shared_libs/play-runtime/suspension.ts +68 -0
- package/dist/repo/shared_libs/play-runtime/tool-batch-executor.ts +146 -0
- package/dist/repo/shared_libs/play-runtime/tool-result.ts +387 -0
- package/dist/repo/shared_libs/play-runtime/tracing.ts +31 -0
- package/dist/repo/shared_libs/play-runtime/waterfall-replay.ts +75 -0
- package/dist/repo/shared_libs/play-runtime/worker-api-types.ts +140 -0
- package/dist/repo/shared_libs/plays/artifact-transport.ts +14 -0
- package/dist/repo/shared_libs/plays/artifact-types.ts +49 -0
- package/dist/repo/shared_libs/plays/bundling/index.ts +1346 -0
- package/dist/repo/shared_libs/plays/compiler-manifest.ts +186 -0
- package/dist/repo/shared_libs/plays/contracts.ts +51 -0
- package/dist/repo/shared_libs/plays/dataset.ts +308 -0
- package/dist/repo/shared_libs/plays/definition.ts +264 -0
- package/dist/repo/shared_libs/plays/file-refs.ts +11 -0
- package/dist/repo/shared_libs/plays/rate-limit-scheduler.ts +206 -0
- package/dist/repo/shared_libs/plays/resolve-static-pipeline.ts +164 -0
- package/dist/repo/shared_libs/plays/row-identity.ts +302 -0
- package/dist/repo/shared_libs/plays/runtime-validation.ts +415 -0
- package/dist/repo/shared_libs/plays/static-pipeline.ts +560 -0
- package/dist/repo/shared_libs/temporal/constants.ts +39 -0
- package/dist/repo/shared_libs/temporal/preview-config.ts +153 -0
- package/package.json +14 -12
|
@@ -0,0 +1,201 @@
|
|
|
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
|
+
/**
|
|
96
|
+
* Cached promise of the loaded Neon module. Once the first connect()
|
|
97
|
+
* resolves, every subsequent connect on the same isolate hits this
|
|
98
|
+
* cache instead of re-importing.
|
|
99
|
+
*
|
|
100
|
+
* We cache the PROMISE rather than the resolved module so concurrent
|
|
101
|
+
* first-connect calls coalesce onto a single import — V8's module
|
|
102
|
+
* loader is already idempotent for the same specifier, but caching at
|
|
103
|
+
* this layer makes the contract obvious to readers.
|
|
104
|
+
*/
|
|
105
|
+
let neonModulePromise: Promise<NeonModule> | null = null;
|
|
106
|
+
function loadNeonModule(): Promise<NeonModule> {
|
|
107
|
+
if (!neonModulePromise) {
|
|
108
|
+
neonModulePromise = import('@neondatabase/serverless') as unknown as Promise<NeonModule>;
|
|
109
|
+
}
|
|
110
|
+
return neonModulePromise;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function wrapNeonClient(client: NeonPoolClient): RuntimePoolClient {
|
|
114
|
+
return {
|
|
115
|
+
query: <R extends Record<string, unknown> = Record<string, unknown>>(
|
|
116
|
+
text: string,
|
|
117
|
+
params?: unknown[],
|
|
118
|
+
) =>
|
|
119
|
+
(client.query as unknown as (
|
|
120
|
+
t: string,
|
|
121
|
+
p?: unknown[],
|
|
122
|
+
) => Promise<{ rows: R[] }>)(text, params),
|
|
123
|
+
release: () => {
|
|
124
|
+
client.release();
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Wrap a Neon `Pool` instance as our internal `RuntimePool` shape.
|
|
131
|
+
* `connect()` and `end()` are async on both sides, so this is a thin
|
|
132
|
+
* adapter — no logic, just type marshaling.
|
|
133
|
+
*/
|
|
134
|
+
function wrapNeonPool(pool: NeonPoolInstance): RuntimePool {
|
|
135
|
+
return {
|
|
136
|
+
connect: async () =>
|
|
137
|
+
wrapNeonClient((await pool.connect()) as unknown as NeonPoolClient),
|
|
138
|
+
end: async () => {
|
|
139
|
+
await pool.end();
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Install the Neon-backed pool factory. Called once at Worker isolate
|
|
146
|
+
* boot — `registerRuntimePoolFactory` is idempotent and throws if a
|
|
147
|
+
* different factory was already registered (build-config bug).
|
|
148
|
+
*
|
|
149
|
+
* The factory itself is SYNCHRONOUS (returns a `RuntimePool`) but the
|
|
150
|
+
* underlying Neon Pool is created LAZILY: we capture the connection
|
|
151
|
+
* config in a closure and instantiate the real `Pool` on first
|
|
152
|
+
* `connect()`. Until then, no `@neondatabase/serverless` runtime code
|
|
153
|
+
* runs in the isolate — V8 doesn't parse the dynamic-imported chunk
|
|
154
|
+
* until the import() resolves.
|
|
155
|
+
*/
|
|
156
|
+
export function installNeonServerlessRuntimePoolDriver(): void {
|
|
157
|
+
registerRuntimePoolFactory((input) => {
|
|
158
|
+
// Captured lazily so a Pool is only constructed if the play actually
|
|
159
|
+
// connects. Most plays that don't touch Postgres directly never even
|
|
160
|
+
// reach this branch — the factory is registered but never invoked.
|
|
161
|
+
let lazyPool: NeonPoolInstance | null = null;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Resolve the lazy Pool, creating it on first call.
|
|
165
|
+
*
|
|
166
|
+
* Race safety: workerd executes within a single JS event loop, so
|
|
167
|
+
* two concurrent `connect()` calls on the SAME wrapper run their
|
|
168
|
+
* "lazyPool == null" checks in turn — the first creates the Pool,
|
|
169
|
+
* the second sees it. No locking needed.
|
|
170
|
+
*
|
|
171
|
+
* If pool creation fails (e.g. DNS, bad connection string), the
|
|
172
|
+
* error propagates out of connect() to the caller. We don't cache
|
|
173
|
+
* a failed pool so retries can succeed on transient failures.
|
|
174
|
+
*/
|
|
175
|
+
async function ensurePool(): Promise<NeonPoolInstance> {
|
|
176
|
+
if (lazyPool) return lazyPool;
|
|
177
|
+
const neon = await loadNeonModule();
|
|
178
|
+
const pool = new neon.Pool({
|
|
179
|
+
connectionString: input.connectionString,
|
|
180
|
+
max: input.maxConnections ?? 4,
|
|
181
|
+
idleTimeoutMillis: input.idleTimeoutMs ?? 15_000,
|
|
182
|
+
connectionTimeoutMillis: input.connectTimeoutMs ?? 10_000,
|
|
183
|
+
});
|
|
184
|
+
lazyPool = pool;
|
|
185
|
+
return pool;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
connect: async () => {
|
|
190
|
+
const pool = await ensurePool();
|
|
191
|
+
return wrapNeonClient(await pool.connect());
|
|
192
|
+
},
|
|
193
|
+
end: async () => {
|
|
194
|
+
if (lazyPool) {
|
|
195
|
+
await lazyPool.end();
|
|
196
|
+
lazyPool = null;
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
});
|
|
201
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pg-backed Pool factory. Installed at boot by the Node-runtime
|
|
3
|
+
* play-runner (Daytona/local). NOT used by the workers_edge harness — pg's
|
|
4
|
+
* raw TCP socket usage is banned in workerd.
|
|
5
|
+
*/
|
|
6
|
+
import { Pool, type PoolClient } from 'pg';
|
|
7
|
+
import {
|
|
8
|
+
registerRuntimePoolFactory,
|
|
9
|
+
type RuntimePool,
|
|
10
|
+
type RuntimePoolClient,
|
|
11
|
+
} from './runtime-pg-driver';
|
|
12
|
+
|
|
13
|
+
function wrapPgClient(client: PoolClient): RuntimePoolClient {
|
|
14
|
+
return {
|
|
15
|
+
query: <R extends Record<string, unknown> = Record<string, unknown>>(
|
|
16
|
+
text: string,
|
|
17
|
+
params?: unknown[],
|
|
18
|
+
) =>
|
|
19
|
+
(client.query as unknown as (
|
|
20
|
+
t: string,
|
|
21
|
+
p?: unknown[],
|
|
22
|
+
) => Promise<{ rows: R[] }>)(text, params).then((result) => ({
|
|
23
|
+
rows: result.rows,
|
|
24
|
+
})),
|
|
25
|
+
release: () => {
|
|
26
|
+
client.release();
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function wrapPgPool(pool: Pool): RuntimePool {
|
|
32
|
+
return {
|
|
33
|
+
connect: async () => wrapPgClient(await pool.connect()),
|
|
34
|
+
end: () => pool.end(),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function installPgRuntimePoolDriver(): void {
|
|
39
|
+
registerRuntimePoolFactory((input) => {
|
|
40
|
+
const pool = new Pool({
|
|
41
|
+
connectionString: input.connectionString,
|
|
42
|
+
max: input.maxConnections ?? 4,
|
|
43
|
+
idleTimeoutMillis: input.idleTimeoutMs ?? 15_000,
|
|
44
|
+
connectionTimeoutMillis: input.connectTimeoutMs ?? 10_000,
|
|
45
|
+
});
|
|
46
|
+
return wrapPgPool(pool);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Driver-agnostic Postgres pool/client surface used by the runtime data plane.
|
|
3
|
+
*
|
|
4
|
+
* The runtime data plane (`runtime-api.ts` direct-Neon writes) needs to run in
|
|
5
|
+
* two very different environments:
|
|
6
|
+
*
|
|
7
|
+
* - Node (Daytona/local play-runner) — uses the `pg` library, raw TCP to Neon.
|
|
8
|
+
* - Cloudflare Workers (workers_edge harness) — `pg` cannot bundle (raw TCP
|
|
9
|
+
* is banned at workerd boundary), so we use `@neondatabase/serverless`
|
|
10
|
+
* which speaks the same Postgres wire protocol over a WebSocket proxy.
|
|
11
|
+
*
|
|
12
|
+
* Rather than `if-platform` branches inside runtime-api.ts, each runtime
|
|
13
|
+
* registers its driver once at boot and the rest of the code uses the
|
|
14
|
+
* abstract `RuntimePool` / `RuntimePoolClient` types. Both `pg` and
|
|
15
|
+
* `@neondatabase/serverless` already expose the relevant subset of the
|
|
16
|
+
* `pg` API surface (`pool.connect()` → `client.query() / .release()`), so
|
|
17
|
+
* the wrappers are thin.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export interface RuntimePoolClient {
|
|
21
|
+
query<R extends Record<string, unknown> = Record<string, unknown>>(
|
|
22
|
+
text: string,
|
|
23
|
+
params?: unknown[],
|
|
24
|
+
): Promise<{ rows: R[] }>;
|
|
25
|
+
release(): void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface RuntimePool {
|
|
29
|
+
connect(): Promise<RuntimePoolClient>;
|
|
30
|
+
end(): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type RuntimePoolFactory = (input: {
|
|
34
|
+
connectionString: string;
|
|
35
|
+
/** Soft cap on simultaneous connections; defaults to 4. */
|
|
36
|
+
maxConnections?: number;
|
|
37
|
+
/** Idle timeout in ms before pool tears down a connection; defaults to 15000. */
|
|
38
|
+
idleTimeoutMs?: number;
|
|
39
|
+
/** Connect timeout in ms; defaults to 10000. */
|
|
40
|
+
connectTimeoutMs?: number;
|
|
41
|
+
}) => RuntimePool;
|
|
42
|
+
|
|
43
|
+
let registeredFactory: RuntimePoolFactory | null = null;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Install a Pool factory at process / Worker boot. Idempotent — calling twice
|
|
47
|
+
* with different factories will throw to prevent silent driver swaps that
|
|
48
|
+
* would otherwise drop in-flight pooled connections.
|
|
49
|
+
*/
|
|
50
|
+
export function registerRuntimePoolFactory(factory: RuntimePoolFactory): void {
|
|
51
|
+
if (registeredFactory && registeredFactory !== factory) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
'A different runtime Postgres pool factory is already registered. ' +
|
|
54
|
+
'This is a build-config bug — only one driver should be installed per process.',
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
registeredFactory = factory;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function isRuntimePoolFactoryRegistered(): boolean {
|
|
61
|
+
return registeredFactory !== null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function createRuntimePool(input: {
|
|
65
|
+
connectionString: string;
|
|
66
|
+
maxConnections?: number;
|
|
67
|
+
idleTimeoutMs?: number;
|
|
68
|
+
connectTimeoutMs?: number;
|
|
69
|
+
}): RuntimePool {
|
|
70
|
+
if (!registeredFactory) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
'No runtime Postgres pool factory registered. Call ' +
|
|
73
|
+
'installPgRuntimePoolDriver() (Node) or ' +
|
|
74
|
+
'installNeonServerlessRuntimePoolDriver() (Workers) at boot before ' +
|
|
75
|
+
'using runtime-api Postgres helpers.',
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return registeredFactory(input);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Test seam — clears the registered factory so a test can swap drivers. */
|
|
82
|
+
export function __resetRuntimePoolFactoryForTests(): void {
|
|
83
|
+
registeredFactory = null;
|
|
84
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scheduler-backend interface — owns play workflow lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* One of three pluggable axes (alongside runner-backends and dedup-backends).
|
|
5
|
+
* Selected per-run via PlayExecutionProfile.
|
|
6
|
+
*
|
|
7
|
+
* Temporal is the existing production scheduler. Cloudflare Workflows is the
|
|
8
|
+
* edge scheduler used by the workers_edge profile.
|
|
9
|
+
*
|
|
10
|
+
* Customer plays are unaffected — this is purely the orchestration layer.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
PlayCheckpoint,
|
|
15
|
+
PlayExecutionEvent,
|
|
16
|
+
PlayRowUpdate,
|
|
17
|
+
} from './ctx-types';
|
|
18
|
+
import type { ExecutionPlan } from './execution-plan';
|
|
19
|
+
import type { PlayRuntimeManifestMap } from '../plays/compiler-manifest';
|
|
20
|
+
|
|
21
|
+
export const PLAY_SCHEDULER_BACKENDS = {
|
|
22
|
+
temporal: 'temporal',
|
|
23
|
+
cfWorkflows: 'cf-workflows',
|
|
24
|
+
inProcess: 'in-process',
|
|
25
|
+
} as const;
|
|
26
|
+
|
|
27
|
+
export type PlaySchedulerBackendId =
|
|
28
|
+
(typeof PLAY_SCHEDULER_BACKENDS)[keyof typeof PLAY_SCHEDULER_BACKENDS];
|
|
29
|
+
|
|
30
|
+
export type PlayCallGovernanceSnapshot = {
|
|
31
|
+
rootRunId: string;
|
|
32
|
+
parentRunId: string;
|
|
33
|
+
parentPlayName: string;
|
|
34
|
+
key: string;
|
|
35
|
+
ancestryPlayIds: string[];
|
|
36
|
+
callDepth: number;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type PlaySchedulerSubmitInput = {
|
|
40
|
+
runId: string;
|
|
41
|
+
playId: string;
|
|
42
|
+
playName: string;
|
|
43
|
+
artifactStorageKey: string;
|
|
44
|
+
artifactHash: string;
|
|
45
|
+
graphHash: string;
|
|
46
|
+
input: Record<string, unknown>;
|
|
47
|
+
inputFile?: {
|
|
48
|
+
name?: string;
|
|
49
|
+
path?: string;
|
|
50
|
+
r2Key?: string;
|
|
51
|
+
storageKey?: string;
|
|
52
|
+
fileName?: string;
|
|
53
|
+
logicalPath?: string;
|
|
54
|
+
} | null;
|
|
55
|
+
inlineCsv?: { name: string; rows: Record<string, unknown>[] } | null;
|
|
56
|
+
/**
|
|
57
|
+
* Tiny input files (<= WORKERS_EDGE_INLINE_INPUT_MAX_BYTES) carried as raw
|
|
58
|
+
* base64 bytes through the scheduler submit so the cf-workflows backend can
|
|
59
|
+
* build inlineCsv without an R2 PUT+GET round trip. Mirrors the
|
|
60
|
+
* Temporal-side `inlineInputFile` fast path.
|
|
61
|
+
*/
|
|
62
|
+
inlineInputFile?: {
|
|
63
|
+
logicalPath: string;
|
|
64
|
+
fileName: string;
|
|
65
|
+
contentBase64: string;
|
|
66
|
+
contentType: string;
|
|
67
|
+
bytes: number;
|
|
68
|
+
} | null;
|
|
69
|
+
packagedFiles?: Array<{
|
|
70
|
+
playPath?: string;
|
|
71
|
+
logicalPath?: string;
|
|
72
|
+
fileName?: string;
|
|
73
|
+
storageKey: string;
|
|
74
|
+
contentType?: string;
|
|
75
|
+
bytes?: number;
|
|
76
|
+
inlineText?: string;
|
|
77
|
+
}> | null;
|
|
78
|
+
contractSnapshot?: unknown;
|
|
79
|
+
executionPlan?: ExecutionPlan | null;
|
|
80
|
+
childPlayManifests?: PlayRuntimeManifestMap | null;
|
|
81
|
+
playCallGovernance?: PlayCallGovernanceSnapshot | null;
|
|
82
|
+
/** Optional immutable Worker module source for local Dynamic Worker loading. */
|
|
83
|
+
dynamicWorkerCode?: string | null;
|
|
84
|
+
executorToken: string;
|
|
85
|
+
baseUrl: string;
|
|
86
|
+
orgId: string;
|
|
87
|
+
userEmail: string;
|
|
88
|
+
userId?: string | null;
|
|
89
|
+
/** runner backend to use for executing attempts */
|
|
90
|
+
runtimeBackend: string;
|
|
91
|
+
/** dedup backend for cross-attempt cross-process idempotency */
|
|
92
|
+
dedupBackend: string;
|
|
93
|
+
/** If known at submit time, total input rows (for partition decisions). */
|
|
94
|
+
totalRows?: number;
|
|
95
|
+
/** Internal scheduler/coordinator URL for Worker-side capabilities. */
|
|
96
|
+
coordinatorUrl?: string | null;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export type PlaySchedulerProgressEvent =
|
|
100
|
+
| { type: 'status'; status: string; logs?: string[]; ts: number }
|
|
101
|
+
| { type: 'log'; line: string; ts: number }
|
|
102
|
+
| { type: 'row'; update: PlayRowUpdate; ts: number }
|
|
103
|
+
| { type: 'execution_event'; event: PlayExecutionEvent; ts: number }
|
|
104
|
+
| { type: 'completed'; result: unknown; ts: number }
|
|
105
|
+
| { type: 'failed'; error: string; ts: number }
|
|
106
|
+
| { type: 'suspended'; reason: string; ts: number };
|
|
107
|
+
|
|
108
|
+
export type PlaySchedulerRunHandle = {
|
|
109
|
+
runId: string;
|
|
110
|
+
/**
|
|
111
|
+
* Optional snapshot of the run's state captured during submit() so callers
|
|
112
|
+
* can skip an immediate follow-up status read. Populated by backends that
|
|
113
|
+
* piggyback the read on the create round-trip (currently cf-workflows).
|
|
114
|
+
*/
|
|
115
|
+
initialState?: Record<string, unknown> | null;
|
|
116
|
+
/**
|
|
117
|
+
* Stream live progress events. Implementations may use SSE, polling, etc.
|
|
118
|
+
* The contract: yields events in order until terminal status.
|
|
119
|
+
*/
|
|
120
|
+
observe(): AsyncIterable<PlaySchedulerProgressEvent>;
|
|
121
|
+
/** Cooperatively cancel the run. */
|
|
122
|
+
cancel(): Promise<void>;
|
|
123
|
+
/** Inject an external event (HITL, webhook). */
|
|
124
|
+
signal(payload: PlaySchedulerSignalPayload): Promise<void>;
|
|
125
|
+
/** Block until terminal state and return final envelope. */
|
|
126
|
+
result(): Promise<PlaySchedulerResultEnvelope>;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export type PlaySchedulerSignalPayload = {
|
|
130
|
+
kind: 'integration_event' | 'cancel' | 'custom';
|
|
131
|
+
eventKey?: string;
|
|
132
|
+
data?: unknown;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export type PlaySchedulerResultEnvelope = {
|
|
136
|
+
runId: string;
|
|
137
|
+
status: 'completed' | 'failed' | 'cancelled';
|
|
138
|
+
output?: unknown;
|
|
139
|
+
error?: string;
|
|
140
|
+
finalCheckpoint?: PlayCheckpoint;
|
|
141
|
+
totalRows?: number;
|
|
142
|
+
durationMs?: number;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export interface PlaySchedulerBackend {
|
|
146
|
+
readonly id: PlaySchedulerBackendId;
|
|
147
|
+
|
|
148
|
+
/** Submit a play run; returns a handle to observe / cancel / signal it. */
|
|
149
|
+
submit(input: PlaySchedulerSubmitInput): Promise<PlaySchedulerRunHandle>;
|
|
150
|
+
|
|
151
|
+
/** Open a handle to an already-submitted run (e.g. for tail-reconnect). */
|
|
152
|
+
attach(
|
|
153
|
+
runId: string,
|
|
154
|
+
options?: { coordinatorUrl?: string | null },
|
|
155
|
+
): Promise<PlaySchedulerRunHandle>;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function normalizePlaySchedulerBackend(
|
|
159
|
+
value?: string | null,
|
|
160
|
+
): PlaySchedulerBackendId {
|
|
161
|
+
const normalized = value?.trim().toLowerCase();
|
|
162
|
+
if (!normalized || normalized === 'temporal') {
|
|
163
|
+
return PLAY_SCHEDULER_BACKENDS.temporal;
|
|
164
|
+
}
|
|
165
|
+
if (normalized === 'cf-workflows' || normalized === 'cf_workflows') {
|
|
166
|
+
return PLAY_SCHEDULER_BACKENDS.cfWorkflows;
|
|
167
|
+
}
|
|
168
|
+
if (normalized === 'in-process' || normalized === 'in_process') {
|
|
169
|
+
return PLAY_SCHEDULER_BACKENDS.inProcess;
|
|
170
|
+
}
|
|
171
|
+
throw new Error(
|
|
172
|
+
`Unsupported scheduler backend "${normalized}". Expected one of: ${Object.values(PLAY_SCHEDULER_BACKENDS).join(', ')}.`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
export interface PlayStaticPipeline {
|
|
2
|
+
tableNamespace?: string;
|
|
3
|
+
inputFields?: string[];
|
|
4
|
+
csvArg?: string;
|
|
5
|
+
hasInlineData?: boolean;
|
|
6
|
+
csvDescription?: string;
|
|
7
|
+
mapDescription?: string;
|
|
8
|
+
fields: string[];
|
|
9
|
+
stages?: PlayStaticSubstep[];
|
|
10
|
+
substeps: PlayStaticSubstep[];
|
|
11
|
+
sheetContract?: PlaySheetContract | null;
|
|
12
|
+
sheetContractErrors?: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type PlaySheetColumnSource =
|
|
16
|
+
| 'input'
|
|
17
|
+
| 'mapField'
|
|
18
|
+
| 'waterfallStep'
|
|
19
|
+
| 'childPlayColumn';
|
|
20
|
+
|
|
21
|
+
export interface PlaySheetColumnContract {
|
|
22
|
+
id: string;
|
|
23
|
+
sqlName: string;
|
|
24
|
+
source: PlaySheetColumnSource;
|
|
25
|
+
field?: string;
|
|
26
|
+
parentField?: string;
|
|
27
|
+
playId?: string;
|
|
28
|
+
waterfallId?: string;
|
|
29
|
+
outputField?: string;
|
|
30
|
+
outputSqlName?: string;
|
|
31
|
+
stepId?: string;
|
|
32
|
+
toolId?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface PlaySheetContract {
|
|
36
|
+
tableNamespace: string;
|
|
37
|
+
columns: PlaySheetColumnContract[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface PlayStaticSourceRange {
|
|
41
|
+
sourcePath?: string;
|
|
42
|
+
startLine: number;
|
|
43
|
+
endLine: number;
|
|
44
|
+
startColumn: number;
|
|
45
|
+
endColumn: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type PlayStaticSubstepMetadata = {
|
|
49
|
+
conditional?: boolean;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type PlayStaticSubstep = PlayStaticSubstepMetadata &
|
|
53
|
+
(
|
|
54
|
+
| {
|
|
55
|
+
type: 'csv';
|
|
56
|
+
field: string;
|
|
57
|
+
path?: string;
|
|
58
|
+
description?: string;
|
|
59
|
+
sourceRange?: PlayStaticSourceRange;
|
|
60
|
+
callDepth?: number;
|
|
61
|
+
callPath?: string[];
|
|
62
|
+
}
|
|
63
|
+
| {
|
|
64
|
+
type: 'map';
|
|
65
|
+
field: string;
|
|
66
|
+
name?: string;
|
|
67
|
+
tableNamespace?: string;
|
|
68
|
+
inputFields?: string[];
|
|
69
|
+
outputFields?: string[];
|
|
70
|
+
waterfallIds?: string[];
|
|
71
|
+
sheetContract?: PlaySheetContract | null;
|
|
72
|
+
description?: string;
|
|
73
|
+
sourceRange?: PlayStaticSourceRange;
|
|
74
|
+
callDepth?: number;
|
|
75
|
+
callPath?: string[];
|
|
76
|
+
}
|
|
77
|
+
| {
|
|
78
|
+
type: 'tool';
|
|
79
|
+
toolId: string;
|
|
80
|
+
field: string;
|
|
81
|
+
description?: string;
|
|
82
|
+
inLoop?: boolean;
|
|
83
|
+
isEventWait?: boolean;
|
|
84
|
+
sourceRange?: PlayStaticSourceRange;
|
|
85
|
+
callDepth?: number;
|
|
86
|
+
callPath?: string[];
|
|
87
|
+
}
|
|
88
|
+
| {
|
|
89
|
+
type: 'waterfall';
|
|
90
|
+
tool?: string;
|
|
91
|
+
field: string;
|
|
92
|
+
inLoop?: boolean;
|
|
93
|
+
id?: string;
|
|
94
|
+
output?: string;
|
|
95
|
+
minResults?: number;
|
|
96
|
+
sourceText?: string;
|
|
97
|
+
steps?: Array<{
|
|
98
|
+
id: string;
|
|
99
|
+
kind?: 'tool' | 'code';
|
|
100
|
+
toolId?: string;
|
|
101
|
+
paramsSource?: string;
|
|
102
|
+
}>;
|
|
103
|
+
description?: string;
|
|
104
|
+
sourceRange?: PlayStaticSourceRange;
|
|
105
|
+
callDepth?: number;
|
|
106
|
+
callPath?: string[];
|
|
107
|
+
}
|
|
108
|
+
| {
|
|
109
|
+
type: 'step_suite';
|
|
110
|
+
field: string;
|
|
111
|
+
steps: PlayStaticSubstep[];
|
|
112
|
+
returnSource?: string;
|
|
113
|
+
description?: string;
|
|
114
|
+
sourceRange?: PlayStaticSourceRange;
|
|
115
|
+
callDepth?: number;
|
|
116
|
+
callPath?: string[];
|
|
117
|
+
}
|
|
118
|
+
| {
|
|
119
|
+
type: 'play_call';
|
|
120
|
+
playId: string;
|
|
121
|
+
field: string;
|
|
122
|
+
inLoop?: boolean;
|
|
123
|
+
pipeline?: PlayStaticPipeline | null;
|
|
124
|
+
cycleDetected?: boolean;
|
|
125
|
+
resolutionError?: string;
|
|
126
|
+
description?: string;
|
|
127
|
+
sourceRange?: PlayStaticSourceRange;
|
|
128
|
+
callDepth?: number;
|
|
129
|
+
callPath?: string[];
|
|
130
|
+
}
|
|
131
|
+
| {
|
|
132
|
+
type: 'run_javascript';
|
|
133
|
+
alias: string;
|
|
134
|
+
description?: string;
|
|
135
|
+
sourceRange?: PlayStaticSourceRange;
|
|
136
|
+
callDepth?: number;
|
|
137
|
+
callPath?: string[];
|
|
138
|
+
}
|
|
139
|
+
| {
|
|
140
|
+
type: 'code';
|
|
141
|
+
field: string;
|
|
142
|
+
description?: string;
|
|
143
|
+
sourceRange?: PlayStaticSourceRange;
|
|
144
|
+
callDepth?: number;
|
|
145
|
+
callPath?: string[];
|
|
146
|
+
}
|
|
147
|
+
);
|