@crvouga/mockingbird-service-step-functions 1.4.0 → 1.5.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.
@@ -541,10 +541,10 @@ var OpenAPIReferenceError = class extends Error {
541
541
  };
542
542
  var unescapePointer = (segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~");
543
543
  var isReference = (value) => typeof value === "object" && value !== null && typeof value.$ref === "string";
544
- var resolveRef = (document2, ref) => {
544
+ var resolveRef = (document3, ref) => {
545
545
  if (!ref.startsWith("#/"))
546
546
  throw new OpenAPIReferenceError(ref);
547
- let cursor = document2;
547
+ let cursor = document3;
548
548
  for (const raw of ref.slice(2).split("/")) {
549
549
  const segment = unescapePointer(raw);
550
550
  if (typeof cursor !== "object" || cursor === null || !(segment in cursor)) {
@@ -556,14 +556,14 @@ var resolveRef = (document2, ref) => {
556
556
  throw new OpenAPIReferenceError(ref);
557
557
  return cursor;
558
558
  };
559
- var deref = (document2, value) => {
559
+ var deref = (document3, value) => {
560
560
  let current = value;
561
561
  const seen = /* @__PURE__ */ new Set();
562
562
  while (isReference(current)) {
563
563
  if (seen.has(current.$ref))
564
564
  throw new OpenAPIReferenceError(`${current.$ref} (cycle)`);
565
565
  seen.add(current.$ref);
566
- current = resolveRef(document2, current.$ref);
566
+ current = resolveRef(document3, current.$ref);
567
567
  }
568
568
  return current;
569
569
  };
@@ -581,36 +581,36 @@ var HTTP_METHODS = [
581
581
  ];
582
582
 
583
583
  // ../../openapi/core/dist/document.js
584
- var mergeParameters = (document2, item, own) => {
584
+ var mergeParameters = (document3, item, own) => {
585
585
  const merged = /* @__PURE__ */ new Map();
586
586
  for (const raw of item.parameters ?? []) {
587
- const parameter = deref(document2, raw);
587
+ const parameter = deref(document3, raw);
588
588
  merged.set(`${parameter.in}:${parameter.name}`, parameter);
589
589
  }
590
590
  for (const raw of own ?? []) {
591
- const parameter = deref(document2, raw);
591
+ const parameter = deref(document3, raw);
592
592
  merged.set(`${parameter.in}:${parameter.name}`, parameter);
593
593
  }
594
594
  return [...merged.values()];
595
595
  };
596
- var listOperations = (document2) => {
596
+ var listOperations = (document3) => {
597
597
  const operations = [];
598
- for (const [path, item] of Object.entries(document2.paths)) {
598
+ for (const [path, item] of Object.entries(document3.paths)) {
599
599
  for (const method of HTTP_METHODS) {
600
600
  const operation = item[method];
601
601
  if (operation?.operationId === void 0)
602
602
  continue;
603
603
  const responses = {};
604
604
  for (const [status, response] of Object.entries(operation.responses)) {
605
- responses[status] = deref(document2, response);
605
+ responses[status] = deref(document3, response);
606
606
  }
607
607
  operations.push({
608
608
  operationId: operation.operationId,
609
609
  method,
610
610
  path,
611
611
  operation,
612
- parameters: mergeParameters(document2, item, operation.parameters),
613
- requestBody: operation.requestBody === void 0 ? void 0 : deref(document2, operation.requestBody),
612
+ parameters: mergeParameters(document3, item, operation.parameters),
613
+ requestBody: operation.requestBody === void 0 ? void 0 : deref(document3, operation.requestBody),
614
614
  responses
615
615
  });
616
616
  }
@@ -971,6 +971,1232 @@ var clearNamespace = (sqlite, namespace) => {
971
971
  });
972
972
  };
973
973
 
974
+ // ../../ui/dist/reset.js
975
+ var CSS_RESET = `*,
976
+ *::before,
977
+ *::after {
978
+ box-sizing: border-box;
979
+ margin: 0;
980
+ padding: 0;
981
+ }
982
+
983
+ * {
984
+ scrollbar-width: thin;
985
+ scrollbar-color: color-mix(in srgb, CanvasText 35%, Canvas) Canvas;
986
+ }
987
+
988
+ *::-webkit-scrollbar {
989
+ width: 12px;
990
+ height: 12px;
991
+ }
992
+
993
+ *::-webkit-scrollbar-track,
994
+ *::-webkit-scrollbar-corner {
995
+ background: Canvas;
996
+ }
997
+
998
+ *::-webkit-scrollbar-thumb {
999
+ background: color-mix(in srgb, CanvasText 35%, Canvas);
1000
+ border: 3px solid Canvas;
1001
+ border-radius: 999px;
1002
+ }
1003
+
1004
+ html {
1005
+ hanging-punctuation: first allow-end last;
1006
+ -webkit-text-size-adjust: none;
1007
+ text-size-adjust: none;
1008
+ tab-size: 4;
1009
+ font-family:
1010
+ ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI",
1011
+ "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
1012
+ font-size: 16px;
1013
+ line-height: 1.5;
1014
+ -webkit-font-smoothing: antialiased;
1015
+ -moz-osx-font-smoothing: grayscale;
1016
+ text-rendering: optimizeLegibility;
1017
+ -webkit-tap-highlight-color: transparent;
1018
+ scroll-behavior: smooth;
1019
+ background: Canvas;
1020
+ color: CanvasText;
1021
+ }
1022
+
1023
+ body {
1024
+ min-height: 100svh;
1025
+ line-height: 1.5;
1026
+ background: Canvas;
1027
+ color: CanvasText;
1028
+ }
1029
+
1030
+ h1 {
1031
+ font-size: 2em;
1032
+ }
1033
+
1034
+ h2 {
1035
+ font-size: 1.5em;
1036
+ }
1037
+
1038
+ h3 {
1039
+ font-size: 1.17em;
1040
+ }
1041
+
1042
+ h4 {
1043
+ font-size: 1em;
1044
+ }
1045
+
1046
+ h5 {
1047
+ font-size: 0.83em;
1048
+ }
1049
+
1050
+ h6 {
1051
+ font-size: 0.67em;
1052
+ }
1053
+
1054
+ h1,
1055
+ h2,
1056
+ h3,
1057
+ h4,
1058
+ h5,
1059
+ h6 {
1060
+ font-weight: 700;
1061
+ line-height: 1.2;
1062
+ text-wrap: balance;
1063
+ overflow-wrap: break-word;
1064
+ }
1065
+
1066
+ p,
1067
+ li,
1068
+ figcaption,
1069
+ dd,
1070
+ dt,
1071
+ blockquote {
1072
+ overflow-wrap: break-word;
1073
+ }
1074
+
1075
+ p,
1076
+ li,
1077
+ figcaption,
1078
+ blockquote {
1079
+ text-wrap: pretty;
1080
+ }
1081
+
1082
+ ul,
1083
+ ol,
1084
+ menu {
1085
+ list-style: none;
1086
+ }
1087
+
1088
+ a {
1089
+ color: inherit;
1090
+ text-decoration: underline;
1091
+ text-decoration-thickness: 1px;
1092
+ text-underline-offset: 0.15em;
1093
+ text-decoration-skip-ink: auto;
1094
+ }
1095
+
1096
+ b,
1097
+ strong {
1098
+ font-weight: 700;
1099
+ }
1100
+
1101
+ small {
1102
+ font-size: 0.875em;
1103
+ }
1104
+
1105
+ code,
1106
+ kbd,
1107
+ samp,
1108
+ pre {
1109
+ font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
1110
+ font-size: 1em;
1111
+ }
1112
+
1113
+ sub,
1114
+ sup {
1115
+ font-size: 75%;
1116
+ line-height: 0;
1117
+ position: relative;
1118
+ vertical-align: baseline;
1119
+ }
1120
+
1121
+ sub {
1122
+ bottom: -0.25em;
1123
+ }
1124
+
1125
+ sup {
1126
+ top: -0.5em;
1127
+ }
1128
+
1129
+ abbr[title] {
1130
+ text-decoration: underline dotted;
1131
+ text-underline-offset: 0.15em;
1132
+ }
1133
+
1134
+ mark {
1135
+ background: Mark;
1136
+ color: MarkText;
1137
+ }
1138
+
1139
+ hr {
1140
+ height: 0;
1141
+ border: 0;
1142
+ border-top: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
1143
+ color: inherit;
1144
+ }
1145
+
1146
+ img,
1147
+ picture,
1148
+ video,
1149
+ canvas {
1150
+ display: block;
1151
+ max-width: 100%;
1152
+ }
1153
+
1154
+ img,
1155
+ video {
1156
+ height: auto;
1157
+ }
1158
+
1159
+ svg {
1160
+ overflow: hidden;
1161
+ }
1162
+
1163
+ iframe {
1164
+ border: 0;
1165
+ max-width: 100%;
1166
+ }
1167
+
1168
+ table {
1169
+ border-collapse: collapse;
1170
+ border-spacing: 0;
1171
+ }
1172
+
1173
+ fieldset {
1174
+ border: 0;
1175
+ min-inline-size: 0;
1176
+ }
1177
+
1178
+ legend {
1179
+ padding: 0;
1180
+ }
1181
+
1182
+ button,
1183
+ input,
1184
+ select,
1185
+ textarea,
1186
+ optgroup {
1187
+ font: inherit;
1188
+ letter-spacing: inherit;
1189
+ color: inherit;
1190
+ margin: 0;
1191
+ vertical-align: middle;
1192
+ }
1193
+
1194
+ button,
1195
+ input[type="button"],
1196
+ input[type="submit"],
1197
+ input[type="reset"] {
1198
+ appearance: none;
1199
+ background: none;
1200
+ border: 0;
1201
+ padding: 0;
1202
+ cursor: pointer;
1203
+ text-align: center;
1204
+ }
1205
+
1206
+ input,
1207
+ textarea,
1208
+ select {
1209
+ background-color: Canvas;
1210
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1211
+ border-radius: 0;
1212
+ padding: 0.5em 0.65em;
1213
+ }
1214
+
1215
+ input[type="checkbox"],
1216
+ input[type="radio"],
1217
+ input[type="range"],
1218
+ input[type="file"],
1219
+ input[type="color"],
1220
+ input[type="button"],
1221
+ input[type="submit"],
1222
+ input[type="reset"],
1223
+ input[type="image"],
1224
+ input[type="hidden"] {
1225
+ background: none;
1226
+ border: 0;
1227
+ padding: 0;
1228
+ border-radius: 0;
1229
+ }
1230
+
1231
+ input[type="checkbox"],
1232
+ input[type="radio"] {
1233
+ accent-color: CanvasText;
1234
+ }
1235
+
1236
+ input[type="search"] {
1237
+ appearance: none;
1238
+ background-color: Canvas;
1239
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1240
+ }
1241
+
1242
+ input[type="search"]::-webkit-search-decoration,
1243
+ input[type="search"]::-webkit-search-cancel-button,
1244
+ input[type="search"]::-webkit-search-results-button,
1245
+ input[type="search"]::-webkit-search-results-decoration {
1246
+ -webkit-appearance: none;
1247
+ display: none;
1248
+ }
1249
+
1250
+ input[type="number"] {
1251
+ appearance: textfield;
1252
+ background-color: Canvas;
1253
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1254
+ }
1255
+
1256
+ input[type="number"]::-webkit-inner-spin-button,
1257
+ input[type="number"]::-webkit-outer-spin-button {
1258
+ -webkit-appearance: none;
1259
+ margin: 0;
1260
+ }
1261
+
1262
+ textarea {
1263
+ resize: vertical;
1264
+ }
1265
+
1266
+ ::placeholder {
1267
+ opacity: 1;
1268
+ color: color-mix(in srgb, CanvasText 45%, transparent);
1269
+ }
1270
+
1271
+ :focus-visible {
1272
+ outline: 2px solid CanvasText;
1273
+ outline-offset: 2px;
1274
+ }
1275
+
1276
+ :focus:not(:focus-visible) {
1277
+ outline: none;
1278
+ }
1279
+
1280
+ :disabled,
1281
+ [disabled] {
1282
+ cursor: default;
1283
+ opacity: 0.6;
1284
+ }
1285
+
1286
+ label,
1287
+ summary,
1288
+ select,
1289
+ [role="button"] {
1290
+ cursor: pointer;
1291
+ }
1292
+
1293
+ summary {
1294
+ display: list-item;
1295
+ }
1296
+
1297
+ details {
1298
+ display: block;
1299
+ }
1300
+
1301
+ dialog {
1302
+ margin: auto;
1303
+ padding: 1rem;
1304
+ border: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
1305
+ background: Canvas;
1306
+ color: CanvasText;
1307
+ max-width: calc(100vw - 2rem);
1308
+ max-height: calc(100svh - 2rem);
1309
+ }
1310
+
1311
+ dialog:not([open]) {
1312
+ display: none;
1313
+ }
1314
+
1315
+ ::backdrop {
1316
+ background: color-mix(in srgb, CanvasText 40%, transparent);
1317
+ }
1318
+
1319
+ [hidden] {
1320
+ display: none !important;
1321
+ }
1322
+
1323
+ ::selection {
1324
+ background: Highlight;
1325
+ color: HighlightText;
1326
+ }
1327
+
1328
+ @media (prefers-reduced-motion: reduce) {
1329
+ *,
1330
+ *::before,
1331
+ *::after {
1332
+ animation-duration: 0.01ms !important;
1333
+ animation-iteration-count: 1 !important;
1334
+ transition-duration: 0.01ms !important;
1335
+ scroll-behavior: auto !important;
1336
+ }
1337
+ }
1338
+
1339
+ @media (pointer: coarse) {
1340
+ input,
1341
+ select,
1342
+ textarea {
1343
+ font-size: max(16px, 1em);
1344
+ }
1345
+ }`;
1346
+
1347
+ // ../core/dist/admin-client.js
1348
+ function isRecord2(value) {
1349
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1350
+ }
1351
+ function mountAdminBrand(doc, host, service, catalog) {
1352
+ const clip = (value, max) => {
1353
+ if (typeof value !== "string")
1354
+ return null;
1355
+ const trimmed = value.trim();
1356
+ if (trimmed === "")
1357
+ return null;
1358
+ return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
1359
+ };
1360
+ const httpUrl = (value) => {
1361
+ if (typeof value !== "string" || value.length > 2e3)
1362
+ return null;
1363
+ try {
1364
+ const url = new URL(value);
1365
+ if (url.protocol !== "https:" && url.protocol !== "http:")
1366
+ return null;
1367
+ return url.href;
1368
+ } catch {
1369
+ return null;
1370
+ }
1371
+ };
1372
+ const hexColor = (value) => {
1373
+ if (typeof value !== "string")
1374
+ return null;
1375
+ const raw = value.trim();
1376
+ return /^#[0-9a-fA-F]{3,8}$/.test(raw) ? raw : null;
1377
+ };
1378
+ const hostname = (value) => {
1379
+ try {
1380
+ return new URL(value).hostname.replace(/^www\./, "");
1381
+ } catch {
1382
+ return value;
1383
+ }
1384
+ };
1385
+ host.replaceChildren();
1386
+ host.hidden = true;
1387
+ if (!isRecord2(catalog) || !Object.hasOwn(catalog, service))
1388
+ return;
1389
+ const row = catalog[service];
1390
+ if (!isRecord2(row))
1391
+ return;
1392
+ const vendor = clip(row.vendor, 80);
1393
+ const website = httpUrl(row.website);
1394
+ const guide = httpUrl(row.guide);
1395
+ const docs = httpUrl(row.docs);
1396
+ const logo = httpUrl(row.logo);
1397
+ const color = hexColor(row.color);
1398
+ const description = clip(row.description, 280);
1399
+ if (vendor === null || website === null && guide === null && docs === null)
1400
+ return;
1401
+ const add = (node) => {
1402
+ host.append(node);
1403
+ return node;
1404
+ };
1405
+ if (logo !== null) {
1406
+ const img = doc.createElement("img");
1407
+ img.alt = "";
1408
+ img.src = logo;
1409
+ img.width = 16;
1410
+ img.height = 16;
1411
+ img.decoding = "async";
1412
+ img.referrerPolicy = "no-referrer";
1413
+ if (color !== null)
1414
+ img.style.borderColor = color;
1415
+ add(img);
1416
+ }
1417
+ const name = doc.createElement("span");
1418
+ name.className = "vendor-name";
1419
+ name.textContent = vendor;
1420
+ if (description !== null)
1421
+ name.title = description;
1422
+ add(name);
1423
+ const sep = () => {
1424
+ const node = doc.createElement("span");
1425
+ node.className = "vendor-sep";
1426
+ node.setAttribute("aria-hidden", "true");
1427
+ node.textContent = "\xB7";
1428
+ return node;
1429
+ };
1430
+ const link = (href, label, title) => {
1431
+ const node = doc.createElement("a");
1432
+ node.className = "vendor-link";
1433
+ node.href = href;
1434
+ node.target = "_blank";
1435
+ node.rel = "noopener noreferrer";
1436
+ node.referrerPolicy = "no-referrer";
1437
+ node.textContent = label;
1438
+ node.title = title;
1439
+ return node;
1440
+ };
1441
+ if (website !== null) {
1442
+ add(sep());
1443
+ add(link(website, hostname(website), `${vendor} website`));
1444
+ }
1445
+ if (guide !== null) {
1446
+ add(sep());
1447
+ add(link(guide, "Docs", `Docs for ${vendor}`));
1448
+ }
1449
+ if (docs !== null) {
1450
+ add(sep());
1451
+ add(link(docs, "API", `${vendor} API reference`));
1452
+ }
1453
+ host.removeAttribute("hidden");
1454
+ host.hidden = false;
1455
+ }
1456
+ function startAdminBrand(env) {
1457
+ const requested = env.params.get("brands");
1458
+ let brandsUrl = env.brandsUrl;
1459
+ if (requested) {
1460
+ try {
1461
+ const url = new URL(requested, env.location.href);
1462
+ const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]" || url.origin === env.location.origin;
1463
+ if ((url.protocol === "https:" || url.protocol === "http:") && local)
1464
+ brandsUrl = url.href;
1465
+ } catch {
1466
+ brandsUrl = env.brandsUrl;
1467
+ }
1468
+ }
1469
+ const vendorHost = env.$("vendor");
1470
+ const brandRoot = env.document.querySelector("[data-mockingbird-admin]");
1471
+ const brandService = brandRoot?.getAttribute("data-service") || "";
1472
+ if (vendorHost === null)
1473
+ return;
1474
+ env.fetch(brandsUrl, {
1475
+ headers: { accept: "application/json" },
1476
+ signal: AbortSignal.timeout(5e3)
1477
+ }).then(async (response) => {
1478
+ if (!response.ok)
1479
+ return null;
1480
+ const catalog = await response.json();
1481
+ return catalog;
1482
+ }).then((catalog) => {
1483
+ mountAdminBrand(env.document, vendorHost, brandService, catalog);
1484
+ }).catch(() => {
1485
+ });
1486
+ }
1487
+ function faultPresetList(value) {
1488
+ if (!isRecord2(value))
1489
+ throw new Error("Fault presets response was not an object");
1490
+ if (!Object.hasOwn(value, "presets") || value.presets === void 0 || value.presets === null) {
1491
+ return { presets: [] };
1492
+ }
1493
+ if (!Array.isArray(value.presets))
1494
+ throw new Error("Fault presets response was not a list");
1495
+ return {
1496
+ presets: value.presets.flatMap((item) => {
1497
+ if (!isRecord2(item) || typeof item.name !== "string")
1498
+ return [];
1499
+ return [{ name: item.name }];
1500
+ })
1501
+ };
1502
+ }
1503
+ function bootAdmin(config) {
1504
+ const fieldKinds = ["string", "number", "boolean", "null", "object", "array", "unknown"];
1505
+ const sources = ["declared", "inferred", "mixed"];
1506
+ const isFieldKind = (value) => typeof value === "string" && fieldKinds.some((kind) => kind === value);
1507
+ const isSource = (value) => typeof value === "string" && sources.some((source) => source === value);
1508
+ const readJson2 = (text) => {
1509
+ const value = JSON.parse(text);
1510
+ return value;
1511
+ };
1512
+ const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => {
1513
+ switch (char) {
1514
+ case "&":
1515
+ return "&amp;";
1516
+ case "<":
1517
+ return "&lt;";
1518
+ case ">":
1519
+ return "&gt;";
1520
+ case '"':
1521
+ return "&quot;";
1522
+ default:
1523
+ return "&#39;";
1524
+ }
1525
+ });
1526
+ const el = (id, kind) => {
1527
+ const node = document.getElementById(id);
1528
+ if (!(node instanceof kind))
1529
+ throw new Error(`admin shell is missing #${id}`);
1530
+ return node;
1531
+ };
1532
+ const errorMessage = (data, fallback) => {
1533
+ if (!isRecord2(data))
1534
+ return fallback;
1535
+ const error = data.error;
1536
+ if (!isRecord2(error) || typeof error.message !== "string" || error.message === "") {
1537
+ return fallback;
1538
+ }
1539
+ return error.message;
1540
+ };
1541
+ const requireList = (value, label) => {
1542
+ if (value === void 0 || value === null)
1543
+ return [];
1544
+ if (!Array.isArray(value))
1545
+ throw new Error(`${label} was not a list`);
1546
+ return value;
1547
+ };
1548
+ const state = {
1549
+ namespace: "default",
1550
+ key: sessionStorage.getItem("mockingbird-admin-key") || "",
1551
+ view: "overview",
1552
+ collection: "",
1553
+ shape: null,
1554
+ route: null,
1555
+ editing: null
1556
+ };
1557
+ const standard = new Set(config.standardRoutes);
1558
+ const banner = el("banner", HTMLElement);
1559
+ const keyInput = el("admin-key", HTMLInputElement);
1560
+ keyInput.value = state.key;
1561
+ const params = new URLSearchParams(location.search);
1562
+ const queryKey = params.get("key");
1563
+ if (queryKey) {
1564
+ state.key = queryKey;
1565
+ keyInput.value = state.key;
1566
+ sessionStorage.setItem("mockingbird-admin-key", state.key);
1567
+ params.delete("key");
1568
+ const next = params.toString();
1569
+ history.replaceState(null, "", location.pathname + (next ? `?${next}` : ""));
1570
+ }
1571
+ const queryNamespace = params.get("namespace");
1572
+ if (queryNamespace)
1573
+ state.namespace = queryNamespace;
1574
+ const showError = (error) => {
1575
+ banner.textContent = error instanceof Error ? error.message : String(error);
1576
+ banner.classList.add("show");
1577
+ };
1578
+ const clearError = () => {
1579
+ banner.classList.remove("show");
1580
+ };
1581
+ const api = async (method, path, body) => {
1582
+ const headers = { accept: "application/json" };
1583
+ if (state.key !== "")
1584
+ headers[config.adminKeyHeader] = state.key;
1585
+ if (state.namespace !== "")
1586
+ headers["x-mockingbird-namespace"] = state.namespace;
1587
+ if (body !== void 0)
1588
+ headers["content-type"] = "application/json";
1589
+ const init = { method, headers };
1590
+ if (body !== void 0)
1591
+ init.body = JSON.stringify(body);
1592
+ const response = await fetch(config.adminPrefix + path, init);
1593
+ const text = await response.text();
1594
+ let data = null;
1595
+ if (text !== "") {
1596
+ try {
1597
+ data = readJson2(text);
1598
+ } catch {
1599
+ data = { raw: text };
1600
+ }
1601
+ }
1602
+ if (response.status === 401) {
1603
+ keyInput.focus();
1604
+ throw new Error(errorMessage(data, "Admin key required"));
1605
+ }
1606
+ if (!response.ok) {
1607
+ throw new Error(errorMessage(data, `${response.status} ${method} ${path}`));
1608
+ }
1609
+ return data;
1610
+ };
1611
+ const originGet = async (path) => {
1612
+ const response = await fetch(path, { headers: { accept: "application/json" } });
1613
+ if (!response.ok)
1614
+ throw new Error(`${response.status} ${path}`);
1615
+ const value = await response.json();
1616
+ return value;
1617
+ };
1618
+ const panelApi = {
1619
+ get: (path) => api("GET", path),
1620
+ send: (method, path, body) => api(method, path, body),
1621
+ namespace: () => state.namespace
1622
+ };
1623
+ const isPanelScript = (value) => typeof value === "function";
1624
+ const setTheme = (theme) => {
1625
+ if (theme)
1626
+ document.documentElement.dataset.theme = theme;
1627
+ else
1628
+ delete document.documentElement.dataset.theme;
1629
+ };
1630
+ const storedTheme = localStorage.getItem("mockingbird-admin-theme");
1631
+ if (storedTheme)
1632
+ setTheme(storedTheme);
1633
+ el("theme", HTMLButtonElement).addEventListener("click", () => {
1634
+ const current = document.documentElement.dataset.theme;
1635
+ const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
1636
+ const next = dark ? "light" : "dark";
1637
+ localStorage.setItem("mockingbird-admin-theme", next);
1638
+ setTheme(next);
1639
+ });
1640
+ const show = (view) => {
1641
+ state.view = view;
1642
+ for (const button of document.querySelectorAll("#nav button")) {
1643
+ if (!(button instanceof HTMLButtonElement))
1644
+ continue;
1645
+ button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false");
1646
+ }
1647
+ for (const section of document.querySelectorAll("main .view")) {
1648
+ if (!(section instanceof HTMLElement))
1649
+ continue;
1650
+ section.hidden = section.id !== `view-${view}`;
1651
+ }
1652
+ };
1653
+ el("nav", HTMLElement).addEventListener("click", (event) => {
1654
+ const target = event.target;
1655
+ if (!(target instanceof Element))
1656
+ return;
1657
+ const button = target.closest("button[data-view]");
1658
+ if (!(button instanceof HTMLButtonElement))
1659
+ return;
1660
+ const view = button.dataset.view;
1661
+ if (view === void 0)
1662
+ return;
1663
+ show(view);
1664
+ refresh().catch(showError);
1665
+ });
1666
+ const cards = (items) => `<div class="grid">${items.map((item) => `<article class="card"><div class="k">${item.k}</div><div class="v">${item.v}</div></article>`).join("")}</div>`;
1667
+ const table = (columns, rows) => {
1668
+ if (rows.length === 0)
1669
+ return '<p class="empty">Nothing here yet.</p>';
1670
+ const head = `<tr>${columns.map((column) => `<th>${column.label}</th>`).join("")}</tr>`;
1671
+ const body = rows.map((row) => {
1672
+ const cells = columns.map((column) => `<td data-label="${column.label}">${column.cell(row)}</td>`).join("");
1673
+ const attrs = "attrs" in row && typeof row.attrs === "string" ? row.attrs : "";
1674
+ return `<tr${attrs}>${cells}</tr>`;
1675
+ }).join("");
1676
+ return `<table><thead>${head}</thead><tbody>${body}</tbody></table>`;
1677
+ };
1678
+ const preview = (value) => {
1679
+ const text = JSON.stringify(value) ?? "null";
1680
+ return esc(text.length > 180 ? `${text.slice(0, 180)}\u2026` : text);
1681
+ };
1682
+ const when = (epoch) => {
1683
+ const date = new Date(epoch);
1684
+ return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString();
1685
+ };
1686
+ const clockCard = (epoch) => {
1687
+ const date = new Date(epoch);
1688
+ if (Number.isNaN(date.getTime()))
1689
+ return esc(String(epoch));
1690
+ const iso = date.toISOString();
1691
+ return `${esc(iso.slice(0, 10))}<span class="clock-time">${esc(iso.slice(11, 19))} UTC</span>`;
1692
+ };
1693
+ const decodeClock = (value) => {
1694
+ if (!isRecord2(value) || typeof value.now !== "number" || typeof value.offsetMs !== "number" || typeof value.frozen !== "boolean") {
1695
+ throw new Error("Clock response was not a clock");
1696
+ }
1697
+ return { now: value.now, offsetMs: value.offsetMs, frozen: value.frozen };
1698
+ };
1699
+ const decodeField = (value) => {
1700
+ if (!isRecord2(value) || typeof value.name !== "string" || !isFieldKind(value.kind)) {
1701
+ return void 0;
1702
+ }
1703
+ const field = {
1704
+ name: value.name,
1705
+ kind: value.kind,
1706
+ optional: value.optional === true
1707
+ };
1708
+ if (typeof value.description === "string")
1709
+ field.description = value.description;
1710
+ if (Array.isArray(value.fields)) {
1711
+ field.fields = value.fields.flatMap((item) => {
1712
+ const child = decodeField(item);
1713
+ return child === void 0 ? [] : [child];
1714
+ });
1715
+ }
1716
+ return field;
1717
+ };
1718
+ const decodeCollection = (value) => {
1719
+ if (!isRecord2(value) || typeof value.name !== "string")
1720
+ return void 0;
1721
+ const fields = Array.isArray(value.fields) ? value.fields.flatMap((item) => {
1722
+ const field = decodeField(item);
1723
+ return field === void 0 ? [] : [field];
1724
+ }) : [];
1725
+ const collection = {
1726
+ name: value.name,
1727
+ label: typeof value.label === "string" ? value.label : value.name,
1728
+ count: typeof value.count === "number" ? value.count : 0,
1729
+ source: isSource(value.source) ? value.source : "inferred",
1730
+ fields
1731
+ };
1732
+ if (typeof value.description === "string")
1733
+ collection.description = value.description;
1734
+ return collection;
1735
+ };
1736
+ const decodeState = (value) => {
1737
+ if (!isRecord2(value) || !Array.isArray(value.collections)) {
1738
+ throw new Error("State response has no collection list");
1739
+ }
1740
+ return {
1741
+ namespace: typeof value.namespace === "string" ? value.namespace : state.namespace,
1742
+ storageNamespace: typeof value.storageNamespace === "string" ? value.storageNamespace : "",
1743
+ collections: value.collections.flatMap((item) => {
1744
+ const collection = decodeCollection(item);
1745
+ return collection === void 0 ? [] : [collection];
1746
+ })
1747
+ };
1748
+ };
1749
+ const decodeRecords = (value) => requireList(isRecord2(value) ? value.records : void 0, "Records").flatMap((item) => {
1750
+ if (!isRecord2(item) || typeof item.id !== "string")
1751
+ return [];
1752
+ return [
1753
+ {
1754
+ id: item.id,
1755
+ seq: typeof item.seq === "number" ? item.seq : 0,
1756
+ value: item.value,
1757
+ attrs: ` class="clickable" data-id="${esc(item.id)}"`
1758
+ }
1759
+ ];
1760
+ });
1761
+ const textOf = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : "";
1762
+ const decodeJournal = (value) => requireList(isRecord2(value) ? value.requests : void 0, "Journal").flatMap((item) => {
1763
+ if (!isRecord2(item))
1764
+ return [];
1765
+ return [
1766
+ {
1767
+ at: textOf(item.at),
1768
+ method: textOf(item.method),
1769
+ path: textOf(item.path),
1770
+ status: textOf(item.status),
1771
+ durationMs: textOf(item.durationMs),
1772
+ operationId: textOf(item.operationId)
1773
+ }
1774
+ ];
1775
+ });
1776
+ const decodeFaults = (value) => requireList(isRecord2(value) ? value.faults : void 0, "Faults").flatMap((item) => {
1777
+ if (!isRecord2(item))
1778
+ return [];
1779
+ return [
1780
+ {
1781
+ id: textOf(item.id),
1782
+ method: typeof item.method === "string" ? item.method : void 0,
1783
+ pathPrefix: typeof item.pathPrefix === "string" ? item.pathPrefix : void 0,
1784
+ operationId: typeof item.operationId === "string" ? item.operationId : void 0,
1785
+ status: typeof item.status === "number" ? item.status : void 0,
1786
+ effect: typeof item.effect === "string" ? item.effect : void 0,
1787
+ drop: typeof item.drop === "boolean" ? item.drop : void 0,
1788
+ latencyMs: typeof item.latencyMs === "number" ? item.latencyMs : void 0
1789
+ }
1790
+ ];
1791
+ });
1792
+ const loadNamespaces = async () => {
1793
+ const data = await api("GET", "/namespaces");
1794
+ const select = el("namespace", HTMLSelectElement);
1795
+ const listed = requireList(isRecord2(data) ? data.namespaces : void 0, "Namespaces");
1796
+ const fallback = isRecord2(data) && typeof data.default === "string" && data.default !== "" ? data.default : "default";
1797
+ const names = (listed.length > 0 ? listed.map((name) => String(name)) : [fallback]).slice();
1798
+ if (!names.includes(state.namespace))
1799
+ names.unshift(state.namespace);
1800
+ select.innerHTML = names.map((name) => `<option value="${esc(name)}">${esc(name)}</option>`).join("");
1801
+ select.value = state.namespace;
1802
+ };
1803
+ const renderOverview = async () => {
1804
+ const [health, shape, clock, journal] = await Promise.all([
1805
+ originGet("/health"),
1806
+ api("GET", "/state"),
1807
+ api("GET", "/clock"),
1808
+ api("GET", "/requests?limit=8")
1809
+ ]);
1810
+ if (!isRecord2(health))
1811
+ throw new Error("Health response was not an object");
1812
+ const decoded = decodeState(shape);
1813
+ state.shape = decoded;
1814
+ const time = decodeClock(clock);
1815
+ el("overview-cards", HTMLElement).innerHTML = cards([
1816
+ {
1817
+ k: "Service",
1818
+ v: esc(typeof health.service === "string" && health.service !== "" ? health.service : config.service)
1819
+ },
1820
+ {
1821
+ k: "Status",
1822
+ v: esc(typeof health.status === "string" && health.status !== "" ? health.status : "ok")
1823
+ },
1824
+ { k: "Namespace", v: esc(state.namespace) },
1825
+ { k: "Collections", v: String(decoded.collections.length) },
1826
+ {
1827
+ k: "Records",
1828
+ v: String(decoded.collections.reduce((sum, item) => sum + item.count, 0))
1829
+ },
1830
+ { k: "Clock", v: clockCard(time.now) }
1831
+ ]);
1832
+ el("overview-requests", HTMLElement).innerHTML = table([
1833
+ { label: "Method", cell: (row) => esc(row.method) },
1834
+ { label: "Path", cell: (row) => esc(row.path) },
1835
+ {
1836
+ label: "Status",
1837
+ cell: (row) => `<span class="status-${row.status.charAt(0)}">${esc(row.status)}</span>`
1838
+ },
1839
+ { label: "Operation", cell: (row) => esc(row.operationId) }
1840
+ ], decodeJournal(journal));
1841
+ };
1842
+ const schemaChips = (collection) => {
1843
+ if (collection.fields.length === 0) {
1844
+ return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>';
1845
+ }
1846
+ return `<div class="chips">${collection.fields.map((field) => `<span class="chip">${esc(field.name)} <i>${esc(field.kind)}${field.optional ? "?" : ""}</i></span>`).join("")}</div>`;
1847
+ };
1848
+ const renderState = async () => {
1849
+ const shape = decodeState(await api("GET", "/state"));
1850
+ state.shape = shape;
1851
+ const host = el("collections", HTMLElement);
1852
+ if (shape.collections.length === 0) {
1853
+ host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>';
1854
+ el("schema", HTMLElement).innerHTML = "";
1855
+ el("records", HTMLElement).innerHTML = "";
1856
+ return;
1857
+ }
1858
+ if (!shape.collections.some((item) => item.name === state.collection)) {
1859
+ const first = shape.collections[0];
1860
+ if (first === void 0)
1861
+ return;
1862
+ state.collection = first.name;
1863
+ }
1864
+ host.innerHTML = shape.collections.map((item) => `<button type="button" class="collection" data-collection="${esc(item.name)}" aria-current="${item.name === state.collection ? "true" : "false"}"><strong>${esc(item.label)}</strong><small>${esc(item.count)} \xB7 ${esc(item.source)}</small></button>`).join("");
1865
+ const selected = shape.collections.find((item) => item.name === state.collection);
1866
+ if (selected === void 0)
1867
+ return;
1868
+ const description = selected.description ? `<p class="lede">${esc(selected.description)}</p>` : "";
1869
+ el("schema", HTMLElement).innerHTML = `<h3>${esc(selected.label)}</h3>${description}${schemaChips(selected)}`;
1870
+ const page = await api("GET", `/state/${encodeURIComponent(state.collection)}?limit=50`);
1871
+ el("records", HTMLElement).innerHTML = table([
1872
+ { label: "Id", cell: (row) => `<code>${esc(row.id)}</code>` },
1873
+ { label: "Seq", cell: (row) => esc(row.seq) },
1874
+ { label: "Value", cell: (row) => preview(row.value) }
1875
+ ], decodeRecords(page));
1876
+ };
1877
+ const renderClock = async () => {
1878
+ const clock = decodeClock(await api("GET", "/clock"));
1879
+ el("clock-readout", HTMLElement).innerHTML = `<div class="k">Now</div><div class="v">${esc(when(clock.now))}</div><p>${esc(clock.now)} ms \xB7 offset ${esc(clock.offsetMs)} ms ${clock.frozen ? "<span class='badge warn'>frozen</span>" : "<span class='badge ok'>live</span>"}</p>`;
1880
+ const freeze = el("freeze", HTMLButtonElement);
1881
+ freeze.textContent = clock.frozen ? "Unfreeze" : "Freeze";
1882
+ freeze.dataset.frozen = clock.frozen ? "1" : "0";
1883
+ };
1884
+ const renderFaults = async () => {
1885
+ const [faults, presets] = await Promise.all([
1886
+ api("GET", "/faults"),
1887
+ api("GET", "/faults/presets").catch(() => ({ presets: [] }))
1888
+ ]);
1889
+ const select = el("fault-preset", HTMLSelectElement);
1890
+ const current = select.value;
1891
+ const list = faultPresetList(presets).presets;
1892
+ select.innerHTML = `<option value="">Custom rule</option>${list.map((preset) => `<option value="${esc(preset.name)}">${esc(preset.name)}</option>`).join("")}`;
1893
+ select.value = current;
1894
+ el("fault-list", HTMLElement).innerHTML = table([
1895
+ { label: "Id", cell: (row) => esc(row.id) },
1896
+ {
1897
+ label: "Match",
1898
+ cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter((part) => Boolean(part)).join(" ") || "all")
1899
+ },
1900
+ {
1901
+ label: "Effect",
1902
+ cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || ""))
1903
+ }
1904
+ ], decodeFaults(faults));
1905
+ };
1906
+ const renderJournal = async () => {
1907
+ const journal = await api("GET", "/requests?limit=100");
1908
+ el("journal", HTMLElement).innerHTML = table([
1909
+ { label: "When", cell: (row) => esc(row.at) },
1910
+ { label: "Method", cell: (row) => esc(row.method) },
1911
+ { label: "Path", cell: (row) => esc(row.path) },
1912
+ { label: "Status", cell: (row) => esc(row.status) },
1913
+ { label: "ms", cell: (row) => esc(row.durationMs) }
1914
+ ], decodeJournal(journal));
1915
+ };
1916
+ const renderRoutes = async () => {
1917
+ const data = await api("GET", "/");
1918
+ if (!isRecord2(data))
1919
+ throw new Error("Route list was not an object");
1920
+ const routes = requireList(data.routes, "Routes").map((route) => String(route));
1921
+ el("route-list", HTMLElement).innerHTML = routes.map((route) => {
1922
+ const extra = standard.has(route) ? "" : " <span class='badge'>extension</span>";
1923
+ const parts = route.split(" ");
1924
+ const method = parts[0] ?? "";
1925
+ const path = parts.slice(1).join(" ");
1926
+ return `<div class="route"><span class="method">${esc(method)}</span><span>${esc(path)}${extra}</span><button class="btn" type="button" data-route="${esc(route)}">Use</button></div>`;
1927
+ }).join("");
1928
+ };
1929
+ const renderers = {
1930
+ overview: renderOverview,
1931
+ state: renderState,
1932
+ clock: renderClock,
1933
+ faults: renderFaults,
1934
+ journal: renderJournal,
1935
+ routes: renderRoutes
1936
+ };
1937
+ async function refresh() {
1938
+ clearError();
1939
+ const render = renderers[state.view];
1940
+ if (render)
1941
+ await render();
1942
+ }
1943
+ el("namespace", HTMLSelectElement).addEventListener("change", () => {
1944
+ state.namespace = el("namespace", HTMLSelectElement).value;
1945
+ refresh().catch(showError);
1946
+ });
1947
+ keyInput.addEventListener("change", () => {
1948
+ state.key = keyInput.value.trim();
1949
+ sessionStorage.setItem("mockingbird-admin-key", state.key);
1950
+ refresh().catch(showError);
1951
+ });
1952
+ el("refresh", HTMLButtonElement).addEventListener("click", () => {
1953
+ refresh().catch(showError);
1954
+ });
1955
+ el("collections", HTMLElement).addEventListener("click", (event) => {
1956
+ const target = event.target;
1957
+ if (!(target instanceof Element))
1958
+ return;
1959
+ const button = target.closest("[data-collection]");
1960
+ if (!(button instanceof HTMLElement))
1961
+ return;
1962
+ const collection = button.dataset.collection;
1963
+ if (collection === void 0)
1964
+ return;
1965
+ state.collection = collection;
1966
+ renderState().catch(showError);
1967
+ });
1968
+ el("records", HTMLElement).addEventListener("click", (event) => {
1969
+ const target = event.target;
1970
+ if (!(target instanceof Element))
1971
+ return;
1972
+ const row = target.closest("[data-id]");
1973
+ if (!(row instanceof HTMLElement))
1974
+ return;
1975
+ const id = row.dataset.id;
1976
+ if (id === void 0)
1977
+ return;
1978
+ openEditor(id).catch(showError);
1979
+ });
1980
+ const editor = el("editor", HTMLDialogElement);
1981
+ const editorId = el("editor-id", HTMLInputElement);
1982
+ const editorValue = el("editor-value", HTMLTextAreaElement);
1983
+ const editorDelete = el("editor-delete", HTMLButtonElement);
1984
+ async function openEditor(id) {
1985
+ state.editing = id;
1986
+ el("editor-title", HTMLElement).textContent = id ? "Edit record" : "New record";
1987
+ editorId.value = id ?? "";
1988
+ editorId.readOnly = id !== null;
1989
+ editorDelete.hidden = id === null;
1990
+ if (id) {
1991
+ const record = await api("GET", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`);
1992
+ if (!isRecord2(record))
1993
+ throw new Error("Record response was not an object");
1994
+ editorValue.value = JSON.stringify(record.value, null, 2) ?? "";
1995
+ } else {
1996
+ editorValue.value = "{\n \n}";
1997
+ }
1998
+ editor.showModal();
1999
+ }
2000
+ el("new-record", HTMLButtonElement).addEventListener("click", () => {
2001
+ openEditor(null).catch(showError);
2002
+ });
2003
+ el("editor-form", HTMLFormElement).addEventListener("submit", (event) => {
2004
+ const submitter = event.submitter;
2005
+ if (!(submitter instanceof HTMLButtonElement) || submitter.value !== "save")
2006
+ return;
2007
+ event.preventDefault();
2008
+ let value;
2009
+ try {
2010
+ value = readJson2(editorValue.value);
2011
+ } catch {
2012
+ showError(new Error("Value is not JSON"));
2013
+ return;
2014
+ }
2015
+ const id = editorId.value.trim();
2016
+ const path = `/state/${encodeURIComponent(state.collection)}`;
2017
+ const request = state.editing ? api("PUT", `${path}/${encodeURIComponent(id)}`, { value }) : api("POST", path, { id: id || void 0, value });
2018
+ request.then(() => {
2019
+ editor.close();
2020
+ return renderState();
2021
+ }).catch(showError);
2022
+ });
2023
+ editorDelete.addEventListener("click", () => {
2024
+ const id = state.editing;
2025
+ if (!id)
2026
+ return;
2027
+ api("DELETE", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`).then(() => {
2028
+ editor.close();
2029
+ return renderState();
2030
+ }).catch(showError);
2031
+ });
2032
+ for (const button of document.querySelectorAll("[data-advance]")) {
2033
+ if (!(button instanceof HTMLButtonElement))
2034
+ continue;
2035
+ button.addEventListener("click", () => {
2036
+ const raw = button.dataset.advance;
2037
+ if (raw === void 0)
2038
+ return;
2039
+ api("POST", "/clock", { advance: Number(raw) }).then(() => renderClock()).catch(showError);
2040
+ });
2041
+ }
2042
+ el("freeze", HTMLButtonElement).addEventListener("click", () => {
2043
+ const frozen = el("freeze", HTMLButtonElement).dataset.frozen === "1";
2044
+ api("POST", "/clock", { freeze: !frozen }).then(() => renderClock()).catch(showError);
2045
+ });
2046
+ el("clock-reset", HTMLButtonElement).addEventListener("click", () => {
2047
+ api("POST", "/clock", { reset: true }).then(() => renderClock()).catch(showError);
2048
+ });
2049
+ el("clock-set", HTMLFormElement).addEventListener("submit", (event) => {
2050
+ event.preventDefault();
2051
+ const raw = el("clock-instant", HTMLInputElement).value.trim();
2052
+ const set = /^-?\d+$/.test(raw) ? Number(raw) : raw;
2053
+ api("POST", "/clock", { set }).then(() => renderClock()).catch(showError);
2054
+ });
2055
+ el("fault-form", HTMLFormElement).addEventListener("submit", (event) => {
2056
+ event.preventDefault();
2057
+ const preset = el("fault-preset", HTMLSelectElement).value;
2058
+ if (preset !== "") {
2059
+ api("POST", "/faults", { preset }).then(() => renderFaults()).catch(showError);
2060
+ return;
2061
+ }
2062
+ const status = Number(el("fault-status", HTMLInputElement).value);
2063
+ if (!Number.isFinite(status)) {
2064
+ showError(new Error("Status must be a number, or choose a preset"));
2065
+ return;
2066
+ }
2067
+ const path = el("fault-path", HTMLInputElement).value;
2068
+ const method = el("fault-method", HTMLInputElement).value;
2069
+ api("POST", "/faults", {
2070
+ status,
2071
+ ...path !== "" ? { pathPrefix: path } : {},
2072
+ ...method !== "" ? { method } : {}
2073
+ }).then(() => renderFaults()).catch(showError);
2074
+ });
2075
+ el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
2076
+ api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
2077
+ });
2078
+ el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
2079
+ api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
2080
+ });
2081
+ el("route-list", HTMLElement).addEventListener("click", (event) => {
2082
+ const target = event.target;
2083
+ if (!(target instanceof Element))
2084
+ return;
2085
+ const button = target.closest("[data-route]");
2086
+ if (!(button instanceof HTMLElement))
2087
+ return;
2088
+ const route = button.dataset.route;
2089
+ if (route === void 0)
2090
+ return;
2091
+ state.route = route;
2092
+ el("route-title", HTMLElement).textContent = route;
2093
+ const method = route.split(" ")[0] ?? "";
2094
+ el("route-body", HTMLTextAreaElement).disabled = method === "GET" || method === "DELETE";
2095
+ });
2096
+ el("route-form", HTMLFormElement).addEventListener("submit", (event) => {
2097
+ event.preventDefault();
2098
+ const send = async () => {
2099
+ if (state.route === null)
2100
+ throw new Error("Choose a route first");
2101
+ const parts = state.route.split(" ");
2102
+ const method = parts[0];
2103
+ const path = parts.slice(1).join(" ");
2104
+ if (method === void 0)
2105
+ throw new Error("Choose a route first");
2106
+ const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_match, key) => prompt(`Value for ${key}`) || "");
2107
+ let body;
2108
+ const routeBody = el("route-body", HTMLTextAreaElement);
2109
+ if (method !== "GET" && method !== "DELETE" && routeBody.value.trim() !== "") {
2110
+ try {
2111
+ body = readJson2(routeBody.value);
2112
+ } catch {
2113
+ throw new Error("Body is not JSON");
2114
+ }
2115
+ }
2116
+ const data = await api(method, concrete, body);
2117
+ el("route-result", HTMLElement).textContent = JSON.stringify(data, null, 2);
2118
+ };
2119
+ send().catch(showError);
2120
+ });
2121
+ const decodePanel = (value) => {
2122
+ if (!isRecord2(value) || typeof value.id !== "string" || typeof value.title !== "string") {
2123
+ return void 0;
2124
+ }
2125
+ const panel = {
2126
+ id: value.id,
2127
+ title: value.title,
2128
+ description: typeof value.description === "string" ? value.description : "",
2129
+ html: typeof value.html === "string" ? value.html : ""
2130
+ };
2131
+ if (typeof value.script === "string")
2132
+ panel.script = value.script;
2133
+ return panel;
2134
+ };
2135
+ const mountPanels = (panels) => {
2136
+ const nav = el("nav", HTMLElement);
2137
+ const views = el("views", HTMLElement);
2138
+ for (const panel of panels) {
2139
+ const button = document.createElement("button");
2140
+ button.type = "button";
2141
+ button.dataset.view = panel.id;
2142
+ button.textContent = panel.title;
2143
+ nav.append(button);
2144
+ const section = document.createElement("section");
2145
+ section.className = "view";
2146
+ section.id = `view-${panel.id}`;
2147
+ section.hidden = true;
2148
+ const title = document.createElement("h2");
2149
+ title.textContent = panel.title;
2150
+ const lede = document.createElement("p");
2151
+ lede.className = "lede";
2152
+ lede.textContent = panel.description;
2153
+ const body = document.createElement("div");
2154
+ body.className = "panel-body";
2155
+ body.id = `panel-${panel.id}`;
2156
+ body.innerHTML = panel.html;
2157
+ section.append(title, lede, body);
2158
+ views.append(section);
2159
+ if (panel.script === void 0)
2160
+ continue;
2161
+ try {
2162
+ const compiled = new Function("root", "api", panel.script);
2163
+ if (!isPanelScript(compiled))
2164
+ throw new Error("panel script did not compile");
2165
+ compiled(body, panelApi);
2166
+ } catch (error) {
2167
+ const note = document.createElement("p");
2168
+ note.className = "banner show";
2169
+ note.textContent = error instanceof Error ? error.message : String(error);
2170
+ body.append(note);
2171
+ }
2172
+ }
2173
+ };
2174
+ startAdminBrand({
2175
+ document,
2176
+ $: (id) => document.getElementById(id),
2177
+ params,
2178
+ location: { href: location.href, origin: location.origin },
2179
+ fetch,
2180
+ brandsUrl: config.brandsUrl
2181
+ });
2182
+ loadNamespaces().then(() => api("GET", "/ui/manifest")).then((manifest) => {
2183
+ const panels = requireList(isRecord2(manifest) ? manifest.panels : void 0, "Panels").flatMap((panel) => {
2184
+ const decoded = decodePanel(panel);
2185
+ return decoded === void 0 ? [] : [decoded];
2186
+ });
2187
+ mountPanels(panels);
2188
+ return refresh();
2189
+ }).catch(showError);
2190
+ }
2191
+ var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
2192
+ var adminClientSource = (config) => {
2193
+ const payload = JSON.stringify(config).replace(/</g, "\\u003c");
2194
+ return `<script>
2195
+ ${clientPrelude}
2196
+ bootAdmin(${payload});
2197
+ </script>`;
2198
+ };
2199
+
974
2200
  // ../core/dist/surface.js
