@crvouga/mockingbird-service-rxvortex 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -540,10 +540,10 @@ var OpenAPIReferenceError = class extends Error {
540
540
  };
541
541
  var unescapePointer = (segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~");
542
542
  var isReference = (value) => typeof value === "object" && value !== null && typeof value.$ref === "string";
543
- var resolveRef = (document2, ref) => {
543
+ var resolveRef = (document3, ref) => {
544
544
  if (!ref.startsWith("#/"))
545
545
  throw new OpenAPIReferenceError(ref);
546
- let cursor = document2;
546
+ let cursor = document3;
547
547
  for (const raw of ref.slice(2).split("/")) {
548
548
  const segment = unescapePointer(raw);
549
549
  if (typeof cursor !== "object" || cursor === null || !(segment in cursor)) {
@@ -555,14 +555,14 @@ var resolveRef = (document2, ref) => {
555
555
  throw new OpenAPIReferenceError(ref);
556
556
  return cursor;
557
557
  };
558
- var deref = (document2, value) => {
558
+ var deref = (document3, value) => {
559
559
  let current = value;
560
560
  const seen = /* @__PURE__ */ new Set();
561
561
  while (isReference(current)) {
562
562
  if (seen.has(current.$ref))
563
563
  throw new OpenAPIReferenceError(`${current.$ref} (cycle)`);
564
564
  seen.add(current.$ref);
565
- current = resolveRef(document2, current.$ref);
565
+ current = resolveRef(document3, current.$ref);
566
566
  }
567
567
  return current;
568
568
  };
@@ -580,36 +580,36 @@ var HTTP_METHODS = [
580
580
  ];
581
581
 
582
582
  // ../../openapi/core/dist/document.js
583
- var mergeParameters = (document2, item, own) => {
583
+ var mergeParameters = (document3, item, own) => {
584
584
  const merged = /* @__PURE__ */ new Map();
585
585
  for (const raw of item.parameters ?? []) {
586
- const parameter = deref(document2, raw);
586
+ const parameter = deref(document3, raw);
587
587
  merged.set(`${parameter.in}:${parameter.name}`, parameter);
588
588
  }
589
589
  for (const raw of own ?? []) {
590
- const parameter = deref(document2, raw);
590
+ const parameter = deref(document3, raw);
591
591
  merged.set(`${parameter.in}:${parameter.name}`, parameter);
592
592
  }
593
593
  return [...merged.values()];
594
594
  };
595
- var listOperations = (document2) => {
595
+ var listOperations = (document3) => {
596
596
  const operations = [];
597
- for (const [path, item] of Object.entries(document2.paths)) {
597
+ for (const [path, item] of Object.entries(document3.paths)) {
598
598
  for (const method of HTTP_METHODS) {
599
599
  const operation = item[method];
600
600
  if (operation?.operationId === void 0)
601
601
  continue;
602
602
  const responses = {};
603
603
  for (const [status, response] of Object.entries(operation.responses)) {
604
- responses[status] = deref(document2, response);
604
+ responses[status] = deref(document3, response);
605
605
  }
606
606
  operations.push({
607
607
  operationId: operation.operationId,
608
608
  method,
609
609
  path,
610
610
  operation,
611
- parameters: mergeParameters(document2, item, operation.parameters),
612
- requestBody: operation.requestBody === void 0 ? void 0 : deref(document2, operation.requestBody),
611
+ parameters: mergeParameters(document3, item, operation.parameters),
612
+ requestBody: operation.requestBody === void 0 ? void 0 : deref(document3, operation.requestBody),
613
613
  responses
614
614
  });
615
615
  }
@@ -618,7 +618,7 @@ var listOperations = (document2) => {
618
618
  };
619
619
 
620
620
  // ../../openapi/core/dist/schema.js
621
- var resolveSchema = (document2, schema) => {
621
+ var resolveSchema = (document3, schema) => {
622
622
  let current = schema;
623
623
  const seen = /* @__PURE__ */ new Set();
624
624
  while (typeof current.$ref === "string") {
@@ -627,7 +627,7 @@ var resolveSchema = (document2, schema) => {
627
627
  break;
628
628
  seen.add(ref);
629
629
  const { $ref: _ignored, ...siblings } = current;
630
- const target = resolveRef(document2, ref);
630
+ const target = resolveRef(document3, ref);
631
631
  current = { ...target, ...siblings };
632
632
  }
633
633
  if (current.nullable === true) {
@@ -698,9 +698,9 @@ var FORMAT_PATTERNS = {
698
698
  ipv4: /^(25[0-5]|2[0-4]\d|1?\d?\d)(\.(25[0-5]|2[0-4]\d|1?\d?\d)){3}$/
699
699
  };
700
700
  var graphemeLength = (value) => [...value].length;
701
- var validateValue = (document2, schema, value, path = []) => {
701
+ var validateValue = (document3, schema, value, path = []) => {
702
702
  const errors = [];
703
- const s = resolveSchema(document2, schema);
703
+ const s = resolveSchema(document3, schema);
704
704
  const fail2 = (message) => errors.push({ path, message });
705
705
  const actual = jsonTypeOf(value);
706
706
  if (actual === "undefined") {
@@ -762,7 +762,7 @@ var validateValue = (document2, schema, value, path = []) => {
762
762
  value.forEach((item, i) => {
763
763
  const itemSchema = s.prefixItems?.[i] ?? s.items;
764
764
  if (itemSchema)
765
- errors.push(...validateValue(document2, itemSchema, item, [...path, i]));
765
+ errors.push(...validateValue(document3, itemSchema, item, [...path, i]));
766
766
  });
767
767
  }
768
768
  if (actual === "object") {
@@ -778,16 +778,16 @@ var validateValue = (document2, schema, value, path = []) => {
778
778
  for (const key of keys) {
779
779
  const property = s.properties?.[key];
780
780
  if (property) {
781
- errors.push(...validateValue(document2, property, record2[key], [...path, key]));
781
+ errors.push(...validateValue(document3, property, record2[key], [...path, key]));
782
782
  continue;
783
783
  }
784
784
  if (s.additionalProperties === false)
785
785
  fail2(`unexpected property ${key}`);
786
786
  else if (typeof s.additionalProperties === "object") {
787
- errors.push(...validateValue(document2, s.additionalProperties, record2[key], [...path, key]));
787
+ errors.push(...validateValue(document3, s.additionalProperties, record2[key], [...path, key]));
788
788
  }
789
789
  if (s.propertyNames) {
790
- const nameErrors = validateValue(document2, s.propertyNames, key, [...path, key]);
790
+ const nameErrors = validateValue(document3, s.propertyNames, key, [...path, key]);
791
791
  if (nameErrors.length > 0)
792
792
  fail2(`property name ${key} is invalid: ${nameErrors[0]?.message}`);
793
793
  }
@@ -795,15 +795,15 @@ var validateValue = (document2, schema, value, path = []) => {
795
795
  }
796
796
  if (s.allOf)
797
797
  for (const branch of s.allOf)
798
- errors.push(...validateValue(document2, branch, value, path));
799
- if (s.anyOf && !s.anyOf.some((branch) => validateValue(document2, branch, value).length === 0))
798
+ errors.push(...validateValue(document3, branch, value, path));
799
+ if (s.anyOf && !s.anyOf.some((branch) => validateValue(document3, branch, value).length === 0))
800
800
  fail2("matches no anyOf branch");
801
801
  if (s.oneOf) {
802
- const matches2 = s.oneOf.filter((branch) => validateValue(document2, branch, value).length === 0).length;
802
+ const matches2 = s.oneOf.filter((branch) => validateValue(document3, branch, value).length === 0).length;
803
803
  if (matches2 !== 1)
804
804
  fail2(`matches ${matches2} oneOf branches, expected exactly 1`);
805
805
  }
806
- if (s.not && validateValue(document2, s.not, value).length === 0)
806
+ if (s.not && validateValue(document3, s.not, value).length === 0)
807
807
  fail2("matches forbidden `not` schema");
808
808
  return errors;
809
809
  };
@@ -1336,6 +1336,1232 @@ var clearNamespace = (sqlite, namespace) => {
1336
1336
  });
1337
1337
  };
1338
1338
 
1339
+ // ../../ui/dist/reset.js
1340
+ var CSS_RESET = `*,
1341
+ *::before,
1342
+ *::after {
1343
+ box-sizing: border-box;
1344
+ margin: 0;
1345
+ padding: 0;
1346
+ }
1347
+
1348
+ * {
1349
+ scrollbar-width: thin;
1350
+ scrollbar-color: color-mix(in srgb, CanvasText 35%, Canvas) Canvas;
1351
+ }
1352
+
1353
+ *::-webkit-scrollbar {
1354
+ width: 12px;
1355
+ height: 12px;
1356
+ }
1357
+
1358
+ *::-webkit-scrollbar-track,
1359
+ *::-webkit-scrollbar-corner {
1360
+ background: Canvas;
1361
+ }
1362
+
1363
+ *::-webkit-scrollbar-thumb {
1364
+ background: color-mix(in srgb, CanvasText 35%, Canvas);
1365
+ border: 3px solid Canvas;
1366
+ border-radius: 999px;
1367
+ }
1368
+
1369
+ html {
1370
+ hanging-punctuation: first allow-end last;
1371
+ -webkit-text-size-adjust: none;
1372
+ text-size-adjust: none;
1373
+ tab-size: 4;
1374
+ font-family:
1375
+ ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI",
1376
+ "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
1377
+ font-size: 16px;
1378
+ line-height: 1.5;
1379
+ -webkit-font-smoothing: antialiased;
1380
+ -moz-osx-font-smoothing: grayscale;
1381
+ text-rendering: optimizeLegibility;
1382
+ -webkit-tap-highlight-color: transparent;
1383
+ scroll-behavior: smooth;
1384
+ background: Canvas;
1385
+ color: CanvasText;
1386
+ }
1387
+
1388
+ body {
1389
+ min-height: 100svh;
1390
+ line-height: 1.5;
1391
+ background: Canvas;
1392
+ color: CanvasText;
1393
+ }
1394
+
1395
+ h1 {
1396
+ font-size: 2em;
1397
+ }
1398
+
1399
+ h2 {
1400
+ font-size: 1.5em;
1401
+ }
1402
+
1403
+ h3 {
1404
+ font-size: 1.17em;
1405
+ }
1406
+
1407
+ h4 {
1408
+ font-size: 1em;
1409
+ }
1410
+
1411
+ h5 {
1412
+ font-size: 0.83em;
1413
+ }
1414
+
1415
+ h6 {
1416
+ font-size: 0.67em;
1417
+ }
1418
+
1419
+ h1,
1420
+ h2,
1421
+ h3,
1422
+ h4,
1423
+ h5,
1424
+ h6 {
1425
+ font-weight: 700;
1426
+ line-height: 1.2;
1427
+ text-wrap: balance;
1428
+ overflow-wrap: break-word;
1429
+ }
1430
+
1431
+ p,
1432
+ li,
1433
+ figcaption,
1434
+ dd,
1435
+ dt,
1436
+ blockquote {
1437
+ overflow-wrap: break-word;
1438
+ }
1439
+
1440
+ p,
1441
+ li,
1442
+ figcaption,
1443
+ blockquote {
1444
+ text-wrap: pretty;
1445
+ }
1446
+
1447
+ ul,
1448
+ ol,
1449
+ menu {
1450
+ list-style: none;
1451
+ }
1452
+
1453
+ a {
1454
+ color: inherit;
1455
+ text-decoration: underline;
1456
+ text-decoration-thickness: 1px;
1457
+ text-underline-offset: 0.15em;
1458
+ text-decoration-skip-ink: auto;
1459
+ }
1460
+
1461
+ b,
1462
+ strong {
1463
+ font-weight: 700;
1464
+ }
1465
+
1466
+ small {
1467
+ font-size: 0.875em;
1468
+ }
1469
+
1470
+ code,
1471
+ kbd,
1472
+ samp,
1473
+ pre {
1474
+ font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
1475
+ font-size: 1em;
1476
+ }
1477
+
1478
+ sub,
1479
+ sup {
1480
+ font-size: 75%;
1481
+ line-height: 0;
1482
+ position: relative;
1483
+ vertical-align: baseline;
1484
+ }
1485
+
1486
+ sub {
1487
+ bottom: -0.25em;
1488
+ }
1489
+
1490
+ sup {
1491
+ top: -0.5em;
1492
+ }
1493
+
1494
+ abbr[title] {
1495
+ text-decoration: underline dotted;
1496
+ text-underline-offset: 0.15em;
1497
+ }
1498
+
1499
+ mark {
1500
+ background: Mark;
1501
+ color: MarkText;
1502
+ }
1503
+
1504
+ hr {
1505
+ height: 0;
1506
+ border: 0;
1507
+ border-top: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
1508
+ color: inherit;
1509
+ }
1510
+
1511
+ img,
1512
+ picture,
1513
+ video,
1514
+ canvas {
1515
+ display: block;
1516
+ max-width: 100%;
1517
+ }
1518
+
1519
+ img,
1520
+ video {
1521
+ height: auto;
1522
+ }
1523
+
1524
+ svg {
1525
+ overflow: hidden;
1526
+ }
1527
+
1528
+ iframe {
1529
+ border: 0;
1530
+ max-width: 100%;
1531
+ }
1532
+
1533
+ table {
1534
+ border-collapse: collapse;
1535
+ border-spacing: 0;
1536
+ }
1537
+
1538
+ fieldset {
1539
+ border: 0;
1540
+ min-inline-size: 0;
1541
+ }
1542
+
1543
+ legend {
1544
+ padding: 0;
1545
+ }
1546
+
1547
+ button,
1548
+ input,
1549
+ select,
1550
+ textarea,
1551
+ optgroup {
1552
+ font: inherit;
1553
+ letter-spacing: inherit;
1554
+ color: inherit;
1555
+ margin: 0;
1556
+ vertical-align: middle;
1557
+ }
1558
+
1559
+ button,
1560
+ input[type="button"],
1561
+ input[type="submit"],
1562
+ input[type="reset"] {
1563
+ appearance: none;
1564
+ background: none;
1565
+ border: 0;
1566
+ padding: 0;
1567
+ cursor: pointer;
1568
+ text-align: center;
1569
+ }
1570
+
1571
+ input,
1572
+ textarea,
1573
+ select {
1574
+ background-color: Canvas;
1575
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1576
+ border-radius: 0;
1577
+ padding: 0.5em 0.65em;
1578
+ }
1579
+
1580
+ input[type="checkbox"],
1581
+ input[type="radio"],
1582
+ input[type="range"],
1583
+ input[type="file"],
1584
+ input[type="color"],
1585
+ input[type="button"],
1586
+ input[type="submit"],
1587
+ input[type="reset"],
1588
+ input[type="image"],
1589
+ input[type="hidden"] {
1590
+ background: none;
1591
+ border: 0;
1592
+ padding: 0;
1593
+ border-radius: 0;
1594
+ }
1595
+
1596
+ input[type="checkbox"],
1597
+ input[type="radio"] {
1598
+ accent-color: CanvasText;
1599
+ }
1600
+
1601
+ input[type="search"] {
1602
+ appearance: none;
1603
+ background-color: Canvas;
1604
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1605
+ }
1606
+
1607
+ input[type="search"]::-webkit-search-decoration,
1608
+ input[type="search"]::-webkit-search-cancel-button,
1609
+ input[type="search"]::-webkit-search-results-button,
1610
+ input[type="search"]::-webkit-search-results-decoration {
1611
+ -webkit-appearance: none;
1612
+ display: none;
1613
+ }
1614
+
1615
+ input[type="number"] {
1616
+ appearance: textfield;
1617
+ background-color: Canvas;
1618
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1619
+ }
1620
+
1621
+ input[type="number"]::-webkit-inner-spin-button,
1622
+ input[type="number"]::-webkit-outer-spin-button {
1623
+ -webkit-appearance: none;
1624
+ margin: 0;
1625
+ }
1626
+
1627
+ textarea {
1628
+ resize: vertical;
1629
+ }
1630
+
1631
+ ::placeholder {
1632
+ opacity: 1;
1633
+ color: color-mix(in srgb, CanvasText 45%, transparent);
1634
+ }
1635
+
1636
+ :focus-visible {
1637
+ outline: 2px solid CanvasText;
1638
+ outline-offset: 2px;
1639
+ }
1640
+
1641
+ :focus:not(:focus-visible) {
1642
+ outline: none;
1643
+ }
1644
+
1645
+ :disabled,
1646
+ [disabled] {
1647
+ cursor: default;
1648
+ opacity: 0.6;
1649
+ }
1650
+
1651
+ label,
1652
+ summary,
1653
+ select,
1654
+ [role="button"] {
1655
+ cursor: pointer;
1656
+ }
1657
+
1658
+ summary {
1659
+ display: list-item;
1660
+ }
1661
+
1662
+ details {
1663
+ display: block;
1664
+ }
1665
+
1666
+ dialog {
1667
+ margin: auto;
1668
+ padding: 1rem;
1669
+ border: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
1670
+ background: Canvas;
1671
+ color: CanvasText;
1672
+ max-width: calc(100vw - 2rem);
1673
+ max-height: calc(100svh - 2rem);
1674
+ }
1675
+
1676
+ dialog:not([open]) {
1677
+ display: none;
1678
+ }
1679
+
1680
+ ::backdrop {
1681
+ background: color-mix(in srgb, CanvasText 40%, transparent);
1682
+ }
1683
+
1684
+ [hidden] {
1685
+ display: none !important;
1686
+ }
1687
+
1688
+ ::selection {
1689
+ background: Highlight;
1690
+ color: HighlightText;
1691
+ }
1692
+
1693
+ @media (prefers-reduced-motion: reduce) {
1694
+ *,
1695
+ *::before,
1696
+ *::after {
1697
+ animation-duration: 0.01ms !important;
1698
+ animation-iteration-count: 1 !important;
1699
+ transition-duration: 0.01ms !important;
1700
+ scroll-behavior: auto !important;
1701
+ }
1702
+ }
1703
+
1704
+ @media (pointer: coarse) {
1705
+ input,
1706
+ select,
1707
+ textarea {
1708
+ font-size: max(16px, 1em);
1709
+ }
1710
+ }`;
1711
+
1712
+ // ../core/dist/admin-client.js
1713
+ function isRecord2(value) {
1714
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1715
+ }
1716
+ function mountAdminBrand(doc, host, service, catalog) {
1717
+ const clip = (value, max) => {
1718
+ if (typeof value !== "string")
1719
+ return null;
1720
+ const trimmed = value.trim();
1721
+ if (trimmed === "")
1722
+ return null;
1723
+ return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
1724
+ };
1725
+ const httpUrl = (value) => {
1726
+ if (typeof value !== "string" || value.length > 2e3)
1727
+ return null;
1728
+ try {
1729
+ const url = new URL(value);
1730
+ if (url.protocol !== "https:" && url.protocol !== "http:")
1731
+ return null;
1732
+ return url.href;
1733
+ } catch {
1734
+ return null;
1735
+ }
1736
+ };
1737
+ const hexColor = (value) => {
1738
+ if (typeof value !== "string")
1739
+ return null;
1740
+ const raw = value.trim();
1741
+ return /^#[0-9a-fA-F]{3,8}$/.test(raw) ? raw : null;
1742
+ };
1743
+ const hostname = (value) => {
1744
+ try {
1745
+ return new URL(value).hostname.replace(/^www\./, "");
1746
+ } catch {
1747
+ return value;
1748
+ }
1749
+ };
1750
+ host.replaceChildren();
1751
+ host.hidden = true;
1752
+ if (!isRecord2(catalog) || !Object.hasOwn(catalog, service))
1753
+ return;
1754
+ const row = catalog[service];
1755
+ if (!isRecord2(row))
1756
+ return;
1757
+ const vendor = clip(row.vendor, 80);
1758
+ const website = httpUrl(row.website);
1759
+ const guide = httpUrl(row.guide);
1760
+ const docs = httpUrl(row.docs);
1761
+ const logo = httpUrl(row.logo);
1762
+ const color = hexColor(row.color);
1763
+ const description = clip(row.description, 280);
1764
+ if (vendor === null || website === null && guide === null && docs === null)
1765
+ return;
1766
+ const add = (node) => {
1767
+ host.append(node);
1768
+ return node;
1769
+ };
1770
+ if (logo !== null) {
1771
+ const img = doc.createElement("img");
1772
+ img.alt = "";
1773
+ img.src = logo;
1774
+ img.width = 16;
1775
+ img.height = 16;
1776
+ img.decoding = "async";
1777
+ img.referrerPolicy = "no-referrer";
1778
+ if (color !== null)
1779
+ img.style.borderColor = color;
1780
+ add(img);
1781
+ }
1782
+ const name = doc.createElement("span");
1783
+ name.className = "vendor-name";
1784
+ name.textContent = vendor;
1785
+ if (description !== null)
1786
+ name.title = description;
1787
+ add(name);
1788
+ const sep = () => {
1789
+ const node = doc.createElement("span");
1790
+ node.className = "vendor-sep";
1791
+ node.setAttribute("aria-hidden", "true");
1792
+ node.textContent = "\xB7";
1793
+ return node;
1794
+ };
1795
+ const link = (href, label, title) => {
1796
+ const node = doc.createElement("a");
1797
+ node.className = "vendor-link";
1798
+ node.href = href;
1799
+ node.target = "_blank";
1800
+ node.rel = "noopener noreferrer";
1801
+ node.referrerPolicy = "no-referrer";
1802
+ node.textContent = label;
1803
+ node.title = title;
1804
+ return node;
1805
+ };
1806
+ if (website !== null) {
1807
+ add(sep());
1808
+ add(link(website, hostname(website), `${vendor} website`));
1809
+ }
1810
+ if (guide !== null) {
1811
+ add(sep());
1812
+ add(link(guide, "Docs", `Docs for ${vendor}`));
1813
+ }
1814
+ if (docs !== null) {
1815
+ add(sep());
1816
+ add(link(docs, "API", `${vendor} API reference`));
1817
+ }
1818
+ host.removeAttribute("hidden");
1819
+ host.hidden = false;
1820
+ }
1821
+ function startAdminBrand(env) {
1822
+ const requested = env.params.get("brands");
1823
+ let brandsUrl = env.brandsUrl;
1824
+ if (requested) {
1825
+ try {
1826
+ const url = new URL(requested, env.location.href);
1827
+ const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]" || url.origin === env.location.origin;
1828
+ if ((url.protocol === "https:" || url.protocol === "http:") && local)
1829
+ brandsUrl = url.href;
1830
+ } catch {
1831
+ brandsUrl = env.brandsUrl;
1832
+ }
1833
+ }
1834
+ const vendorHost = env.$("vendor");
1835
+ const brandRoot = env.document.querySelector("[data-mockingbird-admin]");
1836
+ const brandService = brandRoot?.getAttribute("data-service") || "";
1837
+ if (vendorHost === null)
1838
+ return;
1839
+ env.fetch(brandsUrl, {
1840
+ headers: { accept: "application/json" },
1841
+ signal: AbortSignal.timeout(5e3)
1842
+ }).then(async (response) => {
1843
+ if (!response.ok)
1844
+ return null;
1845
+ const catalog = await response.json();
1846
+ return catalog;
1847
+ }).then((catalog) => {
1848
+ mountAdminBrand(env.document, vendorHost, brandService, catalog);
1849
+ }).catch(() => {
1850
+ });
1851
+ }
1852
+ function faultPresetList(value) {
1853
+ if (!isRecord2(value))
1854
+ throw new Error("Fault presets response was not an object");
1855
+ if (!Object.hasOwn(value, "presets") || value.presets === void 0 || value.presets === null) {
1856
+ return { presets: [] };
1857
+ }
1858
+ if (!Array.isArray(value.presets))
1859
+ throw new Error("Fault presets response was not a list");
1860
+ return {
1861
+ presets: value.presets.flatMap((item) => {
1862
+ if (!isRecord2(item) || typeof item.name !== "string")
1863
+ return [];
1864
+ return [{ name: item.name }];
1865
+ })
1866
+ };
1867
+ }
1868
+ function bootAdmin(config) {
1869
+ const fieldKinds = ["string", "number", "boolean", "null", "object", "array", "unknown"];
1870
+ const sources = ["declared", "inferred", "mixed"];
1871
+ const isFieldKind = (value) => typeof value === "string" && fieldKinds.some((kind) => kind === value);
1872
+ const isSource = (value) => typeof value === "string" && sources.some((source) => source === value);
1873
+ const readJson2 = (text) => {
1874
+ const value = JSON.parse(text);
1875
+ return value;
1876
+ };
1877
+ const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => {
1878
+ switch (char) {
1879
+ case "&":
1880
+ return "&amp;";
1881
+ case "<":
1882
+ return "&lt;";
1883
+ case ">":
1884
+ return "&gt;";
1885
+ case '"':
1886
+ return "&quot;";
1887
+ default:
1888
+ return "&#39;";
1889
+ }
1890
+ });
1891
+ const el = (id, kind) => {
1892
+ const node = document.getElementById(id);
1893
+ if (!(node instanceof kind))
1894
+ throw new Error(`admin shell is missing #${id}`);
1895
+ return node;
1896
+ };
1897
+ const errorMessage = (data, fallback) => {
1898
+ if (!isRecord2(data))
1899
+ return fallback;
1900
+ const error = data.error;
1901
+ if (!isRecord2(error) || typeof error.message !== "string" || error.message === "") {
1902
+ return fallback;
1903
+ }
1904
+ return error.message;
1905
+ };
1906
+ const requireList = (value, label) => {
1907
+ if (value === void 0 || value === null)
1908
+ return [];
1909
+ if (!Array.isArray(value))
1910
+ throw new Error(`${label} was not a list`);
1911
+ return value;
1912
+ };
1913
+ const state = {
1914
+ namespace: "default",
1915
+ key: sessionStorage.getItem("mockingbird-admin-key") || "",
1916
+ view: "overview",
1917
+ collection: "",
1918
+ shape: null,
1919
+ route: null,
1920
+ editing: null
1921
+ };
1922
+ const standard = new Set(config.standardRoutes);
1923
+ const banner = el("banner", HTMLElement);
1924
+ const keyInput = el("admin-key", HTMLInputElement);
1925
+ keyInput.value = state.key;
1926
+ const params = new URLSearchParams(location.search);
1927
+ const queryKey = params.get("key");
1928
+ if (queryKey) {
1929
+ state.key = queryKey;
1930
+ keyInput.value = state.key;
1931
+ sessionStorage.setItem("mockingbird-admin-key", state.key);
1932
+ params.delete("key");
1933
+ const next = params.toString();
1934
+ history.replaceState(null, "", location.pathname + (next ? `?${next}` : ""));
1935
+ }
1936
+ const queryNamespace = params.get("namespace");
1937
+ if (queryNamespace)
1938
+ state.namespace = queryNamespace;
1939
+ const showError = (error) => {
1940
+ banner.textContent = error instanceof Error ? error.message : String(error);
1941
+ banner.classList.add("show");
1942
+ };
1943
+ const clearError = () => {
1944
+ banner.classList.remove("show");
1945
+ };
1946
+ const api = async (method, path, body) => {
1947
+ const headers = { accept: "application/json" };
1948
+ if (state.key !== "")
1949
+ headers[config.adminKeyHeader] = state.key;
1950
+ if (state.namespace !== "")
1951
+ headers["x-mockingbird-namespace"] = state.namespace;
1952
+ if (body !== void 0)
1953
+ headers["content-type"] = "application/json";
1954
+ const init = { method, headers };
1955
+ if (body !== void 0)
1956
+ init.body = JSON.stringify(body);
1957
+ const response = await fetch(config.adminPrefix + path, init);
1958
+ const text = await response.text();
1959
+ let data = null;
1960
+ if (text !== "") {
1961
+ try {
1962
+ data = readJson2(text);
1963
+ } catch {
1964
+ data = { raw: text };
1965
+ }
1966
+ }
1967
+ if (response.status === 401) {
1968
+ keyInput.focus();
1969
+ throw new Error(errorMessage(data, "Admin key required"));
1970
+ }
1971
+ if (!response.ok) {
1972
+ throw new Error(errorMessage(data, `${response.status} ${method} ${path}`));
1973
+ }
1974
+ return data;
1975
+ };
1976
+ const originGet = async (path) => {
1977
+ const response = await fetch(path, { headers: { accept: "application/json" } });
1978
+ if (!response.ok)
1979
+ throw new Error(`${response.status} ${path}`);
1980
+ const value = await response.json();
1981
+ return value;
1982
+ };
1983
+ const panelApi = {
1984
+ get: (path) => api("GET", path),
1985
+ send: (method, path, body) => api(method, path, body),
1986
+ namespace: () => state.namespace
1987
+ };
1988
+ const isPanelScript = (value) => typeof value === "function";
1989
+ const setTheme = (theme) => {
1990
+ if (theme)
1991
+ document.documentElement.dataset.theme = theme;
1992
+ else
1993
+ delete document.documentElement.dataset.theme;
1994
+ };
1995
+ const storedTheme = localStorage.getItem("mockingbird-admin-theme");
1996
+ if (storedTheme)
1997
+ setTheme(storedTheme);
1998
+ el("theme", HTMLButtonElement).addEventListener("click", () => {
1999
+ const current = document.documentElement.dataset.theme;
2000
+ const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
2001
+ const next = dark ? "light" : "dark";
2002
+ localStorage.setItem("mockingbird-admin-theme", next);
2003
+ setTheme(next);
2004
+ });
2005
+ const show = (view) => {
2006
+ state.view = view;
2007
+ for (const button of document.querySelectorAll("#nav button")) {
2008
+ if (!(button instanceof HTMLButtonElement))
2009
+ continue;
2010
+ button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false");
2011
+ }
2012
+ for (const section of document.querySelectorAll("main .view")) {
2013
+ if (!(section instanceof HTMLElement))
2014
+ continue;
2015
+ section.hidden = section.id !== `view-${view}`;
2016
+ }
2017
+ };
2018
+ el("nav", HTMLElement).addEventListener("click", (event) => {
2019
+ const target = event.target;
2020
+ if (!(target instanceof Element))
2021
+ return;
2022
+ const button = target.closest("button[data-view]");
2023
+ if (!(button instanceof HTMLButtonElement))
2024
+ return;
2025
+ const view = button.dataset.view;
2026
+ if (view === void 0)
2027
+ return;
2028
+ show(view);
2029
+ refresh().catch(showError);
2030
+ });
2031
+ 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>`;
2032
+ const table = (columns, rows) => {
2033
+ if (rows.length === 0)
2034
+ return '<p class="empty">Nothing here yet.</p>';
2035
+ const head = `<tr>${columns.map((column) => `<th>${column.label}</th>`).join("")}</tr>`;
2036
+ const body = rows.map((row) => {
2037
+ const cells = columns.map((column) => `<td data-label="${column.label}">${column.cell(row)}</td>`).join("");
2038
+ const attrs = "attrs" in row && typeof row.attrs === "string" ? row.attrs : "";
2039
+ return `<tr${attrs}>${cells}</tr>`;
2040
+ }).join("");
2041
+ return `<table><thead>${head}</thead><tbody>${body}</tbody></table>`;
2042
+ };
2043
+ const preview = (value) => {
2044
+ const text = JSON.stringify(value) ?? "null";
2045
+ return esc(text.length > 180 ? `${text.slice(0, 180)}\u2026` : text);
2046
+ };
2047
+ const when = (epoch) => {
2048
+ const date = new Date(epoch);
2049
+ return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString();
2050
+ };
2051
+ const clockCard = (epoch) => {
2052
+ const date = new Date(epoch);
2053
+ if (Number.isNaN(date.getTime()))
2054
+ return esc(String(epoch));
2055
+ const iso = date.toISOString();
2056
+ return `${esc(iso.slice(0, 10))}<span class="clock-time">${esc(iso.slice(11, 19))} UTC</span>`;
2057
+ };
2058
+ const decodeClock = (value) => {
2059
+ if (!isRecord2(value) || typeof value.now !== "number" || typeof value.offsetMs !== "number" || typeof value.frozen !== "boolean") {
2060
+ throw new Error("Clock response was not a clock");
2061
+ }
2062
+ return { now: value.now, offsetMs: value.offsetMs, frozen: value.frozen };
2063
+ };
2064
+ const decodeField = (value) => {
2065
+ if (!isRecord2(value) || typeof value.name !== "string" || !isFieldKind(value.kind)) {
2066
+ return void 0;
2067
+ }
2068
+ const field = {
2069
+ name: value.name,
2070
+ kind: value.kind,
2071
+ optional: value.optional === true
2072
+ };
2073
+ if (typeof value.description === "string")
2074
+ field.description = value.description;
2075
+ if (Array.isArray(value.fields)) {
2076
+ field.fields = value.fields.flatMap((item) => {
2077
+ const child = decodeField(item);
2078
+ return child === void 0 ? [] : [child];
2079
+ });
2080
+ }
2081
+ return field;
2082
+ };
2083
+ const decodeCollection = (value) => {
2084
+ if (!isRecord2(value) || typeof value.name !== "string")
2085
+ return void 0;
2086
+ const fields = Array.isArray(value.fields) ? value.fields.flatMap((item) => {
2087
+ const field = decodeField(item);
2088
+ return field === void 0 ? [] : [field];
2089
+ }) : [];
2090
+ const collection = {
2091
+ name: value.name,
2092
+ label: typeof value.label === "string" ? value.label : value.name,
2093
+ count: typeof value.count === "number" ? value.count : 0,
2094
+ source: isSource(value.source) ? value.source : "inferred",
2095
+ fields
2096
+ };
2097
+ if (typeof value.description === "string")
2098
+ collection.description = value.description;
2099
+ return collection;
2100
+ };
2101
+ const decodeState = (value) => {
2102
+ if (!isRecord2(value) || !Array.isArray(value.collections)) {
2103
+ throw new Error("State response has no collection list");
2104
+ }
2105
+ return {
2106
+ namespace: typeof value.namespace === "string" ? value.namespace : state.namespace,
2107
+ storageNamespace: typeof value.storageNamespace === "string" ? value.storageNamespace : "",
2108
+ collections: value.collections.flatMap((item) => {
2109
+ const collection = decodeCollection(item);
2110
+ return collection === void 0 ? [] : [collection];
2111
+ })
2112
+ };
2113
+ };
2114
+ const decodeRecords = (value) => requireList(isRecord2(value) ? value.records : void 0, "Records").flatMap((item) => {
2115
+ if (!isRecord2(item) || typeof item.id !== "string")
2116
+ return [];
2117
+ return [
2118
+ {
2119
+ id: item.id,
2120
+ seq: typeof item.seq === "number" ? item.seq : 0,
2121
+ value: item.value,
2122
+ attrs: ` class="clickable" data-id="${esc(item.id)}"`
2123
+ }
2124
+ ];
2125
+ });
2126
+ const textOf = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : "";
2127
+ const decodeJournal = (value) => requireList(isRecord2(value) ? value.requests : void 0, "Journal").flatMap((item) => {
2128
+ if (!isRecord2(item))
2129
+ return [];
2130
+ return [
2131
+ {
2132
+ at: textOf(item.at),
2133
+ method: textOf(item.method),
2134
+ path: textOf(item.path),
2135
+ status: textOf(item.status),
2136
+ durationMs: textOf(item.durationMs),
2137
+ operationId: textOf(item.operationId)
2138
+ }
2139
+ ];
2140
+ });
2141
+ const decodeFaults = (value) => requireList(isRecord2(value) ? value.faults : void 0, "Faults").flatMap((item) => {
2142
+ if (!isRecord2(item))
2143
+ return [];
2144
+ return [
2145
+ {
2146
+ id: textOf(item.id),
2147
+ method: typeof item.method === "string" ? item.method : void 0,
2148
+ pathPrefix: typeof item.pathPrefix === "string" ? item.pathPrefix : void 0,
2149
+ operationId: typeof item.operationId === "string" ? item.operationId : void 0,
2150
+ status: typeof item.status === "number" ? item.status : void 0,
2151
+ effect: typeof item.effect === "string" ? item.effect : void 0,
2152
+ drop: typeof item.drop === "boolean" ? item.drop : void 0,
2153
+ latencyMs: typeof item.latencyMs === "number" ? item.latencyMs : void 0
2154
+ }
2155
+ ];
2156
+ });
2157
+ const loadNamespaces = async () => {
2158
+ const data = await api("GET", "/namespaces");
2159
+ const select = el("namespace", HTMLSelectElement);
2160
+ const listed = requireList(isRecord2(data) ? data.namespaces : void 0, "Namespaces");
2161
+ const fallback = isRecord2(data) && typeof data.default === "string" && data.default !== "" ? data.default : "default";
2162
+ const names = (listed.length > 0 ? listed.map((name) => String(name)) : [fallback]).slice();
2163
+ if (!names.includes(state.namespace))
2164
+ names.unshift(state.namespace);
2165
+ select.innerHTML = names.map((name) => `<option value="${esc(name)}">${esc(name)}</option>`).join("");
2166
+ select.value = state.namespace;
2167
+ };
2168
+ const renderOverview = async () => {
2169
+ const [health, shape, clock, journal] = await Promise.all([
2170
+ originGet("/health"),
2171
+ api("GET", "/state"),
2172
+ api("GET", "/clock"),
2173
+ api("GET", "/requests?limit=8")
2174
+ ]);
2175
+ if (!isRecord2(health))
2176
+ throw new Error("Health response was not an object");
2177
+ const decoded = decodeState(shape);
2178
+ state.shape = decoded;
2179
+ const time = decodeClock(clock);
2180
+ el("overview-cards", HTMLElement).innerHTML = cards([
2181
+ {
2182
+ k: "Service",
2183
+ v: esc(typeof health.service === "string" && health.service !== "" ? health.service : config.service)
2184
+ },
2185
+ {
2186
+ k: "Status",
2187
+ v: esc(typeof health.status === "string" && health.status !== "" ? health.status : "ok")
2188
+ },
2189
+ { k: "Namespace", v: esc(state.namespace) },
2190
+ { k: "Collections", v: String(decoded.collections.length) },
2191
+ {
2192
+ k: "Records",
2193
+ v: String(decoded.collections.reduce((sum, item) => sum + item.count, 0))
2194
+ },
2195
+ { k: "Clock", v: clockCard(time.now) }
2196
+ ]);
2197
+ el("overview-requests", HTMLElement).innerHTML = table([
2198
+ { label: "Method", cell: (row) => esc(row.method) },
2199
+ { label: "Path", cell: (row) => esc(row.path) },
2200
+ {
2201
+ label: "Status",
2202
+ cell: (row) => `<span class="status-${row.status.charAt(0)}">${esc(row.status)}</span>`
2203
+ },
2204
+ { label: "Operation", cell: (row) => esc(row.operationId) }
2205
+ ], decodeJournal(journal));
2206
+ };
2207
+ const schemaChips = (collection) => {
2208
+ if (collection.fields.length === 0) {
2209
+ return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>';
2210
+ }
2211
+ return `<div class="chips">${collection.fields.map((field) => `<span class="chip">${esc(field.name)} <i>${esc(field.kind)}${field.optional ? "?" : ""}</i></span>`).join("")}</div>`;
2212
+ };
2213
+ const renderState = async () => {
2214
+ const shape = decodeState(await api("GET", "/state"));
2215
+ state.shape = shape;
2216
+ const host = el("collections", HTMLElement);
2217
+ if (shape.collections.length === 0) {
2218
+ host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>';
2219
+ el("schema", HTMLElement).innerHTML = "";
2220
+ el("records", HTMLElement).innerHTML = "";
2221
+ return;
2222
+ }
2223
+ if (!shape.collections.some((item) => item.name === state.collection)) {
2224
+ const first = shape.collections[0];
2225
+ if (first === void 0)
2226
+ return;
2227
+ state.collection = first.name;
2228
+ }
2229
+ 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("");
2230
+ const selected = shape.collections.find((item) => item.name === state.collection);
2231
+ if (selected === void 0)
2232
+ return;
2233
+ const description = selected.description ? `<p class="lede">${esc(selected.description)}</p>` : "";
2234
+ el("schema", HTMLElement).innerHTML = `<h3>${esc(selected.label)}</h3>${description}${schemaChips(selected)}`;
2235
+ const page = await api("GET", `/state/${encodeURIComponent(state.collection)}?limit=50`);
2236
+ el("records", HTMLElement).innerHTML = table([
2237
+ { label: "Id", cell: (row) => `<code>${esc(row.id)}</code>` },
2238
+ { label: "Seq", cell: (row) => esc(row.seq) },
2239
+ { label: "Value", cell: (row) => preview(row.value) }
2240
+ ], decodeRecords(page));
2241
+ };
2242
+ const renderClock = async () => {
2243
+ const clock = decodeClock(await api("GET", "/clock"));
2244
+ 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>`;
2245
+ const freeze = el("freeze", HTMLButtonElement);
2246
+ freeze.textContent = clock.frozen ? "Unfreeze" : "Freeze";
2247
+ freeze.dataset.frozen = clock.frozen ? "1" : "0";
2248
+ };
2249
+ const renderFaults = async () => {
2250
+ const [faults, presets] = await Promise.all([
2251
+ api("GET", "/faults"),
2252
+ api("GET", "/faults/presets").catch(() => ({ presets: [] }))
2253
+ ]);
2254
+ const select = el("fault-preset", HTMLSelectElement);
2255
+ const current = select.value;
2256
+ const list = faultPresetList(presets).presets;
2257
+ select.innerHTML = `<option value="">Custom rule</option>${list.map((preset) => `<option value="${esc(preset.name)}">${esc(preset.name)}</option>`).join("")}`;
2258
+ select.value = current;
2259
+ el("fault-list", HTMLElement).innerHTML = table([
2260
+ { label: "Id", cell: (row) => esc(row.id) },
2261
+ {
2262
+ label: "Match",
2263
+ cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter((part) => Boolean(part)).join(" ") || "all")
2264
+ },
2265
+ {
2266
+ label: "Effect",
2267
+ cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || ""))
2268
+ }
2269
+ ], decodeFaults(faults));
2270
+ };
2271
+ const renderJournal = async () => {
2272
+ const journal = await api("GET", "/requests?limit=100");
2273
+ el("journal", HTMLElement).innerHTML = table([
2274
+ { label: "When", cell: (row) => esc(row.at) },
2275
+ { label: "Method", cell: (row) => esc(row.method) },
2276
+ { label: "Path", cell: (row) => esc(row.path) },
2277
+ { label: "Status", cell: (row) => esc(row.status) },
2278
+ { label: "ms", cell: (row) => esc(row.durationMs) }
2279
+ ], decodeJournal(journal));
2280
+ };
2281
+ const renderRoutes = async () => {
2282
+ const data = await api("GET", "/");
2283
+ if (!isRecord2(data))
2284
+ throw new Error("Route list was not an object");
2285
+ const routes = requireList(data.routes, "Routes").map((route) => String(route));
2286
+ el("route-list", HTMLElement).innerHTML = routes.map((route) => {
2287
+ const extra = standard.has(route) ? "" : " <span class='badge'>extension</span>";
2288
+ const parts = route.split(" ");
2289
+ const method = parts[0] ?? "";
2290
+ const path = parts.slice(1).join(" ");
2291
+ 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>`;
2292
+ }).join("");
2293
+ };
2294
+ const renderers = {
2295
+ overview: renderOverview,
2296
+ state: renderState,
2297
+ clock: renderClock,
2298
+ faults: renderFaults,
2299
+ journal: renderJournal,
2300
+ routes: renderRoutes
2301
+ };
2302
+ async function refresh() {
2303
+ clearError();
2304
+ const render = renderers[state.view];
2305
+ if (render)
2306
+ await render();
2307
+ }
2308
+ el("namespace", HTMLSelectElement).addEventListener("change", () => {
2309
+ state.namespace = el("namespace", HTMLSelectElement).value;
2310
+ refresh().catch(showError);
2311
+ });
2312
+ keyInput.addEventListener("change", () => {
2313
+ state.key = keyInput.value.trim();
2314
+ sessionStorage.setItem("mockingbird-admin-key", state.key);
2315
+ refresh().catch(showError);
2316
+ });
2317
+ el("refresh", HTMLButtonElement).addEventListener("click", () => {
2318
+ refresh().catch(showError);
2319
+ });
2320
+ el("collections", HTMLElement).addEventListener("click", (event) => {
2321
+ const target = event.target;
2322
+ if (!(target instanceof Element))
2323
+ return;
2324
+ const button = target.closest("[data-collection]");
2325
+ if (!(button instanceof HTMLElement))
2326
+ return;
2327
+ const collection = button.dataset.collection;
2328
+ if (collection === void 0)
2329
+ return;
2330
+ state.collection = collection;
2331
+ renderState().catch(showError);
2332
+ });
2333
+ el("records", HTMLElement).addEventListener("click", (event) => {
2334
+ const target = event.target;
2335
+ if (!(target instanceof Element))
2336
+ return;
2337
+ const row = target.closest("[data-id]");
2338
+ if (!(row instanceof HTMLElement))
2339
+ return;
2340
+ const id = row.dataset.id;
2341
+ if (id === void 0)
2342
+ return;
2343
+ openEditor(id).catch(showError);
2344
+ });
2345
+ const editor = el("editor", HTMLDialogElement);
2346
+ const editorId = el("editor-id", HTMLInputElement);
2347
+ const editorValue = el("editor-value", HTMLTextAreaElement);
2348
+ const editorDelete = el("editor-delete", HTMLButtonElement);
2349
+ async function openEditor(id) {
2350
+ state.editing = id;
2351
+ el("editor-title", HTMLElement).textContent = id ? "Edit record" : "New record";
2352
+ editorId.value = id ?? "";
2353
+ editorId.readOnly = id !== null;
2354
+ editorDelete.hidden = id === null;
2355
+ if (id) {
2356
+ const record2 = await api("GET", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`);
2357
+ if (!isRecord2(record2))
2358
+ throw new Error("Record response was not an object");
2359
+ editorValue.value = JSON.stringify(record2.value, null, 2) ?? "";
2360
+ } else {
2361
+ editorValue.value = "{\n \n}";
2362
+ }
2363
+ editor.showModal();
2364
+ }
2365
+ el("new-record", HTMLButtonElement).addEventListener("click", () => {
2366
+ openEditor(null).catch(showError);
2367
+ });
2368
+ el("editor-form", HTMLFormElement).addEventListener("submit", (event) => {
2369
+ const submitter = event.submitter;
2370
+ if (!(submitter instanceof HTMLButtonElement) || submitter.value !== "save")
2371
+ return;
2372
+ event.preventDefault();
2373
+ let value;
2374
+ try {
2375
+ value = readJson2(editorValue.value);
2376
+ } catch {
2377
+ showError(new Error("Value is not JSON"));
2378
+ return;
2379
+ }
2380
+ const id = editorId.value.trim();
2381
+ const path = `/state/${encodeURIComponent(state.collection)}`;
2382
+ const request = state.editing ? api("PUT", `${path}/${encodeURIComponent(id)}`, { value }) : api("POST", path, { id: id || void 0, value });
2383
+ request.then(() => {
2384
+ editor.close();
2385
+ return renderState();
2386
+ }).catch(showError);
2387
+ });
2388
+ editorDelete.addEventListener("click", () => {
2389
+ const id = state.editing;
2390
+ if (!id)
2391
+ return;
2392
+ api("DELETE", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`).then(() => {
2393
+ editor.close();
2394
+ return renderState();
2395
+ }).catch(showError);
2396
+ });
2397
+ for (const button of document.querySelectorAll("[data-advance]")) {
2398
+ if (!(button instanceof HTMLButtonElement))
2399
+ continue;
2400
+ button.addEventListener("click", () => {
2401
+ const raw = button.dataset.advance;
2402
+ if (raw === void 0)
2403
+ return;
2404
+ api("POST", "/clock", { advance: Number(raw) }).then(() => renderClock()).catch(showError);
2405
+ });
2406
+ }
2407
+ el("freeze", HTMLButtonElement).addEventListener("click", () => {
2408
+ const frozen = el("freeze", HTMLButtonElement).dataset.frozen === "1";
2409
+ api("POST", "/clock", { freeze: !frozen }).then(() => renderClock()).catch(showError);
2410
+ });
2411
+ el("clock-reset", HTMLButtonElement).addEventListener("click", () => {
2412
+ api("POST", "/clock", { reset: true }).then(() => renderClock()).catch(showError);
2413
+ });
2414
+ el("clock-set", HTMLFormElement).addEventListener("submit", (event) => {
2415
+ event.preventDefault();
2416
+ const raw = el("clock-instant", HTMLInputElement).value.trim();
2417
+ const set = /^-?\d+$/.test(raw) ? Number(raw) : raw;
2418
+ api("POST", "/clock", { set }).then(() => renderClock()).catch(showError);
2419
+ });
2420
+ el("fault-form", HTMLFormElement).addEventListener("submit", (event) => {
2421
+ event.preventDefault();
2422
+ const preset = el("fault-preset", HTMLSelectElement).value;
2423
+ if (preset !== "") {
2424
+ api("POST", "/faults", { preset }).then(() => renderFaults()).catch(showError);
2425
+ return;
2426
+ }
2427
+ const status = Number(el("fault-status", HTMLInputElement).value);
2428
+ if (!Number.isFinite(status)) {
2429
+ showError(new Error("Status must be a number, or choose a preset"));
2430
+ return;
2431
+ }
2432
+ const path = el("fault-path", HTMLInputElement).value;
2433
+ const method = el("fault-method", HTMLInputElement).value;
2434
+ api("POST", "/faults", {
2435
+ status,
2436
+ ...path !== "" ? { pathPrefix: path } : {},
2437
+ ...method !== "" ? { method } : {}
2438
+ }).then(() => renderFaults()).catch(showError);
2439
+ });
2440
+ el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
2441
+ api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
2442
+ });
2443
+ el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
2444
+ api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
2445
+ });
2446
+ el("route-list", HTMLElement).addEventListener("click", (event) => {
2447
+ const target = event.target;
2448
+ if (!(target instanceof Element))
2449
+ return;
2450
+ const button = target.closest("[data-route]");
2451
+ if (!(button instanceof HTMLElement))
2452
+ return;
2453
+ const route = button.dataset.route;
2454
+ if (route === void 0)
2455
+ return;
2456
+ state.route = route;
2457
+ el("route-title", HTMLElement).textContent = route;
2458
+ const method = route.split(" ")[0] ?? "";
2459
+ el("route-body", HTMLTextAreaElement).disabled = method === "GET" || method === "DELETE";
2460
+ });
2461
+ el("route-form", HTMLFormElement).addEventListener("submit", (event) => {
2462
+ event.preventDefault();
2463
+ const send = async () => {
2464
+ if (state.route === null)
2465
+ throw new Error("Choose a route first");
2466
+ const parts = state.route.split(" ");
2467
+ const method = parts[0];
2468
+ const path = parts.slice(1).join(" ");
2469
+ if (method === void 0)
2470
+ throw new Error("Choose a route first");
2471
+ const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_match, key) => prompt(`Value for ${key}`) || "");
2472
+ let body;
2473
+ const routeBody = el("route-body", HTMLTextAreaElement);
2474
+ if (method !== "GET" && method !== "DELETE" && routeBody.value.trim() !== "") {
2475
+ try {
2476
+ body = readJson2(routeBody.value);
2477
+ } catch {
2478
+ throw new Error("Body is not JSON");
2479
+ }
2480
+ }
2481
+ const data = await api(method, concrete, body);
2482
+ el("route-result", HTMLElement).textContent = JSON.stringify(data, null, 2);
2483
+ };
2484
+ send().catch(showError);
2485
+ });
2486
+ const decodePanel = (value) => {
2487
+ if (!isRecord2(value) || typeof value.id !== "string" || typeof value.title !== "string") {
2488
+ return void 0;
2489
+ }
2490
+ const panel = {
2491
+ id: value.id,
2492
+ title: value.title,
2493
+ description: typeof value.description === "string" ? value.description : "",
2494
+ html: typeof value.html === "string" ? value.html : ""
2495
+ };
2496
+ if (typeof value.script === "string")
2497
+ panel.script = value.script;
2498
+ return panel;
2499
+ };
2500
+ const mountPanels = (panels) => {
2501
+ const nav = el("nav", HTMLElement);
2502
+ const views = el("views", HTMLElement);
2503
+ for (const panel of panels) {
2504
+ const button = document.createElement("button");
2505
+ button.type = "button";
2506
+ button.dataset.view = panel.id;
2507
+ button.textContent = panel.title;
2508
+ nav.append(button);
2509
+ const section = document.createElement("section");
2510
+ section.className = "view";
2511
+ section.id = `view-${panel.id}`;
2512
+ section.hidden = true;
2513
+ const title = document.createElement("h2");
2514
+ title.textContent = panel.title;
2515
+ const lede = document.createElement("p");
2516
+ lede.className = "lede";
2517
+ lede.textContent = panel.description;
2518
+ const body = document.createElement("div");
2519
+ body.className = "panel-body";
2520
+ body.id = `panel-${panel.id}`;
2521
+ body.innerHTML = panel.html;
2522
+ section.append(title, lede, body);
2523
+ views.append(section);
2524
+ if (panel.script === void 0)
2525
+ continue;
2526
+ try {
2527
+ const compiled = new Function("root", "api", panel.script);
2528
+ if (!isPanelScript(compiled))
2529
+ throw new Error("panel script did not compile");
2530
+ compiled(body, panelApi);
2531
+ } catch (error) {
2532
+ const note = document.createElement("p");
2533
+ note.className = "banner show";
2534
+ note.textContent = error instanceof Error ? error.message : String(error);
2535
+ body.append(note);
2536
+ }
2537
+ }
2538
+ };
2539
+ startAdminBrand({
2540
+ document,
2541
+ $: (id) => document.getElementById(id),
2542
+ params,
2543
+ location: { href: location.href, origin: location.origin },
2544
+ fetch,
2545
+ brandsUrl: config.brandsUrl
2546
+ });
2547
+ loadNamespaces().then(() => api("GET", "/ui/manifest")).then((manifest) => {
2548
+ const panels = requireList(isRecord2(manifest) ? manifest.panels : void 0, "Panels").flatMap((panel) => {
2549
+ const decoded = decodePanel(panel);
2550
+ return decoded === void 0 ? [] : [decoded];
2551
+ });
2552
+ mountPanels(panels);
2553
+ return refresh();
2554
+ }).catch(showError);
2555
+ }
2556
+ var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
2557
+ var adminClientSource = (config) => {
2558
+ const payload = JSON.stringify(config).replace(/</g, "\\u003c");
2559
+ return `<script>
2560
+ ${clientPrelude}
2561
+ bootAdmin(${payload});
2562
+ </script>`;
2563
+ };
2564
+
1339
2565
  // ../core/dist/surface.js
1340
2566
  var defineMock = (runtime) => runtime;
1341
2567
  var STANDARD_ADMIN_ROUTES = [
@@ -1404,94 +2630,106 @@ var escapeHtml = (value) => value.replace(/[&<>"']/g, (char) => {
1404
2630
  return "&#39;";
1405
2631
  }
1406
2632
  });
2633
+ var ADMIN_BRANDS_URL = "https://mockingbird.chrisvouga.dev/brands.json";
1407
2634
  var renderAdminDocument = (service) => {
1408
2635
  const name = escapeHtml(service);
1409
- const standard = JSON.stringify(STANDARD_ADMIN_ROUTES);
2636
+ const script = adminClientSource({
2637
+ standardRoutes: STANDARD_ADMIN_ROUTES,
2638
+ adminPrefix: ADMIN_PREFIX,
2639
+ adminKeyHeader: ADMIN_KEY_HEADER,
2640
+ brandsUrl: ADMIN_BRANDS_URL,
2641
+ service
2642
+ });
1410
2643
  return `<!DOCTYPE html>
1411
2644
  <html lang="en">
1412
2645
  <head>
1413
2646
  <meta charset="utf-8">
1414
2647
  <meta name="viewport" content="width=device-width, initial-scale=1">
1415
2648
  <meta name="color-scheme" content="light dark">
1416
- <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">
2649
+ <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">
1417
2650
  <title>${name} admin</title>
1418
2651
  <style>
2652
+ ${CSS_RESET}
1419
2653
  :root {
1420
2654
  color-scheme: light;
1421
- --bg: #f6f5f2;
2655
+ --bg: #fafafa;
1422
2656
  --bg-elev: #ffffff;
1423
- --bg-muted: #eeece6;
1424
- --bg-inset: #f3f1eb;
1425
- --line: #e2dfd6;
1426
- --line-strong: #d0ccc1;
1427
- --ink: #1c1915;
1428
- --muted: #5e584f;
1429
- --faint: #8a8378;
1430
- --accent: #3d4f3a;
1431
- --accent-ink: #f7f6f2;
1432
- --accent-soft: #e5ece2;
1433
- --ok: #1f7a45;
1434
- --ok-soft: #e5f4eb;
1435
- --warn: #9a6700;
1436
- --warn-soft: #fbf3df;
1437
- --err: #a33b32;
1438
- --err-soft: #fdecea;
1439
- --shadow: 0 1px 2px rgb(28 25 21 / 0.05), 0 12px 32px rgb(28 25 21 / 0.06);
1440
- --sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
2657
+ --bg-muted: #f4f4f5;
2658
+ --bg-inset: #f4f4f5;
2659
+ --line: #dedee3;
2660
+ --line-strong: #c4c4cc;
2661
+ --ink: #18181b;
2662
+ --muted: #62626b;
2663
+ --faint: #71717a;
2664
+ --accent: #27272a;
2665
+ --accent-ink: #ffffff;
2666
+ --accent-soft: #f4f4f5;
2667
+ --ok: #166534;
2668
+ --ok-soft: #f0fdf4;
2669
+ --warn: #a16207;
2670
+ --warn-soft: #fefce8;
2671
+ --err: #a82d32;
2672
+ --err-soft: #fef2f2;
2673
+ --shadow: none;
2674
+ --sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
1441
2675
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
1442
- --radius: 14px;
1443
- --header: 60px;
1444
- --nav: 220px;
2676
+ --radius: 10px;
2677
+ --header: 56px;
2678
+ --nav: 200px;
1445
2679
  }
1446
2680
  :root[data-theme="dark"] {
1447
2681
  color-scheme: dark;
1448
- --bg: #141311;
1449
- --bg-elev: #1e1c19;
1450
- --bg-muted: #26231f;
1451
- --bg-inset: #181715;
1452
- --line: #34302b;
1453
- --line-strong: #4a453d;
1454
- --ink: #f4f1ea;
1455
- --muted: #c4bdb1;
1456
- --faint: #8f877c;
1457
- --accent: #c5d5b8;
1458
- --accent-ink: #1a2118;
1459
- --accent-soft: #2a3328;
1460
- --ok: #8fd6a8;
1461
- --ok-soft: #1a2e22;
1462
- --warn: #f0cc78;
1463
- --warn-soft: #332911;
1464
- --err: #f0a8a2;
1465
- --err-soft: #3a201e;
1466
- --shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 16px 40px rgb(0 0 0 / 0.28);
2682
+ --bg: #111113;
2683
+ --bg-elev: #19191c;
2684
+ --bg-muted: #242428;
2685
+ --bg-inset: #111113;
2686
+ --line: #36363c;
2687
+ --line-strong: #52525b;
2688
+ --ink: #f4f4f5;
2689
+ --muted: #a9a9b2;
2690
+ --faint: #71717a;
2691
+ --accent: #e4e4e7;
2692
+ --accent-ink: #18181b;
2693
+ --accent-soft: #242428;
2694
+ --ok: #86efac;
2695
+ --ok-soft: #14241b;
2696
+ --warn: #fde68a;
2697
+ --warn-soft: #2a2410;
2698
+ --err: #ffaaaa;
2699
+ --err-soft: #2c1516;
2700
+ --shadow: none;
1467
2701
  }
1468
2702
  @media (prefers-color-scheme: dark) {
1469
2703
  :root:not([data-theme="light"]) {
1470
2704
  color-scheme: dark;
1471
- --bg: #141311;
1472
- --bg-elev: #1e1c19;
1473
- --bg-muted: #26231f;
1474
- --bg-inset: #181715;
1475
- --line: #34302b;
1476
- --line-strong: #4a453d;
1477
- --ink: #f4f1ea;
1478
- --muted: #c4bdb1;
1479
- --faint: #8f877c;
1480
- --accent: #c5d5b8;
1481
- --accent-ink: #1a2118;
1482
- --accent-soft: #2a3328;
1483
- --ok: #8fd6a8;
1484
- --ok-soft: #1a2e22;
1485
- --warn: #f0cc78;
1486
- --warn-soft: #332911;
1487
- --err: #f0a8a2;
1488
- --err-soft: #3a201e;
1489
- --shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 16px 40px rgb(0 0 0 / 0.28);
2705
+ --bg: #111113;
2706
+ --bg-elev: #19191c;
2707
+ --bg-muted: #242428;
2708
+ --bg-inset: #111113;
2709
+ --line: #36363c;
2710
+ --line-strong: #52525b;
2711
+ --ink: #f4f4f5;
2712
+ --muted: #a9a9b2;
2713
+ --faint: #71717a;
2714
+ --accent: #e4e4e7;
2715
+ --accent-ink: #18181b;
2716
+ --accent-soft: #242428;
2717
+ --ok: #86efac;
2718
+ --ok-soft: #14241b;
2719
+ --warn: #fde68a;
2720
+ --warn-soft: #2a2410;
2721
+ --err: #ffaaaa;
2722
+ --err-soft: #2c1516;
2723
+ --shadow: none;
1490
2724
  }
1491
2725
  }
1492
- * { box-sizing: border-box; }
1493
- html, body { margin: 0; height: 100%; }
2726
+ /* Fill the viewport, then grow with the content so the background covers the scroll. */
2727
+ html { height: 100%; }
1494
2728
  body {
2729
+ margin: 0;
2730
+ min-height: 100%;
2731
+ display: flex;
2732
+ flex-direction: column;
1495
2733
  font-family: var(--sans);
1496
2734
  background: var(--bg);
1497
2735
  color: var(--ink);
@@ -1500,24 +2738,37 @@ var renderAdminDocument = (service) => {
1500
2738
  padding-right: env(safe-area-inset-right);
1501
2739
  -webkit-text-size-adjust: 100%;
1502
2740
  }
1503
- button, input, select, textarea { font: inherit; color: inherit; }
1504
- button { cursor: pointer; }
1505
2741
  :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
1506
- @media (prefers-reduced-motion: reduce) {
1507
- * { scroll-behavior: auto !important; transition: none !important; }
1508
- }
1509
- .app { min-height: 100%; display: flex; flex-direction: column; }
2742
+ .app { flex: 1 0 auto; display: flex; flex-direction: column; min-width: 0; width: 100%; }
1510
2743
  header.top {
1511
2744
  position: sticky; top: 0; z-index: 5;
1512
2745
  display: flex; align-items: center; gap: 12px;
1513
2746
  min-height: var(--header); padding: 10px 16px;
1514
- background: color-mix(in srgb, var(--bg-elev) 92%, transparent);
2747
+ background: var(--bg-elev);
1515
2748
  border-bottom: 1px solid var(--line);
1516
- backdrop-filter: blur(10px);
1517
- }
1518
- .brand { display: flex; flex-direction: column; min-width: 0; margin-right: auto; }
1519
- .brand strong { font-size: 15px; letter-spacing: -0.01em; }
1520
- .brand span { color: var(--faint); font-size: 12px; }
2749
+ min-width: 0; max-width: 100%;
2750
+ }
2751
+ .brand {
2752
+ display: flex; flex-flow: row wrap; align-items: center; gap: 14px;
2753
+ min-width: 0; max-width: 100%; margin-right: auto;
2754
+ }
2755
+ .brand-id { display: flex; flex-direction: column; min-width: 0; flex: none; }
2756
+ .brand-id strong { font-size: 15px; letter-spacing: -0.01em; }
2757
+ .brand-id span { color: var(--faint); font-size: 12px; }
2758
+ .vendor {
2759
+ display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
2760
+ min-width: 0; max-width: 100%; font-size: 12px; color: var(--muted);
2761
+ }
2762
+ .vendor[hidden] { display: none; }
2763
+ /* Logos are drawn for a light tile, same as the docs site, so the plate stays white. */
2764
+ .vendor img {
2765
+ width: 16px; height: 16px; padding: 2px; box-sizing: content-box;
2766
+ border-radius: 4px; border: 1px solid var(--line); background: #fff; object-fit: contain;
2767
+ }
2768
+ .vendor-name { color: var(--ink); font-weight: 600; }
2769
+ .vendor-sep { color: var(--faint); }
2770
+ .vendor-link { color: inherit; text-decoration: none; }
2771
+ .vendor-link:hover { color: var(--ink); text-decoration: underline; }
1521
2772
  .controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
1522
2773
  label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
1523
2774
  label.field input, label.field select, .controls input[type="password"] {
@@ -1535,7 +2786,7 @@ var renderAdminDocument = (service) => {
1535
2786
  record or the full nav row would widen the page past the viewport. */
1536
2787
  .layout {
1537
2788
  display: grid; grid-template-columns: var(--nav) minmax(0, 1fr);
1538
- flex: 1; min-height: 0; min-width: 0;
2789
+ flex: 1 0 auto; min-width: 0;
1539
2790
  }
1540
2791
  nav.side {
1541
2792
  border-right: 1px solid var(--line); padding: 12px;
@@ -1589,8 +2840,8 @@ var renderAdminDocument = (service) => {
1589
2840
  .banner.show { display: block; }
1590
2841
  .empty { color: var(--muted); padding: 24px 8px; }
1591
2842
  dialog {
1592
- border: 1px solid var(--line); border-radius: 16px; padding: 0; background: var(--bg-elev);
1593
- color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow: var(--shadow);
2843
+ border: 1px solid var(--line); border-radius: var(--radius); padding: 0; background: var(--bg-elev);
2844
+ color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow: none;
1594
2845
  }
1595
2846
  dialog::backdrop { background: rgb(20 19 17 / 0.45); }
1596
2847
  dialog form, .dialog-body { padding: 16px; display: flex; flex-direction: column; gap: 10px; }
@@ -1617,7 +2868,7 @@ var renderAdminDocument = (service) => {
1617
2868
  padding-top: max(10px, env(safe-area-inset-top));
1618
2869
  }
1619
2870
  input, select, textarea { font-size: 16px; }
1620
- .controls { width: 100%; }
2871
+ .brand, .vendor, .controls { width: 100%; }
1621
2872
  label.field, label.field select, label.field input { flex: 1; min-width: 0; }
1622
2873
  .layout { grid-template-columns: minmax(0, 1fr); }
1623
2874
  nav.side {
@@ -1648,8 +2899,11 @@ var renderAdminDocument = (service) => {
1648
2899
  <div class="app" data-service="${name}" data-mockingbird-admin>
1649
2900
  <header class="top">
1650
2901
  <div class="brand">
1651
- <strong>${name}</strong>
1652
- <span>Mockingbird admin</span>
2902
+ <div class="brand-id">
2903
+ <strong>${name}</strong>
2904
+ <span>Admin</span>
2905
+ </div>
2906
+ <div class="vendor" id="vendor" hidden></div>
1653
2907
  </div>
1654
2908
  <div class="controls">
1655
2909
  <label class="field">Namespace
@@ -1779,460 +3033,7 @@ var renderAdminDocument = (service) => {
1779
3033
  </div>
1780
3034
  </form>
1781
3035
  </dialog>
1782
- <script>
1783
- (() => {
1784
- const STANDARD = new Set(${standard})
1785
- const state = {
1786
- namespace: "default",
1787
- key: sessionStorage.getItem("mockingbird-admin-key") || "",
1788
- view: "overview",
1789
- collection: "",
1790
- shape: null,
1791
- route: null,
1792
- editing: null
1793
- }
1794
- const $ = (id) => document.getElementById(id)
1795
- const banner = $("banner")
1796
- const keyInput = $("admin-key")
1797
- keyInput.value = state.key
1798
- const params = new URLSearchParams(location.search)
1799
- if (params.get("key")) {
1800
- state.key = params.get("key")
1801
- keyInput.value = state.key
1802
- sessionStorage.setItem("mockingbird-admin-key", state.key)
1803
- params.delete("key")
1804
- const next = params.toString()
1805
- history.replaceState(null, "", location.pathname + (next ? "?" + next : ""))
1806
- }
1807
- if (params.get("namespace")) state.namespace = params.get("namespace")
1808
-
1809
- const showError = (error) => {
1810
- banner.textContent = error instanceof Error ? error.message : String(error)
1811
- banner.classList.add("show")
1812
- }
1813
- const clearError = () => banner.classList.remove("show")
1814
-
1815
- async function originGet(path) {
1816
- const response = await fetch(path, { headers: { accept: "application/json" } })
1817
- if (!response.ok) throw new Error(response.status + " " + path)
1818
- return response.json()
1819
- }
1820
- async function api(method, path, body) {
1821
- const headers = { accept: "application/json" }
1822
- if (state.key) headers["${ADMIN_KEY_HEADER}"] = state.key
1823
- if (state.namespace) headers["x-mockingbird-namespace"] = state.namespace
1824
- if (body !== undefined) headers["content-type"] = "application/json"
1825
- const response = await fetch("${ADMIN_PREFIX}" + path, {
1826
- method,
1827
- headers,
1828
- body: body === undefined ? undefined : JSON.stringify(body)
1829
- })
1830
- const text = await response.text()
1831
- let data = null
1832
- if (text) {
1833
- try { data = JSON.parse(text) } catch { data = { raw: text } }
1834
- }
1835
- if (response.status === 401) {
1836
- keyInput.focus()
1837
- throw new Error((data && data.error && data.error.message) || "Admin key required")
1838
- }
1839
- if (!response.ok) {
1840
- throw new Error((data && data.error && data.error.message) || (response.status + " " + method + " " + path))
1841
- }
1842
- return data
1843
- }
1844
- const panelApi = {
1845
- get: (path) => api("GET", path),
1846
- send: (method, path, body) => api(method, path, body),
1847
- namespace: () => state.namespace
1848
- }
1849
-
1850
- function setTheme(theme) {
1851
- if (theme) document.documentElement.dataset.theme = theme
1852
- else delete document.documentElement.dataset.theme
1853
- }
1854
- const storedTheme = localStorage.getItem("mockingbird-admin-theme")
1855
- if (storedTheme) setTheme(storedTheme)
1856
- $("theme").addEventListener("click", () => {
1857
- const current = document.documentElement.dataset.theme
1858
- const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches
1859
- const next = dark ? "light" : "dark"
1860
- localStorage.setItem("mockingbird-admin-theme", next)
1861
- setTheme(next)
1862
- })
1863
-
1864
- function show(view) {
1865
- state.view = view
1866
- for (const button of document.querySelectorAll("#nav button")) {
1867
- button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false")
1868
- }
1869
- for (const section of document.querySelectorAll("main .view")) {
1870
- section.hidden = section.id !== "view-" + view
1871
- }
1872
- }
1873
- document.getElementById("nav").addEventListener("click", (event) => {
1874
- const button = event.target.closest("button[data-view]")
1875
- if (!button) return
1876
- show(button.dataset.view)
1877
- refresh().catch(showError)
1878
- })
1879
-
1880
- function cards(items) {
1881
- return '<div class="grid">' + items.map((item) =>
1882
- '<article class="card"><div class="k">' + item.k + '</div><div class="v">' + item.v + '</div></article>'
1883
- ).join("") + "</div>"
1884
- }
1885
- function table(columns, rows) {
1886
- if (rows.length === 0) return '<p class="empty">Nothing here yet.</p>'
1887
- const head = "<tr>" + columns.map((column) => "<th>" + column.label + "</th>").join("") + "</tr>"
1888
- const body = rows.map((row) => "<tr" + (row.attrs || "") + ">" + columns.map((column) =>
1889
- '<td data-label="' + column.label + '">' + column.cell(row) + "</td>"
1890
- ).join("") + "</tr>").join("")
1891
- return "<table><thead>" + head + "</thead><tbody>" + body + "</tbody></table>"
1892
- }
1893
- const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({
1894
- "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"
1895
- }[char]))
1896
- const preview = (value) => {
1897
- const text = JSON.stringify(value)
1898
- return esc(text.length > 180 ? text.slice(0, 180) + "\u2026" : text)
1899
- }
1900
- const when = (epoch) => {
1901
- const date = new Date(epoch)
1902
- return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString()
1903
- }
1904
- const clockCard = (epoch) => {
1905
- const date = new Date(epoch)
1906
- if (Number.isNaN(date.getTime())) return esc(String(epoch))
1907
- const iso = date.toISOString()
1908
- return esc(iso.slice(0, 10)) + '<span class="clock-time">' + esc(iso.slice(11, 19)) + " UTC</span>"
1909
- }
1910
-
1911
- async function loadNamespaces() {
1912
- const data = await api("GET", "/namespaces")
1913
- const select = $("namespace")
1914
- const names = data.namespaces && data.namespaces.length ? data.namespaces : [data.default || "default"]
1915
- if (!names.includes(state.namespace)) names.unshift(state.namespace)
1916
- select.innerHTML = names.map((name) =>
1917
- '<option value="' + esc(name) + '">' + esc(name) + "</option>"
1918
- ).join("")
1919
- select.value = state.namespace
1920
- }
1921
-
1922
- async function renderOverview() {
1923
- const [health, shape, clock, journal] = await Promise.all([
1924
- originGet("/health"),
1925
- api("GET", "/state"),
1926
- api("GET", "/clock"),
1927
- api("GET", "/requests?limit=8")
1928
- ])
1929
- state.shape = shape
1930
- $("overview-cards").innerHTML = cards([
1931
- { k: "Service", v: esc(health.service || "${name}") },
1932
- { k: "Status", v: esc(health.status || "ok") },
1933
- { k: "Namespace", v: esc(state.namespace) },
1934
- { k: "Collections", v: String(shape.collections.length) },
1935
- { k: "Records", v: String(shape.collections.reduce((sum, item) => sum + item.count, 0)) },
1936
- { k: "Clock", v: clockCard(clock.now) }
1937
- ])
1938
- const requests = (journal && journal.requests) || []
1939
- $("overview-requests").innerHTML = table(
1940
- [
1941
- { label: "Method", cell: (row) => esc(row.method) },
1942
- { label: "Path", cell: (row) => esc(row.path) },
1943
- { label: "Status", cell: (row) => '<span class="status-' + String(row.status).charAt(0) + '">' + esc(row.status) + "</span>" },
1944
- { label: "Operation", cell: (row) => esc(row.operationId || "") }
1945
- ],
1946
- requests
1947
- )
1948
- }
1949
-
1950
- function schemaChips(collection) {
1951
- if (!collection || collection.fields.length === 0) {
1952
- return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>'
1953
- }
1954
- return '<div class="chips">' + collection.fields.map((field) =>
1955
- '<span class="chip">' + esc(field.name) + ' <i>' + esc(field.kind) + (field.optional ? "?" : "") + "</i></span>"
1956
- ).join("") + "</div>"
1957
- }
1958
-
1959
- async function renderState() {
1960
- const shape = await api("GET", "/state")
1961
- state.shape = shape
1962
- const host = $("collections")
1963
- if (shape.collections.length === 0) {
1964
- host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>'
1965
- $("schema").innerHTML = ""
1966
- $("records").innerHTML = ""
1967
- return
1968
- }
1969
- if (!shape.collections.some((item) => item.name === state.collection)) {
1970
- state.collection = shape.collections[0].name
1971
- }
1972
- host.innerHTML = shape.collections.map((item) =>
1973
- '<button type="button" class="collection" data-collection="' + esc(item.name) + '" aria-current="' +
1974
- (item.name === state.collection ? "true" : "false") + '"><strong>' + esc(item.label) +
1975
- '</strong><small>' + esc(item.count) + " \xB7 " + esc(item.source) + "</small></button>"
1976
- ).join("")
1977
- const selected = shape.collections.find((item) => item.name === state.collection)
1978
- $("schema").innerHTML = "<h3>" + esc(selected.label) + "</h3>" +
1979
- (selected.description ? "<p class=" + '"lede">' + esc(selected.description) + "</p>" : "") +
1980
- schemaChips(selected)
1981
- const page = await api("GET", "/state/" + encodeURIComponent(state.collection) + "?limit=50")
1982
- const rows = page.records.map((record) => ({
1983
- ...record,
1984
- attrs: ' class="clickable" data-id="' + esc(record.id) + '"'
1985
- }))
1986
- $("records").innerHTML = table(
1987
- [
1988
- { label: "Id", cell: (row) => "<code>" + esc(row.id) + "</code>" },
1989
- { label: "Seq", cell: (row) => esc(row.seq) },
1990
- { label: "Value", cell: (row) => preview(row.value) }
1991
- ],
1992
- rows
1993
- )
1994
- }
1995
-
1996
- async function renderClock() {
1997
- const clock = await api("GET", "/clock")
1998
- $("clock-readout").innerHTML = "<div class='k'>Now</div><div class='v'>" + esc(when(clock.now)) +
1999
- "</div><p>" + esc(clock.now) + " ms \xB7 offset " + esc(clock.offsetMs) + " ms " +
2000
- (clock.frozen ? "<span class='badge warn'>frozen</span>" : "<span class='badge ok'>live</span>") + "</p>"
2001
- $("freeze").textContent = clock.frozen ? "Unfreeze" : "Freeze"
2002
- $("freeze").dataset.frozen = clock.frozen ? "1" : "0"
2003
- }
2004
-
2005
- async function renderFaults() {
2006
- const [faults, presets] = await Promise.all([
2007
- api("GET", "/faults"),
2008
- api("GET", "/faults/presets").catch(() => ({ presets: [] }))
2009
- ])
2010
- const select = $("fault-preset")
2011
- const current = select.value
2012
- const list = presets.presets || []
2013
- select.innerHTML = '<option value="">Custom rule</option>' + list.map((preset) =>
2014
- '<option value="' + esc(preset.name) + '">' + esc(preset.name) + "</option>"
2015
- ).join("")
2016
- select.value = current
2017
- const rules = faults.faults || []
2018
- $("fault-list").innerHTML = table(
2019
- [
2020
- { label: "Id", cell: (row) => esc(row.id) },
2021
- { label: "Match", cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter(Boolean).join(" ") || "all") },
2022
- { label: "Effect", cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || "")) }
2023
- ],
2024
- rules
2025
- )
2026
- }
2027
-
2028
- async function renderJournal() {
2029
- const journal = await api("GET", "/requests?limit=100")
2030
- $("journal").innerHTML = table(
2031
- [
2032
- { label: "When", cell: (row) => esc(row.at || "") },
2033
- { label: "Method", cell: (row) => esc(row.method) },
2034
- { label: "Path", cell: (row) => esc(row.path) },
2035
- { label: "Status", cell: (row) => esc(row.status) },
2036
- { label: "ms", cell: (row) => esc(row.durationMs) }
2037
- ],
2038
- journal.requests || []
2039
- )
2040
- }
2041
-
2042
- async function renderRoutes() {
2043
- const data = await api("GET", "/")
2044
- const routes = data.routes || []
2045
- $("route-list").innerHTML = routes.map((route) => {
2046
- const extra = STANDARD.has(route) ? "" : " <span class='badge'>extension</span>"
2047
- return '<div class="route"><span class="method">' + esc(route.split(" ")[0]) +
2048
- '</span><span>' + esc(route.split(" ").slice(1).join(" ")) + extra +
2049
- '</span><button class="btn" type="button" data-route="' + esc(route) + '">Use</button></div>'
2050
- }).join("")
2051
- }
2052
-
2053
- const renderers = {
2054
- overview: renderOverview,
2055
- state: renderState,
2056
- clock: renderClock,
2057
- faults: renderFaults,
2058
- journal: renderJournal,
2059
- routes: renderRoutes
2060
- }
2061
-
2062
- async function refresh() {
2063
- clearError()
2064
- if (state.view in renderers) await renderers[state.view]()
2065
- }
2066
-
2067
- $("namespace").addEventListener("change", () => {
2068
- state.namespace = $("namespace").value
2069
- refresh().catch(showError)
2070
- })
2071
- keyInput.addEventListener("change", () => {
2072
- state.key = keyInput.value.trim()
2073
- sessionStorage.setItem("mockingbird-admin-key", state.key)
2074
- refresh().catch(showError)
2075
- })
2076
- $("refresh").addEventListener("click", () => refresh().catch(showError))
2077
-
2078
- $("collections").addEventListener("click", (event) => {
2079
- const button = event.target.closest("[data-collection]")
2080
- if (!button) return
2081
- state.collection = button.dataset.collection
2082
- renderState().catch(showError)
2083
- })
2084
- $("records").addEventListener("click", (event) => {
2085
- const row = event.target.closest("[data-id]")
2086
- if (!row) return
2087
- openEditor(row.dataset.id).catch(showError)
2088
- })
2089
-
2090
- const editor = $("editor")
2091
- async function openEditor(id) {
2092
- state.editing = id || null
2093
- $("editor-title").textContent = id ? "Edit record" : "New record"
2094
- $("editor-id").value = id || ""
2095
- $("editor-id").readOnly = Boolean(id)
2096
- $("editor-delete").hidden = !id
2097
- if (id) {
2098
- const record = await api("GET", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
2099
- $("editor-value").value = JSON.stringify(record.value, null, 2)
2100
- } else {
2101
- $("editor-value").value = "{\\n \\n}"
2102
- }
2103
- editor.showModal()
2104
- }
2105
- $("new-record").addEventListener("click", () => openEditor(null).catch(showError))
2106
- $("editor-form").addEventListener("submit", (event) => {
2107
- const submitter = event.submitter
2108
- if (!submitter || submitter.value !== "save") return
2109
- event.preventDefault()
2110
- let value
2111
- try { value = JSON.parse($("editor-value").value) }
2112
- catch { showError(new Error("Value is not JSON")); return }
2113
- const id = $("editor-id").value.trim()
2114
- const path = "/state/" + encodeURIComponent(state.collection)
2115
- const request = state.editing
2116
- ? api("PUT", path + "/" + encodeURIComponent(id), { value })
2117
- : api("POST", path, { id: id || undefined, value })
2118
- request.then(() => { editor.close(); return renderState() }).catch(showError)
2119
- })
2120
- $("editor-delete").addEventListener("click", () => {
2121
- const id = state.editing
2122
- if (!id) return
2123
- api("DELETE", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
2124
- .then(() => { editor.close(); return renderState() })
2125
- .catch(showError)
2126
- })
2127
-
2128
- document.querySelectorAll("[data-advance]").forEach((button) => {
2129
- button.addEventListener("click", () => {
2130
- api("POST", "/clock", { advance: Number(button.dataset.advance) }).then(renderClock).catch(showError)
2131
- })
2132
- })
2133
- $("freeze").addEventListener("click", () => {
2134
- const frozen = $("freeze").dataset.frozen === "1"
2135
- api("POST", "/clock", { freeze: !frozen }).then(renderClock).catch(showError)
2136
- })
2137
- $("clock-reset").addEventListener("click", () => {
2138
- api("POST", "/clock", { reset: true }).then(renderClock).catch(showError)
2139
- })
2140
- $("clock-set").addEventListener("submit", (event) => {
2141
- event.preventDefault()
2142
- const raw = $("clock-instant").value.trim()
2143
- const set = /^-?\\d+$/.test(raw) ? Number(raw) : raw
2144
- api("POST", "/clock", { set }).then(renderClock).catch(showError)
2145
- })
2146
-
2147
- $("fault-form").addEventListener("submit", (event) => {
2148
- event.preventDefault()
2149
- const preset = $("fault-preset").value
2150
- const body = preset
2151
- ? { preset }
2152
- : {
2153
- status: Number($("fault-status").value),
2154
- ...($("fault-path").value ? { pathPrefix: $("fault-path").value } : {}),
2155
- ...($("fault-method").value ? { method: $("fault-method").value } : {})
2156
- }
2157
- if (!preset && !Number.isFinite(body.status)) {
2158
- showError(new Error("Status must be a number, or choose a preset"))
2159
- return
2160
- }
2161
- api("POST", "/faults", body).then(renderFaults).catch(showError)
2162
- })
2163
- $("fault-clear").addEventListener("click", () => api("DELETE", "/faults").then(renderFaults).catch(showError))
2164
- $("journal-clear").addEventListener("click", () => api("DELETE", "/requests").then(renderJournal).catch(showError))
2165
-
2166
- $("route-list").addEventListener("click", (event) => {
2167
- const button = event.target.closest("[data-route]")
2168
- if (!button) return
2169
- state.route = button.dataset.route
2170
- $("route-title").textContent = state.route
2171
- const method = state.route.split(" ")[0]
2172
- $("route-body").disabled = method === "GET" || method === "DELETE"
2173
- })
2174
- $("route-form").addEventListener("submit", async (event) => {
2175
- event.preventDefault()
2176
- if (!state.route) return showError(new Error("Choose a route first"))
2177
- const [method, path] = [state.route.split(" ")[0], state.route.split(" ").slice(1).join(" ")]
2178
- const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_, key) => prompt("Value for " + key) || "")
2179
- let body
2180
- if (method !== "GET" && method !== "DELETE" && $("route-body").value.trim()) {
2181
- try { body = JSON.parse($("route-body").value) }
2182
- catch { return showError(new Error("Body is not JSON")) }
2183
- }
2184
- try {
2185
- const data = await api(method, concrete, body)
2186
- $("route-result").textContent = JSON.stringify(data, null, 2)
2187
- } catch (error) {
2188
- showError(error)
2189
- }
2190
- })
2191
-
2192
- function mountPanels(panels) {
2193
- const nav = $("nav")
2194
- const views = $("views")
2195
- for (const panel of panels) {
2196
- const button = document.createElement("button")
2197
- button.type = "button"
2198
- button.dataset.view = panel.id
2199
- button.textContent = panel.title
2200
- nav.append(button)
2201
- const section = document.createElement("section")
2202
- section.className = "view"
2203
- section.id = "view-" + panel.id
2204
- section.hidden = true
2205
- const title = document.createElement("h2")
2206
- title.textContent = panel.title
2207
- const lede = document.createElement("p")
2208
- lede.className = "lede"
2209
- lede.textContent = panel.description || ""
2210
- const body = document.createElement("div")
2211
- body.className = "panel-body"
2212
- body.id = "panel-" + panel.id
2213
- body.innerHTML = panel.html || ""
2214
- section.append(title, lede, body)
2215
- views.append(section)
2216
- if (panel.script) {
2217
- try {
2218
- const run = new Function("root", "api", panel.script)
2219
- run(body, panelApi)
2220
- } catch (error) {
2221
- const note = document.createElement("p")
2222
- note.className = "banner show"
2223
- note.textContent = error instanceof Error ? error.message : String(error)
2224
- body.append(note)
2225
- }
2226
- }
2227
- }
2228
- }
2229
-
2230
- loadNamespaces()
2231
- .then(() => api("GET", "/ui/manifest"))
2232
- .then((manifest) => { mountPanels((manifest && manifest.panels) || []); return refresh() })
2233
- .catch(showError)
2234
- })()
2235
- </script>
3036
+ ${script}
2236
3037
  </body>
2237
3038
  </html>`;
2238
3039
  };
@@ -2249,10 +3050,10 @@ var json2 = (body) => new Response(JSON.stringify(body), {
2249
3050
  });
2250
3051
  var adminUiRoutes = (service, ui) => {
2251
3052
  const shell = () => renderAdminDocument(service);
2252
- const document2 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
3053
+ const document3 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
2253
3054
  return {
2254
- "GET /ui": () => htmlResponse(document2()),
2255
- "GET /ui/": () => htmlResponse(document2()),
3055
+ "GET /ui": () => htmlResponse(document3()),
3056
+ "GET /ui/": () => htmlResponse(document3()),
2256
3057
  "GET /ui/manifest": () => json2({
2257
3058
  service,
2258
3059
  panels: (ui?.panels ?? []).map((panel) => ({
@@ -2278,11 +3079,11 @@ var EXTENSION_KEYS = {
2278
3079
  };
2279
3080
 
2280
3081
  // ../../openapi/metadata/dist/read.js
2281
- var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3082
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2282
3083
  var extensionOf = (holder, key) => holder[key];
2283
3084
  var operationMetadata = (operation) => {
2284
3085
  const raw = extensionOf(operation, EXTENSION_KEYS.operation);
2285
- const ext = isRecord2(raw) ? raw : {};
3086
+ const ext = isRecord3(raw) ? raw : {};
2286
3087
  const supported = ext.supported ?? true;
2287
3088
  const parity = ext.parity ?? {};
2288
3089
  return {
@@ -2402,9 +3203,9 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
2402
3203
  this.name = "OperationRegistryError";
2403
3204
  }
2404
3205
  };
2405
- var verifyOperations = (document2, handlers) => {
3206
+ var verifyOperations = (document3, handlers) => {
2406
3207
  const problems = [];
2407
- const operations = listOperations(document2);
3208
+ const operations = listOperations(document3);
2408
3209
  const seen = /* @__PURE__ */ new Set();
2409
3210
  for (const operation of operations) {
2410
3211
  if (seen.has(operation.operationId))
@@ -2520,7 +3321,7 @@ var SAMPLE = 25;
2520
3321
  var WALK_LIMIT = 1e4;
2521
3322
  var SKIP_CTORS = /* @__PURE__ */ new Set(["Hono", "HonoRequest", "Router"]);
2522
3323
  var fail = (status, message) => ({ ok: false, status, message });
2523
- var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3324
+ var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2524
3325
  var kindOf = (value) => {
2525
3326
  if (value === null)
2526
3327
  return "null";
@@ -2619,7 +3420,7 @@ var absorb = (accum, value) => {
2619
3420
  const kind = kindOf(value);
2620
3421
  accum.kinds.add(kind);
2621
3422
  accum.present++;
2622
- if (!isRecord3(value))
3423
+ if (!isRecord4(value))
2623
3424
  return;
2624
3425
  for (const [key, child] of Object.entries(value)) {
2625
3426
  const nested = accum.nested.get(key) ?? blankAccum(key);
@@ -2644,7 +3445,7 @@ var inferFields = (records) => {
2644
3445
  if (records.length === 0)
2645
3446
  return [];
2646
3447
  const sample = records.slice(0, SAMPLE);
2647
- const objects = sample.filter((record2) => isRecord3(record2.value));
3448
+ const objects = sample.filter((record2) => isRecord4(record2.value));
2648
3449
  if (objects.length === 0) {
2649
3450
  const kinds = new Set(sample.map((record2) => kindOf(record2.value)));
2650
3451
  const list = [...kinds];
@@ -2831,7 +3632,7 @@ var patchStateRecord = (scope, name, id, value) => {
2831
3632
  const parsed = jsonValue(value);
2832
3633
  if (!parsed.ok)
2833
3634
  return parsed;
2834
- const next = isRecord3(current.value.value) && isRecord3(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
3635
+ const next = isRecord4(current.value.value) && isRecord4(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
2835
3636
  return replaceStateRecord(scope, name, id, next);
2836
3637
  };
2837
3638
  var deleteStateRecord = (scope, name, id) => {
@@ -2899,7 +3700,7 @@ var stateAdminRoutes = (routes) => {
2899
3700
  const scope = open(namespace);
2900
3701
  if (!scope.ok)
2901
3702
  return fromResult(scope, 201);
2902
- if (!isRecord3(body) || !("value" in body)) {
3703
+ if (!isRecord4(body) || !("value" in body)) {
2903
3704
  return adminError2(400, 'expected {"id?": "<id>", "value": <json>}');
2904
3705
  }
2905
3706
  const id = body.id;
@@ -2917,7 +3718,7 @@ var stateAdminRoutes = (routes) => {
2917
3718
  const scope = open(namespace);
2918
3719
  if (!scope.ok)
2919
3720
  return fromResult(scope, 200);
2920
- if (!isRecord3(body) || !("value" in body))
3721
+ if (!isRecord4(body) || !("value" in body))
2921
3722
  return adminError2(400, 'expected {"value": <json>}');
2922
3723
  const replaced = replaceStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
2923
3724
  if (replaced.ok)
@@ -2928,7 +3729,7 @@ var stateAdminRoutes = (routes) => {
2928
3729
  const scope = open(namespace);
2929
3730
  if (!scope.ok)
2930
3731
  return fromResult(scope, 200);
2931
- if (!isRecord3(body) || !("value" in body))
3732
+ if (!isRecord4(body) || !("value" in body))
2932
3733
  return adminError2(400, 'expected {"value": <json>}');
2933
3734
  const patched = patchStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
2934
3735
  if (patched.ok)
@@ -2948,7 +3749,7 @@ var stateAdminRoutes = (routes) => {
2948
3749
  };
2949
3750
 
2950
3751
  // ../core/dist/version.js
2951
- var PACKAGE_VERSION = true ? "1.5.0" : UNRELEASED_VERSION;
3752
+ var PACKAGE_VERSION = true ? "1.6.0" : UNRELEASED_VERSION;
2952
3753
 
2953
3754
  // ../core/dist/signing.js
2954
3755
  var encoder = new TextEncoder();
@@ -3285,9 +4086,9 @@ var createWebhookHub = (options) => {
3285
4086
  };
3286
4087
  var json4 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
3287
4088
  var adminError3 = (status, message) => json4(status, { error: { type: "mockingbird_admin", message } });
3288
- var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4089
+ var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3289
4090
  var parseEndpoint = (value) => {
3290
- if (!isRecord4(value) || typeof value.url !== "string")
4091
+ if (!isRecord5(value) || typeof value.url !== "string")
3291
4092
  return "each endpoint needs a url";
3292
4093
  try {
3293
4094
  new URL(value.url);
@@ -3304,12 +4105,12 @@ var parseEndpoint = (value) => {
3304
4105
  const events = value.events ?? value.enabledEvents;
3305
4106
  if (Array.isArray(events))
3306
4107
  endpoint.events = events.map(String);
3307
- if (isRecord4(value.tags)) {
4108
+ if (isRecord5(value.tags)) {
3308
4109
  endpoint.tags = Object.fromEntries(Object.entries(value.tags).map(([k, v]) => [k, String(v)]));
3309
4110
  }
3310
4111
  if (typeof value.account === "string")
3311
4112
  endpoint.tags = { ...endpoint.tags, account: value.account };
3312
- if (isRecord4(value.headers)) {
4113
+ if (isRecord5(value.headers)) {
3313
4114
  endpoint.headers = Object.fromEntries(Object.entries(value.headers).map(([k, v]) => [k, String(v)]));
3314
4115
  }
3315
4116
  return endpoint;
@@ -3336,7 +4137,7 @@ var webhookAdminRoutes = (hub) => ({
3336
4137
  return json4(200, { status: "ok" });
3337
4138
  },
3338
4139
  "POST /webhooks/faults": ({ body, namespace }) => {
3339
- if (!isRecord4(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
4140
+ if (!isRecord5(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
3340
4141
  return adminError3(400, "mode must be duplicate, reorder or drop");
3341
4142
  }
3342
4143
  const fault = { mode: body.mode };
@@ -3352,7 +4153,7 @@ var webhookAdminRoutes = (hub) => ({
3352
4153
  }))
3353
4154
  }),
3354
4155
  "PUT /webhook-endpoints": ({ body, namespace }) => {
3355
- const list = Array.isArray(body) ? body : isRecord4(body) ? body.endpoints : void 0;
4156
+ const list = Array.isArray(body) ? body : isRecord5(body) ? body.endpoints : void 0;
3356
4157
  if (!Array.isArray(list))
3357
4158
  return adminError3(400, "expected [{url, secret?, events?}]");
3358
4159
  const parsed = [];
@@ -3422,8 +4223,8 @@ var DroppedConnectionError = class extends TypeError {
3422
4223
  this.name = "TypeError";
3423
4224
  }
3424
4225
  };
3425
- var operationMatcher = (document2) => {
3426
- const matchers = listOperations(document2).map((operation) => ({
4226
+ var operationMatcher = (document3) => {
4227
+ const matchers = listOperations(document3).map((operation) => ({
3427
4228
  operationId: operation.operationId,
3428
4229
  method: operation.method.toUpperCase(),
3429
4230
  pattern: new RegExp(`^${operation.path.split("/").map((segment) => segment.startsWith("{") ? "[^/]+" : segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("/")}/?$`),
@@ -3518,10 +4319,10 @@ var createRuntime = (options) => {
3518
4319
  const ensureBranch = (namespace, branch2, at) => {
3519
4320
  if (!BRANCH_PATTERN2.test(branch2))
3520
4321
  throw new RangeError(`branch must match ${BRANCH_PATTERN2}`);
3521
- const history = timeline(namespace);
4322
+ const history2 = timeline(namespace);
3522
4323
  if (branch2 === "main") {
3523
4324
  if (at !== void 0) {
3524
- const point = history.checkout("main", at);
4325
+ const point = history2.checkout("main", at);
3525
4326
  restoreNamespace(sqlite, storageNamespace(namespace), point.value.snapshot);
3526
4327
  captured.set(namespace, point.value.snapshot);
3527
4328
  rng.setState(point.value.rngState);
@@ -3534,10 +4335,10 @@ var createRuntime = (options) => {
3534
4335
  return namespace;
3535
4336
  }
3536
4337
  const storage = physicalBranch(namespace, branch2);
3537
- if (!history.hasBranch(branch2)) {
4338
+ if (!history2.hasBranch(branch2)) {
3538
4339
  if (at === void 0)
3539
- history.commit(capture(namespace));
3540
- const point = history.fork(branch2, at === void 0 ? {} : { from: at });
4340
+ history2.commit(capture(namespace));
4341
+ const point = history2.fork(branch2, at === void 0 ? {} : { from: at });
3541
4342
  const branchRng = createRng(options.seed ?? 0);
3542
4343
  if (point)
3543
4344
  branchRng.setState(point.value.rngState);
@@ -3546,8 +4347,8 @@ var createRuntime = (options) => {
3546
4347
  restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
3547
4348
  if (point)
3548
4349
  captured.set(storage, point.value.snapshot);
3549
- } else if (at !== void 0 && history.head(branch2)?.id !== at) {
3550
- const point = history.checkout(branch2, at);
4350
+ } else if (at !== void 0 && history2.head(branch2)?.id !== at) {
4351
+ const point = history2.checkout(branch2, at);
3551
4352
  if (!instances.has(storage)) {
3552
4353
  const branchRng = createRng(options.seed ?? 0);
3553
4354
  branchRng.setState(point.value.rngState);
@@ -3558,7 +4359,7 @@ var createRuntime = (options) => {
3558
4359
  captured.set(storage, point.value.snapshot);
3559
4360
  } else {
3560
4361
  if (!instances.has(storage)) {
3561
- const point = history.head(branch2);
4362
+ const point = history2.head(branch2);
3562
4363
  const branchRng = createRng(options.seed ?? 0);
3563
4364
  if (point)
3564
4365
  branchRng.setState(point.value.rngState);
@@ -3582,8 +4383,8 @@ var createRuntime = (options) => {
3582
4383
  const checkout = (checkpointId, checkoutOptions = {}) => {
3583
4384
  const namespace = checkoutOptions.namespace ?? DEFAULT_NAMESPACE;
3584
4385
  const branchName = checkoutOptions.branch ?? "main";
3585
- const history = timeline(namespace);
3586
- const point = history.checkout(branchName, checkpointId);
4386
+ const history2 = timeline(namespace);
4387
+ const point = history2.checkout(branchName, checkpointId);
3587
4388
  const storage = ensureBranch(namespace, branchName);
3588
4389
  restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
3589
4390
  captured.set(storage, point.value.snapshot);
@@ -3633,9 +4434,9 @@ var createRuntime = (options) => {
3633
4434
  instance(name);
3634
4435
  restoreNamespace(sqlite, storageNamespace(name), from);
3635
4436
  captured.set(name, from);
3636
- const history = timelines.get(name);
3637
- if (history)
3638
- history.commit(capture(name), { branch: "main" });
4437
+ const history2 = timelines.get(name);
4438
+ if (history2)
4439
+ history2.commit(capture(name), { branch: "main" });
3639
4440
  else
3640
4441
  timeline(name);
3641
4442
  };
@@ -3864,10 +4665,10 @@ var createRuntime = (options) => {
3864
4665
  },
3865
4666
  release: (name, checkpointId) => timeline(name).release(checkpointId),
3866
4667
  inspect: (name) => {
3867
- const history = timeline(name);
4668
+ const history2 = timeline(name);
3868
4669
  return {
3869
- branches: history.branches(),
3870
- checkpoints: history.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
4670
+ branches: history2.branches(),
4671
+ checkpoints: history2.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
3871
4672
  id,
3872
4673
  branch: branchName,
3873
4674
  parent,
@@ -3882,11 +4683,11 @@ var createRuntime = (options) => {
3882
4683
  } : {},
3883
4684
  routes: {
3884
4685
  ...credentialRoutes(credentials),
3885
- ...options.presets ? presetRoutes(options.presets, runtime) : {},
3886
4686
  ...options.webhooks ? webhookAdminRoutes(options.webhooks) : {},
3887
4687
  ...options.admin?.(runtime) ?? {},
3888
- // Standard state and UI routes win a colliding key. A service adds its own keys
3889
- // beside these; it does not replace `/state` or `/ui`.
4688
+ // Shell routes win a colliding key. Fault presets are one of them: the admin page
4689
+ // maps `presets`, and a service route must not replace that list with a record.
4690
+ ...options.presets ? presetRoutes(options.presets, runtime) : {},
3890
4691
  ...stateAdminRoutes({
3891
4692
  open: stateScope,
3892
4693
  afterWrite: (namespace) => {
@@ -3961,9 +4762,12 @@ var credentialRoutes = (registry) => ({
3961
4762
  }
3962
4763
  });
3963
4764
  var presetRoutes = (presets, runtime) => ({
3964
- "GET /faults/presets": () => adminJson(200, {
3965
- presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
3966
- }),
4765
+ "GET /faults/presets": () => {
4766
+ const body = {
4767
+ presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
4768
+ };
4769
+ return adminJson(200, body);
4770
+ },
3967
4771
  "POST /faults/presets/:name": ({ params, body, namespace }) => {
3968
4772
  const name = params.name;
3969
4773
  if (!presets[name])
@@ -3974,7 +4778,7 @@ var presetRoutes = (presets, runtime) => ({
3974
4778
  });
3975
4779
 
3976
4780
  // src/generated/openapi.ts
3977
- var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"RxVortex (Strive) pharmacy API (Mockingbird subset)","description":"Stateful mock subset of the RxVortex / Strive compounding-pharmacy API: client-credentials\\ntoken, order submit, status, cancel, lookup by sender order id (recovery), and the preset\\ncatalog. Hand-authored from the consumer's adapters (the vendor publishes no spec).\\n","version":"1","x-mockingbird-upstream":{"note":"Hand-authored from the wire shapes our consumer reads and writes (rxvortex-auth.service.ts, rxvortex-fulfillment.adapter.ts, rxvortex-live.client.ts)."}},"servers":[{"url":"https://sandbox.rxvortex.com"}],"security":[{"bearerAuth":[]}],"paths":{"/api/v1/generate-access-token":{"post":{"operationId":"GenerateAccessToken","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["client_id","client_secret"],"properties":{"client_id":{"type":"string","minLength":1,"maxLength":64},"client_secret":{"type":"string","minLength":1,"maxLength":64}}}}}},"responses":{"200":{"description":"Access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"401":{"description":"Bad credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}}}},"/api/v1/orders":{"post":{"operationId":"CreateOrder","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrderBody"}}}},"responses":{"200":{"description":"Order accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrderResponse"}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"Duplicate sender_order_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}}}},"/api/v1/orders/{orderId}":{"parameters":[{"name":"orderId","in":"path","required":true,"description":"The vendor order tracking id, or the sender order id (our payment id).","schema":{"type":"string","x-mockingbird-resource-ref":{"type":"order","missing":"RXV-00000000-MISSING"}}}],"get":{"operationId":"GetOrder","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Order status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderStatus"}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Unknown order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}},"delete":{"operationId":"CancelOrder","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"200":{"description":"Cancelled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelResponse"}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Unknown order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"Not cancellable in its current status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/preset-catalog-items":{"get":{"operationId":"ListPresetCatalogItems","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Preset catalog","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CatalogItem"}}}}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"TokenResponse":{"type":"object","required":["access_token","token_type","expires_in"],"properties":{"access_token":{"type":"string","x-mockingbird-volatile":{"kind":"token"}},"token_type":{"type":"string","enum":["Bearer"]},"expires_in":{"type":"integer"}}},"Address":{"type":"object","required":["line1","city","state","postal_code","country"],"properties":{"line1":{"type":"string","minLength":1,"maxLength":80},"line2":{"type":"string","maxLength":80},"city":{"type":"string","minLength":1,"maxLength":40},"state":{"type":"string","pattern":"^[A-Z]{2}$"},"postal_code":{"type":"string","pattern":"^[0-9]{5}(-[0-9]{4})?$"},"country":{"type":"string","enum":["US"]}}},"Patient":{"type":"object","required":["sender_patient_id","first_name","last_name","dob","gender","email","phone","address"],"properties":{"sender_patient_id":{"type":"string","minLength":1,"maxLength":64},"first_name":{"type":"string","minLength":1,"maxLength":40},"last_name":{"type":"string","minLength":1,"maxLength":40},"dob":{"type":"string","pattern":"^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$"},"gender":{"type":"string","enum":["male","female"]},"email":{"type":"string","format":"email","maxLength":120},"phone":{"type":"string","pattern":"^[0-9]{3}-[0-9]{3}-[0-9]{4}$"},"address":{"$ref":"#/components/schemas/Address"}}},"Prescriber":{"type":"object","required":["first_name","last_name","npi"],"properties":{"first_name":{"type":"string","minLength":1,"maxLength":40},"last_name":{"type":"string","minLength":1,"maxLength":40},"npi":{"type":"string","pattern":"^[0-9]{10}$"},"dea_number":{"type":"string","maxLength":20},"license_number":{"type":"string","maxLength":20},"license_state":{"type":"string","maxLength":2},"phone":{"type":"string","maxLength":20},"address":{"$ref":"#/components/schemas/Address"}}},"MedicationRequest":{"type":"object","required":["type","preset_catalog_id","sender_med_request_id","medication_name","quantity","quantity_units","days_supply_duration","refills","instructions"],"properties":{"type":{"type":"string","enum":["new"]},"preset_catalog_id":{"type":"string","x-mockingbird-resource-ref":{"type":"catalog_item","missing":"00000000-0000-4000-8000-000000000000"}},"sender_med_request_id":{"type":"string","minLength":1,"maxLength":64},"medication_name":{"type":"string","minLength":1,"maxLength":120},"medication_strength":{"type":"string","maxLength":60},"medication_form":{"type":"string","maxLength":40},"quantity":{"type":"number","exclusiveMinimum":0,"maximum":1000},"quantity_units":{"type":"string","enum":["mL","grams","each"]},"days_supply_duration":{"type":"integer","minimum":1,"maximum":365},"refills":{"type":"integer","minimum":0,"maximum":12},"instructions":{"type":"string","minLength":1,"maxLength":500},"note":{"type":"string","maxLength":1000},"authored_on_datetime":{"type":"string","maxLength":40},"clinical_difference":{"type":"string","maxLength":1000},"schedule_code":{"type":"string","enum":["2","3","4","5"]}}},"ClinicalList":{"type":"object","properties":{"has_known_allergies":{"type":"boolean"},"has_known_diseases":{"type":"boolean"},"has_known_medications":{"type":"boolean"},"entries":{"type":"array","maxItems":5,"items":{"type":"object","required":["description"],"properties":{"description":{"type":"string","maxLength":200}}}}}},"CreateOrderBody":{"type":"object","required":["patient","prescriber","order","medication_requests"],"properties":{"patient":{"$ref":"#/components/schemas/Patient"},"prescriber":{"$ref":"#/components/schemas/Prescriber"},"order":{"type":"object","required":["bill_to","ship_to","sender_order_id"],"properties":{"bill_to":{"type":"string","enum":["practice","patient"]},"ship_to":{"type":"string","enum":["patient","practice"]},"sender_order_id":{"type":"string","minLength":1,"maxLength":64}}},"medication_requests":{"type":"array","minItems":1,"maxItems":3,"items":{"$ref":"#/components/schemas/MedicationRequest"}},"clinical":{"type":"object","properties":{"allergies":{"$ref":"#/components/schemas/ClinicalList"},"diseases":{"$ref":"#/components/schemas/ClinicalList"},"medications":{"$ref":"#/components/schemas/ClinicalList"}}},"shipment":{"type":"object","properties":{"recipient_first_name":{"type":"string","maxLength":40},"recipient_last_name":{"type":"string","maxLength":40},"recipient_email":{"type":"string","maxLength":80},"recipient_phone":{"type":"string","maxLength":20},"address":{"$ref":"#/components/schemas/Address"}}}}},"CreateOrderResponse":{"type":"object","required":["success","message","order_tracking_id","sender_order_id","status"],"properties":{"success":{"type":"boolean"},"message":{"type":"string"},"order_tracking_id":{"type":"string","x-mockingbird-resource":{"type":"order","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"sender_order_id":{"type":"string"},"status":{"type":"string"}}},"OrderStatus":{"type":"object","required":["order_tracking_id","tracking_id","orderReferenceID","sender_order_id","rxstatus","orderstatus","shipping_status","cancellable","created_at","updated_at"],"properties":{"order_tracking_id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"tracking_id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"orderReferenceID":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"sender_order_id":{"type":"string"},"rxstatus":{"type":"string"},"orderstatus":{"type":"string"},"shipping_status":{"type":"string"},"delivered_date":{"type":["string","null"]},"trackingnumber":{"type":["string","null"]},"shippingservice":{"type":["string","null"]},"shippingcarrier":{"type":["string","null"]},"shipmenttrackingurl":{"type":["string","null"]},"cancellable":{"type":"boolean"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"CancelResponse":{"type":"object","required":["success","message","order_tracking_id","rxstatus"],"properties":{"success":{"type":"boolean"},"message":{"type":"string"},"order_tracking_id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"rxstatus":{"type":"string"}}},"CatalogItem":{"type":"object","required":["catalog_id","medication_name","status"],"properties":{"catalog_id":{"type":"string","x-mockingbird-resource":{"type":"catalog_item","identity":true}},"medication_name":{"type":"string"},"medication_strength":{"type":["string","null"]},"package_size":{"type":["string","null"]},"quantity":{"type":["number","null"]},"quantity_units":{"type":["string","null"]},"medication_form":{"type":["string","null"]},"route":{"type":["string","null"]},"states":{"type":"array","items":{"type":"string"}},"status":{"type":"string","enum":["active","inactive"]}}},"ErrorBody":{"type":"object","required":["message"],"properties":{"message":{"type":"string"},"success":{"type":"boolean"}}},"ValidationError":{"type":"object","required":["message","errors"],"properties":{"message":{"type":"string"},"errors":{"oneOf":[{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}},{"type":"array","items":{"type":"object"}}]}}}}}}`);
4781
+ var document2 = JSON.parse(`{"openapi":"3.1.0","info":{"title":"RxVortex (Strive) pharmacy API (Mockingbird subset)","description":"Stateful mock subset of the RxVortex / Strive compounding-pharmacy API: client-credentials\\ntoken, order submit, status, cancel, lookup by sender order id (recovery), and the preset\\ncatalog. Hand-authored from the consumer's adapters (the vendor publishes no spec).\\n","version":"1","x-mockingbird-upstream":{"note":"Hand-authored from the wire shapes our consumer reads and writes (rxvortex-auth.service.ts, rxvortex-fulfillment.adapter.ts, rxvortex-live.client.ts)."}},"servers":[{"url":"https://sandbox.rxvortex.com"}],"security":[{"bearerAuth":[]}],"paths":{"/api/v1/generate-access-token":{"post":{"operationId":"GenerateAccessToken","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["client_id","client_secret"],"properties":{"client_id":{"type":"string","minLength":1,"maxLength":64},"client_secret":{"type":"string","minLength":1,"maxLength":64}}}}}},"responses":{"200":{"description":"Access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"401":{"description":"Bad credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}}}},"/api/v1/orders":{"post":{"operationId":"CreateOrder","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrderBody"}}}},"responses":{"200":{"description":"Order accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrderResponse"}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"Duplicate sender_order_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}}}},"/api/v1/orders/{orderId}":{"parameters":[{"name":"orderId","in":"path","required":true,"description":"The vendor order tracking id, or the sender order id (our payment id).","schema":{"type":"string","x-mockingbird-resource-ref":{"type":"order","missing":"RXV-00000000-MISSING"}}}],"get":{"operationId":"GetOrder","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Order status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderStatus"}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Unknown order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}},"delete":{"operationId":"CancelOrder","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"200":{"description":"Cancelled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelResponse"}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Unknown order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"Not cancellable in its current status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/preset-catalog-items":{"get":{"operationId":"ListPresetCatalogItems","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Preset catalog","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CatalogItem"}}}}}}},"401":{"description":"Unauthenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"TokenResponse":{"type":"object","required":["access_token","token_type","expires_in"],"properties":{"access_token":{"type":"string","x-mockingbird-volatile":{"kind":"token"}},"token_type":{"type":"string","enum":["Bearer"]},"expires_in":{"type":"integer"}}},"Address":{"type":"object","required":["line1","city","state","postal_code","country"],"properties":{"line1":{"type":"string","minLength":1,"maxLength":80},"line2":{"type":"string","maxLength":80},"city":{"type":"string","minLength":1,"maxLength":40},"state":{"type":"string","pattern":"^[A-Z]{2}$"},"postal_code":{"type":"string","pattern":"^[0-9]{5}(-[0-9]{4})?$"},"country":{"type":"string","enum":["US"]}}},"Patient":{"type":"object","required":["sender_patient_id","first_name","last_name","dob","gender","email","phone","address"],"properties":{"sender_patient_id":{"type":"string","minLength":1,"maxLength":64},"first_name":{"type":"string","minLength":1,"maxLength":40},"last_name":{"type":"string","minLength":1,"maxLength":40},"dob":{"type":"string","pattern":"^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$"},"gender":{"type":"string","enum":["male","female"]},"email":{"type":"string","format":"email","maxLength":120},"phone":{"type":"string","pattern":"^[0-9]{3}-[0-9]{3}-[0-9]{4}$"},"address":{"$ref":"#/components/schemas/Address"}}},"Prescriber":{"type":"object","required":["first_name","last_name","npi"],"properties":{"first_name":{"type":"string","minLength":1,"maxLength":40},"last_name":{"type":"string","minLength":1,"maxLength":40},"npi":{"type":"string","pattern":"^[0-9]{10}$"},"dea_number":{"type":"string","maxLength":20},"license_number":{"type":"string","maxLength":20},"license_state":{"type":"string","maxLength":2},"phone":{"type":"string","maxLength":20},"address":{"$ref":"#/components/schemas/Address"}}},"MedicationRequest":{"type":"object","required":["type","preset_catalog_id","sender_med_request_id","medication_name","quantity","quantity_units","days_supply_duration","refills","instructions"],"properties":{"type":{"type":"string","enum":["new"]},"preset_catalog_id":{"type":"string","x-mockingbird-resource-ref":{"type":"catalog_item","missing":"00000000-0000-4000-8000-000000000000"}},"sender_med_request_id":{"type":"string","minLength":1,"maxLength":64},"medication_name":{"type":"string","minLength":1,"maxLength":120},"medication_strength":{"type":"string","maxLength":60},"medication_form":{"type":"string","maxLength":40},"quantity":{"type":"number","exclusiveMinimum":0,"maximum":1000},"quantity_units":{"type":"string","enum":["mL","grams","each"]},"days_supply_duration":{"type":"integer","minimum":1,"maximum":365},"refills":{"type":"integer","minimum":0,"maximum":12},"instructions":{"type":"string","minLength":1,"maxLength":500},"note":{"type":"string","maxLength":1000},"authored_on_datetime":{"type":"string","maxLength":40},"clinical_difference":{"type":"string","maxLength":1000},"schedule_code":{"type":"string","enum":["2","3","4","5"]}}},"ClinicalList":{"type":"object","properties":{"has_known_allergies":{"type":"boolean"},"has_known_diseases":{"type":"boolean"},"has_known_medications":{"type":"boolean"},"entries":{"type":"array","maxItems":5,"items":{"type":"object","required":["description"],"properties":{"description":{"type":"string","maxLength":200}}}}}},"CreateOrderBody":{"type":"object","required":["patient","prescriber","order","medication_requests"],"properties":{"patient":{"$ref":"#/components/schemas/Patient"},"prescriber":{"$ref":"#/components/schemas/Prescriber"},"order":{"type":"object","required":["bill_to","ship_to","sender_order_id"],"properties":{"bill_to":{"type":"string","enum":["practice","patient"]},"ship_to":{"type":"string","enum":["patient","practice"]},"sender_order_id":{"type":"string","minLength":1,"maxLength":64}}},"medication_requests":{"type":"array","minItems":1,"maxItems":3,"items":{"$ref":"#/components/schemas/MedicationRequest"}},"clinical":{"type":"object","properties":{"allergies":{"$ref":"#/components/schemas/ClinicalList"},"diseases":{"$ref":"#/components/schemas/ClinicalList"},"medications":{"$ref":"#/components/schemas/ClinicalList"}}},"shipment":{"type":"object","properties":{"recipient_first_name":{"type":"string","maxLength":40},"recipient_last_name":{"type":"string","maxLength":40},"recipient_email":{"type":"string","maxLength":80},"recipient_phone":{"type":"string","maxLength":20},"address":{"$ref":"#/components/schemas/Address"}}}}},"CreateOrderResponse":{"type":"object","required":["success","message","order_tracking_id","sender_order_id","status"],"properties":{"success":{"type":"boolean"},"message":{"type":"string"},"order_tracking_id":{"type":"string","x-mockingbird-resource":{"type":"order","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"sender_order_id":{"type":"string"},"status":{"type":"string"}}},"OrderStatus":{"type":"object","required":["order_tracking_id","tracking_id","orderReferenceID","sender_order_id","rxstatus","orderstatus","shipping_status","cancellable","created_at","updated_at"],"properties":{"order_tracking_id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"tracking_id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"orderReferenceID":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"sender_order_id":{"type":"string"},"rxstatus":{"type":"string"},"orderstatus":{"type":"string"},"shipping_status":{"type":"string"},"delivered_date":{"type":["string","null"]},"trackingnumber":{"type":["string","null"]},"shippingservice":{"type":["string","null"]},"shippingcarrier":{"type":["string","null"]},"shipmenttrackingurl":{"type":["string","null"]},"cancellable":{"type":"boolean"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"CancelResponse":{"type":"object","required":["success","message","order_tracking_id","rxstatus"],"properties":{"success":{"type":"boolean"},"message":{"type":"string"},"order_tracking_id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"rxstatus":{"type":"string"}}},"CatalogItem":{"type":"object","required":["catalog_id","medication_name","status"],"properties":{"catalog_id":{"type":"string","x-mockingbird-resource":{"type":"catalog_item","identity":true}},"medication_name":{"type":"string"},"medication_strength":{"type":["string","null"]},"package_size":{"type":["string","null"]},"quantity":{"type":["number","null"]},"quantity_units":{"type":["string","null"]},"medication_form":{"type":["string","null"]},"route":{"type":["string","null"]},"states":{"type":"array","items":{"type":"string"}},"status":{"type":"string","enum":["active","inactive"]}}},"ErrorBody":{"type":"object","required":["message"],"properties":{"message":{"type":"string"},"success":{"type":"boolean"}}},"ValidationError":{"type":"object","required":["message","errors"],"properties":{"message":{"type":"string"},"errors":{"oneOf":[{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}},{"type":"array","items":{"type":"object"}}]}}}}}}`);
3978
4782
  var operationIds = ["GenerateAccessToken", "CreateOrder", "GetOrder", "CancelOrder", "ListPresetCatalogItems"];
3979
4783
  var supportedOperationIds = ["GenerateAccessToken", "CreateOrder", "GetOrder", "CancelOrder", "ListPresetCatalogItems"];
3980
4784
 
@@ -4061,7 +4865,7 @@ var DEFAULT_CATALOG = [
4061
4865
  status: "inactive"
4062
4866
  }
4063
4867
  ];
4064
- var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4868
+ var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4065
4869
  var optionalString = (row, key) => {
4066
4870
  const value = row[key];
4067
4871
  if (value === void 0 || value === null) return null;
@@ -4069,7 +4873,7 @@ var optionalString = (row, key) => {
4069
4873
  return value;
4070
4874
  };
4071
4875
  var parseCatalogItem = (value) => {
4072
- if (!isRecord5(value)) throw new Error("a catalog row must be an object");
4876
+ if (!isRecord6(value)) throw new Error("a catalog row must be an object");
4073
4877
  const { catalog_id, medication_name, quantity, states, status } = value;
4074
4878
  if (typeof catalog_id !== "string" || catalog_id.length === 0) {
4075
4879
  throw new Error("catalog_id must be a non-empty string");
@@ -4104,7 +4908,7 @@ var parseCatalogItem = (value) => {
4104
4908
  }
4105
4909
  };
4106
4910
  var parseCatalog = (value) => {
4107
- const rows = Array.isArray(value) ? value : isRecord5(value) ? value.data : void 0;
4911
+ const rows = Array.isArray(value) ? value : isRecord6(value) ? value.data : void 0;
4108
4912
  if (!Array.isArray(rows)) throw new Error("a catalog must be an array or {data: [...]}");
4109
4913
  return rows.map(parseCatalogItem);
4110
4914
  };
@@ -4262,10 +5066,10 @@ var RXVORTEX_PRESETS = {
4262
5066
  };
4263
5067
  var json5 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
4264
5068
  var adminError4 = (status, message) => json5(status, { error: { type: "mockingbird_admin", message } });
4265
- var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5069
+ var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4266
5070
  var parseAutoAdvance = (value) => {
4267
5071
  if (value === null) return null;
4268
- if (!isRecord6(value)) return "autoAdvance must be {afterMs, path} or null";
5072
+ if (!isRecord7(value)) return "autoAdvance must be {afterMs, path} or null";
4269
5073
  if (typeof value.afterMs !== "number" || value.afterMs < 0)
4270
5074
  return "autoAdvance.afterMs must be ms";
4271
5075
  if (!Array.isArray(value.path) || value.path.some((s) => typeof s !== "string")) {
@@ -4276,7 +5080,7 @@ var parseAutoAdvance = (value) => {
4276
5080
  var adminRoutes = (runtime) => ({
4277
5081
  "GET /orders": ({ namespace }) => json5(200, { orders: runtime.instance(namespace).orders() }),
4278
5082
  "POST /orders/:id/transition": ({ params, body, namespace }) => {
4279
- if (!isRecord6(body) || typeof body.to !== "string") {
5083
+ if (!isRecord7(body) || typeof body.to !== "string") {
4280
5084
  return adminError4(
4281
5085
  400,
4282
5086
  'expected {"to": "<vendor status>", "trackingnumber"?, "shippingcarrier"?}'
@@ -4294,8 +5098,8 @@ var adminRoutes = (runtime) => ({
4294
5098
  },
4295
5099
  "GET /catalog": ({ namespace }) => json5(200, { data: runtime.instance(namespace).state.catalogRows() }),
4296
5100
  "PUT /catalog": ({ body, namespace }) => {
4297
- const rows = isRecord6(body) ? body.items ?? body.data : void 0;
4298
- if (!isRecord6(body) || !Array.isArray(rows)) {
5101
+ const rows = isRecord7(body) ? body.items ?? body.data : void 0;
5102
+ if (!isRecord7(body) || !Array.isArray(rows)) {
4299
5103
  return adminError4(400, 'expected {"items": [{catalog_id, medication_name, \u2026}], "mode"?}');
4300
5104
  }
4301
5105
  const mode = body.mode ?? "replace";
@@ -4312,7 +5116,7 @@ var adminRoutes = (runtime) => ({
4312
5116
  },
4313
5117
  "GET /settings": ({ namespace }) => json5(200, runtime.instance(namespace).state.current()),
4314
5118
  "PUT /settings": ({ body, namespace }) => {
4315
- if (!isRecord6(body)) return adminError4(400, "expected a JSON object");
5119
+ if (!isRecord7(body)) return adminError4(400, "expected a JSON object");
4316
5120
  const patch = {};
4317
5121
  if (body.tokenTtlSeconds !== void 0) {
4318
5122
  if (typeof body.tokenTtlSeconds !== "number")
@@ -4326,7 +5130,7 @@ var adminRoutes = (runtime) => ({
4326
5130
  if (body.clients !== void 0) {
4327
5131
  if (!Array.isArray(body.clients))
4328
5132
  return adminError4(400, "clients: [{client_id, client_secret}]");
4329
- patch.clients = body.clients.filter(isRecord6).map((c) => ({
5133
+ patch.clients = body.clients.filter(isRecord7).map((c) => ({
4330
5134
  client_id: String(c.client_id),
4331
5135
  client_secret: String(c.client_secret)
4332
5136
  }));
@@ -4356,7 +5160,7 @@ var createRuntime2 = (options = {}) => {
4356
5160
  });
4357
5161
  const runtime = createRuntime({
4358
5162
  name: RXVORTEX_NAMESPACE,
4359
- document,
5163
+ document: document2,
4360
5164
  ...options.sqlite ? { sqlite: options.sqlite } : {},
4361
5165
  ...options.clock ? { clock: options.clock } : {},
4362
5166
  ...options.seed !== void 0 ? { seed: options.seed } : {},
@@ -4488,7 +5292,7 @@ var RxVortexAPI = class {
4488
5292
  ListPresetCatalogItems: () => jsonRes(200, { data: this.state.catalogRows() })
4489
5293
  });
4490
5294
  this.service = createService({
4491
- document,
5295
+ document: document2,
4492
5296
  handlers,
4493
5297
  sqlite,
4494
5298
  namespace,
@@ -4755,7 +5559,7 @@ var RxVortexAPI = class {
4755
5559
  };
4756
5560
 
4757
5561
  export {
4758
- document,
5562
+ document2 as document,
4759
5563
  operationIds,
4760
5564
  supportedOperationIds,
4761
5565
  CUSTOM_CREAM_ANCHOR_PRESET_ID,
@@ -4768,4 +5572,4 @@ export {
4768
5572
  tokenCredential,
4769
5573
  RxVortexAPI
4770
5574
  };
4771
- //# sourceMappingURL=chunk-KQEUUCLU.js.map
5575
+ //# sourceMappingURL=chunk-7IB3GSYU.js.map