@tlockney/cdp-kernel 0.11.0 → 0.12.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 +8 -2
- package/dist/browser/kernel.js.map +2 -2
- package/dist/browser/mod.d.ts +11 -0
- package/dist/browser/mod.js +11 -0
- package/dist/browser/target.d.ts +10 -0
- package/dist/browser/target.js +1 -1
- package/dist/browser/worker.js +154 -7
- package/dist/browser/worker.js.map +4 -4
- package/dist/package.d.ts +3 -3
- package/dist/package.js +3 -2
- package/dist/workerd-target.js +1 -1
- package/package.json +1 -1
- package/runtime/browser-worker.mjs +6 -3
- package/runtime/cdn.d.mts +6 -1
- package/runtime/cdn.mjs +7 -2
- package/runtime/display.d.mts +136 -135
- package/runtime/display.mjs +10 -3
- package/runtime/table-paging.d.mts +19 -0
- package/runtime/table-paging.mjs +183 -0
- package/runtime/table.d.mts +14 -0
- package/runtime/table.mjs +31 -2
- package/runtime/widget.d.mts +3 -3
package/dist/package.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/** Kept equal to package.json and deno.jsonc by test/package.test.ts. */
|
|
2
|
-
export declare const VERSION = "0.
|
|
2
|
+
export declare const VERSION = "0.12.0";
|
|
3
3
|
/**
|
|
4
4
|
* The UTC day this version was released, set with VERSION. The publish
|
|
5
5
|
* workflow refuses a tag unless it's today or yesterday, so it can't go
|
|
6
6
|
* stale: see dependencyCutoff.
|
|
7
7
|
*/
|
|
8
|
-
export declare const RELEASE_DATE = "2026-11-
|
|
8
|
+
export declare const RELEASE_DATE = "2026-11-14";
|
|
9
9
|
/**
|
|
10
10
|
* Deno blocks packages less than 24 hours old (minimum dependency age), so
|
|
11
11
|
* a kernelspec relaunching a release from JSR would fail on release day. It
|
|
@@ -14,7 +14,7 @@ export declare const RELEASE_DATE = "2026-11-13";
|
|
|
14
14
|
* late in the day its tag was pushed), and nothing newer ever is.
|
|
15
15
|
*/
|
|
16
16
|
export declare function dependencyCutoff(releaseDate?: string): string;
|
|
17
|
-
export declare const RUNTIME_FILES: readonly ["bootstrap.mjs", "bun-channel.mjs", "evaluator.mjs", "introspect.mjs", "workerd-worker.mjs", "display.mjs", "comm.mjs", "host.mjs", "widget.mjs", "table.mjs", "sql.mjs", "chart.mjs", "node-hooks.mjs", "deno-compat.mjs", "node-modules-resolve.mjs"];
|
|
17
|
+
export declare const RUNTIME_FILES: readonly ["bootstrap.mjs", "bun-channel.mjs", "evaluator.mjs", "introspect.mjs", "workerd-worker.mjs", "display.mjs", "comm.mjs", "host.mjs", "widget.mjs", "table.mjs", "table-paging.mjs", "sql.mjs", "chart.mjs", "node-hooks.mjs", "deno-compat.mjs", "node-modules-resolve.mjs"];
|
|
18
18
|
export type HostRuntime = "node" | "deno";
|
|
19
19
|
export declare function hostRuntime(): HostRuntime;
|
|
20
20
|
/** Where the CLI module was loaded from. */
|
package/dist/package.js
CHANGED
|
@@ -14,13 +14,13 @@ import { join, sep } from "node:path";
|
|
|
14
14
|
import process from "node:process";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
16
|
/** Kept equal to package.json and deno.jsonc by test/package.test.ts. */
|
|
17
|
-
export const VERSION = "0.
|
|
17
|
+
export const VERSION = "0.12.0";
|
|
18
18
|
/**
|
|
19
19
|
* The UTC day this version was released, set with VERSION. The publish
|
|
20
20
|
* workflow refuses a tag unless it's today or yesterday, so it can't go
|
|
21
21
|
* stale: see dependencyCutoff.
|
|
22
22
|
*/
|
|
23
|
-
export const RELEASE_DATE = "2026-11-
|
|
23
|
+
export const RELEASE_DATE = "2026-11-14";
|
|
24
24
|
/**
|
|
25
25
|
* Deno blocks packages less than 24 hours old (minimum dependency age), so
|
|
26
26
|
* a kernelspec relaunching a release from JSR would fail on release day. It
|
|
@@ -46,6 +46,7 @@ export const RUNTIME_FILES = [
|
|
|
46
46
|
"host.mjs",
|
|
47
47
|
"widget.mjs",
|
|
48
48
|
"table.mjs",
|
|
49
|
+
"table-paging.mjs",
|
|
49
50
|
"sql.mjs",
|
|
50
51
|
"chart.mjs",
|
|
51
52
|
"node-hooks.mjs",
|
package/dist/workerd-target.js
CHANGED
|
@@ -35,7 +35,7 @@ import { connectUnixWebSocket } from "./ws-client.js";
|
|
|
35
35
|
import { findWranglerConfig, startMiniflare } from "./workers-project.js";
|
|
36
36
|
import { ensureInstalledAsync, findLockFile, jsrToNpm, parsePackageSpecifier } from "../runtime/node-hooks.mjs";
|
|
37
37
|
/** The driver Worker's modules; the first is its main module. */
|
|
38
|
-
export const WORKER_MODULES = ["workerd-worker.mjs", "display.mjs", "comm.mjs", "host.mjs", "widget.mjs", "table.mjs", "sql.mjs", "chart.mjs", "evaluator.mjs", "introspect.mjs"];
|
|
38
|
+
export const WORKER_MODULES = ["workerd-worker.mjs", "display.mjs", "comm.mjs", "host.mjs", "widget.mjs", "table.mjs", "table-paging.mjs", "sql.mjs", "chart.mjs", "evaluator.mjs", "introspect.mjs"];
|
|
39
39
|
/** See BunTarget: how long an interrupted cell has to show it isn't busy. */
|
|
40
40
|
export const INTERRUPT_GRACE_MS = 500;
|
|
41
41
|
export function restartNote(runtime) {
|
package/package.json
CHANGED
|
@@ -72,6 +72,8 @@ let paramsText;
|
|
|
72
72
|
|
|
73
73
|
// --- Modules ----------------------------------------------------------------
|
|
74
74
|
let cdn = DEFAULT_CDN;
|
|
75
|
+
/** Specifiers the page serves itself, such as jupyter.chart's libraries in the Ganymede apps. */
|
|
76
|
+
let modules;
|
|
75
77
|
|
|
76
78
|
|
|
77
79
|
// --- The host page (#384) -----------------------------------------------------
|
|
@@ -153,13 +155,14 @@ async function preload(urls, base) {
|
|
|
153
155
|
// --- The shim cells and the evaluator use -----------------------------------
|
|
154
156
|
let handle = null;
|
|
155
157
|
|
|
156
|
-
function install({ base, cdn: cdnBase, params }) {
|
|
158
|
+
function install({ base, cdn: cdnBase, modules: moduleMap, params }) {
|
|
157
159
|
if (typeof params === "string") paramsText = params;
|
|
158
160
|
if (typeof cdnBase === "string") cdn = cdnBase.endsWith("/") ? cdnBase : cdnBase + "/";
|
|
161
|
+
if (moduleMap !== null && typeof moduleMap === "object") modules = { ...moduleMap };
|
|
159
162
|
const url = new URL("$cdpk$repl.mjs", base).href;
|
|
160
163
|
globalThis.__cdpk = {
|
|
161
|
-
import: (specifier, options) => import(resolveSpecifier(specifier, url, cdn), options),
|
|
162
|
-
meta: { url, resolve: (specifier) => resolveSpecifier(specifier, url, cdn) },
|
|
164
|
+
import: (specifier, options) => import(resolveSpecifier(specifier, url, cdn, modules), options),
|
|
165
|
+
meta: { url, resolve: (specifier) => resolveSpecifier(specifier, url, cdn, modules) },
|
|
163
166
|
// inspect_request (#10): by descriptor, so nothing runs.
|
|
164
167
|
describeForInspect: (value, detail) => describeValue(value, { inspect: (v) => inspect(v, { depth: 2 }), detail }),
|
|
165
168
|
inspectPath: (path, detail) => inspectPath(path, { inspect: (v) => inspect(v, { depth: 2 }), detail }),
|
package/runtime/cdn.d.mts
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
export const DEFAULT_CDN: string;
|
|
2
|
-
export function resolveSpecifier(
|
|
2
|
+
export function resolveSpecifier(
|
|
3
|
+
specifier: string,
|
|
4
|
+
base: string,
|
|
5
|
+
cdn?: string,
|
|
6
|
+
modules?: { readonly [specifier: string]: string },
|
|
7
|
+
): string;
|
package/runtime/cdn.mjs
CHANGED
|
@@ -6,8 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
export const DEFAULT_CDN = "https://esm.sh/";
|
|
8
8
|
|
|
9
|
-
/**
|
|
10
|
-
|
|
9
|
+
/**
|
|
10
|
+
* The URL `specifier`, imported from `base`, loads from. `modules` maps
|
|
11
|
+
* specifiers, as written, to URLs (resolved against `base`) that win over
|
|
12
|
+
* the CDN: an app ships the packages it needs offline this way.
|
|
13
|
+
*/
|
|
14
|
+
export function resolveSpecifier(specifier, base, cdn = DEFAULT_CDN, modules = undefined) {
|
|
15
|
+
if (modules && Object.hasOwn(modules, specifier)) return new URL(modules[specifier], base).href;
|
|
11
16
|
if (/^(?:https?|data|blob):/.test(specifier)) return specifier;
|
|
12
17
|
if (/^\.{0,2}\//.test(specifier)) return new URL(specifier, base).href;
|
|
13
18
|
if (specifier.startsWith("npm:")) return cdn + specifier.slice(4);
|
package/runtime/display.d.mts
CHANGED
|
@@ -83,33 +83,6 @@ export interface Displayable {
|
|
|
83
83
|
* as `Deno.jupyter` in `deno jupyter`.
|
|
84
84
|
*/
|
|
85
85
|
export interface JupyterNamespace {
|
|
86
|
-
/**
|
|
87
|
-
* Send a raw Jupyter message on the kernel's output channel (IOPub), as a
|
|
88
|
-
* reply to the running cell. The other helpers are built on it. It does
|
|
89
|
-
* not check the message: a wrong field is ignored or mishandled by the
|
|
90
|
-
* frontend.
|
|
91
|
-
*
|
|
92
|
-
* @param msgType An IOPub message type, such as `"clear_output"` or
|
|
93
|
-
* `"display_data"`.
|
|
94
|
-
* @param content The message content, as the messaging spec defines it
|
|
95
|
-
* for `msgType`.
|
|
96
|
-
* @param options `metadata` for the message, and binary `buffers`.
|
|
97
|
-
* @example
|
|
98
|
-
* ```ts
|
|
99
|
-
* // Count down in place: clear_output with wait avoids a flicker.
|
|
100
|
-
* for (const n of [3, 2, 1]) {
|
|
101
|
-
* jupyter.broadcast("clear_output", { wait: true });
|
|
102
|
-
* await jupyter.display(jupyter.md`Launching in **${n}**…`);
|
|
103
|
-
* await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
104
|
-
* }
|
|
105
|
-
* ```
|
|
106
|
-
* @see jupyter-api.md#raw-messages-with-broadcast
|
|
107
|
-
*/
|
|
108
|
-
broadcast(
|
|
109
|
-
msgType: string,
|
|
110
|
-
content: object,
|
|
111
|
-
options?: { metadata?: object; buffers?: Uint8Array[] },
|
|
112
|
-
): void;
|
|
113
86
|
/**
|
|
114
87
|
* Show a value now, in the middle of a cell, or replace one shown
|
|
115
88
|
* earlier. The value can be anything a cell could end with: a template
|
|
@@ -142,22 +115,6 @@ export interface JupyterNamespace {
|
|
|
142
115
|
* @see jupyter-api.md#showing-output-with-display
|
|
143
116
|
*/
|
|
144
117
|
display(obj: unknown, options?: DisplayOptions): Promise<void>;
|
|
145
|
-
/**
|
|
146
|
-
* The MIME bundle that displaying a value would send, without showing
|
|
147
|
-
* anything. For plain values `text/plain` holds terminal color codes,
|
|
148
|
-
* as the notebook shows them.
|
|
149
|
-
*
|
|
150
|
-
* @param obj The value to format.
|
|
151
|
-
* @returns The bundle, for example `{ "text/plain": "\x1b[33m42\x1b[39m" }`
|
|
152
|
-
* for `42`.
|
|
153
|
-
* @example
|
|
154
|
-
* ```ts
|
|
155
|
-
* const bundle = await jupyter.format(jupyter.md`# Weekly report`);
|
|
156
|
-
* bundle["text/markdown"]; // "# Weekly report"
|
|
157
|
-
* ```
|
|
158
|
-
* @see jupyter-api.md#getting-a-bundle-with-format
|
|
159
|
-
*/
|
|
160
|
-
format(obj: unknown): Promise<MediaBundle>;
|
|
161
118
|
/**
|
|
162
119
|
* A tagged template that renders as Markdown. Interpolated values are
|
|
163
120
|
* inserted as is, without escaping.
|
|
@@ -250,109 +207,48 @@ export interface JupyterNamespace {
|
|
|
250
207
|
*/
|
|
251
208
|
$display: typeof $display;
|
|
252
209
|
/**
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
* @see widgets.md#comms
|
|
257
|
-
*/
|
|
258
|
-
comm: CommApi;
|
|
259
|
-
/**
|
|
260
|
-
* Create an anywidget-compatible widget model. Display it (or end the
|
|
261
|
-
* cell with it) to show a view.
|
|
262
|
-
*
|
|
263
|
-
* @param options The view module, its CSS, and the initial state.
|
|
264
|
-
* @returns The model, synced with every view of it.
|
|
265
|
-
* @throws {TypeError} `options.esm` is not a string.
|
|
266
|
-
* @availability The frontend needs the anywidget extension.
|
|
267
|
-
* @example
|
|
268
|
-
* ```ts
|
|
269
|
-
* const counter = jupyter.widget({
|
|
270
|
-
* esm: `export default {
|
|
271
|
-
* render({ model, el }) {
|
|
272
|
-
* const button = document.createElement("button");
|
|
273
|
-
* const update = () => {
|
|
274
|
-
* button.textContent = "count is " + model.get("count");
|
|
275
|
-
* };
|
|
276
|
-
* button.onclick = () => {
|
|
277
|
-
* model.set("count", model.get("count") + 1);
|
|
278
|
-
* model.save_changes();
|
|
279
|
-
* };
|
|
280
|
-
* model.on("change:count", update);
|
|
281
|
-
* update();
|
|
282
|
-
* el.append(button);
|
|
283
|
-
* },
|
|
284
|
-
* };`,
|
|
285
|
-
* state: { count: 0 },
|
|
286
|
-
* });
|
|
287
|
-
* counter; // the cell's result: displays the widget
|
|
288
|
-
* ```
|
|
289
|
-
* 
|
|
290
|
-
* @see widgets.md#a-first-widget-a-counter
|
|
291
|
-
*/
|
|
292
|
-
widget(options: WidgetOptions): Widget;
|
|
293
|
-
/**
|
|
294
|
-
* An `AbortSignal` that an interrupt aborts, so code waiting on something
|
|
295
|
-
* only it can end can stop and clean up. Each cell gets its own, as does
|
|
296
|
-
* a comm handler that starts while no cell runs. Read it once when the
|
|
297
|
-
* work starts: a later read gets the signal of whatever started last.
|
|
298
|
-
* The abort reason is a `DOMException` named `AbortError`.
|
|
299
|
-
*
|
|
300
|
-
* @example
|
|
301
|
-
* ```ts
|
|
302
|
-
* const response = await fetch("https://example.com/large.csv", { signal: jupyter.signal });
|
|
303
|
-
* ```
|
|
304
|
-
* @example
|
|
305
|
-
* ```ts
|
|
306
|
-
* const signal = jupyter.signal;
|
|
307
|
-
* const ask = jupyter.comm.open("my.app", { question: "ready?" });
|
|
308
|
-
* const answer = await new Promise((resolve, reject) => {
|
|
309
|
-
* ask.onMsg((m) => resolve(m.data));
|
|
310
|
-
* signal.addEventListener("abort", () => { ask.close(); reject(signal.reason); }, { once: true });
|
|
311
|
-
* });
|
|
312
|
-
* ```
|
|
313
|
-
* @see jupyter-api.md#stopping-on-interrupt-jupyter-signal
|
|
314
|
-
*/
|
|
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.
|
|
210
|
+
* The MIME bundle that displaying a value would send, without showing
|
|
211
|
+
* anything. For plain values `text/plain` holds terminal color codes,
|
|
212
|
+
* as the notebook shows them.
|
|
322
213
|
*
|
|
323
|
-
* @
|
|
324
|
-
* @
|
|
325
|
-
*
|
|
214
|
+
* @param obj The value to format.
|
|
215
|
+
* @returns The bundle, for example `{ "text/plain": "\x1b[33m42\x1b[39m" }`
|
|
216
|
+
* for `42`.
|
|
326
217
|
* @example
|
|
327
218
|
* ```ts
|
|
328
|
-
* const
|
|
329
|
-
*
|
|
219
|
+
* const bundle = await jupyter.format(jupyter.md`# Weekly report`);
|
|
220
|
+
* bundle["text/markdown"]; // "# Weekly report"
|
|
330
221
|
* ```
|
|
331
|
-
* @see jupyter-api.md#
|
|
222
|
+
* @see jupyter-api.md#getting-a-bundle-with-format
|
|
332
223
|
*/
|
|
333
|
-
|
|
224
|
+
format(obj: unknown): Promise<MediaBundle>;
|
|
334
225
|
/**
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
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.
|
|
226
|
+
* Send a raw Jupyter message on the kernel's output channel (IOPub), as a
|
|
227
|
+
* reply to the running cell. The other helpers are built on it. It does
|
|
228
|
+
* not check the message: a wrong field is ignored or mishandled by the
|
|
229
|
+
* frontend.
|
|
341
230
|
*
|
|
342
|
-
* @param
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
231
|
+
* @param msgType An IOPub message type, such as `"clear_output"` or
|
|
232
|
+
* `"display_data"`.
|
|
233
|
+
* @param content The message content, as the messaging spec defines it
|
|
234
|
+
* for `msgType`.
|
|
235
|
+
* @param options `metadata` for the message, and binary `buffers`.
|
|
347
236
|
* @example
|
|
348
237
|
* ```ts
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
238
|
+
* // Count down in place: clear_output with wait avoids a flicker.
|
|
239
|
+
* for (const n of [3, 2, 1]) {
|
|
240
|
+
* jupyter.broadcast("clear_output", { wait: true });
|
|
241
|
+
* await jupyter.display(jupyter.md`Launching in **${n}**…`);
|
|
242
|
+
* await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
243
|
+
* }
|
|
352
244
|
* ```
|
|
353
|
-
* @see jupyter-api.md#
|
|
245
|
+
* @see jupyter-api.md#raw-messages-with-broadcast
|
|
354
246
|
*/
|
|
355
|
-
|
|
247
|
+
broadcast(
|
|
248
|
+
msgType: string,
|
|
249
|
+
content: object,
|
|
250
|
+
options?: { metadata?: object; buffers?: Uint8Array[] },
|
|
251
|
+
): void;
|
|
356
252
|
/**
|
|
357
253
|
* Show rows and columns as a table. The output has Ganymede's table
|
|
358
254
|
* type, `application/vnd.dataresource+json` (Table Schema fields, up to
|
|
@@ -456,6 +352,111 @@ export interface JupyterNamespace {
|
|
|
456
352
|
* @see jupyter-api.md#charts
|
|
457
353
|
*/
|
|
458
354
|
chart(chart: object, options?: ChartOptions | Record<string, unknown>): Displayable;
|
|
355
|
+
/**
|
|
356
|
+
* An `AbortSignal` that an interrupt aborts, so code waiting on something
|
|
357
|
+
* only it can end can stop and clean up. Each cell gets its own, as does
|
|
358
|
+
* a comm handler that starts while no cell runs. Read it once when the
|
|
359
|
+
* work starts: a later read gets the signal of whatever started last.
|
|
360
|
+
* The abort reason is a `DOMException` named `AbortError`.
|
|
361
|
+
*
|
|
362
|
+
* @example
|
|
363
|
+
* ```ts
|
|
364
|
+
* const response = await fetch("https://example.com/large.csv", { signal: jupyter.signal });
|
|
365
|
+
* ```
|
|
366
|
+
* @example
|
|
367
|
+
* ```ts
|
|
368
|
+
* const signal = jupyter.signal;
|
|
369
|
+
* const ask = jupyter.comm.open("my.app", { question: "ready?" });
|
|
370
|
+
* const answer = await new Promise((resolve, reject) => {
|
|
371
|
+
* ask.onMsg((m) => resolve(m.data));
|
|
372
|
+
* signal.addEventListener("abort", () => { ask.close(); reject(signal.reason); }, { once: true });
|
|
373
|
+
* });
|
|
374
|
+
* ```
|
|
375
|
+
* @see jupyter-api.md#stopping-on-interrupt-jupyter-signal
|
|
376
|
+
*/
|
|
377
|
+
readonly signal: AbortSignal;
|
|
378
|
+
/**
|
|
379
|
+
* Jupyter comms: message channels to the frontend outside cell
|
|
380
|
+
* execution. Its methods are in the widgets and comms reference.
|
|
381
|
+
*
|
|
382
|
+
* @see widgets.md#comms
|
|
383
|
+
*/
|
|
384
|
+
comm: CommApi;
|
|
385
|
+
/**
|
|
386
|
+
* Create an [anywidget](https://anywidget.dev)-compatible [widget
|
|
387
|
+
* model](widgets-reference.md#widget-model). Display it (or end the
|
|
388
|
+
* cell with it) to show a view.
|
|
389
|
+
*
|
|
390
|
+
* @param options The view module, its CSS, and the initial state.
|
|
391
|
+
* @returns The model, synced with every view of it.
|
|
392
|
+
* @throws {TypeError} `options.esm` is not a string.
|
|
393
|
+
* @availability The frontend needs the [anywidget](https://anywidget.dev) extension.
|
|
394
|
+
* @example
|
|
395
|
+
* ```ts
|
|
396
|
+
* const counter = jupyter.widget({
|
|
397
|
+
* esm: `export default {
|
|
398
|
+
* render({ model, el }) {
|
|
399
|
+
* const button = document.createElement("button");
|
|
400
|
+
* const update = () => {
|
|
401
|
+
* button.textContent = "count is " + model.get("count");
|
|
402
|
+
* };
|
|
403
|
+
* button.onclick = () => {
|
|
404
|
+
* model.set("count", model.get("count") + 1);
|
|
405
|
+
* model.save_changes();
|
|
406
|
+
* };
|
|
407
|
+
* model.on("change:count", update);
|
|
408
|
+
* update();
|
|
409
|
+
* el.append(button);
|
|
410
|
+
* },
|
|
411
|
+
* };`,
|
|
412
|
+
* state: { count: 0 },
|
|
413
|
+
* });
|
|
414
|
+
* counter; // the cell's result: displays the widget
|
|
415
|
+
* ```
|
|
416
|
+
* 
|
|
417
|
+
* @see widgets.md#a-first-widget-a-counter
|
|
418
|
+
*/
|
|
419
|
+
widget(options: WidgetOptions): Widget;
|
|
420
|
+
/**
|
|
421
|
+
* The parameters of this run: a frozen object, empty unless whoever
|
|
422
|
+
* started the kernel passed some, such as `ganymede execute -p
|
|
423
|
+
* counter="2nd Ave"`. They arrive as a JSON object in the kernel's
|
|
424
|
+
* `GANYMEDE_PARAMS` environment variable. Take defaults with
|
|
425
|
+
* destructuring, so the notebook also runs on its own.
|
|
426
|
+
*
|
|
427
|
+
* @throws {TypeError} `GANYMEDE_PARAMS` is set but isn't a JSON object.
|
|
428
|
+
* @availability Node, Deno, Bun, workerd and celld read the kernel's
|
|
429
|
+
* environment; the browser target takes `params` from its options.
|
|
430
|
+
* @example
|
|
431
|
+
* ```ts
|
|
432
|
+
* const { counter = "Fremont", days = 30 } = jupyter.params as { counter?: string; days?: number };
|
|
433
|
+
* `${counter}, last ${days} days`;
|
|
434
|
+
* ```
|
|
435
|
+
* @see jupyter-api.md#parameters-and-named-results
|
|
436
|
+
*/
|
|
437
|
+
readonly params: { readonly [name: string]: JsonValue };
|
|
438
|
+
/**
|
|
439
|
+
* Publish a value under a name, as an output that a run's caller reads
|
|
440
|
+
* by name instead of parsing outputs: `ganymede execute --results`, or
|
|
441
|
+
* another notebook. The output has the MIME type
|
|
442
|
+
* `application/vnd.ganymede.result+json` holding `{ name, value }`, and
|
|
443
|
+
* a `text/plain` line `name = value` for frontends that don't know the
|
|
444
|
+
* type. When a name is published twice, readers take the last one.
|
|
445
|
+
*
|
|
446
|
+
* @param name The result's name.
|
|
447
|
+
* @param value Any value `JSON.stringify` accepts; it is stored as JSON,
|
|
448
|
+
* so a `Date` becomes its ISO string.
|
|
449
|
+
* @throws {TypeError} `name` is empty, or `value` isn't JSON (a
|
|
450
|
+
* function, `undefined`, a `BigInt` or a cycle).
|
|
451
|
+
* @example
|
|
452
|
+
* ```ts
|
|
453
|
+
* const counts = [3120, 2890, 2410];
|
|
454
|
+
* jupyter.result("weekday_drop", (counts[2] - counts[0]) / counts[0]);
|
|
455
|
+
* jupyter.result("counts", counts);
|
|
456
|
+
* ```
|
|
457
|
+
* @see jupyter-api.md#parameters-and-named-results
|
|
458
|
+
*/
|
|
459
|
+
result(name: string, value: unknown): void;
|
|
459
460
|
/**
|
|
460
461
|
* The Ganymede server running this kernel, through its host API: read,
|
|
461
462
|
* search and (when granted) run other notebooks under its root. The
|
package/runtime/display.mjs
CHANGED
|
@@ -23,7 +23,8 @@ import { createHost } from "./host.mjs";
|
|
|
23
23
|
import { createWidgetFactory } from "./widget.mjs";
|
|
24
24
|
import { createSql } from "./sql.mjs";
|
|
25
25
|
import { createChart } from "./chart.mjs";
|
|
26
|
-
import { isTableLike, tableBundle, tableSource } from "./table.mjs";
|
|
26
|
+
import { isTableLike, pageTable, tableBundle, tableSource } from "./table.mjs";
|
|
27
|
+
import { createTablePager } from "./table-paging.mjs";
|
|
27
28
|
|
|
28
29
|
export const $display = Symbol.for("Jupyter.display");
|
|
29
30
|
|
|
@@ -368,10 +369,14 @@ export function createJupyter(host) {
|
|
|
368
369
|
host.send(msgType, content, metadata, buffers);
|
|
369
370
|
}
|
|
370
371
|
|
|
372
|
+
// Set below, once the comms are: a table cut by the cap is kept for
|
|
373
|
+
// paging when it is sent as an output, not when it is formatted.
|
|
374
|
+
let pager;
|
|
375
|
+
|
|
371
376
|
async function display(obj, options = { raw: false, update: false }) {
|
|
372
377
|
const bundle = options.raw && isMediaBundle(obj) ? obj : await format(obj);
|
|
373
378
|
broadcast(options.update ? "update_display_data" : "display_data", {
|
|
374
|
-
data: bundle,
|
|
379
|
+
data: pageTable(bundle, pager, options.display_id),
|
|
375
380
|
metadata: {},
|
|
376
381
|
transient: options.display_id ? { display_id: options.display_id } : {},
|
|
377
382
|
});
|
|
@@ -382,7 +387,7 @@ export function createJupyter(host) {
|
|
|
382
387
|
try {
|
|
383
388
|
broadcast("execute_result", {
|
|
384
389
|
execution_count: executionCount,
|
|
385
|
-
data: await format(result),
|
|
390
|
+
data: pageTable(await format(result), pager),
|
|
386
391
|
metadata: {},
|
|
387
392
|
// nbclient crashes on a null `transient`; Deno learned this too.
|
|
388
393
|
transient: {},
|
|
@@ -463,6 +468,8 @@ export function createJupyter(host) {
|
|
|
463
468
|
const chart = createChart({ importModule: cellImport("jupyter.chart"), summarizeSpec: summarizeChart });
|
|
464
469
|
|
|
465
470
|
const comms = createComms((msgType, content, metadata, buffers) => host.send(msgType, content, metadata, buffers));
|
|
471
|
+
// Tables cut by the cap page through the kernel over a comm (ganymede#747).
|
|
472
|
+
pager = createTablePager(comms.api);
|
|
466
473
|
|
|
467
474
|
// jupyter.signal (#418): aborted when the kernel is interrupted, so cell
|
|
468
475
|
// code can stop waiting on what the interrupt can't reach, such as a comm
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CommApi } from "./comm.d.mts";
|
|
2
|
+
import type { TablePager } from "./table.d.mts";
|
|
3
|
+
|
|
4
|
+
/** The comm target a frontend opens, with `{ id }`, to page through a table. */
|
|
5
|
+
export const PAGE_TARGET: "ganymede.table.v1";
|
|
6
|
+
/** How many tables the kernel keeps for paging, the newest. */
|
|
7
|
+
export const KEEP: number;
|
|
8
|
+
/** The most rows one request returns. */
|
|
9
|
+
export const MAX_PAGE: number;
|
|
10
|
+
/** The most rows a table can have to be sorted in the kernel. */
|
|
11
|
+
export const MAX_SORT: number;
|
|
12
|
+
|
|
13
|
+
/** Compares two encoded values of a column as the Ganymede apps' table does. */
|
|
14
|
+
export function compareEncoded(a: unknown, b: unknown, type: string | undefined, descending?: boolean): number;
|
|
15
|
+
|
|
16
|
+
export function createTablePager(
|
|
17
|
+
commApi: CommApi,
|
|
18
|
+
options?: { keep?: number; newId?: () => string },
|
|
19
|
+
): TablePager & { size(): number };
|