975
2201
  var defineMock = (runtime) => runtime;
976
2202
  var STANDARD_ADMIN_ROUTES = [
@@ -1039,94 +2265,106 @@ var escapeHtml = (value) => value.replace(/[&<>"']/g, (char) => {
1039
2265
  return "&#39;";
1040
2266
  }
1041
2267
  });
2268
+ var ADMIN_BRANDS_URL = "https://mockingbird.chrisvouga.dev/brands.json";
1042
2269
  var renderAdminDocument = (service) => {
1043
2270
  const name = escapeHtml(service);
1044
- const standard = JSON.stringify(STANDARD_ADMIN_ROUTES);
2271
+ const script = adminClientSource({
2272
+ standardRoutes: STANDARD_ADMIN_ROUTES,
2273
+ adminPrefix: ADMIN_PREFIX,
2274
+ adminKeyHeader: ADMIN_KEY_HEADER,
2275
+ brandsUrl: ADMIN_BRANDS_URL,
2276
+ service
2277
+ });
1045
2278
  return `<!DOCTYPE html>
1046
2279
  <html lang="en">
1047
2280
  <head>
1048
2281
  <meta charset="utf-8">
1049
2282
  <meta name="viewport" content="width=device-width, initial-scale=1">
1050
2283
  <meta name="color-scheme" content="light dark">
1051
- <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='3' fill='%233d4f3a'/%3E%3C/svg%3E">
2284
+ <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='2' fill='%2327272a'/%3E%3C/svg%3E">
1052
2285
  <title>${name} admin</title>
1053
2286
  <style>
2287
+ ${CSS_RESET}
1054
2288
  :root {
1055
2289
  color-scheme: light;
1056
- --bg: #f6f5f2;
2290
+ --bg: #fafafa;
1057
2291
  --bg-elev: #ffffff;
1058
- --bg-muted: #eeece6;
1059
- --bg-inset: #f3f1eb;
1060
- --line: #e2dfd6;
1061
- --line-strong: #d0ccc1;
1062
- --ink: #1c1915;
1063
- --muted: #5e584f;
1064
- --faint: #8a8378;
1065
- --accent: #3d4f3a;
1066
- --accent-ink: #f7f6f2;
1067
- --accent-soft: #e5ece2;
1068
- --ok: #1f7a45;
1069
- --ok-soft: #e5f4eb;
1070
- --warn: #9a6700;
1071
- --warn-soft: #fbf3df;
1072
- --err: #a33b32;
1073
- --err-soft: #fdecea;
1074
- --shadow: 0 1px 2px rgb(28 25 21 / 0.05), 0 12px 32px rgb(28 25 21 / 0.06);
1075
- --sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
2292
+ --bg-muted: #f4f4f5;
2293
+ --bg-inset: #f4f4f5;
2294
+ --line: #dedee3;
2295
+ --line-strong: #c4c4cc;
2296
+ --ink: #18181b;
2297
+ --muted: #62626b;
2298
+ --faint: #71717a;
2299
+ --accent: #27272a;
2300
+ --accent-ink: #ffffff;
2301
+ --accent-soft: #f4f4f5;
2302
+ --ok: #166534;
2303
+ --ok-soft: #f0fdf4;
2304
+ --warn: #a16207;
2305
+ --warn-soft: #fefce8;
2306
+ --err: #a82d32;
2307
+ --err-soft: #fef2f2;
2308
+ --shadow: none;
2309
+ --sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
1076
2310
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
1077
- --radius: 14px;
1078
- --header: 60px;
1079
- --nav: 220px;
2311
+ --radius: 10px;
2312
+ --header: 56px;
2313
+ --nav: 200px;
1080
2314
  }
1081
2315
  :root[data-theme="dark"] {
1082
2316
  color-scheme: dark;
1083
- --bg: #141311;
1084
- --bg-elev: #1e1c19;
1085
- --bg-muted: #26231f;
1086
- --bg-inset: #181715;
1087
- --line: #34302b;
1088
- --line-strong: #4a453d;
1089
- --ink: #f4f1ea;
1090
- --muted: #c4bdb1;
1091
- --faint: #8f877c;
1092
- --accent: #c5d5b8;
1093
- --accent-ink: #1a2118;
1094
- --accent-soft: #2a3328;
1095
- --ok: #8fd6a8;
1096
- --ok-soft: #1a2e22;
1097
- --warn: #f0cc78;
1098
- --warn-soft: #332911;
1099
- --err: #f0a8a2;
1100
- --err-soft: #3a201e;
1101
- --shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 16px 40px rgb(0 0 0 / 0.28);
2317
+ --bg: #111113;
2318
+ --bg-elev: #19191c;
2319
+ --bg-muted: #242428;
2320
+ --bg-inset: #111113;
2321
+ --line: #36363c;
2322
+ --line-strong: #52525b;
2323
+ --ink: #f4f4f5;
2324
+ --muted: #a9a9b2;
2325
+ --faint: #71717a;
2326
+ --accent: #e4e4e7;
2327
+ --accent-ink: #18181b;
2328
+ --accent-soft: #242428;
2329
+ --ok: #86efac;
2330
+ --ok-soft: #14241b;
2331
+ --warn: #fde68a;
2332
+ --warn-soft: #2a2410;
2333
+ --err: #ffaaaa;
2334
+ --err-soft: #2c1516;
2335
+ --shadow: none;
1102
2336
  }
1103
2337
  @media (prefers-color-scheme: dark) {
1104
2338
  :root:not([data-theme="light"]) {
1105
2339
  color-scheme: dark;
1106
- --bg: #141311;
1107
- --bg-elev: #1e1c19;
1108
- --bg-muted: #26231f;
1109
- --bg-inset: #181715;
1110
- --line: #34302b;
1111
- --line-strong: #4a453d;
1112
- --ink: #f4f1ea;
1113
- --muted: #c4bdb1;
1114
- --faint: #8f877c;
1115
- --accent: #c5d5b8;
1116
- --accent-ink: #1a2118;
1117
- --accent-soft: #2a3328;
1118
- --ok: #8fd6a8;
1119
- --ok-soft: #1a2e22;
1120
- --warn: #f0cc78;
1121
- --warn-soft: #332911;
1122
- --err: #f0a8a2;
1123
- --err-soft: #3a201e;
1124
- --shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 16px 40px rgb(0 0 0 / 0.28);
1125
- }
1126
- }
1127
- * { box-sizing: border-box; }
1128
- html, body { margin: 0; height: 100%; }
2340
+ --bg: #111113;
2341
+ --bg-elev: #19191c;
2342
+ --bg-muted: #242428;
2343
+ --bg-inset: #111113;
2344
+ --line: #36363c;
2345
+ --line-strong: #52525b;
2346
+ --ink: #f4f4f5;
2347
+ --muted: #a9a9b2;
2348
+ --faint: #71717a;
2349
+ --accent: #e4e4e7;
2350
+ --accent-ink: #18181b;
2351
+ --accent-soft: #242428;
2352
+ --ok: #86efac;
2353
+ --ok-soft: #14241b;
2354
+ --warn: #fde68a;
2355
+ --warn-soft: #2a2410;
2356
+ --err: #ffaaaa;
2357
+ --err-soft: #2c1516;
2358
+ --shadow: none;
2359
+ }
2360
+ }
2361
+ /* Fill the viewport, then grow with the content so the background covers the scroll. */
2362
+ html { height: 100%; }
1129
2363
  body {
2364
+ margin: 0;
2365
+ min-height: 100%;
2366
+ display: flex;
2367
+ flex-direction: column;
1130
2368
  font-family: var(--sans);
1131
2369
  background: var(--bg);
1132
2370
  color: var(--ink);
@@ -1135,24 +2373,37 @@ var renderAdminDocument = (service) => {
1135
2373
  padding-right: env(safe-area-inset-right);
1136
2374
  -webkit-text-size-adjust: 100%;
1137
2375
  }
1138
- button, input, select, textarea { font: inherit; color: inherit; }
1139
- button { cursor: pointer; }
1140
2376
  :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
1141
- @media (prefers-reduced-motion: reduce) {
1142
- * { scroll-behavior: auto !important; transition: none !important; }
1143
- }
1144
- .app { min-height: 100%; display: flex; flex-direction: column; }
2377
+ .app { flex: 1 0 auto; display: flex; flex-direction: column; min-width: 0; width: 100%; }
1145
2378
  header.top {
1146
2379
  position: sticky; top: 0; z-index: 5;
1147
2380
  display: flex; align-items: center; gap: 12px;
1148
2381
  min-height: var(--header); padding: 10px 16px;
1149
- background: color-mix(in srgb, var(--bg-elev) 92%, transparent);
2382
+ background: var(--bg-elev);
1150
2383
  border-bottom: 1px solid var(--line);
1151
- backdrop-filter: blur(10px);
1152
- }
1153
- .brand { display: flex; flex-direction: column; min-width: 0; margin-right: auto; }
1154
- .brand strong { font-size: 15px; letter-spacing: -0.01em; }
1155
- .brand span { color: var(--faint); font-size: 12px; }
2384
+ min-width: 0; max-width: 100%;
2385
+ }
2386
+ .brand {
2387
+ display: flex; flex-flow: row wrap; align-items: center; gap: 14px;
2388
+ min-width: 0; max-width: 100%; margin-right: auto;
2389
+ }
2390
+ .brand-id { display: flex; flex-direction: column; min-width: 0; flex: none; }
2391
+ .brand-id strong { font-size: 15px; letter-spacing: -0.01em; }
2392
+ .brand-id span { color: var(--faint); font-size: 12px; }
2393
+ .vendor {
2394
+ display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
2395
+ min-width: 0; max-width: 100%; font-size: 12px; color: var(--muted);
2396
+ }
2397
+ .vendor[hidden] { display: none; }
2398
+ /* Logos are drawn for a light tile, same as the docs site, so the plate stays white. */
2399
+ .vendor img {
2400
+ width: 16px; height: 16px; padding: 2px; box-sizing: content-box;
2401
+ border-radius: 4px; border: 1px solid var(--line); background: #fff; object-fit: contain;
2402
+ }
2403
+ .vendor-name { color: var(--ink); font-weight: 600; }
2404
+ .vendor-sep { color: var(--faint); }
2405
+ .vendor-link { color: inherit; text-decoration: none; }
2406
+ .vendor-link:hover { color: var(--ink); text-decoration: underline; }
1156
2407
  .controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
1157
2408
  label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
1158
2409
  label.field input, label.field select, .controls input[type="password"] {
@@ -1170,7 +2421,7 @@ var renderAdminDocument = (service) => {
1170
2421
  record or the full nav row would widen the page past the viewport. */
1171
2422
  .layout {
1172
2423
  display: grid; grid-template-columns: var(--nav) minmax(0, 1fr);
1173
- flex: 1; min-height: 0; min-width: 0;
2424
+ flex: 1 0 auto; min-width: 0;
1174
2425
  }
1175
2426
  nav.side {
1176
2427
  border-right: 1px solid var(--line); padding: 12px;
@@ -1224,8 +2475,8 @@ var renderAdminDocument = (service) => {
1224
2475
  .banner.show { display: block; }
1225
2476
  .empty { color: var(--muted); padding: 24px 8px; }
1226
2477
  dialog {
1227
- border: 1px solid var(--line); border-radius: 16px; padding: 0; background: var(--bg-elev);
1228
- color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow: var(--shadow);
2478
+ border: 1px solid var(--line); border-radius: var(--radius); padding: 0; background: var(--bg-elev);
2479
+ color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow: none;
1229
2480
  }
1230
2481
  dialog::backdrop { background: rgb(20 19 17 / 0.45); }
1231
2482
  dialog form, .dialog-body { padding: 16px; display: flex; flex-direction: column; gap: 10px; }
@@ -1252,7 +2503,7 @@ var renderAdminDocument = (service) => {
1252
2503
  padding-top: max(10px, env(safe-area-inset-top));
1253
2504
  }
1254
2505
  input, select, textarea { font-size: 16px; }
1255
- .controls { width: 100%; }
2506
+ .brand, .vendor, .controls { width: 100%; }
1256
2507
  label.field, label.field select, label.field input { flex: 1; min-width: 0; }
1257
2508
  .layout { grid-template-columns: minmax(0, 1fr); }
1258
2509
  nav.side {
@@ -1283,8 +2534,11 @@ var renderAdminDocument = (service) => {
1283
2534
  <div class="app" data-service="${name}" data-mockingbird-admin>
1284
2535
  <header class="top">
1285
2536
  <div class="brand">
1286
- <strong>${name}</strong>
1287
- <span>Mockingbird admin</span>
2537
+ <div class="brand-id">
2538
+ <strong>${name}</strong>
2539
+ <span>Admin</span>
2540
+ </div>
2541
+ <div class="vendor" id="vendor" hidden></div>
1288
2542
  </div>
1289
2543
  <div class="controls">
1290
2544
  <label class="field">Namespace
@@ -1414,460 +2668,7 @@ var renderAdminDocument = (service) => {
1414
2668
  </div>
1415
2669
  </form>
1416
2670
  </dialog>
1417
- <script>
1418
- (() => {
1419
- const STANDARD = new Set(${standard})
1420
- const state = {
1421
- namespace: "default",
1422
- key: sessionStorage.getItem("mockingbird-admin-key") || "",
1423
- view: "overview",
1424
- collection: "",
1425
- shape: null,
1426
- route: null,
1427
- editing: null
1428
- }
1429
- const $ = (id) => document.getElementById(id)
1430
- const banner = $("banner")
1431
- const keyInput = $("admin-key")
1432
- keyInput.value = state.key
1433
- const params = new URLSearchParams(location.search)
1434
- if (params.get("key")) {
1435
- state.key = params.get("key")
1436
- keyInput.value = state.key
1437
- sessionStorage.setItem("mockingbird-admin-key", state.key)
1438
- params.delete("key")
1439
- const next = params.toString()
1440
- history.replaceState(null, "", location.pathname + (next ? "?" + next : ""))
1441
- }
1442
- if (params.get("namespace")) state.namespace = params.get("namespace")
1443
-
1444
- const showError = (error) => {
1445
- banner.textContent = error instanceof Error ? error.message : String(error)
1446
- banner.classList.add("show")
1447
- }
1448
- const clearError = () => banner.classList.remove("show")
1449
-
1450
- async function originGet(path) {
1451
- const response = await fetch(path, { headers: { accept: "application/json" } })
1452
- if (!response.ok) throw new Error(response.status + " " + path)
1453
- return response.json()
1454
- }
1455
- async function api(method, path, body) {
1456
- const headers = { accept: "application/json" }
1457
- if (state.key) headers["${ADMIN_KEY_HEADER}"] = state.key
1458
- if (state.namespace) headers["x-mockingbird-namespace"] = state.namespace
1459
- if (body !== undefined) headers["content-type"] = "application/json"
1460
- const response = await fetch("${ADMIN_PREFIX}" + path, {
1461
- method,
1462
- headers,
1463
- body: body === undefined ? undefined : JSON.stringify(body)
1464
- })
1465
- const text = await response.text()
1466
- let data = null
1467
- if (text) {
1468
- try { data = JSON.parse(text) } catch { data = { raw: text } }
1469
- }
1470
- if (response.status === 401) {
1471
- keyInput.focus()
1472
- throw new Error((data && data.error && data.error.message) || "Admin key required")
1473
- }
1474
- if (!response.ok) {
1475
- throw new Error((data && data.error && data.error.message) || (response.status + " " + method + " " + path))
1476
- }
1477
- return data
1478
- }
1479
- const panelApi = {
1480
- get: (path) => api("GET", path),
1481
- send: (method, path, body) => api(method, path, body),
1482
- namespace: () => state.namespace
1483
- }
1484
-
1485
- function setTheme(theme) {
1486
- if (theme) document.documentElement.dataset.theme = theme
1487
- else delete document.documentElement.dataset.theme
1488
- }
1489
- const storedTheme = localStorage.getItem("mockingbird-admin-theme")
1490
- if (storedTheme) setTheme(storedTheme)
1491
- $("theme").addEventListener("click", () => {
1492
- const current = document.documentElement.dataset.theme
1493
- const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches
1494
- const next = dark ? "light" : "dark"
1495
- localStorage.setItem("mockingbird-admin-theme", next)
1496
- setTheme(next)
1497
- })
1498
-
1499
- function show(view) {
1500
- state.view = view
1501
- for (const button of document.querySelectorAll("#nav button")) {
1502
- button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false")
1503
- }
1504
- for (const section of document.querySelectorAll("main .view")) {
1505
- section.hidden = section.id !== "view-" + view
1506
- }
1507
- }
1508
- document.getElementById("nav").addEventListener("click", (event) => {
1509
- const button = event.target.closest("button[data-view]")
1510
- if (!button) return
1511
- show(button.dataset.view)
1512
- refresh().catch(showError)
1513
- })
1514
-
1515
- function cards(items) {
1516
- return '<div class="grid">' + items.map((item) =>
1517
- '<article class="card"><div class="k">' + item.k + '</div><div class="v">' + item.v + '</div></article>'
1518
- ).join("") + "</div>"
1519
- }
1520
- function table(columns, rows) {
1521
- if (rows.length === 0) return '<p class="empty">Nothing here yet.</p>'
1522
- const head = "<tr>" + columns.map((column) => "<th>" + column.label + "</th>").join("") + "</tr>"
1523
- const body = rows.map((row) => "<tr" + (row.attrs || "") + ">" + columns.map((column) =>
1524
- '<td data-label="' + column.label + '">' + column.cell(row) + "</td>"
1525
- ).join("") + "</tr>").join("")
1526
- return "<table><thead>" + head + "</thead><tbody>" + body + "</tbody></table>"
1527
- }
1528
- const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({
1529
- "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"
1530
- }[char]))
1531
- const preview = (value) => {
1532
- const text = JSON.stringify(value)
1533
- return esc(text.length > 180 ? text.slice(0, 180) + "\u2026" : text)
1534
- }
1535
- const when = (epoch) => {
1536
- const date = new Date(epoch)
1537
- return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString()
1538
- }
1539
- const clockCard = (epoch) => {
1540
- const date = new Date(epoch)
1541
- if (Number.isNaN(date.getTime())) return esc(String(epoch))
1542
- const iso = date.toISOString()
1543
- return esc(iso.slice(0, 10)) + '<span class="clock-time">' + esc(iso.slice(11, 19)) + " UTC</span>"
1544
- }
1545
-
1546
- async function loadNamespaces() {
1547
- const data = await api("GET", "/namespaces")
1548
- const select = $("namespace")
1549
- const names = data.namespaces && data.namespaces.length ? data.namespaces : [data.default || "default"]
1550
- if (!names.includes(state.namespace)) names.unshift(state.namespace)
1551
- select.innerHTML = names.map((name) =>
1552
- '<option value="' + esc(name) + '">' + esc(name) + "</option>"
1553
- ).join("")
1554
- select.value = state.namespace
1555
- }
1556
-
1557
- async function renderOverview() {
1558
- const [health, shape, clock, journal] = await Promise.all([
1559
- originGet("/health"),
1560
- api("GET", "/state"),
1561
- api("GET", "/clock"),
1562
- api("GET", "/requests?limit=8")
1563
- ])
1564
- state.shape = shape
1565
- $("overview-cards").innerHTML = cards([
1566
- { k: "Service", v: esc(health.service || "${name}") },
1567
- { k: "Status", v: esc(health.status || "ok") },
1568
- { k: "Namespace", v: esc(state.namespace) },
1569
- { k: "Collections", v: String(shape.collections.length) },
1570
- { k: "Records", v: String(shape.collections.reduce((sum, item) => sum + item.count, 0)) },
1571
- { k: "Clock", v: clockCard(clock.now) }
1572
- ])
1573
- const requests = (journal && journal.requests) || []
1574
- $("overview-requests").innerHTML = table(
1575
- [
1576
- { label: "Method", cell: (row) => esc(row.method) },
1577
- { label: "Path", cell: (row) => esc(row.path) },
1578
- { label: "Status", cell: (row) => '<span class="status-' + String(row.status).charAt(0) + '">' + esc(row.status) + "</span>" },
1579
- { label: "Operation", cell: (row) => esc(row.operationId || "") }
1580
- ],
1581
- requests
1582
- )
1583
- }
1584
-
1585
- function schemaChips(collection) {
1586
- if (!collection || collection.fields.length === 0) {
1587
- return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>'
1588
- }
1589
- return '<div class="chips">' + collection.fields.map((field) =>
1590
- '<span class="chip">' + esc(field.name) + ' <i>' + esc(field.kind) + (field.optional ? "?" : "") + "</i></span>"
1591
- ).join("") + "</div>"
1592
- }
1593
-
1594
- async function renderState() {
1595
- const shape = await api("GET", "/state")
1596
- state.shape = shape
1597
- const host = $("collections")
1598
- if (shape.collections.length === 0) {
1599
- host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>'
1600
- $("schema").innerHTML = ""
1601
- $("records").innerHTML = ""
1602
- return
1603
- }
1604
- if (!shape.collections.some((item) => item.name === state.collection)) {
1605
- state.collection = shape.collections[0].name
1606
- }
1607
- host.innerHTML = shape.collections.map((item) =>
1608
- '<button type="button" class="collection" data-collection="' + esc(item.name) + '" aria-current="' +
1609
- (item.name === state.collection ? "true" : "false") + '"><strong>' + esc(item.label) +
1610
- '</strong><small>' + esc(item.count) + " \xB7 " + esc(item.source) + "</small></button>"
1611
- ).join("")
1612
- const selected = shape.collections.find((item) => item.name === state.collection)
1613
- $("schema").innerHTML = "<h3>" + esc(selected.label) + "</h3>" +
1614
- (selected.description ? "<p class=" + '"lede">' + esc(selected.description) + "</p>" : "") +
1615
- schemaChips(selected)
1616
- const page = await api("GET", "/state/" + encodeURIComponent(state.collection) + "?limit=50")
1617
- const rows = page.records.map((record) => ({
1618
- ...record,
1619
- attrs: ' class="clickable" data-id="' + esc(record.id) + '"'
1620
- }))
1621
- $("records").innerHTML = table(
1622
- [
1623
- { label: "Id", cell: (row) => "<code>" + esc(row.id) + "</code>" },
1624
- { label: "Seq", cell: (row) => esc(row.seq) },
1625
- { label: "Value", cell: (row) => preview(row.value) }
1626
- ],
1627
- rows
1628
- )
1629
- }
1630
-
1631
- async function renderClock() {
1632
- const clock = await api("GET", "/clock")
1633
- $("clock-readout").innerHTML = "<div class='k'>Now</div><div class='v'>" + esc(when(clock.now)) +
1634
- "</div><p>" + esc(clock.now) + " ms \xB7 offset " + esc(clock.offsetMs) + " ms " +
1635
- (clock.frozen ? "<span class='badge warn'>frozen</span>" : "<span class='badge ok'>live</span>") + "</p>"
1636
- $("freeze").textContent = clock.frozen ? "Unfreeze" : "Freeze"
1637
- $("freeze").dataset.frozen = clock.frozen ? "1" : "0"
1638
- }
1639
-
1640
- async function renderFaults() {
1641
- const [faults, presets] = await Promise.all([
1642
- api("GET", "/faults"),
1643
- api("GET", "/faults/presets").catch(() => ({ presets: [] }))
1644
- ])
1645
- const select = $("fault-preset")
1646
- const current = select.value
1647
- const list = presets.presets || []
1648
- select.innerHTML = '<option value="">Custom rule</option>' + list.map((preset) =>
1649
- '<option value="' + esc(preset.name) + '">' + esc(preset.name) + "</option>"
1650
- ).join("")
1651
- select.value = current
1652
- const rules = faults.faults || []
1653
- $("fault-list").innerHTML = table(
1654
- [
1655
- { label: "Id", cell: (row) => esc(row.id) },
1656
- { label: "Match", cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter(Boolean).join(" ") || "all") },
1657
- { label: "Effect", cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || "")) }
1658
- ],
1659
- rules
1660
- )
1661
- }
1662
-
1663
- async function renderJournal() {
1664
- const journal = await api("GET", "/requests?limit=100")
1665
- $("journal").innerHTML = table(
1666
- [
1667
- { label: "When", cell: (row) => esc(row.at || "") },
1668
- { label: "Method", cell: (row) => esc(row.method) },
1669
- { label: "Path", cell: (row) => esc(row.path) },
1670
- { label: "Status", cell: (row) => esc(row.status) },
1671
- { label: "ms", cell: (row) => esc(row.durationMs) }
1672
- ],
1673
- journal.requests || []
1674
- )
1675
- }
1676
-
1677
- async function renderRoutes() {
1678
- const data = await api("GET", "/")
1679
- const routes = data.routes || []
1680
- $("route-list").innerHTML = routes.map((route) => {
1681
- const extra = STANDARD.has(route) ? "" : " <span class='badge'>extension</span>"
1682
- return '<div class="route"><span class="method">' + esc(route.split(" ")[0]) +
1683
- '</span><span>' + esc(route.split(" ").slice(1).join(" ")) + extra +
1684
- '</span><button class="btn" type="button" data-route="' + esc(route) + '">Use</button></div>'
1685
- }).join("")
1686
- }
1687
-
1688
- const renderers = {
1689
- overview: renderOverview,
1690
- state: renderState,
1691
- clock: renderClock,
1692
- faults: renderFaults,
1693
- journal: renderJournal,
1694
- routes: renderRoutes
1695
- }
1696
-
1697
- async function refresh() {
1698
- clearError()
1699
- if (state.view in renderers) await renderers[state.view]()
1700
- }
1701
-
1702
- $("namespace").addEventListener("change", () => {
1703
- state.namespace = $("namespace").value
1704
- refresh().catch(showError)
1705
- })
1706
- keyInput.addEventListener("change", () => {
1707
- state.key = keyInput.value.trim()
1708
- sessionStorage.setItem("mockingbird-admin-key", state.key)
1709
- refresh().catch(showError)
1710
- })
1711
- $("refresh").addEventListener("click", () => refresh().catch(showError))
1712
-
1713
- $("collections").addEventListener("click", (event) => {
1714
- const button = event.target.closest("[data-collection]")
1715
- if (!button) return
1716
- state.collection = button.dataset.collection
1717
- renderState().catch(showError)
1718
- })
1719
- $("records").addEventListener("click", (event) => {
1720
- const row = event.target.closest("[data-id]")
1721
- if (!row) return
1722
- openEditor(row.dataset.id).catch(showError)
1723
- })
1724
-
1725
- const editor = $("editor")
1726
- async function openEditor(id) {
1727
- state.editing = id || null
1728
- $("editor-title").textContent = id ? "Edit record" : "New record"
1729
- $("editor-id").value = id || ""
1730
- $("editor-id").readOnly = Boolean(id)
1731
- $("editor-delete").hidden = !id
1732
- if (id) {
1733
- const record = await api("GET", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
1734
- $("editor-value").value = JSON.stringify(record.value, null, 2)
1735
- } else {
1736
- $("editor-value").value = "{\\n \\n}"
1737
- }
1738
- editor.showModal()
1739
- }
1740
- $("new-record").addEventListener("click", () => openEditor(null).catch(showError))
1741
- $("editor-form").addEventListener("submit", (event) => {
1742
- const submitter = event.submitter
1743
- if (!submitter || submitter.value !== "save") return
1744
- event.preventDefault()
1745
- let value
1746
- try { value = JSON.parse($("editor-value").value) }
1747
- catch { showError(new Error("Value is not JSON")); return }
1748
- const id = $("editor-id").value.trim()
1749
- const path = "/state/" + encodeURIComponent(state.collection)
1750
- const request = state.editing
1751
- ? api("PUT", path + "/" + encodeURIComponent(id), { value })
1752
- : api("POST", path, { id: id || undefined, value })
1753
- request.then(() => { editor.close(); return renderState() }).catch(showError)
1754
- })
1755
- $("editor-delete").addEventListener("click", () => {
1756
- const id = state.editing
1757
- if (!id) return
1758
- api("DELETE", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
1759
- .then(() => { editor.close(); return renderState() })
1760
- .catch(showError)
1761
- })
1762
-
1763
- document.querySelectorAll("[data-advance]").forEach((button) => {
1764
- button.addEventListener("click", () => {
1765
- api("POST", "/clock", { advance: Number(button.dataset.advance) }).then(renderClock).catch(showError)
1766
- })
1767
- })
1768
- $("freeze").addEventListener("click", () => {
1769
- const frozen = $("freeze").dataset.frozen === "1"
1770
- api("POST", "/clock", { freeze: !frozen }).then(renderClock).catch(showError)
1771
- })
1772
- $("clock-reset").addEventListener("click", () => {
1773
- api("POST", "/clock", { reset: true }).then(renderClock).catch(showError)
1774
- })
1775
- $("clock-set").addEventListener("submit", (event) => {
1776
- event.preventDefault()
1777
- const raw = $("clock-instant").value.trim()
1778
- const set = /^-?\\d+$/.test(raw) ? Number(raw) : raw
1779
- api("POST", "/clock", { set }).then(renderClock).catch(showError)
1780
- })
1781
-
1782
- $("fault-form").addEventListener("submit", (event) => {
1783
- event.preventDefault()
1784
- const preset = $("fault-preset").value
1785
- const body = preset
1786
- ? { preset }
1787
- : {
1788
- status: Number($("fault-status").value),
1789
- ...($("fault-path").value ? { pathPrefix: $("fault-path").value } : {}),
1790
- ...($("fault-method").value ? { method: $("fault-method").value } : {})
1791
- }
1792
- if (!preset && !Number.isFinite(body.status)) {
1793
- showError(new Error("Status must be a number, or choose a preset"))
1794
- return
1795
- }
1796
- api("POST", "/faults", body).then(renderFaults).catch(showError)
1797
- })
1798
- $("fault-clear").addEventListener("click", () => api("DELETE", "/faults").then(renderFaults).catch(showError))
1799
- $("journal-clear").addEventListener("click", () => api("DELETE", "/requests").then(renderJournal).catch(showError))
1800
-
1801
- $("route-list").addEventListener("click", (event) => {
1802
- const button = event.target.closest("[data-route]")
1803
- if (!button) return
1804
- state.route = button.dataset.route
1805
- $("route-title").textContent = state.route
1806
- const method = state.route.split(" ")[0]
1807
- $("route-body").disabled = method === "GET" || method === "DELETE"
1808
- })
1809
- $("route-form").addEventListener("submit", async (event) => {
1810
- event.preventDefault()
1811
- if (!state.route) return showError(new Error("Choose a route first"))
1812
- const [method, path] = [state.route.split(" ")[0], state.route.split(" ").slice(1).join(" ")]
1813
- const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_, key) => prompt("Value for " + key) || "")
1814
- let body
1815
- if (method !== "GET" && method !== "DELETE" && $("route-body").value.trim()) {
1816
- try { body = JSON.parse($("route-body").value) }
1817
- catch { return showError(new Error("Body is not JSON")) }
1818
- }
1819
- try {
1820
- const data = await api(method, concrete, body)
1821
- $("route-result").textContent = JSON.stringify(data, null, 2)
1822
- } catch (error) {
1823
- showError(error)
1824
- }
1825
- })
1826
-
1827
- function mountPanels(panels) {
1828
- const nav = $("nav")
1829
- const views = $("views")
1830
- for (const panel of panels) {
1831
- const button = document.createElement("button")
1832
- button.type = "button"
1833
- button.dataset.view = panel.id
1834
- button.textContent = panel.title
1835
- nav.append(button)
1836
- const section = document.createElement("section")
1837
- section.className = "view"
1838
- section.id = "view-" + panel.id
1839
- section.hidden = true
1840
- const title = document.createElement("h2")
1841
- title.textContent = panel.title
1842
- const lede = document.createElement("p")
1843
- lede.className = "lede"
1844
- lede.textContent = panel.description || ""
1845
- const body = document.createElement("div")
1846
- body.className = "panel-body"
1847
- body.id = "panel-" + panel.id
1848
- body.innerHTML = panel.html || ""
1849
- section.append(title, lede, body)
1850
- views.append(section)
1851
- if (panel.script) {
1852
- try {
1853
- const run = new Function("root", "api", panel.script)
1854
- run(body, panelApi)
1855
- } catch (error) {
1856
- const note = document.createElement("p")
1857
- note.className = "banner show"
1858
- note.textContent = error instanceof Error ? error.message : String(error)
1859
- body.append(note)
1860
- }
1861
- }
1862
- }
1863
- }
1864
-
1865
- loadNamespaces()
1866
- .then(() => api("GET", "/ui/manifest"))
1867
- .then((manifest) => { mountPanels((manifest && manifest.panels) || []); return refresh() })
1868
- .catch(showError)
1869
- })()
1870
- </script>
2671
+ ${script}
1871
2672
  </body>
1872
2673
  </html>`;
1873
2674
  };
