@crvouga/mockingbird-service-flex 1.4.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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, record[key], [...path, key]));
781
+ errors.push(...validateValue(document3, property, record[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, record[key], [...path, key]));
787
+ errors.push(...validateValue(document3, s.additionalProperties, record[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
  };
@@ -1396,6 +1396,1232 @@ var clearNamespace = (sqlite, namespace) => {
1396
1396
  });
1397
1397
  };
1398
1398
 
1399
+ // ../../ui/dist/reset.js
1400
+ var CSS_RESET = `*,
1401
+ *::before,
1402
+ *::after {
1403
+ box-sizing: border-box;
1404
+ margin: 0;
1405
+ padding: 0;
1406
+ }
1407
+
1408
+ * {
1409
+ scrollbar-width: thin;
1410
+ scrollbar-color: color-mix(in srgb, CanvasText 35%, Canvas) Canvas;
1411
+ }
1412
+
1413
+ *::-webkit-scrollbar {
1414
+ width: 12px;
1415
+ height: 12px;
1416
+ }
1417
+
1418
+ *::-webkit-scrollbar-track,
1419
+ *::-webkit-scrollbar-corner {
1420
+ background: Canvas;
1421
+ }
1422
+
1423
+ *::-webkit-scrollbar-thumb {
1424
+ background: color-mix(in srgb, CanvasText 35%, Canvas);
1425
+ border: 3px solid Canvas;
1426
+ border-radius: 999px;
1427
+ }
1428
+
1429
+ html {
1430
+ hanging-punctuation: first allow-end last;
1431
+ -webkit-text-size-adjust: none;
1432
+ text-size-adjust: none;
1433
+ tab-size: 4;
1434
+ font-family:
1435
+ ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI",
1436
+ "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
1437
+ font-size: 16px;
1438
+ line-height: 1.5;
1439
+ -webkit-font-smoothing: antialiased;
1440
+ -moz-osx-font-smoothing: grayscale;
1441
+ text-rendering: optimizeLegibility;
1442
+ -webkit-tap-highlight-color: transparent;
1443
+ scroll-behavior: smooth;
1444
+ background: Canvas;
1445
+ color: CanvasText;
1446
+ }
1447
+
1448
+ body {
1449
+ min-height: 100svh;
1450
+ line-height: 1.5;
1451
+ background: Canvas;
1452
+ color: CanvasText;
1453
+ }
1454
+
1455
+ h1 {
1456
+ font-size: 2em;
1457
+ }
1458
+
1459
+ h2 {
1460
+ font-size: 1.5em;
1461
+ }
1462
+
1463
+ h3 {
1464
+ font-size: 1.17em;
1465
+ }
1466
+
1467
+ h4 {
1468
+ font-size: 1em;
1469
+ }
1470
+
1471
+ h5 {
1472
+ font-size: 0.83em;
1473
+ }
1474
+
1475
+ h6 {
1476
+ font-size: 0.67em;
1477
+ }
1478
+
1479
+ h1,
1480
+ h2,
1481
+ h3,
1482
+ h4,
1483
+ h5,
1484
+ h6 {
1485
+ font-weight: 700;
1486
+ line-height: 1.2;
1487
+ text-wrap: balance;
1488
+ overflow-wrap: break-word;
1489
+ }
1490
+
1491
+ p,
1492
+ li,
1493
+ figcaption,
1494
+ dd,
1495
+ dt,
1496
+ blockquote {
1497
+ overflow-wrap: break-word;
1498
+ }
1499
+
1500
+ p,
1501
+ li,
1502
+ figcaption,
1503
+ blockquote {
1504
+ text-wrap: pretty;
1505
+ }
1506
+
1507
+ ul,
1508
+ ol,
1509
+ menu {
1510
+ list-style: none;
1511
+ }
1512
+
1513
+ a {
1514
+ color: inherit;
1515
+ text-decoration: underline;
1516
+ text-decoration-thickness: 1px;
1517
+ text-underline-offset: 0.15em;
1518
+ text-decoration-skip-ink: auto;
1519
+ }
1520
+
1521
+ b,
1522
+ strong {
1523
+ font-weight: 700;
1524
+ }
1525
+
1526
+ small {
1527
+ font-size: 0.875em;
1528
+ }
1529
+
1530
+ code,
1531
+ kbd,
1532
+ samp,
1533
+ pre {
1534
+ font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
1535
+ font-size: 1em;
1536
+ }
1537
+
1538
+ sub,
1539
+ sup {
1540
+ font-size: 75%;
1541
+ line-height: 0;
1542
+ position: relative;
1543
+ vertical-align: baseline;
1544
+ }
1545
+
1546
+ sub {
1547
+ bottom: -0.25em;
1548
+ }
1549
+
1550
+ sup {
1551
+ top: -0.5em;
1552
+ }
1553
+
1554
+ abbr[title] {
1555
+ text-decoration: underline dotted;
1556
+ text-underline-offset: 0.15em;
1557
+ }
1558
+
1559
+ mark {
1560
+ background: Mark;
1561
+ color: MarkText;
1562
+ }
1563
+
1564
+ hr {
1565
+ height: 0;
1566
+ border: 0;
1567
+ border-top: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
1568
+ color: inherit;
1569
+ }
1570
+
1571
+ img,
1572
+ picture,
1573
+ video,
1574
+ canvas {
1575
+ display: block;
1576
+ max-width: 100%;
1577
+ }
1578
+
1579
+ img,
1580
+ video {
1581
+ height: auto;
1582
+ }
1583
+
1584
+ svg {
1585
+ overflow: hidden;
1586
+ }
1587
+
1588
+ iframe {
1589
+ border: 0;
1590
+ max-width: 100%;
1591
+ }
1592
+
1593
+ table {
1594
+ border-collapse: collapse;
1595
+ border-spacing: 0;
1596
+ }
1597
+
1598
+ fieldset {
1599
+ border: 0;
1600
+ min-inline-size: 0;
1601
+ }
1602
+
1603
+ legend {
1604
+ padding: 0;
1605
+ }
1606
+
1607
+ button,
1608
+ input,
1609
+ select,
1610
+ textarea,
1611
+ optgroup {
1612
+ font: inherit;
1613
+ letter-spacing: inherit;
1614
+ color: inherit;
1615
+ margin: 0;
1616
+ vertical-align: middle;
1617
+ }
1618
+
1619
+ button,
1620
+ input[type="button"],
1621
+ input[type="submit"],
1622
+ input[type="reset"] {
1623
+ appearance: none;
1624
+ background: none;
1625
+ border: 0;
1626
+ padding: 0;
1627
+ cursor: pointer;
1628
+ text-align: center;
1629
+ }
1630
+
1631
+ input,
1632
+ textarea,
1633
+ select {
1634
+ background-color: Canvas;
1635
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1636
+ border-radius: 0;
1637
+ padding: 0.5em 0.65em;
1638
+ }
1639
+
1640
+ input[type="checkbox"],
1641
+ input[type="radio"],
1642
+ input[type="range"],
1643
+ input[type="file"],
1644
+ input[type="color"],
1645
+ input[type="button"],
1646
+ input[type="submit"],
1647
+ input[type="reset"],
1648
+ input[type="image"],
1649
+ input[type="hidden"] {
1650
+ background: none;
1651
+ border: 0;
1652
+ padding: 0;
1653
+ border-radius: 0;
1654
+ }
1655
+
1656
+ input[type="checkbox"],
1657
+ input[type="radio"] {
1658
+ accent-color: CanvasText;
1659
+ }
1660
+
1661
+ input[type="search"] {
1662
+ appearance: none;
1663
+ background-color: Canvas;
1664
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1665
+ }
1666
+
1667
+ input[type="search"]::-webkit-search-decoration,
1668
+ input[type="search"]::-webkit-search-cancel-button,
1669
+ input[type="search"]::-webkit-search-results-button,
1670
+ input[type="search"]::-webkit-search-results-decoration {
1671
+ -webkit-appearance: none;
1672
+ display: none;
1673
+ }
1674
+
1675
+ input[type="number"] {
1676
+ appearance: textfield;
1677
+ background-color: Canvas;
1678
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1679
+ }
1680
+
1681
+ input[type="number"]::-webkit-inner-spin-button,
1682
+ input[type="number"]::-webkit-outer-spin-button {
1683
+ -webkit-appearance: none;
1684
+ margin: 0;
1685
+ }
1686
+
1687
+ textarea {
1688
+ resize: vertical;
1689
+ }
1690
+
1691
+ ::placeholder {
1692
+ opacity: 1;
1693
+ color: color-mix(in srgb, CanvasText 45%, transparent);
1694
+ }
1695
+
1696
+ :focus-visible {
1697
+ outline: 2px solid CanvasText;
1698
+ outline-offset: 2px;
1699
+ }
1700
+
1701
+ :focus:not(:focus-visible) {
1702
+ outline: none;
1703
+ }
1704
+
1705
+ :disabled,
1706
+ [disabled] {
1707
+ cursor: default;
1708
+ opacity: 0.6;
1709
+ }
1710
+
1711
+ label,
1712
+ summary,
1713
+ select,
1714
+ [role="button"] {
1715
+ cursor: pointer;
1716
+ }
1717
+
1718
+ summary {
1719
+ display: list-item;
1720
+ }
1721
+
1722
+ details {
1723
+ display: block;
1724
+ }
1725
+
1726
+ dialog {
1727
+ margin: auto;
1728
+ padding: 1rem;
1729
+ border: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
1730
+ background: Canvas;
1731
+ color: CanvasText;
1732
+ max-width: calc(100vw - 2rem);
1733
+ max-height: calc(100svh - 2rem);
1734
+ }
1735
+
1736
+ dialog:not([open]) {
1737
+ display: none;
1738
+ }
1739
+
1740
+ ::backdrop {
1741
+ background: color-mix(in srgb, CanvasText 40%, transparent);
1742
+ }
1743
+
1744
+ [hidden] {
1745
+ display: none !important;
1746
+ }
1747
+
1748
+ ::selection {
1749
+ background: Highlight;
1750
+ color: HighlightText;
1751
+ }
1752
+
1753
+ @media (prefers-reduced-motion: reduce) {
1754
+ *,
1755
+ *::before,
1756
+ *::after {
1757
+ animation-duration: 0.01ms !important;
1758
+ animation-iteration-count: 1 !important;
1759
+ transition-duration: 0.01ms !important;
1760
+ scroll-behavior: auto !important;
1761
+ }
1762
+ }
1763
+
1764
+ @media (pointer: coarse) {
1765
+ input,
1766
+ select,
1767
+ textarea {
1768
+ font-size: max(16px, 1em);
1769
+ }
1770
+ }`;
1771
+
1772
+ // ../core/dist/admin-client.js
1773
+ function isRecord2(value) {
1774
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1775
+ }
1776
+ function mountAdminBrand(doc, host, service, catalog) {
1777
+ const clip = (value, max) => {
1778
+ if (typeof value !== "string")
1779
+ return null;
1780
+ const trimmed = value.trim();
1781
+ if (trimmed === "")
1782
+ return null;
1783
+ return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
1784
+ };
1785
+ const httpUrl = (value) => {
1786
+ if (typeof value !== "string" || value.length > 2e3)
1787
+ return null;
1788
+ try {
1789
+ const url = new URL(value);
1790
+ if (url.protocol !== "https:" && url.protocol !== "http:")
1791
+ return null;
1792
+ return url.href;
1793
+ } catch {
1794
+ return null;
1795
+ }
1796
+ };
1797
+ const hexColor = (value) => {
1798
+ if (typeof value !== "string")
1799
+ return null;
1800
+ const raw = value.trim();
1801
+ return /^#[0-9a-fA-F]{3,8}$/.test(raw) ? raw : null;
1802
+ };
1803
+ const hostname = (value) => {
1804
+ try {
1805
+ return new URL(value).hostname.replace(/^www\./, "");
1806
+ } catch {
1807
+ return value;
1808
+ }
1809
+ };
1810
+ host.replaceChildren();
1811
+ host.hidden = true;
1812
+ if (!isRecord2(catalog) || !Object.hasOwn(catalog, service))
1813
+ return;
1814
+ const row = catalog[service];
1815
+ if (!isRecord2(row))
1816
+ return;
1817
+ const vendor = clip(row.vendor, 80);
1818
+ const website = httpUrl(row.website);
1819
+ const guide = httpUrl(row.guide);
1820
+ const docs = httpUrl(row.docs);
1821
+ const logo = httpUrl(row.logo);
1822
+ const color = hexColor(row.color);
1823
+ const description = clip(row.description, 280);
1824
+ if (vendor === null || website === null && guide === null && docs === null)
1825
+ return;
1826
+ const add = (node) => {
1827
+ host.append(node);
1828
+ return node;
1829
+ };
1830
+ if (logo !== null) {
1831
+ const img = doc.createElement("img");
1832
+ img.alt = "";
1833
+ img.src = logo;
1834
+ img.width = 16;
1835
+ img.height = 16;
1836
+ img.decoding = "async";
1837
+ img.referrerPolicy = "no-referrer";
1838
+ if (color !== null)
1839
+ img.style.borderColor = color;
1840
+ add(img);
1841
+ }
1842
+ const name = doc.createElement("span");
1843
+ name.className = "vendor-name";
1844
+ name.textContent = vendor;
1845
+ if (description !== null)
1846
+ name.title = description;
1847
+ add(name);
1848
+ const sep = () => {
1849
+ const node = doc.createElement("span");
1850
+ node.className = "vendor-sep";
1851
+ node.setAttribute("aria-hidden", "true");
1852
+ node.textContent = "\xB7";
1853
+ return node;
1854
+ };
1855
+ const link = (href, label, title) => {
1856
+ const node = doc.createElement("a");
1857
+ node.className = "vendor-link";
1858
+ node.href = href;
1859
+ node.target = "_blank";
1860
+ node.rel = "noopener noreferrer";
1861
+ node.referrerPolicy = "no-referrer";
1862
+ node.textContent = label;
1863
+ node.title = title;
1864
+ return node;
1865
+ };
1866
+ if (website !== null) {
1867
+ add(sep());
1868
+ add(link(website, hostname(website), `${vendor} website`));
1869
+ }
1870
+ if (guide !== null) {
1871
+ add(sep());
1872
+ add(link(guide, "Docs", `Docs for ${vendor}`));
1873
+ }
1874
+ if (docs !== null) {
1875
+ add(sep());
1876
+ add(link(docs, "API", `${vendor} API reference`));
1877
+ }
1878
+ host.removeAttribute("hidden");
1879
+ host.hidden = false;
1880
+ }
1881
+ function startAdminBrand(env) {
1882
+ const requested = env.params.get("brands");
1883
+ let brandsUrl = env.brandsUrl;
1884
+ if (requested) {
1885
+ try {
1886
+ const url = new URL(requested, env.location.href);
1887
+ const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]" || url.origin === env.location.origin;
1888
+ if ((url.protocol === "https:" || url.protocol === "http:") && local)
1889
+ brandsUrl = url.href;
1890
+ } catch {
1891
+ brandsUrl = env.brandsUrl;
1892
+ }
1893
+ }
1894
+ const vendorHost = env.$("vendor");
1895
+ const brandRoot = env.document.querySelector("[data-mockingbird-admin]");
1896
+ const brandService = brandRoot?.getAttribute("data-service") || "";
1897
+ if (vendorHost === null)
1898
+ return;
1899
+ env.fetch(brandsUrl, {
1900
+ headers: { accept: "application/json" },
1901
+ signal: AbortSignal.timeout(5e3)
1902
+ }).then(async (response) => {
1903
+ if (!response.ok)
1904
+ return null;
1905
+ const catalog = await response.json();
1906
+ return catalog;
1907
+ }).then((catalog) => {
1908
+ mountAdminBrand(env.document, vendorHost, brandService, catalog);
1909
+ }).catch(() => {
1910
+ });
1911
+ }
1912
+ function faultPresetList(value) {
1913
+ if (!isRecord2(value))
1914
+ throw new Error("Fault presets response was not an object");
1915
+ if (!Object.hasOwn(value, "presets") || value.presets === void 0 || value.presets === null) {
1916
+ return { presets: [] };
1917
+ }
1918
+ if (!Array.isArray(value.presets))
1919
+ throw new Error("Fault presets response was not a list");
1920
+ return {
1921
+ presets: value.presets.flatMap((item) => {
1922
+ if (!isRecord2(item) || typeof item.name !== "string")
1923
+ return [];
1924
+ return [{ name: item.name }];
1925
+ })
1926
+ };
1927
+ }
1928
+ function bootAdmin(config) {
1929
+ const fieldKinds = ["string", "number", "boolean", "null", "object", "array", "unknown"];
1930
+ const sources = ["declared", "inferred", "mixed"];
1931
+ const isFieldKind = (value) => typeof value === "string" && fieldKinds.some((kind) => kind === value);
1932
+ const isSource = (value) => typeof value === "string" && sources.some((source) => source === value);
1933
+ const readJson2 = (text) => {
1934
+ const value = JSON.parse(text);
1935
+ return value;
1936
+ };
1937
+ const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => {
1938
+ switch (char) {
1939
+ case "&":
1940
+ return "&amp;";
1941
+ case "<":
1942
+ return "&lt;";
1943
+ case ">":
1944
+ return "&gt;";
1945
+ case '"':
1946
+ return "&quot;";
1947
+ default:
1948
+ return "&#39;";
1949
+ }
1950
+ });
1951
+ const el = (id, kind) => {
1952
+ const node = document.getElementById(id);
1953
+ if (!(node instanceof kind))
1954
+ throw new Error(`admin shell is missing #${id}`);
1955
+ return node;
1956
+ };
1957
+ const errorMessage = (data, fallback) => {
1958
+ if (!isRecord2(data))
1959
+ return fallback;
1960
+ const error2 = data.error;
1961
+ if (!isRecord2(error2) || typeof error2.message !== "string" || error2.message === "") {
1962
+ return fallback;
1963
+ }
1964
+ return error2.message;
1965
+ };
1966
+ const requireList = (value, label) => {
1967
+ if (value === void 0 || value === null)
1968
+ return [];
1969
+ if (!Array.isArray(value))
1970
+ throw new Error(`${label} was not a list`);
1971
+ return value;
1972
+ };
1973
+ const state = {
1974
+ namespace: "default",
1975
+ key: sessionStorage.getItem("mockingbird-admin-key") || "",
1976
+ view: "overview",
1977
+ collection: "",
1978
+ shape: null,
1979
+ route: null,
1980
+ editing: null
1981
+ };
1982
+ const standard = new Set(config.standardRoutes);
1983
+ const banner = el("banner", HTMLElement);
1984
+ const keyInput = el("admin-key", HTMLInputElement);
1985
+ keyInput.value = state.key;
1986
+ const params = new URLSearchParams(location.search);
1987
+ const queryKey = params.get("key");
1988
+ if (queryKey) {
1989
+ state.key = queryKey;
1990
+ keyInput.value = state.key;
1991
+ sessionStorage.setItem("mockingbird-admin-key", state.key);
1992
+ params.delete("key");
1993
+ const next = params.toString();
1994
+ history.replaceState(null, "", location.pathname + (next ? `?${next}` : ""));
1995
+ }
1996
+ const queryNamespace = params.get("namespace");
1997
+ if (queryNamespace)
1998
+ state.namespace = queryNamespace;
1999
+ const showError = (error2) => {
2000
+ banner.textContent = error2 instanceof Error ? error2.message : String(error2);
2001
+ banner.classList.add("show");
2002
+ };
2003
+ const clearError = () => {
2004
+ banner.classList.remove("show");
2005
+ };
2006
+ const api = async (method, path, body) => {
2007
+ const headers = { accept: "application/json" };
2008
+ if (state.key !== "")
2009
+ headers[config.adminKeyHeader] = state.key;
2010
+ if (state.namespace !== "")
2011
+ headers["x-mockingbird-namespace"] = state.namespace;
2012
+ if (body !== void 0)
2013
+ headers["content-type"] = "application/json";
2014
+ const init = { method, headers };
2015
+ if (body !== void 0)
2016
+ init.body = JSON.stringify(body);
2017
+ const response = await fetch(config.adminPrefix + path, init);
2018
+ const text = await response.text();
2019
+ let data = null;
2020
+ if (text !== "") {
2021
+ try {
2022
+ data = readJson2(text);
2023
+ } catch {
2024
+ data = { raw: text };
2025
+ }
2026
+ }
2027
+ if (response.status === 401) {
2028
+ keyInput.focus();
2029
+ throw new Error(errorMessage(data, "Admin key required"));
2030
+ }
2031
+ if (!response.ok) {
2032
+ throw new Error(errorMessage(data, `${response.status} ${method} ${path}`));
2033
+ }
2034
+ return data;
2035
+ };
2036
+ const originGet = async (path) => {
2037
+ const response = await fetch(path, { headers: { accept: "application/json" } });
2038
+ if (!response.ok)
2039
+ throw new Error(`${response.status} ${path}`);
2040
+ const value = await response.json();
2041
+ return value;
2042
+ };
2043
+ const panelApi = {
2044
+ get: (path) => api("GET", path),
2045
+ send: (method, path, body) => api(method, path, body),
2046
+ namespace: () => state.namespace
2047
+ };
2048
+ const isPanelScript = (value) => typeof value === "function";
2049
+ const setTheme = (theme) => {
2050
+ if (theme)
2051
+ document.documentElement.dataset.theme = theme;
2052
+ else
2053
+ delete document.documentElement.dataset.theme;
2054
+ };
2055
+ const storedTheme = localStorage.getItem("mockingbird-admin-theme");
2056
+ if (storedTheme)
2057
+ setTheme(storedTheme);
2058
+ el("theme", HTMLButtonElement).addEventListener("click", () => {
2059
+ const current = document.documentElement.dataset.theme;
2060
+ const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
2061
+ const next = dark ? "light" : "dark";
2062
+ localStorage.setItem("mockingbird-admin-theme", next);
2063
+ setTheme(next);
2064
+ });
2065
+ const show = (view) => {
2066
+ state.view = view;
2067
+ for (const button of document.querySelectorAll("#nav button")) {
2068
+ if (!(button instanceof HTMLButtonElement))
2069
+ continue;
2070
+ button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false");
2071
+ }
2072
+ for (const section of document.querySelectorAll("main .view")) {
2073
+ if (!(section instanceof HTMLElement))
2074
+ continue;
2075
+ section.hidden = section.id !== `view-${view}`;
2076
+ }
2077
+ };
2078
+ el("nav", HTMLElement).addEventListener("click", (event) => {
2079
+ const target = event.target;
2080
+ if (!(target instanceof Element))
2081
+ return;
2082
+ const button = target.closest("button[data-view]");
2083
+ if (!(button instanceof HTMLButtonElement))
2084
+ return;
2085
+ const view = button.dataset.view;
2086
+ if (view === void 0)
2087
+ return;
2088
+ show(view);
2089
+ refresh().catch(showError);
2090
+ });
2091
+ 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>`;
2092
+ const table = (columns, rows) => {
2093
+ if (rows.length === 0)
2094
+ return '<p class="empty">Nothing here yet.</p>';
2095
+ const head = `<tr>${columns.map((column) => `<th>${column.label}</th>`).join("")}</tr>`;
2096
+ const body = rows.map((row) => {
2097
+ const cells = columns.map((column) => `<td data-label="${column.label}">${column.cell(row)}</td>`).join("");
2098
+ const attrs = "attrs" in row && typeof row.attrs === "string" ? row.attrs : "";
2099
+ return `<tr${attrs}>${cells}</tr>`;
2100
+ }).join("");
2101
+ return `<table><thead>${head}</thead><tbody>${body}</tbody></table>`;
2102
+ };
2103
+ const preview = (value) => {
2104
+ const text = JSON.stringify(value) ?? "null";
2105
+ return esc(text.length > 180 ? `${text.slice(0, 180)}\u2026` : text);
2106
+ };
2107
+ const when = (epoch) => {
2108
+ const date = new Date(epoch);
2109
+ return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString();
2110
+ };
2111
+ const clockCard = (epoch) => {
2112
+ const date = new Date(epoch);
2113
+ if (Number.isNaN(date.getTime()))
2114
+ return esc(String(epoch));
2115
+ const iso = date.toISOString();
2116
+ return `${esc(iso.slice(0, 10))}<span class="clock-time">${esc(iso.slice(11, 19))} UTC</span>`;
2117
+ };
2118
+ const decodeClock = (value) => {
2119
+ if (!isRecord2(value) || typeof value.now !== "number" || typeof value.offsetMs !== "number" || typeof value.frozen !== "boolean") {
2120
+ throw new Error("Clock response was not a clock");
2121
+ }
2122
+ return { now: value.now, offsetMs: value.offsetMs, frozen: value.frozen };
2123
+ };
2124
+ const decodeField = (value) => {
2125
+ if (!isRecord2(value) || typeof value.name !== "string" || !isFieldKind(value.kind)) {
2126
+ return void 0;
2127
+ }
2128
+ const field = {
2129
+ name: value.name,
2130
+ kind: value.kind,
2131
+ optional: value.optional === true
2132
+ };
2133
+ if (typeof value.description === "string")
2134
+ field.description = value.description;
2135
+ if (Array.isArray(value.fields)) {
2136
+ field.fields = value.fields.flatMap((item) => {
2137
+ const child = decodeField(item);
2138
+ return child === void 0 ? [] : [child];
2139
+ });
2140
+ }
2141
+ return field;
2142
+ };
2143
+ const decodeCollection = (value) => {
2144
+ if (!isRecord2(value) || typeof value.name !== "string")
2145
+ return void 0;
2146
+ const fields = Array.isArray(value.fields) ? value.fields.flatMap((item) => {
2147
+ const field = decodeField(item);
2148
+ return field === void 0 ? [] : [field];
2149
+ }) : [];
2150
+ const collection = {
2151
+ name: value.name,
2152
+ label: typeof value.label === "string" ? value.label : value.name,
2153
+ count: typeof value.count === "number" ? value.count : 0,
2154
+ source: isSource(value.source) ? value.source : "inferred",
2155
+ fields
2156
+ };
2157
+ if (typeof value.description === "string")
2158
+ collection.description = value.description;
2159
+ return collection;
2160
+ };
2161
+ const decodeState = (value) => {
2162
+ if (!isRecord2(value) || !Array.isArray(value.collections)) {
2163
+ throw new Error("State response has no collection list");
2164
+ }
2165
+ return {
2166
+ namespace: typeof value.namespace === "string" ? value.namespace : state.namespace,
2167
+ storageNamespace: typeof value.storageNamespace === "string" ? value.storageNamespace : "",
2168
+ collections: value.collections.flatMap((item) => {
2169
+ const collection = decodeCollection(item);
2170
+ return collection === void 0 ? [] : [collection];
2171
+ })
2172
+ };
2173
+ };
2174
+ const decodeRecords = (value) => requireList(isRecord2(value) ? value.records : void 0, "Records").flatMap((item) => {
2175
+ if (!isRecord2(item) || typeof item.id !== "string")
2176
+ return [];
2177
+ return [
2178
+ {
2179
+ id: item.id,
2180
+ seq: typeof item.seq === "number" ? item.seq : 0,
2181
+ value: item.value,
2182
+ attrs: ` class="clickable" data-id="${esc(item.id)}"`
2183
+ }
2184
+ ];
2185
+ });
2186
+ const textOf = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : "";
2187
+ const decodeJournal = (value) => requireList(isRecord2(value) ? value.requests : void 0, "Journal").flatMap((item) => {
2188
+ if (!isRecord2(item))
2189
+ return [];
2190
+ return [
2191
+ {
2192
+ at: textOf(item.at),
2193
+ method: textOf(item.method),
2194
+ path: textOf(item.path),
2195
+ status: textOf(item.status),
2196
+ durationMs: textOf(item.durationMs),
2197
+ operationId: textOf(item.operationId)
2198
+ }
2199
+ ];
2200
+ });
2201
+ const decodeFaults = (value) => requireList(isRecord2(value) ? value.faults : void 0, "Faults").flatMap((item) => {
2202
+ if (!isRecord2(item))
2203
+ return [];
2204
+ return [
2205
+ {
2206
+ id: textOf(item.id),
2207
+ method: typeof item.method === "string" ? item.method : void 0,
2208
+ pathPrefix: typeof item.pathPrefix === "string" ? item.pathPrefix : void 0,
2209
+ operationId: typeof item.operationId === "string" ? item.operationId : void 0,
2210
+ status: typeof item.status === "number" ? item.status : void 0,
2211
+ effect: typeof item.effect === "string" ? item.effect : void 0,
2212
+ drop: typeof item.drop === "boolean" ? item.drop : void 0,
2213
+ latencyMs: typeof item.latencyMs === "number" ? item.latencyMs : void 0
2214
+ }
2215
+ ];
2216
+ });
2217
+ const loadNamespaces = async () => {
2218
+ const data = await api("GET", "/namespaces");
2219
+ const select = el("namespace", HTMLSelectElement);
2220
+ const listed = requireList(isRecord2(data) ? data.namespaces : void 0, "Namespaces");
2221
+ const fallback = isRecord2(data) && typeof data.default === "string" && data.default !== "" ? data.default : "default";
2222
+ const names = (listed.length > 0 ? listed.map((name) => String(name)) : [fallback]).slice();
2223
+ if (!names.includes(state.namespace))
2224
+ names.unshift(state.namespace);
2225
+ select.innerHTML = names.map((name) => `<option value="${esc(name)}">${esc(name)}</option>`).join("");
2226
+ select.value = state.namespace;
2227
+ };
2228
+ const renderOverview = async () => {
2229
+ const [health, shape, clock, journal] = await Promise.all([
2230
+ originGet("/health"),
2231
+ api("GET", "/state"),
2232
+ api("GET", "/clock"),
2233
+ api("GET", "/requests?limit=8")
2234
+ ]);
2235
+ if (!isRecord2(health))
2236
+ throw new Error("Health response was not an object");
2237
+ const decoded = decodeState(shape);
2238
+ state.shape = decoded;
2239
+ const time = decodeClock(clock);
2240
+ el("overview-cards", HTMLElement).innerHTML = cards([
2241
+ {
2242
+ k: "Service",
2243
+ v: esc(typeof health.service === "string" && health.service !== "" ? health.service : config.service)
2244
+ },
2245
+ {
2246
+ k: "Status",
2247
+ v: esc(typeof health.status === "string" && health.status !== "" ? health.status : "ok")
2248
+ },
2249
+ { k: "Namespace", v: esc(state.namespace) },
2250
+ { k: "Collections", v: String(decoded.collections.length) },
2251
+ {
2252
+ k: "Records",
2253
+ v: String(decoded.collections.reduce((sum, item) => sum + item.count, 0))
2254
+ },
2255
+ { k: "Clock", v: clockCard(time.now) }
2256
+ ]);
2257
+ el("overview-requests", HTMLElement).innerHTML = table([
2258
+ { label: "Method", cell: (row) => esc(row.method) },
2259
+ { label: "Path", cell: (row) => esc(row.path) },
2260
+ {
2261
+ label: "Status",
2262
+ cell: (row) => `<span class="status-${row.status.charAt(0)}">${esc(row.status)}</span>`
2263
+ },
2264
+ { label: "Operation", cell: (row) => esc(row.operationId) }
2265
+ ], decodeJournal(journal));
2266
+ };
2267
+ const schemaChips = (collection) => {
2268
+ if (collection.fields.length === 0) {
2269
+ return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>';
2270
+ }
2271
+ return `<div class="chips">${collection.fields.map((field) => `<span class="chip">${esc(field.name)} <i>${esc(field.kind)}${field.optional ? "?" : ""}</i></span>`).join("")}</div>`;
2272
+ };
2273
+ const renderState = async () => {
2274
+ const shape = decodeState(await api("GET", "/state"));
2275
+ state.shape = shape;
2276
+ const host = el("collections", HTMLElement);
2277
+ if (shape.collections.length === 0) {
2278
+ host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>';
2279
+ el("schema", HTMLElement).innerHTML = "";
2280
+ el("records", HTMLElement).innerHTML = "";
2281
+ return;
2282
+ }
2283
+ if (!shape.collections.some((item) => item.name === state.collection)) {
2284
+ const first = shape.collections[0];
2285
+ if (first === void 0)
2286
+ return;
2287
+ state.collection = first.name;
2288
+ }
2289
+ 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("");
2290
+ const selected = shape.collections.find((item) => item.name === state.collection);
2291
+ if (selected === void 0)
2292
+ return;
2293
+ const description = selected.description ? `<p class="lede">${esc(selected.description)}</p>` : "";
2294
+ el("schema", HTMLElement).innerHTML = `<h3>${esc(selected.label)}</h3>${description}${schemaChips(selected)}`;
2295
+ const page = await api("GET", `/state/${encodeURIComponent(state.collection)}?limit=50`);
2296
+ el("records", HTMLElement).innerHTML = table([
2297
+ { label: "Id", cell: (row) => `<code>${esc(row.id)}</code>` },
2298
+ { label: "Seq", cell: (row) => esc(row.seq) },
2299
+ { label: "Value", cell: (row) => preview(row.value) }
2300
+ ], decodeRecords(page));
2301
+ };
2302
+ const renderClock = async () => {
2303
+ const clock = decodeClock(await api("GET", "/clock"));
2304
+ 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>`;
2305
+ const freeze = el("freeze", HTMLButtonElement);
2306
+ freeze.textContent = clock.frozen ? "Unfreeze" : "Freeze";
2307
+ freeze.dataset.frozen = clock.frozen ? "1" : "0";
2308
+ };
2309
+ const renderFaults = async () => {
2310
+ const [faults, presets] = await Promise.all([
2311
+ api("GET", "/faults"),
2312
+ api("GET", "/faults/presets").catch(() => ({ presets: [] }))
2313
+ ]);
2314
+ const select = el("fault-preset", HTMLSelectElement);
2315
+ const current = select.value;
2316
+ const list = faultPresetList(presets).presets;
2317
+ select.innerHTML = `<option value="">Custom rule</option>${list.map((preset) => `<option value="${esc(preset.name)}">${esc(preset.name)}</option>`).join("")}`;
2318
+ select.value = current;
2319
+ el("fault-list", HTMLElement).innerHTML = table([
2320
+ { label: "Id", cell: (row) => esc(row.id) },
2321
+ {
2322
+ label: "Match",
2323
+ cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter((part) => Boolean(part)).join(" ") || "all")
2324
+ },
2325
+ {
2326
+ label: "Effect",
2327
+ cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || ""))
2328
+ }
2329
+ ], decodeFaults(faults));
2330
+ };
2331
+ const renderJournal = async () => {
2332
+ const journal = await api("GET", "/requests?limit=100");
2333
+ el("journal", HTMLElement).innerHTML = table([
2334
+ { label: "When", cell: (row) => esc(row.at) },
2335
+ { label: "Method", cell: (row) => esc(row.method) },
2336
+ { label: "Path", cell: (row) => esc(row.path) },
2337
+ { label: "Status", cell: (row) => esc(row.status) },
2338
+ { label: "ms", cell: (row) => esc(row.durationMs) }
2339
+ ], decodeJournal(journal));
2340
+ };
2341
+ const renderRoutes = async () => {
2342
+ const data = await api("GET", "/");
2343
+ if (!isRecord2(data))
2344
+ throw new Error("Route list was not an object");
2345
+ const routes = requireList(data.routes, "Routes").map((route) => String(route));
2346
+ el("route-list", HTMLElement).innerHTML = routes.map((route) => {
2347
+ const extra = standard.has(route) ? "" : " <span class='badge'>extension</span>";
2348
+ const parts = route.split(" ");
2349
+ const method = parts[0] ?? "";
2350
+ const path = parts.slice(1).join(" ");
2351
+ 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>`;
2352
+ }).join("");
2353
+ };
2354
+ const renderers = {
2355
+ overview: renderOverview,
2356
+ state: renderState,
2357
+ clock: renderClock,
2358
+ faults: renderFaults,
2359
+ journal: renderJournal,
2360
+ routes: renderRoutes
2361
+ };
2362
+ async function refresh() {
2363
+ clearError();
2364
+ const render = renderers[state.view];
2365
+ if (render)
2366
+ await render();
2367
+ }
2368
+ el("namespace", HTMLSelectElement).addEventListener("change", () => {
2369
+ state.namespace = el("namespace", HTMLSelectElement).value;
2370
+ refresh().catch(showError);
2371
+ });
2372
+ keyInput.addEventListener("change", () => {
2373
+ state.key = keyInput.value.trim();
2374
+ sessionStorage.setItem("mockingbird-admin-key", state.key);
2375
+ refresh().catch(showError);
2376
+ });
2377
+ el("refresh", HTMLButtonElement).addEventListener("click", () => {
2378
+ refresh().catch(showError);
2379
+ });
2380
+ el("collections", HTMLElement).addEventListener("click", (event) => {
2381
+ const target = event.target;
2382
+ if (!(target instanceof Element))
2383
+ return;
2384
+ const button = target.closest("[data-collection]");
2385
+ if (!(button instanceof HTMLElement))
2386
+ return;
2387
+ const collection = button.dataset.collection;
2388
+ if (collection === void 0)
2389
+ return;
2390
+ state.collection = collection;
2391
+ renderState().catch(showError);
2392
+ });
2393
+ el("records", HTMLElement).addEventListener("click", (event) => {
2394
+ const target = event.target;
2395
+ if (!(target instanceof Element))
2396
+ return;
2397
+ const row = target.closest("[data-id]");
2398
+ if (!(row instanceof HTMLElement))
2399
+ return;
2400
+ const id = row.dataset.id;
2401
+ if (id === void 0)
2402
+ return;
2403
+ openEditor(id).catch(showError);
2404
+ });
2405
+ const editor = el("editor", HTMLDialogElement);
2406
+ const editorId = el("editor-id", HTMLInputElement);
2407
+ const editorValue = el("editor-value", HTMLTextAreaElement);
2408
+ const editorDelete = el("editor-delete", HTMLButtonElement);
2409
+ async function openEditor(id) {
2410
+ state.editing = id;
2411
+ el("editor-title", HTMLElement).textContent = id ? "Edit record" : "New record";
2412
+ editorId.value = id ?? "";
2413
+ editorId.readOnly = id !== null;
2414
+ editorDelete.hidden = id === null;
2415
+ if (id) {
2416
+ const record = await api("GET", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`);
2417
+ if (!isRecord2(record))
2418
+ throw new Error("Record response was not an object");
2419
+ editorValue.value = JSON.stringify(record.value, null, 2) ?? "";
2420
+ } else {
2421
+ editorValue.value = "{\n \n}";
2422
+ }
2423
+ editor.showModal();
2424
+ }
2425
+ el("new-record", HTMLButtonElement).addEventListener("click", () => {
2426
+ openEditor(null).catch(showError);
2427
+ });
2428
+ el("editor-form", HTMLFormElement).addEventListener("submit", (event) => {
2429
+ const submitter = event.submitter;
2430
+ if (!(submitter instanceof HTMLButtonElement) || submitter.value !== "save")
2431
+ return;
2432
+ event.preventDefault();
2433
+ let value;
2434
+ try {
2435
+ value = readJson2(editorValue.value);
2436
+ } catch {
2437
+ showError(new Error("Value is not JSON"));
2438
+ return;
2439
+ }
2440
+ const id = editorId.value.trim();
2441
+ const path = `/state/${encodeURIComponent(state.collection)}`;
2442
+ const request = state.editing ? api("PUT", `${path}/${encodeURIComponent(id)}`, { value }) : api("POST", path, { id: id || void 0, value });
2443
+ request.then(() => {
2444
+ editor.close();
2445
+ return renderState();
2446
+ }).catch(showError);
2447
+ });
2448
+ editorDelete.addEventListener("click", () => {
2449
+ const id = state.editing;
2450
+ if (!id)
2451
+ return;
2452
+ api("DELETE", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`).then(() => {
2453
+ editor.close();
2454
+ return renderState();
2455
+ }).catch(showError);
2456
+ });
2457
+ for (const button of document.querySelectorAll("[data-advance]")) {
2458
+ if (!(button instanceof HTMLButtonElement))
2459
+ continue;
2460
+ button.addEventListener("click", () => {
2461
+ const raw = button.dataset.advance;
2462
+ if (raw === void 0)
2463
+ return;
2464
+ api("POST", "/clock", { advance: Number(raw) }).then(() => renderClock()).catch(showError);
2465
+ });
2466
+ }
2467
+ el("freeze", HTMLButtonElement).addEventListener("click", () => {
2468
+ const frozen = el("freeze", HTMLButtonElement).dataset.frozen === "1";
2469
+ api("POST", "/clock", { freeze: !frozen }).then(() => renderClock()).catch(showError);
2470
+ });
2471
+ el("clock-reset", HTMLButtonElement).addEventListener("click", () => {
2472
+ api("POST", "/clock", { reset: true }).then(() => renderClock()).catch(showError);
2473
+ });
2474
+ el("clock-set", HTMLFormElement).addEventListener("submit", (event) => {
2475
+ event.preventDefault();
2476
+ const raw = el("clock-instant", HTMLInputElement).value.trim();
2477
+ const set = /^-?\d+$/.test(raw) ? Number(raw) : raw;
2478
+ api("POST", "/clock", { set }).then(() => renderClock()).catch(showError);
2479
+ });
2480
+ el("fault-form", HTMLFormElement).addEventListener("submit", (event) => {
2481
+ event.preventDefault();
2482
+ const preset = el("fault-preset", HTMLSelectElement).value;
2483
+ if (preset !== "") {
2484
+ api("POST", "/faults", { preset }).then(() => renderFaults()).catch(showError);
2485
+ return;
2486
+ }
2487
+ const status = Number(el("fault-status", HTMLInputElement).value);
2488
+ if (!Number.isFinite(status)) {
2489
+ showError(new Error("Status must be a number, or choose a preset"));
2490
+ return;
2491
+ }
2492
+ const path = el("fault-path", HTMLInputElement).value;
2493
+ const method = el("fault-method", HTMLInputElement).value;
2494
+ api("POST", "/faults", {
2495
+ status,
2496
+ ...path !== "" ? { pathPrefix: path } : {},
2497
+ ...method !== "" ? { method } : {}
2498
+ }).then(() => renderFaults()).catch(showError);
2499
+ });
2500
+ el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
2501
+ api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
2502
+ });
2503
+ el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
2504
+ api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
2505
+ });
2506
+ el("route-list", HTMLElement).addEventListener("click", (event) => {
2507
+ const target = event.target;
2508
+ if (!(target instanceof Element))
2509
+ return;
2510
+ const button = target.closest("[data-route]");
2511
+ if (!(button instanceof HTMLElement))
2512
+ return;
2513
+ const route = button.dataset.route;
2514
+ if (route === void 0)
2515
+ return;
2516
+ state.route = route;
2517
+ el("route-title", HTMLElement).textContent = route;
2518
+ const method = route.split(" ")[0] ?? "";
2519
+ el("route-body", HTMLTextAreaElement).disabled = method === "GET" || method === "DELETE";
2520
+ });
2521
+ el("route-form", HTMLFormElement).addEventListener("submit", (event) => {
2522
+ event.preventDefault();
2523
+ const send = async () => {
2524
+ if (state.route === null)
2525
+ throw new Error("Choose a route first");
2526
+ const parts = state.route.split(" ");
2527
+ const method = parts[0];
2528
+ const path = parts.slice(1).join(" ");
2529
+ if (method === void 0)
2530
+ throw new Error("Choose a route first");
2531
+ const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_match, key) => prompt(`Value for ${key}`) || "");
2532
+ let body;
2533
+ const routeBody = el("route-body", HTMLTextAreaElement);
2534
+ if (method !== "GET" && method !== "DELETE" && routeBody.value.trim() !== "") {
2535
+ try {
2536
+ body = readJson2(routeBody.value);
2537
+ } catch {
2538
+ throw new Error("Body is not JSON");
2539
+ }
2540
+ }
2541
+ const data = await api(method, concrete, body);
2542
+ el("route-result", HTMLElement).textContent = JSON.stringify(data, null, 2);
2543
+ };
2544
+ send().catch(showError);
2545
+ });
2546
+ const decodePanel = (value) => {
2547
+ if (!isRecord2(value) || typeof value.id !== "string" || typeof value.title !== "string") {
2548
+ return void 0;
2549
+ }
2550
+ const panel = {
2551
+ id: value.id,
2552
+ title: value.title,
2553
+ description: typeof value.description === "string" ? value.description : "",
2554
+ html: typeof value.html === "string" ? value.html : ""
2555
+ };
2556
+ if (typeof value.script === "string")
2557
+ panel.script = value.script;
2558
+ return panel;
2559
+ };
2560
+ const mountPanels = (panels) => {
2561
+ const nav = el("nav", HTMLElement);
2562
+ const views = el("views", HTMLElement);
2563
+ for (const panel of panels) {
2564
+ const button = document.createElement("button");
2565
+ button.type = "button";
2566
+ button.dataset.view = panel.id;
2567
+ button.textContent = panel.title;
2568
+ nav.append(button);
2569
+ const section = document.createElement("section");
2570
+ section.className = "view";
2571
+ section.id = `view-${panel.id}`;
2572
+ section.hidden = true;
2573
+ const title = document.createElement("h2");
2574
+ title.textContent = panel.title;
2575
+ const lede = document.createElement("p");
2576
+ lede.className = "lede";
2577
+ lede.textContent = panel.description;
2578
+ const body = document.createElement("div");
2579
+ body.className = "panel-body";
2580
+ body.id = `panel-${panel.id}`;
2581
+ body.innerHTML = panel.html;
2582
+ section.append(title, lede, body);
2583
+ views.append(section);
2584
+ if (panel.script === void 0)
2585
+ continue;
2586
+ try {
2587
+ const compiled = new Function("root", "api", panel.script);
2588
+ if (!isPanelScript(compiled))
2589
+ throw new Error("panel script did not compile");
2590
+ compiled(body, panelApi);
2591
+ } catch (error2) {
2592
+ const note = document.createElement("p");
2593
+ note.className = "banner show";
2594
+ note.textContent = error2 instanceof Error ? error2.message : String(error2);
2595
+ body.append(note);
2596
+ }
2597
+ }
2598
+ };
2599
+ startAdminBrand({
2600
+ document,
2601
+ $: (id) => document.getElementById(id),
2602
+ params,
2603
+ location: { href: location.href, origin: location.origin },
2604
+ fetch,
2605
+ brandsUrl: config.brandsUrl
2606
+ });
2607
+ loadNamespaces().then(() => api("GET", "/ui/manifest")).then((manifest) => {
2608
+ const panels = requireList(isRecord2(manifest) ? manifest.panels : void 0, "Panels").flatMap((panel) => {
2609
+ const decoded = decodePanel(panel);
2610
+ return decoded === void 0 ? [] : [decoded];
2611
+ });
2612
+ mountPanels(panels);
2613
+ return refresh();
2614
+ }).catch(showError);
2615
+ }
2616
+ var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
2617
+ var adminClientSource = (config) => {
2618
+ const payload = JSON.stringify(config).replace(/</g, "\\u003c");
2619
+ return `<script>
2620
+ ${clientPrelude}
2621
+ bootAdmin(${payload});
2622
+ </script>`;
2623
+ };
2624
+
1399
2625
  // ../core/dist/surface.js
1400
2626
  var defineMock = (runtime) => runtime;
1401
2627
  var STANDARD_ADMIN_ROUTES = [
@@ -1464,94 +2690,106 @@ var escapeHtml = (value) => value.replace(/[&<>"']/g, (char) => {
1464
2690
  return "&#39;";
1465
2691
  }
1466
2692
  });
2693
+ var ADMIN_BRANDS_URL = "https://mockingbird.chrisvouga.dev/brands.json";
1467
2694
  var renderAdminDocument = (service) => {
1468
2695
  const name = escapeHtml(service);
1469
- const standard = JSON.stringify(STANDARD_ADMIN_ROUTES);
2696
+ const script = adminClientSource({
2697
+ standardRoutes: STANDARD_ADMIN_ROUTES,
2698
+ adminPrefix: ADMIN_PREFIX,
2699
+ adminKeyHeader: ADMIN_KEY_HEADER,
2700
+ brandsUrl: ADMIN_BRANDS_URL,
2701
+ service
2702
+ });
1470
2703
  return `<!DOCTYPE html>
1471
2704
  <html lang="en">
1472
2705
  <head>
1473
2706
  <meta charset="utf-8">
1474
2707
  <meta name="viewport" content="width=device-width, initial-scale=1">
1475
2708
  <meta name="color-scheme" content="light dark">
1476
- <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">
2709
+ <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">
1477
2710
  <title>${name} admin</title>
1478
2711
  <style>
2712
+ ${CSS_RESET}
1479
2713
  :root {
1480
2714
  color-scheme: light;
1481
- --bg: #f6f5f2;
2715
+ --bg: #fafafa;
1482
2716
  --bg-elev: #ffffff;
1483
- --bg-muted: #eeece6;
1484
- --bg-inset: #f3f1eb;
1485
- --line: #e2dfd6;
1486
- --line-strong: #d0ccc1;
1487
- --ink: #1c1915;
1488
- --muted: #5e584f;
1489
- --faint: #8a8378;
1490
- --accent: #3d4f3a;
1491
- --accent-ink: #f7f6f2;
1492
- --accent-soft: #e5ece2;
1493
- --ok: #1f7a45;
1494
- --ok-soft: #e5f4eb;
1495
- --warn: #9a6700;
1496
- --warn-soft: #fbf3df;
1497
- --err: #a33b32;
1498
- --err-soft: #fdecea;
1499
- --shadow: 0 1px 2px rgb(28 25 21 / 0.05), 0 12px 32px rgb(28 25 21 / 0.06);
1500
- --sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
2717
+ --bg-muted: #f4f4f5;
2718
+ --bg-inset: #f4f4f5;
2719
+ --line: #dedee3;
2720
+ --line-strong: #c4c4cc;
2721
+ --ink: #18181b;
2722
+ --muted: #62626b;
2723
+ --faint: #71717a;
2724
+ --accent: #27272a;
2725
+ --accent-ink: #ffffff;
2726
+ --accent-soft: #f4f4f5;
2727
+ --ok: #166534;
2728
+ --ok-soft: #f0fdf4;
2729
+ --warn: #a16207;
2730
+ --warn-soft: #fefce8;
2731
+ --err: #a82d32;
2732
+ --err-soft: #fef2f2;
2733
+ --shadow: none;
2734
+ --sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
1501
2735
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
1502
- --radius: 14px;
1503
- --header: 60px;
1504
- --nav: 220px;
2736
+ --radius: 10px;
2737
+ --header: 56px;
2738
+ --nav: 200px;
1505
2739
  }
1506
2740
  :root[data-theme="dark"] {
1507
2741
  color-scheme: dark;
1508
- --bg: #141311;
1509
- --bg-elev: #1e1c19;
1510
- --bg-muted: #26231f;
1511
- --bg-inset: #181715;
1512
- --line: #34302b;
1513
- --line-strong: #4a453d;
1514
- --ink: #f4f1ea;
1515
- --muted: #c4bdb1;
1516
- --faint: #8f877c;
1517
- --accent: #c5d5b8;
1518
- --accent-ink: #1a2118;
1519
- --accent-soft: #2a3328;
1520
- --ok: #8fd6a8;
1521
- --ok-soft: #1a2e22;
1522
- --warn: #f0cc78;
1523
- --warn-soft: #332911;
1524
- --err: #f0a8a2;
1525
- --err-soft: #3a201e;
1526
- --shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 16px 40px rgb(0 0 0 / 0.28);
2742
+ --bg: #111113;
2743
+ --bg-elev: #19191c;
2744
+ --bg-muted: #242428;
2745
+ --bg-inset: #111113;
2746
+ --line: #36363c;
2747
+ --line-strong: #52525b;
2748
+ --ink: #f4f4f5;
2749
+ --muted: #a9a9b2;
2750
+ --faint: #71717a;
2751
+ --accent: #e4e4e7;
2752
+ --accent-ink: #18181b;
2753
+ --accent-soft: #242428;
2754
+ --ok: #86efac;
2755
+ --ok-soft: #14241b;
2756
+ --warn: #fde68a;
2757
+ --warn-soft: #2a2410;
2758
+ --err: #ffaaaa;
2759
+ --err-soft: #2c1516;
2760
+ --shadow: none;
1527
2761
  }
1528
2762
  @media (prefers-color-scheme: dark) {
1529
2763
  :root:not([data-theme="light"]) {
1530
2764
  color-scheme: dark;
1531
- --bg: #141311;
1532
- --bg-elev: #1e1c19;
1533
- --bg-muted: #26231f;
1534
- --bg-inset: #181715;
1535
- --line: #34302b;
1536
- --line-strong: #4a453d;
1537
- --ink: #f4f1ea;
1538
- --muted: #c4bdb1;
1539
- --faint: #8f877c;
1540
- --accent: #c5d5b8;
1541
- --accent-ink: #1a2118;
1542
- --accent-soft: #2a3328;
1543
- --ok: #8fd6a8;
1544
- --ok-soft: #1a2e22;
1545
- --warn: #f0cc78;
1546
- --warn-soft: #332911;
1547
- --err: #f0a8a2;
1548
- --err-soft: #3a201e;
1549
- --shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 16px 40px rgb(0 0 0 / 0.28);
2765
+ --bg: #111113;
2766
+ --bg-elev: #19191c;
2767
+ --bg-muted: #242428;
2768
+ --bg-inset: #111113;
2769
+ --line: #36363c;
2770
+ --line-strong: #52525b;
2771
+ --ink: #f4f4f5;
2772
+ --muted: #a9a9b2;
2773
+ --faint: #71717a;
2774
+ --accent: #e4e4e7;
2775
+ --accent-ink: #18181b;
2776
+ --accent-soft: #242428;
2777
+ --ok: #86efac;
2778
+ --ok-soft: #14241b;
2779
+ --warn: #fde68a;
2780
+ --warn-soft: #2a2410;
2781
+ --err: #ffaaaa;
2782
+ --err-soft: #2c1516;
2783
+ --shadow: none;
1550
2784
  }
1551
2785
  }
1552
- * { box-sizing: border-box; }
1553
- html, body { margin: 0; height: 100%; }
2786
+ /* Fill the viewport, then grow with the content so the background covers the scroll. */
2787
+ html { height: 100%; }
1554
2788
  body {
2789
+ margin: 0;
2790
+ min-height: 100%;
2791
+ display: flex;
2792
+ flex-direction: column;
1555
2793
  font-family: var(--sans);
1556
2794
  background: var(--bg);
1557
2795
  color: var(--ink);
@@ -1560,24 +2798,37 @@ var renderAdminDocument = (service) => {
1560
2798
  padding-right: env(safe-area-inset-right);
1561
2799
  -webkit-text-size-adjust: 100%;
1562
2800
  }
1563
- button, input, select, textarea { font: inherit; color: inherit; }
1564
- button { cursor: pointer; }
1565
2801
  :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
1566
- @media (prefers-reduced-motion: reduce) {
1567
- * { scroll-behavior: auto !important; transition: none !important; }
1568
- }
1569
- .app { min-height: 100%; display: flex; flex-direction: column; }
2802
+ .app { flex: 1 0 auto; display: flex; flex-direction: column; min-width: 0; width: 100%; }
1570
2803
  header.top {
1571
2804
  position: sticky; top: 0; z-index: 5;
1572
2805
  display: flex; align-items: center; gap: 12px;
1573
2806
  min-height: var(--header); padding: 10px 16px;
1574
- background: color-mix(in srgb, var(--bg-elev) 92%, transparent);
2807
+ background: var(--bg-elev);
1575
2808
  border-bottom: 1px solid var(--line);
1576
- backdrop-filter: blur(10px);
1577
- }
1578
- .brand { display: flex; flex-direction: column; min-width: 0; margin-right: auto; }
1579
- .brand strong { font-size: 15px; letter-spacing: -0.01em; }
1580
- .brand span { color: var(--faint); font-size: 12px; }
2809
+ min-width: 0; max-width: 100%;
2810
+ }
2811
+ .brand {
2812
+ display: flex; flex-flow: row wrap; align-items: center; gap: 14px;
2813
+ min-width: 0; max-width: 100%; margin-right: auto;
2814
+ }
2815
+ .brand-id { display: flex; flex-direction: column; min-width: 0; flex: none; }
2816
+ .brand-id strong { font-size: 15px; letter-spacing: -0.01em; }
2817
+ .brand-id span { color: var(--faint); font-size: 12px; }
2818
+ .vendor {
2819
+ display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
2820
+ min-width: 0; max-width: 100%; font-size: 12px; color: var(--muted);
2821
+ }
2822
+ .vendor[hidden] { display: none; }
2823
+ /* Logos are drawn for a light tile, same as the docs site, so the plate stays white. */
2824
+ .vendor img {
2825
+ width: 16px; height: 16px; padding: 2px; box-sizing: content-box;
2826
+ border-radius: 4px; border: 1px solid var(--line); background: #fff; object-fit: contain;
2827
+ }
2828
+ .vendor-name { color: var(--ink); font-weight: 600; }
2829
+ .vendor-sep { color: var(--faint); }
2830
+ .vendor-link { color: inherit; text-decoration: none; }
2831
+ .vendor-link:hover { color: var(--ink); text-decoration: underline; }
1581
2832
  .controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
1582
2833
  label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
1583
2834
  label.field input, label.field select, .controls input[type="password"] {
@@ -1595,7 +2846,7 @@ var renderAdminDocument = (service) => {
1595
2846
  record or the full nav row would widen the page past the viewport. */
1596
2847
  .layout {
1597
2848
  display: grid; grid-template-columns: var(--nav) minmax(0, 1fr);
1598
- flex: 1; min-height: 0; min-width: 0;
2849
+ flex: 1 0 auto; min-width: 0;
1599
2850
  }
1600
2851
  nav.side {
1601
2852
  border-right: 1px solid var(--line); padding: 12px;
@@ -1610,7 +2861,7 @@ var renderAdminDocument = (service) => {
1610
2861
  main { padding: 20px; max-width: 1100px; width: 100%; min-width: 0; }
1611
2862
  .view h2 { margin: 0 0 4px; font-size: 22px; letter-spacing: -0.02em; }
1612
2863
  .lede { margin: 0 0 16px; color: var(--muted); }
1613
- .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
2864
+ .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr)); gap: 12px; }
1614
2865
  .card {
1615
2866
  background: var(--bg-elev); border: 1px solid var(--line); border-radius: var(--radius);
1616
2867
  padding: 14px 16px; box-shadow: var(--shadow);
@@ -1649,8 +2900,8 @@ var renderAdminDocument = (service) => {
1649
2900
  .banner.show { display: block; }
1650
2901
  .empty { color: var(--muted); padding: 24px 8px; }
1651
2902
  dialog {
1652
- border: 1px solid var(--line); border-radius: 16px; padding: 0; background: var(--bg-elev);
1653
- color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow: var(--shadow);
2903
+ border: 1px solid var(--line); border-radius: var(--radius); padding: 0; background: var(--bg-elev);
2904
+ color: var(--ink); width: min(640px, calc(100% - 24px)); max-width: calc(100vw - 24px); box-shadow: none;
1654
2905
  }
1655
2906
  dialog::backdrop { background: rgb(20 19 17 / 0.45); }
1656
2907
  dialog form, .dialog-body { padding: 16px; display: flex; flex-direction: column; gap: 10px; }
@@ -1677,7 +2928,7 @@ var renderAdminDocument = (service) => {
1677
2928
  padding-top: max(10px, env(safe-area-inset-top));
1678
2929
  }
1679
2930
  input, select, textarea { font-size: 16px; }
1680
- .controls { width: 100%; }
2931
+ .brand, .vendor, .controls { width: 100%; }
1681
2932
  label.field, label.field select, label.field input { flex: 1; min-width: 0; }
1682
2933
  .layout { grid-template-columns: minmax(0, 1fr); }
1683
2934
  nav.side {
@@ -1708,8 +2959,11 @@ var renderAdminDocument = (service) => {
1708
2959
  <div class="app" data-service="${name}" data-mockingbird-admin>
1709
2960
  <header class="top">
1710
2961
  <div class="brand">
1711
- <strong>${name}</strong>
1712
- <span>Mockingbird admin</span>
2962
+ <div class="brand-id">
2963
+ <strong>${name}</strong>
2964
+ <span>Admin</span>
2965
+ </div>
2966
+ <div class="vendor" id="vendor" hidden></div>
1713
2967
  </div>
1714
2968
  <div class="controls">
1715
2969
  <label class="field">Namespace
@@ -1839,460 +3093,7 @@ var renderAdminDocument = (service) => {
1839
3093
  </div>
1840
3094
  </form>
1841
3095
  </dialog>
1842
- <script>
1843
- (() => {
1844
- const STANDARD = new Set(${standard})
1845
- const state = {
1846
- namespace: "default",
1847
- key: sessionStorage.getItem("mockingbird-admin-key") || "",
1848
- view: "overview",
1849
- collection: "",
1850
- shape: null,
1851
- route: null,
1852
- editing: null
1853
- }
1854
- const $ = (id) => document.getElementById(id)
1855
- const banner = $("banner")
1856
- const keyInput = $("admin-key")
1857
- keyInput.value = state.key
1858
- const params = new URLSearchParams(location.search)
1859
- if (params.get("key")) {
1860
- state.key = params.get("key")
1861
- keyInput.value = state.key
1862
- sessionStorage.setItem("mockingbird-admin-key", state.key)
1863
- params.delete("key")
1864
- const next = params.toString()
1865
- history.replaceState(null, "", location.pathname + (next ? "?" + next : ""))
1866
- }
1867
- if (params.get("namespace")) state.namespace = params.get("namespace")
1868
-
1869
- const showError = (error) => {
1870
- banner.textContent = error instanceof Error ? error.message : String(error)
1871
- banner.classList.add("show")
1872
- }
1873
- const clearError = () => banner.classList.remove("show")
1874
-
1875
- async function originGet(path) {
1876
- const response = await fetch(path, { headers: { accept: "application/json" } })
1877
- if (!response.ok) throw new Error(response.status + " " + path)
1878
- return response.json()
1879
- }
1880
- async function api(method, path, body) {
1881
- const headers = { accept: "application/json" }
1882
- if (state.key) headers["${ADMIN_KEY_HEADER}"] = state.key
1883
- if (state.namespace) headers["x-mockingbird-namespace"] = state.namespace
1884
- if (body !== undefined) headers["content-type"] = "application/json"
1885
- const response = await fetch("${ADMIN_PREFIX}" + path, {
1886
- method,
1887
- headers,
1888
- body: body === undefined ? undefined : JSON.stringify(body)
1889
- })
1890
- const text = await response.text()
1891
- let data = null
1892
- if (text) {
1893
- try { data = JSON.parse(text) } catch { data = { raw: text } }
1894
- }
1895
- if (response.status === 401) {
1896
- keyInput.focus()
1897
- throw new Error((data && data.error && data.error.message) || "Admin key required")
1898
- }
1899
- if (!response.ok) {
1900
- throw new Error((data && data.error && data.error.message) || (response.status + " " + method + " " + path))
1901
- }
1902
- return data
1903
- }
1904
- const panelApi = {
1905
- get: (path) => api("GET", path),
1906
- send: (method, path, body) => api(method, path, body),
1907
- namespace: () => state.namespace
1908
- }
1909
-
1910
- function setTheme(theme) {
1911
- if (theme) document.documentElement.dataset.theme = theme
1912
- else delete document.documentElement.dataset.theme
1913
- }
1914
- const storedTheme = localStorage.getItem("mockingbird-admin-theme")
1915
- if (storedTheme) setTheme(storedTheme)
1916
- $("theme").addEventListener("click", () => {
1917
- const current = document.documentElement.dataset.theme
1918
- const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches
1919
- const next = dark ? "light" : "dark"
1920
- localStorage.setItem("mockingbird-admin-theme", next)
1921
- setTheme(next)
1922
- })
1923
-
1924
- function show(view) {
1925
- state.view = view
1926
- for (const button of document.querySelectorAll("#nav button")) {
1927
- button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false")
1928
- }
1929
- for (const section of document.querySelectorAll("main .view")) {
1930
- section.hidden = section.id !== "view-" + view
1931
- }
1932
- }
1933
- document.getElementById("nav").addEventListener("click", (event) => {
1934
- const button = event.target.closest("button[data-view]")
1935
- if (!button) return
1936
- show(button.dataset.view)
1937
- refresh().catch(showError)
1938
- })
1939
-
1940
- function cards(items) {
1941
- return '<div class="grid">' + items.map((item) =>
1942
- '<article class="card"><div class="k">' + item.k + '</div><div class="v">' + item.v + '</div></article>'
1943
- ).join("") + "</div>"
1944
- }
1945
- function table(columns, rows) {
1946
- if (rows.length === 0) return '<p class="empty">Nothing here yet.</p>'
1947
- const head = "<tr>" + columns.map((column) => "<th>" + column.label + "</th>").join("") + "</tr>"
1948
- const body = rows.map((row) => "<tr" + (row.attrs || "") + ">" + columns.map((column) =>
1949
- '<td data-label="' + column.label + '">' + column.cell(row) + "</td>"
1950
- ).join("") + "</tr>").join("")
1951
- return "<table><thead>" + head + "</thead><tbody>" + body + "</tbody></table>"
1952
- }
1953
- const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({
1954
- "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"
1955
- }[char]))
1956
- const preview = (value) => {
1957
- const text = JSON.stringify(value)
1958
- return esc(text.length > 180 ? text.slice(0, 180) + "\u2026" : text)
1959
- }
1960
- const when = (epoch) => {
1961
- const date = new Date(epoch)
1962
- return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString()
1963
- }
1964
- const clockCard = (epoch) => {
1965
- const date = new Date(epoch)
1966
- if (Number.isNaN(date.getTime())) return esc(String(epoch))
1967
- const iso = date.toISOString()
1968
- return esc(iso.slice(0, 10)) + '<span class="clock-time">' + esc(iso.slice(11, 19)) + " UTC</span>"
1969
- }
1970
-
1971
- async function loadNamespaces() {
1972
- const data = await api("GET", "/namespaces")
1973
- const select = $("namespace")
1974
- const names = data.namespaces && data.namespaces.length ? data.namespaces : [data.default || "default"]
1975
- if (!names.includes(state.namespace)) names.unshift(state.namespace)
1976
- select.innerHTML = names.map((name) =>
1977
- '<option value="' + esc(name) + '">' + esc(name) + "</option>"
1978
- ).join("")
1979
- select.value = state.namespace
1980
- }
1981
-
1982
- async function renderOverview() {
1983
- const [health, shape, clock, journal] = await Promise.all([
1984
- originGet("/health"),
1985
- api("GET", "/state"),
1986
- api("GET", "/clock"),
1987
- api("GET", "/requests?limit=8")
1988
- ])
1989
- state.shape = shape
1990
- $("overview-cards").innerHTML = cards([
1991
- { k: "Service", v: esc(health.service || "${name}") },
1992
- { k: "Status", v: esc(health.status || "ok") },
1993
- { k: "Namespace", v: esc(state.namespace) },
1994
- { k: "Collections", v: String(shape.collections.length) },
1995
- { k: "Records", v: String(shape.collections.reduce((sum, item) => sum + item.count, 0)) },
1996
- { k: "Clock", v: clockCard(clock.now) }
1997
- ])
1998
- const requests = (journal && journal.requests) || []
1999
- $("overview-requests").innerHTML = table(
2000
- [
2001
- { label: "Method", cell: (row) => esc(row.method) },
2002
- { label: "Path", cell: (row) => esc(row.path) },
2003
- { label: "Status", cell: (row) => '<span class="status-' + String(row.status).charAt(0) + '">' + esc(row.status) + "</span>" },
2004
- { label: "Operation", cell: (row) => esc(row.operationId || "") }
2005
- ],
2006
- requests
2007
- )
2008
- }
2009
-
2010
- function schemaChips(collection) {
2011
- if (!collection || collection.fields.length === 0) {
2012
- return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>'
2013
- }
2014
- return '<div class="chips">' + collection.fields.map((field) =>
2015
- '<span class="chip">' + esc(field.name) + ' <i>' + esc(field.kind) + (field.optional ? "?" : "") + "</i></span>"
2016
- ).join("") + "</div>"
2017
- }
2018
-
2019
- async function renderState() {
2020
- const shape = await api("GET", "/state")
2021
- state.shape = shape
2022
- const host = $("collections")
2023
- if (shape.collections.length === 0) {
2024
- host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>'
2025
- $("schema").innerHTML = ""
2026
- $("records").innerHTML = ""
2027
- return
2028
- }
2029
- if (!shape.collections.some((item) => item.name === state.collection)) {
2030
- state.collection = shape.collections[0].name
2031
- }
2032
- host.innerHTML = shape.collections.map((item) =>
2033
- '<button type="button" class="collection" data-collection="' + esc(item.name) + '" aria-current="' +
2034
- (item.name === state.collection ? "true" : "false") + '"><strong>' + esc(item.label) +
2035
- '</strong><small>' + esc(item.count) + " \xB7 " + esc(item.source) + "</small></button>"
2036
- ).join("")
2037
- const selected = shape.collections.find((item) => item.name === state.collection)
2038
- $("schema").innerHTML = "<h3>" + esc(selected.label) + "</h3>" +
2039
- (selected.description ? "<p class=" + '"lede">' + esc(selected.description) + "</p>" : "") +
2040
- schemaChips(selected)
2041
- const page = await api("GET", "/state/" + encodeURIComponent(state.collection) + "?limit=50")
2042
- const rows = page.records.map((record) => ({
2043
- ...record,
2044
- attrs: ' class="clickable" data-id="' + esc(record.id) + '"'
2045
- }))
2046
- $("records").innerHTML = table(
2047
- [
2048
- { label: "Id", cell: (row) => "<code>" + esc(row.id) + "</code>" },
2049
- { label: "Seq", cell: (row) => esc(row.seq) },
2050
- { label: "Value", cell: (row) => preview(row.value) }
2051
- ],
2052
- rows
2053
- )
2054
- }
2055
-
2056
- async function renderClock() {
2057
- const clock = await api("GET", "/clock")
2058
- $("clock-readout").innerHTML = "<div class='k'>Now</div><div class='v'>" + esc(when(clock.now)) +
2059
- "</div><p>" + esc(clock.now) + " ms \xB7 offset " + esc(clock.offsetMs) + " ms " +
2060
- (clock.frozen ? "<span class='badge warn'>frozen</span>" : "<span class='badge ok'>live</span>") + "</p>"
2061
- $("freeze").textContent = clock.frozen ? "Unfreeze" : "Freeze"
2062
- $("freeze").dataset.frozen = clock.frozen ? "1" : "0"
2063
- }
2064
-
2065
- async function renderFaults() {
2066
- const [faults, presets] = await Promise.all([
2067
- api("GET", "/faults"),
2068
- api("GET", "/faults/presets").catch(() => ({ presets: [] }))
2069
- ])
2070
- const select = $("fault-preset")
2071
- const current = select.value
2072
- const list = presets.presets || []
2073
- select.innerHTML = '<option value="">Custom rule</option>' + list.map((preset) =>
2074
- '<option value="' + esc(preset.name) + '">' + esc(preset.name) + "</option>"
2075
- ).join("")
2076
- select.value = current
2077
- const rules = faults.faults || []
2078
- $("fault-list").innerHTML = table(
2079
- [
2080
- { label: "Id", cell: (row) => esc(row.id) },
2081
- { label: "Match", cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter(Boolean).join(" ") || "all") },
2082
- { label: "Effect", cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || "")) }
2083
- ],
2084
- rules
2085
- )
2086
- }
2087
-
2088
- async function renderJournal() {
2089
- const journal = await api("GET", "/requests?limit=100")
2090
- $("journal").innerHTML = table(
2091
- [
2092
- { label: "When", cell: (row) => esc(row.at || "") },
2093
- { label: "Method", cell: (row) => esc(row.method) },
2094
- { label: "Path", cell: (row) => esc(row.path) },
2095
- { label: "Status", cell: (row) => esc(row.status) },
2096
- { label: "ms", cell: (row) => esc(row.durationMs) }
2097
- ],
2098
- journal.requests || []
2099
- )
2100
- }
2101
-
2102
- async function renderRoutes() {
2103
- const data = await api("GET", "/")
2104
- const routes = data.routes || []
2105
- $("route-list").innerHTML = routes.map((route) => {
2106
- const extra = STANDARD.has(route) ? "" : " <span class='badge'>extension</span>"
2107
- return '<div class="route"><span class="method">' + esc(route.split(" ")[0]) +
2108
- '</span><span>' + esc(route.split(" ").slice(1).join(" ")) + extra +
2109
- '</span><button class="btn" type="button" data-route="' + esc(route) + '">Use</button></div>'
2110
- }).join("")
2111
- }
2112
-
2113
- const renderers = {
2114
- overview: renderOverview,
2115
- state: renderState,
2116
- clock: renderClock,
2117
- faults: renderFaults,
2118
- journal: renderJournal,
2119
- routes: renderRoutes
2120
- }
2121
-
2122
- async function refresh() {
2123
- clearError()
2124
- if (state.view in renderers) await renderers[state.view]()
2125
- }
2126
-
2127
- $("namespace").addEventListener("change", () => {
2128
- state.namespace = $("namespace").value
2129
- refresh().catch(showError)
2130
- })
2131
- keyInput.addEventListener("change", () => {
2132
- state.key = keyInput.value.trim()
2133
- sessionStorage.setItem("mockingbird-admin-key", state.key)
2134
- refresh().catch(showError)
2135
- })
2136
- $("refresh").addEventListener("click", () => refresh().catch(showError))
2137
-
2138
- $("collections").addEventListener("click", (event) => {
2139
- const button = event.target.closest("[data-collection]")
2140
- if (!button) return
2141
- state.collection = button.dataset.collection
2142
- renderState().catch(showError)
2143
- })
2144
- $("records").addEventListener("click", (event) => {
2145
- const row = event.target.closest("[data-id]")
2146
- if (!row) return
2147
- openEditor(row.dataset.id).catch(showError)
2148
- })
2149
-
2150
- const editor = $("editor")
2151
- async function openEditor(id) {
2152
- state.editing = id || null
2153
- $("editor-title").textContent = id ? "Edit record" : "New record"
2154
- $("editor-id").value = id || ""
2155
- $("editor-id").readOnly = Boolean(id)
2156
- $("editor-delete").hidden = !id
2157
- if (id) {
2158
- const record = await api("GET", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
2159
- $("editor-value").value = JSON.stringify(record.value, null, 2)
2160
- } else {
2161
- $("editor-value").value = "{\\n \\n}"
2162
- }
2163
- editor.showModal()
2164
- }
2165
- $("new-record").addEventListener("click", () => openEditor(null).catch(showError))
2166
- $("editor-form").addEventListener("submit", (event) => {
2167
- const submitter = event.submitter
2168
- if (!submitter || submitter.value !== "save") return
2169
- event.preventDefault()
2170
- let value
2171
- try { value = JSON.parse($("editor-value").value) }
2172
- catch { showError(new Error("Value is not JSON")); return }
2173
- const id = $("editor-id").value.trim()
2174
- const path = "/state/" + encodeURIComponent(state.collection)
2175
- const request = state.editing
2176
- ? api("PUT", path + "/" + encodeURIComponent(id), { value })
2177
- : api("POST", path, { id: id || undefined, value })
2178
- request.then(() => { editor.close(); return renderState() }).catch(showError)
2179
- })
2180
- $("editor-delete").addEventListener("click", () => {
2181
- const id = state.editing
2182
- if (!id) return
2183
- api("DELETE", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
2184
- .then(() => { editor.close(); return renderState() })
2185
- .catch(showError)
2186
- })
2187
-
2188
- document.querySelectorAll("[data-advance]").forEach((button) => {
2189
- button.addEventListener("click", () => {
2190
- api("POST", "/clock", { advance: Number(button.dataset.advance) }).then(renderClock).catch(showError)
2191
- })
2192
- })
2193
- $("freeze").addEventListener("click", () => {
2194
- const frozen = $("freeze").dataset.frozen === "1"
2195
- api("POST", "/clock", { freeze: !frozen }).then(renderClock).catch(showError)
2196
- })
2197
- $("clock-reset").addEventListener("click", () => {
2198
- api("POST", "/clock", { reset: true }).then(renderClock).catch(showError)
2199
- })
2200
- $("clock-set").addEventListener("submit", (event) => {
2201
- event.preventDefault()
2202
- const raw = $("clock-instant").value.trim()
2203
- const set = /^-?\\d+$/.test(raw) ? Number(raw) : raw
2204
- api("POST", "/clock", { set }).then(renderClock).catch(showError)
2205
- })
2206
-
2207
- $("fault-form").addEventListener("submit", (event) => {
2208
- event.preventDefault()
2209
- const preset = $("fault-preset").value
2210
- const body = preset
2211
- ? { preset }
2212
- : {
2213
- status: Number($("fault-status").value),
2214
- ...($("fault-path").value ? { pathPrefix: $("fault-path").value } : {}),
2215
- ...($("fault-method").value ? { method: $("fault-method").value } : {})
2216
- }
2217
- if (!preset && !Number.isFinite(body.status)) {
2218
- showError(new Error("Status must be a number, or choose a preset"))
2219
- return
2220
- }
2221
- api("POST", "/faults", body).then(renderFaults).catch(showError)
2222
- })
2223
- $("fault-clear").addEventListener("click", () => api("DELETE", "/faults").then(renderFaults).catch(showError))
2224
- $("journal-clear").addEventListener("click", () => api("DELETE", "/requests").then(renderJournal).catch(showError))
2225
-
2226
- $("route-list").addEventListener("click", (event) => {
2227
- const button = event.target.closest("[data-route]")
2228
- if (!button) return
2229
- state.route = button.dataset.route
2230
- $("route-title").textContent = state.route
2231
- const method = state.route.split(" ")[0]
2232
- $("route-body").disabled = method === "GET" || method === "DELETE"
2233
- })
2234
- $("route-form").addEventListener("submit", async (event) => {
2235
- event.preventDefault()
2236
- if (!state.route) return showError(new Error("Choose a route first"))
2237
- const [method, path] = [state.route.split(" ")[0], state.route.split(" ").slice(1).join(" ")]
2238
- const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_, key) => prompt("Value for " + key) || "")
2239
- let body
2240
- if (method !== "GET" && method !== "DELETE" && $("route-body").value.trim()) {
2241
- try { body = JSON.parse($("route-body").value) }
2242
- catch { return showError(new Error("Body is not JSON")) }
2243
- }
2244
- try {
2245
- const data = await api(method, concrete, body)
2246
- $("route-result").textContent = JSON.stringify(data, null, 2)
2247
- } catch (error) {
2248
- showError(error)
2249
- }
2250
- })
2251
-
2252
- function mountPanels(panels) {
2253
- const nav = $("nav")
2254
- const views = $("views")
2255
- for (const panel of panels) {
2256
- const button = document.createElement("button")
2257
- button.type = "button"
2258
- button.dataset.view = panel.id
2259
- button.textContent = panel.title
2260
- nav.append(button)
2261
- const section = document.createElement("section")
2262
- section.className = "view"
2263
- section.id = "view-" + panel.id
2264
- section.hidden = true
2265
- const title = document.createElement("h2")
2266
- title.textContent = panel.title
2267
- const lede = document.createElement("p")
2268
- lede.className = "lede"
2269
- lede.textContent = panel.description || ""
2270
- const body = document.createElement("div")
2271
- body.className = "panel-body"
2272
- body.id = "panel-" + panel.id
2273
- body.innerHTML = panel.html || ""
2274
- section.append(title, lede, body)
2275
- views.append(section)
2276
- if (panel.script) {
2277
- try {
2278
- const run = new Function("root", "api", panel.script)
2279
- run(body, panelApi)
2280
- } catch (error) {
2281
- const note = document.createElement("p")
2282
- note.className = "banner show"
2283
- note.textContent = error instanceof Error ? error.message : String(error)
2284
- body.append(note)
2285
- }
2286
- }
2287
- }
2288
- }
2289
-
2290
- loadNamespaces()
2291
- .then(() => api("GET", "/ui/manifest"))
2292
- .then((manifest) => { mountPanels((manifest && manifest.panels) || []); return refresh() })
2293
- .catch(showError)
2294
- })()
2295
- </script>
3096
+ ${script}
2296
3097
  </body>
2297
3098
  </html>`;
2298
3099
  };
@@ -2309,10 +3110,10 @@ var json2 = (body) => new Response(JSON.stringify(body), {
2309
3110
  });
2310
3111
  var adminUiRoutes = (service, ui) => {
2311
3112
  const shell = () => renderAdminDocument(service);
2312
- const document2 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
3113
+ const document3 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
2313
3114
  return {
2314
- "GET /ui": () => htmlResponse(document2()),
2315
- "GET /ui/": () => htmlResponse(document2()),
3115
+ "GET /ui": () => htmlResponse(document3()),
3116
+ "GET /ui/": () => htmlResponse(document3()),
2316
3117
  "GET /ui/manifest": () => json2({
2317
3118
  service,
2318
3119
  panels: (ui?.panels ?? []).map((panel) => ({
@@ -2338,11 +3139,11 @@ var EXTENSION_KEYS = {
2338
3139
  };
2339
3140
 
2340
3141
  // ../../openapi/metadata/dist/read.js
2341
- var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3142
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2342
3143
  var extensionOf = (holder, key) => holder[key];
2343
3144
  var operationMetadata = (operation) => {
2344
3145
  const raw = extensionOf(operation, EXTENSION_KEYS.operation);
2345
- const ext = isRecord2(raw) ? raw : {};
3146
+ const ext = isRecord3(raw) ? raw : {};
2346
3147
  const supported = ext.supported ?? true;
2347
3148
  const parity = ext.parity ?? {};
2348
3149
  return {
@@ -2452,9 +3253,9 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
2452
3253
  this.name = "OperationRegistryError";
2453
3254
  }
2454
3255
  };
2455
- var verifyOperations = (document2, handlers) => {
3256
+ var verifyOperations = (document3, handlers) => {
2456
3257
  const problems = [];
2457
- const operations = listOperations(document2);
3258
+ const operations = listOperations(document3);
2458
3259
  const seen = /* @__PURE__ */ new Set();
2459
3260
  for (const operation of operations) {
2460
3261
  if (seen.has(operation.operationId))
@@ -2570,7 +3371,7 @@ var SAMPLE = 25;
2570
3371
  var WALK_LIMIT = 1e4;
2571
3372
  var SKIP_CTORS = /* @__PURE__ */ new Set(["Hono", "HonoRequest", "Router"]);
2572
3373
  var fail = (status, message) => ({ ok: false, status, message });
2573
- var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3374
+ var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2574
3375
  var kindOf = (value) => {
2575
3376
  if (value === null)
2576
3377
  return "null";
@@ -2669,7 +3470,7 @@ var absorb = (accum, value) => {
2669
3470
  const kind = kindOf(value);
2670
3471
  accum.kinds.add(kind);
2671
3472
  accum.present++;
2672
- if (!isRecord3(value))
3473
+ if (!isRecord4(value))
2673
3474
  return;
2674
3475
  for (const [key, child] of Object.entries(value)) {
2675
3476
  const nested = accum.nested.get(key) ?? blankAccum(key);
@@ -2694,7 +3495,7 @@ var inferFields = (records) => {
2694
3495
  if (records.length === 0)
2695
3496
  return [];
2696
3497
  const sample = records.slice(0, SAMPLE);
2697
- const objects = sample.filter((record) => isRecord3(record.value));
3498
+ const objects = sample.filter((record) => isRecord4(record.value));
2698
3499
  if (objects.length === 0) {
2699
3500
  const kinds = new Set(sample.map((record) => kindOf(record.value)));
2700
3501
  const list = [...kinds];
@@ -2881,7 +3682,7 @@ var patchStateRecord = (scope, name, id, value) => {
2881
3682
  const parsed = jsonValue(value);
2882
3683
  if (!parsed.ok)
2883
3684
  return parsed;
2884
- const next = isRecord3(current.value.value) && isRecord3(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
3685
+ const next = isRecord4(current.value.value) && isRecord4(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
2885
3686
  return replaceStateRecord(scope, name, id, next);
2886
3687
  };
2887
3688
  var deleteStateRecord = (scope, name, id) => {
@@ -2949,7 +3750,7 @@ var stateAdminRoutes = (routes) => {
2949
3750
  const scope = open(namespace);
2950
3751
  if (!scope.ok)
2951
3752
  return fromResult(scope, 201);
2952
- if (!isRecord3(body) || !("value" in body)) {
3753
+ if (!isRecord4(body) || !("value" in body)) {
2953
3754
  return adminError2(400, 'expected {"id?": "<id>", "value": <json>}');
2954
3755
  }
2955
3756
  const id = body.id;
@@ -2967,7 +3768,7 @@ var stateAdminRoutes = (routes) => {
2967
3768
  const scope = open(namespace);
2968
3769
  if (!scope.ok)
2969
3770
  return fromResult(scope, 200);
2970
- if (!isRecord3(body) || !("value" in body))
3771
+ if (!isRecord4(body) || !("value" in body))
2971
3772
  return adminError2(400, 'expected {"value": <json>}');
2972
3773
  const replaced = replaceStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
2973
3774
  if (replaced.ok)
@@ -2978,7 +3779,7 @@ var stateAdminRoutes = (routes) => {
2978
3779
  const scope = open(namespace);
2979
3780
  if (!scope.ok)
2980
3781
  return fromResult(scope, 200);
2981
- if (!isRecord3(body) || !("value" in body))
3782
+ if (!isRecord4(body) || !("value" in body))
2982
3783
  return adminError2(400, 'expected {"value": <json>}');
2983
3784
  const patched = patchStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
2984
3785
  if (patched.ok)
@@ -2998,7 +3799,7 @@ var stateAdminRoutes = (routes) => {
2998
3799
  };
2999
3800
 
3000
3801
  // ../core/dist/version.js
3001
- var PACKAGE_VERSION = true ? "1.4.0" : UNRELEASED_VERSION;
3802
+ var PACKAGE_VERSION = true ? "1.5.1" : UNRELEASED_VERSION;
3002
3803
 
3003
3804
  // ../core/dist/signing.js
3004
3805
  var encoder = new TextEncoder();
@@ -3335,9 +4136,9 @@ var createWebhookHub = (options) => {
3335
4136
  };
3336
4137
  var json4 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
3337
4138
  var adminError3 = (status, message) => json4(status, { error: { type: "mockingbird_admin", message } });
3338
- var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4139
+ var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3339
4140
  var parseEndpoint = (value) => {
3340
- if (!isRecord4(value) || typeof value.url !== "string")
4141
+ if (!isRecord5(value) || typeof value.url !== "string")
3341
4142
  return "each endpoint needs a url";
3342
4143
  try {
3343
4144
  new URL(value.url);
@@ -3354,12 +4155,12 @@ var parseEndpoint = (value) => {
3354
4155
  const events = value.events ?? value.enabledEvents;
3355
4156
  if (Array.isArray(events))
3356
4157
  endpoint.events = events.map(String);
3357
- if (isRecord4(value.tags)) {
4158
+ if (isRecord5(value.tags)) {
3358
4159
  endpoint.tags = Object.fromEntries(Object.entries(value.tags).map(([k, v]) => [k, String(v)]));
3359
4160
  }
3360
4161
  if (typeof value.account === "string")
3361
4162
  endpoint.tags = { ...endpoint.tags, account: value.account };
3362
- if (isRecord4(value.headers)) {
4163
+ if (isRecord5(value.headers)) {
3363
4164
  endpoint.headers = Object.fromEntries(Object.entries(value.headers).map(([k, v]) => [k, String(v)]));
3364
4165
  }
3365
4166
  return endpoint;
@@ -3386,7 +4187,7 @@ var webhookAdminRoutes = (hub) => ({
3386
4187
  return json4(200, { status: "ok" });
3387
4188
  },
3388
4189
  "POST /webhooks/faults": ({ body, namespace }) => {
3389
- if (!isRecord4(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
4190
+ if (!isRecord5(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
3390
4191
  return adminError3(400, "mode must be duplicate, reorder or drop");
3391
4192
  }
3392
4193
  const fault = { mode: body.mode };
@@ -3402,7 +4203,7 @@ var webhookAdminRoutes = (hub) => ({
3402
4203
  }))
3403
4204
  }),
3404
4205
  "PUT /webhook-endpoints": ({ body, namespace }) => {
3405
- const list = Array.isArray(body) ? body : isRecord4(body) ? body.endpoints : void 0;
4206
+ const list = Array.isArray(body) ? body : isRecord5(body) ? body.endpoints : void 0;
3406
4207
  if (!Array.isArray(list))
3407
4208
  return adminError3(400, "expected [{url, secret?, events?}]");
3408
4209
  const parsed = [];
@@ -3472,8 +4273,8 @@ var DroppedConnectionError = class extends TypeError {
3472
4273
  this.name = "TypeError";
3473
4274
  }
3474
4275
  };
3475
- var operationMatcher = (document2) => {
3476
- const matchers = listOperations(document2).map((operation) => ({
4276
+ var operationMatcher = (document3) => {
4277
+ const matchers = listOperations(document3).map((operation) => ({
3477
4278
  operationId: operation.operationId,
3478
4279
  method: operation.method.toUpperCase(),
3479
4280
  pattern: new RegExp(`^${operation.path.split("/").map((segment) => segment.startsWith("{") ? "[^/]+" : segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("/")}/?$`),
@@ -3568,10 +4369,10 @@ var createRuntime = (options) => {
3568
4369
  const ensureBranch = (namespace, branch2, at) => {
3569
4370
  if (!BRANCH_PATTERN2.test(branch2))
3570
4371
  throw new RangeError(`branch must match ${BRANCH_PATTERN2}`);
3571
- const history = timeline(namespace);
4372
+ const history2 = timeline(namespace);
3572
4373
  if (branch2 === "main") {
3573
4374
  if (at !== void 0) {
3574
- const point = history.checkout("main", at);
4375
+ const point = history2.checkout("main", at);
3575
4376
  restoreNamespace(sqlite, storageNamespace(namespace), point.value.snapshot);
3576
4377
  captured.set(namespace, point.value.snapshot);
3577
4378
  rng.setState(point.value.rngState);
@@ -3584,10 +4385,10 @@ var createRuntime = (options) => {
3584
4385
  return namespace;
3585
4386
  }
3586
4387
  const storage = physicalBranch(namespace, branch2);
3587
- if (!history.hasBranch(branch2)) {
4388
+ if (!history2.hasBranch(branch2)) {
3588
4389
  if (at === void 0)
3589
- history.commit(capture(namespace));
3590
- const point = history.fork(branch2, at === void 0 ? {} : { from: at });
4390
+ history2.commit(capture(namespace));
4391
+ const point = history2.fork(branch2, at === void 0 ? {} : { from: at });
3591
4392
  const branchRng = createRng(options.seed ?? 0);
3592
4393
  if (point)
3593
4394
  branchRng.setState(point.value.rngState);
@@ -3596,8 +4397,8 @@ var createRuntime = (options) => {
3596
4397
  restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
3597
4398
  if (point)
3598
4399
  captured.set(storage, point.value.snapshot);
3599
- } else if (at !== void 0 && history.head(branch2)?.id !== at) {
3600
- const point = history.checkout(branch2, at);
4400
+ } else if (at !== void 0 && history2.head(branch2)?.id !== at) {
4401
+ const point = history2.checkout(branch2, at);
3601
4402
  if (!instances.has(storage)) {
3602
4403
  const branchRng = createRng(options.seed ?? 0);
3603
4404
  branchRng.setState(point.value.rngState);
@@ -3608,7 +4409,7 @@ var createRuntime = (options) => {
3608
4409
  captured.set(storage, point.value.snapshot);
3609
4410
  } else {
3610
4411
  if (!instances.has(storage)) {
3611
- const point = history.head(branch2);
4412
+ const point = history2.head(branch2);
3612
4413
  const branchRng = createRng(options.seed ?? 0);
3613
4414
  if (point)
3614
4415
  branchRng.setState(point.value.rngState);
@@ -3632,8 +4433,8 @@ var createRuntime = (options) => {
3632
4433
  const checkout = (checkpointId, checkoutOptions = {}) => {
3633
4434
  const namespace = checkoutOptions.namespace ?? DEFAULT_NAMESPACE;
3634
4435
  const branchName = checkoutOptions.branch ?? "main";
3635
- const history = timeline(namespace);
3636
- const point = history.checkout(branchName, checkpointId);
4436
+ const history2 = timeline(namespace);
4437
+ const point = history2.checkout(branchName, checkpointId);
3637
4438
  const storage = ensureBranch(namespace, branchName);
3638
4439
  restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
3639
4440
  captured.set(storage, point.value.snapshot);
@@ -3683,9 +4484,9 @@ var createRuntime = (options) => {
3683
4484
  instance(name);
3684
4485
  restoreNamespace(sqlite, storageNamespace(name), from);
3685
4486
  captured.set(name, from);
3686
- const history = timelines.get(name);
3687
- if (history)
3688
- history.commit(capture(name), { branch: "main" });
4487
+ const history2 = timelines.get(name);
4488
+ if (history2)
4489
+ history2.commit(capture(name), { branch: "main" });
3689
4490
  else
3690
4491
  timeline(name);
3691
4492
  };
@@ -3914,10 +4715,10 @@ var createRuntime = (options) => {
3914
4715
  },
3915
4716
  release: (name, checkpointId) => timeline(name).release(checkpointId),
3916
4717
  inspect: (name) => {
3917
- const history = timeline(name);
4718
+ const history2 = timeline(name);
3918
4719
  return {
3919
- branches: history.branches(),
3920
- checkpoints: history.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
4720
+ branches: history2.branches(),
4721
+ checkpoints: history2.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
3921
4722
  id,
3922
4723
  branch: branchName,
3923
4724
  parent,
@@ -3932,11 +4733,11 @@ var createRuntime = (options) => {
3932
4733
  } : {},
3933
4734
  routes: {
3934
4735
  ...credentialRoutes(credentials),
3935
- ...options.presets ? presetRoutes(options.presets, runtime) : {},
3936
4736
  ...options.webhooks ? webhookAdminRoutes(options.webhooks) : {},
3937
4737
  ...options.admin?.(runtime) ?? {},
3938
- // Standard state and UI routes win a colliding key. A service adds its own keys
3939
- // beside these; it does not replace `/state` or `/ui`.
4738
+ // Shell routes win a colliding key. Fault presets are one of them: the admin page
4739
+ // maps `presets`, and a service route must not replace that list with a record.
4740
+ ...options.presets ? presetRoutes(options.presets, runtime) : {},
3940
4741
  ...stateAdminRoutes({
3941
4742
  open: stateScope,
3942
4743
  afterWrite: (namespace) => {
@@ -4011,9 +4812,12 @@ var credentialRoutes = (registry) => ({
4011
4812
  }
4012
4813
  });
4013
4814
  var presetRoutes = (presets, runtime) => ({
4014
- "GET /faults/presets": () => adminJson(200, {
4015
- presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
4016
- }),
4815
+ "GET /faults/presets": () => {
4816
+ const body = {
4817
+ presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
4818
+ };
4819
+ return adminJson(200, body);
4820
+ },
4017
4821
  "POST /faults/presets/:name": ({ params, body, namespace }) => {
4018
4822
  const name = params.name;
4019
4823
  if (!presets[name])
@@ -4024,7 +4828,7 @@ var presetRoutes = (presets, runtime) => ({
4024
4828
  });
4025
4829
 
4026
4830
  // src/generated/openapi.ts
4027
- var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Flex HSA/FSA payments API (Mockingbird subset)","description":"Stateful mock subset of the Flex (withflex.com) payments API: products, checkout sessions\\n(payment, subscription, off-session and setup modes), subscriptions, customers, setup\\nintents, refunds, and the hosted checkout page. Request and response bodies are wrapped in an envelope named after the\\nobject (\`{product: {...}}\`, \`{checkout_session: {...}}\`, \`{products: [...]}\`).\\nHand-authored from the consumer's wire shapes and the Flex API reference\\n(https://docs.withflex.com/api-reference); Flex publishes no machine-readable spec.\\n","version":"1","x-mockingbird-upstream":{"note":"Hand-authored from the wire shapes our consumer reads and writes (flex-api.client.ts, flex-payment.types.ts, flex-product.types.ts, flex-webhook.service.ts, do-flex-hosted-checkout.ts). Subscription mode, recurring price_data and the Subscription object follow https://docs.withflex.com/api-reference/checkout-sessions/create-checkout-session, https://docs.withflex.com/api-reference/subscriptions/get-subscription and https://docs.withflex.com/api-reference/prices/create-price."}},"servers":[{"url":"https://api.withflex.com"}],"security":[{"bearerAuth":[]}],"paths":{"/v1/products":{"get":{"operationId":"ListProducts","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"starting_after","in":"query","required":false,"description":"The last product_id of the previous page.","schema":{"type":"string","x-mockingbird-resource-ref":{"type":"product","missing":"fprod_00000000000000000000000000"}}}],"responses":{"200":{"description":"A page of products, oldest first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductList"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"}}},"post":{"operationId":"CreateProduct","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProductBody"}}}},"responses":{"200":{"description":"The created product","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductEnvelope"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"}}}},"/v1/products/{productId}":{"parameters":[{"name":"productId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"product","missing":"fprod_00000000000000000000000000"}}}],"get":{"operationId":"GetProduct","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The product","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductEnvelope"}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}},"patch":{"operationId":"UpdateProduct","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProductBody"}}}},"responses":{"200":{"description":"The updated product (emits product.updated)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductEnvelope"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/v1/checkout/sessions":{"get":{"operationId":"ListCheckoutSessions","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"client_reference_id","in":"query","required":false,"schema":{"type":"string","maxLength":255}},{"name":"expand_customer","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}},{"name":"expand_payment_intent","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"starting_after","in":"query","required":false,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"checkout_session","missing":"fcs_00000000000000000000000000"}}}],"responses":{"200":{"description":"Sessions, newest first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutSessionList"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"}}},"post":{"operationId":"CreateCheckoutSession","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[A-Za-z0-9:_.-]+$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCheckoutSessionBody"}}}},"responses":{"200":{"description":"The created session. Off-session mode charges the saved payment method before answering, so the session is already complete (or its payment intent failed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutSessionEnvelope"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}}}},"/v1/checkout/sessions/{sessionId}":{"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"checkout_session","missing":"fcs_00000000000000000000000000"}}}],"get":{"operationId":"GetCheckoutSession","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"expand_customer","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}},{"name":"expand_payment_intent","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}}],"responses":{"200":{"description":"The session","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutSessionEnvelope"}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/v1/checkout/sessions/{sessionId}/refund":{"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"checkout_session","missing":"fcs_00000000000000000000000000"}}}],"post":{"operationId":"RefundCheckoutSession","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[A-Za-z0-9:_.-]+$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefundCheckoutSessionBody"}}}},"responses":{"200":{"description":"The session with amount_refunded updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutSessionEnvelope"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"}}}},"/v1/customers":{"post":{"operationId":"CreateCustomer","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[A-Za-z0-9:_.-]+$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCustomerBody"}}}},"responses":{"200":{"description":"The created customer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerEnvelope"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"}}}},"/v1/setup_intents/{setupIntentId}":{"parameters":[{"name":"setupIntentId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"setup_intent","missing":"fseti_00000000000000000000000000"}}}],"get":{"operationId":"GetSetupIntent","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"expand","in":"query","required":false,"description":"Comma-separated fields to expand (customer, payment_method).","schema":{"type":"string","enum":["customer","payment_method","customer,payment_method"]}}],"responses":{"200":{"description":"The setup intent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetupIntentEnvelope"}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/v1/subscriptions/{subscriptionId}":{"parameters":[{"name":"subscriptionId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"subscription","missing":"fsub_00000000000000000000000000"}}}],"get":{"operationId":"GetSubscription","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The subscription a completed subscription-mode checkout session created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionEnvelope"}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/pay/{sessionId}":{"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"operationId":"HostedCheckoutPage","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":false,"safe":true,"reason":"A browser-facing HTML page; covered by the acceptance suite, not by JSON walks."}},"responses":{"200":{"description":"The card form (or the letter-of-medical-necessity step)","content":{"text/html":{"schema":{"type":"string"}}}},"404":{"description":"Unknown session","content":{"text/html":{"schema":{"type":"string"}}}},"409":{"description":"The session is no longer payable","content":{"text/html":{"schema":{"type":"string"}}}}}},"post":{"operationId":"SubmitHostedCheckout","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":false,"safe":false,"reason":"A browser form post; covered by the acceptance suite."}},"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"step":{"type":"string","enum":["card","lmn"]},"cardNumber":{"type":"string"},"expiry":{"type":"string"},"cvc":{"type":"string"},"postalCode":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"phone":{"type":"string"}}}}}},"responses":{"302":{"description":"Paid; redirects to success_url (or to the next action's page)"},"400":{"description":"The card form is incomplete or the card number is invalid","content":{"text/html":{"schema":{"type":"string"}}}},"402":{"description":"The card was declined","content":{"text/html":{"schema":{"type":"string"}}}},"404":{"description":"Unknown session","content":{"text/html":{"schema":{"type":"string"}}}},"409":{"description":"The session is no longer payable","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/pay/{sessionId}/cancel":{"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"operationId":"CancelHostedCheckout","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":false,"safe":true,"reason":"A browser navigation to cancel_url; covered by the acceptance suite."}},"responses":{"302":{"description":"Redirects to cancel_url; the session stays open"},"404":{"description":"Unknown session","content":{"text/html":{"schema":{"type":"string"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"A secret key, \`fsk_test_\u2026\` (test mode) or \`fsk_\u2026\` (live mode)."}},"responses":{"Error":{"description":"An error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"schemas":{"ErrorBody":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["type","message"],"properties":{"type":{"type":"string","enum":["invalid_request_error","authentication_error","idempotency_error","api_error","card_error"]},"code":{"type":"string"},"message":{"type":"string"},"param":{"type":"string"}}}}},"StringMap":{"type":"object","maxProperties":50,"additionalProperties":{"type":"string","maxLength":500}},"Product":{"type":"object","required":["product_id","name","active","test_mode","created_at"],"properties":{"product_id":{"type":"string","x-mockingbird-resource":{"type":"product","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"name":{"type":"string"},"description":{"type":["string","null"]},"url":{"type":["string","null"]},"client_reference_id":{"type":["string","null"]},"hsa_fsa_eligibility":{"description":"not_eligible, auto_substantiation, private_label, letter_of_medical_necessity, prescription, vision or service; null until Flex classifies a new product.","type":["string","null"]},"visit_type":{"type":["string","null"]},"active":{"type":"boolean"},"test_mode":{"type":"boolean"},"metadata":{"type":["object","null"],"additionalProperties":{"type":"string"}},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"ProductEnvelope":{"type":"object","required":["product"],"properties":{"product":{"$ref":"#/components/schemas/Product"}}},"ProductList":{"type":"object","required":["products","has_more"],"properties":{"products":{"type":"array","items":{"$ref":"#/components/schemas/Product"}},"has_more":{"type":"boolean"}}},"CreateProductBody":{"type":"object","required":["product"],"properties":{"product":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":250},"description":{"type":"string","maxLength":1000},"url":{"type":"string","maxLength":2048},"client_reference_id":{"type":"string","minLength":1,"maxLength":255},"metadata":{"$ref":"#/components/schemas/StringMap"}}}}},"UpdateProductBody":{"type":"object","required":["product"],"properties":{"product":{"type":"object","properties":{"active":{"type":"boolean"},"name":{"type":"string","minLength":1,"maxLength":250},"description":{"type":"string","maxLength":1000},"url":{"type":"string","maxLength":2048},"metadata":{"$ref":"#/components/schemas/StringMap"}}}}},"LineItem":{"type":"object","required":["price_data","quantity"],"properties":{"price_data":{"type":"object","required":["product","unit_amount"],"properties":{"product":{"type":"string","x-mockingbird-resource-ref":{"type":"product","missing":"fprod_00000000000000000000000000"}},"unit_amount":{"type":"integer","minimum":0,"maximum":10000000},"recurring":{"$ref":"#/components/schemas/Recurring"}}},"quantity":{"type":"integer","minimum":1,"maximum":100}}},"Recurring":{"type":"object","description":"Makes the line item's price recurring; a subscription-mode session needs at least one.","required":["interval"],"properties":{"interval":{"type":"string","enum":["day","week","month","year"]},"interval_count":{"type":"integer","minimum":1,"maximum":12}}},"SubscriptionData":{"type":"object","properties":{"cancel_at_period_end":{"type":"boolean"},"metadata":{"$ref":"#/components/schemas/StringMap"}}},"CreateCheckoutSessionBody":{"type":"object","required":["checkout_session"],"properties":{"checkout_session":{"type":"object","required":["success_url","line_items"],"properties":{"allow_promotion_codes":{"type":"boolean"},"capture_method":{"type":"string","enum":["automatic"]},"cancel_url":{"type":"string","minLength":1,"maxLength":2048},"success_url":{"type":"string","minLength":1,"maxLength":2048},"client_reference_id":{"type":"string","minLength":1,"maxLength":255},"line_items":{"type":"array","maxItems":20,"items":{"$ref":"#/components/schemas/LineItem"}},"metadata":{"$ref":"#/components/schemas/StringMap"},"mode":{"type":"string","enum":["payment","subscription","off_session","setup"]},"subscription_data":{"$ref":"#/components/schemas/SubscriptionData"},"payment_method":{"type":"string","maxLength":255},"customer":{"type":"string","x-mockingbird-resource-ref":{"type":"customer","missing":"fcus_00000000000000000000000000"}},"setup_future_use":{"type":"string","enum":["off_session"]}}}}},"RefundCheckoutSessionBody":{"type":"object","required":["checkout_session"],"properties":{"checkout_session":{"type":"object","properties":{"amount":{"type":"integer","minimum":1,"maximum":10000000}}}}},"CreateCustomerBody":{"type":"object","required":["customer"],"properties":{"customer":{"type":"object","required":["first_name","last_name","email","phone"],"properties":{"first_name":{"type":"string","minLength":1,"maxLength":100},"last_name":{"type":"string","minLength":1,"maxLength":100},"email":{"type":"string","format":"email","maxLength":254},"phone":{"type":"string","minLength":1,"maxLength":32}}}}},"Customer":{"type":"object","required":["customer_id","test_mode","created_at"],"properties":{"customer_id":{"type":"string","x-mockingbird-resource":{"type":"customer","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"first_name":{"type":["string","null"]},"last_name":{"type":["string","null"]},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"test_mode":{"type":"boolean"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"CustomerEnvelope":{"type":"object","required":["customer"],"properties":{"customer":{"$ref":"#/components/schemas/Customer"}}},"PaymentMethod":{"type":"object","required":["payment_method_id","type"],"properties":{"payment_method_id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"type":{"type":"string","enum":["card"]},"hsa_fsa":{"type":"boolean"},"customer":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"id"}}}},"PaymentIntent":{"type":"object","required":["payment_intent_id","amount","status"],"properties":{"payment_intent_id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"amount":{"type":"integer"},"amount_received":{"type":["integer","null"]},"customer":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"id"}},"payment_method":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"id"}},"status":{"type":"string","enum":["requires_payment_method","requires_action","processing","succeeded","canceled"]},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"SetupIntent":{"type":"object","required":["setup_intent_id","status"],"properties":{"setup_intent_id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"status":{"type":"string","enum":["requires_payment_method","requires_action","processing","succeeded","canceled"]},"customer":{"anyOf":[{"type":"string"},{"$ref":"#/components/schemas/Customer"},{"type":"null"}]},"payment_method":{"anyOf":[{"type":"string"},{"$ref":"#/components/schemas/PaymentMethod"},{"type":"null"}]},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"SetupIntentEnvelope":{"type":"object","required":["setup_intent"],"properties":{"setup_intent":{"$ref":"#/components/schemas/SetupIntent"}}},"NextAction":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["collect_letter_of_medical_necessity","provide_second_payment_method","provide_alternative_payment_method","payment_failed"]},"collect_letter_of_medical_necessity":{"type":"object","required":["url"],"properties":{"url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}}}},"provide_second_payment_method":{"type":"object","required":["url"],"properties":{"url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}}}},"provide_alternative_payment_method":{"type":"object","required":["url"],"properties":{"url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}}}},"payment_failed":{"type":"object"}}},"ResponseLineItem":{"type":"object","required":["price_data","quantity","amount_total"],"properties":{"price_data":{"type":"object","required":["product","unit_amount"],"properties":{"product":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"unit_amount":{"type":"integer"},"recurring":{"anyOf":[{"$ref":"#/components/schemas/Recurring"},{"type":"null"}]}}},"quantity":{"type":"integer"},"amount_total":{"type":"integer"}}},"CheckoutSession":{"type":"object","required":["checkout_session_id","amount_total","status","mode","redirect_url","url","test_mode","created_at"],"properties":{"checkout_session_id":{"type":"string","x-mockingbird-resource":{"type":"checkout_session","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"client_reference_id":{"type":["string","null"]},"amount_total":{"type":"integer","minimum":0},"amount_received":{"type":["integer","null"]},"amount_refunded":{"type":"integer","minimum":0},"customer":{"anyOf":[{"type":"string","x-mockingbird-volatile":{"kind":"id"}},{"$ref":"#/components/schemas/Customer"},{"type":"null"}]},"payment_intent":{"anyOf":[{"type":"string","x-mockingbird-volatile":{"kind":"id"}},{"$ref":"#/components/schemas/PaymentIntent"},{"type":"null"}]},"setup_intent":{"type":["string","null"],"x-mockingbird-resource":{"type":"setup_intent","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"mode":{"type":"string","enum":["payment","subscription","off_session","setup"]},"subscription":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"id"}},"subscription_data":{"anyOf":[{"$ref":"#/components/schemas/SubscriptionData"},{"type":"null"}]},"status":{"type":"string","enum":["open","paid","complete","canceled","expired"]},"redirect_url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}},"url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}},"success_url":{"type":"string"},"cancel_url":{"type":["string","null"]},"next_action":{"anyOf":[{"$ref":"#/components/schemas/NextAction"},{"type":"null"}]},"visit_type":{"type":["string","null"]},"metadata":{"type":["object","null"],"additionalProperties":{"type":"string"}},"line_items":{"type":"array","items":{"$ref":"#/components/schemas/ResponseLineItem"}},"allow_promotion_codes":{"type":"boolean"},"capture_method":{"type":"string"},"setup_future_use":{"type":["string","null"]},"test_mode":{"type":"boolean"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"expires_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"CheckoutSessionEnvelope":{"type":"object","required":["checkout_session"],"properties":{"checkout_session":{"$ref":"#/components/schemas/CheckoutSession"}}},"CheckoutSessionList":{"type":"object","required":["checkout_sessions","has_more"],"properties":{"checkout_sessions":{"type":"array","items":{"$ref":"#/components/schemas/CheckoutSession"}},"has_more":{"type":"boolean"}}},"SubscriptionItem":{"type":"object","required":["price_data","quantity"],"properties":{"price_data":{"type":"object","required":["product","unit_amount","recurring"],"properties":{"product":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"unit_amount":{"type":"integer"},"recurring":{"$ref":"#/components/schemas/Recurring"}}},"quantity":{"type":"integer"}}},"Subscription":{"type":"object","required":["subscription_id","created_at","items","status","cancel_at_period_end","test_mode"],"properties":{"subscription_id":{"type":"string","x-mockingbird-resource":{"type":"subscription","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"status":{"type":"string","enum":["incomplete","incomplete_expired","trialing","active","past_due","canceled","unpaid","paused"]},"items":{"type":"array","items":{"$ref":"#/components/schemas/SubscriptionItem"}},"customer":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"id"}},"default_payment_method":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"id"}},"cancel_at_period_end":{"type":"boolean"},"current_period_start":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"current_period_end":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"canceled_at":{"type":["string","null"]},"metadata":{"type":["object","null"],"additionalProperties":{"type":"string"}},"test_mode":{"type":"boolean"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"SubscriptionEnvelope":{"type":"object","required":["subscription"],"properties":{"subscription":{"$ref":"#/components/schemas/Subscription"}}}}}}`);
4831
+ var document2 = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Flex HSA/FSA payments API (Mockingbird subset)","description":"Stateful mock subset of the Flex (withflex.com) payments API: products, checkout sessions\\n(payment, subscription, off-session and setup modes), subscriptions, customers, setup\\nintents, refunds, and the hosted checkout page. Request and response bodies are wrapped in an envelope named after the\\nobject (\`{product: {...}}\`, \`{checkout_session: {...}}\`, \`{products: [...]}\`).\\nHand-authored from the consumer's wire shapes and the Flex API reference\\n(https://docs.withflex.com/api-reference); Flex publishes no machine-readable spec.\\n","version":"1","x-mockingbird-upstream":{"note":"Hand-authored from the wire shapes our consumer reads and writes (flex-api.client.ts, flex-payment.types.ts, flex-product.types.ts, flex-webhook.service.ts, do-flex-hosted-checkout.ts). Subscription mode, recurring price_data and the Subscription object follow https://docs.withflex.com/api-reference/checkout-sessions/create-checkout-session, https://docs.withflex.com/api-reference/subscriptions/get-subscription and https://docs.withflex.com/api-reference/prices/create-price."}},"servers":[{"url":"https://api.withflex.com"}],"security":[{"bearerAuth":[]}],"paths":{"/v1/products":{"get":{"operationId":"ListProducts","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"starting_after","in":"query","required":false,"description":"The last product_id of the previous page.","schema":{"type":"string","x-mockingbird-resource-ref":{"type":"product","missing":"fprod_00000000000000000000000000"}}}],"responses":{"200":{"description":"A page of products, oldest first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductList"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"}}},"post":{"operationId":"CreateProduct","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProductBody"}}}},"responses":{"200":{"description":"The created product","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductEnvelope"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"}}}},"/v1/products/{productId}":{"parameters":[{"name":"productId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"product","missing":"fprod_00000000000000000000000000"}}}],"get":{"operationId":"GetProduct","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The product","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductEnvelope"}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}},"patch":{"operationId":"UpdateProduct","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProductBody"}}}},"responses":{"200":{"description":"The updated product (emits product.updated)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductEnvelope"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/v1/checkout/sessions":{"get":{"operationId":"ListCheckoutSessions","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"client_reference_id","in":"query","required":false,"schema":{"type":"string","maxLength":255}},{"name":"expand_customer","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}},{"name":"expand_payment_intent","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"starting_after","in":"query","required":false,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"checkout_session","missing":"fcs_00000000000000000000000000"}}}],"responses":{"200":{"description":"Sessions, newest first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutSessionList"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"}}},"post":{"operationId":"CreateCheckoutSession","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[A-Za-z0-9:_.-]+$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCheckoutSessionBody"}}}},"responses":{"200":{"description":"The created session. Off-session mode charges the saved payment method before answering, so the session is already complete (or its payment intent failed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutSessionEnvelope"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}}}},"/v1/checkout/sessions/{sessionId}":{"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"checkout_session","missing":"fcs_00000000000000000000000000"}}}],"get":{"operationId":"GetCheckoutSession","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"expand_customer","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}},{"name":"expand_payment_intent","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}}],"responses":{"200":{"description":"The session","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutSessionEnvelope"}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/v1/checkout/sessions/{sessionId}/refund":{"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"checkout_session","missing":"fcs_00000000000000000000000000"}}}],"post":{"operationId":"RefundCheckoutSession","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[A-Za-z0-9:_.-]+$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefundCheckoutSessionBody"}}}},"responses":{"200":{"description":"The session with amount_refunded updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutSessionEnvelope"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"}}}},"/v1/customers":{"post":{"operationId":"CreateCustomer","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[A-Za-z0-9:_.-]+$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCustomerBody"}}}},"responses":{"200":{"description":"The created customer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerEnvelope"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"}}}},"/v1/setup_intents/{setupIntentId}":{"parameters":[{"name":"setupIntentId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"setup_intent","missing":"fseti_00000000000000000000000000"}}}],"get":{"operationId":"GetSetupIntent","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"expand","in":"query","required":false,"description":"Comma-separated fields to expand (customer, payment_method).","schema":{"type":"string","enum":["customer","payment_method","customer,payment_method"]}}],"responses":{"200":{"description":"The setup intent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetupIntentEnvelope"}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/v1/subscriptions/{subscriptionId}":{"parameters":[{"name":"subscriptionId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"subscription","missing":"fsub_00000000000000000000000000"}}}],"get":{"operationId":"GetSubscription","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The subscription a completed subscription-mode checkout session created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionEnvelope"}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/pay/{sessionId}":{"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"operationId":"HostedCheckoutPage","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":false,"safe":true,"reason":"A browser-facing HTML page; covered by the acceptance suite, not by JSON walks."}},"responses":{"200":{"description":"The card form (or the letter-of-medical-necessity step)","content":{"text/html":{"schema":{"type":"string"}}}},"404":{"description":"Unknown session","content":{"text/html":{"schema":{"type":"string"}}}},"409":{"description":"The session is no longer payable","content":{"text/html":{"schema":{"type":"string"}}}}}},"post":{"operationId":"SubmitHostedCheckout","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":false,"safe":false,"reason":"A browser form post; covered by the acceptance suite."}},"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"step":{"type":"string","enum":["card","lmn"]},"cardNumber":{"type":"string"},"expiry":{"type":"string"},"cvc":{"type":"string"},"postalCode":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"phone":{"type":"string"}}}}}},"responses":{"302":{"description":"Paid; redirects to success_url (or to the next action's page)"},"400":{"description":"The card form is incomplete or the card number is invalid","content":{"text/html":{"schema":{"type":"string"}}}},"402":{"description":"The card was declined","content":{"text/html":{"schema":{"type":"string"}}}},"404":{"description":"Unknown session","content":{"text/html":{"schema":{"type":"string"}}}},"409":{"description":"The session is no longer payable","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/pay/{sessionId}/cancel":{"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"operationId":"CancelHostedCheckout","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":false,"safe":true,"reason":"A browser navigation to cancel_url; covered by the acceptance suite."}},"responses":{"302":{"description":"Redirects to cancel_url; the session stays open"},"404":{"description":"Unknown session","content":{"text/html":{"schema":{"type":"string"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"A secret key, \`fsk_test_\u2026\` (test mode) or \`fsk_\u2026\` (live mode)."}},"responses":{"Error":{"description":"An error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"schemas":{"ErrorBody":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["type","message"],"properties":{"type":{"type":"string","enum":["invalid_request_error","authentication_error","idempotency_error","api_error","card_error"]},"code":{"type":"string"},"message":{"type":"string"},"param":{"type":"string"}}}}},"StringMap":{"type":"object","maxProperties":50,"additionalProperties":{"type":"string","maxLength":500}},"Product":{"type":"object","required":["product_id","name","active","test_mode","created_at"],"properties":{"product_id":{"type":"string","x-mockingbird-resource":{"type":"product","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"name":{"type":"string"},"description":{"type":["string","null"]},"url":{"type":["string","null"]},"client_reference_id":{"type":["string","null"]},"hsa_fsa_eligibility":{"description":"not_eligible, auto_substantiation, private_label, letter_of_medical_necessity, prescription, vision or service; null until Flex classifies a new product.","type":["string","null"]},"visit_type":{"type":["string","null"]},"active":{"type":"boolean"},"test_mode":{"type":"boolean"},"metadata":{"type":["object","null"],"additionalProperties":{"type":"string"}},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"ProductEnvelope":{"type":"object","required":["product"],"properties":{"product":{"$ref":"#/components/schemas/Product"}}},"ProductList":{"type":"object","required":["products","has_more"],"properties":{"products":{"type":"array","items":{"$ref":"#/components/schemas/Product"}},"has_more":{"type":"boolean"}}},"CreateProductBody":{"type":"object","required":["product"],"properties":{"product":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":250},"description":{"type":"string","maxLength":1000},"url":{"type":"string","maxLength":2048},"client_reference_id":{"type":"string","minLength":1,"maxLength":255},"metadata":{"$ref":"#/components/schemas/StringMap"}}}}},"UpdateProductBody":{"type":"object","required":["product"],"properties":{"product":{"type":"object","properties":{"active":{"type":"boolean"},"name":{"type":"string","minLength":1,"maxLength":250},"description":{"type":"string","maxLength":1000},"url":{"type":"string","maxLength":2048},"metadata":{"$ref":"#/components/schemas/StringMap"}}}}},"LineItem":{"type":"object","required":["price_data","quantity"],"properties":{"price_data":{"type":"object","required":["product","unit_amount"],"properties":{"product":{"type":"string","x-mockingbird-resource-ref":{"type":"product","missing":"fprod_00000000000000000000000000"}},"unit_amount":{"type":"integer","minimum":0,"maximum":10000000},"recurring":{"$ref":"#/components/schemas/Recurring"}}},"quantity":{"type":"integer","minimum":1,"maximum":100}}},"Recurring":{"type":"object","description":"Makes the line item's price recurring; a subscription-mode session needs at least one.","required":["interval"],"properties":{"interval":{"type":"string","enum":["day","week","month","year"]},"interval_count":{"type":"integer","minimum":1,"maximum":12}}},"SubscriptionData":{"type":"object","properties":{"cancel_at_period_end":{"type":"boolean"},"metadata":{"$ref":"#/components/schemas/StringMap"}}},"CreateCheckoutSessionBody":{"type":"object","required":["checkout_session"],"properties":{"checkout_session":{"type":"object","required":["success_url","line_items"],"properties":{"allow_promotion_codes":{"type":"boolean"},"capture_method":{"type":"string","enum":["automatic"]},"cancel_url":{"type":"string","minLength":1,"maxLength":2048},"success_url":{"type":"string","minLength":1,"maxLength":2048},"client_reference_id":{"type":"string","minLength":1,"maxLength":255},"line_items":{"type":"array","maxItems":20,"items":{"$ref":"#/components/schemas/LineItem"}},"metadata":{"$ref":"#/components/schemas/StringMap"},"mode":{"type":"string","enum":["payment","subscription","off_session","setup"]},"subscription_data":{"$ref":"#/components/schemas/SubscriptionData"},"payment_method":{"type":"string","maxLength":255},"customer":{"type":"string","x-mockingbird-resource-ref":{"type":"customer","missing":"fcus_00000000000000000000000000"}},"setup_future_use":{"type":"string","enum":["off_session"]}}}}},"RefundCheckoutSessionBody":{"type":"object","required":["checkout_session"],"properties":{"checkout_session":{"type":"object","properties":{"amount":{"type":"integer","minimum":1,"maximum":10000000}}}}},"CreateCustomerBody":{"type":"object","required":["customer"],"properties":{"customer":{"type":"object","required":["first_name","last_name","email","phone"],"properties":{"first_name":{"type":"string","minLength":1,"maxLength":100},"last_name":{"type":"string","minLength":1,"maxLength":100},"email":{"type":"string","format":"email","maxLength":254},"phone":{"type":"string","minLength":1,"maxLength":32}}}}},"Customer":{"type":"object","required":["customer_id","test_mode","created_at"],"properties":{"customer_id":{"type":"string","x-mockingbird-resource":{"type":"customer","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"first_name":{"type":["string","null"]},"last_name":{"type":["string","null"]},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"test_mode":{"type":"boolean"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"CustomerEnvelope":{"type":"object","required":["customer"],"properties":{"customer":{"$ref":"#/components/schemas/Customer"}}},"PaymentMethod":{"type":"object","required":["payment_method_id","type"],"properties":{"payment_method_id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"type":{"type":"string","enum":["card"]},"hsa_fsa":{"type":"boolean"},"customer":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"id"}}}},"PaymentIntent":{"type":"object","required":["payment_intent_id","amount","status"],"properties":{"payment_intent_id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"amount":{"type":"integer"},"amount_received":{"type":["integer","null"]},"customer":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"id"}},"payment_method":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"id"}},"status":{"type":"string","enum":["requires_payment_method","requires_action","processing","succeeded","canceled"]},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"SetupIntent":{"type":"object","required":["setup_intent_id","status"],"properties":{"setup_intent_id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"status":{"type":"string","enum":["requires_payment_method","requires_action","processing","succeeded","canceled"]},"customer":{"anyOf":[{"type":"string"},{"$ref":"#/components/schemas/Customer"},{"type":"null"}]},"payment_method":{"anyOf":[{"type":"string"},{"$ref":"#/components/schemas/PaymentMethod"},{"type":"null"}]},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"SetupIntentEnvelope":{"type":"object","required":["setup_intent"],"properties":{"setup_intent":{"$ref":"#/components/schemas/SetupIntent"}}},"NextAction":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["collect_letter_of_medical_necessity","provide_second_payment_method","provide_alternative_payment_method","payment_failed"]},"collect_letter_of_medical_necessity":{"type":"object","required":["url"],"properties":{"url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}}}},"provide_second_payment_method":{"type":"object","required":["url"],"properties":{"url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}}}},"provide_alternative_payment_method":{"type":"object","required":["url"],"properties":{"url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}}}},"payment_failed":{"type":"object"}}},"ResponseLineItem":{"type":"object","required":["price_data","quantity","amount_total"],"properties":{"price_data":{"type":"object","required":["product","unit_amount"],"properties":{"product":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"unit_amount":{"type":"integer"},"recurring":{"anyOf":[{"$ref":"#/components/schemas/Recurring"},{"type":"null"}]}}},"quantity":{"type":"integer"},"amount_total":{"type":"integer"}}},"CheckoutSession":{"type":"object","required":["checkout_session_id","amount_total","status","mode","redirect_url","url","test_mode","created_at"],"properties":{"checkout_session_id":{"type":"string","x-mockingbird-resource":{"type":"checkout_session","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"client_reference_id":{"type":["string","null"]},"amount_total":{"type":"integer","minimum":0},"amount_received":{"type":["integer","null"]},"amount_refunded":{"type":"integer","minimum":0},"customer":{"anyOf":[{"type":"string","x-mockingbird-volatile":{"kind":"id"}},{"$ref":"#/components/schemas/Customer"},{"type":"null"}]},"payment_intent":{"anyOf":[{"type":"string","x-mockingbird-volatile":{"kind":"id"}},{"$ref":"#/components/schemas/PaymentIntent"},{"type":"null"}]},"setup_intent":{"type":["string","null"],"x-mockingbird-resource":{"type":"setup_intent","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"mode":{"type":"string","enum":["payment","subscription","off_session","setup"]},"subscription":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"id"}},"subscription_data":{"anyOf":[{"$ref":"#/components/schemas/SubscriptionData"},{"type":"null"}]},"status":{"type":"string","enum":["open","paid","complete","canceled","expired"]},"redirect_url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}},"url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}},"success_url":{"type":"string"},"cancel_url":{"type":["string","null"]},"next_action":{"anyOf":[{"$ref":"#/components/schemas/NextAction"},{"type":"null"}]},"visit_type":{"type":["string","null"]},"metadata":{"type":["object","null"],"additionalProperties":{"type":"string"}},"line_items":{"type":"array","items":{"$ref":"#/components/schemas/ResponseLineItem"}},"allow_promotion_codes":{"type":"boolean"},"capture_method":{"type":"string"},"setup_future_use":{"type":["string","null"]},"test_mode":{"type":"boolean"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"expires_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"CheckoutSessionEnvelope":{"type":"object","required":["checkout_session"],"properties":{"checkout_session":{"$ref":"#/components/schemas/CheckoutSession"}}},"CheckoutSessionList":{"type":"object","required":["checkout_sessions","has_more"],"properties":{"checkout_sessions":{"type":"array","items":{"$ref":"#/components/schemas/CheckoutSession"}},"has_more":{"type":"boolean"}}},"SubscriptionItem":{"type":"object","required":["price_data","quantity"],"properties":{"price_data":{"type":"object","required":["product","unit_amount","recurring"],"properties":{"product":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"unit_amount":{"type":"integer"},"recurring":{"$ref":"#/components/schemas/Recurring"}}},"quantity":{"type":"integer"}}},"Subscription":{"type":"object","required":["subscription_id","created_at","items","status","cancel_at_period_end","test_mode"],"properties":{"subscription_id":{"type":"string","x-mockingbird-resource":{"type":"subscription","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"status":{"type":"string","enum":["incomplete","incomplete_expired","trialing","active","past_due","canceled","unpaid","paused"]},"items":{"type":"array","items":{"$ref":"#/components/schemas/SubscriptionItem"}},"customer":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"id"}},"default_payment_method":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"id"}},"cancel_at_period_end":{"type":"boolean"},"current_period_start":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"current_period_end":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"canceled_at":{"type":["string","null"]},"metadata":{"type":["object","null"],"additionalProperties":{"type":"string"}},"test_mode":{"type":"boolean"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"SubscriptionEnvelope":{"type":"object","required":["subscription"],"properties":{"subscription":{"$ref":"#/components/schemas/Subscription"}}}}}}`);
4028
4832
  var operationIds = ["ListProducts", "CreateProduct", "GetProduct", "UpdateProduct", "ListCheckoutSessions", "CreateCheckoutSession", "GetCheckoutSession", "RefundCheckoutSession", "CreateCustomer", "GetSetupIntent", "GetSubscription", "HostedCheckoutPage", "SubmitHostedCheckout", "CancelHostedCheckout"];
4029
4833
  var supportedOperationIds = ["ListProducts", "CreateProduct", "GetProduct", "UpdateProduct", "ListCheckoutSessions", "CreateCheckoutSession", "GetCheckoutSession", "RefundCheckoutSession", "CreateCustomer", "GetSetupIntent", "GetSubscription", "HostedCheckoutPage", "SubmitHostedCheckout", "CancelHostedCheckout"];
4030
4834
 
@@ -4074,12 +4878,16 @@ var layout = (title, body) => `<!doctype html>
4074
4878
  <meta name="viewport" content="width=device-width, initial-scale=1">
4075
4879
  <title>${escapeHtml2(title)}</title>
4076
4880
  <style>
4077
- body{font-family:system-ui,sans-serif;max-width:28rem;margin:2rem auto;padding:0 1rem;color:#1a1a1a}
4881
+ ${CSS_RESET}
4882
+ body{font-family:system-ui,sans-serif;max-width:28rem;margin:2rem auto;padding:0 1rem;color:#1a1a1a;background:#fff}
4883
+ h1{font-size:1.5rem;font-weight:650;margin:0 0 .75rem}
4884
+ p{margin:0 0 .75rem}
4885
+ ul{list-style:disc;padding-left:1.25rem}
4078
4886
  label{display:block;margin:.75rem 0 .25rem;font-size:.9rem}
4079
- input{width:100%;box-sizing:border-box;padding:.5rem;font-size:1rem}
4080
- button{margin-top:1rem;width:100%;padding:.75rem;font-size:1rem}
4887
+ input{width:100%;min-width:0;max-width:100%;padding:.5rem;font-size:1rem;border:1px solid #c8c8c8;border-radius:6px;background:#fff}
4888
+ button{margin-top:1rem;width:100%;padding:.75rem;font-size:1rem;background:#1a1a1a;color:#fff;border-radius:6px}
4081
4889
  [role=alert]{color:#b00020;margin:1rem 0}
4082
- .row{display:flex;gap:.5rem}.row>div{flex:1}
4890
+ .row{display:flex;flex-wrap:wrap;gap:.5rem;min-width:0}.row>div{flex:1;min-width:min(100%,8rem)}
4083
4891
  </style>
4084
4892
  </head>
4085
4893
  <body>
@@ -5044,9 +5852,9 @@ var FLEX_PRESETS = {
5044
5852
  };
5045
5853
  var json5 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
5046
5854
  var adminError4 = (status, message) => json5(status, { error: { type: "mockingbird_admin", message } });
5047
- var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5855
+ var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5048
5856
  var parseSettings = (body) => {
5049
- if (!isRecord5(body)) return "expected a JSON object";
5857
+ if (!isRecord6(body)) return "expected a JSON object";
5050
5858
  const patch = {};
5051
5859
  if (body.eventNaming !== void 0) {
5052
5860
  if (body.eventNaming !== "dotted" && body.eventNaming !== "underscored") {
@@ -5091,7 +5899,7 @@ var adminRoutes = (runtime) => {
5091
5899
  "POST /sessions/:id/complete": ({ namespace, params, body }) => {
5092
5900
  const closed = notOpen(namespace, params.id);
5093
5901
  if (closed) return closed;
5094
- const hsa = !(isRecord5(body) && body.card === "4242424242424242");
5902
+ const hsa = !(isRecord6(body) && body.card === "4242424242424242");
5095
5903
  return found(
5096
5904
  runtime.instance(namespace).settle(params.id, { hsa }),
5097
5905
  `session ${params.id}`
@@ -5102,7 +5910,7 @@ var adminRoutes = (runtime) => {
5102
5910
  "POST /sessions/:id/require_action": ({ namespace, params, body }) => {
5103
5911
  const closed = notOpen(namespace, params.id);
5104
5912
  if (closed) return closed;
5105
- const type = isRecord5(body) ? body.next_action_type : void 0;
5913
+ const type = isRecord6(body) ? body.next_action_type : void 0;
5106
5914
  if (type !== void 0 && !isNextActionType(type)) {
5107
5915
  return adminError4(
5108
5916
  400,
@@ -5115,7 +5923,7 @@ var adminRoutes = (runtime) => {
5115
5923
  );
5116
5924
  },
5117
5925
  "PUT /sessions/:id/payment-intent": ({ namespace, params, body }) => {
5118
- if (!isRecord5(body) || !PAYMENT_INTENT_STATUSES.includes(String(body.status))) {
5926
+ if (!isRecord6(body) || !PAYMENT_INTENT_STATUSES.includes(String(body.status))) {
5119
5927
  return adminError4(400, `status: ${PAYMENT_INTENT_STATUSES.join(" | ")}`);
5120
5928
  }
5121
5929
  const api = runtime.instance(namespace);
@@ -5126,7 +5934,7 @@ var adminRoutes = (runtime) => {
5126
5934
  return found(intent, `session ${params.id}`);
5127
5935
  },
5128
5936
  "PUT /products/:id": ({ namespace, params, body }) => {
5129
- if (!isRecord5(body)) return adminError4(400, "expected a JSON object");
5937
+ if (!isRecord6(body)) return adminError4(400, "expected a JSON object");
5130
5938
  const api = runtime.instance(namespace);
5131
5939
  const product = api.state.products.get(params.id);
5132
5940
  if (!product) return adminError4(404, `no product ${params.id}`);
@@ -5149,14 +5957,14 @@ var adminRoutes = (runtime) => {
5149
5957
  next.name = body.name;
5150
5958
  }
5151
5959
  if (body.metadata !== void 0) {
5152
- if (body.metadata !== null && !isRecord5(body.metadata))
5960
+ if (body.metadata !== null && !isRecord6(body.metadata))
5153
5961
  return adminError4(400, "metadata: object | null");
5154
5962
  next.metadata = body.metadata;
5155
5963
  }
5156
5964
  return json5(200, api.putProduct(next));
5157
5965
  },
5158
5966
  "POST /events": ({ namespace, body }) => {
5159
- if (!isRecord5(body) || typeof body.type !== "string") {
5967
+ if (!isRecord6(body) || typeof body.type !== "string") {
5160
5968
  return adminError4(400, 'expected {"type": "<event type>", "session"?: id, "product"?: id}');
5161
5969
  }
5162
5970
  const event = runtime.instance(namespace).emitFor(body.type, {
@@ -5189,7 +5997,7 @@ var createRuntime2 = (options = {}) => {
5189
5997
  });
5190
5998
  const runtime = createRuntime({
5191
5999
  name: FLEX_NAMESPACE,
5192
- document,
6000
+ document: document2,
5193
6001
  ...options.sqlite ? { sqlite: options.sqlite } : {},
5194
6002
  ...options.clock ? { clock: options.clock } : {},
5195
6003
  ...options.seed !== void 0 ? { seed: options.seed } : {},
@@ -5260,11 +6068,11 @@ var keyMode = (key) => {
5260
6068
  var HOSTED = /* @__PURE__ */ new Set(["HostedCheckoutPage", "SubmitHostedCheckout", "CancelHostedCheckout"]);
5261
6069
  var error = (status, type, message, extra = {}) => jsonRes(status, { error: { type, message, ...extra } });
5262
6070
  var invalid = (message, param) => error(400, "invalid_request_error", message, param ? { param } : {});
5263
- var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
6071
+ var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5264
6072
  var envelope = (context, name) => {
5265
6073
  const body = context.body.kind === "json" ? context.body.value : void 0;
5266
- const inner = isRecord6(body) ? body[name] : void 0;
5267
- return isRecord6(inner) ? inner : {};
6074
+ const inner = isRecord7(body) ? body[name] : void 0;
6075
+ return isRecord7(inner) ? inner : {};
5268
6076
  };
5269
6077
  var flag = (value) => value === "true";
5270
6078
  var sleep = (ms, signal) => new Promise((resolve) => {
@@ -5332,7 +6140,7 @@ var FlexAPI = class {
5332
6140
  CancelHostedCheckout: (context) => this.cancelHosted(context)
5333
6141
  });
5334
6142
  this.service = createService({
5335
- document,
6143
+ document: document2,
5336
6144
  handlers,
5337
6145
  sqlite,
5338
6146
  namespace,
@@ -5434,7 +6242,7 @@ var FlexAPI = class {
5434
6242
  visit_type: null,
5435
6243
  active: true,
5436
6244
  test_mode: this.testMode(context),
5437
- metadata: isRecord6(input.metadata) ? input.metadata : null,
6245
+ metadata: isRecord7(input.metadata) ? input.metadata : null,
5438
6246
  created_at: this.iso()
5439
6247
  };
5440
6248
  this.state.products.put(product);
@@ -5459,7 +6267,7 @@ var FlexAPI = class {
5459
6267
  ...typeof input.name === "string" ? { name: input.name } : {},
5460
6268
  ...typeof input.description === "string" ? { description: input.description } : {},
5461
6269
  ...typeof input.url === "string" ? { url: input.url } : {},
5462
- ...isRecord6(input.metadata) ? { metadata: input.metadata } : {}
6270
+ ...isRecord7(input.metadata) ? { metadata: input.metadata } : {}
5463
6271
  };
5464
6272
  this.putProduct(next);
5465
6273
  return annotateResponse(jsonRes(200, { product: next }), {
@@ -5591,7 +6399,7 @@ var FlexAPI = class {
5591
6399
  payment_intent: null,
5592
6400
  setup_intent: setupIntent,
5593
6401
  subscription: null,
5594
- subscription_data: mode === "subscription" ? isRecord6(input.subscription_data) ? input.subscription_data : {} : null,
6402
+ subscription_data: mode === "subscription" ? isRecord7(input.subscription_data) ? input.subscription_data : {} : null,
5595
6403
  mode,
5596
6404
  status: "open",
5597
6405
  url: `${this.pageBase(context)}/pay/${encodeURIComponent(id)}`,
@@ -5599,7 +6407,7 @@ var FlexAPI = class {
5599
6407
  cancel_url: typeof input.cancel_url === "string" ? input.cancel_url : null,
5600
6408
  next_action: null,
5601
6409
  visit_type: products.map((p) => p.visit_type).find((v) => v && v !== "notApplicable") ?? products[0]?.visit_type ?? null,
5602
- metadata: isRecord6(input.metadata) ? input.metadata : null,
6410
+ metadata: isRecord7(input.metadata) ? input.metadata : null,
5603
6411
  line_items: items,
5604
6412
  allow_promotion_codes: input.allow_promotion_codes === true,
5605
6413
  capture_method: typeof input.capture_method === "string" ? input.capture_method : "automatic",
@@ -6101,7 +6909,7 @@ var FlexAPI = class {
6101
6909
  var isNextActionType = (value) => typeof value === "string" && NEXT_ACTION_TYPES.includes(value);
6102
6910
 
6103
6911
  export {
6104
- document,
6912
+ document2 as document,
6105
6913
  operationIds,
6106
6914
  supportedOperationIds,
6107
6915
  CARDS,
@@ -6123,4 +6931,4 @@ export {
6123
6931
  FlexAPI,
6124
6932
  isNextActionType
6125
6933
  };
6126
- //# sourceMappingURL=chunk-UKBUYHZN.js.map
6934
+ //# sourceMappingURL=chunk-O3RZRWN7.js.map