UNPKG

@plutojl/rainbow

Version:

TypeScript/JavaScript API for programmatically interacting with Pluto notebooks

1,632 lines (1,458 loc) 227 kB
import { createContext, useState, useEffect, useMemo, createElement, useContext } from 'react'; var EOL = {}, EOF = {}, QUOTE = 34, NEWLINE = 10, RETURN = 13; function objectConverter(columns) { return new Function("d", "return {" + columns.map(function(name, i) { return JSON.stringify(name) + ": d[" + i + "] || \"\""; }).join(",") + "}"); } function customConverter(columns, f) { var object = objectConverter(columns); return function(row, i) { return f(object(row), i, columns); }; } // Compute unique columns in order of discovery. function inferColumns(rows) { var columnSet = Object.create(null), columns = []; rows.forEach(function(row) { for (var column in row) { if (!(column in columnSet)) { columns.push(columnSet[column] = column); } } }); return columns; } function pad(value, width) { var s = value + "", length = s.length; return length < width ? new Array(width - length + 1).join(0) + s : s; } function formatYear(year) { return year < 0 ? "-" + pad(-year, 6) : year > 9999 ? "+" + pad(year, 6) : pad(year, 4); } function formatDate(date) { var hours = date.getUTCHours(), minutes = date.getUTCMinutes(), seconds = date.getUTCSeconds(), milliseconds = date.getUTCMilliseconds(); return isNaN(date) ? "Invalid Date" : formatYear(date.getUTCFullYear()) + "-" + pad(date.getUTCMonth() + 1, 2) + "-" + pad(date.getUTCDate(), 2) + (milliseconds ? "T" + pad(hours, 2) + ":" + pad(minutes, 2) + ":" + pad(seconds, 2) + "." + pad(milliseconds, 3) + "Z" : seconds ? "T" + pad(hours, 2) + ":" + pad(minutes, 2) + ":" + pad(seconds, 2) + "Z" : minutes || hours ? "T" + pad(hours, 2) + ":" + pad(minutes, 2) + "Z" : ""); } function dsv$1(delimiter) { var reFormat = new RegExp("[\"" + delimiter + "\n\r]"), DELIMITER = delimiter.charCodeAt(0); function parse(text, f) { var convert, columns, rows = parseRows(text, function(row, i) { if (convert) return convert(row, i - 1); columns = row, convert = f ? customConverter(row, f) : objectConverter(row); }); rows.columns = columns || []; return rows; } function parseRows(text, f) { var rows = [], // output rows N = text.length, I = 0, // current character index n = 0, // current line number t, // current token eof = N <= 0, // current token followed by EOF? eol = false; // current token followed by EOL? // Strip the trailing newline. if (text.charCodeAt(N - 1) === NEWLINE) --N; if (text.charCodeAt(N - 1) === RETURN) --N; function token() { if (eof) return EOF; if (eol) return eol = false, EOL; // Unescape quotes. var i, j = I, c; if (text.charCodeAt(j) === QUOTE) { while (I++ < N && text.charCodeAt(I) !== QUOTE || text.charCodeAt(++I) === QUOTE); if ((i = I) >= N) eof = true; else if ((c = text.charCodeAt(I++)) === NEWLINE) eol = true; else if (c === RETURN) { eol = true; if (text.charCodeAt(I) === NEWLINE) ++I; } return text.slice(j + 1, i - 1).replace(/""/g, "\""); } // Find next delimiter or newline. while (I < N) { if ((c = text.charCodeAt(i = I++)) === NEWLINE) eol = true; else if (c === RETURN) { eol = true; if (text.charCodeAt(I) === NEWLINE) ++I; } else if (c !== DELIMITER) continue; return text.slice(j, i); } // Return last token before EOF. return eof = true, text.slice(j, N); } while ((t = token()) !== EOF) { var row = []; while (t !== EOL && t !== EOF) row.push(t), t = token(); if (f && (row = f(row, n++)) == null) continue; rows.push(row); } return rows; } function preformatBody(rows, columns) { return rows.map(function(row) { return columns.map(function(column) { return formatValue(row[column]); }).join(delimiter); }); } function format(rows, columns) { if (columns == null) columns = inferColumns(rows); return [columns.map(formatValue).join(delimiter)].concat(preformatBody(rows, columns)).join("\n"); } function formatBody(rows, columns) { if (columns == null) columns = inferColumns(rows); return preformatBody(rows, columns).join("\n"); } function formatRows(rows) { return rows.map(formatRow).join("\n"); } function formatRow(row) { return row.map(formatValue).join(delimiter); } function formatValue(value) { return value == null ? "" : value instanceof Date ? formatDate(value) : reFormat.test(value += "") ? "\"" + value.replace(/"/g, "\"\"") + "\"" : value; } return { parse: parse, parseRows: parseRows, format: format, formatBody: formatBody, formatRows: formatRows, formatRow: formatRow, formatValue: formatValue }; } var csv = dsv$1(","); var csvParse = csv.parse; var csvParseRows = csv.parseRows; var tsv = dsv$1("\t"); var tsvParse = tsv.parse; var tsvParseRows = tsv.parseRows; function autoType(object) { for (var key in object) { var value = object[key].trim(), number, m; if (!value) value = null; else if (value === "true") value = true; else if (value === "false") value = false; else if (value === "NaN") value = NaN; else if (!isNaN(number = +value)) value = number; else if (m = value.match(/^([-+]\d{2})?\d{4}(-\d{2}(-\d{2})?)?(T\d{2}:\d{2}(:\d{2}(\.\d{3})?)?(Z|[-+]\d{2}:\d{2})?)?$/)) { if (fixtz && !!m[4] && !m[7]) value = value.replace(/-/g, "/").replace(/T/, " "); value = new Date(value); } else continue; object[key] = value; } return object; } // https://github.com/d3/d3-dsv/issues/45 const fixtz = new Date("2019-01-01T00:00").getHours() || new Date("2019-07-01T00:00").getHours(); function dependency(name, version, main) { return { resolve(path = main) { return `${name}@${version}/${path}`; } }; } const d3 = dependency("d3", "7.6.1", "dist/d3.min.js"); const inputs = dependency("@observablehq/inputs", "0.10.4", "dist/inputs.min.js"); const plot = dependency("@observablehq/plot", "0.6.0", "dist/plot.umd.min.js"); const graphviz = dependency("@observablehq/graphviz", "0.2.1", "dist/graphviz.min.js"); const highlight = dependency("@observablehq/highlight.js", "2.0.0", "highlight.min.js"); const katex = dependency("@observablehq/katex", "0.11.1", "dist/katex.min.js"); const lodash = dependency("lodash", "4.17.21", "lodash.min.js"); const htl = dependency("htl", "0.3.1", "dist/htl.min.js"); const jszip = dependency("jszip", "3.10.0", "dist/jszip.min.js"); const marked = dependency("marked", "0.3.12", "marked.min.js"); const sql = dependency("sql.js", "1.7.0", "dist/sql-wasm.js"); const vega = dependency("vega", "5.22.1", "build/vega.min.js"); const vegalite = dependency("vega-lite", "5.5.0", "build/vega-lite.min.js"); const vegaliteApi = dependency("vega-lite-api", "5.0.0", "build/vega-lite-api.min.js"); const arrow = dependency("apache-arrow", "4.0.1", "Arrow.es2015.min.js"); const arquero = dependency("arquero", "4.8.8", "dist/arquero.min.js"); const topojson = dependency("topojson-client", "3.1.0", "dist/topojson-client.min.js"); const exceljs = dependency("exceljs", "4.3.0", "dist/exceljs.min.js"); const mermaid$1 = dependency("mermaid", "9.1.6", "dist/mermaid.min.js"); const leaflet$1 = dependency("leaflet", "1.8.0", "dist/leaflet.js"); const metas = new Map; const queue$1 = []; const map$1 = queue$1.map; const some = queue$1.some; const hasOwnProperty = queue$1.hasOwnProperty; const identifierRe = /^((?:@[^/@]+\/)?[^/@]+)(?:@([^/]+))?(?:\/(.*))?$/; const versionRe = /^\d+\.\d+\.\d+(-[\w-.+]+)?$/; const extensionRe = /(?:\.[^/]*|\/)$/; class RequireError extends Error { constructor(message) { super(message); } } RequireError.prototype.name = RequireError.name; function parseIdentifier(identifier) { const match = identifierRe.exec(identifier); return match && { name: match[1], version: match[2], path: match[3] }; } function resolveFrom(origin = "https://cdn.jsdelivr.net/npm/", mains = ["unpkg", "jsdelivr", "browser", "main"]) { if (!/\/$/.test(origin)) throw new Error("origin lacks trailing slash"); function main(meta) { for (const key of mains) { let value = meta[key]; if (typeof value === "string") { if (value.startsWith("./")) value = value.slice(2); return extensionRe.test(value) ? value : `${value}.js`; } } } function resolveMeta(target) { const url = `${origin}${target.name}${target.version ? `@${target.version}` : ""}/package.json`; let meta = metas.get(url); if (!meta) metas.set(url, meta = fetch(url).then(response => { if (!response.ok) throw new RequireError("unable to load package.json"); if (response.redirected && !metas.has(response.url)) metas.set(response.url, meta); return response.json(); })); return meta; } return async function resolve(name, base) { if (name.startsWith(origin)) name = name.substring(origin.length); if (/^(\w+:)|\/\//i.test(name)) return name; if (/^[.]{0,2}\//i.test(name)) return new URL(name, base == null ? location : base).href; if (!name.length || /^[\s._]/.test(name) || /\s$/.test(name)) throw new RequireError("illegal name"); const target = parseIdentifier(name); if (!target) return `${origin}${name}`; if (!target.version && base != null && base.startsWith(origin)) { const meta = await resolveMeta(parseIdentifier(base.substring(origin.length))); target.version = meta.dependencies && meta.dependencies[target.name] || meta.peerDependencies && meta.peerDependencies[target.name]; } if (target.path && !extensionRe.test(target.path)) target.path += ".js"; if (target.path && target.version && versionRe.test(target.version)) return `${origin}${target.name}@${target.version}/${target.path}`; const meta = await resolveMeta(target); return `${origin}${meta.name}@${meta.version}/${target.path || main(meta) || "index.js"}`; }; } var require$1 = requireFrom(resolveFrom()); function requireFrom(resolver) { const cache = new Map; const requireBase = requireRelative(null); function requireAbsolute(url) { if (typeof url !== "string") return url; let module = cache.get(url); if (!module) cache.set(url, module = new Promise((resolve, reject) => { const script = document.createElement("script"); script.onload = () => { try { resolve(queue$1.pop()(requireRelative(url))); } catch (error) { reject(new RequireError("invalid module")); } script.remove(); }; script.onerror = () => { reject(new RequireError("unable to load module")); script.remove(); }; script.async = true; script.src = url; window.define = define; document.head.appendChild(script); })); return module; } function requireRelative(base) { return name => Promise.resolve(resolver(name, base)).then(requireAbsolute); } function requireAlias(aliases) { return requireFrom((name, base) => { if (name in aliases) { name = aliases[name], base = null; if (typeof name !== "string") return name; } return resolver(name, base); }); } function require(name) { return arguments.length > 1 ? Promise.all(map$1.call(arguments, requireBase)).then(merge) : requireBase(name); } require.alias = requireAlias; require.resolve = resolver; return require; } function merge(modules) { const o = {}; for (const m of modules) { for (const k in m) { if (hasOwnProperty.call(m, k)) { if (m[k] == null) Object.defineProperty(o, k, {get: getter(m, k)}); else o[k] = m[k]; } } } return o; } function getter(object, name) { return () => object[name]; } function isbuiltin(name) { name = name + ""; return name === "exports" || name === "module"; } function define(name, dependencies, factory) { const n = arguments.length; if (n < 2) factory = name, dependencies = []; else if (n < 3) factory = dependencies, dependencies = typeof name === "string" ? [] : name; queue$1.push(some.call(dependencies, isbuiltin) ? require => { const exports = {}; const module = {exports}; return Promise.all(map$1.call(dependencies, name => { name = name + ""; return name === "exports" ? exports : name === "module" ? module : require(name); })).then(dependencies => { factory.apply(null, dependencies); return module.exports; }); } : require => { return Promise.all(map$1.call(dependencies, require)).then(dependencies => { return typeof factory === "function" ? factory.apply(null, dependencies) : factory; }); }); } define.amd = {}; let requireDefault = require$1; function setDefaultRequire(require) { requireDefault = require; } function requirer(resolve) { return resolve == null ? requireDefault : requireFrom(resolve); } async function sqlite(require) { const [init, dist] = await Promise.all([require(sql.resolve()), require.resolve(sql.resolve("dist/"))]); return init({locateFile: file => `${dist}${file}`}); } class SQLiteDatabaseClient { constructor(db) { Object.defineProperties(this, { _db: {value: db} }); } static async open(source) { const [SQL, buffer] = await Promise.all([sqlite(requireDefault), Promise.resolve(source).then(load)]); return new SQLiteDatabaseClient(new SQL.Database(buffer)); } async query(query, params) { return await exec(this._db, query, params); } async queryRow(query, params) { return (await this.query(query, params))[0] || null; } async explain(query, params) { const rows = await this.query(`EXPLAIN QUERY PLAN ${query}`, params); return element$1("pre", {className: "observablehq--inspect"}, [ text$2(rows.map(row => row.detail).join("\n")) ]); } async describeTables({schema} = {}) { return this.query(`SELECT NULLIF(schema, 'main') AS schema, name FROM pragma_table_list() WHERE type = 'table'${schema == null ? "" : ` AND schema = ?`} AND name NOT LIKE 'sqlite_%'`, schema == null ? [] : [schema]); } async describeColumns({schema, table} = {}) { if (table == null) throw new Error(`missing table`); const rows = await this.query(`SELECT name, type, "notnull" FROM pragma_table_info(?${schema == null ? "" : `, ?`}) ORDER BY cid`, schema == null ? [table] : [table, schema]); if (!rows.length) throw new Error(`table not found: ${table}`); return rows.map(({name, type, notnull}) => ({name, type: sqliteType(type), databaseType: type, nullable: !notnull})); } async describe(object) { const rows = await (object === undefined ? this.query(`SELECT name FROM sqlite_master WHERE type = 'table'`) : this.query(`SELECT * FROM pragma_table_info(?)`, [object])); if (!rows.length) throw new Error("Not found"); const {columns} = rows; return element$1("table", {value: rows}, [ element$1("thead", [element$1("tr", columns.map(c => element$1("th", [text$2(c)])))]), element$1("tbody", rows.map(r => element$1("tr", columns.map(c => element$1("td", [text$2(r[c])]))))) ]); } async sql() { return this.query(...this.queryTag.apply(this, arguments)); } queryTag(strings, ...params) { return [strings.join("?"), params]; } } Object.defineProperty(SQLiteDatabaseClient.prototype, "dialect", { value: "sqlite" }); // https://www.sqlite.org/datatype3.html function sqliteType(type) { switch (type) { case "NULL": return "null"; case "INT": case "INTEGER": case "TINYINT": case "SMALLINT": case "MEDIUMINT": case "BIGINT": case "UNSIGNED BIG INT": case "INT2": case "INT8": return "integer"; case "TEXT": case "CLOB": return "string"; case "REAL": case "DOUBLE": case "DOUBLE PRECISION": case "FLOAT": case "NUMERIC": return "number"; case "BLOB": return "buffer"; case "DATE": case "DATETIME": return "string"; // TODO convert strings to Date instances in sql.js default: return /^(?:(?:(?:VARYING|NATIVE) )?CHARACTER|(?:N|VAR|NVAR)CHAR)\(/.test(type) ? "string" : /^(?:DECIMAL|NUMERIC)\(/.test(type) ? "number" : "other"; } } function load(source) { return typeof source === "string" ? fetch(source).then(load) : source instanceof Response || source instanceof Blob ? source.arrayBuffer().then(load) : source instanceof ArrayBuffer ? new Uint8Array(source) : source; } async function exec(db, query, params) { const [result] = await db.exec(query, params); if (!result) return []; const {columns, values} = result; const rows = values.map(row => Object.fromEntries(row.map((value, i) => [columns[i], value]))); rows.columns = columns; return rows; } function element$1(name, props, children) { if (arguments.length === 2) children = props, props = undefined; const element = document.createElement(name); if (props !== undefined) for (const p in props) element[p] = props[p]; if (children !== undefined) for (const c of children) element.appendChild(c); return element; } function text$2(value) { return document.createTextNode(value); } class Workbook { constructor(workbook) { Object.defineProperties(this, { _: {value: workbook}, sheetNames: { value: workbook.worksheets.map((s) => s.name), enumerable: true } }); } sheet(name, options) { const sname = typeof name === "number" ? this.sheetNames[name] : this.sheetNames.includes((name += "")) ? name : null; if (sname == null) throw new Error(`Sheet not found: ${name}`); const sheet = this._.getWorksheet(sname); return extract(sheet, options); } } function extract(sheet, {range, headers} = {}) { let [[c0, r0], [c1, r1]] = parseRange(range, sheet); const headerRow = headers ? sheet._rows[r0++] : null; let names = new Set(["#"]); for (let n = c0; n <= c1; n++) { const value = headerRow ? valueOf(headerRow.findCell(n + 1)) : null; let name = (value && value + "") || toColumn(n); while (names.has(name)) name += "_"; names.add(name); } names = new Array(c0).concat(Array.from(names)); const output = new Array(r1 - r0 + 1); for (let r = r0; r <= r1; r++) { const row = (output[r - r0] = Object.create(null, {"#": {value: r + 1}})); const _row = sheet.getRow(r + 1); if (_row.hasValues) for (let c = c0; c <= c1; c++) { const value = valueOf(_row.findCell(c + 1)); if (value != null) row[names[c + 1]] = value; } } output.columns = names.filter(() => true); // Filter sparse columns return output; } function valueOf(cell) { if (!cell) return; const {value} = cell; if (value && typeof value === "object" && !(value instanceof Date)) { if (value.formula || value.sharedFormula) { return value.result && value.result.error ? NaN : value.result; } if (value.richText) { return richText(value); } if (value.text) { let {text} = value; if (text.richText) text = richText(text); return value.hyperlink && value.hyperlink !== text ? `${value.hyperlink} ${text}` : text; } return value; } return value; } function richText(value) { return value.richText.map((d) => d.text).join(""); } function parseRange(specifier = ":", {columnCount, rowCount}) { specifier += ""; if (!specifier.match(/^[A-Z]*\d*:[A-Z]*\d*$/)) throw new Error("Malformed range specifier"); const [[c0 = 0, r0 = 0], [c1 = columnCount - 1, r1 = rowCount - 1]] = specifier.split(":").map(fromCellReference); return [ [c0, r0], [c1, r1] ]; } // Returns the default column name for a zero-based column index. // For example: 0 -> "A", 1 -> "B", 25 -> "Z", 26 -> "AA", 27 -> "AB". function toColumn(c) { let sc = ""; c++; do { sc = String.fromCharCode(64 + (c % 26 || 26)) + sc; } while ((c = Math.floor((c - 1) / 26))); return sc; } // Returns the zero-based indexes from a cell reference. // For example: "A1" -> [0, 0], "B2" -> [1, 1], "AA10" -> [26, 9]. function fromCellReference(s) { const [, sc, sr] = s.match(/^([A-Z]*)(\d*)$/); let c = 0; if (sc) for (let i = 0; i < sc.length; i++) c += Math.pow(26, sc.length - i - 1) * (sc.charCodeAt(i) - 64); return [c ? c - 1 : undefined, sr ? +sr - 1 : undefined]; } async function remote_fetch(file) { const response = await fetch(await file.url()); if (!response.ok) throw new Error(`Unable to load file: ${file.name}`); return response; } async function dsv(file, delimiter, {array = false, typed = false} = {}) { const text = await file.text(); return (delimiter === "\t" ? (array ? tsvParseRows : tsvParse) : (array ? csvParseRows : csvParse))(text, typed && autoType); } class AbstractFile { constructor(name, mimeType) { Object.defineProperty(this, "name", {value: name, enumerable: true}); if (mimeType !== undefined) Object.defineProperty(this, "mimeType", {value: mimeType + "", enumerable: true}); } async blob() { return (await remote_fetch(this)).blob(); } async arrayBuffer() { return (await remote_fetch(this)).arrayBuffer(); } async text() { return (await remote_fetch(this)).text(); } async json() { return (await remote_fetch(this)).json(); } async stream() { return (await remote_fetch(this)).body; } async csv(options) { return dsv(this, ",", options); } async tsv(options) { return dsv(this, "\t", options); } async image(props) { const url = await this.url(); return new Promise((resolve, reject) => { const i = new Image(); if (new URL(url, document.baseURI).origin !== new URL(location).origin) { i.crossOrigin = "anonymous"; } Object.assign(i, props); i.onload = () => resolve(i); i.onerror = () => reject(new Error(`Unable to load file: ${this.name}`)); i.src = url; }); } async arrow() { const [Arrow, response] = await Promise.all([requireDefault(arrow.resolve()), remote_fetch(this)]); return Arrow.Table.from(response); } async sqlite() { return SQLiteDatabaseClient.open(remote_fetch(this)); } async zip() { const [JSZip, buffer] = await Promise.all([requireDefault(jszip.resolve()), this.arrayBuffer()]); return new ZipArchive(await JSZip.loadAsync(buffer)); } async xml(mimeType = "application/xml") { return (new DOMParser).parseFromString(await this.text(), mimeType); } async html() { return this.xml("text/html"); } async xlsx() { const [ExcelJS, buffer] = await Promise.all([requireDefault(exceljs.resolve()), this.arrayBuffer()]); return new Workbook(await new ExcelJS.Workbook().xlsx.load(buffer)); } } class FileAttachment extends AbstractFile { constructor(url, name, mimeType) { super(name, mimeType); Object.defineProperty(this, "_url", {value: url}); } async url() { return (await this._url) + ""; } } function NoFileAttachments(name) { throw new Error(`File not found: ${name}`); } class ZipArchive { constructor(archive) { Object.defineProperty(this, "_", {value: archive}); this.filenames = Object.keys(archive.files).filter(name => !archive.files[name].dir); } file(path) { const object = this._.file(path += ""); if (!object || object.dir) throw new Error(`file not found: ${path}`); return new ZipArchiveEntry(object); } } class ZipArchiveEntry extends AbstractFile { constructor(object) { super(object.name); Object.defineProperty(this, "_", {value: object}); Object.defineProperty(this, "_url", {writable: true}); } async url() { return this._url || (this._url = this.blob().then(URL.createObjectURL)); } async blob() { return this._.async("blob"); } async arrayBuffer() { return this._.async("arraybuffer"); } async text() { return this._.async("text"); } async json() { return JSON.parse(await this.text()); } } function canvas(width, height) { var canvas = document.createElement("canvas"); canvas.width = width; canvas.height = height; return canvas; } function context2d(width, height, dpi) { if (dpi == null) dpi = devicePixelRatio; var canvas = document.createElement("canvas"); canvas.width = width * dpi; canvas.height = height * dpi; canvas.style.width = width + "px"; var context = canvas.getContext("2d"); context.scale(dpi, dpi); return context; } function download(value, name = "untitled", label = "Save") { const a = document.createElement("a"); const b = a.appendChild(document.createElement("button")); b.textContent = label; a.download = name; async function reset() { await new Promise(requestAnimationFrame); URL.revokeObjectURL(a.href); a.removeAttribute("href"); b.textContent = label; b.disabled = false; } a.onclick = async event => { b.disabled = true; if (a.href) return reset(); // Already saved. b.textContent = "Saving…"; try { const object = await (typeof value === "function" ? value() : value); b.textContent = "Download"; a.href = URL.createObjectURL(object); // eslint-disable-line require-atomic-updates } catch (ignore) { b.textContent = label; } if (event.eventPhase) return reset(); // Already downloaded. b.disabled = false; }; return a; } var namespaces = { math: "http://www.w3.org/1998/Math/MathML", svg: "http://www.w3.org/2000/svg", xhtml: "http://www.w3.org/1999/xhtml", xlink: "http://www.w3.org/1999/xlink", xml: "http://www.w3.org/XML/1998/namespace", xmlns: "http://www.w3.org/2000/xmlns/" }; function element(name, attributes) { var prefix = name += "", i = prefix.indexOf(":"), value; if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns") name = name.slice(i + 1); var element = namespaces.hasOwnProperty(prefix) // eslint-disable-line no-prototype-builtins ? document.createElementNS(namespaces[prefix], name) : document.createElement(name); if (attributes) for (var key in attributes) { prefix = key, i = prefix.indexOf(":"), value = attributes[key]; if (i >= 0 && (prefix = key.slice(0, i)) !== "xmlns") key = key.slice(i + 1); if (namespaces.hasOwnProperty(prefix)) element.setAttributeNS(namespaces[prefix], key, value); // eslint-disable-line no-prototype-builtins else element.setAttribute(key, value); } return element; } function input$1(type) { var input = document.createElement("input"); if (type != null) input.type = type; return input; } function range$1(min, max, step) { if (arguments.length === 1) max = min, min = null; var input = document.createElement("input"); input.min = min = min == null ? 0 : +min; input.max = max = max == null ? 1 : +max; input.step = step == null ? "any" : step = +step; input.type = "range"; return input; } function select(values) { var select = document.createElement("select"); Array.prototype.forEach.call(values, function(value) { var option = document.createElement("option"); option.value = option.textContent = value; select.appendChild(option); }); return select; } function svg$1(width, height) { var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.setAttribute("viewBox", [0, 0, width, height]); svg.setAttribute("width", width); svg.setAttribute("height", height); return svg; } function text$1(value) { return document.createTextNode(value); } var count = 0; function uid(name) { return new Id("O-" + (name == null ? "" : name + "-") + ++count); } function Id(id) { this.id = id; this.href = new URL(`#${id}`, location) + ""; } Id.prototype.toString = function() { return "url(" + this.href + ")"; }; var DOM = { canvas: canvas, context2d: context2d, download: download, element: element, input: input$1, range: range$1, select: select, svg: svg$1, text: text$1, uid: uid }; function buffer(file) { return new Promise(function(resolve, reject) { var reader = new FileReader; reader.onload = function() { resolve(reader.result); }; reader.onerror = reject; reader.readAsArrayBuffer(file); }); } function text(file) { return new Promise(function(resolve, reject) { var reader = new FileReader; reader.onload = function() { resolve(reader.result); }; reader.onerror = reject; reader.readAsText(file); }); } function url(file) { return new Promise(function(resolve, reject) { var reader = new FileReader; reader.onload = function() { resolve(reader.result); }; reader.onerror = reject; reader.readAsDataURL(file); }); } var Files = { buffer: buffer, text: text, url: url }; function that() { return this; } function disposable(value, dispose) { let done = false; if (typeof dispose !== "function") { throw new Error("dispose is not a function"); } return { [Symbol.iterator]: that, next: () => done ? {done: true} : (done = true, {done: false, value}), return: () => (done = true, dispose(value), {done: true}), throw: () => ({done: done = true}) }; } function* filter(iterator, test) { var result, index = -1; while (!(result = iterator.next()).done) { if (test(result.value, ++index)) { yield result.value; } } } function observe(initialize) { let stale = false; let value; let resolve; const dispose = initialize(change); if (dispose != null && typeof dispose !== "function") { throw new Error(typeof dispose.then === "function" ? "async initializers are not supported" : "initializer returned something, but not a dispose function"); } function change(x) { if (resolve) resolve(x), resolve = null; else stale = true; return value = x; } function next() { return {done: false, value: stale ? (stale = false, Promise.resolve(value)) : new Promise(_ => (resolve = _))}; } return { [Symbol.iterator]: that, throw: () => ({done: true}), return: () => (dispose != null && dispose(), {done: true}), next }; } function input(input) { return observe(function(change) { var event = eventof(input), value = valueof(input); function inputted() { change(valueof(input)); } input.addEventListener(event, inputted); if (value !== undefined) change(value); return function() { input.removeEventListener(event, inputted); }; }); } function valueof(input) { switch (input.type) { case "range": case "number": return input.valueAsNumber; case "date": return input.valueAsDate; case "checkbox": return input.checked; case "file": return input.multiple ? input.files : input.files[0]; case "select-multiple": return Array.from(input.selectedOptions, o => o.value); default: return input.value; } } function eventof(input) { switch (input.type) { case "button": case "submit": case "checkbox": return "click"; case "file": return "change"; default: return "input"; } } function* map(iterator, transform) { var result, index = -1; while (!(result = iterator.next()).done) { yield transform(result.value, ++index); } } function queue(initialize) { let resolve; const queue = []; const dispose = initialize(push); if (dispose != null && typeof dispose !== "function") { throw new Error(typeof dispose.then === "function" ? "async initializers are not supported" : "initializer returned something, but not a dispose function"); } function push(x) { queue.push(x); if (resolve) resolve(queue.shift()), resolve = null; return x; } function next() { return {done: false, value: queue.length ? Promise.resolve(queue.shift()) : new Promise(_ => (resolve = _))}; } return { [Symbol.iterator]: that, throw: () => ({done: true}), return: () => (dispose != null && dispose(), {done: true}), next }; } function* range(start, stop, step) { start = +start; stop = +stop; step = (n = arguments.length) < 2 ? (stop = start, start = 0, 1) : n < 3 ? 1 : +step; var i = -1, n = Math.max(0, Math.ceil((stop - start) / step)) | 0; while (++i < n) { yield start + i * step; } } function valueAt(iterator, i) { if (!isFinite(i = +i) || i < 0 || i !== i | 0) return; var result, index = -1; while (!(result = iterator.next()).done) { if (++index === i) { return result.value; } } } function worker(source) { const url = URL.createObjectURL(new Blob([source], {type: "text/javascript"})); const worker = new Worker(url); return disposable(worker, () => { worker.terminate(); URL.revokeObjectURL(url); }); } var Generators = { disposable: disposable, filter: filter, input: input, map: map, observe: observe, queue: queue, range: range, valueAt: valueAt, worker: worker }; function template(render, wrapper) { return function(strings) { var string = strings[0], parts = [], part, root = null, node, nodes, walker, i, n, j, m, k = -1; // Concatenate the text using comments as placeholders. for (i = 1, n = arguments.length; i < n; ++i) { part = arguments[i]; if (part instanceof Node) { parts[++k] = part; string += "<!--o:" + k + "-->"; } else if (Array.isArray(part)) { for (j = 0, m = part.length; j < m; ++j) { node = part[j]; if (node instanceof Node) { if (root === null) { parts[++k] = root = document.createDocumentFragment(); string += "<!--o:" + k + "-->"; } root.appendChild(node); } else { root = null; string += node; } } root = null; } else { string += part; } string += strings[i]; } // Render the text. root = render(string); // Walk the rendered content to replace comment placeholders. if (++k > 0) { nodes = new Array(k); walker = document.createTreeWalker(root, NodeFilter.SHOW_COMMENT, null, false); while (walker.nextNode()) { node = walker.currentNode; if (/^o:/.test(node.nodeValue)) { nodes[+node.nodeValue.slice(2)] = node; } } for (i = 0; i < k; ++i) { if (node = nodes[i]) { node.parentNode.replaceChild(parts[i], node); } } } // Is the rendered content // … a parent of a single child? Detach and return the child. // … a document fragment? Replace the fragment with an element. // … some other node? Return it. return root.childNodes.length === 1 ? root.removeChild(root.firstChild) : root.nodeType === 11 ? ((node = wrapper()).appendChild(root), node) : root; }; } var html = template(function(string) { var template = document.createElement("template"); template.innerHTML = string.trim(); return document.importNode(template.content, true); }, function() { return document.createElement("span"); }); async function leaflet(require) { const L = await require(leaflet$1.resolve()); if (!L._style) { const link = document.createElement("link"); link.rel = "stylesheet"; link.href = await require.resolve(leaflet$1.resolve("dist/leaflet.css")); L._style = document.head.appendChild(link); } return L; } function md(require) { return require(marked.resolve()).then(function(marked) { return template( function(string) { var root = document.createElement("div"); root.innerHTML = marked(string, {langPrefix: ""}).trim(); var code = root.querySelectorAll("pre code[class]"); if (code.length > 0) { require(highlight.resolve()).then(function(hl) { code.forEach(function(block) { function done() { hl.highlightBlock(block); block.parentNode.classList.add("observablehq--md-pre"); } if (hl.getLanguage(block.className)) { done(); } else { require(highlight.resolve("async-languages/index.js")) .then(index => { if (index.has(block.className)) { return require(highlight.resolve("async-languages/" + index.get(block.className))).then(language => { hl.registerLanguage(block.className, language); }); } }) .then(done, done); } }); }); } return root; }, function() { return document.createElement("div"); } ); }); } async function mermaid(require) { const mer = await require(mermaid$1.resolve()); mer.initialize({securityLevel: "loose", theme: "neutral"}); return function mermaid() { const root = document.createElement("div"); root.innerHTML = mer.render(uid().id, String.raw.apply(String, arguments)); return root.removeChild(root.firstChild); }; } function Mutable(value) { let change; Object.defineProperties(this, { generator: {value: observe(_ => void (change = _))}, value: {get: () => value, set: x => change(value = x)} // eslint-disable-line no-setter-return }); if (value !== undefined) change(value); } function* now$1() { while (true) { yield Date.now(); } } function delay(duration, value) { return new Promise(function(resolve) { setTimeout(function() { resolve(value); }, duration); }); } var timeouts = new Map; function timeout(now, time) { var t = new Promise(function(resolve) { timeouts.delete(time); var delay = time - now; if (!(delay > 0)) throw new Error("invalid time"); if (delay > 0x7fffffff) throw new Error("too long to wait"); setTimeout(resolve, delay); }); timeouts.set(time, t); return t; } function when(time, value) { var now; return (now = timeouts.get(time = +time)) ? now.then(() => value) : (now = Date.now()) >= time ? Promise.resolve(value) : timeout(now, time).then(() => value); } function tick(duration, value) { return when(Math.ceil((Date.now() + 1) / duration) * duration, value); } var Promises$1 = { delay: delay, tick: tick, when: when }; function resolve(name, base) { if (/^(\w+:)|\/\//i.test(name)) return name; if (/^[.]{0,2}\//i.test(name)) return new URL(name, base == null ? location : base).href; if (!name.length || /^[\s._]/.test(name) || /\s$/.test(name)) throw new Error("illegal name"); return "https://unpkg.com/" + name; } var svg = template(function(string) { var root = document.createElementNS("http://www.w3.org/2000/svg", "g"); root.innerHTML = string.trim(); return root; }, function() { return document.createElementNS("http://www.w3.org/2000/svg", "g"); }); var raw = String.raw; function style(href) { return new Promise(function(resolve, reject) { var link = document.createElement("link"); link.rel = "stylesheet"; link.href = href; link.onerror = reject; link.onload = resolve; document.head.appendChild(link); }); } function tex(require) { return Promise.all([ require(katex.resolve()), require.resolve(katex.resolve("dist/katex.min.css")).then(style) ]).then(function(values) { var katex = values[0], tex = renderer(); function renderer(options) { return function() { var root = document.createElement("div"); katex.render(raw.apply(String, arguments), root, options); return root.removeChild(root.firstChild); }; } tex.options = renderer; tex.block = renderer({displayMode: true}); return tex; }); } async function vl(require) { const [v, vl, api] = await Promise.all([vega, vegalite, vegaliteApi].map(d => require(d.resolve()))); return api.register(v, vl); } function width() { return observe(function(change) { var width = change(document.body.clientWidth); function resized() { var w = document.body.clientWidth; if (w !== width) change(width = w); } window.addEventListener("resize", resized); return function() { window.removeEventListener("resize", resized); }; }); } // These are copied from d3-array; TODO import once this package adopts type: module. function descending(a, b) { return a == null || b == null ? NaN : b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN; } function ascending(a, b) { return a == null || b == null ? NaN : a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; } function reverse(values) { if (typeof values[Symbol.iterator] !== "function") throw new TypeError("values is not iterable"); return Array.from(values).reverse(); } const nChecks = 20; // number of values to check in each array // We support two levels of DatabaseClient. The simplest DatabaseClient // implements only the client.sql tagged template literal. More advanced // DatabaseClients implement client.query and client.queryStream, which support // streaming and abort, and the client.queryTag tagged template literal is used // to translate the contents of a SQL cell or Table cell into the appropriate // arguments for calling client.query or client.queryStream. For table cells, we // additionally require client.describeColumns. The client.describeTables method // is optional. function isDatabaseClient(value, mode) { return ( value && (typeof value.sql === "function" || (typeof value.queryTag === "function" && (typeof value.query === "function" || typeof value.queryStream === "function"))) && (mode !== "table") && value !== __query // don’t match our internal helper ); } // Returns true if the value is a typed array (for a single-column table), or if // it’s an array. In the latter case, the elements of the array must be // consistently typed: either plain objects or primitives or dates. function isDataArray(value) { return ( (Array.isArray(value) && (isQueryResultSetSchema(value.schema) || isQueryResultSetColumns(value.columns) || arrayContainsObjects(value) || arrayContainsPrimitives(value) || arrayContainsDates(value))) || isTypedArray(value) ); } // Given an array, checks that the given value is an array that does not contain // any primitive values (at least for the first few values that we check), and // that the first object contains enumerable keys (see computeSchema for how we // infer the columns). We assume that the contents of the table are homogenous, // but we don’t currently enforce this. // https://observablehq.com/@observablehq/database-client-specification#§1 function arrayContainsObjects(value) { const n = Math.min(nChecks, value.length); for (let i = 0; i < n; ++i) { const v = value[i]; if (v === null || typeof v !== "object") return false; } return n > 0 && objectHasEnumerableKeys(value[0]); } // Using a for-in loop here means that we can abort after finding at least one // enumerable key (whereas Object.keys would require materializing the array of // all keys, which would be considerably slower if the value has many keys!). // This function assumes that value is an object; see arrayContainsObjects. function objectHasEnumerableKeys(value) { for (const _ in value) return true; return false; } function isQueryResultSetSchema(schemas) { return (Array.isArray(schemas) && schemas.every((s) => s && typeof s.name === "string")); } function isQueryResultSetColumns(columns) { return (Array.isArray(columns) && columns.every((name) => typeof name === "string")); } // Returns true if the value represents an array of primitives (i.e., a // single-column table). This should only be passed values for which // isDataArray returns true. function arrayIsPrimitive(value) { return ( isTypedArray(value) || arrayContainsPrimitives(value) || arrayContainsDates(value) ); } // Given an array, checks that the first n elements are primitives (number, // string, boolean, bigint) of a consistent type. function arrayContainsPrimitives(value) { const n = Math.min(nChecks, value.length); if (!(n > 0)) return false; let type; let hasPrimitive = false; // ensure we encounter 1+ primitives for (let i = 0; i < n; ++i) { const v = value[i]; if (v == null) continue; // ignore null and undefined const t = typeof v; if (type === undefined) { switch (t) { case "number": case "boolean": case "string": case "bigint": type = t; break; default: return false; } } else if (t !== type) { return false; } hasPrimitive = true; } return hasPrimitive; } // Given an array, checks that the first n elements are dates. function arrayContainsDates(value) { const n = Math.min(nChecks, value.length); if (!(n > 0)) return false; let hasDate = false; // ensure we encounter 1+ dates for (let i = 0; i < n; ++i) { const v = value[i]; if (v == null) continue; // ignore null and undefined if (!(v instanceof Date)) return false; hasDate = true; } return hasDate; } function isTypedArray(value) { return ( value instanceof Int8Array || value instanceof Int16Array || value instanceof Int32Array || value instanceof Uint8Array || value instanceof Uint8ClampedArray || value instanceof Uint16Array || value instanceof Uint32Array || value instanceof Float32Array || value instanceof Float64Array ); } // __query is used by table cells; __query.sql is used by SQL cells. const __query = Object.assign( async (source, operations, invalidation) => { source = await loadDataSource(await source, "table"); if (isDatabaseClient(source)) return evaluateQuery(source, makeQueryTemplate(operations, source), invalidation); if (isDataArray(source)) return __table(source, operations); if (!source) throw new Error("missing data source"); throw new Error("invalid data source"); }, { sql(source, invalidation) { return async function () { return evaluateQuery(await loadDataSource(await source, "sql"), arguments, invalidation); }; } } ); async function loadDataSource(source, mode) { if (source instanceof FileAttachment) { if (mode === "table") { switch (source.mimeType) { case "text/csv": return source.csv({typed: true}); case "text/tab-separated-values": return source.tsv({typed: true}); case "application/json": return source.json(); } } if (mode === "table" || mode === "sql") { switch (source.mimeType) { case "application/x-sqlite3": return source.sqlite(); } } throw new Error(`unsupported file type: ${source.mimeType}`); } return source; } async function evaluateQuery(source, args, invalidation) { if (!source) throw new Error("missing data source"); // If this DatabaseClient supports abort and streaming, use that. if (typeof source.queryTag === "function") { const abortController = new AbortController(); const options = {signal: abortController.signal}; invalidation.then(() => abortController.abort("invalidated")); if (typeof source.queryStream === "function") { return accumulateQuery( source.queryStream(...source.queryTag.apply(source, args), options) ); } if (typeof source.query === "function") { return source.query(...source.queryTag.apply(source, args), options); } } // Otherwise, fallback to the basic sql tagged template literal. if (typeof source.sql === "function") { return source.sql.apply(source, args); } // TODO: test if source is a file attachment, and support CSV etc. throw new Error("source does not implement query, queryStream, or sql"); } // Generator function that yields accumulated query results client.queryStream async function* accumulateQuery(queryRequest) { const queryResponse = await queryRequest; const values = []; values.done = false; values.error = null; values.schema = queryResponse.schema; try { const iterator = queryResponse.readRows(); do { const result = await iterator.next(); if (result.done) { values.done = true; } else { for (const value of result.value) { values.push(value); } } yield values; } while (!values.done); } catch (error) { values.error = error; yield values; } } /** * Returns a SQL query in the form [[parts], ...params] where parts is an array * of sub-strings and params are the parameter values to be inserted between each * sub-string. */ function makeQueryTemplate(operations, source) { const escaper = typeof source.escape === "function" ? source.escape : (i) => i; const {select, from, filter, sort, slice} = operations; if (!from.table) throw new Error("missing from table"); if (select.columns && select.columns.length === 0) throw new Error("at least one column must be selected"); const columns = select.columns ? select.columns.map((c) => `t.${escaper(c)}`) : "*"; const args = [ [`SELECT ${columns} FROM ${formatTable(from.table, escaper)} t`] ]; for (let i = 0; i < filter.length; ++i) { appendSql(i ? `\nAND ` : `\nWHERE `, args); appendWhereEntry(filter[i], args, escaper); } for (let i = 0; i < sort.length; ++i) { appendSql(i ? `, ` : `\nORDER BY `, args); appendOrderBy(sort[i], args, escaper); } if (source.dialect === "mssql") { if (slice.to !== null || slice.from !== null) { if (!sort.length) { if (!select.columns) throw new Err