@crvouga/mockingbird-service-google-calendar 1.6.1 → 1.7.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/CHANGELOG.md +10 -0
- package/dist/{chunk-JTIBRMC6.js → chunk-FJRK3DQ5.js} +2 -2
- package/dist/{chunk-UFM3RECQ.js → chunk-PTK5HWQN.js} +268 -14
- package/dist/chunk-PTK5HWQN.js.map +7 -0
- package/dist/cli.js +2 -2
- package/dist/index.js +1 -1
- package/dist/server.js +2 -2
- package/package.json +2 -2
- package/dist/chunk-UFM3RECQ.js.map +0 -7
- /package/dist/{chunk-JTIBRMC6.js.map → chunk-FJRK3DQ5.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog — @crvouga/mockingbird-service-google-calendar
|
|
2
2
|
|
|
3
|
+
## 1.7.0 (2026-10-01)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- record SQL timelines and serve the admin explorer ([8ced17a](https://github.com/crvouga/mockingbird/commit/8ced17ac398e386ef4e9d7e01fc7744c34b95d07))
|
|
8
|
+
|
|
9
|
+
### Dependencies
|
|
10
|
+
|
|
11
|
+
- `@crvouga/mockingbird-service-sqlite`
|
|
12
|
+
|
|
3
13
|
## 1.6.1 (2026-09-30)
|
|
4
14
|
|
|
5
15
|
### Fixes and improvements
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createRuntime
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-PTK5HWQN.js";
|
|
4
4
|
|
|
5
5
|
// ../../adapters/node/dist/cli.js
|
|
6
6
|
import { readFile } from "node:fs/promises";
|
|
@@ -361,4 +361,4 @@ export {
|
|
|
361
361
|
createServer2 as createServer,
|
|
362
362
|
serveTarget
|
|
363
363
|
};
|
|
364
|
-
//# sourceMappingURL=chunk-
|
|
364
|
+
//# sourceMappingURL=chunk-FJRK3DQ5.js.map
|
|
@@ -1883,6 +1883,167 @@ function faultPresetList(value) {
|
|
|
1883
1883
|
})
|
|
1884
1884
|
};
|
|
1885
1885
|
}
|
|
1886
|
+
function mountSqlExplorer(root, api) {
|
|
1887
|
+
const cell = (value) => {
|
|
1888
|
+
if (value === null || value === void 0)
|
|
1889
|
+
return "";
|
|
1890
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
1891
|
+
return String(value);
|
|
1892
|
+
}
|
|
1893
|
+
return JSON.stringify(value) ?? "";
|
|
1894
|
+
};
|
|
1895
|
+
root.replaceChildren();
|
|
1896
|
+
const layout = document.createElement("div");
|
|
1897
|
+
layout.className = "sql-layout";
|
|
1898
|
+
const tables = document.createElement("div");
|
|
1899
|
+
tables.className = "sql-tables";
|
|
1900
|
+
const stack = document.createElement("div");
|
|
1901
|
+
stack.className = "stack";
|
|
1902
|
+
const banner = document.createElement("p");
|
|
1903
|
+
banner.className = "banner";
|
|
1904
|
+
const grid = document.createElement("div");
|
|
1905
|
+
const input = document.createElement("textarea");
|
|
1906
|
+
input.className = "sql-input";
|
|
1907
|
+
input.spellcheck = false;
|
|
1908
|
+
input.placeholder = "SELECT * FROM ...";
|
|
1909
|
+
const actions = document.createElement("div");
|
|
1910
|
+
actions.className = "row-actions";
|
|
1911
|
+
const run = document.createElement("button");
|
|
1912
|
+
run.type = "button";
|
|
1913
|
+
run.textContent = "Run";
|
|
1914
|
+
actions.append(run);
|
|
1915
|
+
const result = document.createElement("pre");
|
|
1916
|
+
stack.append(banner, grid, input, actions, result);
|
|
1917
|
+
layout.append(tables, stack);
|
|
1918
|
+
root.append(layout);
|
|
1919
|
+
const showError = (error) => {
|
|
1920
|
+
banner.textContent = error instanceof Error ? error.message : String(error);
|
|
1921
|
+
banner.classList.add("show");
|
|
1922
|
+
};
|
|
1923
|
+
const clearError = () => {
|
|
1924
|
+
banner.textContent = "";
|
|
1925
|
+
banner.classList.remove("show");
|
|
1926
|
+
};
|
|
1927
|
+
const renderGrid = (columns, rows) => {
|
|
1928
|
+
grid.replaceChildren();
|
|
1929
|
+
if (columns.length === 0) {
|
|
1930
|
+
const empty = document.createElement("p");
|
|
1931
|
+
empty.className = "empty";
|
|
1932
|
+
empty.textContent = "No rows.";
|
|
1933
|
+
grid.append(empty);
|
|
1934
|
+
return;
|
|
1935
|
+
}
|
|
1936
|
+
const table = document.createElement("table");
|
|
1937
|
+
const head = document.createElement("thead");
|
|
1938
|
+
const headRow = document.createElement("tr");
|
|
1939
|
+
for (const column of columns) {
|
|
1940
|
+
const th = document.createElement("th");
|
|
1941
|
+
th.textContent = column;
|
|
1942
|
+
headRow.append(th);
|
|
1943
|
+
}
|
|
1944
|
+
head.append(headRow);
|
|
1945
|
+
const body = document.createElement("tbody");
|
|
1946
|
+
for (const row of rows) {
|
|
1947
|
+
const tr = document.createElement("tr");
|
|
1948
|
+
for (const column of columns) {
|
|
1949
|
+
const td = document.createElement("td");
|
|
1950
|
+
td.dataset.label = column;
|
|
1951
|
+
td.textContent = cell(row[column]);
|
|
1952
|
+
tr.append(td);
|
|
1953
|
+
}
|
|
1954
|
+
body.append(tr);
|
|
1955
|
+
}
|
|
1956
|
+
table.append(head, body);
|
|
1957
|
+
grid.append(table);
|
|
1958
|
+
};
|
|
1959
|
+
const rowsOf = (value) => {
|
|
1960
|
+
if (!isRecord2(value) || !Array.isArray(value.rows))
|
|
1961
|
+
return [];
|
|
1962
|
+
return value.rows.flatMap((row) => isRecord2(row) ? [row] : []);
|
|
1963
|
+
};
|
|
1964
|
+
const columnsOf = (value, rows) => {
|
|
1965
|
+
if (isRecord2(value) && Array.isArray(value.columns)) {
|
|
1966
|
+
const named = value.columns.flatMap((column) => typeof column === "string" ? [column] : []);
|
|
1967
|
+
if (named.length > 0)
|
|
1968
|
+
return named;
|
|
1969
|
+
}
|
|
1970
|
+
const first = rows[0];
|
|
1971
|
+
return first === void 0 ? [] : Object.keys(first);
|
|
1972
|
+
};
|
|
1973
|
+
let selected = "";
|
|
1974
|
+
const loadPage = async (schema, name) => {
|
|
1975
|
+
selected = `${schema}.${name}`;
|
|
1976
|
+
const page = await api.get(`/sql/tables/${encodeURIComponent(schema)}/${encodeURIComponent(name)}?limit=50`);
|
|
1977
|
+
const rows = rowsOf(page);
|
|
1978
|
+
renderGrid(columnsOf(page, rows), rows);
|
|
1979
|
+
const total = isRecord2(page) && typeof page.total === "number" ? page.total : rows.length;
|
|
1980
|
+
result.textContent = `${selected} \xB7 ${total} rows`;
|
|
1981
|
+
for (const button of tables.querySelectorAll("button")) {
|
|
1982
|
+
if (!(button instanceof HTMLButtonElement))
|
|
1983
|
+
continue;
|
|
1984
|
+
button.setAttribute("aria-current", button.dataset.schema === schema && button.dataset.table === name ? "true" : "false");
|
|
1985
|
+
}
|
|
1986
|
+
};
|
|
1987
|
+
const loadTables = async () => {
|
|
1988
|
+
const payload = await api.get("/sql/tables");
|
|
1989
|
+
const listed = isRecord2(payload) && Array.isArray(payload.tables) ? payload.tables : [];
|
|
1990
|
+
tables.replaceChildren();
|
|
1991
|
+
if (listed.length === 0) {
|
|
1992
|
+
const empty = document.createElement("p");
|
|
1993
|
+
empty.className = "empty";
|
|
1994
|
+
empty.textContent = "No tables.";
|
|
1995
|
+
tables.append(empty);
|
|
1996
|
+
return;
|
|
1997
|
+
}
|
|
1998
|
+
for (const item of listed) {
|
|
1999
|
+
if (!isRecord2(item) || typeof item.schema !== "string" || typeof item.name !== "string")
|
|
2000
|
+
continue;
|
|
2001
|
+
const button = document.createElement("button");
|
|
2002
|
+
button.type = "button";
|
|
2003
|
+
button.dataset.schema = item.schema;
|
|
2004
|
+
button.dataset.table = item.name;
|
|
2005
|
+
button.textContent = `${item.schema}.${item.name}`;
|
|
2006
|
+
tables.append(button);
|
|
2007
|
+
}
|
|
2008
|
+
};
|
|
2009
|
+
tables.addEventListener("click", (event) => {
|
|
2010
|
+
const target = event.target;
|
|
2011
|
+
if (!(target instanceof Element))
|
|
2012
|
+
return;
|
|
2013
|
+
const button = target.closest("button");
|
|
2014
|
+
if (!(button instanceof HTMLButtonElement))
|
|
2015
|
+
return;
|
|
2016
|
+
const schema = button.dataset.schema;
|
|
2017
|
+
const name = button.dataset.table;
|
|
2018
|
+
if (schema === void 0 || name === void 0)
|
|
2019
|
+
return;
|
|
2020
|
+
clearError();
|
|
2021
|
+
loadPage(schema, name).catch(showError);
|
|
2022
|
+
});
|
|
2023
|
+
run.addEventListener("click", () => {
|
|
2024
|
+
const sql = input.value.trim();
|
|
2025
|
+
if (sql === "") {
|
|
2026
|
+
showError(new Error("Enter a statement"));
|
|
2027
|
+
return;
|
|
2028
|
+
}
|
|
2029
|
+
clearError();
|
|
2030
|
+
api.send("POST", "/sql/query", { sql }).then((payload) => {
|
|
2031
|
+
const rows = rowsOf(payload);
|
|
2032
|
+
renderGrid(columnsOf(payload, rows), rows);
|
|
2033
|
+
const count = isRecord2(payload) && typeof payload.rowCount === "number" ? payload.rowCount : rows.length;
|
|
2034
|
+
const truncated = isRecord2(payload) && payload.truncated === true ? " (truncated)" : "";
|
|
2035
|
+
result.textContent = `${count} rows${truncated}`;
|
|
2036
|
+
}).catch(showError);
|
|
2037
|
+
});
|
|
2038
|
+
const namespace = document.getElementById("namespace");
|
|
2039
|
+
if (namespace) {
|
|
2040
|
+
namespace.addEventListener("change", () => {
|
|
2041
|
+
clearError();
|
|
2042
|
+
loadTables().catch(showError);
|
|
2043
|
+
});
|
|
2044
|
+
}
|
|
2045
|
+
loadTables().catch(showError);
|
|
2046
|
+
}
|
|
1886
2047
|
function bootAdmin(config) {
|
|
1887
2048
|
const fieldKinds = ["string", "number", "boolean", "null", "object", "array", "unknown"];
|
|
1888
2049
|
const sources = ["declared", "inferred", "mixed"];
|
|
@@ -2559,6 +2720,22 @@ function bootAdmin(config) {
|
|
|
2559
2720
|
panel.script = value.script;
|
|
2560
2721
|
return panel;
|
|
2561
2722
|
};
|
|
2723
|
+
const decodeExtension = (value) => {
|
|
2724
|
+
if (!isRecord2(value))
|
|
2725
|
+
return void 0;
|
|
2726
|
+
if (value.kind === "sql") {
|
|
2727
|
+
return {
|
|
2728
|
+
kind: "sql",
|
|
2729
|
+
id: typeof value.id === "string" && value.id !== "" ? value.id : "sql",
|
|
2730
|
+
title: typeof value.title === "string" && value.title !== "" ? value.title : "SQL",
|
|
2731
|
+
description: typeof value.description === "string" ? value.description : "Browse tables and run queries."
|
|
2732
|
+
};
|
|
2733
|
+
}
|
|
2734
|
+
if (value.kind !== "panel")
|
|
2735
|
+
return void 0;
|
|
2736
|
+
const panel = decodePanel(value);
|
|
2737
|
+
return panel === void 0 ? void 0 : { kind: "panel", ...panel };
|
|
2738
|
+
};
|
|
2562
2739
|
const mountPanels = (panels) => {
|
|
2563
2740
|
const nav = el("nav", HTMLElement);
|
|
2564
2741
|
const views = el("views", HTMLElement);
|
|
@@ -2612,10 +2789,48 @@ function bootAdmin(config) {
|
|
|
2612
2789
|
return decoded === void 0 ? [] : [decoded];
|
|
2613
2790
|
});
|
|
2614
2791
|
mountPanels(panels);
|
|
2792
|
+
const extensions = requireList(isRecord2(manifest) ? manifest.extensions : void 0, "Extensions").flatMap((extension) => {
|
|
2793
|
+
const decoded = decodeExtension(extension);
|
|
2794
|
+
return decoded === void 0 ? [] : [decoded];
|
|
2795
|
+
});
|
|
2796
|
+
const extensionPanels = extensions.flatMap((extension) => extension.kind === "panel" ? [extension] : []);
|
|
2797
|
+
mountPanels(extensionPanels);
|
|
2798
|
+
for (const extension of extensions) {
|
|
2799
|
+
if (extension.kind !== "sql")
|
|
2800
|
+
continue;
|
|
2801
|
+
const nav = el("nav", HTMLElement);
|
|
2802
|
+
const views = el("views", HTMLElement);
|
|
2803
|
+
const button = document.createElement("button");
|
|
2804
|
+
button.type = "button";
|
|
2805
|
+
button.dataset.view = extension.id;
|
|
2806
|
+
button.textContent = extension.title;
|
|
2807
|
+
nav.append(button);
|
|
2808
|
+
const section = document.createElement("section");
|
|
2809
|
+
section.className = "view";
|
|
2810
|
+
section.id = `view-${extension.id}`;
|
|
2811
|
+
section.hidden = true;
|
|
2812
|
+
const title = document.createElement("h2");
|
|
2813
|
+
title.textContent = extension.title;
|
|
2814
|
+
const lede = document.createElement("p");
|
|
2815
|
+
lede.className = "lede";
|
|
2816
|
+
lede.textContent = extension.description;
|
|
2817
|
+
const body = document.createElement("div");
|
|
2818
|
+
body.className = "panel-body";
|
|
2819
|
+
section.append(title, lede, body);
|
|
2820
|
+
views.append(section);
|
|
2821
|
+
mountSqlExplorer(body, panelApi);
|
|
2822
|
+
}
|
|
2615
2823
|
return refresh();
|
|
2616
2824
|
}).catch(showError);
|
|
2617
2825
|
}
|
|
2618
|
-
var clientPrelude = [
|
|
2826
|
+
var clientPrelude = [
|
|
2827
|
+
isRecord2,
|
|
2828
|
+
mountAdminBrand,
|
|
2829
|
+
startAdminBrand,
|
|
2830
|
+
faultPresetList,
|
|
2831
|
+
mountSqlExplorer,
|
|
2832
|
+
bootAdmin
|
|
2833
|
+
].map((fn) => fn.toString()).join("\n");
|
|
2619
2834
|
var adminClientSource = (config) => {
|
|
2620
2835
|
const payload = JSON.stringify(config).replace(/</g, "\\u003c");
|
|
2621
2836
|
const entry = bootAdmin.name;
|
|
@@ -2665,19 +2880,33 @@ var STANDARD_ADMIN_ROUTES = [
|
|
|
2665
2880
|
"GET /ui/manifest"
|
|
2666
2881
|
];
|
|
2667
2882
|
var PANEL_ID = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;
|
|
2883
|
+
var RESERVED_VIEWS = /* @__PURE__ */ new Set(["overview", "state", "clock", "faults", "journal", "routes"]);
|
|
2884
|
+
var checkPanel = (panel, seen, label) => {
|
|
2885
|
+
if (!PANEL_ID.test(panel.id)) {
|
|
2886
|
+
throw new RangeError(`${label} id must match ${PANEL_ID}: ${JSON.stringify(panel.id)}`);
|
|
2887
|
+
}
|
|
2888
|
+
if (RESERVED_VIEWS.has(panel.id)) {
|
|
2889
|
+
throw new RangeError(`${label} id ${panel.id} is reserved by the admin shell`);
|
|
2890
|
+
}
|
|
2891
|
+
if (seen.has(panel.id))
|
|
2892
|
+
throw new RangeError(`duplicate admin view id ${panel.id}`);
|
|
2893
|
+
seen.add(panel.id);
|
|
2894
|
+
if (panel.title.trim() === "")
|
|
2895
|
+
throw new RangeError(`${label} ${panel.id} needs a title`);
|
|
2896
|
+
};
|
|
2668
2897
|
var assertAdminUi = (ui) => {
|
|
2669
|
-
if (!ui
|
|
2898
|
+
if (!ui)
|
|
2670
2899
|
return;
|
|
2671
2900
|
const seen = /* @__PURE__ */ new Set();
|
|
2672
|
-
for (const panel of ui.panels)
|
|
2673
|
-
|
|
2674
|
-
|
|
2901
|
+
for (const panel of ui.panels ?? [])
|
|
2902
|
+
checkPanel(panel, seen, "admin panel");
|
|
2903
|
+
for (const extension of ui.extensions ?? []) {
|
|
2904
|
+
if (extension.kind === "panel") {
|
|
2905
|
+
checkPanel(extension, seen, "admin extension");
|
|
2906
|
+
continue;
|
|
2675
2907
|
}
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
seen.add(panel.id);
|
|
2679
|
-
if (panel.title.trim() === "")
|
|
2680
|
-
throw new RangeError(`admin panel ${panel.id} needs a title`);
|
|
2908
|
+
const id = extension.id ?? "sql";
|
|
2909
|
+
checkPanel({ id, title: extension.title ?? "SQL" }, seen, "admin extension");
|
|
2681
2910
|
}
|
|
2682
2911
|
};
|
|
2683
2912
|
|
|
@@ -2911,6 +3140,12 @@ var renderAdminDocument = (service) => {
|
|
|
2911
3140
|
border-radius: 999px; padding: 4px 8px;
|
|
2912
3141
|
}
|
|
2913
3142
|
.chip i { color: var(--faint); font-style: normal; }
|
|
3143
|
+
.sql-layout {
|
|
3144
|
+
display: grid; grid-template-columns: minmax(10rem, 16rem) minmax(0, 1fr); gap: 16px;
|
|
3145
|
+
}
|
|
3146
|
+
.sql-tables { display: flex; flex-direction: column; gap: 4px; max-height: 28rem; overflow: auto; }
|
|
3147
|
+
.sql-tables button { text-align: left; }
|
|
3148
|
+
.sql-input { font-family: var(--mono); min-height: 7rem; width: 100%; }
|
|
2914
3149
|
table { width: 100%; border-collapse: collapse; font-size: 14px; }
|
|
2915
3150
|
th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--line); vertical-align: top; }
|
|
2916
3151
|
th { color: var(--faint); font-weight: 600; font-size: 12px; }
|
|
@@ -2974,7 +3209,7 @@ var renderAdminDocument = (service) => {
|
|
|
2974
3209
|
padding: 8px;
|
|
2975
3210
|
}
|
|
2976
3211
|
nav.side button { white-space: nowrap; flex: none; }
|
|
2977
|
-
.split { grid-template-columns: minmax(0, 1fr); }
|
|
3212
|
+
.split, .sql-layout { grid-template-columns: minmax(0, 1fr); }
|
|
2978
3213
|
main { padding: 14px; }
|
|
2979
3214
|
table, thead, tbody, tr, th, td { display: block; }
|
|
2980
3215
|
thead { display: none; }
|
|
@@ -3145,6 +3380,24 @@ var json2 = (body) => new Response(JSON.stringify(body), {
|
|
|
3145
3380
|
status: 200,
|
|
3146
3381
|
headers: { "content-type": "application/json; charset=utf-8" }
|
|
3147
3382
|
});
|
|
3383
|
+
var describeExtension = (extension) => {
|
|
3384
|
+
if (extension.kind === "sql") {
|
|
3385
|
+
return {
|
|
3386
|
+
kind: "sql",
|
|
3387
|
+
id: extension.id ?? "sql",
|
|
3388
|
+
title: extension.title ?? "SQL",
|
|
3389
|
+
...extension.description !== void 0 ? { description: extension.description } : {}
|
|
3390
|
+
};
|
|
3391
|
+
}
|
|
3392
|
+
return {
|
|
3393
|
+
kind: "panel",
|
|
3394
|
+
id: extension.id,
|
|
3395
|
+
title: extension.title,
|
|
3396
|
+
...extension.description !== void 0 ? { description: extension.description } : {},
|
|
3397
|
+
html: extension.html,
|
|
3398
|
+
...extension.script !== void 0 ? { script: extension.script } : {}
|
|
3399
|
+
};
|
|
3400
|
+
};
|
|
3148
3401
|
var adminUiRoutes = (service, ui) => {
|
|
3149
3402
|
const shell = () => renderAdminDocument(service);
|
|
3150
3403
|
const document3 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
|
|
@@ -3159,7 +3412,8 @@ var adminUiRoutes = (service, ui) => {
|
|
|
3159
3412
|
...panel.description !== void 0 ? { description: panel.description } : {},
|
|
3160
3413
|
html: panel.html,
|
|
3161
3414
|
...panel.script !== void 0 ? { script: panel.script } : {}
|
|
3162
|
-
}))
|
|
3415
|
+
})),
|
|
3416
|
+
extensions: (ui?.extensions ?? []).map(describeExtension)
|
|
3163
3417
|
})
|
|
3164
3418
|
};
|
|
3165
3419
|
};
|
|
@@ -3836,7 +4090,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
3836
4090
|
};
|
|
3837
4091
|
|
|
3838
4092
|
// ../core/dist/version.js
|
|
3839
|
-
var PACKAGE_VERSION = true ? "1.
|
|
4093
|
+
var PACKAGE_VERSION = true ? "1.7.0" : UNRELEASED_VERSION;
|
|
3840
4094
|
|
|
3841
4095
|
// ../core/dist/signing.js
|
|
3842
4096
|
var encoder = new TextEncoder();
|
|
@@ -5921,4 +6175,4 @@ export {
|
|
|
5921
6175
|
emailFromCode,
|
|
5922
6176
|
GoogleCalendarAPI
|
|
5923
6177
|
};
|
|
5924
|
-
//# sourceMappingURL=chunk-
|
|
6178
|
+
//# sourceMappingURL=chunk-PTK5HWQN.js.map
|