@tlockney/cdp-kernel 0.9.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 +584 -425
- package/dist/browser/kernel.js.map +3 -3
- package/dist/browser/target.d.ts +7 -0
- package/dist/browser/target.js +2 -1
- package/dist/browser/worker.js +1191 -76
- package/dist/browser/worker.js.map +4 -4
- package/dist/celld-target.d.ts +2 -1
- package/dist/celld-target.js +5 -3
- package/dist/kernel-core.js +8 -0
- package/dist/package.d.ts +3 -3
- package/dist/package.js +6 -2
- package/dist/workerd-target.js +6 -2
- package/dist/workers-project.js +1 -1
- package/package.json +3 -3
- package/runtime/bootstrap.mjs +2 -0
- package/runtime/browser-worker.mjs +5 -1
- package/runtime/chart.d.mts +23 -0
- package/runtime/chart.mjs +359 -0
- package/runtime/display.d.mts +199 -1
- package/runtime/display.mjs +101 -65
- package/runtime/host.d.mts +248 -0
- package/runtime/host.mjs +215 -0
- 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/workerd-worker.mjs +5 -1
package/runtime/display.d.mts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { CommApi, Comms } from "./comm.d.mts";
|
|
2
|
+
import type { Host, HostCallOptions, HostError, HostErrorCode, HostNotebooks, HostOptions, HostSelf, NotebookEntry, NotebookHit, NotebookSearch } from "./host.d.mts";
|
|
2
3
|
import type { Widget, WidgetOptions } from "./widget.d.mts";
|
|
4
|
+
import type { Sql, SqlColumn, SqlFragment, SqlResult } from "./sql.d.mts";
|
|
3
5
|
|
|
4
|
-
export type {
|
|
6
|
+
export type { Sql, SqlColumn, SqlFragment, SqlResult };
|
|
7
|
+
export type { CommApi, Host, HostCallOptions, HostError, HostErrorCode, HostNotebooks, HostOptions, HostSelf, NotebookEntry, NotebookHit, NotebookSearch, Widget, WidgetOptions };
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* The symbol a class implements to choose how its instances appear. At
|
|
@@ -23,6 +26,33 @@ export interface DisplayHost {
|
|
|
23
26
|
readFile: (path: string) => Uint8Array;
|
|
24
27
|
send: (msgType: string, content: object, metadata: object, buffers: Uint8Array[]) => void;
|
|
25
28
|
input: (prompt: string, password: boolean) => string | null;
|
|
29
|
+
/** The run's parameters as JSON text, or `undefined` when there are none. */
|
|
30
|
+
params?: () => string | undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The MIME type of a named result: `{ name, value }`. */
|
|
34
|
+
export const RESULT_MIME: "application/vnd.ganymede.result+json";
|
|
35
|
+
|
|
36
|
+
/** A JSON value, as `JSON.parse` returns it. */
|
|
37
|
+
export type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };
|
|
38
|
+
|
|
39
|
+
/** Options for `jupyter.table`. */
|
|
40
|
+
export interface TableOptions {
|
|
41
|
+
/** Rows to embed: default 1,000, at most 10,000, and never past 1 MB of JSON. */
|
|
42
|
+
limit?: number;
|
|
43
|
+
/** The columns to show, in order. Names an array of arrays' columns. */
|
|
44
|
+
columns?: string[];
|
|
45
|
+
/** A caption for the table. */
|
|
46
|
+
title?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Options for `jupyter.chart` with a Vega-Lite or Vega spec. */
|
|
50
|
+
export interface ChartOptions {
|
|
51
|
+
/**
|
|
52
|
+
* Also keep the spec under its Vega MIME type, for a frontend that
|
|
53
|
+
* draws Vega itself. Default `false`: the SVG and a text summary only.
|
|
54
|
+
*/
|
|
55
|
+
spec?: boolean;
|
|
26
56
|
}
|
|
27
57
|
|
|
28
58
|
/** Options for `jupyter.display`. */
|
|
@@ -283,6 +313,174 @@ export interface JupyterNamespace {
|
|
|
283
313
|
* @see jupyter-api.md#stopping-on-interrupt-jupyter-signal
|
|
284
314
|
*/
|
|
285
315
|
readonly signal: AbortSignal;
|
|
316
|
+
/**
|
|
317
|
+
* The parameters of this run: a frozen object, empty unless whoever
|
|
318
|
+
* started the kernel passed some, such as `ganymede execute -p
|
|
319
|
+
* counter="2nd Ave"`. They arrive as a JSON object in the kernel's
|
|
320
|
+
* `GANYMEDE_PARAMS` environment variable. Take defaults with
|
|
321
|
+
* destructuring, so the notebook also runs on its own.
|
|
322
|
+
*
|
|
323
|
+
* @throws {TypeError} `GANYMEDE_PARAMS` is set but isn't a JSON object.
|
|
324
|
+
* @availability Node, Deno, Bun, workerd and celld read the kernel's
|
|
325
|
+
* environment; the browser target takes `params` from its options.
|
|
326
|
+
* @example
|
|
327
|
+
* ```ts
|
|
328
|
+
* const { counter = "Fremont", days = 30 } = jupyter.params as { counter?: string; days?: number };
|
|
329
|
+
* `${counter}, last ${days} days`;
|
|
330
|
+
* ```
|
|
331
|
+
* @see jupyter-api.md#parameters-and-named-results
|
|
332
|
+
*/
|
|
333
|
+
readonly params: { readonly [name: string]: JsonValue };
|
|
334
|
+
/**
|
|
335
|
+
* Publish a value under a name, as an output that a run's caller reads
|
|
336
|
+
* by name instead of parsing outputs: `ganymede execute --results`, or
|
|
337
|
+
* another notebook. The output has the MIME type
|
|
338
|
+
* `application/vnd.ganymede.result+json` holding `{ name, value }`, and
|
|
339
|
+
* a `text/plain` line `name = value` for frontends that don't know the
|
|
340
|
+
* type. When a name is published twice, readers take the last one.
|
|
341
|
+
*
|
|
342
|
+
* @param name The result's name.
|
|
343
|
+
* @param value Any value `JSON.stringify` accepts; it is stored as JSON,
|
|
344
|
+
* so a `Date` becomes its ISO string.
|
|
345
|
+
* @throws {TypeError} `name` is empty, or `value` isn't JSON (a
|
|
346
|
+
* function, `undefined`, a `BigInt` or a cycle).
|
|
347
|
+
* @example
|
|
348
|
+
* ```ts
|
|
349
|
+
* const counts = [3120, 2890, 2410];
|
|
350
|
+
* jupyter.result("weekday_drop", (counts[2] - counts[0]) / counts[0]);
|
|
351
|
+
* jupyter.result("counts", counts);
|
|
352
|
+
* ```
|
|
353
|
+
* @see jupyter-api.md#parameters-and-named-results
|
|
354
|
+
*/
|
|
355
|
+
result(name: string, value: unknown): void;
|
|
356
|
+
/**
|
|
357
|
+
* Show rows and columns as a table. The output has Ganymede's table
|
|
358
|
+
* type, `application/vnd.dataresource+json` (Table Schema fields, up to
|
|
359
|
+
* `limit` rows as JSON, and the total row count), which the Ganymede
|
|
360
|
+
* apps show with sorting and paging; a plain HTML table of the first
|
|
361
|
+
* and last rows for JupyterLab and VS Code; and a text summary of each
|
|
362
|
+
* column's type and range for agents and consoles.
|
|
363
|
+
*
|
|
364
|
+
* An Arrow table, a Polars DataFrame or a DuckDB result also shows this
|
|
365
|
+
* way when it is a cell's value; pass one here to set `limit`,
|
|
366
|
+
* `columns` or `title`.
|
|
367
|
+
*
|
|
368
|
+
* @param value An array of row objects, an array of arrays (name them
|
|
369
|
+
* with `columns`), an Arrow table (apache-arrow, duckdb-wasm), a
|
|
370
|
+
* Polars DataFrame, or a `@duckdb/node-api` result.
|
|
371
|
+
* @param options `limit`: rows to embed, default 1,000, at most 10,000,
|
|
372
|
+
* and never more than 1 MB of JSON. `columns`: which columns, in order.
|
|
373
|
+
* `title`: a caption.
|
|
374
|
+
* @throws {TypeError} `value` isn't one of those, or `limit` isn't a
|
|
375
|
+
* whole number.
|
|
376
|
+
* @example
|
|
377
|
+
* ```ts
|
|
378
|
+
* const moons = [
|
|
379
|
+
* { moon: "Io", radius_km: 1821.6, discovered: new Date("1610-01-08") },
|
|
380
|
+
* { moon: "Europa", radius_km: 1560.8, discovered: new Date("1610-01-08") },
|
|
381
|
+
* { moon: "Ganymede", radius_km: 2634.1, discovered: new Date("1610-01-07") },
|
|
382
|
+
* ];
|
|
383
|
+
* jupyter.table(moons, { title: "Galilean moons" });
|
|
384
|
+
* ```
|
|
385
|
+
* @see jupyter-api.md#tables
|
|
386
|
+
*/
|
|
387
|
+
table(value: unknown, options?: TableOptions): Displayable;
|
|
388
|
+
/**
|
|
389
|
+
* Run SQL on an in-memory [DuckDB](https://duckdb.org) database, as a
|
|
390
|
+
* tagged template: each `${value}` becomes a bound parameter, never
|
|
391
|
+
* query text, so values can't change the query. Resolves to a
|
|
392
|
+
* `SqlResult`, which shows as a table and has `columns` and `rows`.
|
|
393
|
+
*
|
|
394
|
+
* The database opens on the first query and lasts until the kernel
|
|
395
|
+
* restarts or `jupyter.sql.close()`. DuckDB reads CSV, Parquet and JSON
|
|
396
|
+
* files by path or URL; `jupyter.sql.load` makes a table from rows the
|
|
397
|
+
* notebook already has. An interrupt stops a running query.
|
|
398
|
+
*
|
|
399
|
+
* @throws {TypeError} Called as a function rather than a tag.
|
|
400
|
+
* @availability Node, Deno and Bun use `@duckdb/node-api`; the browser
|
|
401
|
+
* target (the Ganymede apps) uses duckdb-wasm from a CDN. Not on
|
|
402
|
+
* workerd or celld.
|
|
403
|
+
* @example
|
|
404
|
+
* ```ts
|
|
405
|
+
* const min = 2000;
|
|
406
|
+
* await jupyter.sql`
|
|
407
|
+
* select moon, radius_km
|
|
408
|
+
* from (values ('Io', 1821.6), ('Ganymede', 2634.1), ('Callisto', 2410.3)) as m(moon, radius_km)
|
|
409
|
+
* where radius_km > ${min}
|
|
410
|
+
* order by radius_km desc`;
|
|
411
|
+
* ```
|
|
412
|
+
* @see jupyter-api.md#sql-with-duckdb
|
|
413
|
+
*/
|
|
414
|
+
readonly sql: Sql;
|
|
415
|
+
/**
|
|
416
|
+
* Draw a chart in the kernel as one SVG, with its title and legend
|
|
417
|
+
* inside, plus a text summary for readers that can't see it. The SVG
|
|
418
|
+
* shows the same in the Ganymede apps, JupyterLab, VS Code and a
|
|
419
|
+
* published page. Text and axes are drawn in `currentColor`: the
|
|
420
|
+
* notebook's text color in the Ganymede apps, black in JupyterLab.
|
|
421
|
+
*
|
|
422
|
+
* Pass a [Vega-Lite](https://vega.github.io/vega-lite/) or Vega spec
|
|
423
|
+
* (or a vega-lite-api chart), or the
|
|
424
|
+
* [Observable Plot](https://observablehq.com/plot/) module and the
|
|
425
|
+
* options you would give `Plot.plot`. Plot's `title`, `subtitle`,
|
|
426
|
+
* `caption` and the color legend (`legend: true`, `"swatches"` or
|
|
427
|
+
* `"ramp"`) are drawn into the SVG; other scales' legends are left
|
|
428
|
+
* out. An SVG element shows as it is.
|
|
429
|
+
*
|
|
430
|
+
* vega and vega-lite (version 6), and linkedom for Plot where there is
|
|
431
|
+
* no DOM, load through the cell's import on first use. A chart past
|
|
432
|
+
* 2 MB of SVG logs a warning: aggregate or sample first.
|
|
433
|
+
*
|
|
434
|
+
* @param chart A Vega-Lite or Vega spec, the Plot module, or an SVG
|
|
435
|
+
* element.
|
|
436
|
+
* @param options With a spec, `ChartOptions`; with Plot, Plot's options.
|
|
437
|
+
* @throws {TypeError} `chart` is none of those, or Plot comes without
|
|
438
|
+
* options.
|
|
439
|
+
* @example
|
|
440
|
+
* ```ts
|
|
441
|
+
* jupyter.chart({
|
|
442
|
+
* title: "Galilean moons",
|
|
443
|
+
* data: { values: [
|
|
444
|
+
* { moon: "Io", radius_km: 1821.6 },
|
|
445
|
+
* { moon: "Europa", radius_km: 1560.8 },
|
|
446
|
+
* { moon: "Ganymede", radius_km: 2634.1 },
|
|
447
|
+
* { moon: "Callisto", radius_km: 2410.3 },
|
|
448
|
+
* ] },
|
|
449
|
+
* mark: "bar",
|
|
450
|
+
* encoding: {
|
|
451
|
+
* x: { field: "moon", sort: "-y" },
|
|
452
|
+
* y: { field: "radius_km", type: "quantitative" },
|
|
453
|
+
* },
|
|
454
|
+
* });
|
|
455
|
+
* ```
|
|
456
|
+
* @see jupyter-api.md#charts
|
|
457
|
+
*/
|
|
458
|
+
chart(chart: object, options?: ChartOptions | Record<string, unknown>): Displayable;
|
|
459
|
+
/**
|
|
460
|
+
* The Ganymede server running this kernel, through its host API: read,
|
|
461
|
+
* search and (when granted) run other notebooks under its root. The
|
|
462
|
+
* server opens a comm to the kernel when it starts; this resolves to
|
|
463
|
+
* `null` when none answers, as under plain Jupyter.
|
|
464
|
+
*
|
|
465
|
+
* Each call returns a promise and takes `{ signal }`, by default
|
|
466
|
+
* `jupyter.signal`, so an interrupt cancels it. A failed call rejects
|
|
467
|
+
* with a `HostError` whose `code` says why: `denied` for a notebook or
|
|
468
|
+
* method this notebook isn't granted. Branch on `code`: `HostError`
|
|
469
|
+
* isn't a global, so `instanceof` isn't available.
|
|
470
|
+
*
|
|
471
|
+
* @param options `timeout`: how long to wait for a server, in ms.
|
|
472
|
+
* @throws {HostError} `cancelled` when an interrupt ends the wait.
|
|
473
|
+
* @example
|
|
474
|
+
* ```ts
|
|
475
|
+
* const host = await jupyter.host();
|
|
476
|
+
* if (host) {
|
|
477
|
+
* const { hits } = await host.notebooks.search({ query: "weekday drop", dir: "journal" });
|
|
478
|
+
* hits.map((h) => `${h.path}: ${h.excerpt}`);
|
|
479
|
+
* }
|
|
480
|
+
* ```
|
|
481
|
+
* @see jupyter-api.md#other-notebooks-jupyter-host
|
|
482
|
+
*/
|
|
483
|
+
host(options?: HostOptions): Promise<Host | null>;
|
|
286
484
|
}
|
|
287
485
|
|
|
288
486
|
export interface Jupyter {
|
package/runtime/display.mjs
CHANGED
|
@@ -4,18 +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";
|
|
22
|
+
import { createHost } from "./host.mjs";
|
|
18
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";
|
|
19
27
|
|
|
20
28
|
export const $display = Symbol.for("Jupyter.display");
|
|
21
29
|
|
|
@@ -242,66 +250,6 @@ function summarizeChart(spec) {
|
|
|
242
250
|
}
|
|
243
251
|
}
|
|
244
252
|
|
|
245
|
-
function isDataFrameLike(obj) {
|
|
246
|
-
return obj !== null && typeof obj === "object" &&
|
|
247
|
-
obj.schema !== undefined && typeof obj.schema === "object" &&
|
|
248
|
-
typeof obj.head === "function" && typeof obj.toRecords === "function";
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function mapPolarsTypeToJSONSchema(colType) {
|
|
252
|
-
const typeMapping = {
|
|
253
|
-
Null: "null",
|
|
254
|
-
Bool: "boolean",
|
|
255
|
-
Int8: "integer",
|
|
256
|
-
Int16: "integer",
|
|
257
|
-
Int32: "integer",
|
|
258
|
-
Int64: "integer",
|
|
259
|
-
UInt8: "integer",
|
|
260
|
-
UInt16: "integer",
|
|
261
|
-
UInt32: "integer",
|
|
262
|
-
UInt64: "integer",
|
|
263
|
-
Float32: "number",
|
|
264
|
-
Float64: "number",
|
|
265
|
-
Date: "string",
|
|
266
|
-
Datetime: "string",
|
|
267
|
-
Utf8: "string",
|
|
268
|
-
Categorical: "string",
|
|
269
|
-
List: "array",
|
|
270
|
-
Struct: "object",
|
|
271
|
-
};
|
|
272
|
-
// Polars DataTypes only expose their name through toJSON().
|
|
273
|
-
const dataType = typeof colType?.toJSON === "function" ? colType.toJSON()["DataType"] : undefined;
|
|
274
|
-
return typeMapping[dataType] || "string";
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function extractDataFrame(df) {
|
|
278
|
-
const fields = [];
|
|
279
|
-
for (const [name, colType] of Object.entries(df.schema)) {
|
|
280
|
-
fields.push({ name, type: mapPolarsTypeToJSONSchema(colType) });
|
|
281
|
-
}
|
|
282
|
-
const data = df.head(50).toRecords();
|
|
283
|
-
let html = "<table><thead><tr>";
|
|
284
|
-
for (const field of fields) html += `<th>${escapeHTML(String(field.name))}</th>`;
|
|
285
|
-
html += "</tr></thead><tbody>";
|
|
286
|
-
for (const row of df.head(10).toRecords()) {
|
|
287
|
-
html += "<tr>";
|
|
288
|
-
for (const field of fields) html += `<td>${escapeHTML(String(row[field.name]))}</td>`;
|
|
289
|
-
html += "</tr>";
|
|
290
|
-
}
|
|
291
|
-
html += "</tbody></table>";
|
|
292
|
-
// Polars has `height`; the row count is left out when there isn't one.
|
|
293
|
-
const size = [];
|
|
294
|
-
if (Number.isInteger(df.height)) size.push(`${df.height} ${df.height === 1 ? "row" : "rows"}`);
|
|
295
|
-
size.push(`${fields.length} ${fields.length === 1 ? "column" : "columns"}`);
|
|
296
|
-
const columns = fields.slice(0, 20).map((field) => `${clip(String(field.name), 40)} (${field.type})`);
|
|
297
|
-
if (fields.length > 20) columns.push(`… ${fields.length - 20} more`);
|
|
298
|
-
return {
|
|
299
|
-
"application/vnd.dataresource+json": { data, schema: { fields } },
|
|
300
|
-
"text/html": html,
|
|
301
|
-
"text/plain": `DataFrame (${size.join(", ")})${columns.length ? `: ${columns.join(", ")}` : ""}`,
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
|
|
305
253
|
function isCanvasLike(obj) {
|
|
306
254
|
return obj !== null && typeof obj === "object" && "toDataURL" in obj;
|
|
307
255
|
}
|
|
@@ -343,8 +291,20 @@ function isMediaBundle(obj) {
|
|
|
343
291
|
* readFile: (path: string) => Uint8Array,
|
|
344
292
|
* send: (msgType: string, content: object, metadata: object, buffers: Uint8Array[]) => void,
|
|
345
293
|
* input: (prompt: string, password: boolean) => string | null,
|
|
294
|
+
* params?: () => string | undefined,
|
|
346
295
|
* }} host
|
|
347
296
|
*/
|
|
297
|
+
/** The MIME type of a named result (cdp-kernel#456): `{ name, value }`. */
|
|
298
|
+
export const RESULT_MIME = "application/vnd.ganymede.result+json";
|
|
299
|
+
|
|
300
|
+
function deepFreeze(value) {
|
|
301
|
+
if (typeof value === "object" && value !== null) {
|
|
302
|
+
for (const v of Object.values(value)) deepFreeze(v);
|
|
303
|
+
Object.freeze(value);
|
|
304
|
+
}
|
|
305
|
+
return value;
|
|
306
|
+
}
|
|
307
|
+
|
|
348
308
|
export function createJupyter(host) {
|
|
349
309
|
async function format(obj) {
|
|
350
310
|
return await formatNow(obj);
|
|
@@ -364,7 +324,8 @@ export function createJupyter(host) {
|
|
|
364
324
|
const vega = extractVega(obj);
|
|
365
325
|
if (vega) return vega;
|
|
366
326
|
}
|
|
367
|
-
|
|
327
|
+
// Arrow tables, Polars DataFrames and DuckDB results (#475).
|
|
328
|
+
if (isTableLike(obj)) return tableBundle(tableSource(obj));
|
|
368
329
|
if (isJpg(obj)) return { "image/jpeg": host.base64(obj) };
|
|
369
330
|
if (isPng(obj)) return { "image/png": host.base64(obj) };
|
|
370
331
|
if (isSVGElementLike(obj)) return { "image/svg+xml": obj.outerHTML };
|
|
@@ -396,6 +357,13 @@ export function createJupyter(host) {
|
|
|
396
357
|
);
|
|
397
358
|
}
|
|
398
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
|
+
|
|
399
367
|
function broadcast(msgType, content, { metadata = {}, buffers = [] } = {}) {
|
|
400
368
|
host.send(msgType, content, metadata, buffers);
|
|
401
369
|
}
|
|
@@ -439,6 +407,61 @@ export function createJupyter(host) {
|
|
|
439
407
|
return answer === "" ? defaultValue : answer;
|
|
440
408
|
}
|
|
441
409
|
|
|
410
|
+
// jupyter.params (cdp-kernel#456): the run's parameters, which the host
|
|
411
|
+
// (ganymede execute -p) passes as JSON in GANYMEDE_PARAMS. Parsed on first
|
|
412
|
+
// read, so a bad value fails the cell that reads it, not the kernel.
|
|
413
|
+
let params;
|
|
414
|
+
function readParams() {
|
|
415
|
+
if (params !== undefined) return params;
|
|
416
|
+
const text = host.params?.();
|
|
417
|
+
if (text === undefined || text === "") return (params = Object.freeze({}));
|
|
418
|
+
let value;
|
|
419
|
+
try {
|
|
420
|
+
value = JSON.parse(text);
|
|
421
|
+
} catch (err) {
|
|
422
|
+
throw new TypeError(`GANYMEDE_PARAMS is not JSON: ${err instanceof Error ? err.message : err}`);
|
|
423
|
+
}
|
|
424
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
425
|
+
throw new TypeError("GANYMEDE_PARAMS must be a JSON object, such as {\"counter\": \"Fremont\"}");
|
|
426
|
+
}
|
|
427
|
+
return (params = deepFreeze(value));
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// jupyter.result (cdp-kernel#456): a named value as an output of
|
|
431
|
+
// Ganymede's result type, which a run's caller reads without parsing
|
|
432
|
+
// outputs. JSON only, so every reader sees the same value.
|
|
433
|
+
function result(name, value) {
|
|
434
|
+
if (typeof name !== "string" || name === "") throw new TypeError("jupyter.result: name must be a non-empty string");
|
|
435
|
+
let text;
|
|
436
|
+
try {
|
|
437
|
+
text = JSON.stringify(value);
|
|
438
|
+
} catch (err) {
|
|
439
|
+
throw new TypeError(`jupyter.result: ${name} is not JSON: ${err instanceof Error ? err.message : err}`);
|
|
440
|
+
}
|
|
441
|
+
if (text === undefined) throw new TypeError(`jupyter.result: ${name} is not JSON (${typeof value})`);
|
|
442
|
+
const json = JSON.parse(text);
|
|
443
|
+
broadcast("display_data", {
|
|
444
|
+
data: { [RESULT_MIME]: { name, value: json }, "text/plain": `${name} = ${text}` },
|
|
445
|
+
metadata: {},
|
|
446
|
+
transient: {},
|
|
447
|
+
});
|
|
448
|
+
}
|
|
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
|
+
|
|
442
465
|
const comms = createComms((msgType, content, metadata, buffers) => host.send(msgType, content, metadata, buffers));
|
|
443
466
|
|
|
444
467
|
// jupyter.signal (#418): aborted when the kernel is interrupted, so cell
|
|
@@ -462,6 +485,11 @@ export function createJupyter(host) {
|
|
|
462
485
|
for (const controller of handlers.keys()) controller.abort(reason);
|
|
463
486
|
handlers.clear();
|
|
464
487
|
}
|
|
488
|
+
// jupyter.host() (#461): Ganymede's host API. Registers its comm target
|
|
489
|
+
// now, so the comm the server opens at kernel start finds it. A call
|
|
490
|
+
// without a signal watches the jupyter.signal of whatever started it.
|
|
491
|
+
const hostApi = createHost(comms.api, () => current.signal);
|
|
492
|
+
|
|
465
493
|
async function commDispatch(msgType, content, metadata, buffers, ownSignal = false) {
|
|
466
494
|
// A handler during a cell shares the cell's signal, and is tracked
|
|
467
495
|
// apart from it: it can outlive the cell.
|
|
@@ -484,6 +512,13 @@ export function createJupyter(host) {
|
|
|
484
512
|
},
|
|
485
513
|
broadcast,
|
|
486
514
|
display,
|
|
515
|
+
get params() {
|
|
516
|
+
return readParams();
|
|
517
|
+
},
|
|
518
|
+
result,
|
|
519
|
+
table,
|
|
520
|
+
sql,
|
|
521
|
+
chart,
|
|
487
522
|
format,
|
|
488
523
|
md: taggedTemplate("text/markdown"),
|
|
489
524
|
html: taggedTemplate("text/html"),
|
|
@@ -491,6 +526,7 @@ export function createJupyter(host) {
|
|
|
491
526
|
image,
|
|
492
527
|
$display,
|
|
493
528
|
comm: comms.api,
|
|
529
|
+
host: hostApi.host,
|
|
494
530
|
widget: createWidgetFactory(comms.api, $display),
|
|
495
531
|
},
|
|
496
532
|
broadcastResult,
|