@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/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.11.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-13";
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.11.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-13";
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",
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tlockney/cdp-kernel",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Deno's Jupyter kernel, extracted to run notebooks on Node, Deno, Bun, workerd and celld",
5
5
  "license": "MIT",
6
6
  "author": "Thomas Lockney <thomas@lockney.net>",
@@ -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(specifier: string, base: string, cdn?: string): string;
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
- /** The URL `specifier`, imported from `base`, loads from. */
10
- export function resolveSpecifier(specifier, base, cdn = DEFAULT_CDN) {
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);
@@ -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
- * Jupyter comms: message channels to the frontend outside cell
254
- * execution. Its methods are in the widgets and comms reference.
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
- * ![A JupyterLab cell that creates a counter widget with jupyter.widget. Below the code, the output is a button reading "count is 3" after three clicks.](https://cdp-kernel.org/images/widgets/counter.png)
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
- * @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.
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 { counter = "Fremont", days = 30 } = jupyter.params as { counter?: string; days?: number };
329
- * `${counter}, last ${days} days`;
219
+ * const bundle = await jupyter.format(jupyter.md`# Weekly report`);
220
+ * bundle["text/markdown"]; // "# Weekly report"
330
221
  * ```
331
- * @see jupyter-api.md#parameters-and-named-results
222
+ * @see jupyter-api.md#getting-a-bundle-with-format
332
223
  */
333
- readonly params: { readonly [name: string]: JsonValue };
224
+ format(obj: unknown): Promise<MediaBundle>;
334
225
  /**
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.
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 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).
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
- * const counts = [3120, 2890, 2410];
350
- * jupyter.result("weekday_drop", (counts[2] - counts[0]) / counts[0]);
351
- * jupyter.result("counts", counts);
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#parameters-and-named-results
245
+ * @see jupyter-api.md#raw-messages-with-broadcast
354
246
  */
355
- result(name: string, value: unknown): void;
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
+ * ![A JupyterLab cell that creates a counter widget with jupyter.widget. Below the code, the output is a button reading "count is 3" after three clicks.](https://cdp-kernel.org/images/widgets/counter.png)
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
@@ -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 };