@crvouga/mockingbird-service-google-maps 1.5.0 → 1.6.1

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.
@@ -533,10 +533,10 @@ var OpenAPIReferenceError = class extends Error {
533
533
  };
534
534
  var unescapePointer = (segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~");
535
535
  var isReference = (value) => typeof value === "object" && value !== null && typeof value.$ref === "string";
536
- var resolveRef = (document2, ref) => {
536
+ var resolveRef = (document3, ref) => {
537
537
  if (!ref.startsWith("#/"))
538
538
  throw new OpenAPIReferenceError(ref);
539
- let cursor = document2;
539
+ let cursor = document3;
540
540
  for (const raw of ref.slice(2).split("/")) {
541
541
  const segment = unescapePointer(raw);
542
542
  if (typeof cursor !== "object" || cursor === null || !(segment in cursor)) {
@@ -548,14 +548,14 @@ var resolveRef = (document2, ref) => {
548
548
  throw new OpenAPIReferenceError(ref);
549
549
  return cursor;
550
550
  };
551
- var deref = (document2, value) => {
551
+ var deref = (document3, value) => {
552
552
  let current = value;
553
553
  const seen = /* @__PURE__ */ new Set();
554
554
  while (isReference(current)) {
555
555
  if (seen.has(current.$ref))
556
556
  throw new OpenAPIReferenceError(`${current.$ref} (cycle)`);
557
557
  seen.add(current.$ref);
558
- current = resolveRef(document2, current.$ref);
558
+ current = resolveRef(document3, current.$ref);
559
559
  }
560
560
  return current;
561
561
  };
@@ -573,36 +573,36 @@ var HTTP_METHODS = [
573
573
  ];
574
574
 
575
575
  // ../../openapi/core/dist/document.js
576
- var mergeParameters = (document2, item, own) => {
576
+ var mergeParameters = (document3, item, own) => {
577
577
  const merged = /* @__PURE__ */ new Map();
578
578
  for (const raw of item.parameters ?? []) {
579
- const parameter = deref(document2, raw);
579
+ const parameter = deref(document3, raw);
580
580
  merged.set(`${parameter.in}:${parameter.name}`, parameter);
581
581
  }
582
582
  for (const raw of own ?? []) {
583
- const parameter = deref(document2, raw);
583
+ const parameter = deref(document3, raw);
584
584
  merged.set(`${parameter.in}:${parameter.name}`, parameter);
585
585
  }
586
586
  return [...merged.values()];
587
587
  };
588
- var listOperations = (document2) => {
588
+ var listOperations = (document3) => {
589
589
  const operations = [];
590
- for (const [path, item] of Object.entries(document2.paths)) {
590
+ for (const [path, item] of Object.entries(document3.paths)) {
591
591
  for (const method of HTTP_METHODS) {
592
592
  const operation = item[method];
593
593
  if (operation?.operationId === void 0)
594
594
  continue;
595
595
  const responses = {};
596
596
  for (const [status, response] of Object.entries(operation.responses)) {
597
- responses[status] = deref(document2, response);
597
+ responses[status] = deref(document3, response);
598
598
  }
599
599
  operations.push({
600
600
  operationId: operation.operationId,
601
601
  method,
602
602
  path,
603
603
  operation,
604
- parameters: mergeParameters(document2, item, operation.parameters),
605
- requestBody: operation.requestBody === void 0 ? void 0 : deref(document2, operation.requestBody),
604
+ parameters: mergeParameters(document3, item, operation.parameters),
605
+ requestBody: operation.requestBody === void 0 ? void 0 : deref(document3, operation.requestBody),
606
606
  responses
607
607
  });
608
608
  }
@@ -1119,6 +1119,1232 @@ var clearNamespace = (sqlite, namespace) => {
1119
1119
  });
1120
1120
  };
1121
1121
 
1122
+ // ../../ui/dist/reset.js
1123
+ var CSS_RESET = `*,
1124
+ *::before,
1125
+ *::after {
1126
+ box-sizing: border-box;
1127
+ margin: 0;
1128
+ padding: 0;
1129
+ }
1130
+
1131
+ * {
1132
+ scrollbar-width: thin;
1133
+ scrollbar-color: color-mix(in srgb, CanvasText 35%, Canvas) Canvas;
1134
+ }
1135
+
1136
+ *::-webkit-scrollbar {
1137
+ width: 12px;
1138
+ height: 12px;
1139
+ }
1140
+
1141
+ *::-webkit-scrollbar-track,
1142
+ *::-webkit-scrollbar-corner {
1143
+ background: Canvas;
1144
+ }
1145
+
1146
+ *::-webkit-scrollbar-thumb {
1147
+ background: color-mix(in srgb, CanvasText 35%, Canvas);
1148
+ border: 3px solid Canvas;
1149
+ border-radius: 999px;
1150
+ }
1151
+
1152
+ html {
1153
+ hanging-punctuation: first allow-end last;
1154
+ -webkit-text-size-adjust: none;
1155
+ text-size-adjust: none;
1156
+ tab-size: 4;
1157
+ font-family:
1158
+ ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI",
1159
+ "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
1160
+ font-size: 16px;
1161
+ line-height: 1.5;
1162
+ -webkit-font-smoothing: antialiased;
1163
+ -moz-osx-font-smoothing: grayscale;
1164
+ text-rendering: optimizeLegibility;
1165
+ -webkit-tap-highlight-color: transparent;
1166
+ scroll-behavior: smooth;
1167
+ background: Canvas;
1168
+ color: CanvasText;
1169
+ }
1170
+
1171
+ body {
1172
+ min-height: 100svh;
1173
+ line-height: 1.5;
1174
+ background: Canvas;
1175
+ color: CanvasText;
1176
+ }
1177
+
1178
+ h1 {
1179
+ font-size: 2em;
1180
+ }
1181
+
1182
+ h2 {
1183
+ font-size: 1.5em;
1184
+ }
1185
+
1186
+ h3 {
1187
+ font-size: 1.17em;
1188
+ }
1189
+
1190
+ h4 {
1191
+ font-size: 1em;
1192
+ }
1193
+
1194
+ h5 {
1195
+ font-size: 0.83em;
1196
+ }
1197
+
1198
+ h6 {
1199
+ font-size: 0.67em;
1200
+ }
1201
+
1202
+ h1,
1203
+ h2,
1204
+ h3,
1205
+ h4,
1206
+ h5,
1207
+ h6 {
1208
+ font-weight: 700;
1209
+ line-height: 1.2;
1210
+ text-wrap: balance;
1211
+ overflow-wrap: break-word;
1212
+ }
1213
+
1214
+ p,
1215
+ li,
1216
+ figcaption,
1217
+ dd,
1218
+ dt,
1219
+ blockquote {
1220
+ overflow-wrap: break-word;
1221
+ }
1222
+
1223
+ p,
1224
+ li,
1225
+ figcaption,
1226
+ blockquote {
1227
+ text-wrap: pretty;
1228
+ }
1229
+
1230
+ ul,
1231
+ ol,
1232
+ menu {
1233
+ list-style: none;
1234
+ }
1235
+
1236
+ a {
1237
+ color: inherit;
1238
+ text-decoration: underline;
1239
+ text-decoration-thickness: 1px;
1240
+ text-underline-offset: 0.15em;
1241
+ text-decoration-skip-ink: auto;
1242
+ }
1243
+
1244
+ b,
1245
+ strong {
1246
+ font-weight: 700;
1247
+ }
1248
+
1249
+ small {
1250
+ font-size: 0.875em;
1251
+ }
1252
+
1253
+ code,
1254
+ kbd,
1255
+ samp,
1256
+ pre {
1257
+ font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
1258
+ font-size: 1em;
1259
+ }
1260
+
1261
+ sub,
1262
+ sup {
1263
+ font-size: 75%;
1264
+ line-height: 0;
1265
+ position: relative;
1266
+ vertical-align: baseline;
1267
+ }
1268
+
1269
+ sub {
1270
+ bottom: -0.25em;
1271
+ }
1272
+
1273
+ sup {
1274
+ top: -0.5em;
1275
+ }
1276
+
1277
+ abbr[title] {
1278
+ text-decoration: underline dotted;
1279
+ text-underline-offset: 0.15em;
1280
+ }
1281
+
1282
+ mark {
1283
+ background: Mark;
1284
+ color: MarkText;
1285
+ }
1286
+
1287
+ hr {
1288
+ height: 0;
1289
+ border: 0;
1290
+ border-top: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
1291
+ color: inherit;
1292
+ }
1293
+
1294
+ img,
1295
+ picture,
1296
+ video,
1297
+ canvas {
1298
+ display: block;
1299
+ max-width: 100%;
1300
+ }
1301
+
1302
+ img,
1303
+ video {
1304
+ height: auto;
1305
+ }
1306
+
1307
+ svg {
1308
+ overflow: hidden;
1309
+ }
1310
+
1311
+ iframe {
1312
+ border: 0;
1313
+ max-width: 100%;
1314
+ }
1315
+
1316
+ table {
1317
+ border-collapse: collapse;
1318
+ border-spacing: 0;
1319
+ }
1320
+
1321
+ fieldset {
1322
+ border: 0;
1323
+ min-inline-size: 0;
1324
+ }
1325
+
1326
+ legend {
1327
+ padding: 0;
1328
+ }
1329
+
1330
+ button,
1331
+ input,
1332
+ select,
1333
+ textarea,
1334
+ optgroup {
1335
+ font: inherit;
1336
+ letter-spacing: inherit;
1337
+ color: inherit;
1338
+ margin: 0;
1339
+ vertical-align: middle;
1340
+ }
1341
+
1342
+ button,
1343
+ input[type="button"],
1344
+ input[type="submit"],
1345
+ input[type="reset"] {
1346
+ appearance: none;
1347
+ background: none;
1348
+ border: 0;
1349
+ padding: 0;
1350
+ cursor: pointer;
1351
+ text-align: center;
1352
+ }
1353
+
1354
+ input,
1355
+ textarea,
1356
+ select {
1357
+ background-color: Canvas;
1358
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1359
+ border-radius: 0;
1360
+ padding: 0.5em 0.65em;
1361
+ }
1362
+
1363
+ input[type="checkbox"],
1364
+ input[type="radio"],
1365
+ input[type="range"],
1366
+ input[type="file"],
1367
+ input[type="color"],
1368
+ input[type="button"],
1369
+ input[type="submit"],
1370
+ input[type="reset"],
1371
+ input[type="image"],
1372
+ input[type="hidden"] {
1373
+ background: none;
1374
+ border: 0;
1375
+ padding: 0;
1376
+ border-radius: 0;
1377
+ }
1378
+
1379
+ input[type="checkbox"],
1380
+ input[type="radio"] {
1381
+ accent-color: CanvasText;
1382
+ }
1383
+
1384
+ input[type="search"] {
1385
+ appearance: none;
1386
+ background-color: Canvas;
1387
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1388
+ }
1389
+
1390
+ input[type="search"]::-webkit-search-decoration,
1391
+ input[type="search"]::-webkit-search-cancel-button,
1392
+ input[type="search"]::-webkit-search-results-button,
1393
+ input[type="search"]::-webkit-search-results-decoration {
1394
+ -webkit-appearance: none;
1395
+ display: none;
1396
+ }
1397
+
1398
+ input[type="number"] {
1399
+ appearance: textfield;
1400
+ background-color: Canvas;
1401
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1402
+ }
1403
+
1404
+ input[type="number"]::-webkit-inner-spin-button,
1405
+ input[type="number"]::-webkit-outer-spin-button {
1406
+ -webkit-appearance: none;
1407
+ margin: 0;
1408
+ }
1409
+
1410
+ textarea {
1411
+ resize: vertical;
1412
+ }
1413
+
1414
+ ::placeholder {
1415
+ opacity: 1;
1416
+ color: color-mix(in srgb, CanvasText 45%, transparent);
1417
+ }
1418
+
1419
+ :focus-visible {
1420
+ outline: 2px solid CanvasText;
1421
+ outline-offset: 2px;
1422
+ }
1423
+
1424
+ :focus:not(:focus-visible) {
1425
+ outline: none;
1426
+ }
1427
+
1428
+ :disabled,
1429
+ [disabled] {
1430
+ cursor: default;
1431
+ opacity: 0.6;
1432
+ }
1433
+
1434
+ label,
1435
+ summary,
1436
+ select,
1437
+ [role="button"] {
1438
+ cursor: pointer;
1439
+ }
1440
+
1441
+ summary {
1442
+ display: list-item;
1443
+ }
1444
+
1445
+ details {
1446
+ display: block;
1447
+ }
1448
+
1449
+ dialog {
1450
+ margin: auto;
1451
+ padding: 1rem;
1452
+ border: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
1453
+ background: Canvas;
1454
+ color: CanvasText;
1455
+ max-width: calc(100vw - 2rem);
1456
+ max-height: calc(100svh - 2rem);
1457
+ }
1458
+
1459
+ dialog:not([open]) {
1460
+ display: none;
1461
+ }
1462
+
1463
+ ::backdrop {
1464
+ background: color-mix(in srgb, CanvasText 40%, transparent);
1465
+ }
1466
+
1467
+ [hidden] {
1468
+ display: none !important;
1469
+ }
1470
+
1471
+ ::selection {
1472
+ background: Highlight;
1473
+ color: HighlightText;
1474
+ }
1475
+
1476
+ @media (prefers-reduced-motion: reduce) {
1477
+ *,
1478
+ *::before,
1479
+ *::after {
1480
+ animation-duration: 0.01ms !important;
1481
+ animation-iteration-count: 1 !important;
1482
+ transition-duration: 0.01ms !important;
1483
+ scroll-behavior: auto !important;
1484
+ }
1485
+ }
1486
+
1487
+ @media (pointer: coarse) {
1488
+ input,
1489
+ select,
1490
+ textarea {
1491
+ font-size: max(16px, 1em);
1492
+ }
1493
+ }`;
1494
+
1495
+ // ../core/dist/admin-client.js
1496
+ function isRecord2(value) {
1497
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1498
+ }
1499
+ function mountAdminBrand(doc, host, service, catalog) {
1500
+ const clip = (value, max) => {
1501
+ if (typeof value !== "string")
1502
+ return null;
1503
+ const trimmed = value.trim();
1504
+ if (trimmed === "")
1505
+ return null;
1506
+ return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
1507
+ };
1508
+ const httpUrl = (value) => {
1509
+ if (typeof value !== "string" || value.length > 2e3)
1510
+ return null;
1511
+ try {
1512
+ const url = new URL(value);
1513
+ if (url.protocol !== "https:" && url.protocol !== "http:")
1514
+ return null;
1515
+ return url.href;
1516
+ } catch {
1517
+ return null;
1518
+ }
1519
+ };
1520
+ const hexColor = (value) => {
1521
+ if (typeof value !== "string")
1522
+ return null;
1523
+ const raw = value.trim();
1524
+ return /^#[0-9a-fA-F]{3,8}$/.test(raw) ? raw : null;
1525
+ };
1526
+ const hostname = (value) => {
1527
+ try {
1528
+ return new URL(value).hostname.replace(/^www\./, "");
1529
+ } catch {
1530
+ return value;
1531
+ }
1532
+ };
1533
+ host.replaceChildren();
1534
+ host.hidden = true;
1535
+ if (!isRecord2(catalog) || !Object.hasOwn(catalog, service))
1536
+ return;
1537
+ const row2 = catalog[service];
1538
+ if (!isRecord2(row2))
1539
+ return;
1540
+ const vendor = clip(row2.vendor, 80);
1541
+ const website = httpUrl(row2.website);
1542
+ const guide = httpUrl(row2.guide);
1543
+ const docs = httpUrl(row2.docs);
1544
+ const logo = httpUrl(row2.logo);
1545
+ const color = hexColor(row2.color);
1546
+ const description = clip(row2.description, 280);
1547
+ if (vendor === null || website === null && guide === null && docs === null)
1548
+ return;
1549
+ const add = (node) => {
1550
+ host.append(node);
1551
+ return node;
1552
+ };
1553
+ if (logo !== null) {
1554
+ const img = doc.createElement("img");
1555
+ img.alt = "";
1556
+ img.src = logo;
1557
+ img.width = 16;
1558
+ img.height = 16;
1559
+ img.decoding = "async";
1560
+ img.referrerPolicy = "no-referrer";
1561
+ if (color !== null)
1562
+ img.style.borderColor = color;
1563
+ add(img);
1564
+ }
1565
+ const name = doc.createElement("span");
1566
+ name.className = "vendor-name";
1567
+ name.textContent = vendor;
1568
+ if (description !== null)
1569
+ name.title = description;
1570
+ add(name);
1571
+ const sep = () => {
1572
+ const node = doc.createElement("span");
1573
+ node.className = "vendor-sep";
1574
+ node.setAttribute("aria-hidden", "true");
1575
+ node.textContent = "\xB7";
1576
+ return node;
1577
+ };
1578
+ const link = (href, label, title) => {
1579
+ const node = doc.createElement("a");
1580
+ node.className = "vendor-link";
1581
+ node.href = href;
1582
+ node.target = "_blank";
1583
+ node.rel = "noopener noreferrer";
1584
+ node.referrerPolicy = "no-referrer";
1585
+ node.textContent = label;
1586
+ node.title = title;
1587
+ return node;
1588
+ };
1589
+ if (website !== null) {
1590
+ add(sep());
1591
+ add(link(website, hostname(website), `${vendor} website`));
1592
+ }
1593
+ if (guide !== null) {
1594
+ add(sep());
1595
+ add(link(guide, "Docs", `Docs for ${vendor}`));
1596
+ }
1597
+ if (docs !== null) {
1598
+ add(sep());
1599
+ add(link(docs, "API", `${vendor} API reference`));
1600
+ }
1601
+ host.removeAttribute("hidden");
1602
+ host.hidden = false;
1603
+ }
1604
+ function startAdminBrand(env) {
1605
+ const requested = env.params.get("brands");
1606
+ let brandsUrl = env.brandsUrl;
1607
+ if (requested) {
1608
+ try {
1609
+ const url = new URL(requested, env.location.href);
1610
+ const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]" || url.origin === env.location.origin;
1611
+ if ((url.protocol === "https:" || url.protocol === "http:") && local)
1612
+ brandsUrl = url.href;
1613
+ } catch {
1614
+ brandsUrl = env.brandsUrl;
1615
+ }
1616
+ }
1617
+ const vendorHost = env.$("vendor");
1618
+ const brandRoot = env.document.querySelector("[data-mockingbird-admin]");
1619
+ const brandService = brandRoot?.getAttribute("data-service") || "";
1620
+ if (vendorHost === null)
1621
+ return;
1622
+ env.fetch(brandsUrl, {
1623
+ headers: { accept: "application/json" },
1624
+ signal: AbortSignal.timeout(5e3)
1625
+ }).then(async (response) => {
1626
+ if (!response.ok)
1627
+ return null;
1628
+ const catalog = await response.json();
1629
+ return catalog;
1630
+ }).then((catalog) => {
1631
+ mountAdminBrand(env.document, vendorHost, brandService, catalog);
1632
+ }).catch(() => {
1633
+ });
1634
+ }
1635
+ function faultPresetList(value) {
1636
+ if (!isRecord2(value))
1637
+ throw new Error("Fault presets response was not an object");
1638
+ if (!Object.hasOwn(value, "presets") || value.presets === void 0 || value.presets === null) {
1639
+ return { presets: [] };
1640
+ }
1641
+ if (!Array.isArray(value.presets))
1642
+ throw new Error("Fault presets response was not a list");
1643
+ return {
1644
+ presets: value.presets.flatMap((item) => {
1645
+ if (!isRecord2(item) || typeof item.name !== "string")
1646
+ return [];
1647
+ return [{ name: item.name }];
1648
+ })
1649
+ };
1650
+ }
1651
+ function bootAdmin(config) {
1652
+ const fieldKinds = ["string", "number", "boolean", "null", "object", "array", "unknown"];
1653
+ const sources = ["declared", "inferred", "mixed"];
1654
+ const isFieldKind = (value) => typeof value === "string" && fieldKinds.some((kind) => kind === value);
1655
+ const isSource = (value) => typeof value === "string" && sources.some((source) => source === value);
1656
+ const readJson2 = (text2) => {
1657
+ const value = JSON.parse(text2);
1658
+ return value;
1659
+ };
1660
+ const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => {
1661
+ switch (char) {
1662
+ case "&":
1663
+ return "&amp;";
1664
+ case "<":
1665
+ return "&lt;";
1666
+ case ">":
1667
+ return "&gt;";
1668
+ case '"':
1669
+ return "&quot;";
1670
+ default:
1671
+ return "&#39;";
1672
+ }
1673
+ });
1674
+ const el = (id, kind) => {
1675
+ const node = document.getElementById(id);
1676
+ if (!(node instanceof kind))
1677
+ throw new Error(`admin shell is missing #${id}`);
1678
+ return node;
1679
+ };
1680
+ const errorMessage = (data, fallback) => {
1681
+ if (!isRecord2(data))
1682
+ return fallback;
1683
+ const error = data.error;
1684
+ if (!isRecord2(error) || typeof error.message !== "string" || error.message === "") {
1685
+ return fallback;
1686
+ }
1687
+ return error.message;
1688
+ };
1689
+ const requireList = (value, label) => {
1690
+ if (value === void 0 || value === null)
1691
+ return [];
1692
+ if (!Array.isArray(value))
1693
+ throw new Error(`${label} was not a list`);
1694
+ return value;
1695
+ };
1696
+ const state = {
1697
+ namespace: "default",
1698
+ key: sessionStorage.getItem("mockingbird-admin-key") || "",
1699
+ view: "overview",
1700
+ collection: "",
1701
+ shape: null,
1702
+ route: null,
1703
+ editing: null
1704
+ };
1705
+ const standard = new Set(config.standardRoutes);
1706
+ const banner = el("banner", HTMLElement);
1707
+ const keyInput = el("admin-key", HTMLInputElement);
1708
+ keyInput.value = state.key;
1709
+ const params = new URLSearchParams(location.search);
1710
+ const queryKey = params.get("key");
1711
+ if (queryKey) {
1712
+ state.key = queryKey;
1713
+ keyInput.value = state.key;
1714
+ sessionStorage.setItem("mockingbird-admin-key", state.key);
1715
+ params.delete("key");
1716
+ const next = params.toString();
1717
+ history.replaceState(null, "", location.pathname + (next ? `?${next}` : ""));
1718
+ }
1719
+ const queryNamespace = params.get("namespace");
1720
+ if (queryNamespace)
1721
+ state.namespace = queryNamespace;
1722
+ const showError = (error) => {
1723
+ banner.textContent = error instanceof Error ? error.message : String(error);
1724
+ banner.classList.add("show");
1725
+ };
1726
+ const clearError = () => {
1727
+ banner.classList.remove("show");
1728
+ };
1729
+ const api = async (method, path, body) => {
1730
+ const headers = { accept: "application/json" };
1731
+ if (state.key !== "")
1732
+ headers[config.adminKeyHeader] = state.key;
1733
+ if (state.namespace !== "")
1734
+ headers["x-mockingbird-namespace"] = state.namespace;
1735
+ if (body !== void 0)
1736
+ headers["content-type"] = "application/json";
1737
+ const init = { method, headers };
1738
+ if (body !== void 0)
1739
+ init.body = JSON.stringify(body);
1740
+ const response = await fetch(config.adminPrefix + path, init);
1741
+ const text2 = await response.text();
1742
+ let data = null;
1743
+ if (text2 !== "") {
1744
+ try {
1745
+ data = readJson2(text2);
1746
+ } catch {
1747
+ data = { raw: text2 };
1748
+ }
1749
+ }
1750
+ if (response.status === 401) {
1751
+ keyInput.focus();
1752
+ throw new Error(errorMessage(data, "Admin key required"));
1753
+ }
1754
+ if (!response.ok) {
1755
+ throw new Error(errorMessage(data, `${response.status} ${method} ${path}`));
1756
+ }
1757
+ return data;
1758
+ };
1759
+ const originGet = async (path) => {
1760
+ const response = await fetch(path, { headers: { accept: "application/json" } });
1761
+ if (!response.ok)
1762
+ throw new Error(`${response.status} ${path}`);
1763
+ const value = await response.json();
1764
+ return value;
1765
+ };
1766
+ const panelApi = {
1767
+ get: (path) => api("GET", path),
1768
+ send: (method, path, body) => api(method, path, body),
1769
+ namespace: () => state.namespace
1770
+ };
1771
+ const isPanelScript = (value) => typeof value === "function";
1772
+ const setTheme = (theme) => {
1773
+ if (theme)
1774
+ document.documentElement.dataset.theme = theme;
1775
+ else
1776
+ delete document.documentElement.dataset.theme;
1777
+ };
1778
+ const storedTheme = localStorage.getItem("mockingbird-admin-theme");
1779
+ if (storedTheme)
1780
+ setTheme(storedTheme);
1781
+ el("theme", HTMLButtonElement).addEventListener("click", () => {
1782
+ const current = document.documentElement.dataset.theme;
1783
+ const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
1784
+ const next = dark ? "light" : "dark";
1785
+ localStorage.setItem("mockingbird-admin-theme", next);
1786
+ setTheme(next);
1787
+ });
1788
+ const show = (view) => {
1789
+ state.view = view;
1790
+ for (const button of document.querySelectorAll("#nav button")) {
1791
+ if (!(button instanceof HTMLButtonElement))
1792
+ continue;
1793
+ button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false");
1794
+ }
1795
+ for (const section of document.querySelectorAll("main .view")) {
1796
+ if (!(section instanceof HTMLElement))
1797
+ continue;
1798
+ section.hidden = section.id !== `view-${view}`;
1799
+ }
1800
+ };
1801
+ el("nav", HTMLElement).addEventListener("click", (event) => {
1802
+ const target = event.target;
1803
+ if (!(target instanceof Element))
1804
+ return;
1805
+ const button = target.closest("button[data-view]");
1806
+ if (!(button instanceof HTMLButtonElement))
1807
+ return;
1808
+ const view = button.dataset.view;
1809
+ if (view === void 0)
1810
+ return;
1811
+ show(view);
1812
+ refresh().catch(showError);
1813
+ });
1814
+ 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>`;
1815
+ const table = (columns, rows) => {
1816
+ if (rows.length === 0)
1817
+ return '<p class="empty">Nothing here yet.</p>';
1818
+ const head = `<tr>${columns.map((column) => `<th>${column.label}</th>`).join("")}</tr>`;
1819
+ const body = rows.map((row2) => {
1820
+ const cells = columns.map((column) => `<td data-label="${column.label}">${column.cell(row2)}</td>`).join("");
1821
+ const attrs = "attrs" in row2 && typeof row2.attrs === "string" ? row2.attrs : "";
1822
+ return `<tr${attrs}>${cells}</tr>`;
1823
+ }).join("");
1824
+ return `<table><thead>${head}</thead><tbody>${body}</tbody></table>`;
1825
+ };
1826
+ const preview = (value) => {
1827
+ const text2 = JSON.stringify(value) ?? "null";
1828
+ return esc(text2.length > 180 ? `${text2.slice(0, 180)}\u2026` : text2);
1829
+ };
1830
+ const when = (epoch) => {
1831
+ const date = new Date(epoch);
1832
+ return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString();
1833
+ };
1834
+ const clockCard = (epoch) => {
1835
+ const date = new Date(epoch);
1836
+ if (Number.isNaN(date.getTime()))
1837
+ return esc(String(epoch));
1838
+ const iso = date.toISOString();
1839
+ return `${esc(iso.slice(0, 10))}<span class="clock-time">${esc(iso.slice(11, 19))} UTC</span>`;
1840
+ };
1841
+ const decodeClock = (value) => {
1842
+ if (!isRecord2(value) || typeof value.now !== "number" || typeof value.offsetMs !== "number" || typeof value.frozen !== "boolean") {
1843
+ throw new Error("Clock response was not a clock");
1844
+ }
1845
+ return { now: value.now, offsetMs: value.offsetMs, frozen: value.frozen };
1846
+ };
1847
+ const decodeField = (value) => {
1848
+ if (!isRecord2(value) || typeof value.name !== "string" || !isFieldKind(value.kind)) {
1849
+ return void 0;
1850
+ }
1851
+ const field = {
1852
+ name: value.name,
1853
+ kind: value.kind,
1854
+ optional: value.optional === true
1855
+ };
1856
+ if (typeof value.description === "string")
1857
+ field.description = value.description;
1858
+ if (Array.isArray(value.fields)) {
1859
+ field.fields = value.fields.flatMap((item) => {
1860
+ const child = decodeField(item);
1861
+ return child === void 0 ? [] : [child];
1862
+ });
1863
+ }
1864
+ return field;
1865
+ };
1866
+ const decodeCollection = (value) => {
1867
+ if (!isRecord2(value) || typeof value.name !== "string")
1868
+ return void 0;
1869
+ const fields = Array.isArray(value.fields) ? value.fields.flatMap((item) => {
1870
+ const field = decodeField(item);
1871
+ return field === void 0 ? [] : [field];
1872
+ }) : [];
1873
+ const collection = {
1874
+ name: value.name,
1875
+ label: typeof value.label === "string" ? value.label : value.name,
1876
+ count: typeof value.count === "number" ? value.count : 0,
1877
+ source: isSource(value.source) ? value.source : "inferred",
1878
+ fields
1879
+ };
1880
+ if (typeof value.description === "string")
1881
+ collection.description = value.description;
1882
+ return collection;
1883
+ };
1884
+ const decodeState = (value) => {
1885
+ if (!isRecord2(value) || !Array.isArray(value.collections)) {
1886
+ throw new Error("State response has no collection list");
1887
+ }
1888
+ return {
1889
+ namespace: typeof value.namespace === "string" ? value.namespace : state.namespace,
1890
+ storageNamespace: typeof value.storageNamespace === "string" ? value.storageNamespace : "",
1891
+ collections: value.collections.flatMap((item) => {
1892
+ const collection = decodeCollection(item);
1893
+ return collection === void 0 ? [] : [collection];
1894
+ })
1895
+ };
1896
+ };
1897
+ const decodeRecords = (value) => requireList(isRecord2(value) ? value.records : void 0, "Records").flatMap((item) => {
1898
+ if (!isRecord2(item) || typeof item.id !== "string")
1899
+ return [];
1900
+ return [
1901
+ {
1902
+ id: item.id,
1903
+ seq: typeof item.seq === "number" ? item.seq : 0,
1904
+ value: item.value,
1905
+ attrs: ` class="clickable" data-id="${esc(item.id)}"`
1906
+ }
1907
+ ];
1908
+ });
1909
+ const textOf = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : "";
1910
+ const decodeJournal = (value) => requireList(isRecord2(value) ? value.requests : void 0, "Journal").flatMap((item) => {
1911
+ if (!isRecord2(item))
1912
+ return [];
1913
+ return [
1914
+ {
1915
+ at: textOf(item.at),
1916
+ method: textOf(item.method),
1917
+ path: textOf(item.path),
1918
+ status: textOf(item.status),
1919
+ durationMs: textOf(item.durationMs),
1920
+ operationId: textOf(item.operationId)
1921
+ }
1922
+ ];
1923
+ });
1924
+ const decodeFaults = (value) => requireList(isRecord2(value) ? value.faults : void 0, "Faults").flatMap((item) => {
1925
+ if (!isRecord2(item))
1926
+ return [];
1927
+ return [
1928
+ {
1929
+ id: textOf(item.id),
1930
+ method: typeof item.method === "string" ? item.method : void 0,
1931
+ pathPrefix: typeof item.pathPrefix === "string" ? item.pathPrefix : void 0,
1932
+ operationId: typeof item.operationId === "string" ? item.operationId : void 0,
1933
+ status: typeof item.status === "number" ? item.status : void 0,
1934
+ effect: typeof item.effect === "string" ? item.effect : void 0,
1935
+ drop: typeof item.drop === "boolean" ? item.drop : void 0,
1936
+ latencyMs: typeof item.latencyMs === "number" ? item.latencyMs : void 0
1937
+ }
1938
+ ];
1939
+ });
1940
+ const loadNamespaces = async () => {
1941
+ const data = await api("GET", "/namespaces");
1942
+ const select = el("namespace", HTMLSelectElement);
1943
+ const listed = requireList(isRecord2(data) ? data.namespaces : void 0, "Namespaces");
1944
+ const fallback = isRecord2(data) && typeof data.default === "string" && data.default !== "" ? data.default : "default";
1945
+ const names = (listed.length > 0 ? listed.map((name) => String(name)) : [fallback]).slice();
1946
+ if (!names.includes(state.namespace))
1947
+ names.unshift(state.namespace);
1948
+ select.innerHTML = names.map((name) => `<option value="${esc(name)}">${esc(name)}</option>`).join("");
1949
+ select.value = state.namespace;
1950
+ };
1951
+ const renderOverview = async () => {
1952
+ const [health, shape, clock, journal] = await Promise.all([
1953
+ originGet("/health"),
1954
+ api("GET", "/state"),
1955
+ api("GET", "/clock"),
1956
+ api("GET", "/requests?limit=8")
1957
+ ]);
1958
+ if (!isRecord2(health))
1959
+ throw new Error("Health response was not an object");
1960
+ const decoded = decodeState(shape);
1961
+ state.shape = decoded;
1962
+ const time = decodeClock(clock);
1963
+ el("overview-cards", HTMLElement).innerHTML = cards([
1964
+ {
1965
+ k: "Service",
1966
+ v: esc(typeof health.service === "string" && health.service !== "" ? health.service : config.service)
1967
+ },
1968
+ {
1969
+ k: "Status",
1970
+ v: esc(typeof health.status === "string" && health.status !== "" ? health.status : "ok")
1971
+ },
1972
+ { k: "Namespace", v: esc(state.namespace) },
1973
+ { k: "Collections", v: String(decoded.collections.length) },
1974
+ {
1975
+ k: "Records",
1976
+ v: String(decoded.collections.reduce((sum, item) => sum + item.count, 0))
1977
+ },
1978
+ { k: "Clock", v: clockCard(time.now) }
1979
+ ]);
1980
+ el("overview-requests", HTMLElement).innerHTML = table([
1981
+ { label: "Method", cell: (row2) => esc(row2.method) },
1982
+ { label: "Path", cell: (row2) => esc(row2.path) },
1983
+ {
1984
+ label: "Status",
1985
+ cell: (row2) => `<span class="status-${row2.status.charAt(0)}">${esc(row2.status)}</span>`
1986
+ },
1987
+ { label: "Operation", cell: (row2) => esc(row2.operationId) }
1988
+ ], decodeJournal(journal));
1989
+ };
1990
+ const schemaChips = (collection) => {
1991
+ if (collection.fields.length === 0) {
1992
+ return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>';
1993
+ }
1994
+ return `<div class="chips">${collection.fields.map((field) => `<span class="chip">${esc(field.name)} <i>${esc(field.kind)}${field.optional ? "?" : ""}</i></span>`).join("")}</div>`;
1995
+ };
1996
+ const renderState = async () => {
1997
+ const shape = decodeState(await api("GET", "/state"));
1998
+ state.shape = shape;
1999
+ const host = el("collections", HTMLElement);
2000
+ if (shape.collections.length === 0) {
2001
+ host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>';
2002
+ el("schema", HTMLElement).innerHTML = "";
2003
+ el("records", HTMLElement).innerHTML = "";
2004
+ return;
2005
+ }
2006
+ if (!shape.collections.some((item) => item.name === state.collection)) {
2007
+ const first = shape.collections[0];
2008
+ if (first === void 0)
2009
+ return;
2010
+ state.collection = first.name;
2011
+ }
2012
+ 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("");
2013
+ const selected = shape.collections.find((item) => item.name === state.collection);
2014
+ if (selected === void 0)
2015
+ return;
2016
+ const description = selected.description ? `<p class="lede">${esc(selected.description)}</p>` : "";
2017
+ el("schema", HTMLElement).innerHTML = `<h3>${esc(selected.label)}</h3>${description}${schemaChips(selected)}`;
2018
+ const page = await api("GET", `/state/${encodeURIComponent(state.collection)}?limit=50`);
2019
+ el("records", HTMLElement).innerHTML = table([
2020
+ { label: "Id", cell: (row2) => `<code>${esc(row2.id)}</code>` },
2021
+ { label: "Seq", cell: (row2) => esc(row2.seq) },
2022
+ { label: "Value", cell: (row2) => preview(row2.value) }
2023
+ ], decodeRecords(page));
2024
+ };
2025
+ const renderClock = async () => {
2026
+ const clock = decodeClock(await api("GET", "/clock"));
2027
+ 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>`;
2028
+ const freeze = el("freeze", HTMLButtonElement);
2029
+ freeze.textContent = clock.frozen ? "Unfreeze" : "Freeze";
2030
+ freeze.dataset.frozen = clock.frozen ? "1" : "0";
2031
+ };
2032
+ const renderFaults = async () => {
2033
+ const [faults, presets] = await Promise.all([
2034
+ api("GET", "/faults"),
2035
+ api("GET", "/faults/presets").catch(() => ({ presets: [] }))
2036
+ ]);
2037
+ const select = el("fault-preset", HTMLSelectElement);
2038
+ const current = select.value;
2039
+ const list = faultPresetList(presets).presets;
2040
+ select.innerHTML = `<option value="">Custom rule</option>${list.map((preset) => `<option value="${esc(preset.name)}">${esc(preset.name)}</option>`).join("")}`;
2041
+ select.value = current;
2042
+ el("fault-list", HTMLElement).innerHTML = table([
2043
+ { label: "Id", cell: (row2) => esc(row2.id) },
2044
+ {
2045
+ label: "Match",
2046
+ cell: (row2) => esc([row2.method, row2.pathPrefix, row2.operationId].filter((part) => Boolean(part)).join(" ") || "all")
2047
+ },
2048
+ {
2049
+ label: "Effect",
2050
+ cell: (row2) => esc(row2.status ?? row2.effect ?? (row2.drop ? "drop" : row2.latencyMs || ""))
2051
+ }
2052
+ ], decodeFaults(faults));
2053
+ };
2054
+ const renderJournal = async () => {
2055
+ const journal = await api("GET", "/requests?limit=100");
2056
+ el("journal", HTMLElement).innerHTML = table([
2057
+ { label: "When", cell: (row2) => esc(row2.at) },
2058
+ { label: "Method", cell: (row2) => esc(row2.method) },
2059
+ { label: "Path", cell: (row2) => esc(row2.path) },
2060
+ { label: "Status", cell: (row2) => esc(row2.status) },
2061
+ { label: "ms", cell: (row2) => esc(row2.durationMs) }
2062
+ ], decodeJournal(journal));
2063
+ };
2064
+ const renderRoutes = async () => {
2065
+ const data = await api("GET", "/");
2066
+ if (!isRecord2(data))
2067
+ throw new Error("Route list was not an object");
2068
+ const routes = requireList(data.routes, "Routes").map((route) => String(route));
2069
+ el("route-list", HTMLElement).innerHTML = routes.map((route) => {
2070
+ const extra = standard.has(route) ? "" : " <span class='badge'>extension</span>";
2071
+ const parts = route.split(" ");
2072
+ const method = parts[0] ?? "";
2073
+ const path = parts.slice(1).join(" ");
2074
+ 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>`;
2075
+ }).join("");
2076
+ };
2077
+ const renderers = {
2078
+ overview: renderOverview,
2079
+ state: renderState,
2080
+ clock: renderClock,
2081
+ faults: renderFaults,
2082
+ journal: renderJournal,
2083
+ routes: renderRoutes
2084
+ };
2085
+ async function refresh() {
2086
+ clearError();
2087
+ const render = renderers[state.view];
2088
+ if (render)
2089
+ await render();
2090
+ }
2091
+ el("namespace", HTMLSelectElement).addEventListener("change", () => {
2092
+ state.namespace = el("namespace", HTMLSelectElement).value;
2093
+ refresh().catch(showError);
2094
+ });
2095
+ keyInput.addEventListener("change", () => {
2096
+ state.key = keyInput.value.trim();
2097
+ sessionStorage.setItem("mockingbird-admin-key", state.key);
2098
+ refresh().catch(showError);
2099
+ });
2100
+ el("refresh", HTMLButtonElement).addEventListener("click", () => {
2101
+ refresh().catch(showError);
2102
+ });
2103
+ el("collections", HTMLElement).addEventListener("click", (event) => {
2104
+ const target = event.target;
2105
+ if (!(target instanceof Element))
2106
+ return;
2107
+ const button = target.closest("[data-collection]");
2108
+ if (!(button instanceof HTMLElement))
2109
+ return;
2110
+ const collection = button.dataset.collection;
2111
+ if (collection === void 0)
2112
+ return;
2113
+ state.collection = collection;
2114
+ renderState().catch(showError);
2115
+ });
2116
+ el("records", HTMLElement).addEventListener("click", (event) => {
2117
+ const target = event.target;
2118
+ if (!(target instanceof Element))
2119
+ return;
2120
+ const row2 = target.closest("[data-id]");
2121
+ if (!(row2 instanceof HTMLElement))
2122
+ return;
2123
+ const id = row2.dataset.id;
2124
+ if (id === void 0)
2125
+ return;
2126
+ openEditor(id).catch(showError);
2127
+ });
2128
+ const editor = el("editor", HTMLDialogElement);
2129
+ const editorId = el("editor-id", HTMLInputElement);
2130
+ const editorValue = el("editor-value", HTMLTextAreaElement);
2131
+ const editorDelete = el("editor-delete", HTMLButtonElement);
2132
+ async function openEditor(id) {
2133
+ state.editing = id;
2134
+ el("editor-title", HTMLElement).textContent = id ? "Edit record" : "New record";
2135
+ editorId.value = id ?? "";
2136
+ editorId.readOnly = id !== null;
2137
+ editorDelete.hidden = id === null;
2138
+ if (id) {
2139
+ const record = await api("GET", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`);
2140
+ if (!isRecord2(record))
2141
+ throw new Error("Record response was not an object");
2142
+ editorValue.value = JSON.stringify(record.value, null, 2) ?? "";
2143
+ } else {
2144
+ editorValue.value = "{\n \n}";
2145
+ }
2146
+ editor.showModal();
2147
+ }
2148
+ el("new-record", HTMLButtonElement).addEventListener("click", () => {
2149
+ openEditor(null).catch(showError);
2150
+ });
2151
+ el("editor-form", HTMLFormElement).addEventListener("submit", (event) => {
2152
+ const submitter = event.submitter;
2153
+ if (!(submitter instanceof HTMLButtonElement) || submitter.value !== "save")
2154
+ return;
2155
+ event.preventDefault();
2156
+ let value;
2157
+ try {
2158
+ value = readJson2(editorValue.value);
2159
+ } catch {
2160
+ showError(new Error("Value is not JSON"));
2161
+ return;
2162
+ }
2163
+ const id = editorId.value.trim();
2164
+ const path = `/state/${encodeURIComponent(state.collection)}`;
2165
+ const request = state.editing ? api("PUT", `${path}/${encodeURIComponent(id)}`, { value }) : api("POST", path, { id: id || void 0, value });
2166
+ request.then(() => {
2167
+ editor.close();
2168
+ return renderState();
2169
+ }).catch(showError);
2170
+ });
2171
+ editorDelete.addEventListener("click", () => {
2172
+ const id = state.editing;
2173
+ if (!id)
2174
+ return;
2175
+ api("DELETE", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`).then(() => {
2176
+ editor.close();
2177
+ return renderState();
2178
+ }).catch(showError);
2179
+ });
2180
+ for (const button of document.querySelectorAll("[data-advance]")) {
2181
+ if (!(button instanceof HTMLButtonElement))
2182
+ continue;
2183
+ button.addEventListener("click", () => {
2184
+ const raw = button.dataset.advance;
2185
+ if (raw === void 0)
2186
+ return;
2187
+ api("POST", "/clock", { advance: Number(raw) }).then(() => renderClock()).catch(showError);
2188
+ });
2189
+ }
2190
+ el("freeze", HTMLButtonElement).addEventListener("click", () => {
2191
+ const frozen = el("freeze", HTMLButtonElement).dataset.frozen === "1";
2192
+ api("POST", "/clock", { freeze: !frozen }).then(() => renderClock()).catch(showError);
2193
+ });
2194
+ el("clock-reset", HTMLButtonElement).addEventListener("click", () => {
2195
+ api("POST", "/clock", { reset: true }).then(() => renderClock()).catch(showError);
2196
+ });
2197
+ el("clock-set", HTMLFormElement).addEventListener("submit", (event) => {
2198
+ event.preventDefault();
2199
+ const raw = el("clock-instant", HTMLInputElement).value.trim();
2200
+ const set = /^-?\d+$/.test(raw) ? Number(raw) : raw;
2201
+ api("POST", "/clock", { set }).then(() => renderClock()).catch(showError);
2202
+ });
2203
+ el("fault-form", HTMLFormElement).addEventListener("submit", (event) => {
2204
+ event.preventDefault();
2205
+ const preset = el("fault-preset", HTMLSelectElement).value;
2206
+ if (preset !== "") {
2207
+ api("POST", "/faults", { preset }).then(() => renderFaults()).catch(showError);
2208
+ return;
2209
+ }
2210
+ const status = Number(el("fault-status", HTMLInputElement).value);
2211
+ if (!Number.isFinite(status)) {
2212
+ showError(new Error("Status must be a number, or choose a preset"));
2213
+ return;
2214
+ }
2215
+ const path = el("fault-path", HTMLInputElement).value;
2216
+ const method = el("fault-method", HTMLInputElement).value;
2217
+ api("POST", "/faults", {
2218
+ status,
2219
+ ...path !== "" ? { pathPrefix: path } : {},
2220
+ ...method !== "" ? { method } : {}
2221
+ }).then(() => renderFaults()).catch(showError);
2222
+ });
2223
+ el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
2224
+ api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
2225
+ });
2226
+ el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
2227
+ api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
2228
+ });
2229
+ el("route-list", HTMLElement).addEventListener("click", (event) => {
2230
+ const target = event.target;
2231
+ if (!(target instanceof Element))
2232
+ return;
2233
+ const button = target.closest("[data-route]");
2234
+ if (!(button instanceof HTMLElement))
2235
+ return;
2236
+ const route = button.dataset.route;
2237
+ if (route === void 0)
2238
+ return;
2239
+ state.route = route;
2240
+ el("route-title", HTMLElement).textContent = route;
2241
+ const method = route.split(" ")[0] ?? "";
2242
+ el("route-body", HTMLTextAreaElement).disabled = method === "GET" || method === "DELETE";
2243
+ });
2244
+ el("route-form", HTMLFormElement).addEventListener("submit", (event) => {
2245
+ event.preventDefault();
2246
+ const send = async () => {
2247
+ if (state.route === null)
2248
+ throw new Error("Choose a route first");
2249
+ const parts = state.route.split(" ");
2250
+ const method = parts[0];
2251
+ const path = parts.slice(1).join(" ");
2252
+ if (method === void 0)
2253
+ throw new Error("Choose a route first");
2254
+ const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_match, key) => prompt(`Value for ${key}`) || "");
2255
+ let body;
2256
+ const routeBody = el("route-body", HTMLTextAreaElement);
2257
+ if (method !== "GET" && method !== "DELETE" && routeBody.value.trim() !== "") {
2258
+ try {
2259
+ body = readJson2(routeBody.value);
2260
+ } catch {
2261
+ throw new Error("Body is not JSON");
2262
+ }
2263
+ }
2264
+ const data = await api(method, concrete, body);
2265
+ el("route-result", HTMLElement).textContent = JSON.stringify(data, null, 2);
2266
+ };
2267
+ send().catch(showError);
2268
+ });
2269
+ const decodePanel = (value) => {
2270
+ if (!isRecord2(value) || typeof value.id !== "string" || typeof value.title !== "string") {
2271
+ return void 0;
2272
+ }
2273
+ const panel = {
2274
+ id: value.id,
2275
+ title: value.title,
2276
+ description: typeof value.description === "string" ? value.description : "",
2277
+ html: typeof value.html === "string" ? value.html : ""
2278
+ };
2279
+ if (typeof value.script === "string")
2280
+ panel.script = value.script;
2281
+ return panel;
2282
+ };
2283
+ const mountPanels = (panels) => {
2284
+ const nav = el("nav", HTMLElement);
2285
+ const views = el("views", HTMLElement);
2286
+ for (const panel of panels) {
2287
+ const button = document.createElement("button");
2288
+ button.type = "button";
2289
+ button.dataset.view = panel.id;
2290
+ button.textContent = panel.title;
2291
+ nav.append(button);
2292
+ const section = document.createElement("section");
2293
+ section.className = "view";
2294
+ section.id = `view-${panel.id}`;
2295
+ section.hidden = true;
2296
+ const title = document.createElement("h2");
2297
+ title.textContent = panel.title;
2298
+ const lede = document.createElement("p");
2299
+ lede.className = "lede";
2300
+ lede.textContent = panel.description;
2301
+ const body = document.createElement("div");
2302
+ body.className = "panel-body";
2303
+ body.id = `panel-${panel.id}`;
2304
+ body.innerHTML = panel.html;
2305
+ section.append(title, lede, body);
2306
+ views.append(section);
2307
+ if (panel.script === void 0)
2308
+ continue;
2309
+ try {
2310
+ const compiled = new Function("root", "api", panel.script);
2311
+ if (!isPanelScript(compiled))
2312
+ throw new Error("panel script did not compile");
2313
+ compiled(body, panelApi);
2314
+ } catch (error) {
2315
+ const note = document.createElement("p");
2316
+ note.className = "banner show";
2317
+ note.textContent = error instanceof Error ? error.message : String(error);
2318
+ body.append(note);
2319
+ }
2320
+ }
2321
+ };
2322
+ startAdminBrand({
2323
+ document,
2324
+ $: (id) => document.getElementById(id),
2325
+ params,
2326
+ location: { href: location.href, origin: location.origin },
2327
+ fetch,
2328
+ brandsUrl: config.brandsUrl
2329
+ });
2330
+ loadNamespaces().then(() => api("GET", "/ui/manifest")).then((manifest) => {
2331
+ const panels = requireList(isRecord2(manifest) ? manifest.panels : void 0, "Panels").flatMap((panel) => {
2332
+ const decoded = decodePanel(panel);
2333
+ return decoded === void 0 ? [] : [decoded];
2334
+ });
2335
+ mountPanels(panels);
2336
+ return refresh();
2337
+ }).catch(showError);
2338
+ }
2339
+ var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
2340
+ var adminClientSource = (config) => {
2341
+ const payload = JSON.stringify(config).replace(/</g, "\\u003c");
2342
+ return `<script>
2343
+ ${clientPrelude}
2344
+ bootAdmin(${payload});
2345
+ </script>`;
2346
+ };
2347
+
1122
2348
  // ../core/dist/surface.js
1123
2349
  var defineMock = (runtime) => runtime;
1124
2350
  var STANDARD_ADMIN_ROUTES = [
@@ -1187,94 +2413,106 @@ var escapeHtml = (value) => value.replace(/[&<>"']/g, (char) => {
1187
2413
  return "&#39;";
1188
2414
  }
1189
2415
  });
2416
+ var ADMIN_BRANDS_URL = "https://mockingbird.chrisvouga.dev/brands.json";
1190
2417
  var renderAdminDocument = (service) => {
1191
2418
  const name = escapeHtml(service);
1192
- const standard = JSON.stringify(STANDARD_ADMIN_ROUTES);
2419
+ const script = adminClientSource({
2420
+ standardRoutes: STANDARD_ADMIN_ROUTES,
2421
+ adminPrefix: ADMIN_PREFIX,
2422
+ adminKeyHeader: ADMIN_KEY_HEADER,
2423
+ brandsUrl: ADMIN_BRANDS_URL,
2424
+ service
2425
+ });
1193
2426
  return `<!DOCTYPE html>
1194
2427
  <html lang="en">
1195
2428
  <head>
1196
2429
  <meta charset="utf-8">
1197
2430
  <meta name="viewport" content="width=device-width, initial-scale=1">
1198
2431
  <meta name="color-scheme" content="light dark">
1199
- <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">
2432
+ <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">
1200
2433
  <title>${name} admin</title>
1201
2434
  <style>
2435
+ ${CSS_RESET}
1202
2436
  :root {
1203
2437
  color-scheme: light;
1204
- --bg: #f6f5f2;
2438
+ --bg: #fafafa;
1205
2439
  --bg-elev: #ffffff;
1206
- --bg-muted: #eeece6;
1207
- --bg-inset: #f3f1eb;
1208
- --line: #e2dfd6;
1209
- --line-strong: #d0ccc1;
1210
- --ink: #1c1915;
1211
- --muted: #5e584f;
1212
- --faint: #8a8378;
1213
- --accent: #3d4f3a;
1214
- --accent-ink: #f7f6f2;
1215
- --accent-soft: #e5ece2;
1216
- --ok: #1f7a45;
1217
- --ok-soft: #e5f4eb;
1218
- --warn: #9a6700;
1219
- --warn-soft: #fbf3df;
1220
- --err: #a33b32;
1221
- --err-soft: #fdecea;
1222
- --shadow: 0 1px 2px rgb(28 25 21 / 0.05), 0 12px 32px rgb(28 25 21 / 0.06);
1223
- --sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
2440
+ --bg-muted: #f4f4f5;
2441
+ --bg-inset: #f4f4f5;
2442
+ --line: #dedee3;
2443
+ --line-strong: #c4c4cc;
2444
+ --ink: #18181b;
2445
+ --muted: #62626b;
2446
+ --faint: #71717a;
2447
+ --accent: #27272a;
2448
+ --accent-ink: #ffffff;
2449
+ --accent-soft: #f4f4f5;
2450
+ --ok: #166534;
2451
+ --ok-soft: #f0fdf4;
2452
+ --warn: #a16207;
2453
+ --warn-soft: #fefce8;
2454
+ --err: #a82d32;
2455
+ --err-soft: #fef2f2;
2456
+ --shadow: none;
2457
+ --sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
1224
2458
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
1225
- --radius: 14px;
1226
- --header: 60px;
1227
- --nav: 220px;
2459
+ --radius: 10px;
2460
+ --header: 56px;
2461
+ --nav: 200px;
1228
2462
  }
1229
2463
  :root[data-theme="dark"] {
1230
2464
  color-scheme: dark;
1231
- --bg: #141311;
1232
- --bg-elev: #1e1c19;
1233
- --bg-muted: #26231f;
1234
- --bg-inset: #181715;
1235
- --line: #34302b;
1236
- --line-strong: #4a453d;
1237
- --ink: #f4f1ea;
1238
- --muted: #c4bdb1;
1239
- --faint: #8f877c;
1240
- --accent: #c5d5b8;
1241
- --accent-ink: #1a2118;
1242
- --accent-soft: #2a3328;
1243
- --ok: #8fd6a8;
1244
- --ok-soft: #1a2e22;
1245
- --warn: #f0cc78;
1246
- --warn-soft: #332911;
1247
- --err: #f0a8a2;
1248
- --err-soft: #3a201e;
1249
- --shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 16px 40px rgb(0 0 0 / 0.28);
2465
+ --bg: #111113;
2466
+ --bg-elev: #19191c;
2467
+ --bg-muted: #242428;
2468
+ --bg-inset: #111113;
2469
+ --line: #36363c;
2470
+ --line-strong: #52525b;
2471
+ --ink: #f4f4f5;
2472
+ --muted: #a9a9b2;
2473
+ --faint: #71717a;
2474
+ --accent: #e4e4e7;
2475
+ --accent-ink: #18181b;
2476
+ --accent-soft: #242428;
2477
+ --ok: #86efac;
2478
+ --ok-soft: #14241b;
2479
+ --warn: #fde68a;
2480
+ --warn-soft: #2a2410;
2481
+ --err: #ffaaaa;
2482
+ --err-soft: #2c1516;
2483
+ --shadow: none;
1250
2484
  }
1251
2485
  @media (prefers-color-scheme: dark) {
1252
2486
  :root:not([data-theme="light"]) {
1253
2487
  color-scheme: dark;
1254
- --bg: #141311;
1255
- --bg-elev: #1e1c19;
1256
- --bg-muted: #26231f;
1257
- --bg-inset: #181715;
1258
- --line: #34302b;
1259
- --line-strong: #4a453d;
1260
- --ink: #f4f1ea;
1261
- --muted: #c4bdb1;
1262
- --faint: #8f877c;
1263
- --accent: #c5d5b8;
1264
- --accent-ink: #1a2118;
1265
- --accent-soft: #2a3328;
1266
- --ok: #8fd6a8;
1267
- --ok-soft: #1a2e22;
1268
- --warn: #f0cc78;
1269
- --warn-soft: #332911;
1270
- --err: #f0a8a2;
1271
- --err-soft: #3a201e;
1272
- --shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 16px 40px rgb(0 0 0 / 0.28);
2488
+ --bg: #111113;
2489
+ --bg-elev: #19191c;
2490
+ --bg-muted: #242428;
2491
+ --bg-inset: #111113;
2492
+ --line: #36363c;
2493
+ --line-strong: #52525b;
2494
+ --ink: #f4f4f5;
2495
+ --muted: #a9a9b2;
2496
+ --faint: #71717a;
2497
+ --accent: #e4e4e7;
2498
+ --accent-ink: #18181b;
2499
+ --accent-soft: #242428;
2500
+ --ok: #86efac;
2501
+ --ok-soft: #14241b;
2502
+ --warn: #fde68a;
2503
+ --warn-soft: #2a2410;
2504
+ --err: #ffaaaa;
2505
+ --err-soft: #2c1516;
2506
+ --shadow: none;
1273
2507
  }
1274
2508
  }
1275
- * { box-sizing: border-box; }
1276
- html, body { margin: 0; height: 100%; }
2509
+ /* Fill the viewport, then grow with the content so the background covers the scroll. */
2510
+ html { height: 100%; }
1277
2511
  body {
2512
+ margin: 0;
2513
+ min-height: 100%;
2514
+ display: flex;
2515
+ flex-direction: column;
1278
2516
  font-family: var(--sans);
1279
2517
  background: var(--bg);
1280
2518
  color: var(--ink);
@@ -1283,24 +2521,37 @@ var renderAdminDocument = (service) => {
1283
2521
  padding-right: env(safe-area-inset-right);
1284
2522
  -webkit-text-size-adjust: 100%;
1285
2523
  }
1286
- button, input, select, textarea { font: inherit; color: inherit; }
1287
- button { cursor: pointer; }
1288
2524
  :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
1289
- @media (prefers-reduced-motion: reduce) {
1290
- * { scroll-behavior: auto !important; transition: none !important; }
1291
- }
1292
- .app { min-height: 100%; display: flex; flex-direction: column; }
2525
+ .app { flex: 1 0 auto; display: flex; flex-direction: column; min-width: 0; width: 100%; }
1293
2526
  header.top {
1294
2527
  position: sticky; top: 0; z-index: 5;
1295
2528
  display: flex; align-items: center; gap: 12px;
1296
2529
  min-height: var(--header); padding: 10px 16px;
1297
- background: color-mix(in srgb, var(--bg-elev) 92%, transparent);
2530
+ background: var(--bg-elev);
1298
2531
  border-bottom: 1px solid var(--line);
1299
- backdrop-filter: blur(10px);
1300
- }
1301
- .brand { display: flex; flex-direction: column; min-width: 0; margin-right: auto; }
1302
- .brand strong { font-size: 15px; letter-spacing: -0.01em; }
1303
- .brand span { color: var(--faint); font-size: 12px; }
2532
+ min-width: 0; max-width: 100%;
2533
+ }
2534
+ .brand {
2535
+ display: flex; flex-flow: row wrap; align-items: center; gap: 14px;
2536
+ min-width: 0; max-width: 100%; margin-right: auto;
2537
+ }
2538
+ .brand-id { display: flex; flex-direction: column; min-width: 0; flex: none; }
2539
+ .brand-id strong { font-size: 15px; letter-spacing: -0.01em; }
2540
+ .brand-id span { color: var(--faint); font-size: 12px; }
2541
+ .vendor {
2542
+ display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
2543
+ min-width: 0; max-width: 100%; font-size: 12px; color: var(--muted);
2544
+ }
2545
+ .vendor[hidden] { display: none; }
2546
+ /* Logos are drawn for a light tile, same as the docs site, so the plate stays white. */
2547
+ .vendor img {
2548
+ width: 16px; height: 16px; padding: 2px; box-sizing: content-box;
2549
+ border-radius: 4px; border: 1px solid var(--line); background: #fff; object-fit: contain;
2550
+ }
2551
+ .vendor-name { color: var(--ink); font-weight: 600; }
2552
+ .vendor-sep { color: var(--faint); }
2553
+ .vendor-link { color: inherit; text-decoration: none; }
2554
+ .vendor-link:hover { color: var(--ink); text-decoration: underline; }
1304
2555
  .controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
1305
2556
  label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
1306
2557
  label.field input, label.field select, .controls input[type="password"] {
@@ -1318,7 +2569,7 @@ var renderAdminDocument = (service) => {
1318
2569
  record or the full nav row would widen the page past the viewport. */
1319
2570
  .layout {
1320
2571
  display: grid; grid-template-columns: var(--nav) minmax(0, 1fr);
1321
- flex: 1; min-height: 0; min-width: 0;
2572
+ flex: 1 0 auto; min-width: 0;
1322
2573
  }
1323
2574
  nav.side {
1324
2575
  border-right: 1px solid var(--line); padding: 12px;
@@ -1333,7 +2584,7 @@ var renderAdminDocument = (service) => {
1333
2584
  main { padding: 20px; max-width: 1100px; width: 100%; min-width: 0; }
1334
2585
  .view h2 { margin: 0 0 4px; font-size: 22px; letter-spacing: -0.02em; }
1335
2586
  .lede { margin: 0 0 16px; color: var(--muted); }
1336
- .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
2587
+ .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr)); gap: 12px; }
1337
2588
  .card {
1338
2589
  background: var(--bg-elev); border: 1px solid var(--line); border-radius: var(--radius);
1339
2590
  padding: 14px 16px; box-shadow: var(--shadow);
@@ -1372,8 +2623,8 @@ var renderAdminDocument = (service) => {
1372
2623
  .banner.show { display: block; }
1373
2624
  .empty { color: var(--muted); padding: 24px 8px; }
1374
2625
  dialog {
1375
- border: 1px solid var(--line); border-radius: 16px; padding: 0; background: var(--bg-elev);
1376
- color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow: var(--shadow);
2626
+ border: 1px solid var(--line); border-radius: var(--radius); padding: 0; background: var(--bg-elev);
2627
+ color: var(--ink); width: min(640px, calc(100% - 24px)); max-width: calc(100vw - 24px); box-shadow: none;
1377
2628
  }
1378
2629
  dialog::backdrop { background: rgb(20 19 17 / 0.45); }
1379
2630
  dialog form, .dialog-body { padding: 16px; display: flex; flex-direction: column; gap: 10px; }
@@ -1400,7 +2651,7 @@ var renderAdminDocument = (service) => {
1400
2651
  padding-top: max(10px, env(safe-area-inset-top));
1401
2652
  }
1402
2653
  input, select, textarea { font-size: 16px; }
1403
- .controls { width: 100%; }
2654
+ .brand, .vendor, .controls { width: 100%; }
1404
2655
  label.field, label.field select, label.field input { flex: 1; min-width: 0; }
1405
2656
  .layout { grid-template-columns: minmax(0, 1fr); }
1406
2657
  nav.side {
@@ -1431,8 +2682,11 @@ var renderAdminDocument = (service) => {
1431
2682
  <div class="app" data-service="${name}" data-mockingbird-admin>
1432
2683
  <header class="top">
1433
2684
  <div class="brand">
1434
- <strong>${name}</strong>
1435
- <span>Mockingbird admin</span>
2685
+ <div class="brand-id">
2686
+ <strong>${name}</strong>
2687
+ <span>Admin</span>
2688
+ </div>
2689
+ <div class="vendor" id="vendor" hidden></div>
1436
2690
  </div>
1437
2691
  <div class="controls">
1438
2692
  <label class="field">Namespace
@@ -1562,460 +2816,7 @@ var renderAdminDocument = (service) => {
1562
2816
  </div>
1563
2817
  </form>
1564
2818
  </dialog>
1565
- <script>
1566
- (() => {
1567
- const STANDARD = new Set(${standard})
1568
- const state = {
1569
- namespace: "default",
1570
- key: sessionStorage.getItem("mockingbird-admin-key") || "",
1571
- view: "overview",
1572
- collection: "",
1573
- shape: null,
1574
- route: null,
1575
- editing: null
1576
- }
1577
- const $ = (id) => document.getElementById(id)
1578
- const banner = $("banner")
1579
- const keyInput = $("admin-key")
1580
- keyInput.value = state.key
1581
- const params = new URLSearchParams(location.search)
1582
- if (params.get("key")) {
1583
- state.key = params.get("key")
1584
- keyInput.value = state.key
1585
- sessionStorage.setItem("mockingbird-admin-key", state.key)
1586
- params.delete("key")
1587
- const next = params.toString()
1588
- history.replaceState(null, "", location.pathname + (next ? "?" + next : ""))
1589
- }
1590
- if (params.get("namespace")) state.namespace = params.get("namespace")
1591
-
1592
- const showError = (error) => {
1593
- banner.textContent = error instanceof Error ? error.message : String(error)
1594
- banner.classList.add("show")
1595
- }
1596
- const clearError = () => banner.classList.remove("show")
1597
-
1598
- async function originGet(path) {
1599
- const response = await fetch(path, { headers: { accept: "application/json" } })
1600
- if (!response.ok) throw new Error(response.status + " " + path)
1601
- return response.json()
1602
- }
1603
- async function api(method, path, body) {
1604
- const headers = { accept: "application/json" }
1605
- if (state.key) headers["${ADMIN_KEY_HEADER}"] = state.key
1606
- if (state.namespace) headers["x-mockingbird-namespace"] = state.namespace
1607
- if (body !== undefined) headers["content-type"] = "application/json"
1608
- const response = await fetch("${ADMIN_PREFIX}" + path, {
1609
- method,
1610
- headers,
1611
- body: body === undefined ? undefined : JSON.stringify(body)
1612
- })
1613
- const text = await response.text()
1614
- let data = null
1615
- if (text) {
1616
- try { data = JSON.parse(text) } catch { data = { raw: text } }
1617
- }
1618
- if (response.status === 401) {
1619
- keyInput.focus()
1620
- throw new Error((data && data.error && data.error.message) || "Admin key required")
1621
- }
1622
- if (!response.ok) {
1623
- throw new Error((data && data.error && data.error.message) || (response.status + " " + method + " " + path))
1624
- }
1625
- return data
1626
- }
1627
- const panelApi = {
1628
- get: (path) => api("GET", path),
1629
- send: (method, path, body) => api(method, path, body),
1630
- namespace: () => state.namespace
1631
- }
1632
-
1633
- function setTheme(theme) {
1634
- if (theme) document.documentElement.dataset.theme = theme
1635
- else delete document.documentElement.dataset.theme
1636
- }
1637
- const storedTheme = localStorage.getItem("mockingbird-admin-theme")
1638
- if (storedTheme) setTheme(storedTheme)
1639
- $("theme").addEventListener("click", () => {
1640
- const current = document.documentElement.dataset.theme
1641
- const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches
1642
- const next = dark ? "light" : "dark"
1643
- localStorage.setItem("mockingbird-admin-theme", next)
1644
- setTheme(next)
1645
- })
1646
-
1647
- function show(view) {
1648
- state.view = view
1649
- for (const button of document.querySelectorAll("#nav button")) {
1650
- button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false")
1651
- }
1652
- for (const section of document.querySelectorAll("main .view")) {
1653
- section.hidden = section.id !== "view-" + view
1654
- }
1655
- }
1656
- document.getElementById("nav").addEventListener("click", (event) => {
1657
- const button = event.target.closest("button[data-view]")
1658
- if (!button) return
1659
- show(button.dataset.view)
1660
- refresh().catch(showError)
1661
- })
1662
-
1663
- function cards(items) {
1664
- return '<div class="grid">' + items.map((item) =>
1665
- '<article class="card"><div class="k">' + item.k + '</div><div class="v">' + item.v + '</div></article>'
1666
- ).join("") + "</div>"
1667
- }
1668
- function table(columns, rows) {
1669
- if (rows.length === 0) 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) => "<tr" + (row.attrs || "") + ">" + columns.map((column) =>
1672
- '<td data-label="' + column.label + '">' + column.cell(row) + "</td>"
1673
- ).join("") + "</tr>").join("")
1674
- return "<table><thead>" + head + "</thead><tbody>" + body + "</tbody></table>"
1675
- }
1676
- const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({
1677
- "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"
1678
- }[char]))
1679
- const preview = (value) => {
1680
- const text = JSON.stringify(value)
1681
- return esc(text.length > 180 ? text.slice(0, 180) + "\u2026" : text)
1682
- }
1683
- const when = (epoch) => {
1684
- const date = new Date(epoch)
1685
- return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString()
1686
- }
1687
- const clockCard = (epoch) => {
1688
- const date = new Date(epoch)
1689
- if (Number.isNaN(date.getTime())) 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
-
1694
- async function loadNamespaces() {
1695
- const data = await api("GET", "/namespaces")
1696
- const select = $("namespace")
1697
- const names = data.namespaces && data.namespaces.length ? data.namespaces : [data.default || "default"]
1698
- if (!names.includes(state.namespace)) names.unshift(state.namespace)
1699
- select.innerHTML = names.map((name) =>
1700
- '<option value="' + esc(name) + '">' + esc(name) + "</option>"
1701
- ).join("")
1702
- select.value = state.namespace
1703
- }
1704
-
1705
- async function renderOverview() {
1706
- const [health, shape, clock, journal] = await Promise.all([
1707
- originGet("/health"),
1708
- api("GET", "/state"),
1709
- api("GET", "/clock"),
1710
- api("GET", "/requests?limit=8")
1711
- ])
1712
- state.shape = shape
1713
- $("overview-cards").innerHTML = cards([
1714
- { k: "Service", v: esc(health.service || "${name}") },
1715
- { k: "Status", v: esc(health.status || "ok") },
1716
- { k: "Namespace", v: esc(state.namespace) },
1717
- { k: "Collections", v: String(shape.collections.length) },
1718
- { k: "Records", v: String(shape.collections.reduce((sum, item) => sum + item.count, 0)) },
1719
- { k: "Clock", v: clockCard(clock.now) }
1720
- ])
1721
- const requests = (journal && journal.requests) || []
1722
- $("overview-requests").innerHTML = table(
1723
- [
1724
- { label: "Method", cell: (row) => esc(row.method) },
1725
- { label: "Path", cell: (row) => esc(row.path) },
1726
- { label: "Status", cell: (row) => '<span class="status-' + String(row.status).charAt(0) + '">' + esc(row.status) + "</span>" },
1727
- { label: "Operation", cell: (row) => esc(row.operationId || "") }
1728
- ],
1729
- requests
1730
- )
1731
- }
1732
-
1733
- function schemaChips(collection) {
1734
- if (!collection || collection.fields.length === 0) {
1735
- return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>'
1736
- }
1737
- return '<div class="chips">' + collection.fields.map((field) =>
1738
- '<span class="chip">' + esc(field.name) + ' <i>' + esc(field.kind) + (field.optional ? "?" : "") + "</i></span>"
1739
- ).join("") + "</div>"
1740
- }
1741
-
1742
- async function renderState() {
1743
- const shape = await api("GET", "/state")
1744
- state.shape = shape
1745
- const host = $("collections")
1746
- if (shape.collections.length === 0) {
1747
- host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>'
1748
- $("schema").innerHTML = ""
1749
- $("records").innerHTML = ""
1750
- return
1751
- }
1752
- if (!shape.collections.some((item) => item.name === state.collection)) {
1753
- state.collection = shape.collections[0].name
1754
- }
1755
- host.innerHTML = shape.collections.map((item) =>
1756
- '<button type="button" class="collection" data-collection="' + esc(item.name) + '" aria-current="' +
1757
- (item.name === state.collection ? "true" : "false") + '"><strong>' + esc(item.label) +
1758
- '</strong><small>' + esc(item.count) + " \xB7 " + esc(item.source) + "</small></button>"
1759
- ).join("")
1760
- const selected = shape.collections.find((item) => item.name === state.collection)
1761
- $("schema").innerHTML = "<h3>" + esc(selected.label) + "</h3>" +
1762
- (selected.description ? "<p class=" + '"lede">' + esc(selected.description) + "</p>" : "") +
1763
- schemaChips(selected)
1764
- const page = await api("GET", "/state/" + encodeURIComponent(state.collection) + "?limit=50")
1765
- const rows = page.records.map((record) => ({
1766
- ...record,
1767
- attrs: ' class="clickable" data-id="' + esc(record.id) + '"'
1768
- }))
1769
- $("records").innerHTML = table(
1770
- [
1771
- { label: "Id", cell: (row) => "<code>" + esc(row.id) + "</code>" },
1772
- { label: "Seq", cell: (row) => esc(row.seq) },
1773
- { label: "Value", cell: (row) => preview(row.value) }
1774
- ],
1775
- rows
1776
- )
1777
- }
1778
-
1779
- async function renderClock() {
1780
- const clock = await api("GET", "/clock")
1781
- $("clock-readout").innerHTML = "<div class='k'>Now</div><div class='v'>" + esc(when(clock.now)) +
1782
- "</div><p>" + esc(clock.now) + " ms \xB7 offset " + esc(clock.offsetMs) + " ms " +
1783
- (clock.frozen ? "<span class='badge warn'>frozen</span>" : "<span class='badge ok'>live</span>") + "</p>"
1784
- $("freeze").textContent = clock.frozen ? "Unfreeze" : "Freeze"
1785
- $("freeze").dataset.frozen = clock.frozen ? "1" : "0"
1786
- }
1787
-
1788
- async function renderFaults() {
1789
- const [faults, presets] = await Promise.all([
1790
- api("GET", "/faults"),
1791
- api("GET", "/faults/presets").catch(() => ({ presets: [] }))
1792
- ])
1793
- const select = $("fault-preset")
1794
- const current = select.value
1795
- const list = presets.presets || []
1796
- select.innerHTML = '<option value="">Custom rule</option>' + list.map((preset) =>
1797
- '<option value="' + esc(preset.name) + '">' + esc(preset.name) + "</option>"
1798
- ).join("")
1799
- select.value = current
1800
- const rules = faults.faults || []
1801
- $("fault-list").innerHTML = table(
1802
- [
1803
- { label: "Id", cell: (row) => esc(row.id) },
1804
- { label: "Match", cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter(Boolean).join(" ") || "all") },
1805
- { label: "Effect", cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || "")) }
1806
- ],
1807
- rules
1808
- )
1809
- }
1810
-
1811
- async function renderJournal() {
1812
- const journal = await api("GET", "/requests?limit=100")
1813
- $("journal").innerHTML = table(
1814
- [
1815
- { label: "When", cell: (row) => esc(row.at || "") },
1816
- { label: "Method", cell: (row) => esc(row.method) },
1817
- { label: "Path", cell: (row) => esc(row.path) },
1818
- { label: "Status", cell: (row) => esc(row.status) },
1819
- { label: "ms", cell: (row) => esc(row.durationMs) }
1820
- ],
1821
- journal.requests || []
1822
- )
1823
- }
1824
-
1825
- async function renderRoutes() {
1826
- const data = await api("GET", "/")
1827
- const routes = data.routes || []
1828
- $("route-list").innerHTML = routes.map((route) => {
1829
- const extra = STANDARD.has(route) ? "" : " <span class='badge'>extension</span>"
1830
- return '<div class="route"><span class="method">' + esc(route.split(" ")[0]) +
1831
- '</span><span>' + esc(route.split(" ").slice(1).join(" ")) + extra +
1832
- '</span><button class="btn" type="button" data-route="' + esc(route) + '">Use</button></div>'
1833
- }).join("")
1834
- }
1835
-
1836
- const renderers = {
1837
- overview: renderOverview,
1838
- state: renderState,
1839
- clock: renderClock,
1840
- faults: renderFaults,
1841
- journal: renderJournal,
1842
- routes: renderRoutes
1843
- }
1844
-
1845
- async function refresh() {
1846
- clearError()
1847
- if (state.view in renderers) await renderers[state.view]()
1848
- }
1849
-
1850
- $("namespace").addEventListener("change", () => {
1851
- state.namespace = $("namespace").value
1852
- refresh().catch(showError)
1853
- })
1854
- keyInput.addEventListener("change", () => {
1855
- state.key = keyInput.value.trim()
1856
- sessionStorage.setItem("mockingbird-admin-key", state.key)
1857
- refresh().catch(showError)
1858
- })
1859
- $("refresh").addEventListener("click", () => refresh().catch(showError))
1860
-
1861
- $("collections").addEventListener("click", (event) => {
1862
- const button = event.target.closest("[data-collection]")
1863
- if (!button) return
1864
- state.collection = button.dataset.collection
1865
- renderState().catch(showError)
1866
- })
1867
- $("records").addEventListener("click", (event) => {
1868
- const row = event.target.closest("[data-id]")
1869
- if (!row) return
1870
- openEditor(row.dataset.id).catch(showError)
1871
- })
1872
-
1873
- const editor = $("editor")
1874
- async function openEditor(id) {
1875
- state.editing = id || null
1876
- $("editor-title").textContent = id ? "Edit record" : "New record"
1877
- $("editor-id").value = id || ""
1878
- $("editor-id").readOnly = Boolean(id)
1879
- $("editor-delete").hidden = !id
1880
- if (id) {
1881
- const record = await api("GET", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
1882
- $("editor-value").value = JSON.stringify(record.value, null, 2)
1883
- } else {
1884
- $("editor-value").value = "{\\n \\n}"
1885
- }
1886
- editor.showModal()
1887
- }
1888
- $("new-record").addEventListener("click", () => openEditor(null).catch(showError))
1889
- $("editor-form").addEventListener("submit", (event) => {
1890
- const submitter = event.submitter
1891
- if (!submitter || submitter.value !== "save") return
1892
- event.preventDefault()
1893
- let value
1894
- try { value = JSON.parse($("editor-value").value) }
1895
- catch { showError(new Error("Value is not JSON")); return }
1896
- const id = $("editor-id").value.trim()
1897
- const path = "/state/" + encodeURIComponent(state.collection)
1898
- const request = state.editing
1899
- ? api("PUT", path + "/" + encodeURIComponent(id), { value })
1900
- : api("POST", path, { id: id || undefined, value })
1901
- request.then(() => { editor.close(); return renderState() }).catch(showError)
1902
- })
1903
- $("editor-delete").addEventListener("click", () => {
1904
- const id = state.editing
1905
- if (!id) return
1906
- api("DELETE", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
1907
- .then(() => { editor.close(); return renderState() })
1908
- .catch(showError)
1909
- })
1910
-
1911
- document.querySelectorAll("[data-advance]").forEach((button) => {
1912
- button.addEventListener("click", () => {
1913
- api("POST", "/clock", { advance: Number(button.dataset.advance) }).then(renderClock).catch(showError)
1914
- })
1915
- })
1916
- $("freeze").addEventListener("click", () => {
1917
- const frozen = $("freeze").dataset.frozen === "1"
1918
- api("POST", "/clock", { freeze: !frozen }).then(renderClock).catch(showError)
1919
- })
1920
- $("clock-reset").addEventListener("click", () => {
1921
- api("POST", "/clock", { reset: true }).then(renderClock).catch(showError)
1922
- })
1923
- $("clock-set").addEventListener("submit", (event) => {
1924
- event.preventDefault()
1925
- const raw = $("clock-instant").value.trim()
1926
- const set = /^-?\\d+$/.test(raw) ? Number(raw) : raw
1927
- api("POST", "/clock", { set }).then(renderClock).catch(showError)
1928
- })
1929
-
1930
- $("fault-form").addEventListener("submit", (event) => {
1931
- event.preventDefault()
1932
- const preset = $("fault-preset").value
1933
- const body = preset
1934
- ? { preset }
1935
- : {
1936
- status: Number($("fault-status").value),
1937
- ...($("fault-path").value ? { pathPrefix: $("fault-path").value } : {}),
1938
- ...($("fault-method").value ? { method: $("fault-method").value } : {})
1939
- }
1940
- if (!preset && !Number.isFinite(body.status)) {
1941
- showError(new Error("Status must be a number, or choose a preset"))
1942
- return
1943
- }
1944
- api("POST", "/faults", body).then(renderFaults).catch(showError)
1945
- })
1946
- $("fault-clear").addEventListener("click", () => api("DELETE", "/faults").then(renderFaults).catch(showError))
1947
- $("journal-clear").addEventListener("click", () => api("DELETE", "/requests").then(renderJournal).catch(showError))
1948
-
1949
- $("route-list").addEventListener("click", (event) => {
1950
- const button = event.target.closest("[data-route]")
1951
- if (!button) return
1952
- state.route = button.dataset.route
1953
- $("route-title").textContent = state.route
1954
- const method = state.route.split(" ")[0]
1955
- $("route-body").disabled = method === "GET" || method === "DELETE"
1956
- })
1957
- $("route-form").addEventListener("submit", async (event) => {
1958
- event.preventDefault()
1959
- if (!state.route) return showError(new Error("Choose a route first"))
1960
- const [method, path] = [state.route.split(" ")[0], state.route.split(" ").slice(1).join(" ")]
1961
- const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_, key) => prompt("Value for " + key) || "")
1962
- let body
1963
- if (method !== "GET" && method !== "DELETE" && $("route-body").value.trim()) {
1964
- try { body = JSON.parse($("route-body").value) }
1965
- catch { return showError(new Error("Body is not JSON")) }
1966
- }
1967
- try {
1968
- const data = await api(method, concrete, body)
1969
- $("route-result").textContent = JSON.stringify(data, null, 2)
1970
- } catch (error) {
1971
- showError(error)
1972
- }
1973
- })
1974
-
1975
- function mountPanels(panels) {
1976
- const nav = $("nav")
1977
- const views = $("views")
1978
- for (const panel of panels) {
1979
- const button = document.createElement("button")
1980
- button.type = "button"
1981
- button.dataset.view = panel.id
1982
- button.textContent = panel.title
1983
- nav.append(button)
1984
- const section = document.createElement("section")
1985
- section.className = "view"
1986
- section.id = "view-" + panel.id
1987
- section.hidden = true
1988
- const title = document.createElement("h2")
1989
- title.textContent = panel.title
1990
- const lede = document.createElement("p")
1991
- lede.className = "lede"
1992
- lede.textContent = panel.description || ""
1993
- const body = document.createElement("div")
1994
- body.className = "panel-body"
1995
- body.id = "panel-" + panel.id
1996
- body.innerHTML = panel.html || ""
1997
- section.append(title, lede, body)
1998
- views.append(section)
1999
- if (panel.script) {
2000
- try {
2001
- const run = new Function("root", "api", panel.script)
2002
- run(body, panelApi)
2003
- } catch (error) {
2004
- const note = document.createElement("p")
2005
- note.className = "banner show"
2006
- note.textContent = error instanceof Error ? error.message : String(error)
2007
- body.append(note)
2008
- }
2009
- }
2010
- }
2011
- }
2012
-
2013
- loadNamespaces()
2014
- .then(() => api("GET", "/ui/manifest"))
2015
- .then((manifest) => { mountPanels((manifest && manifest.panels) || []); return refresh() })
2016
- .catch(showError)
2017
- })()
2018
- </script>
2819
+ ${script}
2019
2820
  </body>
2020
2821
  </html>`;
2021
2822
  };
@@ -2032,10 +2833,10 @@ var json2 = (body) => new Response(JSON.stringify(body), {
2032
2833
  });
2033
2834
  var adminUiRoutes = (service, ui) => {
2034
2835
  const shell = () => renderAdminDocument(service);
2035
- const document2 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
2836
+ const document3 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
2036
2837
  return {
2037
- "GET /ui": () => htmlResponse(document2()),
2038
- "GET /ui/": () => htmlResponse(document2()),
2838
+ "GET /ui": () => htmlResponse(document3()),
2839
+ "GET /ui/": () => htmlResponse(document3()),
2039
2840
  "GET /ui/manifest": () => json2({
2040
2841
  service,
2041
2842
  panels: (ui?.panels ?? []).map((panel) => ({
@@ -2061,11 +2862,11 @@ var EXTENSION_KEYS = {
2061
2862
  };
2062
2863
 
2063
2864
  // ../../openapi/metadata/dist/read.js
2064
- var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2865
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2065
2866
  var extensionOf = (holder, key) => holder[key];
2066
2867
  var operationMetadata = (operation) => {
2067
2868
  const raw = extensionOf(operation, EXTENSION_KEYS.operation);
2068
- const ext = isRecord2(raw) ? raw : {};
2869
+ const ext = isRecord3(raw) ? raw : {};
2069
2870
  const supported = ext.supported ?? true;
2070
2871
  const parity = ext.parity ?? {};
2071
2872
  return {
@@ -2101,9 +2902,9 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
2101
2902
  this.name = "OperationRegistryError";
2102
2903
  }
2103
2904
  };
2104
- var verifyOperations = (document2, handlers) => {
2905
+ var verifyOperations = (document3, handlers) => {
2105
2906
  const problems = [];
2106
- const operations = listOperations(document2);
2907
+ const operations = listOperations(document3);
2107
2908
  const seen = /* @__PURE__ */ new Set();
2108
2909
  for (const operation of operations) {
2109
2910
  if (seen.has(operation.operationId))
@@ -2219,7 +3020,7 @@ var SAMPLE = 25;
2219
3020
  var WALK_LIMIT = 1e4;
2220
3021
  var SKIP_CTORS = /* @__PURE__ */ new Set(["Hono", "HonoRequest", "Router"]);
2221
3022
  var fail = (status, message) => ({ ok: false, status, message });
2222
- var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3023
+ var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2223
3024
  var kindOf = (value) => {
2224
3025
  if (value === null)
2225
3026
  return "null";
@@ -2318,7 +3119,7 @@ var absorb = (accum, value) => {
2318
3119
  const kind = kindOf(value);
2319
3120
  accum.kinds.add(kind);
2320
3121
  accum.present++;
2321
- if (!isRecord3(value))
3122
+ if (!isRecord4(value))
2322
3123
  return;
2323
3124
  for (const [key, child] of Object.entries(value)) {
2324
3125
  const nested = accum.nested.get(key) ?? blankAccum(key);
@@ -2343,7 +3144,7 @@ var inferFields = (records) => {
2343
3144
  if (records.length === 0)
2344
3145
  return [];
2345
3146
  const sample = records.slice(0, SAMPLE);
2346
- const objects = sample.filter((record) => isRecord3(record.value));
3147
+ const objects = sample.filter((record) => isRecord4(record.value));
2347
3148
  if (objects.length === 0) {
2348
3149
  const kinds = new Set(sample.map((record) => kindOf(record.value)));
2349
3150
  const list = [...kinds];
@@ -2530,7 +3331,7 @@ var patchStateRecord = (scope, name, id, value) => {
2530
3331
  const parsed = jsonValue(value);
2531
3332
  if (!parsed.ok)
2532
3333
  return parsed;
2533
- const next = isRecord3(current.value.value) && isRecord3(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
3334
+ const next = isRecord4(current.value.value) && isRecord4(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
2534
3335
  return replaceStateRecord(scope, name, id, next);
2535
3336
  };
2536
3337
  var deleteStateRecord = (scope, name, id) => {
@@ -2598,7 +3399,7 @@ var stateAdminRoutes = (routes) => {
2598
3399
  const scope = open(namespace);
2599
3400
  if (!scope.ok)
2600
3401
  return fromResult(scope, 201);
2601
- if (!isRecord3(body) || !("value" in body)) {
3402
+ if (!isRecord4(body) || !("value" in body)) {
2602
3403
  return adminError2(400, 'expected {"id?": "<id>", "value": <json>}');
2603
3404
  }
2604
3405
  const id = body.id;
@@ -2616,7 +3417,7 @@ var stateAdminRoutes = (routes) => {
2616
3417
  const scope = open(namespace);
2617
3418
  if (!scope.ok)
2618
3419
  return fromResult(scope, 200);
2619
- if (!isRecord3(body) || !("value" in body))
3420
+ if (!isRecord4(body) || !("value" in body))
2620
3421
  return adminError2(400, 'expected {"value": <json>}');
2621
3422
  const replaced = replaceStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
2622
3423
  if (replaced.ok)
@@ -2627,7 +3428,7 @@ var stateAdminRoutes = (routes) => {
2627
3428
  const scope = open(namespace);
2628
3429
  if (!scope.ok)
2629
3430
  return fromResult(scope, 200);
2630
- if (!isRecord3(body) || !("value" in body))
3431
+ if (!isRecord4(body) || !("value" in body))
2631
3432
  return adminError2(400, 'expected {"value": <json>}');
2632
3433
  const patched = patchStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
2633
3434
  if (patched.ok)
@@ -2647,7 +3448,7 @@ var stateAdminRoutes = (routes) => {
2647
3448
  };
2648
3449
 
2649
3450
  // ../core/dist/version.js
2650
- var PACKAGE_VERSION = true ? "1.5.0" : UNRELEASED_VERSION;
3451
+ var PACKAGE_VERSION = true ? "1.6.1" : UNRELEASED_VERSION;
2651
3452
 
2652
3453
  // ../core/dist/signing.js
2653
3454
  var encoder = new TextEncoder();
@@ -2663,9 +3464,9 @@ var fromBase64 = (value) => Uint8Array.from(atob(value), (char) => char.charCode
2663
3464
  // ../core/dist/webhooks.js
2664
3465
  var json4 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
2665
3466
  var adminError3 = (status, message) => json4(status, { error: { type: "mockingbird_admin", message } });
2666
- var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3467
+ var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2667
3468
  var parseEndpoint = (value) => {
2668
- if (!isRecord4(value) || typeof value.url !== "string")
3469
+ if (!isRecord5(value) || typeof value.url !== "string")
2669
3470
  return "each endpoint needs a url";
2670
3471
  try {
2671
3472
  new URL(value.url);
@@ -2682,12 +3483,12 @@ var parseEndpoint = (value) => {
2682
3483
  const events = value.events ?? value.enabledEvents;
2683
3484
  if (Array.isArray(events))
2684
3485
  endpoint.events = events.map(String);
2685
- if (isRecord4(value.tags)) {
3486
+ if (isRecord5(value.tags)) {
2686
3487
  endpoint.tags = Object.fromEntries(Object.entries(value.tags).map(([k, v]) => [k, String(v)]));
2687
3488
  }
2688
3489
  if (typeof value.account === "string")
2689
3490
  endpoint.tags = { ...endpoint.tags, account: value.account };
2690
- if (isRecord4(value.headers)) {
3491
+ if (isRecord5(value.headers)) {
2691
3492
  endpoint.headers = Object.fromEntries(Object.entries(value.headers).map(([k, v]) => [k, String(v)]));
2692
3493
  }
2693
3494
  return endpoint;
@@ -2714,7 +3515,7 @@ var webhookAdminRoutes = (hub) => ({
2714
3515
  return json4(200, { status: "ok" });
2715
3516
  },
2716
3517
  "POST /webhooks/faults": ({ body, namespace }) => {
2717
- if (!isRecord4(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
3518
+ if (!isRecord5(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
2718
3519
  return adminError3(400, "mode must be duplicate, reorder or drop");
2719
3520
  }
2720
3521
  const fault = { mode: body.mode };
@@ -2730,7 +3531,7 @@ var webhookAdminRoutes = (hub) => ({
2730
3531
  }))
2731
3532
  }),
2732
3533
  "PUT /webhook-endpoints": ({ body, namespace }) => {
2733
- const list = Array.isArray(body) ? body : isRecord4(body) ? body.endpoints : void 0;
3534
+ const list = Array.isArray(body) ? body : isRecord5(body) ? body.endpoints : void 0;
2734
3535
  if (!Array.isArray(list))
2735
3536
  return adminError3(400, "expected [{url, secret?, events?}]");
2736
3537
  const parsed = [];
@@ -2800,8 +3601,8 @@ var DroppedConnectionError = class extends TypeError {
2800
3601
  this.name = "TypeError";
2801
3602
  }
2802
3603
  };
2803
- var operationMatcher = (document2) => {
2804
- const matchers = listOperations(document2).map((operation) => ({
3604
+ var operationMatcher = (document3) => {
3605
+ const matchers = listOperations(document3).map((operation) => ({
2805
3606
  operationId: operation.operationId,
2806
3607
  method: operation.method.toUpperCase(),
2807
3608
  pattern: new RegExp(`^${operation.path.split("/").map((segment) => segment.startsWith("{") ? "[^/]+" : segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("/")}/?$`),
@@ -2896,10 +3697,10 @@ var createRuntime = (options) => {
2896
3697
  const ensureBranch = (namespace, branch2, at) => {
2897
3698
  if (!BRANCH_PATTERN2.test(branch2))
2898
3699
  throw new RangeError(`branch must match ${BRANCH_PATTERN2}`);
2899
- const history = timeline(namespace);
3700
+ const history2 = timeline(namespace);
2900
3701
  if (branch2 === "main") {
2901
3702
  if (at !== void 0) {
2902
- const point = history.checkout("main", at);
3703
+ const point = history2.checkout("main", at);
2903
3704
  restoreNamespace(sqlite, storageNamespace(namespace), point.value.snapshot);
2904
3705
  captured.set(namespace, point.value.snapshot);
2905
3706
  rng.setState(point.value.rngState);
@@ -2912,10 +3713,10 @@ var createRuntime = (options) => {
2912
3713
  return namespace;
2913
3714
  }
2914
3715
  const storage = physicalBranch(namespace, branch2);
2915
- if (!history.hasBranch(branch2)) {
3716
+ if (!history2.hasBranch(branch2)) {
2916
3717
  if (at === void 0)
2917
- history.commit(capture(namespace));
2918
- const point = history.fork(branch2, at === void 0 ? {} : { from: at });
3718
+ history2.commit(capture(namespace));
3719
+ const point = history2.fork(branch2, at === void 0 ? {} : { from: at });
2919
3720
  const branchRng = createRng(options.seed ?? 0);
2920
3721
  if (point)
2921
3722
  branchRng.setState(point.value.rngState);
@@ -2924,8 +3725,8 @@ var createRuntime = (options) => {
2924
3725
  restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
2925
3726
  if (point)
2926
3727
  captured.set(storage, point.value.snapshot);
2927
- } else if (at !== void 0 && history.head(branch2)?.id !== at) {
2928
- const point = history.checkout(branch2, at);
3728
+ } else if (at !== void 0 && history2.head(branch2)?.id !== at) {
3729
+ const point = history2.checkout(branch2, at);
2929
3730
  if (!instances.has(storage)) {
2930
3731
  const branchRng = createRng(options.seed ?? 0);
2931
3732
  branchRng.setState(point.value.rngState);
@@ -2936,7 +3737,7 @@ var createRuntime = (options) => {
2936
3737
  captured.set(storage, point.value.snapshot);
2937
3738
  } else {
2938
3739
  if (!instances.has(storage)) {
2939
- const point = history.head(branch2);
3740
+ const point = history2.head(branch2);
2940
3741
  const branchRng = createRng(options.seed ?? 0);
2941
3742
  if (point)
2942
3743
  branchRng.setState(point.value.rngState);
@@ -2960,8 +3761,8 @@ var createRuntime = (options) => {
2960
3761
  const checkout = (checkpointId, checkoutOptions = {}) => {
2961
3762
  const namespace = checkoutOptions.namespace ?? DEFAULT_NAMESPACE;
2962
3763
  const branchName = checkoutOptions.branch ?? "main";
2963
- const history = timeline(namespace);
2964
- const point = history.checkout(branchName, checkpointId);
3764
+ const history2 = timeline(namespace);
3765
+ const point = history2.checkout(branchName, checkpointId);
2965
3766
  const storage = ensureBranch(namespace, branchName);
2966
3767
  restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
2967
3768
  captured.set(storage, point.value.snapshot);
@@ -3011,9 +3812,9 @@ var createRuntime = (options) => {
3011
3812
  instance(name);
3012
3813
  restoreNamespace(sqlite, storageNamespace(name), from);
3013
3814
  captured.set(name, from);
3014
- const history = timelines.get(name);
3015
- if (history)
3016
- history.commit(capture(name), { branch: "main" });
3815
+ const history2 = timelines.get(name);
3816
+ if (history2)
3817
+ history2.commit(capture(name), { branch: "main" });
3017
3818
  else
3018
3819
  timeline(name);
3019
3820
  };
@@ -3242,10 +4043,10 @@ var createRuntime = (options) => {
3242
4043
  },
3243
4044
  release: (name, checkpointId) => timeline(name).release(checkpointId),
3244
4045
  inspect: (name) => {
3245
- const history = timeline(name);
4046
+ const history2 = timeline(name);
3246
4047
  return {
3247
- branches: history.branches(),
3248
- checkpoints: history.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
4048
+ branches: history2.branches(),
4049
+ checkpoints: history2.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
3249
4050
  id,
3250
4051
  branch: branchName,
3251
4052
  parent,
@@ -3260,11 +4061,11 @@ var createRuntime = (options) => {
3260
4061
  } : {},
3261
4062
  routes: {
3262
4063
  ...credentialRoutes(credentials),
3263
- ...options.presets ? presetRoutes(options.presets, runtime) : {},
3264
4064
  ...options.webhooks ? webhookAdminRoutes(options.webhooks) : {},
3265
4065
  ...options.admin?.(runtime) ?? {},
3266
- // Standard state and UI routes win a colliding key. A service adds its own keys
3267
- // beside these; it does not replace `/state` or `/ui`.
4066
+ // Shell routes win a colliding key. Fault presets are one of them: the admin page
4067
+ // maps `presets`, and a service route must not replace that list with a record.
4068
+ ...options.presets ? presetRoutes(options.presets, runtime) : {},
3268
4069
  ...stateAdminRoutes({
3269
4070
  open: stateScope,
3270
4071
  afterWrite: (namespace) => {
@@ -3339,9 +4140,12 @@ var credentialRoutes = (registry) => ({
3339
4140
  }
3340
4141
  });
3341
4142
  var presetRoutes = (presets, runtime) => ({
3342
- "GET /faults/presets": () => adminJson(200, {
3343
- presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
3344
- }),
4143
+ "GET /faults/presets": () => {
4144
+ const body = {
4145
+ presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
4146
+ };
4147
+ return adminJson(200, body);
4148
+ },
3345
4149
  "POST /faults/presets/:name": ({ params, body, namespace }) => {
3346
4150
  const name = params.name;
3347
4151
  if (!presets[name])
@@ -3352,7 +4156,7 @@ var presetRoutes = (presets, runtime) => ({
3352
4156
  });
3353
4157
 
3354
4158
  // src/generated/openapi.ts
3355
- var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Google Places, Geocoding and Maps JavaScript API (Mockingbird subset)","description":"The Google Maps Platform surface our member app calls: Places Autocomplete, Place Details\\nand Find Place From Text (the legacy JSON web services), the Geocoding API, and the Maps\\nJavaScript API loader (\`/maps/api/js?libraries=places\`). Every web-service answer is HTTP 200\\nwith Google's \`status\` field; the mock resolves addresses from a QA corpus.\\n","version":"1","x-mockingbird-upstream":{"note":"Hand-authored from Google's documented response shapes and the fields our consumer reads (address-autocomplete-native-rest.tsx, address-autocomplete-web.tsx, use-geocoded-address.ts). Google publishes no OpenAPI document for these endpoints."}},"servers":[{"url":"https://maps.googleapis.com"}],"paths":{"/maps/api/place/autocomplete/json":{"get":{"operationId":"PlaceAutocomplete","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Key"},{"name":"input","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":60,"examples":["1625 N Central","Phoenix","85004","4821 Maple Street Denver","200 N Spring St, Los Angeles","136 Murray"]}},{"name":"types","in":"query","schema":{"type":"string","enum":["address","geocode"]}},{"name":"components","in":"query","schema":{"type":"string","enum":["country:us","country:ca","country:us|country:ca"]}},{"$ref":"#/components/parameters/SessionToken"}],"responses":{"200":{"description":"Predictions, or a non-OK status (HTTP 200 either way)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutocompleteResponse"}}}},"500":{"description":"Google front-end failure (server_error preset)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/maps/api/place/details/json":{"get":{"operationId":"PlaceDetails","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Key"},{"name":"place_id","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":200,"examples":["ChIJ5BKAwJuCB5951g03ZzrkHx9","ChIJE1ellR5XyWiy2lHIlJ5cls3","ChIJaWYsCINV1XBQQ97jO3pznpg","EieyJsIjoiNDgyMSBNYXBsZSBTdHJlZXQiLCJjIjoiRGVudmVyIiwicyI6IkNPIiwieiI6IjgwMjAyIiwibiI6IkRlbnZlciBDb3VudHkiLCJhIjozOS43NDc4OCwibyI6LTEwNC45OTQyNX0","ChIJdoesNotExist000000000"]}},{"name":"fields","in":"query","schema":{"type":"string","enum":["address_component","address_components","geometry","formatted_address,geometry","place_id,name,types","address_component,geometry,formatted_address"]}},{"$ref":"#/components/parameters/SessionToken"}],"responses":{"200":{"description":"The place, or a non-OK status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailsResponse"}}}},"500":{"description":"Google front-end failure (server_error preset)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/maps/api/geocode/json":{"get":{"operationId":"Geocode","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Key"},{"name":"address","in":"query","schema":{"type":"string","minLength":1,"maxLength":80,"examples":["1625 N Central Ave, Phoenix, AZ 85004","88 Greenwich St, New York, NY 10006","4821 Maple Street, Denver, CO 80202","85004","Phoenix, AZ","1625 N Central Ave"]}},{"name":"place_id","in":"query","schema":{"type":"string","examples":["ChIJ5BKAwJuCB5951g03ZzrkHx9"]}},{"name":"components","in":"query","schema":{"type":"string","enum":["postal_code:85004","postal_code:10006","postal_code:00000"]}}],"responses":{"200":{"description":"Geocoding results, or a non-OK status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeocodeResponse"}}}},"500":{"description":"Google front-end failure (server_error preset)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/maps/api/place/findplacefromtext/json":{"get":{"operationId":"FindPlaceFromText","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Key"},{"name":"input","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":80,"examples":["1625 N Central Ave, Phoenix, AZ 85004","1625 N Central","Gilbert"]}},{"name":"inputtype","in":"query","required":true,"schema":{"type":"string","enum":["textquery","phonenumber"]}},{"name":"fields","in":"query","schema":{"type":"string","enum":["geometry","formatted_address,geometry","place_id,name","place_id"]}}],"responses":{"200":{"description":"Candidates, or a non-OK status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindPlaceResponse"}}}},"500":{"description":"Google front-end failure (server_error preset)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/maps/api/js":{"get":{"operationId":"MapsJavaScriptApi","description":"The Maps JavaScript API loader. The mock serves a shim defining google.maps.places (AutocompleteService, PlacesService, AutocompleteSessionToken, PlacesServiceStatus) and google.maps.Geocoder, backed by the REST operations above.","x-mockingbird":{"supported":true,"parity":{"enabled":false,"reason":"A JavaScript program, not data: it is exercised by evaluating it in a fake window and driving our web client through it (google-maps.acceptance.test.ts)."}},"parameters":[{"name":"key","in":"query","schema":{"type":"string"}},{"name":"libraries","in":"query","schema":{"type":"string"}},{"name":"callback","in":"query","schema":{"type":"string"}},{"name":"v","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The Maps JavaScript shim","content":{"text/javascript":{"schema":{"type":"string"}}}},"503":{"description":"Script unavailable (script_unavailable preset)","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/v1:validateAddress":{"servers":[{"url":"https://addressvalidation.googleapis.com"}],"post":{"operationId":"ValidateAddress","description":"Address Validation API v1 validateAddress, with USPS CASS data (\`enableUspsCass\`). A Google Cloud API: errors are HTTP 4xx/5xx with a \`google.rpc.Status\` envelope, and the key may also be sent as the \`X-Goog-Api-Key\` header. Responses are proto3 JSON (false booleans and empty lists omitted). Reference: https://developers.google.com/maps/documentation/address-validation/reference/rest/v1/TopLevel/validateAddress","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Key"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateAddressRequest"}}}},"responses":{"200":{"description":"The validation result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateAddressResponse"}}}},"400":{"description":"INVALID_ARGUMENT (malformed request, unsupported region, invalid API key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleApiError"}}}},"403":{"description":"PERMISSION_DENIED (missing key; address_validation_denied preset)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleApiError"}}}},"503":{"description":"UNAVAILABLE (address_validation_unavailable preset)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleApiError"}}}}}}}},"components":{"parameters":{"Key":{"name":"key","in":"query","required":true,"description":"The Google Maps Platform API key (\`PLACES_KEY\`); also the namespace credential.","schema":{"type":"string","minLength":1,"maxLength":40,"examples":["parity-key"]}},"SessionToken":{"name":"sessiontoken","in":"query","description":"Autocomplete session token (billing grouping); accepted and journaled as metadata.","schema":{"type":"string","format":"uuid"}}},"schemas":{"Status":{"type":"string","enum":["OK","ZERO_RESULTS","INVALID_REQUEST","OVER_QUERY_LIMIT","REQUEST_DENIED","UNKNOWN_ERROR","NOT_FOUND"]},"LatLng":{"type":"object","required":["lat","lng"],"properties":{"lat":{"type":"number"},"lng":{"type":"number"}}},"Viewport":{"type":"object","required":["northeast","southwest"],"properties":{"northeast":{"$ref":"#/components/schemas/LatLng"},"southwest":{"$ref":"#/components/schemas/LatLng"}}},"Geometry":{"type":"object","required":["location","viewport"],"properties":{"location":{"$ref":"#/components/schemas/LatLng"},"location_type":{"type":"string","enum":["ROOFTOP","RANGE_INTERPOLATED","GEOMETRIC_CENTER","APPROXIMATE"]},"viewport":{"$ref":"#/components/schemas/Viewport"}}},"AddressComponent":{"type":"object","required":["long_name","short_name","types"],"properties":{"long_name":{"type":"string"},"short_name":{"type":"string"},"types":{"type":"array","items":{"type":"string"}}}},"MatchedSubstring":{"type":"object","required":["length","offset"],"properties":{"length":{"type":"integer"},"offset":{"type":"integer"}}},"Prediction":{"type":"object","required":["description","place_id","reference","structured_formatting","terms","types","matched_substrings"],"properties":{"description":{"type":"string"},"matched_substrings":{"type":"array","items":{"$ref":"#/components/schemas/MatchedSubstring"}},"place_id":{"type":"string","x-mockingbird-resource":{"type":"place","identity":true}},"reference":{"type":"string"},"structured_formatting":{"type":"object","required":["main_text","secondary_text"],"properties":{"main_text":{"type":"string"},"main_text_matched_substrings":{"type":"array","items":{"$ref":"#/components/schemas/MatchedSubstring"}},"secondary_text":{"type":"string"}}},"terms":{"type":"array","items":{"type":"object","required":["offset","value"],"properties":{"offset":{"type":"integer"},"value":{"type":"string"}}}},"types":{"type":"array","items":{"type":"string"}}}},"PlaceResult":{"type":"object","description":"Only the fields requested with \`fields=\` are present.","properties":{"address_components":{"type":"array","items":{"$ref":"#/components/schemas/AddressComponent"}},"adr_address":{"type":"string"},"formatted_address":{"type":"string"},"geometry":{"$ref":"#/components/schemas/Geometry"},"name":{"type":"string"},"place_id":{"type":"string"},"reference":{"type":"string"},"types":{"type":"array","items":{"type":"string"}},"url":{"type":"string"},"utc_offset":{"type":"integer"},"vicinity":{"type":"string"}}},"AutocompleteResponse":{"type":"object","required":["predictions","status"],"properties":{"predictions":{"type":"array","items":{"$ref":"#/components/schemas/Prediction"}},"status":{"$ref":"#/components/schemas/Status"},"error_message":{"type":"string"}}},"DetailsResponse":{"type":"object","required":["html_attributions","status"],"properties":{"html_attributions":{"type":"array","items":{"type":"string"}},"result":{"$ref":"#/components/schemas/PlaceResult"},"status":{"$ref":"#/components/schemas/Status"},"error_message":{"type":"string"}}},"GeocodeResult":{"type":"object","required":["address_components","formatted_address","geometry","place_id","types"],"properties":{"address_components":{"type":"array","items":{"$ref":"#/components/schemas/AddressComponent"}},"formatted_address":{"type":"string"},"geometry":{"$ref":"#/components/schemas/Geometry"},"place_id":{"type":"string"},"types":{"type":"array","items":{"type":"string"}}}},"GeocodeResponse":{"type":"object","required":["results","status"],"properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/GeocodeResult"}},"status":{"$ref":"#/components/schemas/Status"},"error_message":{"type":"string"}}},"FindPlaceResponse":{"type":"object","required":["candidates","status"],"properties":{"candidates":{"type":"array","items":{"$ref":"#/components/schemas/PlaceResult"}},"status":{"$ref":"#/components/schemas/Status"},"error_message":{"type":"string"}}},"GoogleApiError":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","status"],"properties":{"code":{"type":"integer"},"message":{"type":"string"},"status":{"type":"string"},"details":{"type":"array","items":{"type":"object"}}}}}},"PostalAddress":{"type":"object","description":"google.type.PostalAddress","properties":{"revision":{"type":"integer"},"regionCode":{"type":"string","examples":["US"]},"languageCode":{"type":"string"},"postalCode":{"type":"string","examples":["85004","10006","80202","90012","00000"]},"sortingCode":{"type":"string"},"administrativeArea":{"type":"string","examples":["AZ","NY","CO","CA"]},"locality":{"type":"string","examples":["Phoenix","New York","Denver","Los Angeles","Springfield"]},"sublocality":{"type":"string"},"addressLines":{"type":"array","maxItems":3,"items":{"type":"string","maxLength":80,"examples":["1625 N Central Ave","1625 North Central Avenue Building A","4821 Maple Street","N Central Ave","Apt 2"]}},"recipients":{"type":"array","items":{"type":"string"}},"organization":{"type":"string"}}},"ValidateAddressRequest":{"type":"object","required":["address"],"additionalProperties":false,"properties":{"address":{"$ref":"#/components/schemas/PostalAddress","examples":[{"regionCode":"US","addressLines":["1625 N Central Ave"],"locality":"Phoenix","administrativeArea":"AZ","postalCode":"85004"},{"regionCode":"US","addressLines":["4821 Maple Street"],"locality":"Denver","administrativeArea":"CO","postalCode":"80202"},{"regionCode":"US","addressLines":["N Central Ave"],"locality":"Phoenix","administrativeArea":"AZ","postalCode":"85004"},{"regionCode":"US","addressLines":["1625 N Central Ave, Phoenix, AZ 85004"]},{"regionCode":"US","addressLines":["12 Nowhere Rd"],"locality":"Springfield","administrativeArea":"ZZ","postalCode":"00000"}]},"previousResponseId":{"type":"string"},"enableUspsCass":{"type":"boolean"},"languageOptions":{"type":"object"},"sessionToken":{"type":"string"}}},"ValidationAddressComponent":{"type":"object","required":["componentName","componentType","confirmationLevel"],"properties":{"componentName":{"type":"object","required":["text"],"properties":{"text":{"type":"string"},"languageCode":{"type":"string"}}},"componentType":{"type":"string"},"confirmationLevel":{"type":"string","enum":["CONFIRMED","UNCONFIRMED_BUT_PLAUSIBLE","UNCONFIRMED_AND_SUSPICIOUS"]},"inferred":{"type":"boolean"},"spellCorrected":{"type":"boolean"},"replaced":{"type":"boolean"},"unexpected":{"type":"boolean"}}},"Granularity":{"type":"string","enum":["GRANULARITY_UNSPECIFIED","SUB_PREMISE","PREMISE","PREMISE_PROXIMITY","BLOCK","ROUTE","OTHER"]},"ValidationResult":{"type":"object","properties":{"verdict":{"type":"object","properties":{"inputGranularity":{"$ref":"#/components/schemas/Granularity"},"validationGranularity":{"$ref":"#/components/schemas/Granularity"},"geocodeGranularity":{"$ref":"#/components/schemas/Granularity"},"addressComplete":{"type":"boolean"},"hasUnconfirmedComponents":{"type":"boolean"},"hasInferredComponents":{"type":"boolean"},"hasReplacedComponents":{"type":"boolean"},"hasSpellCorrectedComponents":{"type":"boolean"},"possibleNextAction":{"type":"string","enum":["POSSIBLE_NEXT_ACTION_UNSPECIFIED","FIX","CONFIRM_ADD_SUBPREMISES","CONFIRM","ACCEPT"]}}},"address":{"type":"object","properties":{"formattedAddress":{"type":"string"},"postalAddress":{"$ref":"#/components/schemas/PostalAddress"},"addressComponents":{"type":"array","items":{"$ref":"#/components/schemas/ValidationAddressComponent"}},"missingComponentTypes":{"type":"array","items":{"type":"string"}},"unconfirmedComponentTypes":{"type":"array","items":{"type":"string"}},"unresolvedTokens":{"type":"array","items":{"type":"string"}}}},"geocode":{"type":"object","properties":{"location":{"type":"object","properties":{"latitude":{"type":"number"},"longitude":{"type":"number"}}},"placeId":{"type":"string"},"placeTypes":{"type":"array","items":{"type":"string"}}}},"uspsData":{"type":"object","properties":{"standardizedAddress":{"type":"object","properties":{"firstAddressLine":{"type":"string"},"secondAddressLine":{"type":"string"},"cityStateZipAddressLine":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"zipCode":{"type":"string"},"zipCodeExtension":{"type":"string"}}},"dpvConfirmation":{"type":"string","enum":["Y","N","S","D"]},"dpvFootnote":{"type":"string"},"postOfficeCity":{"type":"string"},"postOfficeState":{"type":"string"},"errorMessage":{"type":"string"},"cassProcessed":{"type":"boolean"}}}}},"ValidateAddressResponse":{"type":"object","required":["result","responseId"],"properties":{"result":{"$ref":"#/components/schemas/ValidationResult"},"responseId":{"type":"string"}}}}}}`);
4159
+ var document2 = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Google Places, Geocoding and Maps JavaScript API (Mockingbird subset)","description":"The Google Maps Platform surface our member app calls: Places Autocomplete, Place Details\\nand Find Place From Text (the legacy JSON web services), the Geocoding API, and the Maps\\nJavaScript API loader (\`/maps/api/js?libraries=places\`). Every web-service answer is HTTP 200\\nwith Google's \`status\` field; the mock resolves addresses from a QA corpus.\\n","version":"1","x-mockingbird-upstream":{"note":"Hand-authored from Google's documented response shapes and the fields our consumer reads (address-autocomplete-native-rest.tsx, address-autocomplete-web.tsx, use-geocoded-address.ts). Google publishes no OpenAPI document for these endpoints."}},"servers":[{"url":"https://maps.googleapis.com"}],"paths":{"/maps/api/place/autocomplete/json":{"get":{"operationId":"PlaceAutocomplete","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Key"},{"name":"input","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":60,"examples":["1625 N Central","Phoenix","85004","4821 Maple Street Denver","200 N Spring St, Los Angeles","136 Murray"]}},{"name":"types","in":"query","schema":{"type":"string","enum":["address","geocode"]}},{"name":"components","in":"query","schema":{"type":"string","enum":["country:us","country:ca","country:us|country:ca"]}},{"$ref":"#/components/parameters/SessionToken"}],"responses":{"200":{"description":"Predictions, or a non-OK status (HTTP 200 either way)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutocompleteResponse"}}}},"500":{"description":"Google front-end failure (server_error preset)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/maps/api/place/details/json":{"get":{"operationId":"PlaceDetails","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Key"},{"name":"place_id","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":200,"examples":["ChIJ5BKAwJuCB5951g03ZzrkHx9","ChIJE1ellR5XyWiy2lHIlJ5cls3","ChIJaWYsCINV1XBQQ97jO3pznpg","EieyJsIjoiNDgyMSBNYXBsZSBTdHJlZXQiLCJjIjoiRGVudmVyIiwicyI6IkNPIiwieiI6IjgwMjAyIiwibiI6IkRlbnZlciBDb3VudHkiLCJhIjozOS43NDc4OCwibyI6LTEwNC45OTQyNX0","ChIJdoesNotExist000000000"]}},{"name":"fields","in":"query","schema":{"type":"string","enum":["address_component","address_components","geometry","formatted_address,geometry","place_id,name,types","address_component,geometry,formatted_address"]}},{"$ref":"#/components/parameters/SessionToken"}],"responses":{"200":{"description":"The place, or a non-OK status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailsResponse"}}}},"500":{"description":"Google front-end failure (server_error preset)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/maps/api/geocode/json":{"get":{"operationId":"Geocode","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Key"},{"name":"address","in":"query","schema":{"type":"string","minLength":1,"maxLength":80,"examples":["1625 N Central Ave, Phoenix, AZ 85004","88 Greenwich St, New York, NY 10006","4821 Maple Street, Denver, CO 80202","85004","Phoenix, AZ","1625 N Central Ave"]}},{"name":"place_id","in":"query","schema":{"type":"string","examples":["ChIJ5BKAwJuCB5951g03ZzrkHx9"]}},{"name":"components","in":"query","schema":{"type":"string","enum":["postal_code:85004","postal_code:10006","postal_code:00000"]}}],"responses":{"200":{"description":"Geocoding results, or a non-OK status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeocodeResponse"}}}},"500":{"description":"Google front-end failure (server_error preset)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/maps/api/place/findplacefromtext/json":{"get":{"operationId":"FindPlaceFromText","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Key"},{"name":"input","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":80,"examples":["1625 N Central Ave, Phoenix, AZ 85004","1625 N Central","Gilbert"]}},{"name":"inputtype","in":"query","required":true,"schema":{"type":"string","enum":["textquery","phonenumber"]}},{"name":"fields","in":"query","schema":{"type":"string","enum":["geometry","formatted_address,geometry","place_id,name","place_id"]}}],"responses":{"200":{"description":"Candidates, or a non-OK status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindPlaceResponse"}}}},"500":{"description":"Google front-end failure (server_error preset)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/maps/api/js":{"get":{"operationId":"MapsJavaScriptApi","description":"The Maps JavaScript API loader. The mock serves a shim defining google.maps.places (AutocompleteService, PlacesService, AutocompleteSessionToken, PlacesServiceStatus) and google.maps.Geocoder, backed by the REST operations above.","x-mockingbird":{"supported":true,"parity":{"enabled":false,"reason":"A JavaScript program, not data: it is exercised by evaluating it in a fake window and driving our web client through it (google-maps.acceptance.test.ts)."}},"parameters":[{"name":"key","in":"query","schema":{"type":"string"}},{"name":"libraries","in":"query","schema":{"type":"string"}},{"name":"callback","in":"query","schema":{"type":"string"}},{"name":"v","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The Maps JavaScript shim","content":{"text/javascript":{"schema":{"type":"string"}}}},"503":{"description":"Script unavailable (script_unavailable preset)","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/v1:validateAddress":{"servers":[{"url":"https://addressvalidation.googleapis.com"}],"post":{"operationId":"ValidateAddress","description":"Address Validation API v1 validateAddress, with USPS CASS data (\`enableUspsCass\`). A Google Cloud API: errors are HTTP 4xx/5xx with a \`google.rpc.Status\` envelope, and the key may also be sent as the \`X-Goog-Api-Key\` header. Responses are proto3 JSON (false booleans and empty lists omitted). Reference: https://developers.google.com/maps/documentation/address-validation/reference/rest/v1/TopLevel/validateAddress","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Key"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateAddressRequest"}}}},"responses":{"200":{"description":"The validation result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateAddressResponse"}}}},"400":{"description":"INVALID_ARGUMENT (malformed request, unsupported region, invalid API key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleApiError"}}}},"403":{"description":"PERMISSION_DENIED (missing key; address_validation_denied preset)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleApiError"}}}},"503":{"description":"UNAVAILABLE (address_validation_unavailable preset)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleApiError"}}}}}}}},"components":{"parameters":{"Key":{"name":"key","in":"query","required":true,"description":"The Google Maps Platform API key (\`PLACES_KEY\`); also the namespace credential.","schema":{"type":"string","minLength":1,"maxLength":40,"examples":["parity-key"]}},"SessionToken":{"name":"sessiontoken","in":"query","description":"Autocomplete session token (billing grouping); accepted and journaled as metadata.","schema":{"type":"string","format":"uuid"}}},"schemas":{"Status":{"type":"string","enum":["OK","ZERO_RESULTS","INVALID_REQUEST","OVER_QUERY_LIMIT","REQUEST_DENIED","UNKNOWN_ERROR","NOT_FOUND"]},"LatLng":{"type":"object","required":["lat","lng"],"properties":{"lat":{"type":"number"},"lng":{"type":"number"}}},"Viewport":{"type":"object","required":["northeast","southwest"],"properties":{"northeast":{"$ref":"#/components/schemas/LatLng"},"southwest":{"$ref":"#/components/schemas/LatLng"}}},"Geometry":{"type":"object","required":["location","viewport"],"properties":{"location":{"$ref":"#/components/schemas/LatLng"},"location_type":{"type":"string","enum":["ROOFTOP","RANGE_INTERPOLATED","GEOMETRIC_CENTER","APPROXIMATE"]},"viewport":{"$ref":"#/components/schemas/Viewport"}}},"AddressComponent":{"type":"object","required":["long_name","short_name","types"],"properties":{"long_name":{"type":"string"},"short_name":{"type":"string"},"types":{"type":"array","items":{"type":"string"}}}},"MatchedSubstring":{"type":"object","required":["length","offset"],"properties":{"length":{"type":"integer"},"offset":{"type":"integer"}}},"Prediction":{"type":"object","required":["description","place_id","reference","structured_formatting","terms","types","matched_substrings"],"properties":{"description":{"type":"string"},"matched_substrings":{"type":"array","items":{"$ref":"#/components/schemas/MatchedSubstring"}},"place_id":{"type":"string","x-mockingbird-resource":{"type":"place","identity":true}},"reference":{"type":"string"},"structured_formatting":{"type":"object","required":["main_text","secondary_text"],"properties":{"main_text":{"type":"string"},"main_text_matched_substrings":{"type":"array","items":{"$ref":"#/components/schemas/MatchedSubstring"}},"secondary_text":{"type":"string"}}},"terms":{"type":"array","items":{"type":"object","required":["offset","value"],"properties":{"offset":{"type":"integer"},"value":{"type":"string"}}}},"types":{"type":"array","items":{"type":"string"}}}},"PlaceResult":{"type":"object","description":"Only the fields requested with \`fields=\` are present.","properties":{"address_components":{"type":"array","items":{"$ref":"#/components/schemas/AddressComponent"}},"adr_address":{"type":"string"},"formatted_address":{"type":"string"},"geometry":{"$ref":"#/components/schemas/Geometry"},"name":{"type":"string"},"place_id":{"type":"string"},"reference":{"type":"string"},"types":{"type":"array","items":{"type":"string"}},"url":{"type":"string"},"utc_offset":{"type":"integer"},"vicinity":{"type":"string"}}},"AutocompleteResponse":{"type":"object","required":["predictions","status"],"properties":{"predictions":{"type":"array","items":{"$ref":"#/components/schemas/Prediction"}},"status":{"$ref":"#/components/schemas/Status"},"error_message":{"type":"string"}}},"DetailsResponse":{"type":"object","required":["html_attributions","status"],"properties":{"html_attributions":{"type":"array","items":{"type":"string"}},"result":{"$ref":"#/components/schemas/PlaceResult"},"status":{"$ref":"#/components/schemas/Status"},"error_message":{"type":"string"}}},"GeocodeResult":{"type":"object","required":["address_components","formatted_address","geometry","place_id","types"],"properties":{"address_components":{"type":"array","items":{"$ref":"#/components/schemas/AddressComponent"}},"formatted_address":{"type":"string"},"geometry":{"$ref":"#/components/schemas/Geometry"},"place_id":{"type":"string"},"types":{"type":"array","items":{"type":"string"}}}},"GeocodeResponse":{"type":"object","required":["results","status"],"properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/GeocodeResult"}},"status":{"$ref":"#/components/schemas/Status"},"error_message":{"type":"string"}}},"FindPlaceResponse":{"type":"object","required":["candidates","status"],"properties":{"candidates":{"type":"array","items":{"$ref":"#/components/schemas/PlaceResult"}},"status":{"$ref":"#/components/schemas/Status"},"error_message":{"type":"string"}}},"GoogleApiError":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","status"],"properties":{"code":{"type":"integer"},"message":{"type":"string"},"status":{"type":"string"},"details":{"type":"array","items":{"type":"object"}}}}}},"PostalAddress":{"type":"object","description":"google.type.PostalAddress","properties":{"revision":{"type":"integer"},"regionCode":{"type":"string","examples":["US"]},"languageCode":{"type":"string"},"postalCode":{"type":"string","examples":["85004","10006","80202","90012","00000"]},"sortingCode":{"type":"string"},"administrativeArea":{"type":"string","examples":["AZ","NY","CO","CA"]},"locality":{"type":"string","examples":["Phoenix","New York","Denver","Los Angeles","Springfield"]},"sublocality":{"type":"string"},"addressLines":{"type":"array","maxItems":3,"items":{"type":"string","maxLength":80,"examples":["1625 N Central Ave","1625 North Central Avenue Building A","4821 Maple Street","N Central Ave","Apt 2"]}},"recipients":{"type":"array","items":{"type":"string"}},"organization":{"type":"string"}}},"ValidateAddressRequest":{"type":"object","required":["address"],"additionalProperties":false,"properties":{"address":{"$ref":"#/components/schemas/PostalAddress","examples":[{"regionCode":"US","addressLines":["1625 N Central Ave"],"locality":"Phoenix","administrativeArea":"AZ","postalCode":"85004"},{"regionCode":"US","addressLines":["4821 Maple Street"],"locality":"Denver","administrativeArea":"CO","postalCode":"80202"},{"regionCode":"US","addressLines":["N Central Ave"],"locality":"Phoenix","administrativeArea":"AZ","postalCode":"85004"},{"regionCode":"US","addressLines":["1625 N Central Ave, Phoenix, AZ 85004"]},{"regionCode":"US","addressLines":["12 Nowhere Rd"],"locality":"Springfield","administrativeArea":"ZZ","postalCode":"00000"}]},"previousResponseId":{"type":"string"},"enableUspsCass":{"type":"boolean"},"languageOptions":{"type":"object"},"sessionToken":{"type":"string"}}},"ValidationAddressComponent":{"type":"object","required":["componentName","componentType","confirmationLevel"],"properties":{"componentName":{"type":"object","required":["text"],"properties":{"text":{"type":"string"},"languageCode":{"type":"string"}}},"componentType":{"type":"string"},"confirmationLevel":{"type":"string","enum":["CONFIRMED","UNCONFIRMED_BUT_PLAUSIBLE","UNCONFIRMED_AND_SUSPICIOUS"]},"inferred":{"type":"boolean"},"spellCorrected":{"type":"boolean"},"replaced":{"type":"boolean"},"unexpected":{"type":"boolean"}}},"Granularity":{"type":"string","enum":["GRANULARITY_UNSPECIFIED","SUB_PREMISE","PREMISE","PREMISE_PROXIMITY","BLOCK","ROUTE","OTHER"]},"ValidationResult":{"type":"object","properties":{"verdict":{"type":"object","properties":{"inputGranularity":{"$ref":"#/components/schemas/Granularity"},"validationGranularity":{"$ref":"#/components/schemas/Granularity"},"geocodeGranularity":{"$ref":"#/components/schemas/Granularity"},"addressComplete":{"type":"boolean"},"hasUnconfirmedComponents":{"type":"boolean"},"hasInferredComponents":{"type":"boolean"},"hasReplacedComponents":{"type":"boolean"},"hasSpellCorrectedComponents":{"type":"boolean"},"possibleNextAction":{"type":"string","enum":["POSSIBLE_NEXT_ACTION_UNSPECIFIED","FIX","CONFIRM_ADD_SUBPREMISES","CONFIRM","ACCEPT"]}}},"address":{"type":"object","properties":{"formattedAddress":{"type":"string"},"postalAddress":{"$ref":"#/components/schemas/PostalAddress"},"addressComponents":{"type":"array","items":{"$ref":"#/components/schemas/ValidationAddressComponent"}},"missingComponentTypes":{"type":"array","items":{"type":"string"}},"unconfirmedComponentTypes":{"type":"array","items":{"type":"string"}},"unresolvedTokens":{"type":"array","items":{"type":"string"}}}},"geocode":{"type":"object","properties":{"location":{"type":"object","properties":{"latitude":{"type":"number"},"longitude":{"type":"number"}}},"placeId":{"type":"string"},"placeTypes":{"type":"array","items":{"type":"string"}}}},"uspsData":{"type":"object","properties":{"standardizedAddress":{"type":"object","properties":{"firstAddressLine":{"type":"string"},"secondAddressLine":{"type":"string"},"cityStateZipAddressLine":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"zipCode":{"type":"string"},"zipCodeExtension":{"type":"string"}}},"dpvConfirmation":{"type":"string","enum":["Y","N","S","D"]},"dpvFootnote":{"type":"string"},"postOfficeCity":{"type":"string"},"postOfficeState":{"type":"string"},"errorMessage":{"type":"string"},"cassProcessed":{"type":"boolean"}}}}},"ValidateAddressResponse":{"type":"object","required":["result","responseId"],"properties":{"result":{"$ref":"#/components/schemas/ValidationResult"},"responseId":{"type":"string"}}}}}}`);
3356
4160
  var operationIds = ["PlaceAutocomplete", "PlaceDetails", "Geocode", "FindPlaceFromText", "MapsJavaScriptApi", "ValidateAddress"];
3357
4161
  var supportedOperationIds = ["PlaceAutocomplete", "PlaceDetails", "Geocode", "FindPlaceFromText", "MapsJavaScriptApi", "ValidateAddress"];
3358
4162
 
@@ -4462,7 +5266,7 @@ var POSTAL_ADDRESS = /* @__PURE__ */ new Set([
4462
5266
  var MAX_INPUT_LENGTH = 280;
4463
5267
  var MAX_ADDRESS_LINES = 3;
4464
5268
  var MAX_ADDRESS_LINE_LENGTH = 80;
4465
- var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5269
+ var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4466
5270
  var optionalString = (value, field) => {
4467
5271
  if (value === void 0 || value === null) return void 0;
4468
5272
  if (typeof value !== "string") {
@@ -4474,7 +5278,7 @@ var optionalString = (value, field) => {
4474
5278
  return trimmed ? trimmed : void 0;
4475
5279
  };
4476
5280
  var parseValidateRequest = (body) => {
4477
- if (!isRecord5(body)) throw new ValidationRequestError("Invalid JSON payload received.");
5281
+ if (!isRecord6(body)) throw new ValidationRequestError("Invalid JSON payload received.");
4478
5282
  for (const key of Object.keys(body)) {
4479
5283
  if (!TOP_LEVEL.has(key)) {
4480
5284
  throw new ValidationRequestError(
@@ -4486,7 +5290,7 @@ var parseValidateRequest = (body) => {
4486
5290
  if (address === void 0 || address === null) {
4487
5291
  throw new ValidationRequestError("Address is missing from the request.");
4488
5292
  }
4489
- if (!isRecord5(address)) {
5293
+ if (!isRecord6(address)) {
4490
5294
  throw new ValidationRequestError(
4491
5295
  `Invalid value at 'address' (type.googleapis.com/google.type.PostalAddress), ${JSON.stringify(address)}`
4492
5296
  );
@@ -5057,12 +5861,12 @@ var GOOGLE_MAPS_PRESETS = {
5057
5861
  };
5058
5862
  var json5 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
5059
5863
  var adminError4 = (status, message) => json5(status, { error: { type: "mockingbird_admin", message } });
5060
- var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5864
+ var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5061
5865
  var stringList = (value) => Array.isArray(value) && value.every((v) => typeof v === "string") ? value : void 0;
5062
5866
  var parseValidation = (value, index) => {
5063
5867
  if (value === void 0 || value === null) return void 0;
5064
5868
  const at = `addresses[${index}].validation`;
5065
- if (!isRecord6(value)) return `${at} must be an object`;
5869
+ if (!isRecord7(value)) return `${at} must be an object`;
5066
5870
  const out = {};
5067
5871
  const enums = [
5068
5872
  ["granularity", GRANULARITIES],
@@ -5096,7 +5900,7 @@ var parseValidation = (value, index) => {
5096
5900
  return out;
5097
5901
  };
5098
5902
  var parseAddress = (value, index) => {
5099
- if (!isRecord6(value)) return `addresses[${index}] must be an object`;
5903
+ if (!isRecord7(value)) return `addresses[${index}] must be an object`;
5100
5904
  for (const key of ["line1", "city", "state", "zip"]) {
5101
5905
  if (typeof value[key] !== "string" || !value[key].trim()) {
5102
5906
  return `addresses[${index}].${key} must be a non-empty string`;
@@ -5132,7 +5936,7 @@ var adminRoutes = (runtime) => ({
5132
5936
  });
5133
5937
  },
5134
5938
  "PUT /corpus": ({ body, namespace }) => {
5135
- const list = Array.isArray(body) ? body : isRecord6(body) ? body.addresses : void 0;
5939
+ const list = Array.isArray(body) ? body : isRecord7(body) ? body.addresses : void 0;
5136
5940
  if (!Array.isArray(list)) {
5137
5941
  return adminError4(
5138
5942
  400,
@@ -5155,7 +5959,7 @@ var adminRoutes = (runtime) => ({
5155
5959
  },
5156
5960
  "GET /settings": ({ namespace }) => json5(200, runtime.instance(namespace).state.current()),
5157
5961
  "PUT /settings": ({ body, namespace }) => {
5158
- if (!isRecord6(body)) return adminError4(400, "expected a JSON object");
5962
+ if (!isRecord7(body)) return adminError4(400, "expected a JSON object");
5159
5963
  const patch = {};
5160
5964
  if (body.keys !== void 0) {
5161
5965
  if (!Array.isArray(body.keys)) return adminError4(400, "keys: string[]");
@@ -5172,7 +5976,7 @@ var adminRoutes = (runtime) => ({
5172
5976
  });
5173
5977
  var createRuntime2 = (options = {}) => createRuntime({
5174
5978
  name: GOOGLE_MAPS_NAMESPACE,
5175
- document,
5979
+ document: document2,
5176
5980
  ...options.sqlite ? { sqlite: options.sqlite } : {},
5177
5981
  ...options.clock ? { clock: options.clock } : {},
5178
5982
  ...options.seed !== void 0 ? { seed: options.seed } : {},
@@ -5252,7 +6056,7 @@ var GoogleMapsAPI = class {
5252
6056
  ValidateAddress: (context) => this.validate(context)
5253
6057
  });
5254
6058
  this.service = createService({
5255
- document,
6059
+ document: document2,
5256
6060
  handlers,
5257
6061
  sqlite,
5258
6062
  namespace,
@@ -5471,7 +6275,7 @@ var GoogleMapsAPI = class {
5471
6275
  };
5472
6276
 
5473
6277
  export {
5474
- document,
6278
+ document2 as document,
5475
6279
  operationIds,
5476
6280
  supportedOperationIds,
5477
6281
  DEFAULT_CORPUS,
@@ -5490,4 +6294,4 @@ export {
5490
6294
  keyCredential,
5491
6295
  GoogleMapsAPI
5492
6296
  };
5493
- //# sourceMappingURL=chunk-33MOWM5L.js.map
6297
+ //# sourceMappingURL=chunk-KOTM4DOF.js.map