@tlockney/cdp-kernel 0.10.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.
@@ -306,10 +306,10 @@ var Widget = class {
306
306
  #state;
307
307
  #listeners = /* @__PURE__ */ new Map();
308
308
  // event -> Set<listener>
309
- constructor(comm, state, $display2) {
309
+ constructor(comm, state, $display4) {
310
310
  this.#comm = comm;
311
311
  this.#state = state;
312
- this[$display2] = () => ({
312
+ this[$display4] = () => ({
313
313
  [VIEW_MIME]: { version_major: 2, version_minor: 1, model_id: this.id },
314
314
  "text/plain": `Widget(${this.id})`
315
315
  });
@@ -374,7 +374,7 @@ var Widget = class {
374
374
  }
375
375
  }
376
376
  };
377
- function createWidgetFactory(commApi, $display2) {
377
+ function createWidgetFactory(commApi, $display4) {
378
378
  const live = /* @__PURE__ */ new Map();
379
379
  commApi.registerTarget(CONTROL_TARGET, (control) => {
380
380
  control.onMsg((msg) => {
@@ -408,7 +408,7 @@ function createWidgetFactory(commApi, $display2) {
408
408
  metadata: { version: WIDGET_PROTOCOL_VERSION },
409
409
  buffers
410
410
  });
411
- const instance = new Widget(comm, { ...full }, $display2);
411
+ const instance = new Widget(comm, { ...full }, $display4);
412
412
  live.set(comm.id, instance);
413
413
  comm.onMsg((msg) => instance._receive(msg));
414
414
  comm.onClose(() => {
@@ -418,19 +418,1097 @@ function createWidgetFactory(commApi, $display2) {
418
418
  };
419
419
  }
420
420
 
421
- // runtime/display.mjs
421
+ // runtime/table.mjs
422
+ var TABLE_MIME = "application/vnd.dataresource+json";
423
+ var DEFAULT_LIMIT = 1e3;
424
+ var MAX_LIMIT = 1e4;
425
+ var MAX_BYTES = 1e6;
426
+ var MAX_CELL = 1e3;
427
+ var HTML_ALL = 20;
428
+ var HTML_HEAD = 10;
429
+ var HTML_TAIL = 5;
430
+ var TEXT_HEAD = 3;
431
+ var TEXT_TAIL = 2;
432
+ var TEXT_COLUMNS = 20;
433
+ var TEXT_MAX = 4096;
434
+ var rawToEntity = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" };
435
+ var escapeHTML = (s) => s.replace(/[&<>"']/g, (c) => rawToEntity[c]);
436
+ function isObject(value) {
437
+ return value !== null && typeof value === "object";
438
+ }
439
+ function isArrowTable(value) {
440
+ return isObject(value) && isObject(value.schema) && Array.isArray(value.schema.fields) && typeof value.numRows === "number" && typeof value.getChild === "function";
441
+ }
442
+ function isPolarsFrame(value) {
443
+ return isObject(value) && isObject(value.schema) && typeof value.head === "function" && typeof value.toRecords === "function";
444
+ }
445
+ function isDuckDBShaped(value) {
446
+ return isObject(value) && typeof value.columnNames === "function" && typeof value.columnTypes === "function" && typeof value.getRowObjectsJS === "function";
447
+ }
448
+ function isDuckDBResult(value) {
449
+ return isDuckDBShaped(value) && typeof value.currentRowCount === "number" && typeof value.getRowsJS === "function" && value.done === true;
450
+ }
451
+ function isTableLike(value) {
452
+ return isArrowTable(value) || isPolarsFrame(value) || isDuckDBResult(value);
453
+ }
454
+ function schemaType(declared) {
455
+ const t = String(declared ?? "");
456
+ if (t === "") return void 0;
457
+ if (/^(list|large_?list|fixed_?size_?list|array)\b|\[\d*\]$/i.test(t)) return "array";
458
+ if (/^(struct|map|union|json)\b/i.test(t)) return "object";
459
+ if (/^(u?int\d*|u?(tiny|small|big|huge)?int(eger)?)\b/i.test(t)) return "integer";
460
+ if (/^(float\d*|double|real|decimal|numeric)\b/i.test(t)) return "number";
461
+ if (/^bool/i.test(t)) return "boolean";
462
+ if (/^date(32|64)?(<|$|\b(?!time))/i.test(t) && !/^datetime/i.test(t)) return "date";
463
+ if (/^(timestamp|datetime)/i.test(t)) return "datetime";
464
+ if (/^time/i.test(t)) return "time";
465
+ if (/^(interval|duration)/i.test(t)) return "duration";
466
+ if (/^(utf8|large_?utf8|string|varchar|char|text|categorical|enum|uuid|null)\b/i.test(t)) return "string";
467
+ return void 0;
468
+ }
469
+ function polarsDeclared(dtype) {
470
+ if (typeof dtype?.toJSON === "function") {
471
+ const json = dtype.toJSON();
472
+ const name = isObject(json) ? json.DataType ?? Object.keys(json)[0] : json;
473
+ if (typeof name === "string") return name;
474
+ if (isObject(name)) return Object.keys(name)[0];
475
+ }
476
+ return typeof dtype === "string" ? dtype : void 0;
477
+ }
478
+ function inferType(values) {
479
+ let type;
480
+ for (const v of values) {
481
+ if (v === null || v === void 0) continue;
482
+ let t;
483
+ if (typeof v === "bigint") t = "integer";
484
+ else if (typeof v === "number") t = Number.isInteger(v) ? "integer" : "number";
485
+ else if (typeof v === "boolean") t = "boolean";
486
+ else if (typeof v === "string") t = "string";
487
+ else if (v instanceof Date) t = v.getTime() % 864e5 === 0 ? "date" : "datetime";
488
+ else if (Array.isArray(v) || ArrayBuffer.isView(v)) t = ArrayBuffer.isView(v) && !(v instanceof DataView) && v.BYTES_PER_ELEMENT === 1 ? "string" : "array";
489
+ else t = "object";
490
+ if (type === void 0) type = t;
491
+ else if (type !== t) {
492
+ if (type === "integer" && t === "number" || type === "number" && t === "integer") type = "number";
493
+ else if (type === "date" && t === "datetime" || type === "datetime" && t === "date") type = "datetime";
494
+ else return "string";
495
+ }
496
+ }
497
+ return type ?? "string";
498
+ }
499
+ function clipString(s, max = MAX_CELL) {
500
+ return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
501
+ }
502
+ function toDate(value) {
503
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;
504
+ if (typeof value === "number" || typeof value === "bigint") {
505
+ const d = new Date(Number(value));
506
+ return Number.isNaN(d.getTime()) ? null : d;
507
+ }
508
+ return null;
509
+ }
510
+ function plain(value, depth = 0) {
511
+ if (value === null || value === void 0) return null;
512
+ if (typeof value === "bigint") return Number.isSafeInteger(Number(value)) ? Number(value) : String(value);
513
+ if (typeof value === "number") return Number.isFinite(value) ? value : String(value);
514
+ if (typeof value === "string") return clipString(value);
515
+ if (typeof value !== "object") return value;
516
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value.toISOString();
517
+ if (depth > 8) return "\u2026";
518
+ if (ArrayBuffer.isView(value) && !(value instanceof DataView) && value.BYTES_PER_ELEMENT === 1) {
519
+ return `<${value.byteLength} bytes>`;
520
+ }
521
+ if (Array.isArray(value) || ArrayBuffer.isView(value)) return Array.from(value, (v) => plain(v, depth + 1));
522
+ if (typeof value.toJSON === "function") return plain(value.toJSON(), depth + 1);
523
+ if (typeof value.toArray === "function") return plain(value.toArray(), depth + 1);
524
+ const out = {};
525
+ for (const [k, v] of Object.entries(value)) out[k] = plain(v, depth + 1);
526
+ return out;
527
+ }
528
+ function encodeValue(value, type, declared) {
529
+ if (value === null || value === void 0) return null;
530
+ if (type === "time" && (typeof value === "bigint" || typeof value === "number")) return timeText(value, declared);
531
+ if (type === "date" || type === "datetime") {
532
+ const d = toDate(value);
533
+ if (d) return type === "date" ? d.toISOString().slice(0, 10) : d.toISOString();
534
+ }
535
+ if (typeof value === "bigint") return Number.isSafeInteger(Number(value)) ? Number(value) : String(value);
536
+ if (typeof value === "number") {
537
+ if (Number.isNaN(value)) return "NaN";
538
+ if (!Number.isFinite(value)) return value > 0 ? "Infinity" : "-Infinity";
539
+ return value;
540
+ }
541
+ if (typeof value === "string") return clipString(value);
542
+ if (typeof value === "boolean") return value;
543
+ return plain(value);
544
+ }
545
+ function timeText(value, declared = "") {
546
+ const d = String(declared);
547
+ const unit = /NANOSECOND|^Time$/.test(d) || /_NS$/i.test(d) ? 1000000000n : /MILLISECOND/i.test(d) ? 1000n : /<SECOND/i.test(d) ? 1n : 1000000n;
548
+ const total = typeof value === "bigint" ? value : BigInt(Math.trunc(value));
549
+ const seconds = total / unit;
550
+ const fraction = total % unit;
551
+ const pad = (n) => String(n).padStart(2, "0");
552
+ const text = `${pad(seconds / 3600n)}:${pad(seconds / 60n % 60n)}:${pad(seconds % 60n)}`;
553
+ if (fraction === 0n) return text;
554
+ return `${text}.${String(fraction).padStart(String(unit).length - 1, "0").replace(/0+$/, "")}`;
555
+ }
556
+ function uniqueNames(names) {
557
+ const seen = /* @__PURE__ */ new Set();
558
+ return names.map((name) => {
559
+ let unique = name;
560
+ for (let i = 1; seen.has(unique); i++) unique = `${name}_${i}`;
561
+ seen.add(unique);
562
+ return unique;
563
+ });
564
+ }
565
+ function fromObjects(rows, names) {
566
+ let columns = names;
567
+ if (!columns) {
568
+ const seen = /* @__PURE__ */ new Set();
569
+ for (const row of rows.slice(0, DEFAULT_LIMIT)) {
570
+ if (isObject(row)) for (const key2 of Object.keys(row)) seen.add(key2);
571
+ }
572
+ columns = [...seen];
573
+ }
574
+ return {
575
+ columns: columns.map((name) => ({ name: String(name) })),
576
+ total: rows.length,
577
+ read: (start, end) => rows.slice(start, end).map((row) => columns.map((c) => isObject(row) ? row[c] : void 0))
578
+ };
579
+ }
580
+ function fromArrays(rows, names) {
581
+ const width = names?.length ?? Math.max(0, ...rows.slice(0, DEFAULT_LIMIT).map((r) => Array.isArray(r) ? r.length : 0));
582
+ const columns = names ?? Array.from({ length: width }, (_, i) => String(i));
583
+ return {
584
+ columns: columns.map((name) => ({ name: String(name) })),
585
+ total: rows.length,
586
+ read: (start, end) => rows.slice(start, end).map((row) => columns.map((_, i) => Array.isArray(row) ? row[i] : void 0))
587
+ };
588
+ }
589
+ function fromArrow(table, names) {
590
+ const fields = table.schema.fields.filter((f) => !names || names.includes(f.name));
591
+ const vectors = fields.map((f) => table.getChild(f.name));
592
+ return {
593
+ columns: fields.map((f) => ({ name: String(f.name), declared: String(f.type) })),
594
+ total: table.numRows,
595
+ read(start, end) {
596
+ const out = [];
597
+ for (let i = start; i < Math.min(end, table.numRows); i++) out.push(vectors.map((v) => v?.get(i)));
598
+ return out;
599
+ }
600
+ };
601
+ }
602
+ function fromPolars(df, names) {
603
+ const columns = Object.entries(df.schema).filter(([name]) => !names || names.includes(name)).map(([name, dtype]) => ({ name, declared: polarsDeclared(dtype) }));
604
+ const total = Number.isInteger(df.height) ? df.height : df.toRecords().length;
605
+ const records = (start, end) => typeof df.slice === "function" ? df.slice(start, end - start).toRecords() : df.toRecords().slice(start, end);
606
+ return {
607
+ columns,
608
+ total,
609
+ read: (start, end) => records(start, Math.min(end, total)).map((row) => columns.map((c) => row[c.name]))
610
+ };
611
+ }
612
+ function fromDuckDB(result, names) {
613
+ const types = result.columnTypes();
614
+ const all = result.columnNames().map((name, i) => ({ name, declared: String(types[i]) }));
615
+ const columns = names ? all.filter((c) => names.includes(c.name)) : all;
616
+ const at = columns.map((c) => all.indexOf(c));
617
+ let rows;
618
+ const get = () => rows ??= result.getRowsJS();
619
+ return {
620
+ columns,
621
+ total: result.currentRowCount,
622
+ read: (start, end) => get().slice(start, end).map((row) => at.map((i) => row[i]))
623
+ };
624
+ }
625
+ function tableSource(value, options = {}) {
626
+ const names = Array.isArray(options.columns) ? options.columns.map(String) : void 0;
627
+ if (isObject(value) && typeof value.toTableSource === "function") return value.toTableSource(names);
628
+ if (isArrowTable(value)) return fromArrow(value, names);
629
+ if (isPolarsFrame(value)) return fromPolars(value, names);
630
+ if (isDuckDBResult(value)) return fromDuckDB(value, names);
631
+ if (isDuckDBShaped(value)) {
632
+ throw new TypeError(typeof value.currentRowCount === "number" ? "jupyter.table: this DuckDB reader hasn't read all its rows; call readAll() first, or use runAndReadAll()" : "jupyter.table: a DuckDB run() result reads its rows asynchronously; use runAndReadAll(), or jupyter.sql");
633
+ }
634
+ if (Array.isArray(value)) {
635
+ const first = value.find((row) => row !== null && row !== void 0);
636
+ return Array.isArray(first) ? fromArrays(value, names) : fromObjects(value, names);
637
+ }
638
+ throw new TypeError(
639
+ "jupyter.table takes an array of rows, an Arrow table, a Polars DataFrame or a DuckDB result"
640
+ );
641
+ }
642
+ function fieldsOf(source, rows) {
643
+ const names = uniqueNames(source.columns.map((c) => c.name));
644
+ return source.columns.map((c, i) => ({
645
+ name: names[i],
646
+ type: schemaType(c.declared) ?? inferType(rows.map((r) => r[i]))
647
+ }));
648
+ }
649
+ function count(n, noun) {
650
+ return `${n.toLocaleString("en-US")} ${n === 1 ? noun : `${noun}s`}`;
651
+ }
652
+ function shortValue(v) {
653
+ if (v === null || v === void 0) return "";
654
+ const s = typeof v === "string" ? v : JSON.stringify(v);
655
+ return clipString(s.replace(/\s+/g, " "), 30);
656
+ }
657
+ function columnStats(rows, i, type) {
658
+ let nulls = 0;
659
+ const values = [];
660
+ for (const row of rows) {
661
+ const v = row[i];
662
+ if (v === null) nulls++;
663
+ else values.push(v);
664
+ }
665
+ let range = "";
666
+ if (values.length) {
667
+ if (type === "integer" || type === "number") {
668
+ const nums = values.map(Number).filter(Number.isFinite);
669
+ if (nums.length) {
670
+ const min = Math.min(...nums);
671
+ const max = Math.max(...nums);
672
+ const fmt = (n) => Number.isInteger(n) && Math.abs(n) >= 1e4 ? n.toLocaleString("en-US") : String(Number(n.toPrecision(6)));
673
+ range = min === max ? fmt(min) : `${fmt(min)} to ${fmt(max)}`;
674
+ }
675
+ } else if (type === "date" || type === "datetime" || type === "time") {
676
+ const sorted = values.map(String).sort();
677
+ range = sorted[0] === sorted.at(-1) ? sorted[0] : `${sorted[0]} to ${sorted.at(-1)}`;
678
+ } else if (type === "boolean") {
679
+ const t = values.filter((v) => v === true).length;
680
+ range = `${t} true, ${values.length - t} false`;
681
+ } else {
682
+ const distinct = new Set(values.map((v) => typeof v === "string" ? v : JSON.stringify(v))).size;
683
+ range = count(distinct, "value");
684
+ }
685
+ }
686
+ return [range, nulls ? count(nulls, "null") : ""].filter(Boolean).join(", ");
687
+ }
688
+ function htmlTable(fields, head, tail, total, title) {
689
+ let html = "<table>";
690
+ if (title) html += `<caption>${escapeHTML(title)}</caption>`;
691
+ html += "<thead><tr>";
692
+ for (const f of fields) {
693
+ html += `<th>${escapeHTML(f.name)}<br><small>${escapeHTML(f.type)}</small></th>`;
694
+ }
695
+ html += "</tr></thead><tbody>";
696
+ const row = (r) => {
697
+ let s = "<tr>";
698
+ fields.forEach((f, i) => {
699
+ const v = r[i];
700
+ const text = v === null ? "" : typeof v === "string" ? v : JSON.stringify(v);
701
+ const align = f.type === "integer" || f.type === "number" ? ' style="text-align:right"' : "";
702
+ s += `<td${align}>${escapeHTML(text)}</td>`;
703
+ });
704
+ return `${s}</tr>`;
705
+ };
706
+ for (const r of head) html += row(r);
707
+ const hidden = total - head.length - tail.length;
708
+ if (hidden > 0) html += `<tr><td colspan="${Math.max(1, fields.length)}">\u2026 ${count(hidden, "more row")}</td></tr>`;
709
+ for (const r of tail) html += row(r);
710
+ return `${html}</tbody></table>`;
711
+ }
712
+ function textTable(fields, numbered) {
713
+ const cells = numbered.map(([n, r]) => [String(n), ...r.map(shortValue)]);
714
+ const widths = [0, ...fields.map(() => 0)];
715
+ for (const row of cells) row.forEach((c, i) => widths[i] = Math.max(widths[i], c.length));
716
+ return cells.map((row) => ` ${row.map((c, i) => c.padEnd(widths[i])).join(" | ").trimEnd()}`);
717
+ }
718
+ var PAGEABLE = /* @__PURE__ */ Symbol("cdpk.table.pageable");
719
+ function encodeRow(source, fields, row) {
720
+ return fields.map((f, i) => encodeValue(row[i], f.type, source.columns[i].declared));
721
+ }
722
+ function pageTable(bundle, pager, key2) {
723
+ const resource = bundle?.[TABLE_MIME];
724
+ const pending = resource?.[PAGEABLE];
725
+ if (!pending || !pager || resource.ganymede?.page !== void 0) return bundle;
726
+ resource.ganymede.page = pager.register(pending.source, pending.fields, key2);
727
+ return bundle;
728
+ }
729
+ function tableBundle(source, options = {}) {
730
+ const requested = options.limit ?? DEFAULT_LIMIT;
731
+ if (!Number.isInteger(requested) || requested < 0) throw new TypeError("jupyter.table: limit must be a whole number");
732
+ const limit = Math.min(requested, MAX_LIMIT);
733
+ const title = typeof options.title === "string" && options.title !== "" ? options.title : void 0;
734
+ const total = source.total;
735
+ const raw = source.read(0, Math.min(limit, total));
736
+ const fields = fieldsOf(source, raw);
737
+ const encode = (r) => encodeRow(source, fields, r);
738
+ const utf8 = new TextEncoder();
739
+ const encoded = [];
740
+ const data = [];
741
+ let bytes = 0;
742
+ for (const r of raw) {
743
+ const values = encode(r);
744
+ const obj = {};
745
+ fields.forEach((f, i) => Object.defineProperty(obj, f.name, { value: values[i], enumerable: true, writable: true, configurable: true }));
746
+ bytes += utf8.encode(JSON.stringify(obj)).length + 1;
747
+ if (bytes > MAX_BYTES && data.length > 0) break;
748
+ encoded.push(values);
749
+ data.push(obj);
750
+ }
751
+ const shown = data.length;
752
+ const split = total > HTML_ALL;
753
+ const htmlHead = split ? encoded.slice(0, HTML_HEAD) : encoded.slice(0, total);
754
+ const htmlTail = split ? source.read(total - HTML_TAIL, total).map(encode) : [];
755
+ const textSplit = total > TEXT_HEAD + TEXT_TAIL;
756
+ const textHeadRows = encoded.slice(0, textSplit ? TEXT_HEAD : total);
757
+ const textTail = !textSplit ? [] : htmlTail.length ? htmlTail.slice(-TEXT_TAIL) : source.read(total - TEXT_TAIL, total).map(encode);
758
+ const lines = [];
759
+ const name = title ? `Table "${clipString(title, 80)}"` : "Table";
760
+ const shape = `${count(total, "row")}, ${count(fields.length, "column")}`;
761
+ lines.push(`${name} (${shape}${shown < total ? `; ${shown.toLocaleString("en-US")} shown` : ""})`);
762
+ if (fields.length) {
763
+ lines.push(shown < total ? `Columns (ranges over the first ${shown.toLocaleString("en-US")} of ${total.toLocaleString("en-US")} rows):` : "Columns:");
764
+ const nameWidth = Math.min(30, Math.max(...fields.slice(0, TEXT_COLUMNS).map((f) => f.name.length)));
765
+ const typeWidth = Math.max(...fields.slice(0, TEXT_COLUMNS).map((f) => f.type.length));
766
+ fields.slice(0, TEXT_COLUMNS).forEach((f, i) => {
767
+ const stats = columnStats(encoded, i, f.type);
768
+ lines.push(` ${clipString(f.name, 30).padEnd(nameWidth)} ${f.type.padEnd(typeWidth)} ${stats}`.trimEnd());
769
+ });
770
+ if (fields.length > TEXT_COLUMNS) lines.push(` \u2026 ${count(fields.length - TEXT_COLUMNS, "more column")}`);
771
+ const numbered = [
772
+ ...textHeadRows.map((r, i) => [i, r]),
773
+ ...textTail.map((r, i) => [total - textTail.length + i, r])
774
+ ];
775
+ if (numbered.length) {
776
+ lines.push("Rows:");
777
+ const table = textTable(fields.slice(0, TEXT_COLUMNS), numbered.map(([n, r]) => [n, r.slice(0, TEXT_COLUMNS)]));
778
+ if (textTail.length) table.splice(textHeadRows.length, 0, " \u2026");
779
+ lines.push(...table);
780
+ }
781
+ }
782
+ let text = lines.join("\n");
783
+ if (text.length > TEXT_MAX) text = `${text.slice(0, TEXT_MAX - 1)}\u2026`;
784
+ const resource = { schema: { fields }, data, ganymede: { rows: total, shown } };
785
+ if (title) resource.ganymede.title = title;
786
+ if (shown < total) {
787
+ Object.defineProperty(resource, PAGEABLE, { value: { source, fields } });
788
+ if (options.pager) pageTable({ [TABLE_MIME]: resource }, options.pager);
789
+ }
790
+ return {
791
+ [TABLE_MIME]: resource,
792
+ "text/html": htmlTable(fields, htmlHead, htmlTail, total, title),
793
+ "text/plain": text
794
+ };
795
+ }
796
+
797
+ // runtime/sql.mjs
798
+ var NODE_API = "npm:@duckdb/node-api@1.5.6-r.2";
799
+ var DUCKDB_WASM = "npm:@duckdb/duckdb-wasm@1.29.0";
422
800
  var $display = /* @__PURE__ */ Symbol.for("Jupyter.display");
423
- var rawToEntity = /* @__PURE__ */ new Map([
801
+ var RAW = /* @__PURE__ */ Symbol("cdpk.sql.raw");
802
+ function jsValue(value, declared) {
803
+ if (/^time(?!stamp)/i.test(declared ?? "") && (typeof value === "bigint" || typeof value === "number")) {
804
+ return timeText(value, declared);
805
+ }
806
+ if (typeof value === "bigint") return Number.isSafeInteger(Number(value)) ? Number(value) : value;
807
+ if (typeof value === "number" && /^(date|timestamp)/i.test(declared ?? "")) return new Date(value);
808
+ if (Array.isArray(value)) return value.map((v) => jsValue(v));
809
+ if (value !== null && typeof value === "object" && !(value instanceof Date) && !ArrayBuffer.isView(value)) {
810
+ if (typeof value.toJSON === "function" && !(Symbol.iterator in value) && Object.getPrototypeOf(value) !== Object.prototype) {
811
+ return jsValue(value.toJSON());
812
+ }
813
+ if (typeof value.toArray === "function") return jsValue(Array.from(value.toArray()));
814
+ const out = {};
815
+ for (const [k, v] of Object.entries(value)) out[k] = jsValue(v);
816
+ return out;
817
+ }
818
+ return value;
819
+ }
820
+ function decimalValue(words, scale) {
821
+ if (typeof words === "bigint" && scale === 0) return jsValue(words);
822
+ if (typeof words === "number" || typeof words === "bigint") return Number(words) / 10 ** scale;
823
+ const list2 = Array.from(words);
824
+ let big = 0n;
825
+ list2.forEach((w, i) => big |= BigInt(w >>> 0) << BigInt(32 * i));
826
+ const bits = BigInt(32 * list2.length);
827
+ if (list2.length && list2.at(-1) >>> 31) big -= 1n << bits;
828
+ return scale === 0 ? jsValue(big) : Number(big) / 10 ** scale;
829
+ }
830
+ var SqlResult = class {
831
+ /**
832
+ * @param {{ name: string, type: string }[]} columns
833
+ * @param {Record<string, unknown>[]} rows
834
+ */
835
+ constructor(columns, rows) {
836
+ this.columns = columns;
837
+ this.rows = rows;
838
+ }
839
+ /** table.mjs reads this instead of guessing at the shape. */
840
+ toTableSource(names) {
841
+ const columns = names ? this.columns.filter((c) => names.includes(c.name)) : this.columns;
842
+ return {
843
+ columns: columns.map((c) => ({ name: c.name, declared: c.type })),
844
+ total: this.rows.length,
845
+ read: (start, end) => this.rows.slice(start, end).map((row) => columns.map((c) => row[c.name]))
846
+ };
847
+ }
848
+ [$display]() {
849
+ return tableBundle(tableSource(this));
850
+ }
851
+ };
852
+ function toParam(value) {
853
+ if (value === void 0) return null;
854
+ if (value instanceof Date) return value.toISOString();
855
+ if (typeof value === "object" && value !== null && !ArrayBuffer.isView(value)) return JSON.stringify(value);
856
+ return value;
857
+ }
858
+ function compose(strings, values) {
859
+ let text = strings[0];
860
+ const params = [];
861
+ values.forEach((value, i) => {
862
+ if (value !== null && typeof value === "object" && RAW in value) text += value[RAW];
863
+ else {
864
+ text += "?";
865
+ params.push(toParam(value));
866
+ }
867
+ text += strings[i + 1];
868
+ });
869
+ return { text, params };
870
+ }
871
+ function quoteIdent(name) {
872
+ return `"${String(name).replaceAll('"', '""')}"`;
873
+ }
874
+ function quoteString(text) {
875
+ return `'${String(text).replaceAll("'", "''")}'`;
876
+ }
877
+ function sqlLiteral(value) {
878
+ const text = literalText(value);
879
+ return text.startsWith("-") ? `(${text})` : text;
880
+ }
881
+ function literalText(value) {
882
+ if (value === null || value === void 0) return "NULL";
883
+ if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
884
+ if (typeof value === "bigint") return `${value}`;
885
+ if (typeof value === "number") {
886
+ if (Number.isSafeInteger(value)) return String(value);
887
+ return Number.isFinite(value) ? `${value}::DOUBLE` : `'${value}'::DOUBLE`;
888
+ }
889
+ if (value instanceof Uint8Array) {
890
+ return `'${Array.from(value, (b) => `\\x${b.toString(16).padStart(2, "0")}`).join("")}'::BLOB`;
891
+ }
892
+ if (ArrayBuffer.isView(value)) return `[${Array.from(value, (v) => sqlLiteral(v)).join(", ")}]`;
893
+ return quoteString(value);
894
+ }
895
+ function inlineParams(text, params) {
896
+ if (params.length === 0) return text;
897
+ let out = "";
898
+ let next = 0;
899
+ for (let i = 0; i < text.length; i++) {
900
+ const c = text[i];
901
+ const end = c === "'" || c === '"' ? text.indexOf(c, i + 1) : c === "-" && text[i + 1] === "-" ? text.indexOf("\n", i) : c === "/" && text[i + 1] === "*" ? text.indexOf("*/", i + 2) + 1 : void 0;
902
+ if (end !== void 0) {
903
+ const stop = end < 0 || end === 0 ? text.length : end + 1;
904
+ out += text.slice(i, stop);
905
+ i = stop - 1;
906
+ } else if (c === "?") {
907
+ if (next >= params.length) throw new RangeError("jupyter.sql: more ? placeholders than parameters");
908
+ if (out.endsWith("'") || out.endsWith('"')) out += " ";
909
+ out += sqlLiteral(params[next++]);
910
+ } else out += c;
911
+ }
912
+ if (next < params.length) throw new RangeError("jupyter.sql: more parameters than ? placeholders");
913
+ return out;
914
+ }
915
+ var DUCK_TYPES = {
916
+ integer: "BIGINT",
917
+ number: "DOUBLE",
918
+ boolean: "BOOLEAN",
919
+ string: "VARCHAR",
920
+ date: "DATE",
921
+ datetime: "TIMESTAMP",
922
+ time: "TIME",
923
+ array: "JSON",
924
+ object: "JSON",
925
+ duration: "VARCHAR"
926
+ };
927
+ function createSql({ importModule, signal, openEngine }) {
928
+ let engine = null;
929
+ const open = () => engine ??= (openEngine ?? (() => defaultEngine(importModule)))().catch((err) => {
930
+ engine = null;
931
+ throw err;
932
+ });
933
+ async function run(text, params) {
934
+ const s = signal?.();
935
+ const e = await open();
936
+ if (s?.aborted) throw s.reason;
937
+ const onAbort = () => e.interrupt?.();
938
+ s?.addEventListener("abort", onAbort, { once: true });
939
+ try {
940
+ return await e.query(text, params);
941
+ } finally {
942
+ s?.removeEventListener("abort", onAbort);
943
+ }
944
+ }
945
+ function sql(strings, ...values) {
946
+ if (!Array.isArray(strings) || !Array.isArray(strings.raw)) {
947
+ return Promise.reject(new TypeError("jupyter.sql is a tag: jupyter.sql`select ...`; for a string, use jupyter.sql.query(text, params)"));
948
+ }
949
+ const { text, params } = compose(strings, values);
950
+ return run(text, params);
951
+ }
952
+ sql.query = (text, params = []) => {
953
+ if (typeof text !== "string") return Promise.reject(new TypeError("jupyter.sql.query: text must be a string"));
954
+ return run(text, params.map(toParam));
955
+ };
956
+ sql.raw = (text) => ({ [RAW]: String(text) });
957
+ sql.load = async (name, source) => {
958
+ if (typeof name !== "string" || name === "") throw new TypeError("jupyter.sql.load: name must be a non-empty string");
959
+ const table = quoteIdent(name);
960
+ if (typeof source === "string") {
961
+ await run(`create or replace table ${table} as select * from ${quoteString(source)}`, []);
962
+ return;
963
+ }
964
+ const { structure, json } = rowsAsJson(source);
965
+ if (structure === null) {
966
+ throw new TypeError("jupyter.sql.load: the rows have no columns");
967
+ }
968
+ await run(
969
+ `create or replace table ${table} as select unnest(from_json(?, ?), recursive := true)`,
970
+ [json, structure]
971
+ );
972
+ };
973
+ sql.close = async () => {
974
+ const e = engine;
975
+ engine = null;
976
+ if (e) await (await e).close();
977
+ };
978
+ sql.connection = async () => (await open()).connection;
979
+ return sql;
980
+ }
981
+ function loadValue(value, type, depth = 0) {
982
+ if (value === null || value === void 0) return null;
983
+ if (type === "date" || type === "datetime") {
984
+ const d = value instanceof Date ? value : typeof value === "number" || typeof value === "bigint" ? new Date(Number(value)) : null;
985
+ if (d) return Number.isNaN(d.getTime()) ? null : type === "date" ? d.toISOString().slice(0, 10) : d.toISOString();
986
+ }
987
+ if (typeof value === "bigint") return Number.isSafeInteger(Number(value)) ? Number(value) : String(value);
988
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
989
+ if (typeof value !== "object") return value;
990
+ if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value.toISOString();
991
+ if (depth > 64) throw new TypeError("jupyter.sql.load: a value is nested more than 64 levels deep");
992
+ if (ArrayBuffer.isView(value) && !(value instanceof DataView) && value.BYTES_PER_ELEMENT === 1) {
993
+ let text = "";
994
+ for (const byte of value) text += String.fromCharCode(byte);
995
+ return btoa(text);
996
+ }
997
+ if (Array.isArray(value) || ArrayBuffer.isView(value)) return Array.from(value, (v) => loadValue(v, void 0, depth + 1));
998
+ if (typeof value.toJSON === "function" && Object.getPrototypeOf(value) !== Object.prototype) {
999
+ return loadValue(value.toJSON(), void 0, depth + 1);
1000
+ }
1001
+ const out = {};
1002
+ for (const [k, v] of Object.entries(value)) out[k] = loadValue(v, void 0, depth + 1);
1003
+ return out;
1004
+ }
1005
+ function rowsAsJson(source) {
1006
+ const src = tableSource(source);
1007
+ const raw = src.read(0, src.total);
1008
+ const fields = fieldsOf(src, raw);
1009
+ if (fields.length === 0) return { structure: null, json: "[]" };
1010
+ const rows = raw.map((r) => {
1011
+ const obj = {};
1012
+ fields.forEach((f, i) => {
1013
+ obj[f.name] = loadValue(r[i], f.type);
1014
+ });
1015
+ return obj;
1016
+ });
1017
+ const structure = {};
1018
+ for (const f of fields) structure[f.name] = DUCK_TYPES[f.type] ?? "VARCHAR";
1019
+ return { structure: JSON.stringify([structure]), json: JSON.stringify(rows) };
1020
+ }
1021
+ function inWorker() {
1022
+ return typeof globalThis.WorkerGlobalScope === "function" && globalThis instanceof globalThis.WorkerGlobalScope;
1023
+ }
1024
+ function hasNativeRuntime() {
1025
+ return typeof globalThis.Deno?.version?.deno === "string" || typeof globalThis.Bun?.version === "string" || typeof globalThis.process?.versions?.node === "string" && !inWorker();
1026
+ }
1027
+ async function defaultEngine(importModule) {
1028
+ if (hasNativeRuntime()) return nativeEngine(importModule);
1029
+ if (inWorker() && typeof Worker === "function") return wasmEngine(importModule);
1030
+ throw new Error("jupyter.sql needs Node, Deno, Bun or the browser target; workerd and celld have no DuckDB");
1031
+ }
1032
+ async function nativeEngine(importModule) {
1033
+ const { DuckDBInstance, blobValue } = await importModule(NODE_API);
1034
+ const db = await DuckDBInstance.create(":memory:");
1035
+ const conn = await db.connect();
1036
+ return {
1037
+ connection: conn,
1038
+ interrupt: () => conn.interrupt?.(),
1039
+ async query(text, params) {
1040
+ const bound = params.map((p) => p instanceof Uint8Array && blobValue ? blobValue(p) : p);
1041
+ const result = await conn.runAndReadAll(text, bound);
1042
+ const types = result.columnTypes();
1043
+ const columns = uniqueNames(result.columnNames()).map((name, i) => ({ name, type: String(types[i]) }));
1044
+ const rows = result.getRowsJS().map((row) => {
1045
+ const out = {};
1046
+ columns.forEach((c, j) => out[c.name] = jsValue(row[j], c.type));
1047
+ return out;
1048
+ });
1049
+ return new SqlResult(columns, rows);
1050
+ },
1051
+ async close() {
1052
+ conn.closeSync?.();
1053
+ db.closeSync?.();
1054
+ }
1055
+ };
1056
+ }
1057
+ async function wasmEngine(importModule) {
1058
+ const duckdb = await importModule(DUCKDB_WASM);
1059
+ const bundle = await duckdb.selectBundle(duckdb.getJsDelivrBundles());
1060
+ const loader = URL.createObjectURL(new Blob([`importScripts(${JSON.stringify(bundle.mainWorker)});`], { type: "text/javascript" }));
1061
+ const worker = new Worker(loader);
1062
+ const db = new duckdb.AsyncDuckDB(new duckdb.VoidLogger(), worker);
1063
+ await db.instantiate(bundle.mainModule, bundle.pthreadWorker);
1064
+ URL.revokeObjectURL(loader);
1065
+ const conn = await db.connect();
1066
+ let cancelled = false;
1067
+ return {
1068
+ connection: conn,
1069
+ interrupt: () => {
1070
+ cancelled = true;
1071
+ void conn.cancelSent?.();
1072
+ },
1073
+ async query(text, params) {
1074
+ cancelled = false;
1075
+ const reader = await conn.send(inlineParams(text, params), true);
1076
+ const stop = () => {
1077
+ if (cancelled) throw new DOMException("The query was interrupted", "AbortError");
1078
+ };
1079
+ try {
1080
+ stop();
1081
+ const columns = [];
1082
+ const rows = [];
1083
+ let decimal = [];
1084
+ for await (const batch of reader) {
1085
+ if (columns.length === 0 && batch.schema.fields.length) {
1086
+ const fields = batch.schema.fields;
1087
+ uniqueNames(fields.map((f) => f.name)).forEach((name, j) => columns.push({ name, type: String(fields[j].type) }));
1088
+ decimal = fields.map((f) => /^decimal/i.test(String(f.type)) ? f.type.scale ?? 0 : null);
1089
+ }
1090
+ stop();
1091
+ const vectors = columns.map((_, j) => batch.getChildAt(j));
1092
+ for (let i = 0; i < batch.numRows; i++) {
1093
+ const row = {};
1094
+ columns.forEach((c, j) => {
1095
+ const v = vectors[j]?.get(i);
1096
+ row[c.name] = decimal[j] !== null && v != null ? decimalValue(v, decimal[j]) : jsValue(v, c.type);
1097
+ });
1098
+ rows.push(row);
1099
+ }
1100
+ }
1101
+ if (columns.length === 0 && reader.schema?.fields) {
1102
+ const fields = reader.schema.fields;
1103
+ uniqueNames(fields.map((f) => f.name)).forEach((name, j) => columns.push({ name, type: String(fields[j].type) }));
1104
+ }
1105
+ return new SqlResult(columns, rows);
1106
+ } catch (err) {
1107
+ await Promise.resolve((reader.cancel ?? reader.return)?.call(reader)).catch(() => {
1108
+ });
1109
+ throw err;
1110
+ }
1111
+ },
1112
+ async close() {
1113
+ await conn.close();
1114
+ await db.terminate();
1115
+ worker.terminate();
1116
+ }
1117
+ };
1118
+ }
1119
+
1120
+ // runtime/chart.mjs
1121
+ var VEGA = "npm:vega@6";
1122
+ var VEGA_LITE = "npm:vega-lite@6";
1123
+ var LINKEDOM = "npm:linkedom@0.18";
1124
+ var WARN_BYTES = 2e6;
1125
+ var $display2 = /* @__PURE__ */ Symbol.for("Jupyter.display");
1126
+ var OTHER_LEGENDS = ["opacity", "r", "symbol"];
1127
+ var FONT = "system-ui, sans-serif";
1128
+ var rawToEntity2 = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" };
1129
+ var esc = (s) => String(s).replace(/[&<>"']/g, (c) => rawToEntity2[c]);
1130
+ function isObject2(value) {
1131
+ return value !== null && typeof value === "object";
1132
+ }
1133
+ function isPlotModule(value) {
1134
+ return (isObject2(value) || typeof value === "function") && typeof value.plot === "function" && typeof value.barY === "function";
1135
+ }
1136
+ var SPEC_KEYS = ["$schema", "mark", "marks", "layer", "spec", "concat", "hconcat", "vconcat", "facet", "repeat"];
1137
+ function specOf(value) {
1138
+ const spec = isObject2(value) && typeof value.toSpec === "function" ? value.toSpec() : value;
1139
+ if (!isObject2(spec) || Array.isArray(spec) || !SPEC_KEYS.some((key2) => key2 in spec)) {
1140
+ throw new TypeError(
1141
+ "jupyter.chart takes a Vega-Lite or Vega spec, the Plot module and Plot options, or an SVG element"
1142
+ );
1143
+ }
1144
+ return spec;
1145
+ }
1146
+ function isSvgElement(value) {
1147
+ return isObject2(value) && typeof value.outerHTML === "string" && /^<svg\b/i.test(value.outerHTML);
1148
+ }
1149
+ function isVegaSpec(spec) {
1150
+ return typeof spec.$schema === "string" && /\/schema\/vega\/v\d/.test(spec.$schema);
1151
+ }
1152
+ function svgSize(svg) {
1153
+ const open = /<svg\b[^>]*>/i.exec(svg)?.[0] ?? "";
1154
+ const attr = (name) => {
1155
+ const m = new RegExp(`\\s${name}="([^"]*)"`, "i").exec(open);
1156
+ return m ? m[1] : void 0;
1157
+ };
1158
+ let width = parseFloat(attr("width") ?? "");
1159
+ let height = parseFloat(attr("height") ?? "");
1160
+ const box = attr("viewBox")?.trim().split(/[\s,]+/).map(Number);
1161
+ if (!Number.isFinite(width) && box?.length === 4) width = box[2];
1162
+ if (!Number.isFinite(height) && box?.length === 4) height = box[3];
1163
+ return { width: Number.isFinite(width) ? width : 640, height: Number.isFinite(height) ? height : 400 };
1164
+ }
1165
+ function withNamespace(svg) {
1166
+ return /<svg\b[^>]*\sxmlns=/i.test(svg) ? svg : svg.replace(/<svg\b/i, '<svg xmlns="http://www.w3.org/2000/svg"');
1167
+ }
1168
+ function hash(text) {
1169
+ let h = 2166136261;
1170
+ for (let i = 0; i < text.length; i++) h = Math.imul(h ^ text.charCodeAt(i), 16777619);
1171
+ return (h >>> 0).toString(36);
1172
+ }
1173
+ var textWidth = (text, size) => String(text).length * size * 0.6;
1174
+ function legendSVG(scale, width, form) {
1175
+ if (!isObject2(scale) || typeof scale.apply !== "function" || !Array.isArray(scale.domain)) return { svg: "", height: 0 };
1176
+ const label = typeof scale.label === "string" && scale.label ? scale.label : "";
1177
+ const parts = [];
1178
+ let y = 0;
1179
+ if (label) {
1180
+ parts.push(`<text x="0" y="10" font-weight="bold">${esc(label)}</text>`);
1181
+ y = 16;
1182
+ }
1183
+ const continuous = form === "ramp" || form !== "swatches" && /^(linear|log|pow|sqrt|symlog|utc|time|sequential|diverging|quantize|threshold|quantile)/.test(String(scale.type));
1184
+ if (!continuous) {
1185
+ let x = 0;
1186
+ let row = y;
1187
+ for (const value of scale.domain) {
1188
+ const text = String(value);
1189
+ const w = 14 + textWidth(text, 10) + 12;
1190
+ if (x > 0 && x + w > width) {
1191
+ x = 0;
1192
+ row += 16;
1193
+ }
1194
+ parts.push(
1195
+ `<rect x="${x}" y="${row}" width="10" height="10" fill="${esc(scale.apply(value))}"/><text x="${x + 14}" y="${row + 9}">${esc(text)}</text>`
1196
+ );
1197
+ x += w;
1198
+ }
1199
+ return { svg: parts.join(""), height: row + 14 };
1200
+ }
1201
+ const [lo, hi] = [scale.domain[0], scale.domain.at(-1)];
1202
+ const n = 16;
1203
+ const stops = [];
1204
+ const range = Array.isArray(scale.range) && scale.range.length >= 2 && scale.range.every((r) => typeof r === "number") ? [scale.range[0], scale.range.at(-1)] : void 0;
1205
+ const numeric = (v) => v instanceof Date ? v.getTime() : typeof v === "number" ? v : NaN;
1206
+ const [a, b] = [numeric(lo), numeric(hi)];
1207
+ for (let i = 0; i <= n; i++) {
1208
+ const t = i / n;
1209
+ let color;
1210
+ if (typeof scale.interpolate === "function" && range) color = scale.interpolate(range[0] + (range[1] - range[0]) * t);
1211
+ else if (Number.isFinite(a) && Number.isFinite(b)) {
1212
+ const v = a + (b - a) * t;
1213
+ color = scale.apply(lo instanceof Date ? new Date(v) : v);
1214
+ } else color = scale.apply(i === 0 ? lo : hi);
1215
+ stops.push(`<stop offset="${t * 100}%" stop-color="${esc(color)}"/>`);
1216
+ }
1217
+ const id = `ramp-${hash(stops.join(""))}`;
1218
+ const rampWidth = Math.min(240, width);
1219
+ const fmt = (v) => v instanceof Date ? v.toISOString().slice(0, 10) : typeof v === "number" ? String(Number(v.toPrecision(4))) : String(v);
1220
+ parts.push(
1221
+ `<defs><linearGradient id="${id}">${stops.join("")}</linearGradient></defs><rect x="0" y="${y}" width="${rampWidth}" height="10" fill="url(#${id})"/><text x="0" y="${y + 22}">${esc(fmt(lo))}</text><text x="${rampWidth}" y="${y + 22}" text-anchor="end">${esc(fmt(hi))}</text>`
1222
+ );
1223
+ return { svg: parts.join(""), height: y + 26 };
1224
+ }
1225
+ function composeSVG(inner, { title, subtitle, caption, legend }) {
1226
+ const { width, height } = svgSize(inner);
1227
+ const head = [];
1228
+ let y = 0;
1229
+ if (title) {
1230
+ head.push(`<text x="0" y="${y + 16}" font-size="16" font-weight="bold">${esc(title)}</text>`);
1231
+ y += 24;
1232
+ }
1233
+ if (subtitle) {
1234
+ head.push(`<text x="0" y="${y + 13}" font-size="13">${esc(subtitle)}</text>`);
1235
+ y += 20;
1236
+ }
1237
+ if (legend?.svg) {
1238
+ head.push(`<g transform="translate(0,${y})" font-size="10">${legend.svg}</g>`);
1239
+ y += legend.height + 8;
1240
+ }
1241
+ const body = withNamespace(inner).replace(/<svg\b/i, `<svg x="0" y="${y}"`);
1242
+ let total = y + height;
1243
+ let foot = "";
1244
+ if (caption) {
1245
+ foot = `<text x="0" y="${total + 16}" font-size="12">${esc(caption)}</text>`;
1246
+ total += 24;
1247
+ }
1248
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${total}" viewBox="0 0 ${width} ${total}" font-family="${FONT}" fill="currentColor">${head.join("")}${body}${foot}</svg>`;
1249
+ }
1250
+ function markName(mark) {
1251
+ if (typeof mark?.ariaLabel === "string" && mark.ariaLabel) return mark.ariaLabel;
1252
+ return mark?.constructor?.name?.toLowerCase() ?? "mark";
1253
+ }
1254
+ function describeScale(name, scale) {
1255
+ if (!isObject2(scale)) return null;
1256
+ const label = typeof scale.label === "string" && scale.label ? scale.label : "";
1257
+ const domain = Array.isArray(scale.domain) ? scale.domain : [];
1258
+ let range = "";
1259
+ if (domain.length && domain.every((v) => typeof v === "number" || v instanceof Date) && !/ordinal|band|point/.test(scale.type)) {
1260
+ const fmt = (v) => v instanceof Date ? v.toISOString().slice(0, 10) : String(Number(v.toPrecision(4)));
1261
+ range = `${fmt(domain[0])} to ${fmt(domain.at(-1))}`;
1262
+ } else if (domain.length) {
1263
+ range = `${domain.length} ${domain.length === 1 ? "value" : "values"}`;
1264
+ }
1265
+ const details = [scale.type, range].filter(Boolean).join(", ");
1266
+ return `${name}${label ? `=${label}` : ""}${details ? ` (${details})` : ""}`;
1267
+ }
1268
+ function summarizePlot(element, options) {
1269
+ const marks = (Array.isArray(options.marks) ? options.marks.flat(Infinity) : []).filter(isObject2);
1270
+ const names = [...new Set(marks.map(markName))];
1271
+ const scales = ["x", "y", "fx", "fy", "color", "r", "opacity", "symbol"].map((name) => typeof element?.scale === "function" ? describeScale(name, element.scale(name)) : null).filter(Boolean);
1272
+ const rows = marks.map((m) => Array.isArray(m.data) ? m.data.length : typeof m.data?.length === "number" ? m.data.length : 0);
1273
+ const most = rows.length ? Math.max(...rows) : 0;
1274
+ const title = typeof options.title === "string" && options.title ? ` "${options.title}"` : "";
1275
+ const parts = [...scales];
1276
+ if (most) parts.push(`${most} ${most === 1 ? "row" : "rows"}`);
1277
+ return `Plot ${names.length ? `${names.join(", ")} ` : ""}chart${title}${parts.length ? `: ${parts.join(", ")}` : ""}`;
1278
+ }
1279
+ var INK = "currentColor";
1280
+ var VEGA_CONFIG = {
1281
+ background: "transparent",
1282
+ axis: { labelColor: INK, titleColor: INK, domainColor: INK, tickColor: INK, gridColor: INK, gridOpacity: 0.15 },
1283
+ legend: { labelColor: INK, titleColor: INK },
1284
+ title: { color: INK, subtitleColor: INK },
1285
+ header: { labelColor: INK, titleColor: INK },
1286
+ view: { stroke: null }
1287
+ };
1288
+ function mergeConfig(base, own) {
1289
+ if (!isObject2(own)) return base;
1290
+ const out = { ...base };
1291
+ for (const [key2, value] of Object.entries(own)) {
1292
+ out[key2] = isObject2(value) && isObject2(base[key2]) && !Array.isArray(value) ? mergeConfig(base[key2], value) : value;
1293
+ }
1294
+ return out;
1295
+ }
1296
+ function createChart({ importModule, summarizeSpec, warn = (m) => console.warn(m) }) {
1297
+ let dom;
1298
+ async function document() {
1299
+ if (globalThis.document && typeof globalThis.document.createElementNS === "function") return globalThis.document;
1300
+ dom ??= importModule(LINKEDOM).then(({ parseHTML }) => parseHTML("<!doctype html><html><body></body></html>").document).catch((err) => {
1301
+ dom = void 0;
1302
+ throw err;
1303
+ });
1304
+ return dom;
1305
+ }
1306
+ async function renderVega(spec, options) {
1307
+ const keepSpec = isObject2(options) && options.spec === true;
1308
+ const [vega, vl] = await Promise.all([importModule(VEGA), isVegaSpec(spec) ? null : importModule(VEGA_LITE)]);
1309
+ const config = mergeConfig(VEGA_CONFIG, spec.config);
1310
+ const vgSpec = vl ? vl.compile({ ...spec, config }).spec : { ...spec, config };
1311
+ const view = new vega.View(vega.parse(vgSpec), { renderer: "none" });
1312
+ let svg;
1313
+ try {
1314
+ svg = await view.toSVG();
1315
+ } finally {
1316
+ view.finalize();
1317
+ }
1318
+ const bundle = { "image/svg+xml": withNamespace(svg) };
1319
+ const summary = summarizeSpec?.({ $schema: "https://vega.github.io/schema/vega-lite/v5.json", ...spec });
1320
+ if (summary) bundle["text/plain"] = summary;
1321
+ if (keepSpec) {
1322
+ const v4 = /\/schema\/vega-lite\/v4/.test(String(spec.$schema));
1323
+ bundle[vl ? v4 ? "application/vnd.vegalite.v4+json" : "application/vnd.vegalite.v5+json" : "application/vnd.vega.v5+json"] = spec;
1324
+ }
1325
+ return bundle;
1326
+ }
1327
+ async function renderPlot(Plot, options) {
1328
+ if (!isObject2(options)) throw new TypeError("jupyter.chart(Plot, options): options must be Plot's options object");
1329
+ const { title, subtitle, caption, ...rest } = options;
1330
+ const color = isObject2(rest.color) ? rest.color : void 0;
1331
+ const wantLegend = Boolean(color?.legend);
1332
+ const plotOptions = { ...rest, document: await document(), figure: false };
1333
+ if (color) plotOptions.color = { ...color, legend: false };
1334
+ const others = OTHER_LEGENDS.filter((name) => isObject2(rest[name]) && rest[name].legend);
1335
+ if (others.length) {
1336
+ warn(`jupyter.chart draws only a color legend; the ${others.join(", ")} legend is left out`);
1337
+ }
1338
+ const element = Plot.plot(plotOptions);
1339
+ const inner = element.outerHTML;
1340
+ const { width } = svgSize(inner);
1341
+ const scale = wantLegend && typeof element.scale === "function" ? element.scale("color") : void 0;
1342
+ const form = color?.legend === "ramp" || color?.legend === "swatches" ? color.legend : void 0;
1343
+ const legend = scale ? legendSVG({ ...scale, label: scale.label ?? color.label }, width, form) : void 0;
1344
+ const svg = composeSVG(inner, { title, subtitle, caption, legend });
1345
+ return { "image/svg+xml": withNamespace(svg), "text/plain": summarizePlot(element, options) };
1346
+ }
1347
+ function chart(first, second) {
1348
+ const spec = isPlotModule(first) || isSvgElement(first) ? void 0 : specOf(first);
1349
+ if (isPlotModule(first) && !isObject2(second)) {
1350
+ throw new TypeError("jupyter.chart(Plot, options): options must be Plot's options object");
1351
+ }
1352
+ if (isSvgElement(first)) {
1353
+ const bundle = { "image/svg+xml": withNamespace(first.outerHTML) };
1354
+ return { [$display2]: () => bundle };
1355
+ }
1356
+ let pending;
1357
+ const render = () => pending ??= (async () => {
1358
+ const bundle = spec ? await renderVega(spec, second) : await renderPlot(first, second);
1359
+ const size = bundle["image/svg+xml"].length;
1360
+ if (size > WARN_BYTES) {
1361
+ warn(
1362
+ `jupyter.chart: the SVG is ${(size / 1e6).toFixed(1)} MB, which makes the notebook large; aggregate or sample the data first`
1363
+ );
1364
+ }
1365
+ return bundle;
1366
+ })().catch((err) => {
1367
+ pending = void 0;
1368
+ throw err;
1369
+ });
1370
+ return { [$display2]: render };
1371
+ }
1372
+ return chart;
1373
+ }
1374
+
1375
+ // runtime/table-paging.mjs
1376
+ var PAGE_TARGET = "ganymede.table.v1";
1377
+ var KEEP = 16;
1378
+ var MAX_PAGE = 1e3;
1379
+ var MAX_SORT = 1e6;
1380
+ var CHUNK = 1e4;
1381
+ var SORTS = 2;
1382
+ var NUMERIC = /* @__PURE__ */ new Set(["integer", "number"]);
1383
+ var INTEGER_TEXT = /^-?\d+$/;
1384
+ function sortNumber(value) {
1385
+ if (typeof value === "number") return value;
1386
+ if (typeof value === "string" && value.trim() !== "") return Number(value);
1387
+ return Number.NaN;
1388
+ }
1389
+ function textOf(value) {
1390
+ if (value === null || value === void 0) return "null";
1391
+ if (typeof value === "string") return value;
1392
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
1393
+ return JSON.stringify(value);
1394
+ }
1395
+ var collator = new Intl.Collator(void 0, { numeric: true });
1396
+ function compareEncoded(a, b, type, descending = false) {
1397
+ const numeric = NUMERIC.has(type) || typeof a === "number" && typeof b === "number";
1398
+ const last = (v) => v === null || v === void 0 || numeric && Number.isNaN(sortNumber(v));
1399
+ const aLast = last(a);
1400
+ const bLast = last(b);
1401
+ if (aLast || bLast) return aLast === bLast ? 0 : aLast ? 1 : -1;
1402
+ const sign = descending ? -1 : 1;
1403
+ if (numeric) {
1404
+ if (typeof a === "string" && typeof b === "string" && INTEGER_TEXT.test(a) && INTEGER_TEXT.test(b)) {
1405
+ const x = BigInt(a);
1406
+ const y = BigInt(b);
1407
+ return sign * (x < y ? -1 : x > y ? 1 : 0);
1408
+ }
1409
+ return sign * (sortNumber(a) - sortNumber(b));
1410
+ }
1411
+ if (typeof a === "boolean" && typeof b === "boolean") return sign * (Number(a) - Number(b));
1412
+ return sign * collator.compare(textOf(a), textOf(b));
1413
+ }
1414
+ function createTablePager(commApi, { keep = KEEP, newId = () => crypto.randomUUID() } = {}) {
1415
+ const tables = /* @__PURE__ */ new Map();
1416
+ const keyed = /* @__PURE__ */ new Map();
1417
+ function register(source, fields, key2) {
1418
+ let id = key2 === void 0 ? void 0 : keyed.get(key2);
1419
+ if (id === void 0 || !tables.has(id)) id = newId();
1420
+ if (key2 !== void 0) keyed.set(key2, id);
1421
+ tables.delete(id);
1422
+ tables.set(id, { source, fields, key: key2, orders: /* @__PURE__ */ new Map() });
1423
+ while (tables.size > keep) {
1424
+ const [oldest, entry] = tables.entries().next().value;
1425
+ tables.delete(oldest);
1426
+ if (entry.key !== void 0 && keyed.get(entry.key) === oldest) keyed.delete(entry.key);
1427
+ }
1428
+ return id;
1429
+ }
1430
+ function encodeRows(entry, raw) {
1431
+ return raw.map((r) => encodeRow(entry.source, entry.fields, r));
1432
+ }
1433
+ function orderFor(entry, column, descending) {
1434
+ const key2 = `${column}\0${descending}`;
1435
+ const kept = entry.orders.get(key2);
1436
+ if (kept) return kept;
1437
+ const { source, fields } = entry;
1438
+ const c = fields.findIndex((f) => f.name === column);
1439
+ if (c < 0) throw new Error(`no column ${JSON.stringify(column)}`);
1440
+ if (source.total > MAX_SORT) throw new Error(`sorting is offered up to ${MAX_SORT.toLocaleString("en-US")} rows`);
1441
+ const type = fields[c].type;
1442
+ const declared = source.columns[c].declared;
1443
+ const values = new Array(source.total);
1444
+ for (let start = 0; start < source.total; start += CHUNK) {
1445
+ const raw = source.read(start, Math.min(start + CHUNK, source.total));
1446
+ raw.forEach((r, i) => values[start + i] = encodeValue(r[c], type, declared));
1447
+ }
1448
+ const index = Array.from({ length: source.total }, (_, i) => i);
1449
+ index.sort((i, j) => compareEncoded(values[i], values[j], type, descending));
1450
+ entry.orders.set(key2, index);
1451
+ while (entry.orders.size > SORTS) entry.orders.delete(entry.orders.keys().next().value);
1452
+ return index;
1453
+ }
1454
+ function page(entry, { offset, limit, sort }) {
1455
+ const { source } = entry;
1456
+ const start = Math.max(0, Math.min(offset, source.total));
1457
+ const end = Math.min(source.total, start + Math.min(limit, MAX_PAGE));
1458
+ if (!sort) return encodeRows(entry, source.read(start, end));
1459
+ const index = orderFor(entry, String(sort.column), sort.descending === true).slice(start, end);
1460
+ const rows = [];
1461
+ for (let i = 0; i < index.length; ) {
1462
+ let j = i + 1;
1463
+ while (j < index.length && index[j] === index[j - 1] + 1) j++;
1464
+ rows.push(...source.read(index[i], index[j - 1] + 1));
1465
+ i = j;
1466
+ }
1467
+ return encodeRows(entry, rows);
1468
+ }
1469
+ const gone = "The kernel no longer has this table: run the cell again to page through it.";
1470
+ commApi.registerTarget(PAGE_TARGET, (comm, msg) => {
1471
+ const id = msg.data?.id;
1472
+ if (typeof id !== "string" || !tables.has(id)) {
1473
+ comm.send({ error: gone });
1474
+ comm.close();
1475
+ return;
1476
+ }
1477
+ comm.onMsg(({ data }) => {
1478
+ const request = data?.request;
1479
+ const entry = tables.get(id);
1480
+ if (!entry) {
1481
+ comm.send({ request, error: gone });
1482
+ comm.close();
1483
+ return;
1484
+ }
1485
+ const offset = data?.offset;
1486
+ const limit = data?.limit ?? MAX_PAGE;
1487
+ if (!Number.isInteger(offset) || offset < 0 || !Number.isInteger(limit) || limit < 1) {
1488
+ comm.send({ request, error: "offset and limit must be whole numbers" });
1489
+ return;
1490
+ }
1491
+ try {
1492
+ const rows = page(entry, { offset, limit, sort: data?.sort ?? void 0 });
1493
+ comm.send({ request, offset, total: entry.source.total, rows });
1494
+ } catch (err) {
1495
+ comm.send({ request, error: err instanceof Error ? err.message : String(err) });
1496
+ }
1497
+ });
1498
+ });
1499
+ return { register, size: () => tables.size };
1500
+ }
1501
+
1502
+ // runtime/display.mjs
1503
+ var $display3 = /* @__PURE__ */ Symbol.for("Jupyter.display");
1504
+ var rawToEntity3 = /* @__PURE__ */ new Map([
424
1505
  ["&", "&amp;"],
425
1506
  ["<", "&lt;"],
426
1507
  [">", "&gt;"],
427
1508
  ['"', "&quot;"],
428
1509
  ["'", "&#39;"]
429
1510
  ]);
430
- var rawRe = new RegExp(`[${[...rawToEntity.keys()].join("")}]`, "g");
431
- function escapeHTML(str) {
432
- return str.replaceAll(rawRe, (m) => rawToEntity.get(m) ?? m);
433
- }
1511
+ var rawRe = new RegExp(`[${[...rawToEntity3.keys()].join("")}]`, "g");
434
1512
  function isVegaLike(obj) {
435
1513
  return obj !== null && typeof obj === "object" && "toSpec" in obj;
436
1514
  }
@@ -617,59 +1695,6 @@ function summarizeChart(spec) {
617
1695
  return void 0;
618
1696
  }
619
1697
  }
620
- function isDataFrameLike(obj) {
621
- return obj !== null && typeof obj === "object" && obj.schema !== void 0 && typeof obj.schema === "object" && typeof obj.head === "function" && typeof obj.toRecords === "function";
622
- }
623
- function mapPolarsTypeToJSONSchema(colType) {
624
- const typeMapping = {
625
- Null: "null",
626
- Bool: "boolean",
627
- Int8: "integer",
628
- Int16: "integer",
629
- Int32: "integer",
630
- Int64: "integer",
631
- UInt8: "integer",
632
- UInt16: "integer",
633
- UInt32: "integer",
634
- UInt64: "integer",
635
- Float32: "number",
636
- Float64: "number",
637
- Date: "string",
638
- Datetime: "string",
639
- Utf8: "string",
640
- Categorical: "string",
641
- List: "array",
642
- Struct: "object"
643
- };
644
- const dataType = typeof colType?.toJSON === "function" ? colType.toJSON()["DataType"] : void 0;
645
- return typeMapping[dataType] || "string";
646
- }
647
- function extractDataFrame(df) {
648
- const fields = [];
649
- for (const [name, colType] of Object.entries(df.schema)) {
650
- fields.push({ name, type: mapPolarsTypeToJSONSchema(colType) });
651
- }
652
- const data = df.head(50).toRecords();
653
- let html = "<table><thead><tr>";
654
- for (const field of fields) html += `<th>${escapeHTML(String(field.name))}</th>`;
655
- html += "</tr></thead><tbody>";
656
- for (const row of df.head(10).toRecords()) {
657
- html += "<tr>";
658
- for (const field of fields) html += `<td>${escapeHTML(String(row[field.name]))}</td>`;
659
- html += "</tr>";
660
- }
661
- html += "</tbody></table>";
662
- const size = [];
663
- if (Number.isInteger(df.height)) size.push(`${df.height} ${df.height === 1 ? "row" : "rows"}`);
664
- size.push(`${fields.length} ${fields.length === 1 ? "column" : "columns"}`);
665
- const columns = fields.slice(0, 20).map((field) => `${clip(String(field.name), 40)} (${field.type})`);
666
- if (fields.length > 20) columns.push(`\u2026 ${fields.length - 20} more`);
667
- return {
668
- "application/vnd.dataresource+json": { data, schema: { fields } },
669
- "text/html": html,
670
- "text/plain": `DataFrame (${size.join(", ")})${columns.length ? `: ${columns.join(", ")}` : ""}`
671
- };
672
- }
673
1698
  function isCanvasLike(obj) {
674
1699
  return obj !== null && typeof obj === "object" && "toDataURL" in obj;
675
1700
  }
@@ -687,7 +1712,7 @@ function isHTMLElementLike(obj) {
687
1712
  return obj !== null && typeof obj === "object" && typeof obj.outerHTML === "string";
688
1713
  }
689
1714
  function hasDisplaySymbol(obj) {
690
- return obj !== null && typeof obj === "object" && typeof obj[$display] === "function";
1715
+ return obj !== null && typeof obj === "object" && typeof obj[$display3] === "function";
691
1716
  }
692
1717
  function isMediaBundle(obj) {
693
1718
  return obj !== null && typeof obj === "object" && !Array.isArray(obj);
@@ -705,7 +1730,7 @@ function createJupyter(host2) {
705
1730
  return await formatNow(obj);
706
1731
  }
707
1732
  function formatNow(obj) {
708
- if (hasDisplaySymbol(obj)) return obj[$display]();
1733
+ if (hasDisplaySymbol(obj)) return obj[$display3]();
709
1734
  if (typeof obj !== "object" || obj === null) return { "text/plain": host2.inspect(obj) };
710
1735
  if (isCanvasLike(obj)) {
711
1736
  const [prefix, data] = obj.toDataURL().split(",");
@@ -715,7 +1740,7 @@ function createJupyter(host2) {
715
1740
  const vega = extractVega(obj);
716
1741
  if (vega) return vega;
717
1742
  }
718
- if (isDataFrameLike(obj)) return extractDataFrame(obj);
1743
+ if (isTableLike(obj)) return tableBundle(tableSource(obj));
719
1744
  if (isJpg(obj)) return { "image/jpeg": host2.base64(obj) };
720
1745
  if (isPng(obj)) return { "image/png": host2.base64(obj) };
721
1746
  if (isSVGElementLike(obj)) return { "image/svg+xml": obj.outerHTML };
@@ -725,7 +1750,7 @@ function createJupyter(host2) {
725
1750
  function taggedTemplate(mediatype) {
726
1751
  return (strings, ...values) => {
727
1752
  const payload = strings.reduce((acc, string, i) => acc + string + (values[i] !== void 0 ? values[i] : ""), "");
728
- return { [$display]: () => ({ [mediatype]: payload }) };
1753
+ return { [$display3]: () => ({ [mediatype]: payload }) };
729
1754
  };
730
1755
  }
731
1756
  function image(obj) {
@@ -735,19 +1760,24 @@ function createJupyter(host2) {
735
1760
  } catch {
736
1761
  }
737
1762
  }
738
- if (isJpg(obj)) return { [$display]: () => ({ "image/jpeg": host2.base64(obj) }) };
739
- if (isPng(obj)) return { [$display]: () => ({ "image/png": host2.base64(obj) }) };
1763
+ if (isJpg(obj)) return { [$display3]: () => ({ "image/jpeg": host2.base64(obj) }) };
1764
+ if (isPng(obj)) return { [$display3]: () => ({ "image/png": host2.base64(obj) }) };
740
1765
  throw new TypeError(
741
1766
  "Object is not a valid image or a path to an image. `jupyter.image` supports displaying JPG or PNG images."
742
1767
  );
743
1768
  }
1769
+ function table(value, options = {}) {
1770
+ const bundle = tableBundle(tableSource(value, options), options);
1771
+ return { [$display3]: () => bundle };
1772
+ }
744
1773
  function broadcast(msgType, content, { metadata = {}, buffers = [] } = {}) {
745
1774
  host2.send(msgType, content, metadata, buffers);
746
1775
  }
1776
+ let pager;
747
1777
  async function display(obj, options = { raw: false, update: false }) {
748
1778
  const bundle = options.raw && isMediaBundle(obj) ? obj : await format2(obj);
749
1779
  broadcast(options.update ? "update_display_data" : "display_data", {
750
- data: bundle,
1780
+ data: pageTable(bundle, pager, options.display_id),
751
1781
  metadata: {},
752
1782
  transient: options.display_id ? { display_id: options.display_id } : {}
753
1783
  });
@@ -757,7 +1787,7 @@ function createJupyter(host2) {
757
1787
  try {
758
1788
  broadcast("execute_result", {
759
1789
  execution_count: executionCount,
760
- data: await format2(result2),
1790
+ data: pageTable(await format2(result2), pager),
761
1791
  metadata: {},
762
1792
  // nbclient crashes on a null `transient`; Deno learned this too.
763
1793
  transient: {}
@@ -811,7 +1841,15 @@ function createJupyter(host2) {
811
1841
  transient: {}
812
1842
  });
813
1843
  }
1844
+ const cellImport = (what) => (specifier) => {
1845
+ const load = globalThis.__cdpk?.import;
1846
+ if (typeof load !== "function") return Promise.reject(new Error(`${what}: no module loader in this runtime`));
1847
+ return load(specifier);
1848
+ };
1849
+ const sql = createSql({ importModule: cellImport("jupyter.sql"), signal: () => current.signal });
1850
+ const chart = createChart({ importModule: cellImport("jupyter.chart"), summarizeSpec: summarizeChart });
814
1851
  const comms = createComms((msgType, content, metadata, buffers) => host2.send(msgType, content, metadata, buffers));
1852
+ pager = createTablePager(comms.api);
815
1853
  let cell = new AbortController();
816
1854
  let current = cell;
817
1855
  const handlers = /* @__PURE__ */ new Map();
@@ -848,15 +1886,18 @@ function createJupyter(host2) {
848
1886
  return readParams();
849
1887
  },
850
1888
  result,
1889
+ table,
1890
+ sql,
1891
+ chart,
851
1892
  format: format2,
852
1893
  md: taggedTemplate("text/markdown"),
853
1894
  html: taggedTemplate("text/html"),
854
1895
  svg: taggedTemplate("image/svg+xml"),
855
1896
  image,
856
- $display,
1897
+ $display: $display3,
857
1898
  comm: comms.api,
858
1899
  host: hostApi.host,
859
- widget: createWidgetFactory(comms.api, $display)
1900
+ widget: createWidgetFactory(comms.api, $display3)
860
1901
  },
861
1902
  broadcastResult,
862
1903
  formatNow,
@@ -1155,7 +2196,8 @@ ${resolved.name} has a getter, which isn't called for inspection: it could have
1155
2196
 
1156
2197
  // runtime/cdn.mjs
1157
2198
  var DEFAULT_CDN = "https://esm.sh/";
1158
- function resolveSpecifier(specifier, base, cdn2 = DEFAULT_CDN) {
2199
+ function resolveSpecifier(specifier, base, cdn2 = DEFAULT_CDN, modules2 = void 0) {
2200
+ if (modules2 && Object.hasOwn(modules2, specifier)) return new URL(modules2[specifier], base).href;
1159
2201
  if (/^(?:https?|data|blob):/.test(specifier)) return specifier;
1160
2202
  if (/^\.{0,2}\//.test(specifier)) return new URL(specifier, base).href;
1161
2203
  if (specifier.startsWith("npm:")) return cdn2 + specifier.slice(4);
@@ -1210,6 +2252,7 @@ var jupyter = createJupyter({
1210
2252
  });
1211
2253
  var paramsText;
1212
2254
  var cdn = DEFAULT_CDN;
2255
+ var modules;
1213
2256
  var hostCalls = /* @__PURE__ */ new Map();
1214
2257
  var hostListeners = /* @__PURE__ */ new Set();
1215
2258
  var nextHostId = 0;
@@ -1276,13 +2319,14 @@ async function preload(urls, base) {
1276
2319
  }
1277
2320
  }
1278
2321
  var handle = null;
1279
- function install({ base, cdn: cdnBase, params }) {
2322
+ function install({ base, cdn: cdnBase, modules: moduleMap, params }) {
1280
2323
  if (typeof params === "string") paramsText = params;
1281
2324
  if (typeof cdnBase === "string") cdn = cdnBase.endsWith("/") ? cdnBase : cdnBase + "/";
2325
+ if (moduleMap !== null && typeof moduleMap === "object") modules = { ...moduleMap };
1282
2326
  const url = new URL("$cdpk$repl.mjs", base).href;
1283
2327
  globalThis.__cdpk = {
1284
- import: (specifier, options) => import(resolveSpecifier(specifier, url, cdn)),
1285
- meta: { url, resolve: (specifier) => resolveSpecifier(specifier, url, cdn) },
2328
+ import: (specifier, options) => import(resolveSpecifier(specifier, url, cdn, modules)),
2329
+ meta: { url, resolve: (specifier) => resolveSpecifier(specifier, url, cdn, modules) },
1286
2330
  // inspect_request (#10): by descriptor, so nothing runs.
1287
2331
  describeForInspect: (value, detail) => describeValue(value, { inspect: (v) => inspect(v, { depth: 2 }), detail }),
1288
2332
  inspectPath: (path, detail) => inspectPath(path, { inspect: (v) => inspect(v, { depth: 2 }), detail }),