@@ -1884,10 +2685,10 @@ var json2 = (body) => new Response(JSON.stringify(body), {
1884
2685
  });
1885
2686
  var adminUiRoutes = (service, ui) => {
1886
2687
  const shell = () => renderAdminDocument(service);
1887
- const document2 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
2688
+ const document3 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
1888
2689
  return {
1889
- "GET /ui": () => htmlResponse(document2()),
1890
- "GET /ui/": () => htmlResponse(document2()),
2690
+ "GET /ui": () => htmlResponse(document3()),
2691
+ "GET /ui/": () => htmlResponse(document3()),
1891
2692
  "GET /ui/manifest": () => json2({
1892
2693
  service,
1893
2694
  panels: (ui?.panels ?? []).map((panel) => ({
@@ -2375,7 +3176,7 @@ var SAMPLE = 25;
2375
3176
  var WALK_LIMIT = 1e4;
2376
3177
  var SKIP_CTORS = /* @__PURE__ */ new Set(["Hono", "HonoRequest", "Router"]);
2377
3178
  var fail = (status, message) => ({ ok: false, status, message });
2378
- var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3179
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2379
3180
  var kindOf = (value) => {
2380
3181
  if (value === null)
2381
3182
  return "null";
@@ -2474,7 +3275,7 @@ var absorb = (accum, value) => {
2474
3275
  const kind = kindOf(value);
2475
3276
  accum.kinds.add(kind);
2476
3277
  accum.present++;
2477
- if (!isRecord2(value))
3278
+ if (!isRecord3(value))
2478
3279
  return;
2479
3280
  for (const [key, child] of Object.entries(value)) {
2480
3281
  const nested = accum.nested.get(key) ?? blankAccum(key);
@@ -2499,7 +3300,7 @@ var inferFields = (records) => {
2499
3300
  if (records.length === 0)
2500
3301
  return [];
2501
3302
  const sample = records.slice(0, SAMPLE);
2502
- const objects = sample.filter((record) => isRecord2(record.value));
3303
+ const objects = sample.filter((record) => isRecord3(record.value));
2503
3304
  if (objects.length === 0) {
2504
3305
  const kinds = new Set(sample.map((record) => kindOf(record.value)));
2505
3306
  const list = [...kinds];
@@ -2686,7 +3487,7 @@ var patchStateRecord = (scope, name, id, value) => {
2686
3487
  const parsed = jsonValue(value);
2687
3488
  if (!parsed.ok)
2688
3489
  return parsed;
2689
- const next = isRecord2(current.value.value) && isRecord2(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
3490
+ const next = isRecord3(current.value.value) && isRecord3(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
2690
3491
  return replaceStateRecord(scope, name, id, next);
2691
3492
  };
2692
3493
  var deleteStateRecord = (scope, name, id) => {
@@ -2754,7 +3555,7 @@ var stateAdminRoutes = (routes) => {
2754
3555
  const scope = open(namespace);
2755
3556
  if (!scope.ok)
2756
3557
  return fromResult(scope, 201);
2757
- if (!isRecord2(body) || !("value" in body)) {
3558
+ if (!isRecord3(body) || !("value" in body)) {
2758
3559
  return adminError2(400, 'expected {"id?": "<id>", "value": <json>}');
2759
3560
  }
2760
3561
  const id = body.id;
@@ -2772,7 +3573,7 @@ var stateAdminRoutes = (routes) => {
2772
3573
  const scope = open(namespace);
2773
3574
  if (!scope.ok)
2774
3575
  return fromResult(scope, 200);
2775
- if (!isRecord2(body) || !("value" in body))
3576
+ if (!isRecord3(body) || !("value" in body))
2776
3577
  return adminError2(400, 'expected {"value": <json>}');
2777
3578
  const replaced = replaceStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
2778
3579
  if (replaced.ok)
@@ -2783,7 +3584,7 @@ var stateAdminRoutes = (routes) => {
2783
3584
  const scope = open(namespace);
2784
3585
  if (!scope.ok)
2785
3586
  return fromResult(scope, 200);
2786
- if (!isRecord2(body) || !("value" in body))
3587
+ if (!isRecord3(body) || !("value" in body))
2787
3588
  return adminError2(400, 'expected {"value": <json>}');
2788
3589
  const patched = patchStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
2789
3590
  if (patched.ok)
@@ -2803,14 +3604,14 @@ var stateAdminRoutes = (routes) => {
2803
3604
  };
2804
3605
 
2805
3606
  // ../core/dist/version.js
2806
- var PACKAGE_VERSION = true ? "1.4.0" : UNRELEASED_VERSION;
3607
+ var PACKAGE_VERSION = true ? "1.5.0" : UNRELEASED_VERSION;
2807
3608
 
2808
3609
  // ../core/dist/webhooks.js
2809
3610
  var json4 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
2810
3611
  var adminError3 = (status, message) => json4(status, { error: { type: "mockingbird_admin", message } });
2811
- var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3612
+ var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2812
3613
  var parseEndpoint = (value) => {
2813
- if (!isRecord3(value) || typeof value.url !== "string")
3614
+ if (!isRecord4(value) || typeof value.url !== "string")
2814
3615
  return "each endpoint needs a url";
2815
3616
  try {
2816
3617
  new URL(value.url);
@@ -2827,12 +3628,12 @@ var parseEndpoint = (value) => {
2827
3628
  const events = value.events ?? value.enabledEvents;
2828
3629
  if (Array.isArray(events))
2829
3630
  endpoint.events = events.map(String);
2830
- if (isRecord3(value.tags)) {
3631
+ if (isRecord4(value.tags)) {
2831
3632
  endpoint.tags = Object.fromEntries(Object.entries(value.tags).map(([k, v]) => [k, String(v)]));
2832
3633
  }
2833
3634
  if (typeof value.account === "string")
2834
3635
  endpoint.tags = { ...endpoint.tags, account: value.account };
2835
- if (isRecord3(value.headers)) {
3636
+ if (isRecord4(value.headers)) {
2836
3637
  endpoint.headers = Object.fromEntries(Object.entries(value.headers).map(([k, v]) => [k, String(v)]));
2837
3638
  }
2838
3639
  return endpoint;
@@ -2859,7 +3660,7 @@ var webhookAdminRoutes = (hub) => ({
2859
3660
  return json4(200, { status: "ok" });
2860
3661
  },
2861
3662
  "POST /webhooks/faults": ({ body, namespace }) => {
2862
- if (!isRecord3(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
3663
+ if (!isRecord4(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
2863
3664
  return adminError3(400, "mode must be duplicate, reorder or drop");
2864
3665
  }
2865
3666
  const fault = { mode: body.mode };
@@ -2875,7 +3676,7 @@ var webhookAdminRoutes = (hub) => ({
2875
3676
  }))
2876
3677
  }),
2877
3678
  "PUT /webhook-endpoints": ({ body, namespace }) => {
2878
- const list = Array.isArray(body) ? body : isRecord3(body) ? body.endpoints : void 0;
3679
+ const list = Array.isArray(body) ? body : isRecord4(body) ? body.endpoints : void 0;
2879
3680
  if (!Array.isArray(list))
2880
3681
  return adminError3(400, "expected [{url, secret?, events?}]");
2881
3682
  const parsed = [];
@@ -2943,8 +3744,8 @@ var DroppedConnectionError = class extends TypeError {
2943
3744
  this.name = "TypeError";
2944
3745
  }
2945
3746
  };
2946
- var operationMatcher = (document2) => {
2947
- const matchers = listOperations(document2).map((operation) => ({
3747
+ var operationMatcher = (document3) => {
3748
+ const matchers = listOperations(document3).map((operation) => ({
2948
3749
  operationId: operation.operationId,
2949
3750
  method: operation.method.toUpperCase(),
2950
3751
  pattern: new RegExp(`^${operation.path.split("/").map((segment) => segment.startsWith("{") ? "[^/]+" : segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("/")}/?$`),
@@ -3039,10 +3840,10 @@ var createRuntime = (options) => {
3039
3840
  const ensureBranch = (namespace, branch2, at) => {
3040
3841
  if (!BRANCH_PATTERN2.test(branch2))
3041
3842
  throw new RangeError(`branch must match ${BRANCH_PATTERN2}`);
3042
- const history = timeline(namespace);
3843
+ const history2 = timeline(namespace);
3043
3844
  if (branch2 === "main") {
3044
3845
  if (at !== void 0) {
3045
- const point = history.checkout("main", at);
3846
+ const point = history2.checkout("main", at);
3046
3847
  restoreNamespace(sqlite, storageNamespace(namespace), point.value.snapshot);
3047
3848
  captured.set(namespace, point.value.snapshot);
3048
3849
  rng.setState(point.value.rngState);
@@ -3055,10 +3856,10 @@ var createRuntime = (options) => {
3055
3856
  return namespace;
3056
3857
  }
3057
3858
  const storage = physicalBranch(namespace, branch2);
3058
- if (!history.hasBranch(branch2)) {
3859
+ if (!history2.hasBranch(branch2)) {
3059
3860
  if (at === void 0)
3060
- history.commit(capture(namespace));
3061
- const point = history.fork(branch2, at === void 0 ? {} : { from: at });
3861
+ history2.commit(capture(namespace));
3862
+ const point = history2.fork(branch2, at === void 0 ? {} : { from: at });
3062
3863
  const branchRng = createRng(options.seed ?? 0);
3063
3864
  if (point)
3064
3865
  branchRng.setState(point.value.rngState);
@@ -3067,8 +3868,8 @@ var createRuntime = (options) => {
3067
3868
  restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
3068
3869
  if (point)
3069
3870
  captured.set(storage, point.value.snapshot);
3070
- } else if (at !== void 0 && history.head(branch2)?.id !== at) {
3071
- const point = history.checkout(branch2, at);
3871
+ } else if (at !== void 0 && history2.head(branch2)?.id !== at) {
3872
+ const point = history2.checkout(branch2, at);
3072
3873
  if (!instances.has(storage)) {
3073
3874
  const branchRng = createRng(options.seed ?? 0);
3074
3875
  branchRng.setState(point.value.rngState);
@@ -3079,7 +3880,7 @@ var createRuntime = (options) => {
3079
3880
  captured.set(storage, point.value.snapshot);
3080
3881
  } else {
3081
3882
  if (!instances.has(storage)) {
3082
- const point = history.head(branch2);
3883
+ const point = history2.head(branch2);
3083
3884
  const branchRng = createRng(options.seed ?? 0);
3084
3885
  if (point)
3085
3886
  branchRng.setState(point.value.rngState);
@@ -3103,8 +3904,8 @@ var createRuntime = (options) => {
3103
3904
  const checkout = (checkpointId, checkoutOptions = {}) => {
3104
3905
  const namespace = checkoutOptions.namespace ?? DEFAULT_NAMESPACE;
3105
3906
  const branchName = checkoutOptions.branch ?? "main";
3106
- const history = timeline(namespace);
3107
- const point = history.checkout(branchName, checkpointId);
3907
+ const history2 = timeline(namespace);
3908
+ const point = history2.checkout(branchName, checkpointId);
3108
3909
  const storage = ensureBranch(namespace, branchName);
3109
3910
  restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
3110
3911
  captured.set(storage, point.value.snapshot);
@@ -3154,9 +3955,9 @@ var createRuntime = (options) => {
3154
3955
  instance(name);
3155
3956
  restoreNamespace(sqlite, storageNamespace(name), from);
3156
3957
  captured.set(name, from);
3157
- const history = timelines.get(name);
3158
- if (history)
3159
- history.commit(capture(name), { branch: "main" });
3958
+ const history2 = timelines.get(name);
3959
+ if (history2)
3960
+ history2.commit(capture(name), { branch: "main" });
3160
3961
  else
3161
3962
  timeline(name);
3162
3963
  };
@@ -3385,10 +4186,10 @@ var createRuntime = (options) => {
3385
4186
  },
3386
4187
  release: (name, checkpointId) => timeline(name).release(checkpointId),
3387
4188
  inspect: (name) => {
3388
- const history = timeline(name);
4189
+ const history2 = timeline(name);
3389
4190
  return {
3390
- branches: history.branches(),
3391
- checkpoints: history.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
4191
+ branches: history2.branches(),
4192
+ checkpoints: history2.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
3392
4193
  id,
3393
4194
  branch: branchName,
3394
4195
  parent,
@@ -3403,11 +4204,11 @@ var createRuntime = (options) => {
3403
4204
  } : {},
3404
4205
  routes: {
3405
4206
  ...credentialRoutes(credentials),
3406
- ...options.presets ? presetRoutes(options.presets, runtime) : {},
3407
4207
  ...options.webhooks ? webhookAdminRoutes(options.webhooks) : {},
3408
4208
  ...options.admin?.(runtime) ?? {},
3409
- // Standard state and UI routes win a colliding key. A service adds its own keys
3410
- // beside these; it does not replace `/state` or `/ui`.
4209
+ // Shell routes win a colliding key. Fault presets are one of them: the admin page
4210
+ // maps `presets`, and a service route must not replace that list with a record.
4211
+ ...options.presets ? presetRoutes(options.presets, runtime) : {},
3411
4212
  ...stateAdminRoutes({
3412
4213
  open: stateScope,
3413
4214
  afterWrite: (namespace) => {
@@ -3482,9 +4283,12 @@ var credentialRoutes = (registry) => ({
3482
4283
  }
3483
4284
  });
3484
4285
  var presetRoutes = (presets, runtime) => ({
3485
- "GET /faults/presets": () => adminJson(200, {
3486
- presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
3487
- }),
4286
+ "GET /faults/presets": () => {
4287
+ const body = {
4288
+ presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
4289
+ };
4290
+ return adminJson(200, body);
4291
+ },
3488
4292
  "POST /faults/presets/:name": ({ params, body, namespace }) => {
3489
4293
  const name = params.name;
3490
4294
  if (!presets[name])
@@ -3495,7 +4299,7 @@ var presetRoutes = (presets, runtime) => ({
3495
4299
  });
3496
4300
 
3497
4301
  // src/generated/openapi.ts
3498
- var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"AWS Step Functions (Mockingbird subset)","version":"2016-11-23","description":"AWS JSON contract used by the pinned SFN client."},"servers":[{"url":"https://states.us-east-1.amazonaws.com"}],"paths":{"/":{"post":{"operationId":"StepFunctionsRpc","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"name":"X-Amz-Target","in":"header","required":true,"schema":{"type":"string","pattern":"^AWSStepFunctions\\\\\\\\.[A-Za-z]+$"}}],"requestBody":{"required":true,"content":{"application/x-amz-json-1.0":{"schema":{"type":"object","additionalProperties":true}},"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"Operation response","content":{"application/x-amz-json-1.0":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"SFN exception","content":{"application/x-amz-json-1.0":{"schema":{"type":"object","additionalProperties":true}}}}}}}}}`);
4302
+ var document2 = JSON.parse(`{"openapi":"3.1.0","info":{"title":"AWS Step Functions (Mockingbird subset)","version":"2016-11-23","description":"AWS JSON contract used by the pinned SFN client."},"servers":[{"url":"https://states.us-east-1.amazonaws.com"}],"paths":{"/":{"post":{"operationId":"StepFunctionsRpc","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"name":"X-Amz-Target","in":"header","required":true,"schema":{"type":"string","pattern":"^AWSStepFunctions\\\\\\\\.[A-Za-z]+$"}}],"requestBody":{"required":true,"content":{"application/x-amz-json-1.0":{"schema":{"type":"object","additionalProperties":true}},"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"Operation response","content":{"application/x-amz-json-1.0":{"schema":{"type":"object","additionalProperties":true}}}},"400":{"description":"SFN exception","content":{"application/x-amz-json-1.0":{"schema":{"type":"object","additionalProperties":true}}}}}}}}}`);
3499
4303
  var operationIds = ["StepFunctionsRpc"];
3500
4304
  var supportedOperationIds = ["StepFunctionsRpc"];
3501
4305
 
@@ -3559,7 +4363,7 @@ var admin = (runtime) => ({
3559
4363
  });
3560
4364
  var createRuntime2 = (options = {}) => createRuntime({
3561
4365
  name: STEP_FUNCTIONS_NAMESPACE,
3562
- document,
4366
+ document: document2,
3563
4367
  presets: STEP_FUNCTIONS_PRESETS,
3564
4368
  ...options.sqlite ? { sqlite: options.sqlite } : {},
3565
4369
  ...options.clock ? { clock: options.clock } : {},
@@ -3823,7 +4627,7 @@ var StepFunctionsAPI = class {
3823
4627
  };
3824
4628
 
3825
4629
  export {
3826
- document,
4630
+ document2 as document,
3827
4631
  operationIds,
3828
4632
  supportedOperationIds,
3829
4633
  STEP_FUNCTIONS_PRESETS,
@@ -3832,4 +4636,4 @@ export {
3832
4636
  accessKeyCredential,
3833
4637
  StepFunctionsAPI
3834
4638
  };
3835
- //# sourceMappingURL=chunk-4S65JRDA.js.map
4639
+ //# sourceMappingURL=chunk-FWF7NYHK.js.map