class DatasetteClient {
constructor(baseUrl) {
this.baseUrl = baseUrl;
}
_getFetchURL(sql, params, type = "json") {
const searchParams = new URLSearchParams();
searchParams.append("sql", sql);
if (params)
for (const key in params) {
searchParams.append(key, params[key]);
}
return `${this.baseUrl}.${type}?${searchParams.toString()}`;
}
_parseTemplate(strings, ...values) {
let sql = "";
const params = {};
let paramsI = 0;
for (const [i, s] of strings.entries()) {
if (i < values.length) {
if (Array.isArray(values[i])) {
const sqlArrayParts = [];
for (const v of values[i]) {
const param = `p${paramsI++}`;
sqlArrayParts.push(`:${param}`);
params[param] = v;
}
sql += `${s}(${sqlArrayParts.join(",")})`;
}
else {
const param = `p${paramsI++}`;
sql += `${s}:${param}`;
params[param] = values[i];
}
} else {
sql += s;
}
}
return { sql, params };
}
_element(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;
}
_text(value) {
return document.createTextNode(value);
}
async query(query, params) {
const fetchUrl = this._getFetchURL(query, params, "csv");
return d3.csv(fetchUrl, d3.autoType);
}
async geoquery(query, params) {
const fetchUrl = this._getFetchURL(query, params, "geojson");
const result = await fetch(fetchUrl).then((r) => r.json());
if (typeof result.ok !== "undefined" && !result.ok)
throw Error(result.error);
return result;
}
async _row_count(query, params) {
const count_query = `select count(*) as num_rows from (${query.replace(
/(\s*;?\s*)*$/,
""
)})`;
const count_data = await this._page(count_query, params);
return count_data[0].num_rows;
}
async _page(query, params) {
const fetchUrl = this._getFetchURL(query, params);
const result = await fetch(fetchUrl).then((r) => r.json());
if (typeof result.ok !== "undefined" && !result.ok)
throw Error(result.error);
const data = result.rows.map((row) =>
row.reduce((res, field, index) => {
res[result.columns[index]] = field;
return res;
}, {})
);
data.truncated = result.truncated;
return data;
}
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(:p0)`, { p0: object }));
if (!rows.length) throw new Error("Not found");
const { columns } = rows;
return this._element("table", { value: rows }, [
this._element("thead", [
this._element(
"tr",
columns.map((c) => this._element("th", [this._text(c)]))
)
]),
this._element(
"tbody",
rows.map((r) =>
this._element(
"tr",
columns.map((c) => this._element("td", [this._text(r[c])]))
)
)
)
]);
}
async sql(strings, ...values) {
const { sql, params } = this._parseTemplate(strings, ...values);
return this.query(sql, params);
}
}