@tlockney/cdp-kernel 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/kernel.js +584 -425
- package/dist/browser/kernel.js.map +3 -3
- package/dist/browser/target.d.ts +7 -0
- package/dist/browser/target.js +2 -1
- package/dist/browser/worker.js +1191 -76
- package/dist/browser/worker.js.map +4 -4
- package/dist/celld-target.d.ts +2 -1
- package/dist/celld-target.js +5 -3
- package/dist/kernel-core.js +8 -0
- package/dist/package.d.ts +3 -3
- package/dist/package.js +6 -2
- package/dist/workerd-target.js +6 -2
- package/dist/workers-project.js +1 -1
- package/package.json +3 -3
- package/runtime/bootstrap.mjs +2 -0
- package/runtime/browser-worker.mjs +5 -1
- package/runtime/chart.d.mts +23 -0
- package/runtime/chart.mjs +359 -0
- package/runtime/display.d.mts +199 -1
- package/runtime/display.mjs +101 -65
- package/runtime/host.d.mts +248 -0
- package/runtime/host.mjs +215 -0
- package/runtime/sql.d.mts +147 -0
- package/runtime/sql.mjs +426 -0
- package/runtime/table.d.mts +39 -0
- package/runtime/table.mjs +466 -0
- package/runtime/workerd-worker.mjs +5 -1
package/dist/browser/worker.js
CHANGED
|
@@ -86,6 +86,174 @@ function createComms(send, newId = () => crypto.randomUUID()) {
|
|
|
86
86
|
return { api, dispatch, list: list2 };
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
// runtime/host.mjs
|
|
90
|
+
var HOST_TARGET = "ganymede.host.v1";
|
|
91
|
+
var CODES = /* @__PURE__ */ new Set(["unavailable", "denied", "cancelled", "timeout", "failed", "disconnected"]);
|
|
92
|
+
var HostError = class _HostError extends Error {
|
|
93
|
+
constructor(code, message) {
|
|
94
|
+
super(message);
|
|
95
|
+
this.name = "HostError";
|
|
96
|
+
this.code = code;
|
|
97
|
+
}
|
|
98
|
+
/** An abort becomes `cancelled`, a timeout signal's abort `timeout`, anything else `failed`. */
|
|
99
|
+
static from(err) {
|
|
100
|
+
if (err instanceof _HostError) return err;
|
|
101
|
+
const message = typeof err?.message === "string" ? err.message : String(err);
|
|
102
|
+
if (err?.name === "AbortError") return new _HostError("cancelled", message);
|
|
103
|
+
if (err?.name === "TimeoutError") return new _HostError("timeout", message);
|
|
104
|
+
if (typeof err?.code === "string" && CODES.has(err.code)) return new _HostError(err.code, message);
|
|
105
|
+
return new _HostError("failed", message);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
function abortError(reason) {
|
|
109
|
+
const message = typeof reason?.message === "string" ? reason.message : String(reason ?? "aborted");
|
|
110
|
+
return new HostError(reason?.name === "TimeoutError" ? "timeout" : "cancelled", message);
|
|
111
|
+
}
|
|
112
|
+
function helloOf(data) {
|
|
113
|
+
if (data === null || typeof data !== "object" || typeof data.protocol !== "number") return null;
|
|
114
|
+
return {
|
|
115
|
+
protocol: data.protocol,
|
|
116
|
+
server: typeof data.server === "string" ? data.server : "",
|
|
117
|
+
notebook: typeof data.notebook === "string" ? data.notebook : null,
|
|
118
|
+
methods: Array.isArray(data.methods) ? data.methods.filter((m) => typeof m === "string") : []
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function createHost(comms, defaultSignal = () => void 0) {
|
|
122
|
+
let link = null;
|
|
123
|
+
const waiters = /* @__PURE__ */ new Set();
|
|
124
|
+
let noServer = false;
|
|
125
|
+
let nextId = 0;
|
|
126
|
+
function settle() {
|
|
127
|
+
const host3 = link?.host ?? null;
|
|
128
|
+
if (host3 === null && link !== null) return;
|
|
129
|
+
for (const waiter of [...waiters]) waiter(host3);
|
|
130
|
+
}
|
|
131
|
+
function adopt(comm, data, ours = false) {
|
|
132
|
+
const old = link;
|
|
133
|
+
const l = { comm, host: null, pending: /* @__PURE__ */ new Map(), ours };
|
|
134
|
+
if (!ours) noServer = false;
|
|
135
|
+
link = l;
|
|
136
|
+
if (old !== null && !old.comm.closed) old.comm.close();
|
|
137
|
+
const hello = helloOf(data);
|
|
138
|
+
if (hello !== null) l.host = makeHost(l, hello);
|
|
139
|
+
comm.onMsg(({ data: data2 }) => {
|
|
140
|
+
if (data2.hello !== void 0) {
|
|
141
|
+
const hello2 = helloOf(data2.hello);
|
|
142
|
+
if (hello2 !== null) {
|
|
143
|
+
l.host = makeHost(l, hello2);
|
|
144
|
+
if (link === l) settle();
|
|
145
|
+
}
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const call = l.pending.get(data2.id);
|
|
149
|
+
if (call === void 0) return;
|
|
150
|
+
if (data2.error !== void 0) {
|
|
151
|
+
const { code, message } = data2.error ?? {};
|
|
152
|
+
call.reject(new HostError(CODES.has(code) ? code : "failed", typeof message === "string" ? message : String(code)));
|
|
153
|
+
} else {
|
|
154
|
+
call.resolve(data2.result);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
comm.onClose(({ data: data2 }) => {
|
|
158
|
+
for (const call of l.pending.values()) {
|
|
159
|
+
call.reject(new HostError("disconnected", typeof data2?.error === "string" ? `host comm closed: ${data2.error}` : "host comm closed"));
|
|
160
|
+
}
|
|
161
|
+
l.pending.clear();
|
|
162
|
+
if (link === l) {
|
|
163
|
+
link = null;
|
|
164
|
+
if (l.ours && l.host === null) noServer = true;
|
|
165
|
+
settle();
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
if (l.host !== null) settle();
|
|
169
|
+
}
|
|
170
|
+
function makeHost(l, hello) {
|
|
171
|
+
const methods = Object.freeze([...hello.methods]);
|
|
172
|
+
function call(method, params = {}, { signal = defaultSignal() } = {}) {
|
|
173
|
+
if (signal?.aborted) return Promise.reject(abortError(signal.reason));
|
|
174
|
+
if (l.comm.closed) return Promise.reject(new HostError("disconnected", "host comm closed"));
|
|
175
|
+
const id = nextId++;
|
|
176
|
+
return new Promise((resolve, reject) => {
|
|
177
|
+
const onAbort = () => {
|
|
178
|
+
l.pending.delete(id);
|
|
179
|
+
if (!l.comm.closed) l.comm.send({ id, cancel: true });
|
|
180
|
+
reject(abortError(signal.reason));
|
|
181
|
+
};
|
|
182
|
+
const done = () => signal?.removeEventListener("abort", onAbort);
|
|
183
|
+
const call2 = {
|
|
184
|
+
resolve: (value) => (l.pending.delete(id), done(), resolve(value)),
|
|
185
|
+
reject: (error) => (l.pending.delete(id), done(), reject(error))
|
|
186
|
+
};
|
|
187
|
+
l.pending.set(id, call2);
|
|
188
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
189
|
+
try {
|
|
190
|
+
l.comm.send({ id, method, params });
|
|
191
|
+
} catch (err) {
|
|
192
|
+
call2.reject(new HostError("failed", `cannot send ${method}: ${err instanceof Error ? err.message : err}`));
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
const bind = (method) => (params, options) => call(method, params, options);
|
|
197
|
+
return Object.freeze({
|
|
198
|
+
protocol: hello.protocol,
|
|
199
|
+
server: hello.server,
|
|
200
|
+
notebook: hello.notebook,
|
|
201
|
+
methods,
|
|
202
|
+
has: (method) => methods.includes(method),
|
|
203
|
+
call,
|
|
204
|
+
notebooks: Object.freeze({
|
|
205
|
+
list: bind("notebooks.list"),
|
|
206
|
+
read: bind("notebooks.read"),
|
|
207
|
+
search: bind("notebooks.search"),
|
|
208
|
+
results: bind("notebooks.results"),
|
|
209
|
+
run: bind("notebooks.run")
|
|
210
|
+
}),
|
|
211
|
+
self: Object.freeze({
|
|
212
|
+
add: bind("self.add"),
|
|
213
|
+
edit: bind("self.edit"),
|
|
214
|
+
delete: bind("self.delete")
|
|
215
|
+
})
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
comms.registerTarget(HOST_TARGET, (comm, msg) => adopt(comm, msg.data));
|
|
219
|
+
async function host2({ timeout = 1e3 } = {}) {
|
|
220
|
+
if (link?.host) return link.host;
|
|
221
|
+
const signal = defaultSignal();
|
|
222
|
+
if (signal?.aborted) throw abortError(signal.reason);
|
|
223
|
+
if (noServer) return null;
|
|
224
|
+
if (link === null) adopt(comms.open(HOST_TARGET), null, true);
|
|
225
|
+
const mine = link;
|
|
226
|
+
return new Promise((resolve, reject) => {
|
|
227
|
+
let timer;
|
|
228
|
+
const onAbort = () => {
|
|
229
|
+
done();
|
|
230
|
+
reject(abortError(signal.reason));
|
|
231
|
+
};
|
|
232
|
+
const done = () => {
|
|
233
|
+
waiters.delete(waiter);
|
|
234
|
+
clearTimeout(timer);
|
|
235
|
+
signal?.removeEventListener("abort", onAbort);
|
|
236
|
+
};
|
|
237
|
+
const waiter = (host3) => {
|
|
238
|
+
done();
|
|
239
|
+
resolve(host3);
|
|
240
|
+
};
|
|
241
|
+
waiters.add(waiter);
|
|
242
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
243
|
+
timer = setTimeout(() => {
|
|
244
|
+
done();
|
|
245
|
+
if (waiters.size === 0 && link === mine && mine.host === null && mine.ours) {
|
|
246
|
+
link = null;
|
|
247
|
+
noServer = true;
|
|
248
|
+
mine.comm.close();
|
|
249
|
+
}
|
|
250
|
+
resolve(null);
|
|
251
|
+
}, timeout);
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
return { host: host2 };
|
|
255
|
+
}
|
|
256
|
+
|
|
89
257
|
// runtime/widget.mjs
|
|
90
258
|
var WIDGET_PROTOCOL_VERSION = "2.1.0";
|
|
91
259
|
var WIDGET_TARGET = "jupyter.widget";
|
|
@@ -138,10 +306,10 @@ var Widget = class {
|
|
|
138
306
|
#state;
|
|
139
307
|
#listeners = /* @__PURE__ */ new Map();
|
|
140
308
|
// event -> Set<listener>
|
|
141
|
-
constructor(comm, state, $
|
|
309
|
+
constructor(comm, state, $display4) {
|
|
142
310
|
this.#comm = comm;
|
|
143
311
|
this.#state = state;
|
|
144
|
-
this[$
|
|
312
|
+
this[$display4] = () => ({
|
|
145
313
|
[VIEW_MIME]: { version_major: 2, version_minor: 1, model_id: this.id },
|
|
146
314
|
"text/plain": `Widget(${this.id})`
|
|
147
315
|
});
|
|
@@ -206,7 +374,7 @@ var Widget = class {
|
|
|
206
374
|
}
|
|
207
375
|
}
|
|
208
376
|
};
|
|
209
|
-
function createWidgetFactory(commApi, $
|
|
377
|
+
function createWidgetFactory(commApi, $display4) {
|
|
210
378
|
const live = /* @__PURE__ */ new Map();
|
|
211
379
|
commApi.registerTarget(CONTROL_TARGET, (control) => {
|
|
212
380
|
control.onMsg((msg) => {
|
|
@@ -240,7 +408,7 @@ function createWidgetFactory(commApi, $display2) {
|
|
|
240
408
|
metadata: { version: WIDGET_PROTOCOL_VERSION },
|
|
241
409
|
buffers
|
|
242
410
|
});
|
|
243
|
-
const instance = new Widget(comm, { ...full }, $
|
|
411
|
+
const instance = new Widget(comm, { ...full }, $display4);
|
|
244
412
|
live.set(comm.id, instance);
|
|
245
413
|
comm.onMsg((msg) => instance._receive(msg));
|
|
246
414
|
comm.onClose(() => {
|
|
@@ -250,19 +418,955 @@ function createWidgetFactory(commApi, $display2) {
|
|
|
250
418
|
};
|
|
251
419
|
}
|
|
252
420
|
|
|
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 = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
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
|
+
function tableBundle(source, options = {}) {
|
|
719
|
+
const requested = options.limit ?? DEFAULT_LIMIT;
|
|
720
|
+
if (!Number.isInteger(requested) || requested < 0) throw new TypeError("jupyter.table: limit must be a whole number");
|
|
721
|
+
const limit = Math.min(requested, MAX_LIMIT);
|
|
722
|
+
const title = typeof options.title === "string" && options.title !== "" ? options.title : void 0;
|
|
723
|
+
const total = source.total;
|
|
724
|
+
const raw = source.read(0, Math.min(limit, total));
|
|
725
|
+
const fields = fieldsOf(source, raw);
|
|
726
|
+
const encode = (r) => fields.map((f, i) => encodeValue(r[i], f.type, source.columns[i].declared));
|
|
727
|
+
const utf8 = new TextEncoder();
|
|
728
|
+
const encoded = [];
|
|
729
|
+
const data = [];
|
|
730
|
+
let bytes = 0;
|
|
731
|
+
for (const r of raw) {
|
|
732
|
+
const values = encode(r);
|
|
733
|
+
const obj = {};
|
|
734
|
+
fields.forEach((f, i) => Object.defineProperty(obj, f.name, { value: values[i], enumerable: true, writable: true, configurable: true }));
|
|
735
|
+
bytes += utf8.encode(JSON.stringify(obj)).length + 1;
|
|
736
|
+
if (bytes > MAX_BYTES && data.length > 0) break;
|
|
737
|
+
encoded.push(values);
|
|
738
|
+
data.push(obj);
|
|
739
|
+
}
|
|
740
|
+
const shown = data.length;
|
|
741
|
+
const split = total > HTML_ALL;
|
|
742
|
+
const htmlHead = split ? encoded.slice(0, HTML_HEAD) : encoded.slice(0, total);
|
|
743
|
+
const htmlTail = split ? source.read(total - HTML_TAIL, total).map(encode) : [];
|
|
744
|
+
const textSplit = total > TEXT_HEAD + TEXT_TAIL;
|
|
745
|
+
const textHeadRows = encoded.slice(0, textSplit ? TEXT_HEAD : total);
|
|
746
|
+
const textTail = !textSplit ? [] : htmlTail.length ? htmlTail.slice(-TEXT_TAIL) : source.read(total - TEXT_TAIL, total).map(encode);
|
|
747
|
+
const lines = [];
|
|
748
|
+
const name = title ? `Table "${clipString(title, 80)}"` : "Table";
|
|
749
|
+
const shape = `${count(total, "row")}, ${count(fields.length, "column")}`;
|
|
750
|
+
lines.push(`${name} (${shape}${shown < total ? `; ${shown.toLocaleString("en-US")} shown` : ""})`);
|
|
751
|
+
if (fields.length) {
|
|
752
|
+
lines.push(shown < total ? `Columns (ranges over the first ${shown.toLocaleString("en-US")} of ${total.toLocaleString("en-US")} rows):` : "Columns:");
|
|
753
|
+
const nameWidth = Math.min(30, Math.max(...fields.slice(0, TEXT_COLUMNS).map((f) => f.name.length)));
|
|
754
|
+
const typeWidth = Math.max(...fields.slice(0, TEXT_COLUMNS).map((f) => f.type.length));
|
|
755
|
+
fields.slice(0, TEXT_COLUMNS).forEach((f, i) => {
|
|
756
|
+
const stats = columnStats(encoded, i, f.type);
|
|
757
|
+
lines.push(` ${clipString(f.name, 30).padEnd(nameWidth)} ${f.type.padEnd(typeWidth)} ${stats}`.trimEnd());
|
|
758
|
+
});
|
|
759
|
+
if (fields.length > TEXT_COLUMNS) lines.push(` \u2026 ${count(fields.length - TEXT_COLUMNS, "more column")}`);
|
|
760
|
+
const numbered = [
|
|
761
|
+
...textHeadRows.map((r, i) => [i, r]),
|
|
762
|
+
...textTail.map((r, i) => [total - textTail.length + i, r])
|
|
763
|
+
];
|
|
764
|
+
if (numbered.length) {
|
|
765
|
+
lines.push("Rows:");
|
|
766
|
+
const table = textTable(fields.slice(0, TEXT_COLUMNS), numbered.map(([n, r]) => [n, r.slice(0, TEXT_COLUMNS)]));
|
|
767
|
+
if (textTail.length) table.splice(textHeadRows.length, 0, " \u2026");
|
|
768
|
+
lines.push(...table);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
let text = lines.join("\n");
|
|
772
|
+
if (text.length > TEXT_MAX) text = `${text.slice(0, TEXT_MAX - 1)}\u2026`;
|
|
773
|
+
const resource = { schema: { fields }, data, ganymede: { rows: total, shown } };
|
|
774
|
+
if (title) resource.ganymede.title = title;
|
|
775
|
+
return {
|
|
776
|
+
[TABLE_MIME]: resource,
|
|
777
|
+
"text/html": htmlTable(fields, htmlHead, htmlTail, total, title),
|
|
778
|
+
"text/plain": text
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// runtime/sql.mjs
|
|
783
|
+
var NODE_API = "npm:@duckdb/node-api@1.5.6-r.2";
|
|
784
|
+
var DUCKDB_WASM = "npm:@duckdb/duckdb-wasm@1.29.0";
|
|
785
|
+
var $display = /* @__PURE__ */ Symbol.for("Jupyter.display");
|
|
786
|
+
var RAW = /* @__PURE__ */ Symbol("cdpk.sql.raw");
|
|
787
|
+
function jsValue(value, declared) {
|
|
788
|
+
if (/^time(?!stamp)/i.test(declared ?? "") && (typeof value === "bigint" || typeof value === "number")) {
|
|
789
|
+
return timeText(value, declared);
|
|
790
|
+
}
|
|
791
|
+
if (typeof value === "bigint") return Number.isSafeInteger(Number(value)) ? Number(value) : value;
|
|
792
|
+
if (typeof value === "number" && /^(date|timestamp)/i.test(declared ?? "")) return new Date(value);
|
|
793
|
+
if (Array.isArray(value)) return value.map((v) => jsValue(v));
|
|
794
|
+
if (value !== null && typeof value === "object" && !(value instanceof Date) && !ArrayBuffer.isView(value)) {
|
|
795
|
+
if (typeof value.toJSON === "function" && !(Symbol.iterator in value) && Object.getPrototypeOf(value) !== Object.prototype) {
|
|
796
|
+
return jsValue(value.toJSON());
|
|
797
|
+
}
|
|
798
|
+
if (typeof value.toArray === "function") return jsValue(Array.from(value.toArray()));
|
|
799
|
+
const out = {};
|
|
800
|
+
for (const [k, v] of Object.entries(value)) out[k] = jsValue(v);
|
|
801
|
+
return out;
|
|
802
|
+
}
|
|
803
|
+
return value;
|
|
804
|
+
}
|
|
805
|
+
function decimalValue(words, scale) {
|
|
806
|
+
if (typeof words === "bigint" && scale === 0) return jsValue(words);
|
|
807
|
+
if (typeof words === "number" || typeof words === "bigint") return Number(words) / 10 ** scale;
|
|
808
|
+
const list2 = Array.from(words);
|
|
809
|
+
let big = 0n;
|
|
810
|
+
list2.forEach((w, i) => big |= BigInt(w >>> 0) << BigInt(32 * i));
|
|
811
|
+
const bits = BigInt(32 * list2.length);
|
|
812
|
+
if (list2.length && list2.at(-1) >>> 31) big -= 1n << bits;
|
|
813
|
+
return scale === 0 ? jsValue(big) : Number(big) / 10 ** scale;
|
|
814
|
+
}
|
|
815
|
+
var SqlResult = class {
|
|
816
|
+
/**
|
|
817
|
+
* @param {{ name: string, type: string }[]} columns
|
|
818
|
+
* @param {Record<string, unknown>[]} rows
|
|
819
|
+
*/
|
|
820
|
+
constructor(columns, rows) {
|
|
821
|
+
this.columns = columns;
|
|
822
|
+
this.rows = rows;
|
|
823
|
+
}
|
|
824
|
+
/** table.mjs reads this instead of guessing at the shape. */
|
|
825
|
+
toTableSource(names) {
|
|
826
|
+
const columns = names ? this.columns.filter((c) => names.includes(c.name)) : this.columns;
|
|
827
|
+
return {
|
|
828
|
+
columns: columns.map((c) => ({ name: c.name, declared: c.type })),
|
|
829
|
+
total: this.rows.length,
|
|
830
|
+
read: (start, end) => this.rows.slice(start, end).map((row) => columns.map((c) => row[c.name]))
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
[$display]() {
|
|
834
|
+
return tableBundle(tableSource(this));
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
function toParam(value) {
|
|
838
|
+
if (value === void 0) return null;
|
|
839
|
+
if (value instanceof Date) return value.toISOString();
|
|
840
|
+
if (typeof value === "object" && value !== null && !ArrayBuffer.isView(value)) return JSON.stringify(value);
|
|
841
|
+
return value;
|
|
842
|
+
}
|
|
843
|
+
function compose(strings, values) {
|
|
844
|
+
let text = strings[0];
|
|
845
|
+
const params = [];
|
|
846
|
+
values.forEach((value, i) => {
|
|
847
|
+
if (value !== null && typeof value === "object" && RAW in value) text += value[RAW];
|
|
848
|
+
else {
|
|
849
|
+
text += "?";
|
|
850
|
+
params.push(toParam(value));
|
|
851
|
+
}
|
|
852
|
+
text += strings[i + 1];
|
|
853
|
+
});
|
|
854
|
+
return { text, params };
|
|
855
|
+
}
|
|
856
|
+
function quoteIdent(name) {
|
|
857
|
+
return `"${String(name).replaceAll('"', '""')}"`;
|
|
858
|
+
}
|
|
859
|
+
function quoteString(text) {
|
|
860
|
+
return `'${String(text).replaceAll("'", "''")}'`;
|
|
861
|
+
}
|
|
862
|
+
function sqlLiteral(value) {
|
|
863
|
+
const text = literalText(value);
|
|
864
|
+
return text.startsWith("-") ? `(${text})` : text;
|
|
865
|
+
}
|
|
866
|
+
function literalText(value) {
|
|
867
|
+
if (value === null || value === void 0) return "NULL";
|
|
868
|
+
if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
|
|
869
|
+
if (typeof value === "bigint") return `${value}`;
|
|
870
|
+
if (typeof value === "number") {
|
|
871
|
+
if (Number.isSafeInteger(value)) return String(value);
|
|
872
|
+
return Number.isFinite(value) ? `${value}::DOUBLE` : `'${value}'::DOUBLE`;
|
|
873
|
+
}
|
|
874
|
+
if (value instanceof Uint8Array) {
|
|
875
|
+
return `'${Array.from(value, (b) => `\\x${b.toString(16).padStart(2, "0")}`).join("")}'::BLOB`;
|
|
876
|
+
}
|
|
877
|
+
if (ArrayBuffer.isView(value)) return `[${Array.from(value, (v) => sqlLiteral(v)).join(", ")}]`;
|
|
878
|
+
return quoteString(value);
|
|
879
|
+
}
|
|
880
|
+
function inlineParams(text, params) {
|
|
881
|
+
if (params.length === 0) return text;
|
|
882
|
+
let out = "";
|
|
883
|
+
let next = 0;
|
|
884
|
+
for (let i = 0; i < text.length; i++) {
|
|
885
|
+
const c = text[i];
|
|
886
|
+
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;
|
|
887
|
+
if (end !== void 0) {
|
|
888
|
+
const stop = end < 0 || end === 0 ? text.length : end + 1;
|
|
889
|
+
out += text.slice(i, stop);
|
|
890
|
+
i = stop - 1;
|
|
891
|
+
} else if (c === "?") {
|
|
892
|
+
if (next >= params.length) throw new RangeError("jupyter.sql: more ? placeholders than parameters");
|
|
893
|
+
if (out.endsWith("'") || out.endsWith('"')) out += " ";
|
|
894
|
+
out += sqlLiteral(params[next++]);
|
|
895
|
+
} else out += c;
|
|
896
|
+
}
|
|
897
|
+
if (next < params.length) throw new RangeError("jupyter.sql: more parameters than ? placeholders");
|
|
898
|
+
return out;
|
|
899
|
+
}
|
|
900
|
+
var DUCK_TYPES = {
|
|
901
|
+
integer: "BIGINT",
|
|
902
|
+
number: "DOUBLE",
|
|
903
|
+
boolean: "BOOLEAN",
|
|
904
|
+
string: "VARCHAR",
|
|
905
|
+
date: "DATE",
|
|
906
|
+
datetime: "TIMESTAMP",
|
|
907
|
+
time: "TIME",
|
|
908
|
+
array: "JSON",
|
|
909
|
+
object: "JSON",
|
|
910
|
+
duration: "VARCHAR"
|
|
911
|
+
};
|
|
912
|
+
function createSql({ importModule, signal, openEngine }) {
|
|
913
|
+
let engine = null;
|
|
914
|
+
const open = () => engine ??= (openEngine ?? (() => defaultEngine(importModule)))().catch((err) => {
|
|
915
|
+
engine = null;
|
|
916
|
+
throw err;
|
|
917
|
+
});
|
|
918
|
+
async function run(text, params) {
|
|
919
|
+
const s = signal?.();
|
|
920
|
+
const e = await open();
|
|
921
|
+
if (s?.aborted) throw s.reason;
|
|
922
|
+
const onAbort = () => e.interrupt?.();
|
|
923
|
+
s?.addEventListener("abort", onAbort, { once: true });
|
|
924
|
+
try {
|
|
925
|
+
return await e.query(text, params);
|
|
926
|
+
} finally {
|
|
927
|
+
s?.removeEventListener("abort", onAbort);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
function sql(strings, ...values) {
|
|
931
|
+
if (!Array.isArray(strings) || !Array.isArray(strings.raw)) {
|
|
932
|
+
return Promise.reject(new TypeError("jupyter.sql is a tag: jupyter.sql`select ...`; for a string, use jupyter.sql.query(text, params)"));
|
|
933
|
+
}
|
|
934
|
+
const { text, params } = compose(strings, values);
|
|
935
|
+
return run(text, params);
|
|
936
|
+
}
|
|
937
|
+
sql.query = (text, params = []) => {
|
|
938
|
+
if (typeof text !== "string") return Promise.reject(new TypeError("jupyter.sql.query: text must be a string"));
|
|
939
|
+
return run(text, params.map(toParam));
|
|
940
|
+
};
|
|
941
|
+
sql.raw = (text) => ({ [RAW]: String(text) });
|
|
942
|
+
sql.load = async (name, source) => {
|
|
943
|
+
if (typeof name !== "string" || name === "") throw new TypeError("jupyter.sql.load: name must be a non-empty string");
|
|
944
|
+
const table = quoteIdent(name);
|
|
945
|
+
if (typeof source === "string") {
|
|
946
|
+
await run(`create or replace table ${table} as select * from ${quoteString(source)}`, []);
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
const { structure, json } = rowsAsJson(source);
|
|
950
|
+
if (structure === null) {
|
|
951
|
+
throw new TypeError("jupyter.sql.load: the rows have no columns");
|
|
952
|
+
}
|
|
953
|
+
await run(
|
|
954
|
+
`create or replace table ${table} as select unnest(from_json(?, ?), recursive := true)`,
|
|
955
|
+
[json, structure]
|
|
956
|
+
);
|
|
957
|
+
};
|
|
958
|
+
sql.close = async () => {
|
|
959
|
+
const e = engine;
|
|
960
|
+
engine = null;
|
|
961
|
+
if (e) await (await e).close();
|
|
962
|
+
};
|
|
963
|
+
sql.connection = async () => (await open()).connection;
|
|
964
|
+
return sql;
|
|
965
|
+
}
|
|
966
|
+
function loadValue(value, type, depth = 0) {
|
|
967
|
+
if (value === null || value === void 0) return null;
|
|
968
|
+
if (type === "date" || type === "datetime") {
|
|
969
|
+
const d = value instanceof Date ? value : typeof value === "number" || typeof value === "bigint" ? new Date(Number(value)) : null;
|
|
970
|
+
if (d) return Number.isNaN(d.getTime()) ? null : type === "date" ? d.toISOString().slice(0, 10) : d.toISOString();
|
|
971
|
+
}
|
|
972
|
+
if (typeof value === "bigint") return Number.isSafeInteger(Number(value)) ? Number(value) : String(value);
|
|
973
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
974
|
+
if (typeof value !== "object") return value;
|
|
975
|
+
if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value.toISOString();
|
|
976
|
+
if (depth > 64) throw new TypeError("jupyter.sql.load: a value is nested more than 64 levels deep");
|
|
977
|
+
if (ArrayBuffer.isView(value) && !(value instanceof DataView) && value.BYTES_PER_ELEMENT === 1) {
|
|
978
|
+
let text = "";
|
|
979
|
+
for (const byte of value) text += String.fromCharCode(byte);
|
|
980
|
+
return btoa(text);
|
|
981
|
+
}
|
|
982
|
+
if (Array.isArray(value) || ArrayBuffer.isView(value)) return Array.from(value, (v) => loadValue(v, void 0, depth + 1));
|
|
983
|
+
if (typeof value.toJSON === "function" && Object.getPrototypeOf(value) !== Object.prototype) {
|
|
984
|
+
return loadValue(value.toJSON(), void 0, depth + 1);
|
|
985
|
+
}
|
|
986
|
+
const out = {};
|
|
987
|
+
for (const [k, v] of Object.entries(value)) out[k] = loadValue(v, void 0, depth + 1);
|
|
988
|
+
return out;
|
|
989
|
+
}
|
|
990
|
+
function rowsAsJson(source) {
|
|
991
|
+
const src = tableSource(source);
|
|
992
|
+
const raw = src.read(0, src.total);
|
|
993
|
+
const fields = fieldsOf(src, raw);
|
|
994
|
+
if (fields.length === 0) return { structure: null, json: "[]" };
|
|
995
|
+
const rows = raw.map((r) => {
|
|
996
|
+
const obj = {};
|
|
997
|
+
fields.forEach((f, i) => {
|
|
998
|
+
obj[f.name] = loadValue(r[i], f.type);
|
|
999
|
+
});
|
|
1000
|
+
return obj;
|
|
1001
|
+
});
|
|
1002
|
+
const structure = {};
|
|
1003
|
+
for (const f of fields) structure[f.name] = DUCK_TYPES[f.type] ?? "VARCHAR";
|
|
1004
|
+
return { structure: JSON.stringify([structure]), json: JSON.stringify(rows) };
|
|
1005
|
+
}
|
|
1006
|
+
function inWorker() {
|
|
1007
|
+
return typeof globalThis.WorkerGlobalScope === "function" && globalThis instanceof globalThis.WorkerGlobalScope;
|
|
1008
|
+
}
|
|
1009
|
+
function hasNativeRuntime() {
|
|
1010
|
+
return typeof globalThis.Deno?.version?.deno === "string" || typeof globalThis.Bun?.version === "string" || typeof globalThis.process?.versions?.node === "string" && !inWorker();
|
|
1011
|
+
}
|
|
1012
|
+
async function defaultEngine(importModule) {
|
|
1013
|
+
if (hasNativeRuntime()) return nativeEngine(importModule);
|
|
1014
|
+
if (inWorker() && typeof Worker === "function") return wasmEngine(importModule);
|
|
1015
|
+
throw new Error("jupyter.sql needs Node, Deno, Bun or the browser target; workerd and celld have no DuckDB");
|
|
1016
|
+
}
|
|
1017
|
+
async function nativeEngine(importModule) {
|
|
1018
|
+
const { DuckDBInstance, blobValue } = await importModule(NODE_API);
|
|
1019
|
+
const db = await DuckDBInstance.create(":memory:");
|
|
1020
|
+
const conn = await db.connect();
|
|
1021
|
+
return {
|
|
1022
|
+
connection: conn,
|
|
1023
|
+
interrupt: () => conn.interrupt?.(),
|
|
1024
|
+
async query(text, params) {
|
|
1025
|
+
const bound = params.map((p) => p instanceof Uint8Array && blobValue ? blobValue(p) : p);
|
|
1026
|
+
const result = await conn.runAndReadAll(text, bound);
|
|
1027
|
+
const types = result.columnTypes();
|
|
1028
|
+
const columns = uniqueNames(result.columnNames()).map((name, i) => ({ name, type: String(types[i]) }));
|
|
1029
|
+
const rows = result.getRowsJS().map((row) => {
|
|
1030
|
+
const out = {};
|
|
1031
|
+
columns.forEach((c, j) => out[c.name] = jsValue(row[j], c.type));
|
|
1032
|
+
return out;
|
|
1033
|
+
});
|
|
1034
|
+
return new SqlResult(columns, rows);
|
|
1035
|
+
},
|
|
1036
|
+
async close() {
|
|
1037
|
+
conn.closeSync?.();
|
|
1038
|
+
db.closeSync?.();
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
async function wasmEngine(importModule) {
|
|
1043
|
+
const duckdb = await importModule(DUCKDB_WASM);
|
|
1044
|
+
const bundle = await duckdb.selectBundle(duckdb.getJsDelivrBundles());
|
|
1045
|
+
const loader = URL.createObjectURL(new Blob([`importScripts(${JSON.stringify(bundle.mainWorker)});`], { type: "text/javascript" }));
|
|
1046
|
+
const worker = new Worker(loader);
|
|
1047
|
+
const db = new duckdb.AsyncDuckDB(new duckdb.VoidLogger(), worker);
|
|
1048
|
+
await db.instantiate(bundle.mainModule, bundle.pthreadWorker);
|
|
1049
|
+
URL.revokeObjectURL(loader);
|
|
1050
|
+
const conn = await db.connect();
|
|
1051
|
+
let cancelled = false;
|
|
1052
|
+
return {
|
|
1053
|
+
connection: conn,
|
|
1054
|
+
interrupt: () => {
|
|
1055
|
+
cancelled = true;
|
|
1056
|
+
void conn.cancelSent?.();
|
|
1057
|
+
},
|
|
1058
|
+
async query(text, params) {
|
|
1059
|
+
cancelled = false;
|
|
1060
|
+
const reader = await conn.send(inlineParams(text, params), true);
|
|
1061
|
+
const stop = () => {
|
|
1062
|
+
if (cancelled) throw new DOMException("The query was interrupted", "AbortError");
|
|
1063
|
+
};
|
|
1064
|
+
try {
|
|
1065
|
+
stop();
|
|
1066
|
+
const columns = [];
|
|
1067
|
+
const rows = [];
|
|
1068
|
+
let decimal = [];
|
|
1069
|
+
for await (const batch of reader) {
|
|
1070
|
+
if (columns.length === 0 && batch.schema.fields.length) {
|
|
1071
|
+
const fields = batch.schema.fields;
|
|
1072
|
+
uniqueNames(fields.map((f) => f.name)).forEach((name, j) => columns.push({ name, type: String(fields[j].type) }));
|
|
1073
|
+
decimal = fields.map((f) => /^decimal/i.test(String(f.type)) ? f.type.scale ?? 0 : null);
|
|
1074
|
+
}
|
|
1075
|
+
stop();
|
|
1076
|
+
const vectors = columns.map((_, j) => batch.getChildAt(j));
|
|
1077
|
+
for (let i = 0; i < batch.numRows; i++) {
|
|
1078
|
+
const row = {};
|
|
1079
|
+
columns.forEach((c, j) => {
|
|
1080
|
+
const v = vectors[j]?.get(i);
|
|
1081
|
+
row[c.name] = decimal[j] !== null && v != null ? decimalValue(v, decimal[j]) : jsValue(v, c.type);
|
|
1082
|
+
});
|
|
1083
|
+
rows.push(row);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
if (columns.length === 0 && reader.schema?.fields) {
|
|
1087
|
+
const fields = reader.schema.fields;
|
|
1088
|
+
uniqueNames(fields.map((f) => f.name)).forEach((name, j) => columns.push({ name, type: String(fields[j].type) }));
|
|
1089
|
+
}
|
|
1090
|
+
return new SqlResult(columns, rows);
|
|
1091
|
+
} catch (err) {
|
|
1092
|
+
await Promise.resolve((reader.cancel ?? reader.return)?.call(reader)).catch(() => {
|
|
1093
|
+
});
|
|
1094
|
+
throw err;
|
|
1095
|
+
}
|
|
1096
|
+
},
|
|
1097
|
+
async close() {
|
|
1098
|
+
await conn.close();
|
|
1099
|
+
await db.terminate();
|
|
1100
|
+
worker.terminate();
|
|
1101
|
+
}
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// runtime/chart.mjs
|
|
1106
|
+
var VEGA = "npm:vega@6";
|
|
1107
|
+
var VEGA_LITE = "npm:vega-lite@6";
|
|
1108
|
+
var LINKEDOM = "npm:linkedom@0.18";
|
|
1109
|
+
var WARN_BYTES = 2e6;
|
|
1110
|
+
var $display2 = /* @__PURE__ */ Symbol.for("Jupyter.display");
|
|
1111
|
+
var OTHER_LEGENDS = ["opacity", "r", "symbol"];
|
|
1112
|
+
var FONT = "system-ui, sans-serif";
|
|
1113
|
+
var rawToEntity2 = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
1114
|
+
var esc = (s) => String(s).replace(/[&<>"']/g, (c) => rawToEntity2[c]);
|
|
1115
|
+
function isObject2(value) {
|
|
1116
|
+
return value !== null && typeof value === "object";
|
|
1117
|
+
}
|
|
1118
|
+
function isPlotModule(value) {
|
|
1119
|
+
return (isObject2(value) || typeof value === "function") && typeof value.plot === "function" && typeof value.barY === "function";
|
|
1120
|
+
}
|
|
1121
|
+
var SPEC_KEYS = ["$schema", "mark", "marks", "layer", "spec", "concat", "hconcat", "vconcat", "facet", "repeat"];
|
|
1122
|
+
function specOf(value) {
|
|
1123
|
+
const spec = isObject2(value) && typeof value.toSpec === "function" ? value.toSpec() : value;
|
|
1124
|
+
if (!isObject2(spec) || Array.isArray(spec) || !SPEC_KEYS.some((key2) => key2 in spec)) {
|
|
1125
|
+
throw new TypeError(
|
|
1126
|
+
"jupyter.chart takes a Vega-Lite or Vega spec, the Plot module and Plot options, or an SVG element"
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
return spec;
|
|
1130
|
+
}
|
|
1131
|
+
function isSvgElement(value) {
|
|
1132
|
+
return isObject2(value) && typeof value.outerHTML === "string" && /^<svg\b/i.test(value.outerHTML);
|
|
1133
|
+
}
|
|
1134
|
+
function isVegaSpec(spec) {
|
|
1135
|
+
return typeof spec.$schema === "string" && /\/schema\/vega\/v\d/.test(spec.$schema);
|
|
1136
|
+
}
|
|
1137
|
+
function svgSize(svg) {
|
|
1138
|
+
const open = /<svg\b[^>]*>/i.exec(svg)?.[0] ?? "";
|
|
1139
|
+
const attr = (name) => {
|
|
1140
|
+
const m = new RegExp(`\\s${name}="([^"]*)"`, "i").exec(open);
|
|
1141
|
+
return m ? m[1] : void 0;
|
|
1142
|
+
};
|
|
1143
|
+
let width = parseFloat(attr("width") ?? "");
|
|
1144
|
+
let height = parseFloat(attr("height") ?? "");
|
|
1145
|
+
const box = attr("viewBox")?.trim().split(/[\s,]+/).map(Number);
|
|
1146
|
+
if (!Number.isFinite(width) && box?.length === 4) width = box[2];
|
|
1147
|
+
if (!Number.isFinite(height) && box?.length === 4) height = box[3];
|
|
1148
|
+
return { width: Number.isFinite(width) ? width : 640, height: Number.isFinite(height) ? height : 400 };
|
|
1149
|
+
}
|
|
1150
|
+
function withNamespace(svg) {
|
|
1151
|
+
return /<svg\b[^>]*\sxmlns=/i.test(svg) ? svg : svg.replace(/<svg\b/i, '<svg xmlns="http://www.w3.org/2000/svg"');
|
|
1152
|
+
}
|
|
1153
|
+
function hash(text) {
|
|
1154
|
+
let h = 2166136261;
|
|
1155
|
+
for (let i = 0; i < text.length; i++) h = Math.imul(h ^ text.charCodeAt(i), 16777619);
|
|
1156
|
+
return (h >>> 0).toString(36);
|
|
1157
|
+
}
|
|
1158
|
+
var textWidth = (text, size) => String(text).length * size * 0.6;
|
|
1159
|
+
function legendSVG(scale, width, form) {
|
|
1160
|
+
if (!isObject2(scale) || typeof scale.apply !== "function" || !Array.isArray(scale.domain)) return { svg: "", height: 0 };
|
|
1161
|
+
const label = typeof scale.label === "string" && scale.label ? scale.label : "";
|
|
1162
|
+
const parts = [];
|
|
1163
|
+
let y = 0;
|
|
1164
|
+
if (label) {
|
|
1165
|
+
parts.push(`<text x="0" y="10" font-weight="bold">${esc(label)}</text>`);
|
|
1166
|
+
y = 16;
|
|
1167
|
+
}
|
|
1168
|
+
const continuous = form === "ramp" || form !== "swatches" && /^(linear|log|pow|sqrt|symlog|utc|time|sequential|diverging|quantize|threshold|quantile)/.test(String(scale.type));
|
|
1169
|
+
if (!continuous) {
|
|
1170
|
+
let x = 0;
|
|
1171
|
+
let row = y;
|
|
1172
|
+
for (const value of scale.domain) {
|
|
1173
|
+
const text = String(value);
|
|
1174
|
+
const w = 14 + textWidth(text, 10) + 12;
|
|
1175
|
+
if (x > 0 && x + w > width) {
|
|
1176
|
+
x = 0;
|
|
1177
|
+
row += 16;
|
|
1178
|
+
}
|
|
1179
|
+
parts.push(
|
|
1180
|
+
`<rect x="${x}" y="${row}" width="10" height="10" fill="${esc(scale.apply(value))}"/><text x="${x + 14}" y="${row + 9}">${esc(text)}</text>`
|
|
1181
|
+
);
|
|
1182
|
+
x += w;
|
|
1183
|
+
}
|
|
1184
|
+
return { svg: parts.join(""), height: row + 14 };
|
|
1185
|
+
}
|
|
1186
|
+
const [lo, hi] = [scale.domain[0], scale.domain.at(-1)];
|
|
1187
|
+
const n = 16;
|
|
1188
|
+
const stops = [];
|
|
1189
|
+
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;
|
|
1190
|
+
const numeric = (v) => v instanceof Date ? v.getTime() : typeof v === "number" ? v : NaN;
|
|
1191
|
+
const [a, b] = [numeric(lo), numeric(hi)];
|
|
1192
|
+
for (let i = 0; i <= n; i++) {
|
|
1193
|
+
const t = i / n;
|
|
1194
|
+
let color;
|
|
1195
|
+
if (typeof scale.interpolate === "function" && range) color = scale.interpolate(range[0] + (range[1] - range[0]) * t);
|
|
1196
|
+
else if (Number.isFinite(a) && Number.isFinite(b)) {
|
|
1197
|
+
const v = a + (b - a) * t;
|
|
1198
|
+
color = scale.apply(lo instanceof Date ? new Date(v) : v);
|
|
1199
|
+
} else color = scale.apply(i === 0 ? lo : hi);
|
|
1200
|
+
stops.push(`<stop offset="${t * 100}%" stop-color="${esc(color)}"/>`);
|
|
1201
|
+
}
|
|
1202
|
+
const id = `ramp-${hash(stops.join(""))}`;
|
|
1203
|
+
const rampWidth = Math.min(240, width);
|
|
1204
|
+
const fmt = (v) => v instanceof Date ? v.toISOString().slice(0, 10) : typeof v === "number" ? String(Number(v.toPrecision(4))) : String(v);
|
|
1205
|
+
parts.push(
|
|
1206
|
+
`<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>`
|
|
1207
|
+
);
|
|
1208
|
+
return { svg: parts.join(""), height: y + 26 };
|
|
1209
|
+
}
|
|
1210
|
+
function composeSVG(inner, { title, subtitle, caption, legend }) {
|
|
1211
|
+
const { width, height } = svgSize(inner);
|
|
1212
|
+
const head = [];
|
|
1213
|
+
let y = 0;
|
|
1214
|
+
if (title) {
|
|
1215
|
+
head.push(`<text x="0" y="${y + 16}" font-size="16" font-weight="bold">${esc(title)}</text>`);
|
|
1216
|
+
y += 24;
|
|
1217
|
+
}
|
|
1218
|
+
if (subtitle) {
|
|
1219
|
+
head.push(`<text x="0" y="${y + 13}" font-size="13">${esc(subtitle)}</text>`);
|
|
1220
|
+
y += 20;
|
|
1221
|
+
}
|
|
1222
|
+
if (legend?.svg) {
|
|
1223
|
+
head.push(`<g transform="translate(0,${y})" font-size="10">${legend.svg}</g>`);
|
|
1224
|
+
y += legend.height + 8;
|
|
1225
|
+
}
|
|
1226
|
+
const body = withNamespace(inner).replace(/<svg\b/i, `<svg x="0" y="${y}"`);
|
|
1227
|
+
let total = y + height;
|
|
1228
|
+
let foot = "";
|
|
1229
|
+
if (caption) {
|
|
1230
|
+
foot = `<text x="0" y="${total + 16}" font-size="12">${esc(caption)}</text>`;
|
|
1231
|
+
total += 24;
|
|
1232
|
+
}
|
|
1233
|
+
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>`;
|
|
1234
|
+
}
|
|
1235
|
+
function markName(mark) {
|
|
1236
|
+
if (typeof mark?.ariaLabel === "string" && mark.ariaLabel) return mark.ariaLabel;
|
|
1237
|
+
return mark?.constructor?.name?.toLowerCase() ?? "mark";
|
|
1238
|
+
}
|
|
1239
|
+
function describeScale(name, scale) {
|
|
1240
|
+
if (!isObject2(scale)) return null;
|
|
1241
|
+
const label = typeof scale.label === "string" && scale.label ? scale.label : "";
|
|
1242
|
+
const domain = Array.isArray(scale.domain) ? scale.domain : [];
|
|
1243
|
+
let range = "";
|
|
1244
|
+
if (domain.length && domain.every((v) => typeof v === "number" || v instanceof Date) && !/ordinal|band|point/.test(scale.type)) {
|
|
1245
|
+
const fmt = (v) => v instanceof Date ? v.toISOString().slice(0, 10) : String(Number(v.toPrecision(4)));
|
|
1246
|
+
range = `${fmt(domain[0])} to ${fmt(domain.at(-1))}`;
|
|
1247
|
+
} else if (domain.length) {
|
|
1248
|
+
range = `${domain.length} ${domain.length === 1 ? "value" : "values"}`;
|
|
1249
|
+
}
|
|
1250
|
+
const details = [scale.type, range].filter(Boolean).join(", ");
|
|
1251
|
+
return `${name}${label ? `=${label}` : ""}${details ? ` (${details})` : ""}`;
|
|
1252
|
+
}
|
|
1253
|
+
function summarizePlot(element, options) {
|
|
1254
|
+
const marks = (Array.isArray(options.marks) ? options.marks.flat(Infinity) : []).filter(isObject2);
|
|
1255
|
+
const names = [...new Set(marks.map(markName))];
|
|
1256
|
+
const scales = ["x", "y", "fx", "fy", "color", "r", "opacity", "symbol"].map((name) => typeof element?.scale === "function" ? describeScale(name, element.scale(name)) : null).filter(Boolean);
|
|
1257
|
+
const rows = marks.map((m) => Array.isArray(m.data) ? m.data.length : typeof m.data?.length === "number" ? m.data.length : 0);
|
|
1258
|
+
const most = rows.length ? Math.max(...rows) : 0;
|
|
1259
|
+
const title = typeof options.title === "string" && options.title ? ` "${options.title}"` : "";
|
|
1260
|
+
const parts = [...scales];
|
|
1261
|
+
if (most) parts.push(`${most} ${most === 1 ? "row" : "rows"}`);
|
|
1262
|
+
return `Plot ${names.length ? `${names.join(", ")} ` : ""}chart${title}${parts.length ? `: ${parts.join(", ")}` : ""}`;
|
|
1263
|
+
}
|
|
1264
|
+
var INK = "currentColor";
|
|
1265
|
+
var VEGA_CONFIG = {
|
|
1266
|
+
background: "transparent",
|
|
1267
|
+
axis: { labelColor: INK, titleColor: INK, domainColor: INK, tickColor: INK, gridColor: INK, gridOpacity: 0.15 },
|
|
1268
|
+
legend: { labelColor: INK, titleColor: INK },
|
|
1269
|
+
title: { color: INK, subtitleColor: INK },
|
|
1270
|
+
header: { labelColor: INK, titleColor: INK },
|
|
1271
|
+
view: { stroke: null }
|
|
1272
|
+
};
|
|
1273
|
+
function mergeConfig(base, own) {
|
|
1274
|
+
if (!isObject2(own)) return base;
|
|
1275
|
+
const out = { ...base };
|
|
1276
|
+
for (const [key2, value] of Object.entries(own)) {
|
|
1277
|
+
out[key2] = isObject2(value) && isObject2(base[key2]) && !Array.isArray(value) ? mergeConfig(base[key2], value) : value;
|
|
1278
|
+
}
|
|
1279
|
+
return out;
|
|
1280
|
+
}
|
|
1281
|
+
function createChart({ importModule, summarizeSpec, warn = (m) => console.warn(m) }) {
|
|
1282
|
+
let dom;
|
|
1283
|
+
async function document() {
|
|
1284
|
+
if (globalThis.document && typeof globalThis.document.createElementNS === "function") return globalThis.document;
|
|
1285
|
+
dom ??= importModule(LINKEDOM).then(({ parseHTML }) => parseHTML("<!doctype html><html><body></body></html>").document).catch((err) => {
|
|
1286
|
+
dom = void 0;
|
|
1287
|
+
throw err;
|
|
1288
|
+
});
|
|
1289
|
+
return dom;
|
|
1290
|
+
}
|
|
1291
|
+
async function renderVega(spec, options) {
|
|
1292
|
+
const keepSpec = isObject2(options) && options.spec === true;
|
|
1293
|
+
const [vega, vl] = await Promise.all([importModule(VEGA), isVegaSpec(spec) ? null : importModule(VEGA_LITE)]);
|
|
1294
|
+
const config = mergeConfig(VEGA_CONFIG, spec.config);
|
|
1295
|
+
const vgSpec = vl ? vl.compile({ ...spec, config }).spec : { ...spec, config };
|
|
1296
|
+
const view = new vega.View(vega.parse(vgSpec), { renderer: "none" });
|
|
1297
|
+
let svg;
|
|
1298
|
+
try {
|
|
1299
|
+
svg = await view.toSVG();
|
|
1300
|
+
} finally {
|
|
1301
|
+
view.finalize();
|
|
1302
|
+
}
|
|
1303
|
+
const bundle = { "image/svg+xml": withNamespace(svg) };
|
|
1304
|
+
const summary = summarizeSpec?.({ $schema: "https://vega.github.io/schema/vega-lite/v5.json", ...spec });
|
|
1305
|
+
if (summary) bundle["text/plain"] = summary;
|
|
1306
|
+
if (keepSpec) {
|
|
1307
|
+
const v4 = /\/schema\/vega-lite\/v4/.test(String(spec.$schema));
|
|
1308
|
+
bundle[vl ? v4 ? "application/vnd.vegalite.v4+json" : "application/vnd.vegalite.v5+json" : "application/vnd.vega.v5+json"] = spec;
|
|
1309
|
+
}
|
|
1310
|
+
return bundle;
|
|
1311
|
+
}
|
|
1312
|
+
async function renderPlot(Plot, options) {
|
|
1313
|
+
if (!isObject2(options)) throw new TypeError("jupyter.chart(Plot, options): options must be Plot's options object");
|
|
1314
|
+
const { title, subtitle, caption, ...rest } = options;
|
|
1315
|
+
const color = isObject2(rest.color) ? rest.color : void 0;
|
|
1316
|
+
const wantLegend = Boolean(color?.legend);
|
|
1317
|
+
const plotOptions = { ...rest, document: await document(), figure: false };
|
|
1318
|
+
if (color) plotOptions.color = { ...color, legend: false };
|
|
1319
|
+
const others = OTHER_LEGENDS.filter((name) => isObject2(rest[name]) && rest[name].legend);
|
|
1320
|
+
if (others.length) {
|
|
1321
|
+
warn(`jupyter.chart draws only a color legend; the ${others.join(", ")} legend is left out`);
|
|
1322
|
+
}
|
|
1323
|
+
const element = Plot.plot(plotOptions);
|
|
1324
|
+
const inner = element.outerHTML;
|
|
1325
|
+
const { width } = svgSize(inner);
|
|
1326
|
+
const scale = wantLegend && typeof element.scale === "function" ? element.scale("color") : void 0;
|
|
1327
|
+
const form = color?.legend === "ramp" || color?.legend === "swatches" ? color.legend : void 0;
|
|
1328
|
+
const legend = scale ? legendSVG({ ...scale, label: scale.label ?? color.label }, width, form) : void 0;
|
|
1329
|
+
const svg = composeSVG(inner, { title, subtitle, caption, legend });
|
|
1330
|
+
return { "image/svg+xml": withNamespace(svg), "text/plain": summarizePlot(element, options) };
|
|
1331
|
+
}
|
|
1332
|
+
function chart(first, second) {
|
|
1333
|
+
const spec = isPlotModule(first) || isSvgElement(first) ? void 0 : specOf(first);
|
|
1334
|
+
if (isPlotModule(first) && !isObject2(second)) {
|
|
1335
|
+
throw new TypeError("jupyter.chart(Plot, options): options must be Plot's options object");
|
|
1336
|
+
}
|
|
1337
|
+
if (isSvgElement(first)) {
|
|
1338
|
+
const bundle = { "image/svg+xml": withNamespace(first.outerHTML) };
|
|
1339
|
+
return { [$display2]: () => bundle };
|
|
1340
|
+
}
|
|
1341
|
+
let pending;
|
|
1342
|
+
const render = () => pending ??= (async () => {
|
|
1343
|
+
const bundle = spec ? await renderVega(spec, second) : await renderPlot(first, second);
|
|
1344
|
+
const size = bundle["image/svg+xml"].length;
|
|
1345
|
+
if (size > WARN_BYTES) {
|
|
1346
|
+
warn(
|
|
1347
|
+
`jupyter.chart: the SVG is ${(size / 1e6).toFixed(1)} MB, which makes the notebook large; aggregate or sample the data first`
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
return bundle;
|
|
1351
|
+
})().catch((err) => {
|
|
1352
|
+
pending = void 0;
|
|
1353
|
+
throw err;
|
|
1354
|
+
});
|
|
1355
|
+
return { [$display2]: render };
|
|
1356
|
+
}
|
|
1357
|
+
return chart;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
253
1360
|
// runtime/display.mjs
|
|
254
|
-
var $
|
|
255
|
-
var
|
|
1361
|
+
var $display3 = /* @__PURE__ */ Symbol.for("Jupyter.display");
|
|
1362
|
+
var rawToEntity3 = /* @__PURE__ */ new Map([
|
|
256
1363
|
["&", "&"],
|
|
257
1364
|
["<", "<"],
|
|
258
1365
|
[">", ">"],
|
|
259
1366
|
['"', """],
|
|
260
1367
|
["'", "'"]
|
|
261
1368
|
]);
|
|
262
|
-
var rawRe = new RegExp(`[${[...
|
|
263
|
-
function escapeHTML(str) {
|
|
264
|
-
return str.replaceAll(rawRe, (m) => rawToEntity.get(m) ?? m);
|
|
265
|
-
}
|
|
1369
|
+
var rawRe = new RegExp(`[${[...rawToEntity3.keys()].join("")}]`, "g");
|
|
266
1370
|
function isVegaLike(obj) {
|
|
267
1371
|
return obj !== null && typeof obj === "object" && "toSpec" in obj;
|
|
268
1372
|
}
|
|
@@ -449,59 +1553,6 @@ function summarizeChart(spec) {
|
|
|
449
1553
|
return void 0;
|
|
450
1554
|
}
|
|
451
1555
|
}
|
|
452
|
-
function isDataFrameLike(obj) {
|
|
453
|
-
return obj !== null && typeof obj === "object" && obj.schema !== void 0 && typeof obj.schema === "object" && typeof obj.head === "function" && typeof obj.toRecords === "function";
|
|
454
|
-
}
|
|
455
|
-
function mapPolarsTypeToJSONSchema(colType) {
|
|
456
|
-
const typeMapping = {
|
|
457
|
-
Null: "null",
|
|
458
|
-
Bool: "boolean",
|
|
459
|
-
Int8: "integer",
|
|
460
|
-
Int16: "integer",
|
|
461
|
-
Int32: "integer",
|
|
462
|
-
Int64: "integer",
|
|
463
|
-
UInt8: "integer",
|
|
464
|
-
UInt16: "integer",
|
|
465
|
-
UInt32: "integer",
|
|
466
|
-
UInt64: "integer",
|
|
467
|
-
Float32: "number",
|
|
468
|
-
Float64: "number",
|
|
469
|
-
Date: "string",
|
|
470
|
-
Datetime: "string",
|
|
471
|
-
Utf8: "string",
|
|
472
|
-
Categorical: "string",
|
|
473
|
-
List: "array",
|
|
474
|
-
Struct: "object"
|
|
475
|
-
};
|
|
476
|
-
const dataType = typeof colType?.toJSON === "function" ? colType.toJSON()["DataType"] : void 0;
|
|
477
|
-
return typeMapping[dataType] || "string";
|
|
478
|
-
}
|
|
479
|
-
function extractDataFrame(df) {
|
|
480
|
-
const fields = [];
|
|
481
|
-
for (const [name, colType] of Object.entries(df.schema)) {
|
|
482
|
-
fields.push({ name, type: mapPolarsTypeToJSONSchema(colType) });
|
|
483
|
-
}
|
|
484
|
-
const data = df.head(50).toRecords();
|
|
485
|
-
let html = "<table><thead><tr>";
|
|
486
|
-
for (const field of fields) html += `<th>${escapeHTML(String(field.name))}</th>`;
|
|
487
|
-
html += "</tr></thead><tbody>";
|
|
488
|
-
for (const row of df.head(10).toRecords()) {
|
|
489
|
-
html += "<tr>";
|
|
490
|
-
for (const field of fields) html += `<td>${escapeHTML(String(row[field.name]))}</td>`;
|
|
491
|
-
html += "</tr>";
|
|
492
|
-
}
|
|
493
|
-
html += "</tbody></table>";
|
|
494
|
-
const size = [];
|
|
495
|
-
if (Number.isInteger(df.height)) size.push(`${df.height} ${df.height === 1 ? "row" : "rows"}`);
|
|
496
|
-
size.push(`${fields.length} ${fields.length === 1 ? "column" : "columns"}`);
|
|
497
|
-
const columns = fields.slice(0, 20).map((field) => `${clip(String(field.name), 40)} (${field.type})`);
|
|
498
|
-
if (fields.length > 20) columns.push(`\u2026 ${fields.length - 20} more`);
|
|
499
|
-
return {
|
|
500
|
-
"application/vnd.dataresource+json": { data, schema: { fields } },
|
|
501
|
-
"text/html": html,
|
|
502
|
-
"text/plain": `DataFrame (${size.join(", ")})${columns.length ? `: ${columns.join(", ")}` : ""}`
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
1556
|
function isCanvasLike(obj) {
|
|
506
1557
|
return obj !== null && typeof obj === "object" && "toDataURL" in obj;
|
|
507
1558
|
}
|
|
@@ -519,17 +1570,25 @@ function isHTMLElementLike(obj) {
|
|
|
519
1570
|
return obj !== null && typeof obj === "object" && typeof obj.outerHTML === "string";
|
|
520
1571
|
}
|
|
521
1572
|
function hasDisplaySymbol(obj) {
|
|
522
|
-
return obj !== null && typeof obj === "object" && typeof obj[$
|
|
1573
|
+
return obj !== null && typeof obj === "object" && typeof obj[$display3] === "function";
|
|
523
1574
|
}
|
|
524
1575
|
function isMediaBundle(obj) {
|
|
525
1576
|
return obj !== null && typeof obj === "object" && !Array.isArray(obj);
|
|
526
1577
|
}
|
|
1578
|
+
var RESULT_MIME = "application/vnd.ganymede.result+json";
|
|
1579
|
+
function deepFreeze(value) {
|
|
1580
|
+
if (typeof value === "object" && value !== null) {
|
|
1581
|
+
for (const v of Object.values(value)) deepFreeze(v);
|
|
1582
|
+
Object.freeze(value);
|
|
1583
|
+
}
|
|
1584
|
+
return value;
|
|
1585
|
+
}
|
|
527
1586
|
function createJupyter(host2) {
|
|
528
1587
|
async function format2(obj) {
|
|
529
1588
|
return await formatNow(obj);
|
|
530
1589
|
}
|
|
531
1590
|
function formatNow(obj) {
|
|
532
|
-
if (hasDisplaySymbol(obj)) return obj[$
|
|
1591
|
+
if (hasDisplaySymbol(obj)) return obj[$display3]();
|
|
533
1592
|
if (typeof obj !== "object" || obj === null) return { "text/plain": host2.inspect(obj) };
|
|
534
1593
|
if (isCanvasLike(obj)) {
|
|
535
1594
|
const [prefix, data] = obj.toDataURL().split(",");
|
|
@@ -539,7 +1598,7 @@ function createJupyter(host2) {
|
|
|
539
1598
|
const vega = extractVega(obj);
|
|
540
1599
|
if (vega) return vega;
|
|
541
1600
|
}
|
|
542
|
-
if (
|
|
1601
|
+
if (isTableLike(obj)) return tableBundle(tableSource(obj));
|
|
543
1602
|
if (isJpg(obj)) return { "image/jpeg": host2.base64(obj) };
|
|
544
1603
|
if (isPng(obj)) return { "image/png": host2.base64(obj) };
|
|
545
1604
|
if (isSVGElementLike(obj)) return { "image/svg+xml": obj.outerHTML };
|
|
@@ -549,7 +1608,7 @@ function createJupyter(host2) {
|
|
|
549
1608
|
function taggedTemplate(mediatype) {
|
|
550
1609
|
return (strings, ...values) => {
|
|
551
1610
|
const payload = strings.reduce((acc, string, i) => acc + string + (values[i] !== void 0 ? values[i] : ""), "");
|
|
552
|
-
return { [$
|
|
1611
|
+
return { [$display3]: () => ({ [mediatype]: payload }) };
|
|
553
1612
|
};
|
|
554
1613
|
}
|
|
555
1614
|
function image(obj) {
|
|
@@ -559,12 +1618,16 @@ function createJupyter(host2) {
|
|
|
559
1618
|
} catch {
|
|
560
1619
|
}
|
|
561
1620
|
}
|
|
562
|
-
if (isJpg(obj)) return { [$
|
|
563
|
-
if (isPng(obj)) return { [$
|
|
1621
|
+
if (isJpg(obj)) return { [$display3]: () => ({ "image/jpeg": host2.base64(obj) }) };
|
|
1622
|
+
if (isPng(obj)) return { [$display3]: () => ({ "image/png": host2.base64(obj) }) };
|
|
564
1623
|
throw new TypeError(
|
|
565
1624
|
"Object is not a valid image or a path to an image. `jupyter.image` supports displaying JPG or PNG images."
|
|
566
1625
|
);
|
|
567
1626
|
}
|
|
1627
|
+
function table(value, options = {}) {
|
|
1628
|
+
const bundle = tableBundle(tableSource(value, options), options);
|
|
1629
|
+
return { [$display3]: () => bundle };
|
|
1630
|
+
}
|
|
568
1631
|
function broadcast(msgType, content, { metadata = {}, buffers = [] } = {}) {
|
|
569
1632
|
host2.send(msgType, content, metadata, buffers);
|
|
570
1633
|
}
|
|
@@ -576,12 +1639,12 @@ function createJupyter(host2) {
|
|
|
576
1639
|
transient: options.display_id ? { display_id: options.display_id } : {}
|
|
577
1640
|
});
|
|
578
1641
|
}
|
|
579
|
-
async function broadcastResult(executionCount,
|
|
580
|
-
if (
|
|
1642
|
+
async function broadcastResult(executionCount, result2) {
|
|
1643
|
+
if (result2 === void 0) return;
|
|
581
1644
|
try {
|
|
582
1645
|
broadcast("execute_result", {
|
|
583
1646
|
execution_count: executionCount,
|
|
584
|
-
data: await format2(
|
|
1647
|
+
data: await format2(result2),
|
|
585
1648
|
metadata: {},
|
|
586
1649
|
// nbclient crashes on a null `transient`; Deno learned this too.
|
|
587
1650
|
transient: {}
|
|
@@ -603,6 +1666,45 @@ function createJupyter(host2) {
|
|
|
603
1666
|
const answer2 = host2.input(message, password);
|
|
604
1667
|
return answer2 === "" ? defaultValue : answer2;
|
|
605
1668
|
}
|
|
1669
|
+
let params;
|
|
1670
|
+
function readParams() {
|
|
1671
|
+
if (params !== void 0) return params;
|
|
1672
|
+
const text = host2.params?.();
|
|
1673
|
+
if (text === void 0 || text === "") return params = Object.freeze({});
|
|
1674
|
+
let value;
|
|
1675
|
+
try {
|
|
1676
|
+
value = JSON.parse(text);
|
|
1677
|
+
} catch (err) {
|
|
1678
|
+
throw new TypeError(`GANYMEDE_PARAMS is not JSON: ${err instanceof Error ? err.message : err}`);
|
|
1679
|
+
}
|
|
1680
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1681
|
+
throw new TypeError('GANYMEDE_PARAMS must be a JSON object, such as {"counter": "Fremont"}');
|
|
1682
|
+
}
|
|
1683
|
+
return params = deepFreeze(value);
|
|
1684
|
+
}
|
|
1685
|
+
function result(name, value) {
|
|
1686
|
+
if (typeof name !== "string" || name === "") throw new TypeError("jupyter.result: name must be a non-empty string");
|
|
1687
|
+
let text;
|
|
1688
|
+
try {
|
|
1689
|
+
text = JSON.stringify(value);
|
|
1690
|
+
} catch (err) {
|
|
1691
|
+
throw new TypeError(`jupyter.result: ${name} is not JSON: ${err instanceof Error ? err.message : err}`);
|
|
1692
|
+
}
|
|
1693
|
+
if (text === void 0) throw new TypeError(`jupyter.result: ${name} is not JSON (${typeof value})`);
|
|
1694
|
+
const json = JSON.parse(text);
|
|
1695
|
+
broadcast("display_data", {
|
|
1696
|
+
data: { [RESULT_MIME]: { name, value: json }, "text/plain": `${name} = ${text}` },
|
|
1697
|
+
metadata: {},
|
|
1698
|
+
transient: {}
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
const cellImport = (what) => (specifier) => {
|
|
1702
|
+
const load = globalThis.__cdpk?.import;
|
|
1703
|
+
if (typeof load !== "function") return Promise.reject(new Error(`${what}: no module loader in this runtime`));
|
|
1704
|
+
return load(specifier);
|
|
1705
|
+
};
|
|
1706
|
+
const sql = createSql({ importModule: cellImport("jupyter.sql"), signal: () => current.signal });
|
|
1707
|
+
const chart = createChart({ importModule: cellImport("jupyter.chart"), summarizeSpec: summarizeChart });
|
|
606
1708
|
const comms = createComms((msgType, content, metadata, buffers) => host2.send(msgType, content, metadata, buffers));
|
|
607
1709
|
let cell = new AbortController();
|
|
608
1710
|
let current = cell;
|
|
@@ -616,6 +1718,7 @@ function createJupyter(host2) {
|
|
|
616
1718
|
for (const controller of handlers.keys()) controller.abort(reason);
|
|
617
1719
|
handlers.clear();
|
|
618
1720
|
}
|
|
1721
|
+
const hostApi = createHost(comms.api, () => current.signal);
|
|
619
1722
|
async function commDispatch(msgType, content, metadata, buffers, ownSignal = false) {
|
|
620
1723
|
const controller = ownSignal ? new AbortController() : cell;
|
|
621
1724
|
if (ownSignal) current = controller;
|
|
@@ -635,14 +1738,22 @@ function createJupyter(host2) {
|
|
|
635
1738
|
},
|
|
636
1739
|
broadcast,
|
|
637
1740
|
display,
|
|
1741
|
+
get params() {
|
|
1742
|
+
return readParams();
|
|
1743
|
+
},
|
|
1744
|
+
result,
|
|
1745
|
+
table,
|
|
1746
|
+
sql,
|
|
1747
|
+
chart,
|
|
638
1748
|
format: format2,
|
|
639
1749
|
md: taggedTemplate("text/markdown"),
|
|
640
1750
|
html: taggedTemplate("text/html"),
|
|
641
1751
|
svg: taggedTemplate("image/svg+xml"),
|
|
642
1752
|
image,
|
|
643
|
-
$display,
|
|
1753
|
+
$display: $display3,
|
|
644
1754
|
comm: comms.api,
|
|
645
|
-
|
|
1755
|
+
host: hostApi.host,
|
|
1756
|
+
widget: createWidgetFactory(comms.api, $display3)
|
|
646
1757
|
},
|
|
647
1758
|
broadcastResult,
|
|
648
1759
|
formatNow,
|
|
@@ -990,8 +2101,11 @@ var jupyter = createJupyter({
|
|
|
990
2101
|
},
|
|
991
2102
|
// A synchronous prompt would need SharedArrayBuffer and Atomics.wait, so
|
|
992
2103
|
// a cross-origin isolated page; until then, as with no stdin.
|
|
993
|
-
input: () => null
|
|
2104
|
+
input: () => null,
|
|
2105
|
+
// jupyter.params (#456): the target's `params` option, sent with info.
|
|
2106
|
+
params: () => paramsText
|
|
994
2107
|
});
|
|
2108
|
+
var paramsText;
|
|
995
2109
|
var cdn = DEFAULT_CDN;
|
|
996
2110
|
var hostCalls = /* @__PURE__ */ new Map();
|
|
997
2111
|
var hostListeners = /* @__PURE__ */ new Set();
|
|
@@ -1059,7 +2173,8 @@ async function preload(urls, base) {
|
|
|
1059
2173
|
}
|
|
1060
2174
|
}
|
|
1061
2175
|
var handle = null;
|
|
1062
|
-
function install({ base, cdn: cdnBase }) {
|
|
2176
|
+
function install({ base, cdn: cdnBase, params }) {
|
|
2177
|
+
if (typeof params === "string") paramsText = params;
|
|
1063
2178
|
if (typeof cdnBase === "string") cdn = cdnBase.endsWith("/") ? cdnBase : cdnBase + "/";
|
|
1064
2179
|
const url = new URL("$cdpk$repl.mjs", base).href;
|
|
1065
2180
|
globalThis.__cdpk = {
|