@crvouga/mockingbird-service-easypost 1.4.0 → 1.5.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.
@@ -551,10 +551,10 @@ var OpenAPIReferenceError = class extends Error {
551
551
  };
552
552
  var unescapePointer = (segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~");
553
553
  var isReference = (value) => typeof value === "object" && value !== null && typeof value.$ref === "string";
554
- var resolveRef = (document2, ref) => {
554
+ var resolveRef = (document3, ref) => {
555
555
  if (!ref.startsWith("#/"))
556
556
  throw new OpenAPIReferenceError(ref);
557
- let cursor = document2;
557
+ let cursor = document3;
558
558
  for (const raw of ref.slice(2).split("/")) {
559
559
  const segment = unescapePointer(raw);
560
560
  if (typeof cursor !== "object" || cursor === null || !(segment in cursor)) {
@@ -566,14 +566,14 @@ var resolveRef = (document2, ref) => {
566
566
  throw new OpenAPIReferenceError(ref);
567
567
  return cursor;
568
568
  };
569
- var deref = (document2, value) => {
569
+ var deref = (document3, value) => {
570
570
  let current = value;
571
571
  const seen = /* @__PURE__ */ new Set();
572
572
  while (isReference(current)) {
573
573
  if (seen.has(current.$ref))
574
574
  throw new OpenAPIReferenceError(`${current.$ref} (cycle)`);
575
575
  seen.add(current.$ref);
576
- current = resolveRef(document2, current.$ref);
576
+ current = resolveRef(document3, current.$ref);
577
577
  }
578
578
  return current;
579
579
  };
@@ -591,36 +591,36 @@ var HTTP_METHODS = [
591
591
  ];
592
592
 
593
593
  // ../../openapi/core/dist/document.js
594
- var mergeParameters = (document2, item, own) => {
594
+ var mergeParameters = (document3, item, own) => {
595
595
  const merged = /* @__PURE__ */ new Map();
596
596
  for (const raw of item.parameters ?? []) {
597
- const parameter = deref(document2, raw);
597
+ const parameter = deref(document3, raw);
598
598
  merged.set(`${parameter.in}:${parameter.name}`, parameter);
599
599
  }
600
600
  for (const raw of own ?? []) {
601
- const parameter = deref(document2, raw);
601
+ const parameter = deref(document3, raw);
602
602
  merged.set(`${parameter.in}:${parameter.name}`, parameter);
603
603
  }
604
604
  return [...merged.values()];
605
605
  };
606
- var listOperations = (document2) => {
606
+ var listOperations = (document3) => {
607
607
  const operations = [];
608
- for (const [path, item] of Object.entries(document2.paths)) {
608
+ for (const [path, item] of Object.entries(document3.paths)) {
609
609
  for (const method of HTTP_METHODS) {
610
610
  const operation = item[method];
611
611
  if (operation?.operationId === void 0)
612
612
  continue;
613
613
  const responses = {};
614
614
  for (const [status, response] of Object.entries(operation.responses)) {
615
- responses[status] = deref(document2, response);
615
+ responses[status] = deref(document3, response);
616
616
  }
617
617
  operations.push({
618
618
  operationId: operation.operationId,
619
619
  method,
620
620
  path,
621
621
  operation,
622
- parameters: mergeParameters(document2, item, operation.parameters),
623
- requestBody: operation.requestBody === void 0 ? void 0 : deref(document2, operation.requestBody),
622
+ parameters: mergeParameters(document3, item, operation.parameters),
623
+ requestBody: operation.requestBody === void 0 ? void 0 : deref(document3, operation.requestBody),
624
624
  responses
625
625
  });
626
626
  }
@@ -629,7 +629,7 @@ var listOperations = (document2) => {
629
629
  };
630
630
 
631
631
  // ../../openapi/core/dist/schema.js
632
- var resolveSchema = (document2, schema) => {
632
+ var resolveSchema = (document3, schema) => {
633
633
  let current = schema;
634
634
  const seen = /* @__PURE__ */ new Set();
635
635
  while (typeof current.$ref === "string") {
@@ -638,7 +638,7 @@ var resolveSchema = (document2, schema) => {
638
638
  break;
639
639
  seen.add(ref);
640
640
  const { $ref: _ignored, ...siblings } = current;
641
- const target = resolveRef(document2, ref);
641
+ const target = resolveRef(document3, ref);
642
642
  current = { ...target, ...siblings };
643
643
  }
644
644
  if (current.nullable === true) {
@@ -709,9 +709,9 @@ var FORMAT_PATTERNS = {
709
709
  ipv4: /^(25[0-5]|2[0-4]\d|1?\d?\d)(\.(25[0-5]|2[0-4]\d|1?\d?\d)){3}$/
710
710
  };
711
711
  var graphemeLength = (value) => [...value].length;
712
- var validateValue = (document2, schema, value, path = []) => {
712
+ var validateValue = (document3, schema, value, path = []) => {
713
713
  const errors = [];
714
- const s = resolveSchema(document2, schema);
714
+ const s = resolveSchema(document3, schema);
715
715
  const fail3 = (message) => errors.push({ path, message });
716
716
  const actual = jsonTypeOf(value);
717
717
  if (actual === "undefined") {
@@ -773,7 +773,7 @@ var validateValue = (document2, schema, value, path = []) => {
773
773
  value.forEach((item, i) => {
774
774
  const itemSchema = s.prefixItems?.[i] ?? s.items;
775
775
  if (itemSchema)
776
- errors.push(...validateValue(document2, itemSchema, item, [...path, i]));
776
+ errors.push(...validateValue(document3, itemSchema, item, [...path, i]));
777
777
  });
778
778
  }
779
779
  if (actual === "object") {
@@ -789,16 +789,16 @@ var validateValue = (document2, schema, value, path = []) => {
789
789
  for (const key of keys) {
790
790
  const property = s.properties?.[key];
791
791
  if (property) {
792
- errors.push(...validateValue(document2, property, record[key], [...path, key]));
792
+ errors.push(...validateValue(document3, property, record[key], [...path, key]));
793
793
  continue;
794
794
  }
795
795
  if (s.additionalProperties === false)
796
796
  fail3(`unexpected property ${key}`);
797
797
  else if (typeof s.additionalProperties === "object") {
798
- errors.push(...validateValue(document2, s.additionalProperties, record[key], [...path, key]));
798
+ errors.push(...validateValue(document3, s.additionalProperties, record[key], [...path, key]));
799
799
  }
800
800
  if (s.propertyNames) {
801
- const nameErrors = validateValue(document2, s.propertyNames, key, [...path, key]);
801
+ const nameErrors = validateValue(document3, s.propertyNames, key, [...path, key]);
802
802
  if (nameErrors.length > 0)
803
803
  fail3(`property name ${key} is invalid: ${nameErrors[0]?.message}`);
804
804
  }
@@ -806,15 +806,15 @@ var validateValue = (document2, schema, value, path = []) => {
806
806
  }
807
807
  if (s.allOf)
808
808
  for (const branch of s.allOf)
809
- errors.push(...validateValue(document2, branch, value, path));
810
- if (s.anyOf && !s.anyOf.some((branch) => validateValue(document2, branch, value).length === 0))
809
+ errors.push(...validateValue(document3, branch, value, path));
810
+ if (s.anyOf && !s.anyOf.some((branch) => validateValue(document3, branch, value).length === 0))
811
811
  fail3("matches no anyOf branch");
812
812
  if (s.oneOf) {
813
- const matches2 = s.oneOf.filter((branch) => validateValue(document2, branch, value).length === 0).length;
813
+ const matches2 = s.oneOf.filter((branch) => validateValue(document3, branch, value).length === 0).length;
814
814
  if (matches2 !== 1)
815
815
  fail3(`matches ${matches2} oneOf branches, expected exactly 1`);
816
816
  }
817
- if (s.not && validateValue(document2, s.not, value).length === 0)
817
+ if (s.not && validateValue(document3, s.not, value).length === 0)
818
818
  fail3("matches forbidden `not` schema");
819
819
  return errors;
820
820
  };
@@ -1388,6 +1388,1232 @@ var clearNamespace = (sqlite, namespace) => {
1388
1388
  });
1389
1389
  };
1390
1390
 
1391
+ // ../../ui/dist/reset.js
1392
+ var CSS_RESET = `*,
1393
+ *::before,
1394
+ *::after {
1395
+ box-sizing: border-box;
1396
+ margin: 0;
1397
+ padding: 0;
1398
+ }
1399
+
1400
+ * {
1401
+ scrollbar-width: thin;
1402
+ scrollbar-color: color-mix(in srgb, CanvasText 35%, Canvas) Canvas;
1403
+ }
1404
+
1405
+ *::-webkit-scrollbar {
1406
+ width: 12px;
1407
+ height: 12px;
1408
+ }
1409
+
1410
+ *::-webkit-scrollbar-track,
1411
+ *::-webkit-scrollbar-corner {
1412
+ background: Canvas;
1413
+ }
1414
+
1415
+ *::-webkit-scrollbar-thumb {
1416
+ background: color-mix(in srgb, CanvasText 35%, Canvas);
1417
+ border: 3px solid Canvas;
1418
+ border-radius: 999px;
1419
+ }
1420
+
1421
+ html {
1422
+ hanging-punctuation: first allow-end last;
1423
+ -webkit-text-size-adjust: none;
1424
+ text-size-adjust: none;
1425
+ tab-size: 4;
1426
+ font-family:
1427
+ ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI",
1428
+ "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
1429
+ font-size: 16px;
1430
+ line-height: 1.5;
1431
+ -webkit-font-smoothing: antialiased;
1432
+ -moz-osx-font-smoothing: grayscale;
1433
+ text-rendering: optimizeLegibility;
1434
+ -webkit-tap-highlight-color: transparent;
1435
+ scroll-behavior: smooth;
1436
+ background: Canvas;
1437
+ color: CanvasText;
1438
+ }
1439
+
1440
+ body {
1441
+ min-height: 100svh;
1442
+ line-height: 1.5;
1443
+ background: Canvas;
1444
+ color: CanvasText;
1445
+ }
1446
+
1447
+ h1 {
1448
+ font-size: 2em;
1449
+ }
1450
+
1451
+ h2 {
1452
+ font-size: 1.5em;
1453
+ }
1454
+
1455
+ h3 {
1456
+ font-size: 1.17em;
1457
+ }
1458
+
1459
+ h4 {
1460
+ font-size: 1em;
1461
+ }
1462
+
1463
+ h5 {
1464
+ font-size: 0.83em;
1465
+ }
1466
+
1467
+ h6 {
1468
+ font-size: 0.67em;
1469
+ }
1470
+
1471
+ h1,
1472
+ h2,
1473
+ h3,
1474
+ h4,
1475
+ h5,
1476
+ h6 {
1477
+ font-weight: 700;
1478
+ line-height: 1.2;
1479
+ text-wrap: balance;
1480
+ overflow-wrap: break-word;
1481
+ }
1482
+
1483
+ p,
1484
+ li,
1485
+ figcaption,
1486
+ dd,
1487
+ dt,
1488
+ blockquote {
1489
+ overflow-wrap: break-word;
1490
+ }
1491
+
1492
+ p,
1493
+ li,
1494
+ figcaption,
1495
+ blockquote {
1496
+ text-wrap: pretty;
1497
+ }
1498
+
1499
+ ul,
1500
+ ol,
1501
+ menu {
1502
+ list-style: none;
1503
+ }
1504
+
1505
+ a {
1506
+ color: inherit;
1507
+ text-decoration: underline;
1508
+ text-decoration-thickness: 1px;
1509
+ text-underline-offset: 0.15em;
1510
+ text-decoration-skip-ink: auto;
1511
+ }
1512
+
1513
+ b,
1514
+ strong {
1515
+ font-weight: 700;
1516
+ }
1517
+
1518
+ small {
1519
+ font-size: 0.875em;
1520
+ }
1521
+
1522
+ code,
1523
+ kbd,
1524
+ samp,
1525
+ pre {
1526
+ font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
1527
+ font-size: 1em;
1528
+ }
1529
+
1530
+ sub,
1531
+ sup {
1532
+ font-size: 75%;
1533
+ line-height: 0;
1534
+ position: relative;
1535
+ vertical-align: baseline;
1536
+ }
1537
+
1538
+ sub {
1539
+ bottom: -0.25em;
1540
+ }
1541
+
1542
+ sup {
1543
+ top: -0.5em;
1544
+ }
1545
+
1546
+ abbr[title] {
1547
+ text-decoration: underline dotted;
1548
+ text-underline-offset: 0.15em;
1549
+ }
1550
+
1551
+ mark {
1552
+ background: Mark;
1553
+ color: MarkText;
1554
+ }
1555
+
1556
+ hr {
1557
+ height: 0;
1558
+ border: 0;
1559
+ border-top: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
1560
+ color: inherit;
1561
+ }
1562
+
1563
+ img,
1564
+ picture,
1565
+ video,
1566
+ canvas {
1567
+ display: block;
1568
+ max-width: 100%;
1569
+ }
1570
+
1571
+ img,
1572
+ video {
1573
+ height: auto;
1574
+ }
1575
+
1576
+ svg {
1577
+ overflow: hidden;
1578
+ }
1579
+
1580
+ iframe {
1581
+ border: 0;
1582
+ max-width: 100%;
1583
+ }
1584
+
1585
+ table {
1586
+ border-collapse: collapse;
1587
+ border-spacing: 0;
1588
+ }
1589
+
1590
+ fieldset {
1591
+ border: 0;
1592
+ min-inline-size: 0;
1593
+ }
1594
+
1595
+ legend {
1596
+ padding: 0;
1597
+ }
1598
+
1599
+ button,
1600
+ input,
1601
+ select,
1602
+ textarea,
1603
+ optgroup {
1604
+ font: inherit;
1605
+ letter-spacing: inherit;
1606
+ color: inherit;
1607
+ margin: 0;
1608
+ vertical-align: middle;
1609
+ }
1610
+
1611
+ button,
1612
+ input[type="button"],
1613
+ input[type="submit"],
1614
+ input[type="reset"] {
1615
+ appearance: none;
1616
+ background: none;
1617
+ border: 0;
1618
+ padding: 0;
1619
+ cursor: pointer;
1620
+ text-align: center;
1621
+ }
1622
+
1623
+ input,
1624
+ textarea,
1625
+ select {
1626
+ background-color: Canvas;
1627
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1628
+ border-radius: 0;
1629
+ padding: 0.5em 0.65em;
1630
+ }
1631
+
1632
+ input[type="checkbox"],
1633
+ input[type="radio"],
1634
+ input[type="range"],
1635
+ input[type="file"],
1636
+ input[type="color"],
1637
+ input[type="button"],
1638
+ input[type="submit"],
1639
+ input[type="reset"],
1640
+ input[type="image"],
1641
+ input[type="hidden"] {
1642
+ background: none;
1643
+ border: 0;
1644
+ padding: 0;
1645
+ border-radius: 0;
1646
+ }
1647
+
1648
+ input[type="checkbox"],
1649
+ input[type="radio"] {
1650
+ accent-color: CanvasText;
1651
+ }
1652
+
1653
+ input[type="search"] {
1654
+ appearance: none;
1655
+ background-color: Canvas;
1656
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1657
+ }
1658
+
1659
+ input[type="search"]::-webkit-search-decoration,
1660
+ input[type="search"]::-webkit-search-cancel-button,
1661
+ input[type="search"]::-webkit-search-results-button,
1662
+ input[type="search"]::-webkit-search-results-decoration {
1663
+ -webkit-appearance: none;
1664
+ display: none;
1665
+ }
1666
+
1667
+ input[type="number"] {
1668
+ appearance: textfield;
1669
+ background-color: Canvas;
1670
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1671
+ }
1672
+
1673
+ input[type="number"]::-webkit-inner-spin-button,
1674
+ input[type="number"]::-webkit-outer-spin-button {
1675
+ -webkit-appearance: none;
1676
+ margin: 0;
1677
+ }
1678
+
1679
+ textarea {
1680
+ resize: vertical;
1681
+ }
1682
+
1683
+ ::placeholder {
1684
+ opacity: 1;
1685
+ color: color-mix(in srgb, CanvasText 45%, transparent);
1686
+ }
1687
+
1688
+ :focus-visible {
1689
+ outline: 2px solid CanvasText;
1690
+ outline-offset: 2px;
1691
+ }
1692
+
1693
+ :focus:not(:focus-visible) {
1694
+ outline: none;
1695
+ }
1696
+
1697
+ :disabled,
1698
+ [disabled] {
1699
+ cursor: default;
1700
+ opacity: 0.6;
1701
+ }
1702
+
1703
+ label,
1704
+ summary,
1705
+ select,
1706
+ [role="button"] {
1707
+ cursor: pointer;
1708
+ }
1709
+
1710
+ summary {
1711
+ display: list-item;
1712
+ }
1713
+
1714
+ details {
1715
+ display: block;
1716
+ }
1717
+
1718
+ dialog {
1719
+ margin: auto;
1720
+ padding: 1rem;
1721
+ border: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
1722
+ background: Canvas;
1723
+ color: CanvasText;
1724
+ max-width: calc(100vw - 2rem);
1725
+ max-height: calc(100svh - 2rem);
1726
+ }
1727
+
1728
+ dialog:not([open]) {
1729
+ display: none;
1730
+ }
1731
+
1732
+ ::backdrop {
1733
+ background: color-mix(in srgb, CanvasText 40%, transparent);
1734
+ }
1735
+
1736
+ [hidden] {
1737
+ display: none !important;
1738
+ }
1739
+
1740
+ ::selection {
1741
+ background: Highlight;
1742
+ color: HighlightText;
1743
+ }
1744
+
1745
+ @media (prefers-reduced-motion: reduce) {
1746
+ *,
1747
+ *::before,
1748
+ *::after {
1749
+ animation-duration: 0.01ms !important;
1750
+ animation-iteration-count: 1 !important;
1751
+ transition-duration: 0.01ms !important;
1752
+ scroll-behavior: auto !important;
1753
+ }
1754
+ }
1755
+
1756
+ @media (pointer: coarse) {
1757
+ input,
1758
+ select,
1759
+ textarea {
1760
+ font-size: max(16px, 1em);
1761
+ }
1762
+ }`;
1763
+
1764
+ // ../core/dist/admin-client.js
1765
+ function isRecord2(value) {
1766
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1767
+ }
1768
+ function mountAdminBrand(doc, host, service, catalog) {
1769
+ const clip = (value, max) => {
1770
+ if (typeof value !== "string")
1771
+ return null;
1772
+ const trimmed = value.trim();
1773
+ if (trimmed === "")
1774
+ return null;
1775
+ return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
1776
+ };
1777
+ const httpUrl = (value) => {
1778
+ if (typeof value !== "string" || value.length > 2e3)
1779
+ return null;
1780
+ try {
1781
+ const url = new URL(value);
1782
+ if (url.protocol !== "https:" && url.protocol !== "http:")
1783
+ return null;
1784
+ return url.href;
1785
+ } catch {
1786
+ return null;
1787
+ }
1788
+ };
1789
+ const hexColor = (value) => {
1790
+ if (typeof value !== "string")
1791
+ return null;
1792
+ const raw = value.trim();
1793
+ return /^#[0-9a-fA-F]{3,8}$/.test(raw) ? raw : null;
1794
+ };
1795
+ const hostname = (value) => {
1796
+ try {
1797
+ return new URL(value).hostname.replace(/^www\./, "");
1798
+ } catch {
1799
+ return value;
1800
+ }
1801
+ };
1802
+ host.replaceChildren();
1803
+ host.hidden = true;
1804
+ if (!isRecord2(catalog) || !Object.hasOwn(catalog, service))
1805
+ return;
1806
+ const row = catalog[service];
1807
+ if (!isRecord2(row))
1808
+ return;
1809
+ const vendor = clip(row.vendor, 80);
1810
+ const website = httpUrl(row.website);
1811
+ const guide = httpUrl(row.guide);
1812
+ const docs = httpUrl(row.docs);
1813
+ const logo = httpUrl(row.logo);
1814
+ const color = hexColor(row.color);
1815
+ const description = clip(row.description, 280);
1816
+ if (vendor === null || website === null && guide === null && docs === null)
1817
+ return;
1818
+ const add = (node) => {
1819
+ host.append(node);
1820
+ return node;
1821
+ };
1822
+ if (logo !== null) {
1823
+ const img = doc.createElement("img");
1824
+ img.alt = "";
1825
+ img.src = logo;
1826
+ img.width = 16;
1827
+ img.height = 16;
1828
+ img.decoding = "async";
1829
+ img.referrerPolicy = "no-referrer";
1830
+ if (color !== null)
1831
+ img.style.borderColor = color;
1832
+ add(img);
1833
+ }
1834
+ const name = doc.createElement("span");
1835
+ name.className = "vendor-name";
1836
+ name.textContent = vendor;
1837
+ if (description !== null)
1838
+ name.title = description;
1839
+ add(name);
1840
+ const sep = () => {
1841
+ const node = doc.createElement("span");
1842
+ node.className = "vendor-sep";
1843
+ node.setAttribute("aria-hidden", "true");
1844
+ node.textContent = "\xB7";
1845
+ return node;
1846
+ };
1847
+ const link = (href, label, title) => {
1848
+ const node = doc.createElement("a");
1849
+ node.className = "vendor-link";
1850
+ node.href = href;
1851
+ node.target = "_blank";
1852
+ node.rel = "noopener noreferrer";
1853
+ node.referrerPolicy = "no-referrer";
1854
+ node.textContent = label;
1855
+ node.title = title;
1856
+ return node;
1857
+ };
1858
+ if (website !== null) {
1859
+ add(sep());
1860
+ add(link(website, hostname(website), `${vendor} website`));
1861
+ }
1862
+ if (guide !== null) {
1863
+ add(sep());
1864
+ add(link(guide, "Docs", `Docs for ${vendor}`));
1865
+ }
1866
+ if (docs !== null) {
1867
+ add(sep());
1868
+ add(link(docs, "API", `${vendor} API reference`));
1869
+ }
1870
+ host.removeAttribute("hidden");
1871
+ host.hidden = false;
1872
+ }
1873
+ function startAdminBrand(env) {
1874
+ const requested = env.params.get("brands");
1875
+ let brandsUrl = env.brandsUrl;
1876
+ if (requested) {
1877
+ try {
1878
+ const url = new URL(requested, env.location.href);
1879
+ const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]" || url.origin === env.location.origin;
1880
+ if ((url.protocol === "https:" || url.protocol === "http:") && local)
1881
+ brandsUrl = url.href;
1882
+ } catch {
1883
+ brandsUrl = env.brandsUrl;
1884
+ }
1885
+ }
1886
+ const vendorHost = env.$("vendor");
1887
+ const brandRoot = env.document.querySelector("[data-mockingbird-admin]");
1888
+ const brandService = brandRoot?.getAttribute("data-service") || "";
1889
+ if (vendorHost === null)
1890
+ return;
1891
+ env.fetch(brandsUrl, {
1892
+ headers: { accept: "application/json" },
1893
+ signal: AbortSignal.timeout(5e3)
1894
+ }).then(async (response) => {
1895
+ if (!response.ok)
1896
+ return null;
1897
+ const catalog = await response.json();
1898
+ return catalog;
1899
+ }).then((catalog) => {
1900
+ mountAdminBrand(env.document, vendorHost, brandService, catalog);
1901
+ }).catch(() => {
1902
+ });
1903
+ }
1904
+ function faultPresetList(value) {
1905
+ if (!isRecord2(value))
1906
+ throw new Error("Fault presets response was not an object");
1907
+ if (!Object.hasOwn(value, "presets") || value.presets === void 0 || value.presets === null) {
1908
+ return { presets: [] };
1909
+ }
1910
+ if (!Array.isArray(value.presets))
1911
+ throw new Error("Fault presets response was not a list");
1912
+ return {
1913
+ presets: value.presets.flatMap((item) => {
1914
+ if (!isRecord2(item) || typeof item.name !== "string")
1915
+ return [];
1916
+ return [{ name: item.name }];
1917
+ })
1918
+ };
1919
+ }
1920
+ function bootAdmin(config) {
1921
+ const fieldKinds = ["string", "number", "boolean", "null", "object", "array", "unknown"];
1922
+ const sources = ["declared", "inferred", "mixed"];
1923
+ const isFieldKind = (value) => typeof value === "string" && fieldKinds.some((kind) => kind === value);
1924
+ const isSource = (value) => typeof value === "string" && sources.some((source) => source === value);
1925
+ const readJson2 = (text) => {
1926
+ const value = JSON.parse(text);
1927
+ return value;
1928
+ };
1929
+ const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => {
1930
+ switch (char) {
1931
+ case "&":
1932
+ return "&amp;";
1933
+ case "<":
1934
+ return "&lt;";
1935
+ case ">":
1936
+ return "&gt;";
1937
+ case '"':
1938
+ return "&quot;";
1939
+ default:
1940
+ return "&#39;";
1941
+ }
1942
+ });
1943
+ const el = (id, kind) => {
1944
+ const node = document.getElementById(id);
1945
+ if (!(node instanceof kind))
1946
+ throw new Error(`admin shell is missing #${id}`);
1947
+ return node;
1948
+ };
1949
+ const errorMessage = (data, fallback) => {
1950
+ if (!isRecord2(data))
1951
+ return fallback;
1952
+ const error = data.error;
1953
+ if (!isRecord2(error) || typeof error.message !== "string" || error.message === "") {
1954
+ return fallback;
1955
+ }
1956
+ return error.message;
1957
+ };
1958
+ const requireList = (value, label) => {
1959
+ if (value === void 0 || value === null)
1960
+ return [];
1961
+ if (!Array.isArray(value))
1962
+ throw new Error(`${label} was not a list`);
1963
+ return value;
1964
+ };
1965
+ const state = {
1966
+ namespace: "default",
1967
+ key: sessionStorage.getItem("mockingbird-admin-key") || "",
1968
+ view: "overview",
1969
+ collection: "",
1970
+ shape: null,
1971
+ route: null,
1972
+ editing: null
1973
+ };
1974
+ const standard = new Set(config.standardRoutes);
1975
+ const banner = el("banner", HTMLElement);
1976
+ const keyInput = el("admin-key", HTMLInputElement);
1977
+ keyInput.value = state.key;
1978
+ const params = new URLSearchParams(location.search);
1979
+ const queryKey = params.get("key");
1980
+ if (queryKey) {
1981
+ state.key = queryKey;
1982
+ keyInput.value = state.key;
1983
+ sessionStorage.setItem("mockingbird-admin-key", state.key);
1984
+ params.delete("key");
1985
+ const next = params.toString();
1986
+ history.replaceState(null, "", location.pathname + (next ? `?${next}` : ""));
1987
+ }
1988
+ const queryNamespace = params.get("namespace");
1989
+ if (queryNamespace)
1990
+ state.namespace = queryNamespace;
1991
+ const showError = (error) => {
1992
+ banner.textContent = error instanceof Error ? error.message : String(error);
1993
+ banner.classList.add("show");
1994
+ };
1995
+ const clearError = () => {
1996
+ banner.classList.remove("show");
1997
+ };
1998
+ const api = async (method, path, body) => {
1999
+ const headers = { accept: "application/json" };
2000
+ if (state.key !== "")
2001
+ headers[config.adminKeyHeader] = state.key;
2002
+ if (state.namespace !== "")
2003
+ headers["x-mockingbird-namespace"] = state.namespace;
2004
+ if (body !== void 0)
2005
+ headers["content-type"] = "application/json";
2006
+ const init = { method, headers };
2007
+ if (body !== void 0)
2008
+ init.body = JSON.stringify(body);
2009
+ const response = await fetch(config.adminPrefix + path, init);
2010
+ const text = await response.text();
2011
+ let data = null;
2012
+ if (text !== "") {
2013
+ try {
2014
+ data = readJson2(text);
2015
+ } catch {
2016
+ data = { raw: text };
2017
+ }
2018
+ }
2019
+ if (response.status === 401) {
2020
+ keyInput.focus();
2021
+ throw new Error(errorMessage(data, "Admin key required"));
2022
+ }
2023
+ if (!response.ok) {
2024
+ throw new Error(errorMessage(data, `${response.status} ${method} ${path}`));
2025
+ }
2026
+ return data;
2027
+ };
2028
+ const originGet = async (path) => {
2029
+ const response = await fetch(path, { headers: { accept: "application/json" } });
2030
+ if (!response.ok)
2031
+ throw new Error(`${response.status} ${path}`);
2032
+ const value = await response.json();
2033
+ return value;
2034
+ };
2035
+ const panelApi = {
2036
+ get: (path) => api("GET", path),
2037
+ send: (method, path, body) => api(method, path, body),
2038
+ namespace: () => state.namespace
2039
+ };
2040
+ const isPanelScript = (value) => typeof value === "function";
2041
+ const setTheme = (theme) => {
2042
+ if (theme)
2043
+ document.documentElement.dataset.theme = theme;
2044
+ else
2045
+ delete document.documentElement.dataset.theme;
2046
+ };
2047
+ const storedTheme = localStorage.getItem("mockingbird-admin-theme");
2048
+ if (storedTheme)
2049
+ setTheme(storedTheme);
2050
+ el("theme", HTMLButtonElement).addEventListener("click", () => {
2051
+ const current = document.documentElement.dataset.theme;
2052
+ const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
2053
+ const next = dark ? "light" : "dark";
2054
+ localStorage.setItem("mockingbird-admin-theme", next);
2055
+ setTheme(next);
2056
+ });
2057
+ const show = (view) => {
2058
+ state.view = view;
2059
+ for (const button of document.querySelectorAll("#nav button")) {
2060
+ if (!(button instanceof HTMLButtonElement))
2061
+ continue;
2062
+ button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false");
2063
+ }
2064
+ for (const section of document.querySelectorAll("main .view")) {
2065
+ if (!(section instanceof HTMLElement))
2066
+ continue;
2067
+ section.hidden = section.id !== `view-${view}`;
2068
+ }
2069
+ };
2070
+ el("nav", HTMLElement).addEventListener("click", (event) => {
2071
+ const target = event.target;
2072
+ if (!(target instanceof Element))
2073
+ return;
2074
+ const button = target.closest("button[data-view]");
2075
+ if (!(button instanceof HTMLButtonElement))
2076
+ return;
2077
+ const view = button.dataset.view;
2078
+ if (view === void 0)
2079
+ return;
2080
+ show(view);
2081
+ refresh().catch(showError);
2082
+ });
2083
+ 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>`;
2084
+ const table = (columns, rows) => {
2085
+ if (rows.length === 0)
2086
+ return '<p class="empty">Nothing here yet.</p>';
2087
+ const head = `<tr>${columns.map((column) => `<th>${column.label}</th>`).join("")}</tr>`;
2088
+ const body = rows.map((row) => {
2089
+ const cells = columns.map((column) => `<td data-label="${column.label}">${column.cell(row)}</td>`).join("");
2090
+ const attrs = "attrs" in row && typeof row.attrs === "string" ? row.attrs : "";
2091
+ return `<tr${attrs}>${cells}</tr>`;
2092
+ }).join("");
2093
+ return `<table><thead>${head}</thead><tbody>${body}</tbody></table>`;
2094
+ };
2095
+ const preview = (value) => {
2096
+ const text = JSON.stringify(value) ?? "null";
2097
+ return esc(text.length > 180 ? `${text.slice(0, 180)}\u2026` : text);
2098
+ };
2099
+ const when = (epoch) => {
2100
+ const date = new Date(epoch);
2101
+ return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString();
2102
+ };
2103
+ const clockCard = (epoch) => {
2104
+ const date = new Date(epoch);
2105
+ if (Number.isNaN(date.getTime()))
2106
+ return esc(String(epoch));
2107
+ const iso = date.toISOString();
2108
+ return `${esc(iso.slice(0, 10))}<span class="clock-time">${esc(iso.slice(11, 19))} UTC</span>`;
2109
+ };
2110
+ const decodeClock = (value) => {
2111
+ if (!isRecord2(value) || typeof value.now !== "number" || typeof value.offsetMs !== "number" || typeof value.frozen !== "boolean") {
2112
+ throw new Error("Clock response was not a clock");
2113
+ }
2114
+ return { now: value.now, offsetMs: value.offsetMs, frozen: value.frozen };
2115
+ };
2116
+ const decodeField = (value) => {
2117
+ if (!isRecord2(value) || typeof value.name !== "string" || !isFieldKind(value.kind)) {
2118
+ return void 0;
2119
+ }
2120
+ const field = {
2121
+ name: value.name,
2122
+ kind: value.kind,
2123
+ optional: value.optional === true
2124
+ };
2125
+ if (typeof value.description === "string")
2126
+ field.description = value.description;
2127
+ if (Array.isArray(value.fields)) {
2128
+ field.fields = value.fields.flatMap((item) => {
2129
+ const child = decodeField(item);
2130
+ return child === void 0 ? [] : [child];
2131
+ });
2132
+ }
2133
+ return field;
2134
+ };
2135
+ const decodeCollection = (value) => {
2136
+ if (!isRecord2(value) || typeof value.name !== "string")
2137
+ return void 0;
2138
+ const fields = Array.isArray(value.fields) ? value.fields.flatMap((item) => {
2139
+ const field = decodeField(item);
2140
+ return field === void 0 ? [] : [field];
2141
+ }) : [];
2142
+ const collection = {
2143
+ name: value.name,
2144
+ label: typeof value.label === "string" ? value.label : value.name,
2145
+ count: typeof value.count === "number" ? value.count : 0,
2146
+ source: isSource(value.source) ? value.source : "inferred",
2147
+ fields
2148
+ };
2149
+ if (typeof value.description === "string")
2150
+ collection.description = value.description;
2151
+ return collection;
2152
+ };
2153
+ const decodeState = (value) => {
2154
+ if (!isRecord2(value) || !Array.isArray(value.collections)) {
2155
+ throw new Error("State response has no collection list");
2156
+ }
2157
+ return {
2158
+ namespace: typeof value.namespace === "string" ? value.namespace : state.namespace,
2159
+ storageNamespace: typeof value.storageNamespace === "string" ? value.storageNamespace : "",
2160
+ collections: value.collections.flatMap((item) => {
2161
+ const collection = decodeCollection(item);
2162
+ return collection === void 0 ? [] : [collection];
2163
+ })
2164
+ };
2165
+ };
2166
+ const decodeRecords = (value) => requireList(isRecord2(value) ? value.records : void 0, "Records").flatMap((item) => {
2167
+ if (!isRecord2(item) || typeof item.id !== "string")
2168
+ return [];
2169
+ return [
2170
+ {
2171
+ id: item.id,
2172
+ seq: typeof item.seq === "number" ? item.seq : 0,
2173
+ value: item.value,
2174
+ attrs: ` class="clickable" data-id="${esc(item.id)}"`
2175
+ }
2176
+ ];
2177
+ });
2178
+ const textOf = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : "";
2179
+ const decodeJournal = (value) => requireList(isRecord2(value) ? value.requests : void 0, "Journal").flatMap((item) => {
2180
+ if (!isRecord2(item))
2181
+ return [];
2182
+ return [
2183
+ {
2184
+ at: textOf(item.at),
2185
+ method: textOf(item.method),
2186
+ path: textOf(item.path),
2187
+ status: textOf(item.status),
2188
+ durationMs: textOf(item.durationMs),
2189
+ operationId: textOf(item.operationId)
2190
+ }
2191
+ ];
2192
+ });
2193
+ const decodeFaults = (value) => requireList(isRecord2(value) ? value.faults : void 0, "Faults").flatMap((item) => {
2194
+ if (!isRecord2(item))
2195
+ return [];
2196
+ return [
2197
+ {
2198
+ id: textOf(item.id),
2199
+ method: typeof item.method === "string" ? item.method : void 0,
2200
+ pathPrefix: typeof item.pathPrefix === "string" ? item.pathPrefix : void 0,
2201
+ operationId: typeof item.operationId === "string" ? item.operationId : void 0,
2202
+ status: typeof item.status === "number" ? item.status : void 0,
2203
+ effect: typeof item.effect === "string" ? item.effect : void 0,
2204
+ drop: typeof item.drop === "boolean" ? item.drop : void 0,
2205
+ latencyMs: typeof item.latencyMs === "number" ? item.latencyMs : void 0
2206
+ }
2207
+ ];
2208
+ });
2209
+ const loadNamespaces = async () => {
2210
+ const data = await api("GET", "/namespaces");
2211
+ const select = el("namespace", HTMLSelectElement);
2212
+ const listed = requireList(isRecord2(data) ? data.namespaces : void 0, "Namespaces");
2213
+ const fallback = isRecord2(data) && typeof data.default === "string" && data.default !== "" ? data.default : "default";
2214
+ const names = (listed.length > 0 ? listed.map((name) => String(name)) : [fallback]).slice();
2215
+ if (!names.includes(state.namespace))
2216
+ names.unshift(state.namespace);
2217
+ select.innerHTML = names.map((name) => `<option value="${esc(name)}">${esc(name)}</option>`).join("");
2218
+ select.value = state.namespace;
2219
+ };
2220
+ const renderOverview = async () => {
2221
+ const [health, shape, clock, journal] = await Promise.all([
2222
+ originGet("/health"),
2223
+ api("GET", "/state"),
2224
+ api("GET", "/clock"),
2225
+ api("GET", "/requests?limit=8")
2226
+ ]);
2227
+ if (!isRecord2(health))
2228
+ throw new Error("Health response was not an object");
2229
+ const decoded = decodeState(shape);
2230
+ state.shape = decoded;
2231
+ const time = decodeClock(clock);
2232
+ el("overview-cards", HTMLElement).innerHTML = cards([
2233
+ {
2234
+ k: "Service",
2235
+ v: esc(typeof health.service === "string" && health.service !== "" ? health.service : config.service)
2236
+ },
2237
+ {
2238
+ k: "Status",
2239
+ v: esc(typeof health.status === "string" && health.status !== "" ? health.status : "ok")
2240
+ },
2241
+ { k: "Namespace", v: esc(state.namespace) },
2242
+ { k: "Collections", v: String(decoded.collections.length) },
2243
+ {
2244
+ k: "Records",
2245
+ v: String(decoded.collections.reduce((sum, item) => sum + item.count, 0))
2246
+ },
2247
+ { k: "Clock", v: clockCard(time.now) }
2248
+ ]);
2249
+ el("overview-requests", HTMLElement).innerHTML = table([
2250
+ { label: "Method", cell: (row) => esc(row.method) },
2251
+ { label: "Path", cell: (row) => esc(row.path) },
2252
+ {
2253
+ label: "Status",
2254
+ cell: (row) => `<span class="status-${row.status.charAt(0)}">${esc(row.status)}</span>`
2255
+ },
2256
+ { label: "Operation", cell: (row) => esc(row.operationId) }
2257
+ ], decodeJournal(journal));
2258
+ };
2259
+ const schemaChips = (collection) => {
2260
+ if (collection.fields.length === 0) {
2261
+ return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>';
2262
+ }
2263
+ return `<div class="chips">${collection.fields.map((field) => `<span class="chip">${esc(field.name)} <i>${esc(field.kind)}${field.optional ? "?" : ""}</i></span>`).join("")}</div>`;
2264
+ };
2265
+ const renderState = async () => {
2266
+ const shape = decodeState(await api("GET", "/state"));
2267
+ state.shape = shape;
2268
+ const host = el("collections", HTMLElement);
2269
+ if (shape.collections.length === 0) {
2270
+ host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>';
2271
+ el("schema", HTMLElement).innerHTML = "";
2272
+ el("records", HTMLElement).innerHTML = "";
2273
+ return;
2274
+ }
2275
+ if (!shape.collections.some((item) => item.name === state.collection)) {
2276
+ const first = shape.collections[0];
2277
+ if (first === void 0)
2278
+ return;
2279
+ state.collection = first.name;
2280
+ }
2281
+ 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("");
2282
+ const selected = shape.collections.find((item) => item.name === state.collection);
2283
+ if (selected === void 0)
2284
+ return;
2285
+ const description = selected.description ? `<p class="lede">${esc(selected.description)}</p>` : "";
2286
+ el("schema", HTMLElement).innerHTML = `<h3>${esc(selected.label)}</h3>${description}${schemaChips(selected)}`;
2287
+ const page = await api("GET", `/state/${encodeURIComponent(state.collection)}?limit=50`);
2288
+ el("records", HTMLElement).innerHTML = table([
2289
+ { label: "Id", cell: (row) => `<code>${esc(row.id)}</code>` },
2290
+ { label: "Seq", cell: (row) => esc(row.seq) },
2291
+ { label: "Value", cell: (row) => preview(row.value) }
2292
+ ], decodeRecords(page));
2293
+ };
2294
+ const renderClock = async () => {
2295
+ const clock = decodeClock(await api("GET", "/clock"));
2296
+ 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>`;
2297
+ const freeze = el("freeze", HTMLButtonElement);
2298
+ freeze.textContent = clock.frozen ? "Unfreeze" : "Freeze";
2299
+ freeze.dataset.frozen = clock.frozen ? "1" : "0";
2300
+ };
2301
+ const renderFaults = async () => {
2302
+ const [faults, presets] = await Promise.all([
2303
+ api("GET", "/faults"),
2304
+ api("GET", "/faults/presets").catch(() => ({ presets: [] }))
2305
+ ]);
2306
+ const select = el("fault-preset", HTMLSelectElement);
2307
+ const current = select.value;
2308
+ const list = faultPresetList(presets).presets;
2309
+ select.innerHTML = `<option value="">Custom rule</option>${list.map((preset) => `<option value="${esc(preset.name)}">${esc(preset.name)}</option>`).join("")}`;
2310
+ select.value = current;
2311
+ el("fault-list", HTMLElement).innerHTML = table([
2312
+ { label: "Id", cell: (row) => esc(row.id) },
2313
+ {
2314
+ label: "Match",
2315
+ cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter((part) => Boolean(part)).join(" ") || "all")
2316
+ },
2317
+ {
2318
+ label: "Effect",
2319
+ cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || ""))
2320
+ }
2321
+ ], decodeFaults(faults));
2322
+ };
2323
+ const renderJournal = async () => {
2324
+ const journal = await api("GET", "/requests?limit=100");
2325
+ el("journal", HTMLElement).innerHTML = table([
2326
+ { label: "When", cell: (row) => esc(row.at) },
2327
+ { label: "Method", cell: (row) => esc(row.method) },
2328
+ { label: "Path", cell: (row) => esc(row.path) },
2329
+ { label: "Status", cell: (row) => esc(row.status) },
2330
+ { label: "ms", cell: (row) => esc(row.durationMs) }
2331
+ ], decodeJournal(journal));
2332
+ };
2333
+ const renderRoutes = async () => {
2334
+ const data = await api("GET", "/");
2335
+ if (!isRecord2(data))
2336
+ throw new Error("Route list was not an object");
2337
+ const routes = requireList(data.routes, "Routes").map((route) => String(route));
2338
+ el("route-list", HTMLElement).innerHTML = routes.map((route) => {
2339
+ const extra = standard.has(route) ? "" : " <span class='badge'>extension</span>";
2340
+ const parts = route.split(" ");
2341
+ const method = parts[0] ?? "";
2342
+ const path = parts.slice(1).join(" ");
2343
+ 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>`;
2344
+ }).join("");
2345
+ };
2346
+ const renderers = {
2347
+ overview: renderOverview,
2348
+ state: renderState,
2349
+ clock: renderClock,
2350
+ faults: renderFaults,
2351
+ journal: renderJournal,
2352
+ routes: renderRoutes
2353
+ };
2354
+ async function refresh() {
2355
+ clearError();
2356
+ const render = renderers[state.view];
2357
+ if (render)
2358
+ await render();
2359
+ }
2360
+ el("namespace", HTMLSelectElement).addEventListener("change", () => {
2361
+ state.namespace = el("namespace", HTMLSelectElement).value;
2362
+ refresh().catch(showError);
2363
+ });
2364
+ keyInput.addEventListener("change", () => {
2365
+ state.key = keyInput.value.trim();
2366
+ sessionStorage.setItem("mockingbird-admin-key", state.key);
2367
+ refresh().catch(showError);
2368
+ });
2369
+ el("refresh", HTMLButtonElement).addEventListener("click", () => {
2370
+ refresh().catch(showError);
2371
+ });
2372
+ el("collections", HTMLElement).addEventListener("click", (event) => {
2373
+ const target = event.target;
2374
+ if (!(target instanceof Element))
2375
+ return;
2376
+ const button = target.closest("[data-collection]");
2377
+ if (!(button instanceof HTMLElement))
2378
+ return;
2379
+ const collection = button.dataset.collection;
2380
+ if (collection === void 0)
2381
+ return;
2382
+ state.collection = collection;
2383
+ renderState().catch(showError);
2384
+ });
2385
+ el("records", HTMLElement).addEventListener("click", (event) => {
2386
+ const target = event.target;
2387
+ if (!(target instanceof Element))
2388
+ return;
2389
+ const row = target.closest("[data-id]");
2390
+ if (!(row instanceof HTMLElement))
2391
+ return;
2392
+ const id = row.dataset.id;
2393
+ if (id === void 0)
2394
+ return;
2395
+ openEditor(id).catch(showError);
2396
+ });
2397
+ const editor = el("editor", HTMLDialogElement);
2398
+ const editorId = el("editor-id", HTMLInputElement);
2399
+ const editorValue = el("editor-value", HTMLTextAreaElement);
2400
+ const editorDelete = el("editor-delete", HTMLButtonElement);
2401
+ async function openEditor(id) {
2402
+ state.editing = id;
2403
+ el("editor-title", HTMLElement).textContent = id ? "Edit record" : "New record";
2404
+ editorId.value = id ?? "";
2405
+ editorId.readOnly = id !== null;
2406
+ editorDelete.hidden = id === null;
2407
+ if (id) {
2408
+ const record = await api("GET", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`);
2409
+ if (!isRecord2(record))
2410
+ throw new Error("Record response was not an object");
2411
+ editorValue.value = JSON.stringify(record.value, null, 2) ?? "";
2412
+ } else {
2413
+ editorValue.value = "{\n \n}";
2414
+ }
2415
+ editor.showModal();
2416
+ }
2417
+ el("new-record", HTMLButtonElement).addEventListener("click", () => {
2418
+ openEditor(null).catch(showError);
2419
+ });
2420
+ el("editor-form", HTMLFormElement).addEventListener("submit", (event) => {
2421
+ const submitter = event.submitter;
2422
+ if (!(submitter instanceof HTMLButtonElement) || submitter.value !== "save")
2423
+ return;
2424
+ event.preventDefault();
2425
+ let value;
2426
+ try {
2427
+ value = readJson2(editorValue.value);
2428
+ } catch {
2429
+ showError(new Error("Value is not JSON"));
2430
+ return;
2431
+ }
2432
+ const id = editorId.value.trim();
2433
+ const path = `/state/${encodeURIComponent(state.collection)}`;
2434
+ const request = state.editing ? api("PUT", `${path}/${encodeURIComponent(id)}`, { value }) : api("POST", path, { id: id || void 0, value });
2435
+ request.then(() => {
2436
+ editor.close();
2437
+ return renderState();
2438
+ }).catch(showError);
2439
+ });
2440
+ editorDelete.addEventListener("click", () => {
2441
+ const id = state.editing;
2442
+ if (!id)
2443
+ return;
2444
+ api("DELETE", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`).then(() => {
2445
+ editor.close();
2446
+ return renderState();
2447
+ }).catch(showError);
2448
+ });
2449
+ for (const button of document.querySelectorAll("[data-advance]")) {
2450
+ if (!(button instanceof HTMLButtonElement))
2451
+ continue;
2452
+ button.addEventListener("click", () => {
2453
+ const raw = button.dataset.advance;
2454
+ if (raw === void 0)
2455
+ return;
2456
+ api("POST", "/clock", { advance: Number(raw) }).then(() => renderClock()).catch(showError);
2457
+ });
2458
+ }
2459
+ el("freeze", HTMLButtonElement).addEventListener("click", () => {
2460
+ const frozen = el("freeze", HTMLButtonElement).dataset.frozen === "1";
2461
+ api("POST", "/clock", { freeze: !frozen }).then(() => renderClock()).catch(showError);
2462
+ });
2463
+ el("clock-reset", HTMLButtonElement).addEventListener("click", () => {
2464
+ api("POST", "/clock", { reset: true }).then(() => renderClock()).catch(showError);
2465
+ });
2466
+ el("clock-set", HTMLFormElement).addEventListener("submit", (event) => {
2467
+ event.preventDefault();
2468
+ const raw = el("clock-instant", HTMLInputElement).value.trim();
2469
+ const set = /^-?\d+$/.test(raw) ? Number(raw) : raw;
2470
+ api("POST", "/clock", { set }).then(() => renderClock()).catch(showError);
2471
+ });
2472
+ el("fault-form", HTMLFormElement).addEventListener("submit", (event) => {
2473
+ event.preventDefault();
2474
+ const preset = el("fault-preset", HTMLSelectElement).value;
2475
+ if (preset !== "") {
2476
+ api("POST", "/faults", { preset }).then(() => renderFaults()).catch(showError);
2477
+ return;
2478
+ }
2479
+ const status = Number(el("fault-status", HTMLInputElement).value);
2480
+ if (!Number.isFinite(status)) {
2481
+ showError(new Error("Status must be a number, or choose a preset"));
2482
+ return;
2483
+ }
2484
+ const path = el("fault-path", HTMLInputElement).value;
2485
+ const method = el("fault-method", HTMLInputElement).value;
2486
+ api("POST", "/faults", {
2487
+ status,
2488
+ ...path !== "" ? { pathPrefix: path } : {},
2489
+ ...method !== "" ? { method } : {}
2490
+ }).then(() => renderFaults()).catch(showError);
2491
+ });
2492
+ el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
2493
+ api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
2494
+ });
2495
+ el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
2496
+ api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
2497
+ });
2498
+ el("route-list", HTMLElement).addEventListener("click", (event) => {
2499
+ const target = event.target;
2500
+ if (!(target instanceof Element))
2501
+ return;
2502
+ const button = target.closest("[data-route]");
2503
+ if (!(button instanceof HTMLElement))
2504
+ return;
2505
+ const route = button.dataset.route;
2506
+ if (route === void 0)
2507
+ return;
2508
+ state.route = route;
2509
+ el("route-title", HTMLElement).textContent = route;
2510
+ const method = route.split(" ")[0] ?? "";
2511
+ el("route-body", HTMLTextAreaElement).disabled = method === "GET" || method === "DELETE";
2512
+ });
2513
+ el("route-form", HTMLFormElement).addEventListener("submit", (event) => {
2514
+ event.preventDefault();
2515
+ const send = async () => {
2516
+ if (state.route === null)
2517
+ throw new Error("Choose a route first");
2518
+ const parts = state.route.split(" ");
2519
+ const method = parts[0];
2520
+ const path = parts.slice(1).join(" ");
2521
+ if (method === void 0)
2522
+ throw new Error("Choose a route first");
2523
+ const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_match, key) => prompt(`Value for ${key}`) || "");
2524
+ let body;
2525
+ const routeBody = el("route-body", HTMLTextAreaElement);
2526
+ if (method !== "GET" && method !== "DELETE" && routeBody.value.trim() !== "") {
2527
+ try {
2528
+ body = readJson2(routeBody.value);
2529
+ } catch {
2530
+ throw new Error("Body is not JSON");
2531
+ }
2532
+ }
2533
+ const data = await api(method, concrete, body);
2534
+ el("route-result", HTMLElement).textContent = JSON.stringify(data, null, 2);
2535
+ };
2536
+ send().catch(showError);
2537
+ });
2538
+ const decodePanel = (value) => {
2539
+ if (!isRecord2(value) || typeof value.id !== "string" || typeof value.title !== "string") {
2540
+ return void 0;
2541
+ }
2542
+ const panel = {
2543
+ id: value.id,
2544
+ title: value.title,
2545
+ description: typeof value.description === "string" ? value.description : "",
2546
+ html: typeof value.html === "string" ? value.html : ""
2547
+ };
2548
+ if (typeof value.script === "string")
2549
+ panel.script = value.script;
2550
+ return panel;
2551
+ };
2552
+ const mountPanels = (panels) => {
2553
+ const nav = el("nav", HTMLElement);
2554
+ const views = el("views", HTMLElement);
2555
+ for (const panel of panels) {
2556
+ const button = document.createElement("button");
2557
+ button.type = "button";
2558
+ button.dataset.view = panel.id;
2559
+ button.textContent = panel.title;
2560
+ nav.append(button);
2561
+ const section = document.createElement("section");
2562
+ section.className = "view";
2563
+ section.id = `view-${panel.id}`;
2564
+ section.hidden = true;
2565
+ const title = document.createElement("h2");
2566
+ title.textContent = panel.title;
2567
+ const lede = document.createElement("p");
2568
+ lede.className = "lede";
2569
+ lede.textContent = panel.description;
2570
+ const body = document.createElement("div");
2571
+ body.className = "panel-body";
2572
+ body.id = `panel-${panel.id}`;
2573
+ body.innerHTML = panel.html;
2574
+ section.append(title, lede, body);
2575
+ views.append(section);
2576
+ if (panel.script === void 0)
2577
+ continue;
2578
+ try {
2579
+ const compiled = new Function("root", "api", panel.script);
2580
+ if (!isPanelScript(compiled))
2581
+ throw new Error("panel script did not compile");
2582
+ compiled(body, panelApi);
2583
+ } catch (error) {
2584
+ const note = document.createElement("p");
2585
+ note.className = "banner show";
2586
+ note.textContent = error instanceof Error ? error.message : String(error);
2587
+ body.append(note);
2588
+ }
2589
+ }
2590
+ };
2591
+ startAdminBrand({
2592
+ document,
2593
+ $: (id) => document.getElementById(id),
2594
+ params,
2595
+ location: { href: location.href, origin: location.origin },
2596
+ fetch,
2597
+ brandsUrl: config.brandsUrl
2598
+ });
2599
+ loadNamespaces().then(() => api("GET", "/ui/manifest")).then((manifest) => {
2600
+ const panels = requireList(isRecord2(manifest) ? manifest.panels : void 0, "Panels").flatMap((panel) => {
2601
+ const decoded = decodePanel(panel);
2602
+ return decoded === void 0 ? [] : [decoded];
2603
+ });
2604
+ mountPanels(panels);
2605
+ return refresh();
2606
+ }).catch(showError);
2607
+ }
2608
+ var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
2609
+ var adminClientSource = (config) => {
2610
+ const payload = JSON.stringify(config).replace(/</g, "\\u003c");
2611
+ return `<script>
2612
+ ${clientPrelude}
2613
+ bootAdmin(${payload});
2614
+ </script>`;
2615
+ };
2616
+
1391
2617
  // ../core/dist/surface.js
1392
2618
  var defineMock = (runtime) => runtime;
1393
2619
  var STANDARD_ADMIN_ROUTES = [
@@ -1456,94 +2682,106 @@ var escapeHtml = (value) => value.replace(/[&<>"']/g, (char) => {
1456
2682
  return "&#39;";
1457
2683
  }
1458
2684
  });
2685
+ var ADMIN_BRANDS_URL = "https://mockingbird.chrisvouga.dev/brands.json";
1459
2686
  var renderAdminDocument = (service) => {
1460
2687
  const name = escapeHtml(service);
1461
- const standard = JSON.stringify(STANDARD_ADMIN_ROUTES);
2688
+ const script = adminClientSource({
2689
+ standardRoutes: STANDARD_ADMIN_ROUTES,
2690
+ adminPrefix: ADMIN_PREFIX,
2691
+ adminKeyHeader: ADMIN_KEY_HEADER,
2692
+ brandsUrl: ADMIN_BRANDS_URL,
2693
+ service
2694
+ });
1462
2695
  return `<!DOCTYPE html>
1463
2696
  <html lang="en">
1464
2697
  <head>
1465
2698
  <meta charset="utf-8">
1466
2699
  <meta name="viewport" content="width=device-width, initial-scale=1">
1467
2700
  <meta name="color-scheme" content="light dark">
1468
- <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">
2701
+ <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">
1469
2702
  <title>${name} admin</title>
1470
2703
  <style>
2704
+ ${CSS_RESET}
1471
2705
  :root {
1472
2706
  color-scheme: light;
1473
- --bg: #f6f5f2;
2707
+ --bg: #fafafa;
1474
2708
  --bg-elev: #ffffff;
1475
- --bg-muted: #eeece6;
1476
- --bg-inset: #f3f1eb;
1477
- --line: #e2dfd6;
1478
- --line-strong: #d0ccc1;
1479
- --ink: #1c1915;
1480
- --muted: #5e584f;
1481
- --faint: #8a8378;
1482
- --accent: #3d4f3a;
1483
- --accent-ink: #f7f6f2;
1484
- --accent-soft: #e5ece2;
1485
- --ok: #1f7a45;
1486
- --ok-soft: #e5f4eb;
1487
- --warn: #9a6700;
1488
- --warn-soft: #fbf3df;
1489
- --err: #a33b32;
1490
- --err-soft: #fdecea;
1491
- --shadow: 0 1px 2px rgb(28 25 21 / 0.05), 0 12px 32px rgb(28 25 21 / 0.06);
1492
- --sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
2709
+ --bg-muted: #f4f4f5;
2710
+ --bg-inset: #f4f4f5;
2711
+ --line: #dedee3;
2712
+ --line-strong: #c4c4cc;
2713
+ --ink: #18181b;
2714
+ --muted: #62626b;
2715
+ --faint: #71717a;
2716
+ --accent: #27272a;
2717
+ --accent-ink: #ffffff;
2718
+ --accent-soft: #f4f4f5;
2719
+ --ok: #166534;
2720
+ --ok-soft: #f0fdf4;
2721
+ --warn: #a16207;
2722
+ --warn-soft: #fefce8;
2723
+ --err: #a82d32;
2724
+ --err-soft: #fef2f2;
2725
+ --shadow: none;
2726
+ --sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
1493
2727
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
1494
- --radius: 14px;
1495
- --header: 60px;
1496
- --nav: 220px;
2728
+ --radius: 10px;
2729
+ --header: 56px;
2730
+ --nav: 200px;
1497
2731
  }
1498
2732
  :root[data-theme="dark"] {
1499
2733
  color-scheme: dark;
1500
- --bg: #141311;
1501
- --bg-elev: #1e1c19;
1502
- --bg-muted: #26231f;
1503
- --bg-inset: #181715;
1504
- --line: #34302b;
1505
- --line-strong: #4a453d;
1506
- --ink: #f4f1ea;
1507
- --muted: #c4bdb1;
1508
- --faint: #8f877c;
1509
- --accent: #c5d5b8;
1510
- --accent-ink: #1a2118;
1511
- --accent-soft: #2a3328;
1512
- --ok: #8fd6a8;
1513
- --ok-soft: #1a2e22;
1514
- --warn: #f0cc78;
1515
- --warn-soft: #332911;
1516
- --err: #f0a8a2;
1517
- --err-soft: #3a201e;
1518
- --shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 16px 40px rgb(0 0 0 / 0.28);
2734
+ --bg: #111113;
2735
+ --bg-elev: #19191c;
2736
+ --bg-muted: #242428;
2737
+ --bg-inset: #111113;
2738
+ --line: #36363c;
2739
+ --line-strong: #52525b;
2740
+ --ink: #f4f4f5;
2741
+ --muted: #a9a9b2;
2742
+ --faint: #71717a;
2743
+ --accent: #e4e4e7;
2744
+ --accent-ink: #18181b;
2745
+ --accent-soft: #242428;
2746
+ --ok: #86efac;
2747
+ --ok-soft: #14241b;
2748
+ --warn: #fde68a;
2749
+ --warn-soft: #2a2410;
2750
+ --err: #ffaaaa;
2751
+ --err-soft: #2c1516;
2752
+ --shadow: none;
1519
2753
  }
1520
2754
  @media (prefers-color-scheme: dark) {
1521
2755
  :root:not([data-theme="light"]) {
1522
2756
  color-scheme: dark;
1523
- --bg: #141311;
1524
- --bg-elev: #1e1c19;
1525
- --bg-muted: #26231f;
1526
- --bg-inset: #181715;
1527
- --line: #34302b;
1528
- --line-strong: #4a453d;
1529
- --ink: #f4f1ea;
1530
- --muted: #c4bdb1;
1531
- --faint: #8f877c;
1532
- --accent: #c5d5b8;
1533
- --accent-ink: #1a2118;
1534
- --accent-soft: #2a3328;
1535
- --ok: #8fd6a8;
1536
- --ok-soft: #1a2e22;
1537
- --warn: #f0cc78;
1538
- --warn-soft: #332911;
1539
- --err: #f0a8a2;
1540
- --err-soft: #3a201e;
1541
- --shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 16px 40px rgb(0 0 0 / 0.28);
1542
- }
1543
- }
1544
- * { box-sizing: border-box; }
1545
- html, body { margin: 0; height: 100%; }
2757
+ --bg: #111113;
2758
+ --bg-elev: #19191c;
2759
+ --bg-muted: #242428;
2760
+ --bg-inset: #111113;
2761
+ --line: #36363c;
2762
+ --line-strong: #52525b;
2763
+ --ink: #f4f4f5;
2764
+ --muted: #a9a9b2;
2765
+ --faint: #71717a;
2766
+ --accent: #e4e4e7;
2767
+ --accent-ink: #18181b;
2768
+ --accent-soft: #242428;
2769
+ --ok: #86efac;
2770
+ --ok-soft: #14241b;
2771
+ --warn: #fde68a;
2772
+ --warn-soft: #2a2410;
2773
+ --err: #ffaaaa;
2774
+ --err-soft: #2c1516;
2775
+ --shadow: none;
2776
+ }
2777
+ }
2778
+ /* Fill the viewport, then grow with the content so the background covers the scroll. */
2779
+ html { height: 100%; }
1546
2780
  body {
2781
+ margin: 0;
2782
+ min-height: 100%;
2783
+ display: flex;
2784
+ flex-direction: column;
1547
2785
  font-family: var(--sans);
1548
2786
  background: var(--bg);
1549
2787
  color: var(--ink);
@@ -1552,24 +2790,37 @@ var renderAdminDocument = (service) => {
1552
2790
  padding-right: env(safe-area-inset-right);
1553
2791
  -webkit-text-size-adjust: 100%;
1554
2792
  }
1555
- button, input, select, textarea { font: inherit; color: inherit; }
1556
- button { cursor: pointer; }
1557
2793
  :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
1558
- @media (prefers-reduced-motion: reduce) {
1559
- * { scroll-behavior: auto !important; transition: none !important; }
1560
- }
1561
- .app { min-height: 100%; display: flex; flex-direction: column; }
2794
+ .app { flex: 1 0 auto; display: flex; flex-direction: column; min-width: 0; width: 100%; }
1562
2795
  header.top {
1563
2796
  position: sticky; top: 0; z-index: 5;
1564
2797
  display: flex; align-items: center; gap: 12px;
1565
2798
  min-height: var(--header); padding: 10px 16px;
1566
- background: color-mix(in srgb, var(--bg-elev) 92%, transparent);
2799
+ background: var(--bg-elev);
1567
2800
  border-bottom: 1px solid var(--line);
1568
- backdrop-filter: blur(10px);
1569
- }
1570
- .brand { display: flex; flex-direction: column; min-width: 0; margin-right: auto; }
1571
- .brand strong { font-size: 15px; letter-spacing: -0.01em; }
1572
- .brand span { color: var(--faint); font-size: 12px; }
2801
+ min-width: 0; max-width: 100%;
2802
+ }
2803
+ .brand {
2804
+ display: flex; flex-flow: row wrap; align-items: center; gap: 14px;
2805
+ min-width: 0; max-width: 100%; margin-right: auto;
2806
+ }
2807
+ .brand-id { display: flex; flex-direction: column; min-width: 0; flex: none; }
2808
+ .brand-id strong { font-size: 15px; letter-spacing: -0.01em; }
2809
+ .brand-id span { color: var(--faint); font-size: 12px; }
2810
+ .vendor {
2811
+ display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
2812
+ min-width: 0; max-width: 100%; font-size: 12px; color: var(--muted);
2813
+ }
2814
+ .vendor[hidden] { display: none; }
2815
+ /* Logos are drawn for a light tile, same as the docs site, so the plate stays white. */
2816
+ .vendor img {
2817
+ width: 16px; height: 16px; padding: 2px; box-sizing: content-box;
2818
+ border-radius: 4px; border: 1px solid var(--line); background: #fff; object-fit: contain;
2819
+ }
2820
+ .vendor-name { color: var(--ink); font-weight: 600; }
2821
+ .vendor-sep { color: var(--faint); }
2822
+ .vendor-link { color: inherit; text-decoration: none; }
2823
+ .vendor-link:hover { color: var(--ink); text-decoration: underline; }
1573
2824
  .controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
1574
2825
  label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
1575
2826
  label.field input, label.field select, .controls input[type="password"] {
@@ -1587,7 +2838,7 @@ var renderAdminDocument = (service) => {
1587
2838
  record or the full nav row would widen the page past the viewport. */
1588
2839
  .layout {
1589
2840
  display: grid; grid-template-columns: var(--nav) minmax(0, 1fr);
1590
- flex: 1; min-height: 0; min-width: 0;
2841
+ flex: 1 0 auto; min-width: 0;
1591
2842
  }
1592
2843
  nav.side {
1593
2844
  border-right: 1px solid var(--line); padding: 12px;
@@ -1602,7 +2853,7 @@ var renderAdminDocument = (service) => {
1602
2853
  main { padding: 20px; max-width: 1100px; width: 100%; min-width: 0; }
1603
2854
  .view h2 { margin: 0 0 4px; font-size: 22px; letter-spacing: -0.02em; }
1604
2855
  .lede { margin: 0 0 16px; color: var(--muted); }
1605
- .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
2856
+ .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr)); gap: 12px; }
1606
2857
  .card {
1607
2858
  background: var(--bg-elev); border: 1px solid var(--line); border-radius: var(--radius);
1608
2859
  padding: 14px 16px; box-shadow: var(--shadow);
@@ -1641,8 +2892,8 @@ var renderAdminDocument = (service) => {
1641
2892
  .banner.show { display: block; }
1642
2893
  .empty { color: var(--muted); padding: 24px 8px; }
1643
2894
  dialog {
1644
- border: 1px solid var(--line); border-radius: 16px; padding: 0; background: var(--bg-elev);
1645
- color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow: var(--shadow);
2895
+ border: 1px solid var(--line); border-radius: var(--radius); padding: 0; background: var(--bg-elev);
2896
+ color: var(--ink); width: min(640px, calc(100% - 24px)); max-width: calc(100vw - 24px); box-shadow: none;
1646
2897
  }
1647
2898
  dialog::backdrop { background: rgb(20 19 17 / 0.45); }
1648
2899
  dialog form, .dialog-body { padding: 16px; display: flex; flex-direction: column; gap: 10px; }
@@ -1669,7 +2920,7 @@ var renderAdminDocument = (service) => {
1669
2920
  padding-top: max(10px, env(safe-area-inset-top));
1670
2921
  }
1671
2922
  input, select, textarea { font-size: 16px; }
1672
- .controls { width: 100%; }
2923
+ .brand, .vendor, .controls { width: 100%; }
1673
2924
  label.field, label.field select, label.field input { flex: 1; min-width: 0; }
1674
2925
  .layout { grid-template-columns: minmax(0, 1fr); }
1675
2926
  nav.side {
@@ -1700,8 +2951,11 @@ var renderAdminDocument = (service) => {
1700
2951
  <div class="app" data-service="${name}" data-mockingbird-admin>
1701
2952
  <header class="top">
1702
2953
  <div class="brand">
1703
- <strong>${name}</strong>
1704
- <span>Mockingbird admin</span>
2954
+ <div class="brand-id">
2955
+ <strong>${name}</strong>
2956
+ <span>Admin</span>
2957
+ </div>
2958
+ <div class="vendor" id="vendor" hidden></div>
1705
2959
  </div>
1706
2960
  <div class="controls">
1707
2961
  <label class="field">Namespace
@@ -1831,460 +3085,7 @@ var renderAdminDocument = (service) => {
1831
3085
  </div>
1832
3086
  </form>
1833
3087
  </dialog>
1834
- <script>
1835
- (() => {
1836
- const STANDARD = new Set(${standard})
1837
- const state = {
1838
- namespace: "default",
1839
- key: sessionStorage.getItem("mockingbird-admin-key") || "",
1840
- view: "overview",
1841
- collection: "",
1842
- shape: null,
1843
- route: null,
1844
- editing: null
1845
- }
1846
- const $ = (id) => document.getElementById(id)
1847
- const banner = $("banner")
1848
- const keyInput = $("admin-key")
1849
- keyInput.value = state.key
1850
- const params = new URLSearchParams(location.search)
1851
- if (params.get("key")) {
1852
- state.key = params.get("key")
1853
- keyInput.value = state.key
1854
- sessionStorage.setItem("mockingbird-admin-key", state.key)
1855
- params.delete("key")
1856
- const next = params.toString()
1857
- history.replaceState(null, "", location.pathname + (next ? "?" + next : ""))
1858
- }
1859
- if (params.get("namespace")) state.namespace = params.get("namespace")
1860
-
1861
- const showError = (error) => {
1862
- banner.textContent = error instanceof Error ? error.message : String(error)
1863
- banner.classList.add("show")
1864
- }
1865
- const clearError = () => banner.classList.remove("show")
1866
-
1867
- async function originGet(path) {
1868
- const response = await fetch(path, { headers: { accept: "application/json" } })
1869
- if (!response.ok) throw new Error(response.status + " " + path)
1870
- return response.json()
1871
- }
1872
- async function api(method, path, body) {
1873
- const headers = { accept: "application/json" }
1874
- if (state.key) headers["${ADMIN_KEY_HEADER}"] = state.key
1875
- if (state.namespace) headers["x-mockingbird-namespace"] = state.namespace
1876
- if (body !== undefined) headers["content-type"] = "application/json"
1877
- const response = await fetch("${ADMIN_PREFIX}" + path, {
1878
- method,
1879
- headers,
1880
- body: body === undefined ? undefined : JSON.stringify(body)
1881
- })
1882
- const text = await response.text()
1883
- let data = null
1884
- if (text) {
1885
- try { data = JSON.parse(text) } catch { data = { raw: text } }
1886
- }
1887
- if (response.status === 401) {
1888
- keyInput.focus()
1889
- throw new Error((data && data.error && data.error.message) || "Admin key required")
1890
- }
1891
- if (!response.ok) {
1892
- throw new Error((data && data.error && data.error.message) || (response.status + " " + method + " " + path))
1893
- }
1894
- return data
1895
- }
1896
- const panelApi = {
1897
- get: (path) => api("GET", path),
1898
- send: (method, path, body) => api(method, path, body),
1899
- namespace: () => state.namespace
1900
- }
1901
-
1902
- function setTheme(theme) {
1903
- if (theme) document.documentElement.dataset.theme = theme
1904
- else delete document.documentElement.dataset.theme
1905
- }
1906
- const storedTheme = localStorage.getItem("mockingbird-admin-theme")
1907
- if (storedTheme) setTheme(storedTheme)
1908
- $("theme").addEventListener("click", () => {
1909
- const current = document.documentElement.dataset.theme
1910
- const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches
1911
- const next = dark ? "light" : "dark"
1912
- localStorage.setItem("mockingbird-admin-theme", next)
1913
- setTheme(next)
1914
- })
1915
-
1916
- function show(view) {
1917
- state.view = view
1918
- for (const button of document.querySelectorAll("#nav button")) {
1919
- button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false")
1920
- }
1921
- for (const section of document.querySelectorAll("main .view")) {
1922
- section.hidden = section.id !== "view-" + view
1923
- }
1924
- }
1925
- document.getElementById("nav").addEventListener("click", (event) => {
1926
- const button = event.target.closest("button[data-view]")
1927
- if (!button) return
1928
- show(button.dataset.view)
1929
- refresh().catch(showError)
1930
- })
1931
-
1932
- function cards(items) {
1933
- return '<div class="grid">' + items.map((item) =>
1934
- '<article class="card"><div class="k">' + item.k + '</div><div class="v">' + item.v + '</div></article>'
1935
- ).join("") + "</div>"
1936
- }
1937
- function table(columns, rows) {
1938
- if (rows.length === 0) return '<p class="empty">Nothing here yet.</p>'
1939
- const head = "<tr>" + columns.map((column) => "<th>" + column.label + "</th>").join("") + "</tr>"
1940
- const body = rows.map((row) => "<tr" + (row.attrs || "") + ">" + columns.map((column) =>
1941
- '<td data-label="' + column.label + '">' + column.cell(row) + "</td>"
1942
- ).join("") + "</tr>").join("")
1943
- return "<table><thead>" + head + "</thead><tbody>" + body + "</tbody></table>"
1944
- }
1945
- const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({
1946
- "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"
1947
- }[char]))
1948
- const preview = (value) => {
1949
- const text = JSON.stringify(value)
1950
- return esc(text.length > 180 ? text.slice(0, 180) + "\u2026" : text)
1951
- }
1952
- const when = (epoch) => {
1953
- const date = new Date(epoch)
1954
- return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString()
1955
- }
1956
- const clockCard = (epoch) => {
1957
- const date = new Date(epoch)
1958
- if (Number.isNaN(date.getTime())) return esc(String(epoch))
1959
- const iso = date.toISOString()
1960
- return esc(iso.slice(0, 10)) + '<span class="clock-time">' + esc(iso.slice(11, 19)) + " UTC</span>"
1961
- }
1962
-
1963
- async function loadNamespaces() {
1964
- const data = await api("GET", "/namespaces")
1965
- const select = $("namespace")
1966
- const names = data.namespaces && data.namespaces.length ? data.namespaces : [data.default || "default"]
1967
- if (!names.includes(state.namespace)) names.unshift(state.namespace)
1968
- select.innerHTML = names.map((name) =>
1969
- '<option value="' + esc(name) + '">' + esc(name) + "</option>"
1970
- ).join("")
1971
- select.value = state.namespace
1972
- }
1973
-
1974
- async function renderOverview() {
1975
- const [health, shape, clock, journal] = await Promise.all([
1976
- originGet("/health"),
1977
- api("GET", "/state"),
1978
- api("GET", "/clock"),
1979
- api("GET", "/requests?limit=8")
1980
- ])
1981
- state.shape = shape
1982
- $("overview-cards").innerHTML = cards([
1983
- { k: "Service", v: esc(health.service || "${name}") },
1984
- { k: "Status", v: esc(health.status || "ok") },
1985
- { k: "Namespace", v: esc(state.namespace) },
1986
- { k: "Collections", v: String(shape.collections.length) },
1987
- { k: "Records", v: String(shape.collections.reduce((sum, item) => sum + item.count, 0)) },
1988
- { k: "Clock", v: clockCard(clock.now) }
1989
- ])
1990
- const requests = (journal && journal.requests) || []
1991
- $("overview-requests").innerHTML = table(
1992
- [
1993
- { label: "Method", cell: (row) => esc(row.method) },
1994
- { label: "Path", cell: (row) => esc(row.path) },
1995
- { label: "Status", cell: (row) => '<span class="status-' + String(row.status).charAt(0) + '">' + esc(row.status) + "</span>" },
1996
- { label: "Operation", cell: (row) => esc(row.operationId || "") }
1997
- ],
1998
- requests
1999
- )
2000
- }
2001
-
2002
- function schemaChips(collection) {
2003
- if (!collection || collection.fields.length === 0) {
2004
- return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>'
2005
- }
2006
- return '<div class="chips">' + collection.fields.map((field) =>
2007
- '<span class="chip">' + esc(field.name) + ' <i>' + esc(field.kind) + (field.optional ? "?" : "") + "</i></span>"
2008
- ).join("") + "</div>"
2009
- }
2010
-
2011
- async function renderState() {
2012
- const shape = await api("GET", "/state")
2013
- state.shape = shape
2014
- const host = $("collections")
2015
- if (shape.collections.length === 0) {
2016
- host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>'
2017
- $("schema").innerHTML = ""
2018
- $("records").innerHTML = ""
2019
- return
2020
- }
2021
- if (!shape.collections.some((item) => item.name === state.collection)) {
2022
- state.collection = shape.collections[0].name
2023
- }
2024
- host.innerHTML = shape.collections.map((item) =>
2025
- '<button type="button" class="collection" data-collection="' + esc(item.name) + '" aria-current="' +
2026
- (item.name === state.collection ? "true" : "false") + '"><strong>' + esc(item.label) +
2027
- '</strong><small>' + esc(item.count) + " \xB7 " + esc(item.source) + "</small></button>"
2028
- ).join("")
2029
- const selected = shape.collections.find((item) => item.name === state.collection)
2030
- $("schema").innerHTML = "<h3>" + esc(selected.label) + "</h3>" +
2031
- (selected.description ? "<p class=" + '"lede">' + esc(selected.description) + "</p>" : "") +
2032
- schemaChips(selected)
2033
- const page = await api("GET", "/state/" + encodeURIComponent(state.collection) + "?limit=50")
2034
- const rows = page.records.map((record) => ({
2035
- ...record,
2036
- attrs: ' class="clickable" data-id="' + esc(record.id) + '"'
2037
- }))
2038
- $("records").innerHTML = table(
2039
- [
2040
- { label: "Id", cell: (row) => "<code>" + esc(row.id) + "</code>" },
2041
- { label: "Seq", cell: (row) => esc(row.seq) },
2042
- { label: "Value", cell: (row) => preview(row.value) }
2043
- ],
2044
- rows
2045
- )
2046
- }
2047
-
2048
- async function renderClock() {
2049
- const clock = await api("GET", "/clock")
2050
- $("clock-readout").innerHTML = "<div class='k'>Now</div><div class='v'>" + esc(when(clock.now)) +
2051
- "</div><p>" + esc(clock.now) + " ms \xB7 offset " + esc(clock.offsetMs) + " ms " +
2052
- (clock.frozen ? "<span class='badge warn'>frozen</span>" : "<span class='badge ok'>live</span>") + "</p>"
2053
- $("freeze").textContent = clock.frozen ? "Unfreeze" : "Freeze"
2054
- $("freeze").dataset.frozen = clock.frozen ? "1" : "0"
2055
- }
2056
-
2057
- async function renderFaults() {
2058
- const [faults, presets] = await Promise.all([
2059
- api("GET", "/faults"),
2060
- api("GET", "/faults/presets").catch(() => ({ presets: [] }))
2061
- ])
2062
- const select = $("fault-preset")
2063
- const current = select.value
2064
- const list = presets.presets || []
2065
- select.innerHTML = '<option value="">Custom rule</option>' + list.map((preset) =>
2066
- '<option value="' + esc(preset.name) + '">' + esc(preset.name) + "</option>"
2067
- ).join("")
2068
- select.value = current
2069
- const rules = faults.faults || []
2070
- $("fault-list").innerHTML = table(
2071
- [
2072
- { label: "Id", cell: (row) => esc(row.id) },
2073
- { label: "Match", cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter(Boolean).join(" ") || "all") },
2074
- { label: "Effect", cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || "")) }
2075
- ],
2076
- rules
2077
- )
2078
- }
2079
-
2080
- async function renderJournal() {
2081
- const journal = await api("GET", "/requests?limit=100")
2082
- $("journal").innerHTML = table(
2083
- [
2084
- { label: "When", cell: (row) => esc(row.at || "") },
2085
- { label: "Method", cell: (row) => esc(row.method) },
2086
- { label: "Path", cell: (row) => esc(row.path) },
2087
- { label: "Status", cell: (row) => esc(row.status) },
2088
- { label: "ms", cell: (row) => esc(row.durationMs) }
2089
- ],
2090
- journal.requests || []
2091
- )
2092
- }
2093
-
2094
- async function renderRoutes() {
2095
- const data = await api("GET", "/")
2096
- const routes = data.routes || []
2097
- $("route-list").innerHTML = routes.map((route) => {
2098
- const extra = STANDARD.has(route) ? "" : " <span class='badge'>extension</span>"
2099
- return '<div class="route"><span class="method">' + esc(route.split(" ")[0]) +
2100
- '</span><span>' + esc(route.split(" ").slice(1).join(" ")) + extra +
2101
- '</span><button class="btn" type="button" data-route="' + esc(route) + '">Use</button></div>'
2102
- }).join("")
2103
- }
2104
-
2105
- const renderers = {
2106
- overview: renderOverview,
2107
- state: renderState,
2108
- clock: renderClock,
2109
- faults: renderFaults,
2110
- journal: renderJournal,
2111
- routes: renderRoutes
2112
- }
2113
-
2114
- async function refresh() {
2115
- clearError()
2116
- if (state.view in renderers) await renderers[state.view]()
2117
- }
2118
-
2119
- $("namespace").addEventListener("change", () => {
2120
- state.namespace = $("namespace").value
2121
- refresh().catch(showError)
2122
- })
2123
- keyInput.addEventListener("change", () => {
2124
- state.key = keyInput.value.trim()
2125
- sessionStorage.setItem("mockingbird-admin-key", state.key)
2126
- refresh().catch(showError)
2127
- })
2128
- $("refresh").addEventListener("click", () => refresh().catch(showError))
2129
-
2130
- $("collections").addEventListener("click", (event) => {
2131
- const button = event.target.closest("[data-collection]")
2132
- if (!button) return
2133
- state.collection = button.dataset.collection
2134
- renderState().catch(showError)
2135
- })
2136
- $("records").addEventListener("click", (event) => {
2137
- const row = event.target.closest("[data-id]")
2138
- if (!row) return
2139
- openEditor(row.dataset.id).catch(showError)
2140
- })
2141
-
2142
- const editor = $("editor")
2143
- async function openEditor(id) {
2144
- state.editing = id || null
2145
- $("editor-title").textContent = id ? "Edit record" : "New record"
2146
- $("editor-id").value = id || ""
2147
- $("editor-id").readOnly = Boolean(id)
2148
- $("editor-delete").hidden = !id
2149
- if (id) {
2150
- const record = await api("GET", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
2151
- $("editor-value").value = JSON.stringify(record.value, null, 2)
2152
- } else {
2153
- $("editor-value").value = "{\\n \\n}"
2154
- }
2155
- editor.showModal()
2156
- }
2157
- $("new-record").addEventListener("click", () => openEditor(null).catch(showError))
2158
- $("editor-form").addEventListener("submit", (event) => {
2159
- const submitter = event.submitter
2160
- if (!submitter || submitter.value !== "save") return
2161
- event.preventDefault()
2162
- let value
2163
- try { value = JSON.parse($("editor-value").value) }
2164
- catch { showError(new Error("Value is not JSON")); return }
2165
- const id = $("editor-id").value.trim()
2166
- const path = "/state/" + encodeURIComponent(state.collection)
2167
- const request = state.editing
2168
- ? api("PUT", path + "/" + encodeURIComponent(id), { value })
2169
- : api("POST", path, { id: id || undefined, value })
2170
- request.then(() => { editor.close(); return renderState() }).catch(showError)
2171
- })
2172
- $("editor-delete").addEventListener("click", () => {
2173
- const id = state.editing
2174
- if (!id) return
2175
- api("DELETE", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
2176
- .then(() => { editor.close(); return renderState() })
2177
- .catch(showError)
2178
- })
2179
-
2180
- document.querySelectorAll("[data-advance]").forEach((button) => {
2181
- button.addEventListener("click", () => {
2182
- api("POST", "/clock", { advance: Number(button.dataset.advance) }).then(renderClock).catch(showError)
2183
- })
2184
- })
2185
- $("freeze").addEventListener("click", () => {
2186
- const frozen = $("freeze").dataset.frozen === "1"
2187
- api("POST", "/clock", { freeze: !frozen }).then(renderClock).catch(showError)
2188
- })
2189
- $("clock-reset").addEventListener("click", () => {
2190
- api("POST", "/clock", { reset: true }).then(renderClock).catch(showError)
2191
- })
2192
- $("clock-set").addEventListener("submit", (event) => {
2193
- event.preventDefault()
2194
- const raw = $("clock-instant").value.trim()
2195
- const set = /^-?\\d+$/.test(raw) ? Number(raw) : raw
2196
- api("POST", "/clock", { set }).then(renderClock).catch(showError)
2197
- })
2198
-
2199
- $("fault-form").addEventListener("submit", (event) => {
2200
- event.preventDefault()
2201
- const preset = $("fault-preset").value
2202
- const body = preset
2203
- ? { preset }
2204
- : {
2205
- status: Number($("fault-status").value),
2206
- ...($("fault-path").value ? { pathPrefix: $("fault-path").value } : {}),
2207
- ...($("fault-method").value ? { method: $("fault-method").value } : {})
2208
- }
2209
- if (!preset && !Number.isFinite(body.status)) {
2210
- showError(new Error("Status must be a number, or choose a preset"))
2211
- return
2212
- }
2213
- api("POST", "/faults", body).then(renderFaults).catch(showError)
2214
- })
2215
- $("fault-clear").addEventListener("click", () => api("DELETE", "/faults").then(renderFaults).catch(showError))
2216
- $("journal-clear").addEventListener("click", () => api("DELETE", "/requests").then(renderJournal).catch(showError))
2217
-
2218
- $("route-list").addEventListener("click", (event) => {
2219
- const button = event.target.closest("[data-route]")
2220
- if (!button) return
2221
- state.route = button.dataset.route
2222
- $("route-title").textContent = state.route
2223
- const method = state.route.split(" ")[0]
2224
- $("route-body").disabled = method === "GET" || method === "DELETE"
2225
- })
2226
- $("route-form").addEventListener("submit", async (event) => {
2227
- event.preventDefault()
2228
- if (!state.route) return showError(new Error("Choose a route first"))
2229
- const [method, path] = [state.route.split(" ")[0], state.route.split(" ").slice(1).join(" ")]
2230
- const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_, key) => prompt("Value for " + key) || "")
2231
- let body
2232
- if (method !== "GET" && method !== "DELETE" && $("route-body").value.trim()) {
2233
- try { body = JSON.parse($("route-body").value) }
2234
- catch { return showError(new Error("Body is not JSON")) }
2235
- }
2236
- try {
2237
- const data = await api(method, concrete, body)
2238
- $("route-result").textContent = JSON.stringify(data, null, 2)
2239
- } catch (error) {
2240
- showError(error)
2241
- }
2242
- })
2243
-
2244
- function mountPanels(panels) {
2245
- const nav = $("nav")
2246
- const views = $("views")
2247
- for (const panel of panels) {
2248
- const button = document.createElement("button")
2249
- button.type = "button"
2250
- button.dataset.view = panel.id
2251
- button.textContent = panel.title
2252
- nav.append(button)
2253
- const section = document.createElement("section")
2254
- section.className = "view"
2255
- section.id = "view-" + panel.id
2256
- section.hidden = true
2257
- const title = document.createElement("h2")
2258
- title.textContent = panel.title
2259
- const lede = document.createElement("p")
2260
- lede.className = "lede"
2261
- lede.textContent = panel.description || ""
2262
- const body = document.createElement("div")
2263
- body.className = "panel-body"
2264
- body.id = "panel-" + panel.id
2265
- body.innerHTML = panel.html || ""
2266
- section.append(title, lede, body)
2267
- views.append(section)
2268
- if (panel.script) {
2269
- try {
2270
- const run = new Function("root", "api", panel.script)
2271
- run(body, panelApi)
2272
- } catch (error) {
2273
- const note = document.createElement("p")
2274
- note.className = "banner show"
2275
- note.textContent = error instanceof Error ? error.message : String(error)
2276
- body.append(note)
2277
- }
2278
- }
2279
- }
2280
- }
2281
-
2282
- loadNamespaces()
2283
- .then(() => api("GET", "/ui/manifest"))
2284
- .then((manifest) => { mountPanels((manifest && manifest.panels) || []); return refresh() })
2285
- .catch(showError)
2286
- })()
2287
- </script>
3088
+ ${script}
2288
3089
  </body>
2289
3090
  </html>`;
2290
3091
  };
@@ -2301,10 +3102,10 @@ var json2 = (body) => new Response(JSON.stringify(body), {
2301
3102
  });
2302
3103
  var adminUiRoutes = (service, ui) => {
2303
3104
  const shell = () => renderAdminDocument(service);
2304
- const document2 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
3105
+ const document3 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
2305
3106
  return {
2306
- "GET /ui": () => htmlResponse(document2()),
2307
- "GET /ui/": () => htmlResponse(document2()),
3107
+ "GET /ui": () => htmlResponse(document3()),
3108
+ "GET /ui/": () => htmlResponse(document3()),
2308
3109
  "GET /ui/manifest": () => json2({
2309
3110
  service,
2310
3111
  panels: (ui?.panels ?? []).map((panel) => ({
@@ -2330,11 +3131,11 @@ var EXTENSION_KEYS = {
2330
3131
  };
2331
3132
 
2332
3133
  // ../../openapi/metadata/dist/read.js
2333
- var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3134
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2334
3135
  var extensionOf = (holder, key) => holder[key];
2335
3136
  var operationMetadata = (operation) => {
2336
3137
  const raw = extensionOf(operation, EXTENSION_KEYS.operation);
2337
- const ext = isRecord2(raw) ? raw : {};
3138
+ const ext = isRecord3(raw) ? raw : {};
2338
3139
  const supported = ext.supported ?? true;
2339
3140
  const parity = ext.parity ?? {};
2340
3141
  return {
@@ -2444,9 +3245,9 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
2444
3245
  this.name = "OperationRegistryError";
2445
3246
  }
2446
3247
  };
2447
- var verifyOperations = (document2, handlers) => {
3248
+ var verifyOperations = (document3, handlers) => {
2448
3249
  const problems = [];
2449
- const operations = listOperations(document2);
3250
+ const operations = listOperations(document3);
2450
3251
  const seen = /* @__PURE__ */ new Set();
2451
3252
  for (const operation of operations) {
2452
3253
  if (seen.has(operation.operationId))
@@ -2562,7 +3363,7 @@ var SAMPLE = 25;
2562
3363
  var WALK_LIMIT = 1e4;
2563
3364
  var SKIP_CTORS = /* @__PURE__ */ new Set(["Hono", "HonoRequest", "Router"]);
2564
3365
  var fail2 = (status, message) => ({ ok: false, status, message });
2565
- var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3366
+ var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2566
3367
  var kindOf = (value) => {
2567
3368
  if (value === null)
2568
3369
  return "null";
@@ -2661,7 +3462,7 @@ var absorb = (accum, value) => {
2661
3462
  const kind = kindOf(value);
2662
3463
  accum.kinds.add(kind);
2663
3464
  accum.present++;
2664
- if (!isRecord3(value))
3465
+ if (!isRecord4(value))
2665
3466
  return;
2666
3467
  for (const [key, child] of Object.entries(value)) {
2667
3468
  const nested = accum.nested.get(key) ?? blankAccum(key);
@@ -2686,7 +3487,7 @@ var inferFields = (records) => {
2686
3487
  if (records.length === 0)
2687
3488
  return [];
2688
3489
  const sample = records.slice(0, SAMPLE);
2689
- const objects = sample.filter((record) => isRecord3(record.value));
3490
+ const objects = sample.filter((record) => isRecord4(record.value));
2690
3491
  if (objects.length === 0) {
2691
3492
  const kinds = new Set(sample.map((record) => kindOf(record.value)));
2692
3493
  const list = [...kinds];
@@ -2873,7 +3674,7 @@ var patchStateRecord = (scope, name, id, value) => {
2873
3674
  const parsed = jsonValue(value);
2874
3675
  if (!parsed.ok)
2875
3676
  return parsed;
2876
- const next = isRecord3(current.value.value) && isRecord3(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
3677
+ const next = isRecord4(current.value.value) && isRecord4(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
2877
3678
  return replaceStateRecord(scope, name, id, next);
2878
3679
  };
2879
3680
  var deleteStateRecord = (scope, name, id) => {
@@ -2941,7 +3742,7 @@ var stateAdminRoutes = (routes) => {
2941
3742
  const scope = open(namespace);
2942
3743
  if (!scope.ok)
2943
3744
  return fromResult(scope, 201);
2944
- if (!isRecord3(body) || !("value" in body)) {
3745
+ if (!isRecord4(body) || !("value" in body)) {
2945
3746
  return adminError2(400, 'expected {"id?": "<id>", "value": <json>}');
2946
3747
  }
2947
3748
  const id = body.id;
@@ -2959,7 +3760,7 @@ var stateAdminRoutes = (routes) => {
2959
3760
  const scope = open(namespace);
2960
3761
  if (!scope.ok)
2961
3762
  return fromResult(scope, 200);
2962
- if (!isRecord3(body) || !("value" in body))
3763
+ if (!isRecord4(body) || !("value" in body))
2963
3764
  return adminError2(400, 'expected {"value": <json>}');
2964
3765
  const replaced = replaceStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
2965
3766
  if (replaced.ok)
@@ -2970,7 +3771,7 @@ var stateAdminRoutes = (routes) => {
2970
3771
  const scope = open(namespace);
2971
3772
  if (!scope.ok)
2972
3773
  return fromResult(scope, 200);
2973
- if (!isRecord3(body) || !("value" in body))
3774
+ if (!isRecord4(body) || !("value" in body))
2974
3775
  return adminError2(400, 'expected {"value": <json>}');
2975
3776
  const patched = patchStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
2976
3777
  if (patched.ok)
@@ -2990,14 +3791,14 @@ var stateAdminRoutes = (routes) => {
2990
3791
  };
2991
3792
 
2992
3793
  // ../core/dist/version.js
2993
- var PACKAGE_VERSION = true ? "1.4.0" : UNRELEASED_VERSION;
3794
+ var PACKAGE_VERSION = true ? "1.5.1" : UNRELEASED_VERSION;
2994
3795
 
2995
3796
  // ../core/dist/webhooks.js
2996
3797
  var json4 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
2997
3798
  var adminError3 = (status, message) => json4(status, { error: { type: "mockingbird_admin", message } });
2998
- var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3799
+ var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2999
3800
  var parseEndpoint = (value) => {
3000
- if (!isRecord4(value) || typeof value.url !== "string")
3801
+ if (!isRecord5(value) || typeof value.url !== "string")
3001
3802
  return "each endpoint needs a url";
3002
3803
  try {
3003
3804
  new URL(value.url);
@@ -3014,12 +3815,12 @@ var parseEndpoint = (value) => {
3014
3815
  const events = value.events ?? value.enabledEvents;
3015
3816
  if (Array.isArray(events))
3016
3817
  endpoint.events = events.map(String);
3017
- if (isRecord4(value.tags)) {
3818
+ if (isRecord5(value.tags)) {
3018
3819
  endpoint.tags = Object.fromEntries(Object.entries(value.tags).map(([k, v]) => [k, String(v)]));
3019
3820
  }
3020
3821
  if (typeof value.account === "string")
3021
3822
  endpoint.tags = { ...endpoint.tags, account: value.account };
3022
- if (isRecord4(value.headers)) {
3823
+ if (isRecord5(value.headers)) {
3023
3824
  endpoint.headers = Object.fromEntries(Object.entries(value.headers).map(([k, v]) => [k, String(v)]));
3024
3825
  }
3025
3826
  return endpoint;
@@ -3046,7 +3847,7 @@ var webhookAdminRoutes = (hub) => ({
3046
3847
  return json4(200, { status: "ok" });
3047
3848
  },
3048
3849
  "POST /webhooks/faults": ({ body, namespace }) => {
3049
- if (!isRecord4(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
3850
+ if (!isRecord5(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
3050
3851
  return adminError3(400, "mode must be duplicate, reorder or drop");
3051
3852
  }
3052
3853
  const fault = { mode: body.mode };
@@ -3062,7 +3863,7 @@ var webhookAdminRoutes = (hub) => ({
3062
3863
  }))
3063
3864
  }),
3064
3865
  "PUT /webhook-endpoints": ({ body, namespace }) => {
3065
- const list = Array.isArray(body) ? body : isRecord4(body) ? body.endpoints : void 0;
3866
+ const list = Array.isArray(body) ? body : isRecord5(body) ? body.endpoints : void 0;
3066
3867
  if (!Array.isArray(list))
3067
3868
  return adminError3(400, "expected [{url, secret?, events?}]");
3068
3869
  const parsed = [];
@@ -3130,8 +3931,8 @@ var DroppedConnectionError = class extends TypeError {
3130
3931
  this.name = "TypeError";
3131
3932
  }
3132
3933
  };
3133
- var operationMatcher = (document2) => {
3134
- const matchers = listOperations(document2).map((operation) => ({
3934
+ var operationMatcher = (document3) => {
3935
+ const matchers = listOperations(document3).map((operation) => ({
3135
3936
  operationId: operation.operationId,
3136
3937
  method: operation.method.toUpperCase(),
3137
3938
  pattern: new RegExp(`^${operation.path.split("/").map((segment) => segment.startsWith("{") ? "[^/]+" : segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("/")}/?$`),
@@ -3226,10 +4027,10 @@ var createRuntime = (options) => {
3226
4027
  const ensureBranch = (namespace, branch2, at) => {
3227
4028
  if (!BRANCH_PATTERN2.test(branch2))
3228
4029
  throw new RangeError(`branch must match ${BRANCH_PATTERN2}`);
3229
- const history = timeline(namespace);
4030
+ const history2 = timeline(namespace);
3230
4031
  if (branch2 === "main") {
3231
4032
  if (at !== void 0) {
3232
- const point = history.checkout("main", at);
4033
+ const point = history2.checkout("main", at);
3233
4034
  restoreNamespace(sqlite, storageNamespace(namespace), point.value.snapshot);
3234
4035
  captured.set(namespace, point.value.snapshot);
3235
4036
  rng.setState(point.value.rngState);
@@ -3242,10 +4043,10 @@ var createRuntime = (options) => {
3242
4043
  return namespace;
3243
4044
  }
3244
4045
  const storage = physicalBranch(namespace, branch2);
3245
- if (!history.hasBranch(branch2)) {
4046
+ if (!history2.hasBranch(branch2)) {
3246
4047
  if (at === void 0)
3247
- history.commit(capture(namespace));
3248
- const point = history.fork(branch2, at === void 0 ? {} : { from: at });
4048
+ history2.commit(capture(namespace));
4049
+ const point = history2.fork(branch2, at === void 0 ? {} : { from: at });
3249
4050
  const branchRng = createRng(options.seed ?? 0);
3250
4051
  if (point)
3251
4052
  branchRng.setState(point.value.rngState);
@@ -3254,8 +4055,8 @@ var createRuntime = (options) => {
3254
4055
  restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
3255
4056
  if (point)
3256
4057
  captured.set(storage, point.value.snapshot);
3257
- } else if (at !== void 0 && history.head(branch2)?.id !== at) {
3258
- const point = history.checkout(branch2, at);
4058
+ } else if (at !== void 0 && history2.head(branch2)?.id !== at) {
4059
+ const point = history2.checkout(branch2, at);
3259
4060
  if (!instances.has(storage)) {
3260
4061
  const branchRng = createRng(options.seed ?? 0);
3261
4062
  branchRng.setState(point.value.rngState);
@@ -3266,7 +4067,7 @@ var createRuntime = (options) => {
3266
4067
  captured.set(storage, point.value.snapshot);
3267
4068
  } else {
3268
4069
  if (!instances.has(storage)) {
3269
- const point = history.head(branch2);
4070
+ const point = history2.head(branch2);
3270
4071
  const branchRng = createRng(options.seed ?? 0);
3271
4072
  if (point)
3272
4073
  branchRng.setState(point.value.rngState);
@@ -3290,8 +4091,8 @@ var createRuntime = (options) => {
3290
4091
  const checkout = (checkpointId, checkoutOptions = {}) => {
3291
4092
  const namespace = checkoutOptions.namespace ?? DEFAULT_NAMESPACE;
3292
4093
  const branchName = checkoutOptions.branch ?? "main";
3293
- const history = timeline(namespace);
3294
- const point = history.checkout(branchName, checkpointId);
4094
+ const history2 = timeline(namespace);
4095
+ const point = history2.checkout(branchName, checkpointId);
3295
4096
  const storage = ensureBranch(namespace, branchName);
3296
4097
  restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
3297
4098
  captured.set(storage, point.value.snapshot);
@@ -3341,9 +4142,9 @@ var createRuntime = (options) => {
3341
4142
  instance(name);
3342
4143
  restoreNamespace(sqlite, storageNamespace(name), from);
3343
4144
  captured.set(name, from);
3344
- const history = timelines.get(name);
3345
- if (history)
3346
- history.commit(capture(name), { branch: "main" });
4145
+ const history2 = timelines.get(name);
4146
+ if (history2)
4147
+ history2.commit(capture(name), { branch: "main" });
3347
4148
  else
3348
4149
  timeline(name);
3349
4150
  };
@@ -3572,10 +4373,10 @@ var createRuntime = (options) => {
3572
4373
  },
3573
4374
  release: (name, checkpointId) => timeline(name).release(checkpointId),
3574
4375
  inspect: (name) => {
3575
- const history = timeline(name);
4376
+ const history2 = timeline(name);
3576
4377
  return {
3577
- branches: history.branches(),
3578
- checkpoints: history.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
4378
+ branches: history2.branches(),
4379
+ checkpoints: history2.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
3579
4380
  id,
3580
4381
  branch: branchName,
3581
4382
  parent,
@@ -3590,11 +4391,11 @@ var createRuntime = (options) => {
3590
4391
  } : {},
3591
4392
  routes: {
3592
4393
  ...credentialRoutes(credentials),
3593
- ...options.presets ? presetRoutes(options.presets, runtime) : {},
3594
4394
  ...options.webhooks ? webhookAdminRoutes(options.webhooks) : {},
3595
4395
  ...options.admin?.(runtime) ?? {},
3596
- // Standard state and UI routes win a colliding key. A service adds its own keys
3597
- // beside these; it does not replace `/state` or `/ui`.
4396
+ // Shell routes win a colliding key. Fault presets are one of them: the admin page
4397
+ // maps `presets`, and a service route must not replace that list with a record.
4398
+ ...options.presets ? presetRoutes(options.presets, runtime) : {},
3598
4399
  ...stateAdminRoutes({
3599
4400
  open: stateScope,
3600
4401
  afterWrite: (namespace) => {
@@ -3669,9 +4470,12 @@ var credentialRoutes = (registry) => ({
3669
4470
  }
3670
4471
  });
3671
4472
  var presetRoutes = (presets, runtime) => ({
3672
- "GET /faults/presets": () => adminJson(200, {
3673
- presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
3674
- }),
4473
+ "GET /faults/presets": () => {
4474
+ const body = {
4475
+ presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
4476
+ };
4477
+ return adminJson(200, body);
4478
+ },
3675
4479
  "POST /faults/presets/:name": ({ params, body, namespace }) => {
3676
4480
  const name = params.name;
3677
4481
  if (!presets[name])
@@ -3682,7 +4486,7 @@ var presetRoutes = (presets, runtime) => ({
3682
4486
  });
3683
4487
 
3684
4488
  // src/generated/openapi.ts
3685
- var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"EasyPost Trackers API (Mockingbird subset)","description":"Stateful mock subset of the EasyPost v2 API: create (or re-use) a tracker for a tracking\\ncode, retrieve it, and list trackers. Trimmed from EasyPost's published reference to what\\nour genomics tracking lookup calls, plus the two reads that make parity walks meaningful.\\n","version":"2","x-mockingbird-upstream":{"note":"Shapes from https://docs.easypost.com/docs/trackers; request/response fields our consumer reads come from packages/lib/src/shipment-tracking-status/easypost-client.ts."}},"servers":[{"url":"https://api.easypost.com"}],"security":[{"basicAuth":[]}],"paths":{"/v2/trackers":{"post":{"operationId":"CreateTracker","description":"Creates a tracker, or answers the existing one when the same tracking code (and carrier) was already tracked. EasyPost's test codes (EZ1000000001 \u2026 EZ7000000007) answer their documented fixed statuses.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/CreateTrackerBody"}},"application/json":{"schema":{"$ref":"#/components/schemas/CreateTrackerBody"}}}},"responses":{"201":{"description":"The tracker (new or re-used)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tracker"}}}},"401":{"description":"Missing or unknown API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Missing tracking code or invalid carrier","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"ListTrackers","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"tracking_code","in":"query","required":false,"schema":{"type":"string","maxLength":40}},{"name":"carrier","in":"query","required":false,"schema":{"type":"string","enum":["USPS","UPS","FedEx","DHLExpress"]}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"Newest trackers first","content":{"application/json":{"schema":{"type":"object","required":["trackers","has_more"],"properties":{"trackers":{"type":"array","items":{"$ref":"#/components/schemas/Tracker"}},"has_more":{"type":"boolean"}}}}}},"401":{"description":"Missing or unknown API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Invalid query parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v2/trackers/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"tracker","missing":"trk_00000000000000000000000000000000"}}}],"get":{"operationId":"RetrieveTracker","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The tracker","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tracker"}}}},"401":{"description":"Missing or unknown API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown tracker","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"basicAuth":{"type":"http","scheme":"basic","description":"The API key as the username, an empty password (\`Basic base64(key:)\`)."}},"schemas":{"CreateTrackerBody":{"type":"object","required":["tracker"],"properties":{"tracker":{"type":"object","required":["tracking_code"],"properties":{"tracking_code":{"type":"string","minLength":1,"maxLength":40,"examples":["EZ1000000001","EZ4000000004","1Z999AA10123456784"]},"carrier":{"type":"string","enum":["USPS","UPS","FedEx","DHL","DHLExpress"]}}}}},"Tracker":{"type":"object","required":["id","object","mode","tracking_code","status","status_detail","carrier","tracking_details","public_url","created_at","updated_at"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"tracker","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"object":{"type":"string","enum":["Tracker"]},"mode":{"type":"string","enum":["test","production"]},"tracking_code":{"type":"string"},"status":{"type":"string","enum":["unknown","pre_transit","in_transit","out_for_delivery","delivered","available_for_pickup","return_to_sender","failure","cancelled","error"]},"status_detail":{"type":"string"},"carrier":{"type":"string"},"signed_by":{"type":["string","null"]},"weight":{"type":["number","null"]},"est_delivery_date":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"timestamp"}},"shipment_id":{"type":["string","null"]},"tracking_details":{"type":"array","items":{"$ref":"#/components/schemas/TrackingDetail"}},"carrier_detail":{"type":["object","null"]},"public_url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}},"fees":{"type":"array","items":{"type":"object"}},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"TrackingDetail":{"type":"object","required":["object","message","status","status_detail","datetime","source"],"properties":{"object":{"type":"string","enum":["TrackingDetail"]},"message":{"type":"string"},"status":{"type":"string"},"status_detail":{"type":"string"},"datetime":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"source":{"type":"string"},"tracking_location":{"type":"object"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"},"errors":{"type":"array","items":{"type":"object"}}}}}}}}}`);
4489
+ var document2 = JSON.parse(`{"openapi":"3.1.0","info":{"title":"EasyPost Trackers API (Mockingbird subset)","description":"Stateful mock subset of the EasyPost v2 API: create (or re-use) a tracker for a tracking\\ncode, retrieve it, and list trackers. Trimmed from EasyPost's published reference to what\\nour genomics tracking lookup calls, plus the two reads that make parity walks meaningful.\\n","version":"2","x-mockingbird-upstream":{"note":"Shapes from https://docs.easypost.com/docs/trackers; request/response fields our consumer reads come from packages/lib/src/shipment-tracking-status/easypost-client.ts."}},"servers":[{"url":"https://api.easypost.com"}],"security":[{"basicAuth":[]}],"paths":{"/v2/trackers":{"post":{"operationId":"CreateTracker","description":"Creates a tracker, or answers the existing one when the same tracking code (and carrier) was already tracked. EasyPost's test codes (EZ1000000001 \u2026 EZ7000000007) answer their documented fixed statuses.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/CreateTrackerBody"}},"application/json":{"schema":{"$ref":"#/components/schemas/CreateTrackerBody"}}}},"responses":{"201":{"description":"The tracker (new or re-used)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tracker"}}}},"401":{"description":"Missing or unknown API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Missing tracking code or invalid carrier","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"ListTrackers","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"tracking_code","in":"query","required":false,"schema":{"type":"string","maxLength":40}},{"name":"carrier","in":"query","required":false,"schema":{"type":"string","enum":["USPS","UPS","FedEx","DHLExpress"]}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"Newest trackers first","content":{"application/json":{"schema":{"type":"object","required":["trackers","has_more"],"properties":{"trackers":{"type":"array","items":{"$ref":"#/components/schemas/Tracker"}},"has_more":{"type":"boolean"}}}}}},"401":{"description":"Missing or unknown API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Invalid query parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v2/trackers/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"tracker","missing":"trk_00000000000000000000000000000000"}}}],"get":{"operationId":"RetrieveTracker","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The tracker","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tracker"}}}},"401":{"description":"Missing or unknown API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown tracker","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"basicAuth":{"type":"http","scheme":"basic","description":"The API key as the username, an empty password (\`Basic base64(key:)\`)."}},"schemas":{"CreateTrackerBody":{"type":"object","required":["tracker"],"properties":{"tracker":{"type":"object","required":["tracking_code"],"properties":{"tracking_code":{"type":"string","minLength":1,"maxLength":40,"examples":["EZ1000000001","EZ4000000004","1Z999AA10123456784"]},"carrier":{"type":"string","enum":["USPS","UPS","FedEx","DHL","DHLExpress"]}}}}},"Tracker":{"type":"object","required":["id","object","mode","tracking_code","status","status_detail","carrier","tracking_details","public_url","created_at","updated_at"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"tracker","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"object":{"type":"string","enum":["Tracker"]},"mode":{"type":"string","enum":["test","production"]},"tracking_code":{"type":"string"},"status":{"type":"string","enum":["unknown","pre_transit","in_transit","out_for_delivery","delivered","available_for_pickup","return_to_sender","failure","cancelled","error"]},"status_detail":{"type":"string"},"carrier":{"type":"string"},"signed_by":{"type":["string","null"]},"weight":{"type":["number","null"]},"est_delivery_date":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"timestamp"}},"shipment_id":{"type":["string","null"]},"tracking_details":{"type":"array","items":{"$ref":"#/components/schemas/TrackingDetail"}},"carrier_detail":{"type":["object","null"]},"public_url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}},"fees":{"type":"array","items":{"type":"object"}},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"TrackingDetail":{"type":"object","required":["object","message","status","status_detail","datetime","source"],"properties":{"object":{"type":"string","enum":["TrackingDetail"]},"message":{"type":"string"},"status":{"type":"string"},"status_detail":{"type":"string"},"datetime":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"source":{"type":"string"},"tracking_location":{"type":"object"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"},"errors":{"type":"array","items":{"type":"object"}}}}}}}}}`);
3686
4490
  var operationIds = ["ListTrackers", "CreateTracker", "RetrieveTracker"];
3687
4491
  var supportedOperationIds = ["ListTrackers", "CreateTracker", "RetrieveTracker"];
3688
4492
 
@@ -3805,11 +4609,11 @@ var EASYPOST_PRESETS = {
3805
4609
  };
3806
4610
  var json5 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
3807
4611
  var adminError4 = (status, message) => json5(status, { error: { type: "mockingbird_admin", message } });
3808
- var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4612
+ var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3809
4613
  var adminRoutes = (runtime) => ({
3810
4614
  "GET /trackers": ({ namespace }) => json5(200, { trackers: runtime.instance(namespace).trackers() }),
3811
4615
  "POST /trackers/:code/transition": ({ params, body, namespace }) => {
3812
- if (!isRecord5(body) || !isTrackerStatus(body.status)) {
4616
+ if (!isRecord6(body) || !isTrackerStatus(body.status)) {
3813
4617
  return adminError4(
3814
4618
  400,
3815
4619
  'expected {"status": "<unknown|pre_transit|in_transit|out_for_delivery|delivered|available_for_pickup|return_to_sender|failure|cancelled|error>", "status_detail"?, "message"?, "carrier"?}'
@@ -3825,7 +4629,7 @@ var adminRoutes = (runtime) => ({
3825
4629
  },
3826
4630
  "GET /settings": ({ namespace }) => json5(200, runtime.instance(namespace).state.current()),
3827
4631
  "PUT /settings": ({ body, namespace }) => {
3828
- if (!isRecord5(body)) return adminError4(400, "expected a JSON object");
4632
+ if (!isRecord6(body)) return adminError4(400, "expected a JSON object");
3829
4633
  const patch = {};
3830
4634
  if (body.apiKeys !== void 0) {
3831
4635
  if (!Array.isArray(body.apiKeys)) return adminError4(400, "apiKeys: string[]");
@@ -3836,7 +4640,7 @@ var adminRoutes = (runtime) => ({
3836
4640
  });
3837
4641
  var createRuntime2 = (options = {}) => createRuntime({
3838
4642
  name: EASYPOST_NAMESPACE,
3839
- document,
4643
+ document: document2,
3840
4644
  ...options.sqlite ? { sqlite: options.sqlite } : {},
3841
4645
  ...options.clock ? { clock: options.clock } : {},
3842
4646
  ...options.seed !== void 0 ? { seed: options.seed } : {},
@@ -3920,7 +4724,7 @@ var EasyPostAPI = class {
3920
4724
  }
3921
4725
  });
3922
4726
  this.service = createService({
3923
- document,
4727
+ document: document2,
3924
4728
  handlers,
3925
4729
  sqlite,
3926
4730
  namespace,
@@ -4078,7 +4882,7 @@ var EasyPostAPI = class {
4078
4882
  };
4079
4883
 
4080
4884
  export {
4081
- document,
4885
+ document2 as document,
4082
4886
  operationIds,
4083
4887
  supportedOperationIds,
4084
4888
  TRACKER_STATUSES,
@@ -4092,4 +4896,4 @@ export {
4092
4896
  isTrackerStatus,
4093
4897
  EasyPostAPI
4094
4898
  };
4095
- //# sourceMappingURL=chunk-JKZDMND6.js.map
4899
+ //# sourceMappingURL=chunk-EVFM6BRF.js.map