@tlockney/cdp-kernel 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +166 -17
- package/dist/bun-target.d.ts +58 -0
- package/dist/bun-target.js +231 -0
- package/dist/celld-target.d.ts +39 -0
- package/dist/celld-target.js +274 -0
- package/dist/kernel.js +29 -10
- package/dist/main.js +5 -5
- package/dist/package.d.ts +2 -2
- package/dist/package.js +4 -1
- package/dist/target.d.ts +57 -7
- package/dist/target.js +6 -5
- package/dist/transform.d.ts +6 -0
- package/dist/transform.js +135 -6
- package/dist/workerd-target.d.ts +123 -0
- package/dist/workerd-target.js +450 -0
- package/dist/workers-project.d.ts +27 -0
- package/dist/workers-project.js +195 -0
- package/dist/ws-client.d.ts +32 -0
- package/dist/ws-client.js +166 -0
- package/package.json +17 -3
- package/runtime/bootstrap.mjs +39 -4
- package/runtime/bun-channel.d.mts +3 -0
- package/runtime/bun-channel.mjs +58 -0
- package/runtime/evaluator.d.mts +18 -0
- package/runtime/evaluator.mjs +83 -0
- package/runtime/node-hooks.d.mts +11 -0
- package/runtime/node-hooks.mjs +24 -12
- package/runtime/workerd-worker.mjs +189 -0
package/dist/transform.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// callback was not specified").
|
|
17
17
|
import { transformSync } from "esbuild";
|
|
18
18
|
import { parse } from "acorn";
|
|
19
|
-
import { full } from "acorn-walk";
|
|
19
|
+
import { full, recursive } from "acorn-walk";
|
|
20
20
|
export const SHIM = "__cdpk";
|
|
21
21
|
// Each cell first yields one macrotask. User code then runs as an ordinary
|
|
22
22
|
// task instead of nested inside the inspector's dispatch of Runtime.evaluate,
|
|
@@ -80,11 +80,9 @@ function rewriteImport(code, node, index) {
|
|
|
80
80
|
});
|
|
81
81
|
return `var ${ns} = ${load}; var ${bindings.join(", ")};`;
|
|
82
82
|
}
|
|
83
|
-
|
|
84
|
-
export function rewriteModuleSyntax(js) {
|
|
85
|
-
let program;
|
|
83
|
+
function parseCell(js) {
|
|
86
84
|
try {
|
|
87
|
-
|
|
85
|
+
return parse(js, {
|
|
88
86
|
ecmaVersion: "latest",
|
|
89
87
|
sourceType: "module",
|
|
90
88
|
allowAwaitOutsideFunction: true,
|
|
@@ -94,6 +92,10 @@ export function rewriteModuleSyntax(js) {
|
|
|
94
92
|
catch (err) {
|
|
95
93
|
throw new CellSyntaxError(err instanceof Error ? err.message : String(err));
|
|
96
94
|
}
|
|
95
|
+
}
|
|
96
|
+
/** Rewrites module-only syntax in plain JavaScript into script syntax. */
|
|
97
|
+
export function rewriteModuleSyntax(js) {
|
|
98
|
+
const program = parseCell(js);
|
|
97
99
|
const edits = [];
|
|
98
100
|
let importIndex = 0;
|
|
99
101
|
full(program, (node) => {
|
|
@@ -136,15 +138,142 @@ export function rewriteModuleSyntax(js) {
|
|
|
136
138
|
});
|
|
137
139
|
// Nested edits can't occur (imports/exports are top level, import()'s
|
|
138
140
|
// keyword edit doesn't overlap its arguments), so apply back to front.
|
|
139
|
-
|
|
141
|
+
return applyEdits(js, edits);
|
|
142
|
+
}
|
|
143
|
+
function applyEdits(js, edits) {
|
|
144
|
+
// Back to front; at a shared start, a replacement goes before an insertion.
|
|
145
|
+
edits.sort((a, b) => b.start - a.start || b.end - a.end);
|
|
140
146
|
let out = js;
|
|
141
147
|
for (const edit of edits)
|
|
142
148
|
out = out.slice(0, edit.start) + edit.text + out.slice(edit.end);
|
|
143
149
|
return out;
|
|
144
150
|
}
|
|
151
|
+
/** Adds the names a binding pattern declares. */
|
|
152
|
+
function patternNames(pattern, names) {
|
|
153
|
+
switch (pattern.type) {
|
|
154
|
+
case "Identifier":
|
|
155
|
+
names.add(pattern.name);
|
|
156
|
+
break;
|
|
157
|
+
case "ObjectPattern":
|
|
158
|
+
for (const prop of pattern.properties)
|
|
159
|
+
patternNames(prop.type === "Property" ? prop.value : prop, names);
|
|
160
|
+
break;
|
|
161
|
+
case "ArrayPattern":
|
|
162
|
+
for (const element of pattern.elements)
|
|
163
|
+
if (element)
|
|
164
|
+
patternNames(element, names);
|
|
165
|
+
break;
|
|
166
|
+
case "RestElement":
|
|
167
|
+
patternNames(pattern.argument, names);
|
|
168
|
+
break;
|
|
169
|
+
case "AssignmentPattern":
|
|
170
|
+
patternNames(pattern.left, names);
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Makes a cell's declarations global without replMode, the tslab/IJavascript
|
|
176
|
+
* technique: the cell runs inside an async function (for top-level await),
|
|
177
|
+
* and every name it declares at the top level is declared with `var` in the
|
|
178
|
+
* surrounding script, which a global-code eval turns into a property of
|
|
179
|
+
* globalThis. Inside the function the declarations become assignments to
|
|
180
|
+
* those globals:
|
|
181
|
+
* const { a } = o; class K {} -> var a, K;
|
|
182
|
+
* (async () => { ({ a } = o); K = class K {}; })()
|
|
183
|
+
* So later cells, and closures from this one, all see one binding. A `var`
|
|
184
|
+
* in a top-level block or loop head is global too, as in a script; block
|
|
185
|
+
* scoped and function-local declarations stay where they are. Functions
|
|
186
|
+
* keep their in-cell hoisting and are copied to globalThis on entry.
|
|
187
|
+
*
|
|
188
|
+
* A trailing expression statement becomes `return [value]`; the box keeps a
|
|
189
|
+
* promise from being awaited, so the cell's result is the promise itself, as
|
|
190
|
+
* in Deno.
|
|
191
|
+
*/
|
|
192
|
+
function hoistForEval(js) {
|
|
193
|
+
const program = parseCell(js);
|
|
194
|
+
const names = new Set();
|
|
195
|
+
const functions = [];
|
|
196
|
+
const edits = [];
|
|
197
|
+
const src = (node) => js.slice(node.start, node.end);
|
|
198
|
+
const replace = (node, text) => edits.push({ start: node.start, end: node.end, text });
|
|
199
|
+
// A declarator without an initializer resets the name, except under var,
|
|
200
|
+
// where redeclaring keeps the value.
|
|
201
|
+
const assignments = (decl) => decl.declarations.flatMap((d) => {
|
|
202
|
+
patternNames(d.id, names);
|
|
203
|
+
if (d.init)
|
|
204
|
+
return [`${src(d.id)} = ${src(d.init)}`];
|
|
205
|
+
return decl.kind === "var" ? [] : [`${src(d.id)} = undefined`];
|
|
206
|
+
});
|
|
207
|
+
const statement = (decl) => {
|
|
208
|
+
const parts = assignments(decl);
|
|
209
|
+
replace(decl, parts.length > 0 ? `(${parts.join(", ")});` : ";");
|
|
210
|
+
};
|
|
211
|
+
const isVar = (node) => node?.type === "VariableDeclaration" && node.kind === "var";
|
|
212
|
+
const loopHead = (left) => {
|
|
213
|
+
if (!isVar(left))
|
|
214
|
+
return;
|
|
215
|
+
const id = left.declarations[0].id;
|
|
216
|
+
patternNames(id, names);
|
|
217
|
+
replace(left, src(id));
|
|
218
|
+
};
|
|
219
|
+
// Expressions can't hold statements outside a function or class, so only
|
|
220
|
+
// statement bodies are walked.
|
|
221
|
+
const nestedVars = (node) => recursive(node, null, {
|
|
222
|
+
Function() { },
|
|
223
|
+
Class() { },
|
|
224
|
+
VariableDeclaration(decl) {
|
|
225
|
+
if (decl.kind === "var")
|
|
226
|
+
statement(decl);
|
|
227
|
+
},
|
|
228
|
+
ForStatement(loop, state, walk) {
|
|
229
|
+
if (isVar(loop.init))
|
|
230
|
+
replace(loop.init, assignments(loop.init).join(", "));
|
|
231
|
+
walk(loop.body, state);
|
|
232
|
+
},
|
|
233
|
+
ForInStatement(loop, state, walk) {
|
|
234
|
+
loopHead(loop.left);
|
|
235
|
+
walk(loop.body, state);
|
|
236
|
+
},
|
|
237
|
+
ForOfStatement(loop, state, walk) {
|
|
238
|
+
loopHead(loop.left);
|
|
239
|
+
walk(loop.body, state);
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
program.body.forEach((stmt, i) => {
|
|
243
|
+
if (stmt.type === "VariableDeclaration" && (stmt.kind === "var" || stmt.kind === "let" || stmt.kind === "const")) {
|
|
244
|
+
statement(stmt);
|
|
245
|
+
}
|
|
246
|
+
else if (stmt.type === "FunctionDeclaration") {
|
|
247
|
+
names.add(stmt.id.name);
|
|
248
|
+
functions.push(stmt.id.name);
|
|
249
|
+
}
|
|
250
|
+
else if (stmt.type === "ClassDeclaration") {
|
|
251
|
+
names.add(stmt.id.name);
|
|
252
|
+
edits.push({ start: stmt.start, end: stmt.start, text: `${stmt.id.name} = ` });
|
|
253
|
+
edits.push({ start: stmt.end, end: stmt.end, text: ";" });
|
|
254
|
+
}
|
|
255
|
+
else if (stmt.type === "ExpressionStatement" && i === program.body.length - 1) {
|
|
256
|
+
replace(stmt, `return [${src(stmt.expression)}];`);
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
nestedVars(stmt);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
const declare = names.size > 0 ? `var ${[...names].join(", ")};\n` : "";
|
|
263
|
+
const publish = functions.map((name) => ` globalThis.${name} = ${name};`).join("");
|
|
264
|
+
return `${declare}(async () => { 'use strict';${publish}\n${applyEdits(js, edits)}\n})()`;
|
|
265
|
+
}
|
|
145
266
|
export function transformCell(code, options = {}) {
|
|
146
267
|
return PRELUDE + rewriteModuleSyntax(stripTypes(code, options));
|
|
147
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Like transformCell, for a target that evaluates cells itself, as global
|
|
271
|
+
* code (an indirect eval), instead of through the inspector's replMode: see
|
|
272
|
+
* hoistForEval. The script's value is a promise of the boxed result.
|
|
273
|
+
*/
|
|
274
|
+
export function transformCellForEval(code, options = {}) {
|
|
275
|
+
return hoistForEval(rewriteModuleSyntax(stripTypes(code, options)));
|
|
276
|
+
}
|
|
148
277
|
/**
|
|
149
278
|
* Deno treats a cell like `{ "a": 1 }` as an object literal rather than a
|
|
150
279
|
* block, as browser consoles do. Returns the wrapped form to try first, or
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { CdpParams, EvaluateResult, RemoteObject } from "./cdp.ts";
|
|
2
|
+
import { transformCellForEval } from "./transform.ts";
|
|
3
|
+
import { type KernelTarget, type TargetEvents } from "./target.ts";
|
|
4
|
+
import { type WsChannel } from "./ws-client.ts";
|
|
5
|
+
/** The driver Worker's modules; the first is its main module. */
|
|
6
|
+
export declare const WORKER_MODULES: string[];
|
|
7
|
+
/** See BunTarget: how long an interrupted cell has to show it isn't busy. */
|
|
8
|
+
export declare const INTERRUPT_GRACE_MS = 500;
|
|
9
|
+
export declare function restartNote(runtime: string): string;
|
|
10
|
+
export declare const RESTART_NOTE: string;
|
|
11
|
+
export declare function workerdConfig({ socket, compatibilityDate }: {
|
|
12
|
+
socket: string;
|
|
13
|
+
compatibilityDate: string;
|
|
14
|
+
}): string;
|
|
15
|
+
/**
|
|
16
|
+
* A Workers project has workerd in its node_modules (wrangler and miniflare
|
|
17
|
+
* depend on it), so look there first, from the notebook's directory up;
|
|
18
|
+
* otherwise use the one on PATH.
|
|
19
|
+
*/
|
|
20
|
+
export declare function findWorkerd(cwd: string): string;
|
|
21
|
+
/** "workerd 2026-10-15" -> "2026-10-15": the newest date it supports. */
|
|
22
|
+
export declare function compatibilityDateOf(versionOutput: string): string;
|
|
23
|
+
/** The literal specifiers a transformed cell imports, in order. */
|
|
24
|
+
export declare function importSpecifiers(script: string): string[];
|
|
25
|
+
/**
|
|
26
|
+
* Bundles `specifier`, as imported from `cwd`, into global code whose value
|
|
27
|
+
* is a promise of the module's namespace. npm: and jsr: install into the
|
|
28
|
+
* npm cache, as on a Node target. Builtins stay external and are handed to
|
|
29
|
+
* the bundle as `require` (workerd-worker.mjs's requireFor). A specifier
|
|
30
|
+
* that can't be bundled becomes code that throws when imported, so the
|
|
31
|
+
* error appears in the cell.
|
|
32
|
+
*/
|
|
33
|
+
export declare function bundleModule(specifier: string, cwd: string): Promise<string>;
|
|
34
|
+
/** What import.meta says in a cell: the notebook's directory, not workerd's. */
|
|
35
|
+
export declare function cellMeta(cwd: string): {
|
|
36
|
+
url: string;
|
|
37
|
+
dirname: string;
|
|
38
|
+
filename: string;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* How the target reaches its driver Worker: a workerd of its own
|
|
42
|
+
* (RawWorkerd, below), or a Workers project's Miniflare
|
|
43
|
+
* (src/workers-project.ts), which adds the project's bindings.
|
|
44
|
+
*/
|
|
45
|
+
export interface WorkerHost {
|
|
46
|
+
/** The runtime's name for the banner: workerd's date, or Miniflare's version. */
|
|
47
|
+
readonly runtime: string;
|
|
48
|
+
/** The wrangler config whose bindings the driver has, if any. */
|
|
49
|
+
readonly project?: string;
|
|
50
|
+
/** The open session with the driver. */
|
|
51
|
+
readonly channel: WsChannel;
|
|
52
|
+
/** Stops the runtime; `now` even while a cell is busy. */
|
|
53
|
+
stop(now: boolean): void;
|
|
54
|
+
onExit(listener: (code: number | null) => void): void;
|
|
55
|
+
}
|
|
56
|
+
/** The driver on a workerd of its own, behind a Unix socket. */
|
|
57
|
+
export declare class RawWorkerd implements WorkerHost {
|
|
58
|
+
#private;
|
|
59
|
+
readonly runtime: string;
|
|
60
|
+
readonly channel: WsChannel;
|
|
61
|
+
private constructor();
|
|
62
|
+
static start(cwd: string, executable?: string): Promise<RawWorkerd>;
|
|
63
|
+
stop(now: boolean): void;
|
|
64
|
+
onExit(listener: (code: number | null) => void): void;
|
|
65
|
+
}
|
|
66
|
+
/** A runtime that runs the driver Worker: workerd, or celld (src/celld-target.ts). */
|
|
67
|
+
export type WorkersRuntime = "workerd" | "celld";
|
|
68
|
+
export declare class WorkerdTarget implements KernelTarget {
|
|
69
|
+
#private;
|
|
70
|
+
readonly runtime: WorkersRuntime;
|
|
71
|
+
readonly versions: {
|
|
72
|
+
runtime: string;
|
|
73
|
+
engine: string;
|
|
74
|
+
};
|
|
75
|
+
notice: string | undefined;
|
|
76
|
+
readonly transform: typeof transformCellForEval;
|
|
77
|
+
private constructor();
|
|
78
|
+
/**
|
|
79
|
+
* In a Workers project (a wrangler config at or above `cwd`), the
|
|
80
|
+
* project's own Miniflare runs the driver with the project's bindings;
|
|
81
|
+
* otherwise a workerd of its own does (#26).
|
|
82
|
+
*/
|
|
83
|
+
static launch(events: TargetEvents, options?: {
|
|
84
|
+
cwd?: string;
|
|
85
|
+
executable?: string;
|
|
86
|
+
}): Promise<WorkerdTarget>;
|
|
87
|
+
/**
|
|
88
|
+
* A target over a started host's session. `notice` (a project that
|
|
89
|
+
* failed to load) is replaced by the bindings when the host has a project.
|
|
90
|
+
*/
|
|
91
|
+
static open(host: WorkerHost, cwd: string, events: TargetEvents, runtime: WorkersRuntime, notice?: string): Promise<WorkerdTarget>;
|
|
92
|
+
evaluate(script: string): Promise<EvaluateResult>;
|
|
93
|
+
/**
|
|
94
|
+
* A cell that's waiting on something leaves the driver free: it answers a
|
|
95
|
+
* ping, and the cell is abandoned with the session intact (its pending
|
|
96
|
+
* work still runs, as on Bun). A busy cell can't be reached, so workerd is
|
|
97
|
+
* stopped and restarts on the next cell.
|
|
98
|
+
*/
|
|
99
|
+
interrupt(): Promise<void>;
|
|
100
|
+
/** Output arrives on the session before the reply it precedes. */
|
|
101
|
+
flush(): Promise<void>;
|
|
102
|
+
broadcastResult(executionCount: number, result: RemoteObject): Promise<void>;
|
|
103
|
+
dispatchComm(msgType: string, content: CdpParams, metadata: CdpParams, buffers: Uint8Array[]): Promise<{
|
|
104
|
+
handled: boolean;
|
|
105
|
+
} | {
|
|
106
|
+
error: {
|
|
107
|
+
name: string;
|
|
108
|
+
message: string;
|
|
109
|
+
stack: string;
|
|
110
|
+
};
|
|
111
|
+
}>;
|
|
112
|
+
describeError(exception: RemoteObject): Promise<{
|
|
113
|
+
name: string;
|
|
114
|
+
message: string;
|
|
115
|
+
stack: string;
|
|
116
|
+
}>;
|
|
117
|
+
propertyNames(expression: string): Promise<string[]>;
|
|
118
|
+
/** None: top-level declarations here are properties of globalThis. */
|
|
119
|
+
lexicalNames(): Promise<string[]>;
|
|
120
|
+
/** Never asked for: prompt() in workerd answers null without asking. */
|
|
121
|
+
replyInput(): void;
|
|
122
|
+
kill(): void;
|
|
123
|
+
}
|