@tlockney/cdp-kernel 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/kernel.js +1 -1
- package/dist/browser/kernel.js.map +1 -1
- package/dist/browser/worker.js +968 -71
- package/dist/browser/worker.js.map +4 -4
- package/dist/package.d.ts +3 -3
- package/dist/package.js +5 -2
- package/dist/workerd-target.js +1 -1
- package/package.json +1 -1
- package/runtime/chart.d.mts +23 -0
- package/runtime/chart.mjs +359 -0
- package/runtime/display.d.mts +124 -0
- package/runtime/display.mjs +38 -65
- package/runtime/host.d.mts +7 -5
- package/runtime/sql.d.mts +147 -0
- package/runtime/sql.mjs +426 -0
- package/runtime/table.d.mts +39 -0
- package/runtime/table.mjs +466 -0
package/runtime/display.mjs
CHANGED
|
@@ -4,19 +4,26 @@
|
|
|
4
4
|
//
|
|
5
5
|
// Ported from Deno's cli/js/40_jupyter.js (v2.9.6), which is
|
|
6
6
|
// Copyright 2018-2026 the Deno authors, MIT license. The formatting rules
|
|
7
|
-
// (display symbol, canvas, Vega,
|
|
8
|
-
//
|
|
7
|
+
// (display symbol, canvas, Vega, JPEG/PNG bytes, HTML/SVG elements) are
|
|
8
|
+
// unchanged; Polars-style DataFrames show through table.mjs instead (#475),
|
|
9
|
+
// as do Arrow tables and DuckDB results. The Deno internals it used are
|
|
10
|
+
// injected:
|
|
9
11
|
// Deno[Deno.internal].inspectArgs -> host.inspect
|
|
10
12
|
// op_base64_encode_from_buffer -> host.base64
|
|
11
13
|
// Deno.readFileSync -> host.readFile
|
|
12
14
|
// op_jupyter_broadcast -> host.send
|
|
13
15
|
// WebGPU texture/buffer display depends on Deno's wgpu bindings and is
|
|
14
|
-
// omitted. Comms and
|
|
15
|
-
//
|
|
16
|
+
// omitted. Comms, widgets, tables, SQL and charts (jupyter.comm,
|
|
17
|
+
// jupyter.widget, jupyter.table, jupyter.sql, jupyter.chart) are additions
|
|
18
|
+
// Deno doesn't have; see comm.mjs, widget.mjs, table.mjs, sql.mjs and
|
|
19
|
+
// chart.mjs.
|
|
16
20
|
|
|
17
21
|
import { createComms } from "./comm.mjs";
|
|
18
22
|
import { createHost } from "./host.mjs";
|
|
19
23
|
import { createWidgetFactory } from "./widget.mjs";
|
|
24
|
+
import { createSql } from "./sql.mjs";
|
|
25
|
+
import { createChart } from "./chart.mjs";
|
|
26
|
+
import { isTableLike, tableBundle, tableSource } from "./table.mjs";
|
|
20
27
|
|
|
21
28
|
export const $display = Symbol.for("Jupyter.display");
|
|
22
29
|
|
|
@@ -243,66 +250,6 @@ function summarizeChart(spec) {
|
|
|
243
250
|
}
|
|
244
251
|
}
|
|
245
252
|
|
|
246
|
-
function isDataFrameLike(obj) {
|
|
247
|
-
return obj !== null && typeof obj === "object" &&
|
|
248
|
-
obj.schema !== undefined && typeof obj.schema === "object" &&
|
|
249
|
-
typeof obj.head === "function" && typeof obj.toRecords === "function";
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function mapPolarsTypeToJSONSchema(colType) {
|
|
253
|
-
const typeMapping = {
|
|
254
|
-
Null: "null",
|
|
255
|
-
Bool: "boolean",
|
|
256
|
-
Int8: "integer",
|
|
257
|
-
Int16: "integer",
|
|
258
|
-
Int32: "integer",
|
|
259
|
-
Int64: "integer",
|
|
260
|
-
UInt8: "integer",
|
|
261
|
-
UInt16: "integer",
|
|
262
|
-
UInt32: "integer",
|
|
263
|
-
UInt64: "integer",
|
|
264
|
-
Float32: "number",
|
|
265
|
-
Float64: "number",
|
|
266
|
-
Date: "string",
|
|
267
|
-
Datetime: "string",
|
|
268
|
-
Utf8: "string",
|
|
269
|
-
Categorical: "string",
|
|
270
|
-
List: "array",
|
|
271
|
-
Struct: "object",
|
|
272
|
-
};
|
|
273
|
-
// Polars DataTypes only expose their name through toJSON().
|
|
274
|
-
const dataType = typeof colType?.toJSON === "function" ? colType.toJSON()["DataType"] : undefined;
|
|
275
|
-
return typeMapping[dataType] || "string";
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
function extractDataFrame(df) {
|
|
279
|
-
const fields = [];
|
|
280
|
-
for (const [name, colType] of Object.entries(df.schema)) {
|
|
281
|
-
fields.push({ name, type: mapPolarsTypeToJSONSchema(colType) });
|
|
282
|
-
}
|
|
283
|
-
const data = df.head(50).toRecords();
|
|
284
|
-
let html = "<table><thead><tr>";
|
|
285
|
-
for (const field of fields) html += `<th>${escapeHTML(String(field.name))}</th>`;
|
|
286
|
-
html += "</tr></thead><tbody>";
|
|
287
|
-
for (const row of df.head(10).toRecords()) {
|
|
288
|
-
html += "<tr>";
|
|
289
|
-
for (const field of fields) html += `<td>${escapeHTML(String(row[field.name]))}</td>`;
|
|
290
|
-
html += "</tr>";
|
|
291
|
-
}
|
|
292
|
-
html += "</tbody></table>";
|
|
293
|
-
// Polars has `height`; the row count is left out when there isn't one.
|
|
294
|
-
const size = [];
|
|
295
|
-
if (Number.isInteger(df.height)) size.push(`${df.height} ${df.height === 1 ? "row" : "rows"}`);
|
|
296
|
-
size.push(`${fields.length} ${fields.length === 1 ? "column" : "columns"}`);
|
|
297
|
-
const columns = fields.slice(0, 20).map((field) => `${clip(String(field.name), 40)} (${field.type})`);
|
|
298
|
-
if (fields.length > 20) columns.push(`… ${fields.length - 20} more`);
|
|
299
|
-
return {
|
|
300
|
-
"application/vnd.dataresource+json": { data, schema: { fields } },
|
|
301
|
-
"text/html": html,
|
|
302
|
-
"text/plain": `DataFrame (${size.join(", ")})${columns.length ? `: ${columns.join(", ")}` : ""}`,
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
|
|
306
253
|
function isCanvasLike(obj) {
|
|
307
254
|
return obj !== null && typeof obj === "object" && "toDataURL" in obj;
|
|
308
255
|
}
|
|
@@ -377,7 +324,8 @@ export function createJupyter(host) {
|
|
|
377
324
|
const vega = extractVega(obj);
|
|
378
325
|
if (vega) return vega;
|
|
379
326
|
}
|
|
380
|
-
|
|
327
|
+
// Arrow tables, Polars DataFrames and DuckDB results (#475).
|
|
328
|
+
if (isTableLike(obj)) return tableBundle(tableSource(obj));
|
|
381
329
|
if (isJpg(obj)) return { "image/jpeg": host.base64(obj) };
|
|
382
330
|
if (isPng(obj)) return { "image/png": host.base64(obj) };
|
|
383
331
|
if (isSVGElementLike(obj)) return { "image/svg+xml": obj.outerHTML };
|
|
@@ -409,6 +357,13 @@ export function createJupyter(host) {
|
|
|
409
357
|
);
|
|
410
358
|
}
|
|
411
359
|
|
|
360
|
+
// jupyter.table (#475): rows and columns as Ganymede's table output,
|
|
361
|
+
// built now so a bad value fails the call, not the display.
|
|
362
|
+
function table(value, options = {}) {
|
|
363
|
+
const bundle = tableBundle(tableSource(value, options), options);
|
|
364
|
+
return { [$display]: () => bundle };
|
|
365
|
+
}
|
|
366
|
+
|
|
412
367
|
function broadcast(msgType, content, { metadata = {}, buffers = [] } = {}) {
|
|
413
368
|
host.send(msgType, content, metadata, buffers);
|
|
414
369
|
}
|
|
@@ -492,6 +447,21 @@ export function createJupyter(host) {
|
|
|
492
447
|
});
|
|
493
448
|
}
|
|
494
449
|
|
|
450
|
+
// The cell's own import, so npm: resolution and the browser target's CDN
|
|
451
|
+
// apply to the packages jupyter.sql and jupyter.chart load.
|
|
452
|
+
const cellImport = (what) => (specifier) => {
|
|
453
|
+
const load = globalThis.__cdpk?.import;
|
|
454
|
+
if (typeof load !== "function") return Promise.reject(new Error(`${what}: no module loader in this runtime`));
|
|
455
|
+
return load(specifier);
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
// jupyter.sql (#477): DuckDB, loaded on the first query; an interrupt
|
|
459
|
+
// (jupyter.signal) interrupts the query.
|
|
460
|
+
const sql = createSql({ importModule: cellImport("jupyter.sql"), signal: () => current.signal });
|
|
461
|
+
|
|
462
|
+
// jupyter.chart (#479): vega, vega-lite or linkedom, loaded on first use.
|
|
463
|
+
const chart = createChart({ importModule: cellImport("jupyter.chart"), summarizeSpec: summarizeChart });
|
|
464
|
+
|
|
495
465
|
const comms = createComms((msgType, content, metadata, buffers) => host.send(msgType, content, metadata, buffers));
|
|
496
466
|
|
|
497
467
|
// jupyter.signal (#418): aborted when the kernel is interrupted, so cell
|
|
@@ -546,6 +516,9 @@ export function createJupyter(host) {
|
|
|
546
516
|
return readParams();
|
|
547
517
|
},
|
|
548
518
|
result,
|
|
519
|
+
table,
|
|
520
|
+
sql,
|
|
521
|
+
chart,
|
|
549
522
|
format,
|
|
550
523
|
md: taggedTemplate("text/markdown"),
|
|
551
524
|
html: taggedTemplate("text/html"),
|
package/runtime/host.d.mts
CHANGED
|
@@ -103,7 +103,7 @@ export interface Host {
|
|
|
103
103
|
call(method: string, params?: HostParams, options?: HostCallOptions): Promise<any>;
|
|
104
104
|
/** Other notebooks under the server's root, as far as this notebook's grant reaches. */
|
|
105
105
|
readonly notebooks: HostNotebooks;
|
|
106
|
-
/** This notebook's own cells.
|
|
106
|
+
/** This notebook's own cells. Ganymede 0.8.0 or later; an older server rejects `unavailable`. */
|
|
107
107
|
readonly self: HostSelf;
|
|
108
108
|
}
|
|
109
109
|
|
|
@@ -160,7 +160,8 @@ export interface HostNotebooks {
|
|
|
160
160
|
options?: HostCallOptions,
|
|
161
161
|
): Promise<NotebookSearch>;
|
|
162
162
|
/**
|
|
163
|
-
* A notebook's named results.
|
|
163
|
+
* A notebook's named results. Ganymede 0.8.0 or later; an older server
|
|
164
|
+
* rejects `unavailable`.
|
|
164
165
|
*
|
|
165
166
|
* @param params `path`: the notebook.
|
|
166
167
|
* @param options `signal` cancels the call.
|
|
@@ -172,8 +173,9 @@ export interface HostNotebooks {
|
|
|
172
173
|
*/
|
|
173
174
|
results(params: { path: string }, options?: HostCallOptions): Promise<any>;
|
|
174
175
|
/**
|
|
175
|
-
* Run a notebook with parameters and return its results
|
|
176
|
-
*
|
|
176
|
+
* Run a notebook with parameters and return its results, in a fresh
|
|
177
|
+
* kernel, when this notebook's grant names it. Ganymede 0.8.0 or later;
|
|
178
|
+
* an older server rejects `unavailable`.
|
|
177
179
|
*
|
|
178
180
|
* @param params `path`: the notebook; `params`: its parameters.
|
|
179
181
|
* @param options `signal` cancels the run.
|
|
@@ -186,7 +188,7 @@ export interface HostNotebooks {
|
|
|
186
188
|
run(params: { path: string; params?: HostParams }, options?: HostCallOptions): Promise<any>;
|
|
187
189
|
}
|
|
188
190
|
|
|
189
|
-
/** `host.self`: this notebook's own cells.
|
|
191
|
+
/** `host.self`: this notebook's own cells. Ganymede 0.8.0 or later; an older server rejects each with `unavailable`. */
|
|
190
192
|
export interface HostSelf {
|
|
191
193
|
/**
|
|
192
194
|
* Add a cell to this notebook.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { Displayable } from "./display.d.mts";
|
|
2
|
+
|
|
3
|
+
export const NODE_API: string;
|
|
4
|
+
export const DUCKDB_WASM: string;
|
|
5
|
+
|
|
6
|
+
/** One column of a query's result. */
|
|
7
|
+
export interface SqlColumn {
|
|
8
|
+
name: string;
|
|
9
|
+
/** DuckDB's type name (`BIGINT`, `VARCHAR`, `DATE`), or Arrow's in the browser. */
|
|
10
|
+
type: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A query's result. It shows as a table (the same output as
|
|
15
|
+
* `jupyter.table`) when it is a cell's value or passed to
|
|
16
|
+
* `jupyter.display`.
|
|
17
|
+
*/
|
|
18
|
+
export interface SqlResult extends Displayable {
|
|
19
|
+
/** The result's columns, in order. */
|
|
20
|
+
readonly columns: SqlColumn[];
|
|
21
|
+
/**
|
|
22
|
+
* The rows as plain objects keyed by column name. Integers that fit a
|
|
23
|
+
* JavaScript number are numbers, larger ones `bigint`; `DATE` and
|
|
24
|
+
* `TIMESTAMP` values are `Date`s.
|
|
25
|
+
*/
|
|
26
|
+
readonly rows: Record<string, unknown>[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const SqlResult: new (columns: SqlColumn[], rows: Record<string, unknown>[]) => SqlResult & {
|
|
30
|
+
toTableSource(names?: string[]): import("./table.d.mts").TableSource;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** Text that `jupyter.sql` puts into the query as written: see `sql.raw`. */
|
|
34
|
+
export interface SqlFragment {
|
|
35
|
+
readonly __sqlRaw: never;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* `jupyter.sql`: a tagged template that runs SQL on the kernel's DuckDB.
|
|
40
|
+
* Each `${value}` becomes a bound parameter.
|
|
41
|
+
*/
|
|
42
|
+
export interface Sql {
|
|
43
|
+
/**
|
|
44
|
+
* Run the query, with each `${value}` as a bound parameter.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* await jupyter.sql`select ${"Ganymede"} as moon`;
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
(strings: TemplateStringsArray, ...values: unknown[]): Promise<SqlResult>;
|
|
52
|
+
/**
|
|
53
|
+
* Run SQL from a string, with values for its `?` parameters. For a
|
|
54
|
+
* query built at run time; prefer the tag otherwise.
|
|
55
|
+
*
|
|
56
|
+
* @param text The SQL.
|
|
57
|
+
* @param params Values for its `?` placeholders, in order.
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* const op = ">";
|
|
61
|
+
* await jupyter.sql.query(`select 2 ${op} ? as answer`, [1]);
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
query(text: string, params?: unknown[]): Promise<SqlResult>;
|
|
65
|
+
/**
|
|
66
|
+
* Text the tag puts into the query as written, rather than as a
|
|
67
|
+
* parameter: a table name or a file path built in code. Never pass it
|
|
68
|
+
* text from outside the notebook.
|
|
69
|
+
*
|
|
70
|
+
* @param text SQL text.
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* const table = "moons";
|
|
74
|
+
* await jupyter.sql`select count(*) as n from ${jupyter.sql.raw(table)}`;
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
raw(text: string): SqlFragment;
|
|
78
|
+
/**
|
|
79
|
+
* Create (or replace) table `name` from a file DuckDB can read (CSV,
|
|
80
|
+
* Parquet or JSON, by path or URL), from an array of row objects, or
|
|
81
|
+
* from an Arrow table or Polars DataFrame. Rows go in as one JSON
|
|
82
|
+
* parameter, typed from the values: integers as `BIGINT`, numbers as
|
|
83
|
+
* `DOUBLE`, `Date`s as `TIMESTAMP`, arrays and objects as `JSON`.
|
|
84
|
+
*
|
|
85
|
+
* @param name The table's name.
|
|
86
|
+
* @param source A path or URL, rows, an Arrow table or a DataFrame.
|
|
87
|
+
* @throws {TypeError} `name` is empty, or the rows have no columns.
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* await jupyter.sql.load("moons", [
|
|
91
|
+
* { moon: "Io", radius_km: 1821.6 },
|
|
92
|
+
* { moon: "Ganymede", radius_km: 2634.1 },
|
|
93
|
+
* ]);
|
|
94
|
+
* await jupyter.sql`select moon from moons order by radius_km desc`;
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
load(name: string, source: unknown): Promise<void>;
|
|
98
|
+
/**
|
|
99
|
+
* Close the database and free its memory. The next query opens a new,
|
|
100
|
+
* empty one.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* await jupyter.sql.close();
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
close(): Promise<void>;
|
|
108
|
+
/**
|
|
109
|
+
* The DuckDB connection itself, for what the tag doesn't cover: a
|
|
110
|
+
* `@duckdb/node-api` `DuckDBConnection` on Node, Deno and Bun, a
|
|
111
|
+
* duckdb-wasm `AsyncDuckDBConnection` in the browser.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* const conn = await jupyter.sql.connection();
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
connection(): Promise<unknown>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function decimalValue(words: ArrayLike<number> | number | bigint, scale: number): number;
|
|
122
|
+
export function toParam(value: unknown): unknown;
|
|
123
|
+
/** A parameter as a SQL literal. */
|
|
124
|
+
export function sqlLiteral(value: unknown): string;
|
|
125
|
+
/** The query with each `?` placeholder (outside strings, quoted names and comments) replaced by its parameter as a literal. */
|
|
126
|
+
export function inlineParams(text: string, params: unknown[]): string;
|
|
127
|
+
/** A DuckDB TIME as `HH:MM:SS[.fraction]`, from its count in the declared type's unit (microseconds by default). */
|
|
128
|
+
export function timeText(value: number | bigint, declared?: string): string;
|
|
129
|
+
/** Column names made unique: a repeated `id` becomes `id_1`, `id_2`, and so on. */
|
|
130
|
+
export function uniqueNames(names: readonly string[]): string[];
|
|
131
|
+
export function compose(strings: readonly string[], values: unknown[]): { text: string; params: unknown[] };
|
|
132
|
+
export function quoteIdent(name: string): string;
|
|
133
|
+
export function quoteString(text: string): string;
|
|
134
|
+
export function rowsAsJson(source: unknown): { structure: string | null; json: string };
|
|
135
|
+
|
|
136
|
+
export interface SqlEngine {
|
|
137
|
+
query: (text: string, params: unknown[]) => Promise<SqlResult>;
|
|
138
|
+
interrupt?: () => void;
|
|
139
|
+
close: () => Promise<void>;
|
|
140
|
+
connection: unknown;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function createSql(options: {
|
|
144
|
+
importModule: (specifier: string) => Promise<any>;
|
|
145
|
+
signal?: () => AbortSignal | undefined;
|
|
146
|
+
openEngine?: () => Promise<SqlEngine>;
|
|
147
|
+
}): Sql;
|