@crvouga/mockingbird-service-intercom 1.5.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 matches3 = s.oneOf.filter((branch) => validateValue(document2, branch, value).length === 0).length;
802
+ const matches3 = s.oneOf.filter((branch) => validateValue(document3, branch, value).length === 0).length;
803
803
  if (matches3 !== 1)
804
804
  fail2(`matches ${matches3} 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
  };
@@ -1444,6 +1444,1232 @@ var clearNamespace = (sqlite, namespace) => {
1444
1444
  });
1445
1445
  };
1446
1446
 
1447
+ // ../../ui/dist/reset.js
1448
+ var CSS_RESET = `*,
1449
+ *::before,
1450
+ *::after {
1451
+ box-sizing: border-box;
1452
+ margin: 0;
1453
+ padding: 0;
1454
+ }
1455
+
1456
+ * {
1457
+ scrollbar-width: thin;
1458
+ scrollbar-color: color-mix(in srgb, CanvasText 35%, Canvas) Canvas;
1459
+ }
1460
+
1461
+ *::-webkit-scrollbar {
1462
+ width: 12px;
1463
+ height: 12px;
1464
+ }
1465
+
1466
+ *::-webkit-scrollbar-track,
1467
+ *::-webkit-scrollbar-corner {
1468
+ background: Canvas;
1469
+ }
1470
+
1471
+ *::-webkit-scrollbar-thumb {
1472
+ background: color-mix(in srgb, CanvasText 35%, Canvas);
1473
+ border: 3px solid Canvas;
1474
+ border-radius: 999px;
1475
+ }
1476
+
1477
+ html {
1478
+ hanging-punctuation: first allow-end last;
1479
+ -webkit-text-size-adjust: none;
1480
+ text-size-adjust: none;
1481
+ tab-size: 4;
1482
+ font-family:
1483
+ ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI",
1484
+ "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
1485
+ font-size: 16px;
1486
+ line-height: 1.5;
1487
+ -webkit-font-smoothing: antialiased;
1488
+ -moz-osx-font-smoothing: grayscale;
1489
+ text-rendering: optimizeLegibility;
1490
+ -webkit-tap-highlight-color: transparent;
1491
+ scroll-behavior: smooth;
1492
+ background: Canvas;
1493
+ color: CanvasText;
1494
+ }
1495
+
1496
+ body {
1497
+ min-height: 100svh;
1498
+ line-height: 1.5;
1499
+ background: Canvas;
1500
+ color: CanvasText;
1501
+ }
1502
+
1503
+ h1 {
1504
+ font-size: 2em;
1505
+ }
1506
+
1507
+ h2 {
1508
+ font-size: 1.5em;
1509
+ }
1510
+
1511
+ h3 {
1512
+ font-size: 1.17em;
1513
+ }
1514
+
1515
+ h4 {
1516
+ font-size: 1em;
1517
+ }
1518
+
1519
+ h5 {
1520
+ font-size: 0.83em;
1521
+ }
1522
+
1523
+ h6 {
1524
+ font-size: 0.67em;
1525
+ }
1526
+
1527
+ h1,
1528
+ h2,
1529
+ h3,
1530
+ h4,
1531
+ h5,
1532
+ h6 {
1533
+ font-weight: 700;
1534
+ line-height: 1.2;
1535
+ text-wrap: balance;
1536
+ overflow-wrap: break-word;
1537
+ }
1538
+
1539
+ p,
1540
+ li,
1541
+ figcaption,
1542
+ dd,
1543
+ dt,
1544
+ blockquote {
1545
+ overflow-wrap: break-word;
1546
+ }
1547
+
1548
+ p,
1549
+ li,
1550
+ figcaption,
1551
+ blockquote {
1552
+ text-wrap: pretty;
1553
+ }
1554
+
1555
+ ul,
1556
+ ol,
1557
+ menu {
1558
+ list-style: none;
1559
+ }
1560
+
1561
+ a {
1562
+ color: inherit;
1563
+ text-decoration: underline;
1564
+ text-decoration-thickness: 1px;
1565
+ text-underline-offset: 0.15em;
1566
+ text-decoration-skip-ink: auto;
1567
+ }
1568
+
1569
+ b,
1570
+ strong {
1571
+ font-weight: 700;
1572
+ }
1573
+
1574
+ small {
1575
+ font-size: 0.875em;
1576
+ }
1577
+
1578
+ code,
1579
+ kbd,
1580
+ samp,
1581
+ pre {
1582
+ font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
1583
+ font-size: 1em;
1584
+ }
1585
+
1586
+ sub,
1587
+ sup {
1588
+ font-size: 75%;
1589
+ line-height: 0;
1590
+ position: relative;
1591
+ vertical-align: baseline;
1592
+ }
1593
+
1594
+ sub {
1595
+ bottom: -0.25em;
1596
+ }
1597
+
1598
+ sup {
1599
+ top: -0.5em;
1600
+ }
1601
+
1602
+ abbr[title] {
1603
+ text-decoration: underline dotted;
1604
+ text-underline-offset: 0.15em;
1605
+ }
1606
+
1607
+ mark {
1608
+ background: Mark;
1609
+ color: MarkText;
1610
+ }
1611
+
1612
+ hr {
1613
+ height: 0;
1614
+ border: 0;
1615
+ border-top: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
1616
+ color: inherit;
1617
+ }
1618
+
1619
+ img,
1620
+ picture,
1621
+ video,
1622
+ canvas {
1623
+ display: block;
1624
+ max-width: 100%;
1625
+ }
1626
+
1627
+ img,
1628
+ video {
1629
+ height: auto;
1630
+ }
1631
+
1632
+ svg {
1633
+ overflow: hidden;
1634
+ }
1635
+
1636
+ iframe {
1637
+ border: 0;
1638
+ max-width: 100%;
1639
+ }
1640
+
1641
+ table {
1642
+ border-collapse: collapse;
1643
+ border-spacing: 0;
1644
+ }
1645
+
1646
+ fieldset {
1647
+ border: 0;
1648
+ min-inline-size: 0;
1649
+ }
1650
+
1651
+ legend {
1652
+ padding: 0;
1653
+ }
1654
+
1655
+ button,
1656
+ input,
1657
+ select,
1658
+ textarea,
1659
+ optgroup {
1660
+ font: inherit;
1661
+ letter-spacing: inherit;
1662
+ color: inherit;
1663
+ margin: 0;
1664
+ vertical-align: middle;
1665
+ }
1666
+
1667
+ button,
1668
+ input[type="button"],
1669
+ input[type="submit"],
1670
+ input[type="reset"] {
1671
+ appearance: none;
1672
+ background: none;
1673
+ border: 0;
1674
+ padding: 0;
1675
+ cursor: pointer;
1676
+ text-align: center;
1677
+ }
1678
+
1679
+ input,
1680
+ textarea,
1681
+ select {
1682
+ background-color: Canvas;
1683
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1684
+ border-radius: 0;
1685
+ padding: 0.5em 0.65em;
1686
+ }
1687
+
1688
+ input[type="checkbox"],
1689
+ input[type="radio"],
1690
+ input[type="range"],
1691
+ input[type="file"],
1692
+ input[type="color"],
1693
+ input[type="button"],
1694
+ input[type="submit"],
1695
+ input[type="reset"],
1696
+ input[type="image"],
1697
+ input[type="hidden"] {
1698
+ background: none;
1699
+ border: 0;
1700
+ padding: 0;
1701
+ border-radius: 0;
1702
+ }
1703
+
1704
+ input[type="checkbox"],
1705
+ input[type="radio"] {
1706
+ accent-color: CanvasText;
1707
+ }
1708
+
1709
+ input[type="search"] {
1710
+ appearance: none;
1711
+ background-color: Canvas;
1712
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1713
+ }
1714
+
1715
+ input[type="search"]::-webkit-search-decoration,
1716
+ input[type="search"]::-webkit-search-cancel-button,
1717
+ input[type="search"]::-webkit-search-results-button,
1718
+ input[type="search"]::-webkit-search-results-decoration {
1719
+ -webkit-appearance: none;
1720
+ display: none;
1721
+ }
1722
+
1723
+ input[type="number"] {
1724
+ appearance: textfield;
1725
+ background-color: Canvas;
1726
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1727
+ }
1728
+
1729
+ input[type="number"]::-webkit-inner-spin-button,
1730
+ input[type="number"]::-webkit-outer-spin-button {
1731
+ -webkit-appearance: none;
1732
+ margin: 0;
1733
+ }
1734
+
1735
+ textarea {
1736
+ resize: vertical;
1737
+ }
1738
+
1739
+ ::placeholder {
1740
+ opacity: 1;
1741
+ color: color-mix(in srgb, CanvasText 45%, transparent);
1742
+ }
1743
+
1744
+ :focus-visible {
1745
+ outline: 2px solid CanvasText;
1746
+ outline-offset: 2px;
1747
+ }
1748
+
1749
+ :focus:not(:focus-visible) {
1750
+ outline: none;
1751
+ }
1752
+
1753
+ :disabled,
1754
+ [disabled] {
1755
+ cursor: default;
1756
+ opacity: 0.6;
1757
+ }
1758
+
1759
+ label,
1760
+ summary,
1761
+ select,
1762
+ [role="button"] {
1763
+ cursor: pointer;
1764
+ }
1765
+
1766
+ summary {
1767
+ display: list-item;
1768
+ }
1769
+
1770
+ details {
1771
+ display: block;
1772
+ }
1773
+
1774
+ dialog {
1775
+ margin: auto;
1776
+ padding: 1rem;
1777
+ border: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
1778
+ background: Canvas;
1779
+ color: CanvasText;
1780
+ max-width: calc(100vw - 2rem);
1781
+ max-height: calc(100svh - 2rem);
1782
+ }
1783
+
1784
+ dialog:not([open]) {
1785
+ display: none;
1786
+ }
1787
+
1788
+ ::backdrop {
1789
+ background: color-mix(in srgb, CanvasText 40%, transparent);
1790
+ }
1791
+
1792
+ [hidden] {
1793
+ display: none !important;
1794
+ }
1795
+
1796
+ ::selection {
1797
+ background: Highlight;
1798
+ color: HighlightText;
1799
+ }
1800
+
1801
+ @media (prefers-reduced-motion: reduce) {
1802
+ *,
1803
+ *::before,
1804
+ *::after {
1805
+ animation-duration: 0.01ms !important;
1806
+ animation-iteration-count: 1 !important;
1807
+ transition-duration: 0.01ms !important;
1808
+ scroll-behavior: auto !important;
1809
+ }
1810
+ }
1811
+
1812
+ @media (pointer: coarse) {
1813
+ input,
1814
+ select,
1815
+ textarea {
1816
+ font-size: max(16px, 1em);
1817
+ }
1818
+ }`;
1819
+
1820
+ // ../core/dist/admin-client.js
1821
+ function isRecord2(value) {
1822
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1823
+ }
1824
+ function mountAdminBrand(doc, host, service, catalog) {
1825
+ const clip = (value, max) => {
1826
+ if (typeof value !== "string")
1827
+ return null;
1828
+ const trimmed = value.trim();
1829
+ if (trimmed === "")
1830
+ return null;
1831
+ return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
1832
+ };
1833
+ const httpUrl = (value) => {
1834
+ if (typeof value !== "string" || value.length > 2e3)
1835
+ return null;
1836
+ try {
1837
+ const url = new URL(value);
1838
+ if (url.protocol !== "https:" && url.protocol !== "http:")
1839
+ return null;
1840
+ return url.href;
1841
+ } catch {
1842
+ return null;
1843
+ }
1844
+ };
1845
+ const hexColor = (value) => {
1846
+ if (typeof value !== "string")
1847
+ return null;
1848
+ const raw = value.trim();
1849
+ return /^#[0-9a-fA-F]{3,8}$/.test(raw) ? raw : null;
1850
+ };
1851
+ const hostname = (value) => {
1852
+ try {
1853
+ return new URL(value).hostname.replace(/^www\./, "");
1854
+ } catch {
1855
+ return value;
1856
+ }
1857
+ };
1858
+ host.replaceChildren();
1859
+ host.hidden = true;
1860
+ if (!isRecord2(catalog) || !Object.hasOwn(catalog, service))
1861
+ return;
1862
+ const row = catalog[service];
1863
+ if (!isRecord2(row))
1864
+ return;
1865
+ const vendor = clip(row.vendor, 80);
1866
+ const website = httpUrl(row.website);
1867
+ const guide = httpUrl(row.guide);
1868
+ const docs = httpUrl(row.docs);
1869
+ const logo = httpUrl(row.logo);
1870
+ const color = hexColor(row.color);
1871
+ const description = clip(row.description, 280);
1872
+ if (vendor === null || website === null && guide === null && docs === null)
1873
+ return;
1874
+ const add = (node) => {
1875
+ host.append(node);
1876
+ return node;
1877
+ };
1878
+ if (logo !== null) {
1879
+ const img = doc.createElement("img");
1880
+ img.alt = "";
1881
+ img.src = logo;
1882
+ img.width = 16;
1883
+ img.height = 16;
1884
+ img.decoding = "async";
1885
+ img.referrerPolicy = "no-referrer";
1886
+ if (color !== null)
1887
+ img.style.borderColor = color;
1888
+ add(img);
1889
+ }
1890
+ const name = doc.createElement("span");
1891
+ name.className = "vendor-name";
1892
+ name.textContent = vendor;
1893
+ if (description !== null)
1894
+ name.title = description;
1895
+ add(name);
1896
+ const sep = () => {
1897
+ const node = doc.createElement("span");
1898
+ node.className = "vendor-sep";
1899
+ node.setAttribute("aria-hidden", "true");
1900
+ node.textContent = "\xB7";
1901
+ return node;
1902
+ };
1903
+ const link = (href, label, title) => {
1904
+ const node = doc.createElement("a");
1905
+ node.className = "vendor-link";
1906
+ node.href = href;
1907
+ node.target = "_blank";
1908
+ node.rel = "noopener noreferrer";
1909
+ node.referrerPolicy = "no-referrer";
1910
+ node.textContent = label;
1911
+ node.title = title;
1912
+ return node;
1913
+ };
1914
+ if (website !== null) {
1915
+ add(sep());
1916
+ add(link(website, hostname(website), `${vendor} website`));
1917
+ }
1918
+ if (guide !== null) {
1919
+ add(sep());
1920
+ add(link(guide, "Docs", `Docs for ${vendor}`));
1921
+ }
1922
+ if (docs !== null) {
1923
+ add(sep());
1924
+ add(link(docs, "API", `${vendor} API reference`));
1925
+ }
1926
+ host.removeAttribute("hidden");
1927
+ host.hidden = false;
1928
+ }
1929
+ function startAdminBrand(env) {
1930
+ const requested = env.params.get("brands");
1931
+ let brandsUrl = env.brandsUrl;
1932
+ if (requested) {
1933
+ try {
1934
+ const url = new URL(requested, env.location.href);
1935
+ const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]" || url.origin === env.location.origin;
1936
+ if ((url.protocol === "https:" || url.protocol === "http:") && local)
1937
+ brandsUrl = url.href;
1938
+ } catch {
1939
+ brandsUrl = env.brandsUrl;
1940
+ }
1941
+ }
1942
+ const vendorHost = env.$("vendor");
1943
+ const brandRoot = env.document.querySelector("[data-mockingbird-admin]");
1944
+ const brandService = brandRoot?.getAttribute("data-service") || "";
1945
+ if (vendorHost === null)
1946
+ return;
1947
+ env.fetch(brandsUrl, {
1948
+ headers: { accept: "application/json" },
1949
+ signal: AbortSignal.timeout(5e3)
1950
+ }).then(async (response) => {
1951
+ if (!response.ok)
1952
+ return null;
1953
+ const catalog = await response.json();
1954
+ return catalog;
1955
+ }).then((catalog) => {
1956
+ mountAdminBrand(env.document, vendorHost, brandService, catalog);
1957
+ }).catch(() => {
1958
+ });
1959
+ }
1960
+ function faultPresetList(value) {
1961
+ if (!isRecord2(value))
1962
+ throw new Error("Fault presets response was not an object");
1963
+ if (!Object.hasOwn(value, "presets") || value.presets === void 0 || value.presets === null) {
1964
+ return { presets: [] };
1965
+ }
1966
+ if (!Array.isArray(value.presets))
1967
+ throw new Error("Fault presets response was not a list");
1968
+ return {
1969
+ presets: value.presets.flatMap((item) => {
1970
+ if (!isRecord2(item) || typeof item.name !== "string")
1971
+ return [];
1972
+ return [{ name: item.name }];
1973
+ })
1974
+ };
1975
+ }
1976
+ function bootAdmin(config) {
1977
+ const fieldKinds = ["string", "number", "boolean", "null", "object", "array", "unknown"];
1978
+ const sources = ["declared", "inferred", "mixed"];
1979
+ const isFieldKind = (value) => typeof value === "string" && fieldKinds.some((kind) => kind === value);
1980
+ const isSource = (value) => typeof value === "string" && sources.some((source) => source === value);
1981
+ const readJson2 = (text2) => {
1982
+ const value = JSON.parse(text2);
1983
+ return value;
1984
+ };
1985
+ const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => {
1986
+ switch (char) {
1987
+ case "&":
1988
+ return "&amp;";
1989
+ case "<":
1990
+ return "&lt;";
1991
+ case ">":
1992
+ return "&gt;";
1993
+ case '"':
1994
+ return "&quot;";
1995
+ default:
1996
+ return "&#39;";
1997
+ }
1998
+ });
1999
+ const el = (id, kind) => {
2000
+ const node = document.getElementById(id);
2001
+ if (!(node instanceof kind))
2002
+ throw new Error(`admin shell is missing #${id}`);
2003
+ return node;
2004
+ };
2005
+ const errorMessage = (data, fallback) => {
2006
+ if (!isRecord2(data))
2007
+ return fallback;
2008
+ const error = data.error;
2009
+ if (!isRecord2(error) || typeof error.message !== "string" || error.message === "") {
2010
+ return fallback;
2011
+ }
2012
+ return error.message;
2013
+ };
2014
+ const requireList = (value, label) => {
2015
+ if (value === void 0 || value === null)
2016
+ return [];
2017
+ if (!Array.isArray(value))
2018
+ throw new Error(`${label} was not a list`);
2019
+ return value;
2020
+ };
2021
+ const state = {
2022
+ namespace: "default",
2023
+ key: sessionStorage.getItem("mockingbird-admin-key") || "",
2024
+ view: "overview",
2025
+ collection: "",
2026
+ shape: null,
2027
+ route: null,
2028
+ editing: null
2029
+ };
2030
+ const standard = new Set(config.standardRoutes);
2031
+ const banner = el("banner", HTMLElement);
2032
+ const keyInput = el("admin-key", HTMLInputElement);
2033
+ keyInput.value = state.key;
2034
+ const params = new URLSearchParams(location.search);
2035
+ const queryKey = params.get("key");
2036
+ if (queryKey) {
2037
+ state.key = queryKey;
2038
+ keyInput.value = state.key;
2039
+ sessionStorage.setItem("mockingbird-admin-key", state.key);
2040
+ params.delete("key");
2041
+ const next = params.toString();
2042
+ history.replaceState(null, "", location.pathname + (next ? `?${next}` : ""));
2043
+ }
2044
+ const queryNamespace = params.get("namespace");
2045
+ if (queryNamespace)
2046
+ state.namespace = queryNamespace;
2047
+ const showError = (error) => {
2048
+ banner.textContent = error instanceof Error ? error.message : String(error);
2049
+ banner.classList.add("show");
2050
+ };
2051
+ const clearError = () => {
2052
+ banner.classList.remove("show");
2053
+ };
2054
+ const api = async (method, path, body) => {
2055
+ const headers = { accept: "application/json" };
2056
+ if (state.key !== "")
2057
+ headers[config.adminKeyHeader] = state.key;
2058
+ if (state.namespace !== "")
2059
+ headers["x-mockingbird-namespace"] = state.namespace;
2060
+ if (body !== void 0)
2061
+ headers["content-type"] = "application/json";
2062
+ const init = { method, headers };
2063
+ if (body !== void 0)
2064
+ init.body = JSON.stringify(body);
2065
+ const response = await fetch(config.adminPrefix + path, init);
2066
+ const text2 = await response.text();
2067
+ let data = null;
2068
+ if (text2 !== "") {
2069
+ try {
2070
+ data = readJson2(text2);
2071
+ } catch {
2072
+ data = { raw: text2 };
2073
+ }
2074
+ }
2075
+ if (response.status === 401) {
2076
+ keyInput.focus();
2077
+ throw new Error(errorMessage(data, "Admin key required"));
2078
+ }
2079
+ if (!response.ok) {
2080
+ throw new Error(errorMessage(data, `${response.status} ${method} ${path}`));
2081
+ }
2082
+ return data;
2083
+ };
2084
+ const originGet = async (path) => {
2085
+ const response = await fetch(path, { headers: { accept: "application/json" } });
2086
+ if (!response.ok)
2087
+ throw new Error(`${response.status} ${path}`);
2088
+ const value = await response.json();
2089
+ return value;
2090
+ };
2091
+ const panelApi = {
2092
+ get: (path) => api("GET", path),
2093
+ send: (method, path, body) => api(method, path, body),
2094
+ namespace: () => state.namespace
2095
+ };
2096
+ const isPanelScript = (value) => typeof value === "function";
2097
+ const setTheme = (theme) => {
2098
+ if (theme)
2099
+ document.documentElement.dataset.theme = theme;
2100
+ else
2101
+ delete document.documentElement.dataset.theme;
2102
+ };
2103
+ const storedTheme = localStorage.getItem("mockingbird-admin-theme");
2104
+ if (storedTheme)
2105
+ setTheme(storedTheme);
2106
+ el("theme", HTMLButtonElement).addEventListener("click", () => {
2107
+ const current = document.documentElement.dataset.theme;
2108
+ const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
2109
+ const next = dark ? "light" : "dark";
2110
+ localStorage.setItem("mockingbird-admin-theme", next);
2111
+ setTheme(next);
2112
+ });
2113
+ const show = (view) => {
2114
+ state.view = view;
2115
+ for (const button of document.querySelectorAll("#nav button")) {
2116
+ if (!(button instanceof HTMLButtonElement))
2117
+ continue;
2118
+ button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false");
2119
+ }
2120
+ for (const section of document.querySelectorAll("main .view")) {
2121
+ if (!(section instanceof HTMLElement))
2122
+ continue;
2123
+ section.hidden = section.id !== `view-${view}`;
2124
+ }
2125
+ };
2126
+ el("nav", HTMLElement).addEventListener("click", (event) => {
2127
+ const target = event.target;
2128
+ if (!(target instanceof Element))
2129
+ return;
2130
+ const button = target.closest("button[data-view]");
2131
+ if (!(button instanceof HTMLButtonElement))
2132
+ return;
2133
+ const view = button.dataset.view;
2134
+ if (view === void 0)
2135
+ return;
2136
+ show(view);
2137
+ refresh().catch(showError);
2138
+ });
2139
+ 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>`;
2140
+ const table = (columns, rows) => {
2141
+ if (rows.length === 0)
2142
+ return '<p class="empty">Nothing here yet.</p>';
2143
+ const head = `<tr>${columns.map((column) => `<th>${column.label}</th>`).join("")}</tr>`;
2144
+ const body = rows.map((row) => {
2145
+ const cells = columns.map((column) => `<td data-label="${column.label}">${column.cell(row)}</td>`).join("");
2146
+ const attrs = "attrs" in row && typeof row.attrs === "string" ? row.attrs : "";
2147
+ return `<tr${attrs}>${cells}</tr>`;
2148
+ }).join("");
2149
+ return `<table><thead>${head}</thead><tbody>${body}</tbody></table>`;
2150
+ };
2151
+ const preview = (value) => {
2152
+ const text2 = JSON.stringify(value) ?? "null";
2153
+ return esc(text2.length > 180 ? `${text2.slice(0, 180)}\u2026` : text2);
2154
+ };
2155
+ const when = (epoch) => {
2156
+ const date = new Date(epoch);
2157
+ return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString();
2158
+ };
2159
+ const clockCard = (epoch) => {
2160
+ const date = new Date(epoch);
2161
+ if (Number.isNaN(date.getTime()))
2162
+ return esc(String(epoch));
2163
+ const iso = date.toISOString();
2164
+ return `${esc(iso.slice(0, 10))}<span class="clock-time">${esc(iso.slice(11, 19))} UTC</span>`;
2165
+ };
2166
+ const decodeClock = (value) => {
2167
+ if (!isRecord2(value) || typeof value.now !== "number" || typeof value.offsetMs !== "number" || typeof value.frozen !== "boolean") {
2168
+ throw new Error("Clock response was not a clock");
2169
+ }
2170
+ return { now: value.now, offsetMs: value.offsetMs, frozen: value.frozen };
2171
+ };
2172
+ const decodeField = (value) => {
2173
+ if (!isRecord2(value) || typeof value.name !== "string" || !isFieldKind(value.kind)) {
2174
+ return void 0;
2175
+ }
2176
+ const field = {
2177
+ name: value.name,
2178
+ kind: value.kind,
2179
+ optional: value.optional === true
2180
+ };
2181
+ if (typeof value.description === "string")
2182
+ field.description = value.description;
2183
+ if (Array.isArray(value.fields)) {
2184
+ field.fields = value.fields.flatMap((item) => {
2185
+ const child = decodeField(item);
2186
+ return child === void 0 ? [] : [child];
2187
+ });
2188
+ }
2189
+ return field;
2190
+ };
2191
+ const decodeCollection = (value) => {
2192
+ if (!isRecord2(value) || typeof value.name !== "string")
2193
+ return void 0;
2194
+ const fields = Array.isArray(value.fields) ? value.fields.flatMap((item) => {
2195
+ const field = decodeField(item);
2196
+ return field === void 0 ? [] : [field];
2197
+ }) : [];
2198
+ const collection = {
2199
+ name: value.name,
2200
+ label: typeof value.label === "string" ? value.label : value.name,
2201
+ count: typeof value.count === "number" ? value.count : 0,
2202
+ source: isSource(value.source) ? value.source : "inferred",
2203
+ fields
2204
+ };
2205
+ if (typeof value.description === "string")
2206
+ collection.description = value.description;
2207
+ return collection;
2208
+ };
2209
+ const decodeState = (value) => {
2210
+ if (!isRecord2(value) || !Array.isArray(value.collections)) {
2211
+ throw new Error("State response has no collection list");
2212
+ }
2213
+ return {
2214
+ namespace: typeof value.namespace === "string" ? value.namespace : state.namespace,
2215
+ storageNamespace: typeof value.storageNamespace === "string" ? value.storageNamespace : "",
2216
+ collections: value.collections.flatMap((item) => {
2217
+ const collection = decodeCollection(item);
2218
+ return collection === void 0 ? [] : [collection];
2219
+ })
2220
+ };
2221
+ };
2222
+ const decodeRecords = (value) => requireList(isRecord2(value) ? value.records : void 0, "Records").flatMap((item) => {
2223
+ if (!isRecord2(item) || typeof item.id !== "string")
2224
+ return [];
2225
+ return [
2226
+ {
2227
+ id: item.id,
2228
+ seq: typeof item.seq === "number" ? item.seq : 0,
2229
+ value: item.value,
2230
+ attrs: ` class="clickable" data-id="${esc(item.id)}"`
2231
+ }
2232
+ ];
2233
+ });
2234
+ const textOf = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : "";
2235
+ const decodeJournal = (value) => requireList(isRecord2(value) ? value.requests : void 0, "Journal").flatMap((item) => {
2236
+ if (!isRecord2(item))
2237
+ return [];
2238
+ return [
2239
+ {
2240
+ at: textOf(item.at),
2241
+ method: textOf(item.method),
2242
+ path: textOf(item.path),
2243
+ status: textOf(item.status),
2244
+ durationMs: textOf(item.durationMs),
2245
+ operationId: textOf(item.operationId)
2246
+ }
2247
+ ];
2248
+ });
2249
+ const decodeFaults = (value) => requireList(isRecord2(value) ? value.faults : void 0, "Faults").flatMap((item) => {
2250
+ if (!isRecord2(item))
2251
+ return [];
2252
+ return [
2253
+ {
2254
+ id: textOf(item.id),
2255
+ method: typeof item.method === "string" ? item.method : void 0,
2256
+ pathPrefix: typeof item.pathPrefix === "string" ? item.pathPrefix : void 0,
2257
+ operationId: typeof item.operationId === "string" ? item.operationId : void 0,
2258
+ status: typeof item.status === "number" ? item.status : void 0,
2259
+ effect: typeof item.effect === "string" ? item.effect : void 0,
2260
+ drop: typeof item.drop === "boolean" ? item.drop : void 0,
2261
+ latencyMs: typeof item.latencyMs === "number" ? item.latencyMs : void 0
2262
+ }
2263
+ ];
2264
+ });
2265
+ const loadNamespaces = async () => {
2266
+ const data = await api("GET", "/namespaces");
2267
+ const select = el("namespace", HTMLSelectElement);
2268
+ const listed = requireList(isRecord2(data) ? data.namespaces : void 0, "Namespaces");
2269
+ const fallback = isRecord2(data) && typeof data.default === "string" && data.default !== "" ? data.default : "default";
2270
+ const names = (listed.length > 0 ? listed.map((name) => String(name)) : [fallback]).slice();
2271
+ if (!names.includes(state.namespace))
2272
+ names.unshift(state.namespace);
2273
+ select.innerHTML = names.map((name) => `<option value="${esc(name)}">${esc(name)}</option>`).join("");
2274
+ select.value = state.namespace;
2275
+ };
2276
+ const renderOverview = async () => {
2277
+ const [health, shape, clock, journal] = await Promise.all([
2278
+ originGet("/health"),
2279
+ api("GET", "/state"),
2280
+ api("GET", "/clock"),
2281
+ api("GET", "/requests?limit=8")
2282
+ ]);
2283
+ if (!isRecord2(health))
2284
+ throw new Error("Health response was not an object");
2285
+ const decoded = decodeState(shape);
2286
+ state.shape = decoded;
2287
+ const time = decodeClock(clock);
2288
+ el("overview-cards", HTMLElement).innerHTML = cards([
2289
+ {
2290
+ k: "Service",
2291
+ v: esc(typeof health.service === "string" && health.service !== "" ? health.service : config.service)
2292
+ },
2293
+ {
2294
+ k: "Status",
2295
+ v: esc(typeof health.status === "string" && health.status !== "" ? health.status : "ok")
2296
+ },
2297
+ { k: "Namespace", v: esc(state.namespace) },
2298
+ { k: "Collections", v: String(decoded.collections.length) },
2299
+ {
2300
+ k: "Records",
2301
+ v: String(decoded.collections.reduce((sum, item) => sum + item.count, 0))
2302
+ },
2303
+ { k: "Clock", v: clockCard(time.now) }
2304
+ ]);
2305
+ el("overview-requests", HTMLElement).innerHTML = table([
2306
+ { label: "Method", cell: (row) => esc(row.method) },
2307
+ { label: "Path", cell: (row) => esc(row.path) },
2308
+ {
2309
+ label: "Status",
2310
+ cell: (row) => `<span class="status-${row.status.charAt(0)}">${esc(row.status)}</span>`
2311
+ },
2312
+ { label: "Operation", cell: (row) => esc(row.operationId) }
2313
+ ], decodeJournal(journal));
2314
+ };
2315
+ const schemaChips = (collection) => {
2316
+ if (collection.fields.length === 0) {
2317
+ return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>';
2318
+ }
2319
+ return `<div class="chips">${collection.fields.map((field) => `<span class="chip">${esc(field.name)} <i>${esc(field.kind)}${field.optional ? "?" : ""}</i></span>`).join("")}</div>`;
2320
+ };
2321
+ const renderState = async () => {
2322
+ const shape = decodeState(await api("GET", "/state"));
2323
+ state.shape = shape;
2324
+ const host = el("collections", HTMLElement);
2325
+ if (shape.collections.length === 0) {
2326
+ host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>';
2327
+ el("schema", HTMLElement).innerHTML = "";
2328
+ el("records", HTMLElement).innerHTML = "";
2329
+ return;
2330
+ }
2331
+ if (!shape.collections.some((item) => item.name === state.collection)) {
2332
+ const first = shape.collections[0];
2333
+ if (first === void 0)
2334
+ return;
2335
+ state.collection = first.name;
2336
+ }
2337
+ 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("");
2338
+ const selected = shape.collections.find((item) => item.name === state.collection);
2339
+ if (selected === void 0)
2340
+ return;
2341
+ const description = selected.description ? `<p class="lede">${esc(selected.description)}</p>` : "";
2342
+ el("schema", HTMLElement).innerHTML = `<h3>${esc(selected.label)}</h3>${description}${schemaChips(selected)}`;
2343
+ const page = await api("GET", `/state/${encodeURIComponent(state.collection)}?limit=50`);
2344
+ el("records", HTMLElement).innerHTML = table([
2345
+ { label: "Id", cell: (row) => `<code>${esc(row.id)}</code>` },
2346
+ { label: "Seq", cell: (row) => esc(row.seq) },
2347
+ { label: "Value", cell: (row) => preview(row.value) }
2348
+ ], decodeRecords(page));
2349
+ };
2350
+ const renderClock = async () => {
2351
+ const clock = decodeClock(await api("GET", "/clock"));
2352
+ 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>`;
2353
+ const freeze = el("freeze", HTMLButtonElement);
2354
+ freeze.textContent = clock.frozen ? "Unfreeze" : "Freeze";
2355
+ freeze.dataset.frozen = clock.frozen ? "1" : "0";
2356
+ };
2357
+ const renderFaults = async () => {
2358
+ const [faults, presets] = await Promise.all([
2359
+ api("GET", "/faults"),
2360
+ api("GET", "/faults/presets").catch(() => ({ presets: [] }))
2361
+ ]);
2362
+ const select = el("fault-preset", HTMLSelectElement);
2363
+ const current = select.value;
2364
+ const list = faultPresetList(presets).presets;
2365
+ select.innerHTML = `<option value="">Custom rule</option>${list.map((preset) => `<option value="${esc(preset.name)}">${esc(preset.name)}</option>`).join("")}`;
2366
+ select.value = current;
2367
+ el("fault-list", HTMLElement).innerHTML = table([
2368
+ { label: "Id", cell: (row) => esc(row.id) },
2369
+ {
2370
+ label: "Match",
2371
+ cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter((part) => Boolean(part)).join(" ") || "all")
2372
+ },
2373
+ {
2374
+ label: "Effect",
2375
+ cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || ""))
2376
+ }
2377
+ ], decodeFaults(faults));
2378
+ };
2379
+ const renderJournal = async () => {
2380
+ const journal = await api("GET", "/requests?limit=100");
2381
+ el("journal", HTMLElement).innerHTML = table([
2382
+ { label: "When", cell: (row) => esc(row.at) },
2383
+ { label: "Method", cell: (row) => esc(row.method) },
2384
+ { label: "Path", cell: (row) => esc(row.path) },
2385
+ { label: "Status", cell: (row) => esc(row.status) },
2386
+ { label: "ms", cell: (row) => esc(row.durationMs) }
2387
+ ], decodeJournal(journal));
2388
+ };
2389
+ const renderRoutes = async () => {
2390
+ const data = await api("GET", "/");
2391
+ if (!isRecord2(data))
2392
+ throw new Error("Route list was not an object");
2393
+ const routes = requireList(data.routes, "Routes").map((route) => String(route));
2394
+ el("route-list", HTMLElement).innerHTML = routes.map((route) => {
2395
+ const extra = standard.has(route) ? "" : " <span class='badge'>extension</span>";
2396
+ const parts = route.split(" ");
2397
+ const method = parts[0] ?? "";
2398
+ const path = parts.slice(1).join(" ");
2399
+ 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>`;
2400
+ }).join("");
2401
+ };
2402
+ const renderers = {
2403
+ overview: renderOverview,
2404
+ state: renderState,
2405
+ clock: renderClock,
2406
+ faults: renderFaults,
2407
+ journal: renderJournal,
2408
+ routes: renderRoutes
2409
+ };
2410
+ async function refresh() {
2411
+ clearError();
2412
+ const render = renderers[state.view];
2413
+ if (render)
2414
+ await render();
2415
+ }
2416
+ el("namespace", HTMLSelectElement).addEventListener("change", () => {
2417
+ state.namespace = el("namespace", HTMLSelectElement).value;
2418
+ refresh().catch(showError);
2419
+ });
2420
+ keyInput.addEventListener("change", () => {
2421
+ state.key = keyInput.value.trim();
2422
+ sessionStorage.setItem("mockingbird-admin-key", state.key);
2423
+ refresh().catch(showError);
2424
+ });
2425
+ el("refresh", HTMLButtonElement).addEventListener("click", () => {
2426
+ refresh().catch(showError);
2427
+ });
2428
+ el("collections", HTMLElement).addEventListener("click", (event) => {
2429
+ const target = event.target;
2430
+ if (!(target instanceof Element))
2431
+ return;
2432
+ const button = target.closest("[data-collection]");
2433
+ if (!(button instanceof HTMLElement))
2434
+ return;
2435
+ const collection = button.dataset.collection;
2436
+ if (collection === void 0)
2437
+ return;
2438
+ state.collection = collection;
2439
+ renderState().catch(showError);
2440
+ });
2441
+ el("records", HTMLElement).addEventListener("click", (event) => {
2442
+ const target = event.target;
2443
+ if (!(target instanceof Element))
2444
+ return;
2445
+ const row = target.closest("[data-id]");
2446
+ if (!(row instanceof HTMLElement))
2447
+ return;
2448
+ const id = row.dataset.id;
2449
+ if (id === void 0)
2450
+ return;
2451
+ openEditor(id).catch(showError);
2452
+ });
2453
+ const editor = el("editor", HTMLDialogElement);
2454
+ const editorId = el("editor-id", HTMLInputElement);
2455
+ const editorValue = el("editor-value", HTMLTextAreaElement);
2456
+ const editorDelete = el("editor-delete", HTMLButtonElement);
2457
+ async function openEditor(id) {
2458
+ state.editing = id;
2459
+ el("editor-title", HTMLElement).textContent = id ? "Edit record" : "New record";
2460
+ editorId.value = id ?? "";
2461
+ editorId.readOnly = id !== null;
2462
+ editorDelete.hidden = id === null;
2463
+ if (id) {
2464
+ const record = await api("GET", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`);
2465
+ if (!isRecord2(record))
2466
+ throw new Error("Record response was not an object");
2467
+ editorValue.value = JSON.stringify(record.value, null, 2) ?? "";
2468
+ } else {
2469
+ editorValue.value = "{\n \n}";
2470
+ }
2471
+ editor.showModal();
2472
+ }
2473
+ el("new-record", HTMLButtonElement).addEventListener("click", () => {
2474
+ openEditor(null).catch(showError);
2475
+ });
2476
+ el("editor-form", HTMLFormElement).addEventListener("submit", (event) => {
2477
+ const submitter = event.submitter;
2478
+ if (!(submitter instanceof HTMLButtonElement) || submitter.value !== "save")
2479
+ return;
2480
+ event.preventDefault();
2481
+ let value;
2482
+ try {
2483
+ value = readJson2(editorValue.value);
2484
+ } catch {
2485
+ showError(new Error("Value is not JSON"));
2486
+ return;
2487
+ }
2488
+ const id = editorId.value.trim();
2489
+ const path = `/state/${encodeURIComponent(state.collection)}`;
2490
+ const request = state.editing ? api("PUT", `${path}/${encodeURIComponent(id)}`, { value }) : api("POST", path, { id: id || void 0, value });
2491
+ request.then(() => {
2492
+ editor.close();
2493
+ return renderState();
2494
+ }).catch(showError);
2495
+ });
2496
+ editorDelete.addEventListener("click", () => {
2497
+ const id = state.editing;
2498
+ if (!id)
2499
+ return;
2500
+ api("DELETE", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`).then(() => {
2501
+ editor.close();
2502
+ return renderState();
2503
+ }).catch(showError);
2504
+ });
2505
+ for (const button of document.querySelectorAll("[data-advance]")) {
2506
+ if (!(button instanceof HTMLButtonElement))
2507
+ continue;
2508
+ button.addEventListener("click", () => {
2509
+ const raw = button.dataset.advance;
2510
+ if (raw === void 0)
2511
+ return;
2512
+ api("POST", "/clock", { advance: Number(raw) }).then(() => renderClock()).catch(showError);
2513
+ });
2514
+ }
2515
+ el("freeze", HTMLButtonElement).addEventListener("click", () => {
2516
+ const frozen = el("freeze", HTMLButtonElement).dataset.frozen === "1";
2517
+ api("POST", "/clock", { freeze: !frozen }).then(() => renderClock()).catch(showError);
2518
+ });
2519
+ el("clock-reset", HTMLButtonElement).addEventListener("click", () => {
2520
+ api("POST", "/clock", { reset: true }).then(() => renderClock()).catch(showError);
2521
+ });
2522
+ el("clock-set", HTMLFormElement).addEventListener("submit", (event) => {
2523
+ event.preventDefault();
2524
+ const raw = el("clock-instant", HTMLInputElement).value.trim();
2525
+ const set = /^-?\d+$/.test(raw) ? Number(raw) : raw;
2526
+ api("POST", "/clock", { set }).then(() => renderClock()).catch(showError);
2527
+ });
2528
+ el("fault-form", HTMLFormElement).addEventListener("submit", (event) => {
2529
+ event.preventDefault();
2530
+ const preset = el("fault-preset", HTMLSelectElement).value;
2531
+ if (preset !== "") {
2532
+ api("POST", "/faults", { preset }).then(() => renderFaults()).catch(showError);
2533
+ return;
2534
+ }
2535
+ const status = Number(el("fault-status", HTMLInputElement).value);
2536
+ if (!Number.isFinite(status)) {
2537
+ showError(new Error("Status must be a number, or choose a preset"));
2538
+ return;
2539
+ }
2540
+ const path = el("fault-path", HTMLInputElement).value;
2541
+ const method = el("fault-method", HTMLInputElement).value;
2542
+ api("POST", "/faults", {
2543
+ status,
2544
+ ...path !== "" ? { pathPrefix: path } : {},
2545
+ ...method !== "" ? { method } : {}
2546
+ }).then(() => renderFaults()).catch(showError);
2547
+ });
2548
+ el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
2549
+ api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
2550
+ });
2551
+ el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
2552
+ api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
2553
+ });
2554
+ el("route-list", HTMLElement).addEventListener("click", (event) => {
2555
+ const target = event.target;
2556
+ if (!(target instanceof Element))
2557
+ return;
2558
+ const button = target.closest("[data-route]");
2559
+ if (!(button instanceof HTMLElement))
2560
+ return;
2561
+ const route = button.dataset.route;
2562
+ if (route === void 0)
2563
+ return;
2564
+ state.route = route;
2565
+ el("route-title", HTMLElement).textContent = route;
2566
+ const method = route.split(" ")[0] ?? "";
2567
+ el("route-body", HTMLTextAreaElement).disabled = method === "GET" || method === "DELETE";
2568
+ });
2569
+ el("route-form", HTMLFormElement).addEventListener("submit", (event) => {
2570
+ event.preventDefault();
2571
+ const send = async () => {
2572
+ if (state.route === null)
2573
+ throw new Error("Choose a route first");
2574
+ const parts = state.route.split(" ");
2575
+ const method = parts[0];
2576
+ const path = parts.slice(1).join(" ");
2577
+ if (method === void 0)
2578
+ throw new Error("Choose a route first");
2579
+ const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_match, key) => prompt(`Value for ${key}`) || "");
2580
+ let body;
2581
+ const routeBody = el("route-body", HTMLTextAreaElement);
2582
+ if (method !== "GET" && method !== "DELETE" && routeBody.value.trim() !== "") {
2583
+ try {
2584
+ body = readJson2(routeBody.value);
2585
+ } catch {
2586
+ throw new Error("Body is not JSON");
2587
+ }
2588
+ }
2589
+ const data = await api(method, concrete, body);
2590
+ el("route-result", HTMLElement).textContent = JSON.stringify(data, null, 2);
2591
+ };
2592
+ send().catch(showError);
2593
+ });
2594
+ const decodePanel = (value) => {
2595
+ if (!isRecord2(value) || typeof value.id !== "string" || typeof value.title !== "string") {
2596
+ return void 0;
2597
+ }
2598
+ const panel = {
2599
+ id: value.id,
2600
+ title: value.title,
2601
+ description: typeof value.description === "string" ? value.description : "",
2602
+ html: typeof value.html === "string" ? value.html : ""
2603
+ };
2604
+ if (typeof value.script === "string")
2605
+ panel.script = value.script;
2606
+ return panel;
2607
+ };
2608
+ const mountPanels = (panels) => {
2609
+ const nav = el("nav", HTMLElement);
2610
+ const views = el("views", HTMLElement);
2611
+ for (const panel of panels) {
2612
+ const button = document.createElement("button");
2613
+ button.type = "button";
2614
+ button.dataset.view = panel.id;
2615
+ button.textContent = panel.title;
2616
+ nav.append(button);
2617
+ const section = document.createElement("section");
2618
+ section.className = "view";
2619
+ section.id = `view-${panel.id}`;
2620
+ section.hidden = true;
2621
+ const title = document.createElement("h2");
2622
+ title.textContent = panel.title;
2623
+ const lede = document.createElement("p");
2624
+ lede.className = "lede";
2625
+ lede.textContent = panel.description;
2626
+ const body = document.createElement("div");
2627
+ body.className = "panel-body";
2628
+ body.id = `panel-${panel.id}`;
2629
+ body.innerHTML = panel.html;
2630
+ section.append(title, lede, body);
2631
+ views.append(section);
2632
+ if (panel.script === void 0)
2633
+ continue;
2634
+ try {
2635
+ const compiled = new Function("root", "api", panel.script);
2636
+ if (!isPanelScript(compiled))
2637
+ throw new Error("panel script did not compile");
2638
+ compiled(body, panelApi);
2639
+ } catch (error) {
2640
+ const note = document.createElement("p");
2641
+ note.className = "banner show";
2642
+ note.textContent = error instanceof Error ? error.message : String(error);
2643
+ body.append(note);
2644
+ }
2645
+ }
2646
+ };
2647
+ startAdminBrand({
2648
+ document,
2649
+ $: (id) => document.getElementById(id),
2650
+ params,
2651
+ location: { href: location.href, origin: location.origin },
2652
+ fetch,
2653
+ brandsUrl: config.brandsUrl
2654
+ });
2655
+ loadNamespaces().then(() => api("GET", "/ui/manifest")).then((manifest) => {
2656
+ const panels = requireList(isRecord2(manifest) ? manifest.panels : void 0, "Panels").flatMap((panel) => {
2657
+ const decoded = decodePanel(panel);
2658
+ return decoded === void 0 ? [] : [decoded];
2659
+ });
2660
+ mountPanels(panels);
2661
+ return refresh();
2662
+ }).catch(showError);
2663
+ }
2664
+ var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
2665
+ var adminClientSource = (config) => {
2666
+ const payload = JSON.stringify(config).replace(/</g, "\\u003c");
2667
+ return `<script>
2668
+ ${clientPrelude}
2669
+ bootAdmin(${payload});
2670
+ </script>`;
2671
+ };
2672
+
1447
2673
  // ../core/dist/surface.js
1448
2674
  var defineMock = (runtime) => runtime;
1449
2675
  var STANDARD_ADMIN_ROUTES = [
@@ -1512,94 +2738,106 @@ var escapeHtml = (value) => value.replace(/[&<>"']/g, (char) => {
1512
2738
  return "&#39;";
1513
2739
  }
1514
2740
  });
2741
+ var ADMIN_BRANDS_URL = "https://mockingbird.chrisvouga.dev/brands.json";
1515
2742
  var renderAdminDocument = (service) => {
1516
2743
  const name = escapeHtml(service);
1517
- const standard = JSON.stringify(STANDARD_ADMIN_ROUTES);
2744
+ const script = adminClientSource({
2745
+ standardRoutes: STANDARD_ADMIN_ROUTES,
2746
+ adminPrefix: ADMIN_PREFIX,
2747
+ adminKeyHeader: ADMIN_KEY_HEADER,
2748
+ brandsUrl: ADMIN_BRANDS_URL,
2749
+ service
2750
+ });
1518
2751
  return `<!DOCTYPE html>
1519
2752
  <html lang="en">
1520
2753
  <head>
1521
2754
  <meta charset="utf-8">
1522
2755
  <meta name="viewport" content="width=device-width, initial-scale=1">
1523
2756
  <meta name="color-scheme" content="light dark">
1524
- <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">
2757
+ <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">
1525
2758
  <title>${name} admin</title>
1526
2759
  <style>
2760
+ ${CSS_RESET}
1527
2761
  :root {
1528
2762
  color-scheme: light;
1529
- --bg: #f6f5f2;
2763
+ --bg: #fafafa;
1530
2764
  --bg-elev: #ffffff;
1531
- --bg-muted: #eeece6;
1532
- --bg-inset: #f3f1eb;
1533
- --line: #e2dfd6;
1534
- --line-strong: #d0ccc1;
1535
- --ink: #1c1915;
1536
- --muted: #5e584f;
1537
- --faint: #8a8378;
1538
- --accent: #3d4f3a;
1539
- --accent-ink: #f7f6f2;
1540
- --accent-soft: #e5ece2;
1541
- --ok: #1f7a45;
1542
- --ok-soft: #e5f4eb;
1543
- --warn: #9a6700;
1544
- --warn-soft: #fbf3df;
1545
- --err: #a33b32;
1546
- --err-soft: #fdecea;
1547
- --shadow: 0 1px 2px rgb(28 25 21 / 0.05), 0 12px 32px rgb(28 25 21 / 0.06);
1548
- --sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
2765
+ --bg-muted: #f4f4f5;
2766
+ --bg-inset: #f4f4f5;
2767
+ --line: #dedee3;
2768
+ --line-strong: #c4c4cc;
2769
+ --ink: #18181b;
2770
+ --muted: #62626b;
2771
+ --faint: #71717a;
2772
+ --accent: #27272a;
2773
+ --accent-ink: #ffffff;
2774
+ --accent-soft: #f4f4f5;
2775
+ --ok: #166534;
2776
+ --ok-soft: #f0fdf4;
2777
+ --warn: #a16207;
2778
+ --warn-soft: #fefce8;
2779
+ --err: #a82d32;
2780
+ --err-soft: #fef2f2;
2781
+ --shadow: none;
2782
+ --sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
1549
2783
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
1550
- --radius: 14px;
1551
- --header: 60px;
1552
- --nav: 220px;
2784
+ --radius: 10px;
2785
+ --header: 56px;
2786
+ --nav: 200px;
1553
2787
  }
1554
2788
  :root[data-theme="dark"] {
1555
2789
  color-scheme: dark;
1556
- --bg: #141311;
1557
- --bg-elev: #1e1c19;
1558
- --bg-muted: #26231f;
1559
- --bg-inset: #181715;
1560
- --line: #34302b;
1561
- --line-strong: #4a453d;
1562
- --ink: #f4f1ea;
1563
- --muted: #c4bdb1;
1564
- --faint: #8f877c;
1565
- --accent: #c5d5b8;
1566
- --accent-ink: #1a2118;
1567
- --accent-soft: #2a3328;
1568
- --ok: #8fd6a8;
1569
- --ok-soft: #1a2e22;
1570
- --warn: #f0cc78;
1571
- --warn-soft: #332911;
1572
- --err: #f0a8a2;
1573
- --err-soft: #3a201e;
1574
- --shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 16px 40px rgb(0 0 0 / 0.28);
2790
+ --bg: #111113;
2791
+ --bg-elev: #19191c;
2792
+ --bg-muted: #242428;
2793
+ --bg-inset: #111113;
2794
+ --line: #36363c;
2795
+ --line-strong: #52525b;
2796
+ --ink: #f4f4f5;
2797
+ --muted: #a9a9b2;
2798
+ --faint: #71717a;
2799
+ --accent: #e4e4e7;
2800
+ --accent-ink: #18181b;
2801
+ --accent-soft: #242428;
2802
+ --ok: #86efac;
2803
+ --ok-soft: #14241b;
2804
+ --warn: #fde68a;
2805
+ --warn-soft: #2a2410;
2806
+ --err: #ffaaaa;
2807
+ --err-soft: #2c1516;
2808
+ --shadow: none;
1575
2809
  }
1576
2810
  @media (prefers-color-scheme: dark) {
1577
2811
  :root:not([data-theme="light"]) {
1578
2812
  color-scheme: dark;
1579
- --bg: #141311;
1580
- --bg-elev: #1e1c19;
1581
- --bg-muted: #26231f;
1582
- --bg-inset: #181715;
1583
- --line: #34302b;
1584
- --line-strong: #4a453d;
1585
- --ink: #f4f1ea;
1586
- --muted: #c4bdb1;
1587
- --faint: #8f877c;
1588
- --accent: #c5d5b8;
1589
- --accent-ink: #1a2118;
1590
- --accent-soft: #2a3328;
1591
- --ok: #8fd6a8;
1592
- --ok-soft: #1a2e22;
1593
- --warn: #f0cc78;
1594
- --warn-soft: #332911;
1595
- --err: #f0a8a2;
1596
- --err-soft: #3a201e;
1597
- --shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 16px 40px rgb(0 0 0 / 0.28);
2813
+ --bg: #111113;
2814
+ --bg-elev: #19191c;
2815
+ --bg-muted: #242428;
2816
+ --bg-inset: #111113;
2817
+ --line: #36363c;
2818
+ --line-strong: #52525b;
2819
+ --ink: #f4f4f5;
2820
+ --muted: #a9a9b2;
2821
+ --faint: #71717a;
2822
+ --accent: #e4e4e7;
2823
+ --accent-ink: #18181b;
2824
+ --accent-soft: #242428;
2825
+ --ok: #86efac;
2826
+ --ok-soft: #14241b;
2827
+ --warn: #fde68a;
2828
+ --warn-soft: #2a2410;
2829
+ --err: #ffaaaa;
2830
+ --err-soft: #2c1516;
2831
+ --shadow: none;
1598
2832
  }
1599
2833
  }
1600
- * { box-sizing: border-box; }
1601
- html, body { margin: 0; height: 100%; }
2834
+ /* Fill the viewport, then grow with the content so the background covers the scroll. */
2835
+ html { height: 100%; }
1602
2836
  body {
2837
+ margin: 0;
2838
+ min-height: 100%;
2839
+ display: flex;
2840
+ flex-direction: column;
1603
2841
  font-family: var(--sans);
1604
2842
  background: var(--bg);
1605
2843
  color: var(--ink);
@@ -1608,24 +2846,37 @@ var renderAdminDocument = (service) => {
1608
2846
  padding-right: env(safe-area-inset-right);
1609
2847
  -webkit-text-size-adjust: 100%;
1610
2848
  }
1611
- button, input, select, textarea { font: inherit; color: inherit; }
1612
- button { cursor: pointer; }
1613
2849
  :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
1614
- @media (prefers-reduced-motion: reduce) {
1615
- * { scroll-behavior: auto !important; transition: none !important; }
1616
- }
1617
- .app { min-height: 100%; display: flex; flex-direction: column; }
2850
+ .app { flex: 1 0 auto; display: flex; flex-direction: column; min-width: 0; width: 100%; }
1618
2851
  header.top {
1619
2852
  position: sticky; top: 0; z-index: 5;
1620
2853
  display: flex; align-items: center; gap: 12px;
1621
2854
  min-height: var(--header); padding: 10px 16px;
1622
- background: color-mix(in srgb, var(--bg-elev) 92%, transparent);
2855
+ background: var(--bg-elev);
1623
2856
  border-bottom: 1px solid var(--line);
1624
- backdrop-filter: blur(10px);
1625
- }
1626
- .brand { display: flex; flex-direction: column; min-width: 0; margin-right: auto; }
1627
- .brand strong { font-size: 15px; letter-spacing: -0.01em; }
1628
- .brand span { color: var(--faint); font-size: 12px; }
2857
+ min-width: 0; max-width: 100%;
2858
+ }
2859
+ .brand {
2860
+ display: flex; flex-flow: row wrap; align-items: center; gap: 14px;
2861
+ min-width: 0; max-width: 100%; margin-right: auto;
2862
+ }
2863
+ .brand-id { display: flex; flex-direction: column; min-width: 0; flex: none; }
2864
+ .brand-id strong { font-size: 15px; letter-spacing: -0.01em; }
2865
+ .brand-id span { color: var(--faint); font-size: 12px; }
2866
+ .vendor {
2867
+ display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
2868
+ min-width: 0; max-width: 100%; font-size: 12px; color: var(--muted);
2869
+ }
2870
+ .vendor[hidden] { display: none; }
2871
+ /* Logos are drawn for a light tile, same as the docs site, so the plate stays white. */
2872
+ .vendor img {
2873
+ width: 16px; height: 16px; padding: 2px; box-sizing: content-box;
2874
+ border-radius: 4px; border: 1px solid var(--line); background: #fff; object-fit: contain;
2875
+ }
2876
+ .vendor-name { color: var(--ink); font-weight: 600; }
2877
+ .vendor-sep { color: var(--faint); }
2878
+ .vendor-link { color: inherit; text-decoration: none; }
2879
+ .vendor-link:hover { color: var(--ink); text-decoration: underline; }
1629
2880
  .controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
1630
2881
  label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
1631
2882
  label.field input, label.field select, .controls input[type="password"] {
@@ -1643,7 +2894,7 @@ var renderAdminDocument = (service) => {
1643
2894
  record or the full nav row would widen the page past the viewport. */
1644
2895
  .layout {
1645
2896
  display: grid; grid-template-columns: var(--nav) minmax(0, 1fr);
1646
- flex: 1; min-height: 0; min-width: 0;
2897
+ flex: 1 0 auto; min-width: 0;
1647
2898
  }
1648
2899
  nav.side {
1649
2900
  border-right: 1px solid var(--line); padding: 12px;
@@ -1658,7 +2909,7 @@ var renderAdminDocument = (service) => {
1658
2909
  main { padding: 20px; max-width: 1100px; width: 100%; min-width: 0; }
1659
2910
  .view h2 { margin: 0 0 4px; font-size: 22px; letter-spacing: -0.02em; }
1660
2911
  .lede { margin: 0 0 16px; color: var(--muted); }
1661
- .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
2912
+ .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr)); gap: 12px; }
1662
2913
  .card {
1663
2914
  background: var(--bg-elev); border: 1px solid var(--line); border-radius: var(--radius);
1664
2915
  padding: 14px 16px; box-shadow: var(--shadow);
@@ -1697,8 +2948,8 @@ var renderAdminDocument = (service) => {
1697
2948
  .banner.show { display: block; }
1698
2949
  .empty { color: var(--muted); padding: 24px 8px; }
1699
2950
  dialog {
1700
- border: 1px solid var(--line); border-radius: 16px; padding: 0; background: var(--bg-elev);
1701
- color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow: var(--shadow);
2951
+ border: 1px solid var(--line); border-radius: var(--radius); padding: 0; background: var(--bg-elev);
2952
+ color: var(--ink); width: min(640px, calc(100% - 24px)); max-width: calc(100vw - 24px); box-shadow: none;
1702
2953
  }
1703
2954
  dialog::backdrop { background: rgb(20 19 17 / 0.45); }
1704
2955
  dialog form, .dialog-body { padding: 16px; display: flex; flex-direction: column; gap: 10px; }
@@ -1725,7 +2976,7 @@ var renderAdminDocument = (service) => {
1725
2976
  padding-top: max(10px, env(safe-area-inset-top));
1726
2977
  }
1727
2978
  input, select, textarea { font-size: 16px; }
1728
- .controls { width: 100%; }
2979
+ .brand, .vendor, .controls { width: 100%; }
1729
2980
  label.field, label.field select, label.field input { flex: 1; min-width: 0; }
1730
2981
  .layout { grid-template-columns: minmax(0, 1fr); }
1731
2982
  nav.side {
@@ -1756,8 +3007,11 @@ var renderAdminDocument = (service) => {
1756
3007
  <div class="app" data-service="${name}" data-mockingbird-admin>
1757
3008
  <header class="top">
1758
3009
  <div class="brand">
1759
- <strong>${name}</strong>
1760
- <span>Mockingbird admin</span>
3010
+ <div class="brand-id">
3011
+ <strong>${name}</strong>
3012
+ <span>Admin</span>
3013
+ </div>
3014
+ <div class="vendor" id="vendor" hidden></div>
1761
3015
  </div>
1762
3016
  <div class="controls">
1763
3017
  <label class="field">Namespace
@@ -1887,460 +3141,7 @@ var renderAdminDocument = (service) => {
1887
3141
  </div>
1888
3142
  </form>
1889
3143
  </dialog>
1890
- <script>
1891
- (() => {
1892
- const STANDARD = new Set(${standard})
1893
- const state = {
1894
- namespace: "default",
1895
- key: sessionStorage.getItem("mockingbird-admin-key") || "",
1896
- view: "overview",
1897
- collection: "",
1898
- shape: null,
1899
- route: null,
1900
- editing: null
1901
- }
1902
- const $ = (id) => document.getElementById(id)
1903
- const banner = $("banner")
1904
- const keyInput = $("admin-key")
1905
- keyInput.value = state.key
1906
- const params = new URLSearchParams(location.search)
1907
- if (params.get("key")) {
1908
- state.key = params.get("key")
1909
- keyInput.value = state.key
1910
- sessionStorage.setItem("mockingbird-admin-key", state.key)
1911
- params.delete("key")
1912
- const next = params.toString()
1913
- history.replaceState(null, "", location.pathname + (next ? "?" + next : ""))
1914
- }
1915
- if (params.get("namespace")) state.namespace = params.get("namespace")
1916
-
1917
- const showError = (error) => {
1918
- banner.textContent = error instanceof Error ? error.message : String(error)
1919
- banner.classList.add("show")
1920
- }
1921
- const clearError = () => banner.classList.remove("show")
1922
-
1923
- async function originGet(path) {
1924
- const response = await fetch(path, { headers: { accept: "application/json" } })
1925
- if (!response.ok) throw new Error(response.status + " " + path)
1926
- return response.json()
1927
- }
1928
- async function api(method, path, body) {
1929
- const headers = { accept: "application/json" }
1930
- if (state.key) headers["${ADMIN_KEY_HEADER}"] = state.key
1931
- if (state.namespace) headers["x-mockingbird-namespace"] = state.namespace
1932
- if (body !== undefined) headers["content-type"] = "application/json"
1933
- const response = await fetch("${ADMIN_PREFIX}" + path, {
1934
- method,
1935
- headers,
1936
- body: body === undefined ? undefined : JSON.stringify(body)
1937
- })
1938
- const text = await response.text()
1939
- let data = null
1940
- if (text) {
1941
- try { data = JSON.parse(text) } catch { data = { raw: text } }
1942
- }
1943
- if (response.status === 401) {
1944
- keyInput.focus()
1945
- throw new Error((data && data.error && data.error.message) || "Admin key required")
1946
- }
1947
- if (!response.ok) {
1948
- throw new Error((data && data.error && data.error.message) || (response.status + " " + method + " " + path))
1949
- }
1950
- return data
1951
- }
1952
- const panelApi = {
1953
- get: (path) => api("GET", path),
1954
- send: (method, path, body) => api(method, path, body),
1955
- namespace: () => state.namespace
1956
- }
1957
-
1958
- function setTheme(theme) {
1959
- if (theme) document.documentElement.dataset.theme = theme
1960
- else delete document.documentElement.dataset.theme
1961
- }
1962
- const storedTheme = localStorage.getItem("mockingbird-admin-theme")
1963
- if (storedTheme) setTheme(storedTheme)
1964
- $("theme").addEventListener("click", () => {
1965
- const current = document.documentElement.dataset.theme
1966
- const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches
1967
- const next = dark ? "light" : "dark"
1968
- localStorage.setItem("mockingbird-admin-theme", next)
1969
- setTheme(next)
1970
- })
1971
-
1972
- function show(view) {
1973
- state.view = view
1974
- for (const button of document.querySelectorAll("#nav button")) {
1975
- button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false")
1976
- }
1977
- for (const section of document.querySelectorAll("main .view")) {
1978
- section.hidden = section.id !== "view-" + view
1979
- }
1980
- }
1981
- document.getElementById("nav").addEventListener("click", (event) => {
1982
- const button = event.target.closest("button[data-view]")
1983
- if (!button) return
1984
- show(button.dataset.view)
1985
- refresh().catch(showError)
1986
- })
1987
-
1988
- function cards(items) {
1989
- return '<div class="grid">' + items.map((item) =>
1990
- '<article class="card"><div class="k">' + item.k + '</div><div class="v">' + item.v + '</div></article>'
1991
- ).join("") + "</div>"
1992
- }
1993
- function table(columns, rows) {
1994
- if (rows.length === 0) return '<p class="empty">Nothing here yet.</p>'
1995
- const head = "<tr>" + columns.map((column) => "<th>" + column.label + "</th>").join("") + "</tr>"
1996
- const body = rows.map((row) => "<tr" + (row.attrs || "") + ">" + columns.map((column) =>
1997
- '<td data-label="' + column.label + '">' + column.cell(row) + "</td>"
1998
- ).join("") + "</tr>").join("")
1999
- return "<table><thead>" + head + "</thead><tbody>" + body + "</tbody></table>"
2000
- }
2001
- const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({
2002
- "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"
2003
- }[char]))
2004
- const preview = (value) => {
2005
- const text = JSON.stringify(value)
2006
- return esc(text.length > 180 ? text.slice(0, 180) + "\u2026" : text)
2007
- }
2008
- const when = (epoch) => {
2009
- const date = new Date(epoch)
2010
- return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString()
2011
- }
2012
- const clockCard = (epoch) => {
2013
- const date = new Date(epoch)
2014
- if (Number.isNaN(date.getTime())) return esc(String(epoch))
2015
- const iso = date.toISOString()
2016
- return esc(iso.slice(0, 10)) + '<span class="clock-time">' + esc(iso.slice(11, 19)) + " UTC</span>"
2017
- }
2018
-
2019
- async function loadNamespaces() {
2020
- const data = await api("GET", "/namespaces")
2021
- const select = $("namespace")
2022
- const names = data.namespaces && data.namespaces.length ? data.namespaces : [data.default || "default"]
2023
- if (!names.includes(state.namespace)) names.unshift(state.namespace)
2024
- select.innerHTML = names.map((name) =>
2025
- '<option value="' + esc(name) + '">' + esc(name) + "</option>"
2026
- ).join("")
2027
- select.value = state.namespace
2028
- }
2029
-
2030
- async function renderOverview() {
2031
- const [health, shape, clock, journal] = await Promise.all([
2032
- originGet("/health"),
2033
- api("GET", "/state"),
2034
- api("GET", "/clock"),
2035
- api("GET", "/requests?limit=8")
2036
- ])
2037
- state.shape = shape
2038
- $("overview-cards").innerHTML = cards([
2039
- { k: "Service", v: esc(health.service || "${name}") },
2040
- { k: "Status", v: esc(health.status || "ok") },
2041
- { k: "Namespace", v: esc(state.namespace) },
2042
- { k: "Collections", v: String(shape.collections.length) },
2043
- { k: "Records", v: String(shape.collections.reduce((sum, item) => sum + item.count, 0)) },
2044
- { k: "Clock", v: clockCard(clock.now) }
2045
- ])
2046
- const requests = (journal && journal.requests) || []
2047
- $("overview-requests").innerHTML = table(
2048
- [
2049
- { label: "Method", cell: (row) => esc(row.method) },
2050
- { label: "Path", cell: (row) => esc(row.path) },
2051
- { label: "Status", cell: (row) => '<span class="status-' + String(row.status).charAt(0) + '">' + esc(row.status) + "</span>" },
2052
- { label: "Operation", cell: (row) => esc(row.operationId || "") }
2053
- ],
2054
- requests
2055
- )
2056
- }
2057
-
2058
- function schemaChips(collection) {
2059
- if (!collection || collection.fields.length === 0) {
2060
- return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>'
2061
- }
2062
- return '<div class="chips">' + collection.fields.map((field) =>
2063
- '<span class="chip">' + esc(field.name) + ' <i>' + esc(field.kind) + (field.optional ? "?" : "") + "</i></span>"
2064
- ).join("") + "</div>"
2065
- }
2066
-
2067
- async function renderState() {
2068
- const shape = await api("GET", "/state")
2069
- state.shape = shape
2070
- const host = $("collections")
2071
- if (shape.collections.length === 0) {
2072
- host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>'
2073
- $("schema").innerHTML = ""
2074
- $("records").innerHTML = ""
2075
- return
2076
- }
2077
- if (!shape.collections.some((item) => item.name === state.collection)) {
2078
- state.collection = shape.collections[0].name
2079
- }
2080
- host.innerHTML = shape.collections.map((item) =>
2081
- '<button type="button" class="collection" data-collection="' + esc(item.name) + '" aria-current="' +
2082
- (item.name === state.collection ? "true" : "false") + '"><strong>' + esc(item.label) +
2083
- '</strong><small>' + esc(item.count) + " \xB7 " + esc(item.source) + "</small></button>"
2084
- ).join("")
2085
- const selected = shape.collections.find((item) => item.name === state.collection)
2086
- $("schema").innerHTML = "<h3>" + esc(selected.label) + "</h3>" +
2087
- (selected.description ? "<p class=" + '"lede">' + esc(selected.description) + "</p>" : "") +
2088
- schemaChips(selected)
2089
- const page = await api("GET", "/state/" + encodeURIComponent(state.collection) + "?limit=50")
2090
- const rows = page.records.map((record) => ({
2091
- ...record,
2092
- attrs: ' class="clickable" data-id="' + esc(record.id) + '"'
2093
- }))
2094
- $("records").innerHTML = table(
2095
- [
2096
- { label: "Id", cell: (row) => "<code>" + esc(row.id) + "</code>" },
2097
- { label: "Seq", cell: (row) => esc(row.seq) },
2098
- { label: "Value", cell: (row) => preview(row.value) }
2099
- ],
2100
- rows
2101
- )
2102
- }
2103
-
2104
- async function renderClock() {
2105
- const clock = await api("GET", "/clock")
2106
- $("clock-readout").innerHTML = "<div class='k'>Now</div><div class='v'>" + esc(when(clock.now)) +
2107
- "</div><p>" + esc(clock.now) + " ms \xB7 offset " + esc(clock.offsetMs) + " ms " +
2108
- (clock.frozen ? "<span class='badge warn'>frozen</span>" : "<span class='badge ok'>live</span>") + "</p>"
2109
- $("freeze").textContent = clock.frozen ? "Unfreeze" : "Freeze"
2110
- $("freeze").dataset.frozen = clock.frozen ? "1" : "0"
2111
- }
2112
-
2113
- async function renderFaults() {
2114
- const [faults, presets] = await Promise.all([
2115
- api("GET", "/faults"),
2116
- api("GET", "/faults/presets").catch(() => ({ presets: [] }))
2117
- ])
2118
- const select = $("fault-preset")
2119
- const current = select.value
2120
- const list = presets.presets || []
2121
- select.innerHTML = '<option value="">Custom rule</option>' + list.map((preset) =>
2122
- '<option value="' + esc(preset.name) + '">' + esc(preset.name) + "</option>"
2123
- ).join("")
2124
- select.value = current
2125
- const rules = faults.faults || []
2126
- $("fault-list").innerHTML = table(
2127
- [
2128
- { label: "Id", cell: (row) => esc(row.id) },
2129
- { label: "Match", cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter(Boolean).join(" ") || "all") },
2130
- { label: "Effect", cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || "")) }
2131
- ],
2132
- rules
2133
- )
2134
- }
2135
-
2136
- async function renderJournal() {
2137
- const journal = await api("GET", "/requests?limit=100")
2138
- $("journal").innerHTML = table(
2139
- [
2140
- { label: "When", cell: (row) => esc(row.at || "") },
2141
- { label: "Method", cell: (row) => esc(row.method) },
2142
- { label: "Path", cell: (row) => esc(row.path) },
2143
- { label: "Status", cell: (row) => esc(row.status) },
2144
- { label: "ms", cell: (row) => esc(row.durationMs) }
2145
- ],
2146
- journal.requests || []
2147
- )
2148
- }
2149
-
2150
- async function renderRoutes() {
2151
- const data = await api("GET", "/")
2152
- const routes = data.routes || []
2153
- $("route-list").innerHTML = routes.map((route) => {
2154
- const extra = STANDARD.has(route) ? "" : " <span class='badge'>extension</span>"
2155
- return '<div class="route"><span class="method">' + esc(route.split(" ")[0]) +
2156
- '</span><span>' + esc(route.split(" ").slice(1).join(" ")) + extra +
2157
- '</span><button class="btn" type="button" data-route="' + esc(route) + '">Use</button></div>'
2158
- }).join("")
2159
- }
2160
-
2161
- const renderers = {
2162
- overview: renderOverview,
2163
- state: renderState,
2164
- clock: renderClock,
2165
- faults: renderFaults,
2166
- journal: renderJournal,
2167
- routes: renderRoutes
2168
- }
2169
-
2170
- async function refresh() {
2171
- clearError()
2172
- if (state.view in renderers) await renderers[state.view]()
2173
- }
2174
-
2175
- $("namespace").addEventListener("change", () => {
2176
- state.namespace = $("namespace").value
2177
- refresh().catch(showError)
2178
- })
2179
- keyInput.addEventListener("change", () => {
2180
- state.key = keyInput.value.trim()
2181
- sessionStorage.setItem("mockingbird-admin-key", state.key)
2182
- refresh().catch(showError)
2183
- })
2184
- $("refresh").addEventListener("click", () => refresh().catch(showError))
2185
-
2186
- $("collections").addEventListener("click", (event) => {
2187
- const button = event.target.closest("[data-collection]")
2188
- if (!button) return
2189
- state.collection = button.dataset.collection
2190
- renderState().catch(showError)
2191
- })
2192
- $("records").addEventListener("click", (event) => {
2193
- const row = event.target.closest("[data-id]")
2194
- if (!row) return
2195
- openEditor(row.dataset.id).catch(showError)
2196
- })
2197
-
2198
- const editor = $("editor")
2199
- async function openEditor(id) {
2200
- state.editing = id || null
2201
- $("editor-title").textContent = id ? "Edit record" : "New record"
2202
- $("editor-id").value = id || ""
2203
- $("editor-id").readOnly = Boolean(id)
2204
- $("editor-delete").hidden = !id
2205
- if (id) {
2206
- const record = await api("GET", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
2207
- $("editor-value").value = JSON.stringify(record.value, null, 2)
2208
- } else {
2209
- $("editor-value").value = "{\\n \\n}"
2210
- }
2211
- editor.showModal()
2212
- }
2213
- $("new-record").addEventListener("click", () => openEditor(null).catch(showError))
2214
- $("editor-form").addEventListener("submit", (event) => {
2215
- const submitter = event.submitter
2216
- if (!submitter || submitter.value !== "save") return
2217
- event.preventDefault()
2218
- let value
2219
- try { value = JSON.parse($("editor-value").value) }
2220
- catch { showError(new Error("Value is not JSON")); return }
2221
- const id = $("editor-id").value.trim()
2222
- const path = "/state/" + encodeURIComponent(state.collection)
2223
- const request = state.editing
2224
- ? api("PUT", path + "/" + encodeURIComponent(id), { value })
2225
- : api("POST", path, { id: id || undefined, value })
2226
- request.then(() => { editor.close(); return renderState() }).catch(showError)
2227
- })
2228
- $("editor-delete").addEventListener("click", () => {
2229
- const id = state.editing
2230
- if (!id) return
2231
- api("DELETE", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
2232
- .then(() => { editor.close(); return renderState() })
2233
- .catch(showError)
2234
- })
2235
-
2236
- document.querySelectorAll("[data-advance]").forEach((button) => {
2237
- button.addEventListener("click", () => {
2238
- api("POST", "/clock", { advance: Number(button.dataset.advance) }).then(renderClock).catch(showError)
2239
- })
2240
- })
2241
- $("freeze").addEventListener("click", () => {
2242
- const frozen = $("freeze").dataset.frozen === "1"
2243
- api("POST", "/clock", { freeze: !frozen }).then(renderClock).catch(showError)
2244
- })
2245
- $("clock-reset").addEventListener("click", () => {
2246
- api("POST", "/clock", { reset: true }).then(renderClock).catch(showError)
2247
- })
2248
- $("clock-set").addEventListener("submit", (event) => {
2249
- event.preventDefault()
2250
- const raw = $("clock-instant").value.trim()
2251
- const set = /^-?\\d+$/.test(raw) ? Number(raw) : raw
2252
- api("POST", "/clock", { set }).then(renderClock).catch(showError)
2253
- })
2254
-
2255
- $("fault-form").addEventListener("submit", (event) => {
2256
- event.preventDefault()
2257
- const preset = $("fault-preset").value
2258
- const body = preset
2259
- ? { preset }
2260
- : {
2261
- status: Number($("fault-status").value),
2262
- ...($("fault-path").value ? { pathPrefix: $("fault-path").value } : {}),
2263
- ...($("fault-method").value ? { method: $("fault-method").value } : {})
2264
- }
2265
- if (!preset && !Number.isFinite(body.status)) {
2266
- showError(new Error("Status must be a number, or choose a preset"))
2267
- return
2268
- }
2269
- api("POST", "/faults", body).then(renderFaults).catch(showError)
2270
- })
2271
- $("fault-clear").addEventListener("click", () => api("DELETE", "/faults").then(renderFaults).catch(showError))
2272
- $("journal-clear").addEventListener("click", () => api("DELETE", "/requests").then(renderJournal).catch(showError))
2273
-
2274
- $("route-list").addEventListener("click", (event) => {
2275
- const button = event.target.closest("[data-route]")
2276
- if (!button) return
2277
- state.route = button.dataset.route
2278
- $("route-title").textContent = state.route
2279
- const method = state.route.split(" ")[0]
2280
- $("route-body").disabled = method === "GET" || method === "DELETE"
2281
- })
2282
- $("route-form").addEventListener("submit", async (event) => {
2283
- event.preventDefault()
2284
- if (!state.route) return showError(new Error("Choose a route first"))
2285
- const [method, path] = [state.route.split(" ")[0], state.route.split(" ").slice(1).join(" ")]
2286
- const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_, key) => prompt("Value for " + key) || "")
2287
- let body
2288
- if (method !== "GET" && method !== "DELETE" && $("route-body").value.trim()) {
2289
- try { body = JSON.parse($("route-body").value) }
2290
- catch { return showError(new Error("Body is not JSON")) }
2291
- }
2292
- try {
2293
- const data = await api(method, concrete, body)
2294
- $("route-result").textContent = JSON.stringify(data, null, 2)
2295
- } catch (error) {
2296
- showError(error)
2297
- }
2298
- })
2299
-
2300
- function mountPanels(panels) {
2301
- const nav = $("nav")
2302
- const views = $("views")
2303
- for (const panel of panels) {
2304
- const button = document.createElement("button")
2305
- button.type = "button"
2306
- button.dataset.view = panel.id
2307
- button.textContent = panel.title
2308
- nav.append(button)
2309
- const section = document.createElement("section")
2310
- section.className = "view"
2311
- section.id = "view-" + panel.id
2312
- section.hidden = true
2313
- const title = document.createElement("h2")
2314
- title.textContent = panel.title
2315
- const lede = document.createElement("p")
2316
- lede.className = "lede"
2317
- lede.textContent = panel.description || ""
2318
- const body = document.createElement("div")
2319
- body.className = "panel-body"
2320
- body.id = "panel-" + panel.id
2321
- body.innerHTML = panel.html || ""
2322
- section.append(title, lede, body)
2323
- views.append(section)
2324
- if (panel.script) {
2325
- try {
2326
- const run = new Function("root", "api", panel.script)
2327
- run(body, panelApi)
2328
- } catch (error) {
2329
- const note = document.createElement("p")
2330
- note.className = "banner show"
2331
- note.textContent = error instanceof Error ? error.message : String(error)
2332
- body.append(note)
2333
- }
2334
- }
2335
- }
2336
- }
2337
-
2338
- loadNamespaces()
2339
- .then(() => api("GET", "/ui/manifest"))
2340
- .then((manifest) => { mountPanels((manifest && manifest.panels) || []); return refresh() })
2341
- .catch(showError)
2342
- })()
2343
- </script>
3144
+ ${script}
2344
3145
  </body>
2345
3146
  </html>`;
2346
3147
  };
@@ -2357,10 +3158,10 @@ var json3 = (body) => new Response(JSON.stringify(body), {
2357
3158
  });
2358
3159
  var adminUiRoutes = (service, ui) => {
2359
3160
  const shell = () => renderAdminDocument(service);
2360
- const document2 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
3161
+ const document3 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
2361
3162
  return {
2362
- "GET /ui": () => htmlResponse(document2()),
2363
- "GET /ui/": () => htmlResponse(document2()),
3163
+ "GET /ui": () => htmlResponse(document3()),
3164
+ "GET /ui/": () => htmlResponse(document3()),
2364
3165
  "GET /ui/manifest": () => json3({
2365
3166
  service,
2366
3167
  panels: (ui?.panels ?? []).map((panel) => ({
@@ -2386,11 +3187,11 @@ var EXTENSION_KEYS = {
2386
3187
  };
2387
3188
 
2388
3189
  // ../../openapi/metadata/dist/read.js
2389
- var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3190
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2390
3191
  var extensionOf = (holder, key) => holder[key];
2391
3192
  var operationMetadata = (operation) => {
2392
3193
  const raw = extensionOf(operation, EXTENSION_KEYS.operation);
2393
- const ext = isRecord2(raw) ? raw : {};
3194
+ const ext = isRecord3(raw) ? raw : {};
2394
3195
  const supported = ext.supported ?? true;
2395
3196
  const parity = ext.parity ?? {};
2396
3197
  return {
@@ -2500,9 +3301,9 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
2500
3301
  this.name = "OperationRegistryError";
2501
3302
  }
2502
3303
  };
2503
- var verifyOperations = (document2, handlers) => {
3304
+ var verifyOperations = (document3, handlers) => {
2504
3305
  const problems = [];
2505
- const operations = listOperations(document2);
3306
+ const operations = listOperations(document3);
2506
3307
  const seen = /* @__PURE__ */ new Set();
2507
3308
  for (const operation of operations) {
2508
3309
  if (seen.has(operation.operationId))
@@ -2618,7 +3419,7 @@ var SAMPLE = 25;
2618
3419
  var WALK_LIMIT = 1e4;
2619
3420
  var SKIP_CTORS = /* @__PURE__ */ new Set(["Hono", "HonoRequest", "Router"]);
2620
3421
  var fail = (status, message) => ({ ok: false, status, message });
2621
- var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3422
+ var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2622
3423
  var kindOf = (value) => {
2623
3424
  if (value === null)
2624
3425
  return "null";
@@ -2717,7 +3518,7 @@ var absorb = (accum, value) => {
2717
3518
  const kind = kindOf(value);
2718
3519
  accum.kinds.add(kind);
2719
3520
  accum.present++;
2720
- if (!isRecord3(value))
3521
+ if (!isRecord4(value))
2721
3522
  return;
2722
3523
  for (const [key, child] of Object.entries(value)) {
2723
3524
  const nested = accum.nested.get(key) ?? blankAccum(key);
@@ -2742,7 +3543,7 @@ var inferFields = (records) => {
2742
3543
  if (records.length === 0)
2743
3544
  return [];
2744
3545
  const sample = records.slice(0, SAMPLE);
2745
- const objects = sample.filter((record) => isRecord3(record.value));
3546
+ const objects = sample.filter((record) => isRecord4(record.value));
2746
3547
  if (objects.length === 0) {
2747
3548
  const kinds = new Set(sample.map((record) => kindOf(record.value)));
2748
3549
  const list = [...kinds];
@@ -2929,7 +3730,7 @@ var patchStateRecord = (scope, name, id, value) => {
2929
3730
  const parsed = jsonValue(value);
2930
3731
  if (!parsed.ok)
2931
3732
  return parsed;
2932
- const next = isRecord3(current.value.value) && isRecord3(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
3733
+ const next = isRecord4(current.value.value) && isRecord4(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
2933
3734
  return replaceStateRecord(scope, name, id, next);
2934
3735
  };
2935
3736
  var deleteStateRecord = (scope, name, id) => {
@@ -2997,7 +3798,7 @@ var stateAdminRoutes = (routes) => {
2997
3798
  const scope = open(namespace);
2998
3799
  if (!scope.ok)
2999
3800
  return fromResult(scope, 201);
3000
- if (!isRecord3(body) || !("value" in body)) {
3801
+ if (!isRecord4(body) || !("value" in body)) {
3001
3802
  return adminError2(400, 'expected {"id?": "<id>", "value": <json>}');
3002
3803
  }
3003
3804
  const id = body.id;
@@ -3015,7 +3816,7 @@ var stateAdminRoutes = (routes) => {
3015
3816
  const scope = open(namespace);
3016
3817
  if (!scope.ok)
3017
3818
  return fromResult(scope, 200);
3018
- if (!isRecord3(body) || !("value" in body))
3819
+ if (!isRecord4(body) || !("value" in body))
3019
3820
  return adminError2(400, 'expected {"value": <json>}');
3020
3821
  const replaced = replaceStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
3021
3822
  if (replaced.ok)
@@ -3026,7 +3827,7 @@ var stateAdminRoutes = (routes) => {
3026
3827
  const scope = open(namespace);
3027
3828
  if (!scope.ok)
3028
3829
  return fromResult(scope, 200);
3029
- if (!isRecord3(body) || !("value" in body))
3830
+ if (!isRecord4(body) || !("value" in body))
3030
3831
  return adminError2(400, 'expected {"value": <json>}');
3031
3832
  const patched = patchStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
3032
3833
  if (patched.ok)
@@ -3046,7 +3847,7 @@ var stateAdminRoutes = (routes) => {
3046
3847
  };
3047
3848
 
3048
3849
  // ../core/dist/version.js
3049
- var PACKAGE_VERSION = true ? "1.5.0" : UNRELEASED_VERSION;
3850
+ var PACKAGE_VERSION = true ? "1.6.1" : UNRELEASED_VERSION;
3050
3851
 
3051
3852
  // ../core/dist/signing.js
3052
3853
  var encoder = new TextEncoder();
@@ -3383,9 +4184,9 @@ var createWebhookHub = (options) => {
3383
4184
  };
3384
4185
  var json5 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
3385
4186
  var adminError3 = (status, message) => json5(status, { error: { type: "mockingbird_admin", message } });
3386
- var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4187
+ var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3387
4188
  var parseEndpoint = (value) => {
3388
- if (!isRecord4(value) || typeof value.url !== "string")
4189
+ if (!isRecord5(value) || typeof value.url !== "string")
3389
4190
  return "each endpoint needs a url";
3390
4191
  try {
3391
4192
  new URL(value.url);
@@ -3402,12 +4203,12 @@ var parseEndpoint = (value) => {
3402
4203
  const events = value.events ?? value.enabledEvents;
3403
4204
  if (Array.isArray(events))
3404
4205
  endpoint.events = events.map(String);
3405
- if (isRecord4(value.tags)) {
4206
+ if (isRecord5(value.tags)) {
3406
4207
  endpoint.tags = Object.fromEntries(Object.entries(value.tags).map(([k, v]) => [k, String(v)]));
3407
4208
  }
3408
4209
  if (typeof value.account === "string")
3409
4210
  endpoint.tags = { ...endpoint.tags, account: value.account };
3410
- if (isRecord4(value.headers)) {
4211
+ if (isRecord5(value.headers)) {
3411
4212
  endpoint.headers = Object.fromEntries(Object.entries(value.headers).map(([k, v]) => [k, String(v)]));
3412
4213
  }
3413
4214
  return endpoint;
@@ -3434,7 +4235,7 @@ var webhookAdminRoutes = (hub) => ({
3434
4235
  return json5(200, { status: "ok" });
3435
4236
  },
3436
4237
  "POST /webhooks/faults": ({ body, namespace }) => {
3437
- if (!isRecord4(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
4238
+ if (!isRecord5(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
3438
4239
  return adminError3(400, "mode must be duplicate, reorder or drop");
3439
4240
  }
3440
4241
  const fault = { mode: body.mode };
@@ -3450,7 +4251,7 @@ var webhookAdminRoutes = (hub) => ({
3450
4251
  }))
3451
4252
  }),
3452
4253
  "PUT /webhook-endpoints": ({ body, namespace }) => {
3453
- const list = Array.isArray(body) ? body : isRecord4(body) ? body.endpoints : void 0;
4254
+ const list = Array.isArray(body) ? body : isRecord5(body) ? body.endpoints : void 0;
3454
4255
  if (!Array.isArray(list))
3455
4256
  return adminError3(400, "expected [{url, secret?, events?}]");
3456
4257
  const parsed = [];
@@ -3520,8 +4321,8 @@ var DroppedConnectionError = class extends TypeError {
3520
4321
  this.name = "TypeError";
3521
4322
  }
3522
4323
  };
3523
- var operationMatcher = (document2) => {
3524
- const matchers = listOperations(document2).map((operation) => ({
4324
+ var operationMatcher = (document3) => {
4325
+ const matchers = listOperations(document3).map((operation) => ({
3525
4326
  operationId: operation.operationId,
3526
4327
  method: operation.method.toUpperCase(),
3527
4328
  pattern: new RegExp(`^${operation.path.split("/").map((segment) => segment.startsWith("{") ? "[^/]+" : segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("/")}/?$`),
@@ -3616,10 +4417,10 @@ var createRuntime = (options) => {
3616
4417
  const ensureBranch = (namespace, branch2, at) => {
3617
4418
  if (!BRANCH_PATTERN2.test(branch2))
3618
4419
  throw new RangeError(`branch must match ${BRANCH_PATTERN2}`);
3619
- const history = timeline(namespace);
4420
+ const history2 = timeline(namespace);
3620
4421
  if (branch2 === "main") {
3621
4422
  if (at !== void 0) {
3622
- const point = history.checkout("main", at);
4423
+ const point = history2.checkout("main", at);
3623
4424
  restoreNamespace(sqlite, storageNamespace(namespace), point.value.snapshot);
3624
4425
  captured.set(namespace, point.value.snapshot);
3625
4426
  rng.setState(point.value.rngState);
@@ -3632,10 +4433,10 @@ var createRuntime = (options) => {
3632
4433
  return namespace;
3633
4434
  }
3634
4435
  const storage = physicalBranch(namespace, branch2);
3635
- if (!history.hasBranch(branch2)) {
4436
+ if (!history2.hasBranch(branch2)) {
3636
4437
  if (at === void 0)
3637
- history.commit(capture(namespace));
3638
- const point = history.fork(branch2, at === void 0 ? {} : { from: at });
4438
+ history2.commit(capture(namespace));
4439
+ const point = history2.fork(branch2, at === void 0 ? {} : { from: at });
3639
4440
  const branchRng = createRng(options.seed ?? 0);
3640
4441
  if (point)
3641
4442
  branchRng.setState(point.value.rngState);
@@ -3644,8 +4445,8 @@ var createRuntime = (options) => {
3644
4445
  restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
3645
4446
  if (point)
3646
4447
  captured.set(storage, point.value.snapshot);
3647
- } else if (at !== void 0 && history.head(branch2)?.id !== at) {
3648
- const point = history.checkout(branch2, at);
4448
+ } else if (at !== void 0 && history2.head(branch2)?.id !== at) {
4449
+ const point = history2.checkout(branch2, at);
3649
4450
  if (!instances.has(storage)) {
3650
4451
  const branchRng = createRng(options.seed ?? 0);
3651
4452
  branchRng.setState(point.value.rngState);
@@ -3656,7 +4457,7 @@ var createRuntime = (options) => {
3656
4457
  captured.set(storage, point.value.snapshot);
3657
4458
  } else {
3658
4459
  if (!instances.has(storage)) {
3659
- const point = history.head(branch2);
4460
+ const point = history2.head(branch2);
3660
4461
  const branchRng = createRng(options.seed ?? 0);
3661
4462
  if (point)
3662
4463
  branchRng.setState(point.value.rngState);
@@ -3680,8 +4481,8 @@ var createRuntime = (options) => {
3680
4481
  const checkout = (checkpointId, checkoutOptions = {}) => {
3681
4482
  const namespace = checkoutOptions.namespace ?? DEFAULT_NAMESPACE;
3682
4483
  const branchName = checkoutOptions.branch ?? "main";
3683
- const history = timeline(namespace);
3684
- const point = history.checkout(branchName, checkpointId);
4484
+ const history2 = timeline(namespace);
4485
+ const point = history2.checkout(branchName, checkpointId);
3685
4486
  const storage = ensureBranch(namespace, branchName);
3686
4487
  restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
3687
4488
  captured.set(storage, point.value.snapshot);
@@ -3731,9 +4532,9 @@ var createRuntime = (options) => {
3731
4532
  instance(name);
3732
4533
  restoreNamespace(sqlite, storageNamespace(name), from);
3733
4534
  captured.set(name, from);
3734
- const history = timelines.get(name);
3735
- if (history)
3736
- history.commit(capture(name), { branch: "main" });
4535
+ const history2 = timelines.get(name);
4536
+ if (history2)
4537
+ history2.commit(capture(name), { branch: "main" });
3737
4538
  else
3738
4539
  timeline(name);
3739
4540
  };
@@ -3962,10 +4763,10 @@ var createRuntime = (options) => {
3962
4763
  },
3963
4764
  release: (name, checkpointId) => timeline(name).release(checkpointId),
3964
4765
  inspect: (name) => {
3965
- const history = timeline(name);
4766
+ const history2 = timeline(name);
3966
4767
  return {
3967
- branches: history.branches(),
3968
- checkpoints: history.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
4768
+ branches: history2.branches(),
4769
+ checkpoints: history2.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
3969
4770
  id,
3970
4771
  branch: branchName,
3971
4772
  parent,
@@ -3980,11 +4781,11 @@ var createRuntime = (options) => {
3980
4781
  } : {},
3981
4782
  routes: {
3982
4783
  ...credentialRoutes(credentials),
3983
- ...options.presets ? presetRoutes(options.presets, runtime) : {},
3984
4784
  ...options.webhooks ? webhookAdminRoutes(options.webhooks) : {},
3985
4785
  ...options.admin?.(runtime) ?? {},
3986
- // Standard state and UI routes win a colliding key. A service adds its own keys
3987
- // beside these; it does not replace `/state` or `/ui`.
4786
+ // Shell routes win a colliding key. Fault presets are one of them: the admin page
4787
+ // maps `presets`, and a service route must not replace that list with a record.
4788
+ ...options.presets ? presetRoutes(options.presets, runtime) : {},
3988
4789
  ...stateAdminRoutes({
3989
4790
  open: stateScope,
3990
4791
  afterWrite: (namespace) => {
@@ -4059,9 +4860,12 @@ var credentialRoutes = (registry) => ({
4059
4860
  }
4060
4861
  });
4061
4862
  var presetRoutes = (presets, runtime) => ({
4062
- "GET /faults/presets": () => adminJson(200, {
4063
- presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
4064
- }),
4863
+ "GET /faults/presets": () => {
4864
+ const body = {
4865
+ presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
4866
+ };
4867
+ return adminJson(200, body);
4868
+ },
4065
4869
  "POST /faults/presets/:name": ({ params, body, namespace }) => {
4066
4870
  const name = params.name;
4067
4871
  if (!presets[name])
@@ -4072,7 +4876,7 @@ var presetRoutes = (presets, runtime) => ({
4072
4876
  });
4073
4877
 
4074
4878
  // src/generated/openapi.ts
4075
- var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Intercom REST API 2.11 (Mockingbird subset)","description":"Stateful mock subset of the Intercom REST API (version 2.11): contacts (search, create,\\nupdate, get), conversations (create, update, reply as user or admin with attachments,\\nclose/open/snooze/assign, get, search) and the admin identity endpoints. Covers the backend's\\nmember messaging adapter and the Intercom sync adapter.\\n","version":"2.11","x-mockingbird-upstream":{"note":"Trimmed from Intercom's published 2.11 API reference to the operations and fields our consumers use (intercom-messaging.adapter.ts, intercom-api.adapter.ts) and the webhook receivers (messaging.controller.ts, the EMR IntercomWebhookService)."}},"servers":[{"url":"https://api.intercom.io"}],"security":[{"bearerAuth":[]}],"paths":{"/contacts/search":{"post":{"operationId":"SearchContacts","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["query"],"properties":{"query":{"$ref":"#/components/schemas/ContactQuery"},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"responses":{"200":{"description":"Matching contacts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/contacts":{"post":{"operationId":"CreateContact","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactCreate"}}}},"responses":{"200":{"description":"The created contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/contacts/{contact_id}":{"parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"contact","missing":0}}}],"get":{"operationId":"GetContact","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"operationId":"UpdateContact","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactUpdate"}}}},"responses":{"200":{"description":"The updated contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/conversations":{"post":{"operationId":"CreateConversation","description":"A contact-initiated conversation. An optional \`Idempotency-Key\` header replays.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"name":"Idempotency-Key","in":"header","schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,40}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["from","body"],"properties":{"from":{"type":"object","required":["type","id"],"properties":{"type":{"type":"string","enum":["user","lead","contact"]},"id":{"type":"string","x-mockingbird-resource-ref":{"type":"contact","missing":0}}}},"body":{"type":"string","minLength":1,"maxLength":400},"created_at":{"type":"integer","minimum":1600000000,"maximum":1900000000}}}}}},"responses":{"200":{"description":"The first message of the new conversation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/conversations/search":{"post":{"operationId":"SearchConversations","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/DisplayAs"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["query"],"properties":{"query":{"$ref":"#/components/schemas/ConversationQuery"},"pagination":{"$ref":"#/components/schemas/Pagination"},"sort_field":{"type":"string","enum":["updated_at","created_at","id","waiting_since"]},"sort_order":{"type":"string","enum":["ascending","descending"]}}}}}},"responses":{"200":{"description":"A page of matching conversations (without conversation parts)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/conversations/{conversation_id}":{"parameters":[{"$ref":"#/components/parameters/ConversationId"}],"get":{"operationId":"GetConversation","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/DisplayAs"}],"responses":{"200":{"description":"The conversation with its parts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conversation"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"operationId":"UpdateConversation","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"$ref":"#/components/parameters/DisplayAs"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"read":{"type":"boolean"},"title":{"type":"string","maxLength":120},"custom_attributes":{"type":"object","maxProperties":4,"additionalProperties":{"type":["string","number","boolean","null"]}}}}}}},"responses":{"200":{"description":"The updated conversation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conversation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/conversations/{conversation_id}/reply":{"parameters":[{"$ref":"#/components/parameters/ConversationId"}],"post":{"operationId":"ReplyConversation","description":"Reply as the contact (\`type: user\` + \`intercom_user_id\`) or as an admin (\`type: admin\` + \`admin_id\`), as JSON (attachments as base64 \`attachment_files\`) or multipart (\`attachment_files[]\` file parts). Admin comments fire \`conversation.admin.replied\`.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplyBody"}},"multipart/form-data":{"schema":{"type":"object","required":["message_type","type"],"properties":{"message_type":{"type":"string"},"type":{"type":"string"},"intercom_user_id":{"type":"string"},"admin_id":{"type":"string"},"body":{"type":"string"},"attachment_files[]":{"type":"array","items":{"type":"string","format":"binary"}}}}}}},"responses":{"200":{"description":"The conversation, with the new part","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conversation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/conversations/{conversation_id}/parts":{"parameters":[{"$ref":"#/components/parameters/ConversationId"}],"post":{"operationId":"ManageConversation","description":"Close, open, snooze or assign; close and open fire \`conversation.admin.closed\` / \`opened\`.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["message_type","type","admin_id"],"properties":{"message_type":{"type":"string","enum":["close","open","snoozed","assignment"]},"type":{"type":"string","enum":["admin"]},"admin_id":{"type":"string","x-mockingbird-resource-ref":{"type":"admin","missing":"9999999"}},"body":{"type":"string","maxLength":400},"snoozed_until":{"type":"integer","minimum":1600000000,"maximum":1900000000},"assignee_id":{"type":"string","x-mockingbird-resource-ref":{"type":"admin","missing":"9999999"}}}}}}},"responses":{"200":{"description":"The conversation, with the new part","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conversation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/admins":{"get":{"operationId":"ListAdmins","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The workspace's admins","content":{"application/json":{"schema":{"type":"object","required":["type","admins"],"properties":{"type":{"type":"string","enum":["admin.list"]},"admins":{"type":"array","items":{"$ref":"#/components/schemas/Admin"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/me":{"get":{"operationId":"GetMe","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The admin that owns the access token","content":{"application/json":{"schema":{"type":"object","required":["type","id","email","app"],"properties":{"type":{"type":"string","enum":["admin"]},"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"},"email_verified":{"type":"boolean"},"has_inbox_seat":{"type":"boolean"},"avatar":{"type":"object"},"app":{"type":"object","required":["type","id_code"],"properties":{"type":{"type":"string"},"id_code":{"type":"string"},"name":{"type":"string"},"created_at":{"type":"integer"},"secure":{"type":"boolean"},"identity_verification":{"type":"boolean"},"timezone":{"type":"string"},"region":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"parameters":{"ConversationId":{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"conversation","missing":"999999999999999"}}},"DisplayAs":{"name":"display_as","in":"query","schema":{"type":"string","enum":["plaintext"]}}},"responses":{"BadRequest":{"description":"Invalid parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorList"}}}},"Unauthorized":{"description":"Missing or invalid access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorList"}}}},"NotFound":{"description":"Unknown resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorList"}}}},"Conflict":{"description":"A contact with that external_id or email already exists (or an idempotency conflict)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorList"}}}}},"schemas":{"ErrorList":{"type":"object","required":["type","errors"],"properties":{"type":{"type":"string","enum":["error.list"]},"request_id":{"type":"string","x-mockingbird-volatile":{"kind":"token"}},"errors":{"type":"array","minItems":1,"items":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}},"Pagination":{"type":"object","properties":{"per_page":{"type":"integer","minimum":1,"maximum":150},"starting_after":{"type":"string","maxLength":80}}},"ContactFilter":{"type":"object","required":["field","operator","value"],"properties":{"field":{"type":"string","enum":["external_id","email","id","name","role","phone"]},"operator":{"type":"string","enum":["=","!=","~","!~","^","$","IN","NIN"]},"value":{"oneOf":[{"type":"string","maxLength":80},{"type":"array","maxItems":3,"items":{"type":"string","maxLength":80}}]}}},"ContactQuery":{"oneOf":[{"$ref":"#/components/schemas/ContactFilter"},{"type":"object","required":["operator","value"],"properties":{"operator":{"type":"string","enum":["AND","OR"]},"value":{"type":"array","minItems":1,"maxItems":3,"items":{"$ref":"#/components/schemas/ContactFilter"}}}}]},"ConversationFilter":{"type":"object","required":["field","operator","value"],"properties":{"field":{"type":"string","enum":["id","contact_ids","admin_assignee_id","team_assignee_id","state","open","read","created_at","updated_at","source.author.email","source.delivered_as"]},"operator":{"type":"string","enum":["=","!=","<",">","~","IN","NIN"]},"value":{"oneOf":[{"type":"string","maxLength":80},{"type":"integer"},{"type":"boolean"},{"type":"array","maxItems":3,"items":{"type":"string","maxLength":80}}]}}},"ConversationQuery":{"oneOf":[{"$ref":"#/components/schemas/ConversationFilter"},{"type":"object","required":["operator","value"],"properties":{"operator":{"type":"string","enum":["AND","OR"]},"value":{"type":"array","minItems":1,"maxItems":3,"items":{"$ref":"#/components/schemas/ConversationFilter"}}}}]},"CustomAttributes":{"type":"object","maxProperties":30,"additionalProperties":{"type":["string","number","boolean","null"]}},"ContactCreate":{"type":"object","properties":{"role":{"type":"string","enum":["user","lead"]},"external_id":{"description":"Any string. The first branch steers the walk generator toward a few values, so random walks reach the duplicate (409) path.","anyOf":[{"type":"string","pattern":"^[1-4]$"},{"type":"string","minLength":1,"maxLength":64}]},"email":{"type":"string","format":"email","maxLength":120},"name":{"type":["string","null"],"maxLength":80},"phone":{"type":["string","null"],"maxLength":40},"signed_up_at":{"type":["integer","null"],"minimum":1000000000,"maximum":1900000000},"last_seen_at":{"type":["integer","null"],"minimum":1000000000,"maximum":1900000000},"custom_attributes":{"$ref":"#/components/schemas/CustomAttributes"}}},"ContactUpdate":{"allOf":[{"$ref":"#/components/schemas/ContactCreate"}]},"ReplyBody":{"type":"object","required":["message_type","type"],"properties":{"message_type":{"type":"string","enum":["comment","note","quick_reply"]},"type":{"type":"string","enum":["user","admin"]},"body":{"type":"string","maxLength":400},"intercom_user_id":{"type":"string","x-mockingbird-resource-ref":{"type":"contact","missing":0}},"user_id":{"type":"string","maxLength":64},"email":{"type":"string","maxLength":120},"admin_id":{"type":"string","x-mockingbird-resource-ref":{"type":"admin","missing":"9999999"}},"created_at":{"type":"integer","minimum":1600000000,"maximum":1900000000},"attachment_urls":{"type":"array","maxItems":3,"items":{"type":"string","maxLength":200}},"attachment_files":{"type":"array","maxItems":3,"items":{"type":"object","required":["content_type","data","name"],"properties":{"content_type":{"type":"string","maxLength":80},"data":{"type":"string","maxLength":400},"name":{"type":"string","minLength":1,"maxLength":80}}}}}},"Contact":{"type":"object","required":["type","id","role","created_at","updated_at","custom_attributes"],"properties":{"type":{"type":"string","enum":["contact"]},"id":{"type":"string","x-mockingbird-resource":{"type":"contact","identity":true}},"workspace_id":{"type":"string"},"external_id":{"type":["string","null"]},"role":{"type":"string"},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"name":{"type":["string","null"]},"avatar":{"type":["string","null"]},"owner_id":{"type":["integer","null"]},"has_hard_bounced":{"type":"boolean"},"marked_email_as_spam":{"type":"boolean"},"unsubscribed_from_emails":{"type":"boolean"},"created_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"signed_up_at":{"type":["integer","null"]},"last_seen_at":{"type":["integer","null"]},"custom_attributes":{"type":"object"},"tags":{"type":"object"},"notes":{"type":"object"},"companies":{"type":"object"},"location":{"type":"object"},"social_profiles":{"type":"object"}}},"Pages":{"type":"object","required":["type","page","per_page","total_pages"],"properties":{"type":{"type":"string","enum":["pages"]},"page":{"type":"integer"},"per_page":{"type":"integer"},"total_pages":{"type":"integer"},"next":{"type":"object","required":["page","starting_after"],"properties":{"page":{"type":"integer"},"starting_after":{"type":"string"}}}}},"ContactList":{"type":"object","required":["type","data","total_count","pages"],"properties":{"type":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}},"total_count":{"type":"integer"},"pages":{"$ref":"#/components/schemas/Pages"}}},"Author":{"type":"object","required":["type","id"],"properties":{"type":{"type":"string"},"id":{"type":"string"},"name":{"type":["string","null"]},"email":{"type":["string","null"]}}},"Attachment":{"type":"object","required":["type","name","url","content_type"],"properties":{"type":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"},"content_type":{"type":"string"},"filesize":{"type":"integer"},"width":{"type":["integer","null"]},"height":{"type":["integer","null"]}}},"ConversationPart":{"type":"object","required":["type","id","part_type","created_at","author"],"properties":{"type":{"type":"string","enum":["conversation_part"]},"id":{"type":"string"},"part_type":{"type":"string"},"body":{"type":["string","null"]},"created_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"notified_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"assigned_to":{"type":["object","null"]},"author":{"$ref":"#/components/schemas/Author"},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/Attachment"}},"external_id":{"type":["string","null"]},"redacted":{"type":"boolean"}}},"Conversation":{"type":"object","required":["type","id","created_at","updated_at","open","state","read","source","contacts"],"properties":{"type":{"type":"string","enum":["conversation"]},"id":{"type":"string","x-mockingbird-resource":{"type":"conversation","identity":true}},"title":{"type":["string","null"]},"created_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"waiting_since":{"type":["integer","null"],"x-mockingbird-volatile":{"kind":"timestamp"}},"snoozed_until":{"type":["integer","null"]},"open":{"type":"boolean"},"state":{"type":"string","enum":["open","closed","snoozed"]},"read":{"type":"boolean"},"priority":{"type":"string"},"admin_assignee_id":{"type":["integer","null"]},"team_assignee_id":{"type":["string","null"]},"tags":{"type":"object"},"conversation_rating":{"type":["object","null"]},"source":{"type":"object","required":["type","id","delivered_as","body","author"],"properties":{"type":{"type":"string"},"id":{"type":"string"},"delivered_as":{"type":"string"},"subject":{"type":"string"},"body":{"type":["string","null"]},"author":{"$ref":"#/components/schemas/Author"},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/Attachment"}},"url":{"type":["string","null"]},"redacted":{"type":"boolean"}}},"contacts":{"type":"object","required":["type","contacts"],"properties":{"type":{"type":"string"},"contacts":{"type":"array","items":{"type":"object","required":["type","id"],"properties":{"type":{"type":"string"},"id":{"type":"string"},"external_id":{"type":["string","null"]}}}}}},"teammates":{"type":"object"},"custom_attributes":{"type":"object"},"first_contact_reply":{"type":["object","null"]},"sla_applied":{"type":["object","null"]},"statistics":{"type":["object","null"]},"ai_agent_participated":{"type":"boolean"},"conversation_parts":{"type":"object","required":["type","conversation_parts","total_count"],"properties":{"type":{"type":"string"},"conversation_parts":{"type":"array","items":{"$ref":"#/components/schemas/ConversationPart"}},"total_count":{"type":"integer"}}}}},"ConversationList":{"type":"object","required":["type","pages","total_count","conversations"],"properties":{"type":{"type":"string","enum":["conversation.list"]},"pages":{"$ref":"#/components/schemas/Pages"},"total_count":{"type":"integer"},"conversations":{"type":"array","items":{"$ref":"#/components/schemas/Conversation"}}}},"Message":{"type":"object","required":["type","id","created_at","body","message_type","conversation_id"],"properties":{"type":{"type":"string","enum":["user_message"]},"id":{"type":"string"},"created_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"body":{"type":"string"},"message_type":{"type":"string","enum":["inapp"]},"conversation_id":{"type":"string","x-mockingbird-resource":{"type":"conversation","identity":true}}}},"Admin":{"type":"object","required":["type","id","name","email"],"properties":{"type":{"type":"string","enum":["admin"]},"id":{"type":"string","x-mockingbird-resource":{"type":"admin","identity":true}},"name":{"type":"string"},"email":{"type":"string"},"job_title":{"type":["string","null"]},"away_mode_enabled":{"type":"boolean"},"away_mode_reassign":{"type":"boolean"},"has_inbox_seat":{"type":"boolean"},"team_ids":{"type":"array","items":{"type":"integer"}}}}}}}`);
4879
+ var document2 = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Intercom REST API 2.11 (Mockingbird subset)","description":"Stateful mock subset of the Intercom REST API (version 2.11): contacts (search, create,\\nupdate, get), conversations (create, update, reply as user or admin with attachments,\\nclose/open/snooze/assign, get, search) and the admin identity endpoints. Covers the backend's\\nmember messaging adapter and the Intercom sync adapter.\\n","version":"2.11","x-mockingbird-upstream":{"note":"Trimmed from Intercom's published 2.11 API reference to the operations and fields our consumers use (intercom-messaging.adapter.ts, intercom-api.adapter.ts) and the webhook receivers (messaging.controller.ts, the EMR IntercomWebhookService)."}},"servers":[{"url":"https://api.intercom.io"}],"security":[{"bearerAuth":[]}],"paths":{"/contacts/search":{"post":{"operationId":"SearchContacts","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["query"],"properties":{"query":{"$ref":"#/components/schemas/ContactQuery"},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"responses":{"200":{"description":"Matching contacts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/contacts":{"post":{"operationId":"CreateContact","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactCreate"}}}},"responses":{"200":{"description":"The created contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/contacts/{contact_id}":{"parameters":[{"name":"contact_id","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"contact","missing":0}}}],"get":{"operationId":"GetContact","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"operationId":"UpdateContact","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactUpdate"}}}},"responses":{"200":{"description":"The updated contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/conversations":{"post":{"operationId":"CreateConversation","description":"A contact-initiated conversation. An optional \`Idempotency-Key\` header replays.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"name":"Idempotency-Key","in":"header","schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,40}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["from","body"],"properties":{"from":{"type":"object","required":["type","id"],"properties":{"type":{"type":"string","enum":["user","lead","contact"]},"id":{"type":"string","x-mockingbird-resource-ref":{"type":"contact","missing":0}}}},"body":{"type":"string","minLength":1,"maxLength":400},"created_at":{"type":"integer","minimum":1600000000,"maximum":1900000000}}}}}},"responses":{"200":{"description":"The first message of the new conversation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Message"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/conversations/search":{"post":{"operationId":"SearchConversations","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/DisplayAs"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["query"],"properties":{"query":{"$ref":"#/components/schemas/ConversationQuery"},"pagination":{"$ref":"#/components/schemas/Pagination"},"sort_field":{"type":"string","enum":["updated_at","created_at","id","waiting_since"]},"sort_order":{"type":"string","enum":["ascending","descending"]}}}}}},"responses":{"200":{"description":"A page of matching conversations (without conversation parts)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConversationList"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/conversations/{conversation_id}":{"parameters":[{"$ref":"#/components/parameters/ConversationId"}],"get":{"operationId":"GetConversation","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/DisplayAs"}],"responses":{"200":{"description":"The conversation with its parts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conversation"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"operationId":"UpdateConversation","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"$ref":"#/components/parameters/DisplayAs"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"read":{"type":"boolean"},"title":{"type":"string","maxLength":120},"custom_attributes":{"type":"object","maxProperties":4,"additionalProperties":{"type":["string","number","boolean","null"]}}}}}}},"responses":{"200":{"description":"The updated conversation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conversation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/conversations/{conversation_id}/reply":{"parameters":[{"$ref":"#/components/parameters/ConversationId"}],"post":{"operationId":"ReplyConversation","description":"Reply as the contact (\`type: user\` + \`intercom_user_id\`) or as an admin (\`type: admin\` + \`admin_id\`), as JSON (attachments as base64 \`attachment_files\`) or multipart (\`attachment_files[]\` file parts). Admin comments fire \`conversation.admin.replied\`.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReplyBody"}},"multipart/form-data":{"schema":{"type":"object","required":["message_type","type"],"properties":{"message_type":{"type":"string"},"type":{"type":"string"},"intercom_user_id":{"type":"string"},"admin_id":{"type":"string"},"body":{"type":"string"},"attachment_files[]":{"type":"array","items":{"type":"string","format":"binary"}}}}}}},"responses":{"200":{"description":"The conversation, with the new part","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conversation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/conversations/{conversation_id}/parts":{"parameters":[{"$ref":"#/components/parameters/ConversationId"}],"post":{"operationId":"ManageConversation","description":"Close, open, snooze or assign; close and open fire \`conversation.admin.closed\` / \`opened\`.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["message_type","type","admin_id"],"properties":{"message_type":{"type":"string","enum":["close","open","snoozed","assignment"]},"type":{"type":"string","enum":["admin"]},"admin_id":{"type":"string","x-mockingbird-resource-ref":{"type":"admin","missing":"9999999"}},"body":{"type":"string","maxLength":400},"snoozed_until":{"type":"integer","minimum":1600000000,"maximum":1900000000},"assignee_id":{"type":"string","x-mockingbird-resource-ref":{"type":"admin","missing":"9999999"}}}}}}},"responses":{"200":{"description":"The conversation, with the new part","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Conversation"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/admins":{"get":{"operationId":"ListAdmins","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The workspace's admins","content":{"application/json":{"schema":{"type":"object","required":["type","admins"],"properties":{"type":{"type":"string","enum":["admin.list"]},"admins":{"type":"array","items":{"$ref":"#/components/schemas/Admin"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/me":{"get":{"operationId":"GetMe","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The admin that owns the access token","content":{"application/json":{"schema":{"type":"object","required":["type","id","email","app"],"properties":{"type":{"type":"string","enum":["admin"]},"id":{"type":"string"},"name":{"type":"string"},"email":{"type":"string"},"email_verified":{"type":"boolean"},"has_inbox_seat":{"type":"boolean"},"avatar":{"type":"object"},"app":{"type":"object","required":["type","id_code"],"properties":{"type":{"type":"string"},"id_code":{"type":"string"},"name":{"type":"string"},"created_at":{"type":"integer"},"secure":{"type":"boolean"},"identity_verification":{"type":"boolean"},"timezone":{"type":"string"},"region":{"type":"string"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"parameters":{"ConversationId":{"name":"conversation_id","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"conversation","missing":"999999999999999"}}},"DisplayAs":{"name":"display_as","in":"query","schema":{"type":"string","enum":["plaintext"]}}},"responses":{"BadRequest":{"description":"Invalid parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorList"}}}},"Unauthorized":{"description":"Missing or invalid access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorList"}}}},"NotFound":{"description":"Unknown resource","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorList"}}}},"Conflict":{"description":"A contact with that external_id or email already exists (or an idempotency conflict)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorList"}}}}},"schemas":{"ErrorList":{"type":"object","required":["type","errors"],"properties":{"type":{"type":"string","enum":["error.list"]},"request_id":{"type":"string","x-mockingbird-volatile":{"kind":"token"}},"errors":{"type":"array","minItems":1,"items":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}},"Pagination":{"type":"object","properties":{"per_page":{"type":"integer","minimum":1,"maximum":150},"starting_after":{"type":"string","maxLength":80}}},"ContactFilter":{"type":"object","required":["field","operator","value"],"properties":{"field":{"type":"string","enum":["external_id","email","id","name","role","phone"]},"operator":{"type":"string","enum":["=","!=","~","!~","^","$","IN","NIN"]},"value":{"oneOf":[{"type":"string","maxLength":80},{"type":"array","maxItems":3,"items":{"type":"string","maxLength":80}}]}}},"ContactQuery":{"oneOf":[{"$ref":"#/components/schemas/ContactFilter"},{"type":"object","required":["operator","value"],"properties":{"operator":{"type":"string","enum":["AND","OR"]},"value":{"type":"array","minItems":1,"maxItems":3,"items":{"$ref":"#/components/schemas/ContactFilter"}}}}]},"ConversationFilter":{"type":"object","required":["field","operator","value"],"properties":{"field":{"type":"string","enum":["id","contact_ids","admin_assignee_id","team_assignee_id","state","open","read","created_at","updated_at","source.author.email","source.delivered_as"]},"operator":{"type":"string","enum":["=","!=","<",">","~","IN","NIN"]},"value":{"oneOf":[{"type":"string","maxLength":80},{"type":"integer"},{"type":"boolean"},{"type":"array","maxItems":3,"items":{"type":"string","maxLength":80}}]}}},"ConversationQuery":{"oneOf":[{"$ref":"#/components/schemas/ConversationFilter"},{"type":"object","required":["operator","value"],"properties":{"operator":{"type":"string","enum":["AND","OR"]},"value":{"type":"array","minItems":1,"maxItems":3,"items":{"$ref":"#/components/schemas/ConversationFilter"}}}}]},"CustomAttributes":{"type":"object","maxProperties":30,"additionalProperties":{"type":["string","number","boolean","null"]}},"ContactCreate":{"type":"object","properties":{"role":{"type":"string","enum":["user","lead"]},"external_id":{"description":"Any string. The first branch steers the walk generator toward a few values, so random walks reach the duplicate (409) path.","anyOf":[{"type":"string","pattern":"^[1-4]$"},{"type":"string","minLength":1,"maxLength":64}]},"email":{"type":"string","format":"email","maxLength":120},"name":{"type":["string","null"],"maxLength":80},"phone":{"type":["string","null"],"maxLength":40},"signed_up_at":{"type":["integer","null"],"minimum":1000000000,"maximum":1900000000},"last_seen_at":{"type":["integer","null"],"minimum":1000000000,"maximum":1900000000},"custom_attributes":{"$ref":"#/components/schemas/CustomAttributes"}}},"ContactUpdate":{"allOf":[{"$ref":"#/components/schemas/ContactCreate"}]},"ReplyBody":{"type":"object","required":["message_type","type"],"properties":{"message_type":{"type":"string","enum":["comment","note","quick_reply"]},"type":{"type":"string","enum":["user","admin"]},"body":{"type":"string","maxLength":400},"intercom_user_id":{"type":"string","x-mockingbird-resource-ref":{"type":"contact","missing":0}},"user_id":{"type":"string","maxLength":64},"email":{"type":"string","maxLength":120},"admin_id":{"type":"string","x-mockingbird-resource-ref":{"type":"admin","missing":"9999999"}},"created_at":{"type":"integer","minimum":1600000000,"maximum":1900000000},"attachment_urls":{"type":"array","maxItems":3,"items":{"type":"string","maxLength":200}},"attachment_files":{"type":"array","maxItems":3,"items":{"type":"object","required":["content_type","data","name"],"properties":{"content_type":{"type":"string","maxLength":80},"data":{"type":"string","maxLength":400},"name":{"type":"string","minLength":1,"maxLength":80}}}}}},"Contact":{"type":"object","required":["type","id","role","created_at","updated_at","custom_attributes"],"properties":{"type":{"type":"string","enum":["contact"]},"id":{"type":"string","x-mockingbird-resource":{"type":"contact","identity":true}},"workspace_id":{"type":"string"},"external_id":{"type":["string","null"]},"role":{"type":"string"},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"name":{"type":["string","null"]},"avatar":{"type":["string","null"]},"owner_id":{"type":["integer","null"]},"has_hard_bounced":{"type":"boolean"},"marked_email_as_spam":{"type":"boolean"},"unsubscribed_from_emails":{"type":"boolean"},"created_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"signed_up_at":{"type":["integer","null"]},"last_seen_at":{"type":["integer","null"]},"custom_attributes":{"type":"object"},"tags":{"type":"object"},"notes":{"type":"object"},"companies":{"type":"object"},"location":{"type":"object"},"social_profiles":{"type":"object"}}},"Pages":{"type":"object","required":["type","page","per_page","total_pages"],"properties":{"type":{"type":"string","enum":["pages"]},"page":{"type":"integer"},"per_page":{"type":"integer"},"total_pages":{"type":"integer"},"next":{"type":"object","required":["page","starting_after"],"properties":{"page":{"type":"integer"},"starting_after":{"type":"string"}}}}},"ContactList":{"type":"object","required":["type","data","total_count","pages"],"properties":{"type":{"type":"string","enum":["list"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}},"total_count":{"type":"integer"},"pages":{"$ref":"#/components/schemas/Pages"}}},"Author":{"type":"object","required":["type","id"],"properties":{"type":{"type":"string"},"id":{"type":"string"},"name":{"type":["string","null"]},"email":{"type":["string","null"]}}},"Attachment":{"type":"object","required":["type","name","url","content_type"],"properties":{"type":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"},"content_type":{"type":"string"},"filesize":{"type":"integer"},"width":{"type":["integer","null"]},"height":{"type":["integer","null"]}}},"ConversationPart":{"type":"object","required":["type","id","part_type","created_at","author"],"properties":{"type":{"type":"string","enum":["conversation_part"]},"id":{"type":"string"},"part_type":{"type":"string"},"body":{"type":["string","null"]},"created_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"notified_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"assigned_to":{"type":["object","null"]},"author":{"$ref":"#/components/schemas/Author"},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/Attachment"}},"external_id":{"type":["string","null"]},"redacted":{"type":"boolean"}}},"Conversation":{"type":"object","required":["type","id","created_at","updated_at","open","state","read","source","contacts"],"properties":{"type":{"type":"string","enum":["conversation"]},"id":{"type":"string","x-mockingbird-resource":{"type":"conversation","identity":true}},"title":{"type":["string","null"]},"created_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"waiting_since":{"type":["integer","null"],"x-mockingbird-volatile":{"kind":"timestamp"}},"snoozed_until":{"type":["integer","null"]},"open":{"type":"boolean"},"state":{"type":"string","enum":["open","closed","snoozed"]},"read":{"type":"boolean"},"priority":{"type":"string"},"admin_assignee_id":{"type":["integer","null"]},"team_assignee_id":{"type":["string","null"]},"tags":{"type":"object"},"conversation_rating":{"type":["object","null"]},"source":{"type":"object","required":["type","id","delivered_as","body","author"],"properties":{"type":{"type":"string"},"id":{"type":"string"},"delivered_as":{"type":"string"},"subject":{"type":"string"},"body":{"type":["string","null"]},"author":{"$ref":"#/components/schemas/Author"},"attachments":{"type":"array","items":{"$ref":"#/components/schemas/Attachment"}},"url":{"type":["string","null"]},"redacted":{"type":"boolean"}}},"contacts":{"type":"object","required":["type","contacts"],"properties":{"type":{"type":"string"},"contacts":{"type":"array","items":{"type":"object","required":["type","id"],"properties":{"type":{"type":"string"},"id":{"type":"string"},"external_id":{"type":["string","null"]}}}}}},"teammates":{"type":"object"},"custom_attributes":{"type":"object"},"first_contact_reply":{"type":["object","null"]},"sla_applied":{"type":["object","null"]},"statistics":{"type":["object","null"]},"ai_agent_participated":{"type":"boolean"},"conversation_parts":{"type":"object","required":["type","conversation_parts","total_count"],"properties":{"type":{"type":"string"},"conversation_parts":{"type":"array","items":{"$ref":"#/components/schemas/ConversationPart"}},"total_count":{"type":"integer"}}}}},"ConversationList":{"type":"object","required":["type","pages","total_count","conversations"],"properties":{"type":{"type":"string","enum":["conversation.list"]},"pages":{"$ref":"#/components/schemas/Pages"},"total_count":{"type":"integer"},"conversations":{"type":"array","items":{"$ref":"#/components/schemas/Conversation"}}}},"Message":{"type":"object","required":["type","id","created_at","body","message_type","conversation_id"],"properties":{"type":{"type":"string","enum":["user_message"]},"id":{"type":"string"},"created_at":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"body":{"type":"string"},"message_type":{"type":"string","enum":["inapp"]},"conversation_id":{"type":"string","x-mockingbird-resource":{"type":"conversation","identity":true}}}},"Admin":{"type":"object","required":["type","id","name","email"],"properties":{"type":{"type":"string","enum":["admin"]},"id":{"type":"string","x-mockingbird-resource":{"type":"admin","identity":true}},"name":{"type":"string"},"email":{"type":"string"},"job_title":{"type":["string","null"]},"away_mode_enabled":{"type":"boolean"},"away_mode_reassign":{"type":"boolean"},"has_inbox_seat":{"type":"boolean"},"team_ids":{"type":"array","items":{"type":"integer"}}}}}}}`);
4076
4880
  var operationIds = ["SearchContacts", "CreateContact", "GetContact", "UpdateContact", "CreateConversation", "SearchConversations", "GetConversation", "UpdateConversation", "ReplyConversation", "ManageConversation", "ListAdmins", "GetMe"];
4077
4881
  var supportedOperationIds = ["SearchContacts", "CreateContact", "GetContact", "UpdateContact", "CreateConversation", "SearchConversations", "GetConversation", "UpdateConversation", "ReplyConversation", "ManageConversation", "ListAdmins", "GetMe"];
4078
4882
 
@@ -4080,9 +4884,9 @@ var supportedOperationIds = ["SearchContacts", "CreateContact", "GetContact", "U
4080
4884
  var QueryError = class extends Error {
4081
4885
  };
4082
4886
  var FILTER_OPERATORS = ["=", "!=", "IN", "NIN", "<", ">", "~", "!~", "^", "$"];
4083
- var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4887
+ var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4084
4888
  var parseQuery = (value, fields, depth = 0) => {
4085
- if (!isRecord5(value)) throw new QueryError("query must be an object");
4889
+ if (!isRecord6(value)) throw new QueryError("query must be an object");
4086
4890
  const operator = value.operator;
4087
4891
  if (operator === "AND" || operator === "OR") {
4088
4892
  if (depth >= 2) throw new QueryError("queries can be nested at most two levels deep");
@@ -4157,7 +4961,7 @@ var decodeCursor = (cursor) => {
4157
4961
  throw new QueryError("starting_after is not a valid cursor");
4158
4962
  };
4159
4963
  var paginate = (items, pagination, defaultPerPage) => {
4160
- const options = isRecord5(pagination) ? pagination : {};
4964
+ const options = isRecord6(pagination) ? pagination : {};
4161
4965
  const perPage = options.per_page === void 0 ? defaultPerPage : Number(options.per_page);
4162
4966
  if (!Number.isInteger(perPage) || perPage < 1 || perPage > 150) {
4163
4967
  throw new QueryError("per_page must be between 1 and 150");
@@ -4355,7 +5159,7 @@ var INTERCOM_PRESETS = {
4355
5159
  };
4356
5160
  var json6 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
4357
5161
  var adminError4 = (status, message) => json6(status, { error: { type: "mockingbird_admin", message } });
4358
- var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5162
+ var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4359
5163
  var guard = (run) => {
4360
5164
  try {
4361
5165
  return run();
@@ -4373,7 +5177,7 @@ var adminRoutes = (runtime) => {
4373
5177
  "GET /contacts": ({ namespace }) => json6(200, { contacts: api(namespace).contacts() }),
4374
5178
  "GET /conversations": ({ namespace }) => json6(200, { conversations: api(namespace).conversations() }),
4375
5179
  "POST /conversations": ({ body, namespace }) => guard(() => {
4376
- if (!isRecord6(body) || typeof body.body !== "string") {
5180
+ if (!isRecord7(body) || typeof body.body !== "string") {
4377
5181
  return adminError4(400, 'expected {"contactId" | "externalId", "adminId"?, "body"}');
4378
5182
  }
4379
5183
  const state = api(namespace).state;
@@ -4389,7 +5193,7 @@ var adminRoutes = (runtime) => {
4389
5193
  "POST /conversations/:id/admin-reply": ({ params, body, namespace }) => guard(() => {
4390
5194
  const found = conversation(namespace, params.id);
4391
5195
  if (!found) return adminError4(404, `no conversation ${params.id}`);
4392
- if (!isRecord6(body) || typeof body.body !== "string") {
5196
+ if (!isRecord7(body) || typeof body.body !== "string") {
4393
5197
  return adminError4(
4394
5198
  400,
4395
5199
  'expected {"adminId" | "admin_id"?, "body", "messageType"?: "comment" | "note"}'
@@ -4411,18 +5215,18 @@ var adminRoutes = (runtime) => {
4411
5215
  "POST /conversations/:id/close": ({ params, body, namespace }) => guard(() => {
4412
5216
  const found = conversation(namespace, params.id);
4413
5217
  if (!found) return adminError4(404, `no conversation ${params.id}`);
4414
- const adminId = isRecord6(body) && typeof body.adminId === "string" ? body.adminId : defaultAdmin(namespace);
5218
+ const adminId = isRecord7(body) && typeof body.adminId === "string" ? body.adminId : defaultAdmin(namespace);
4415
5219
  return json6(200, api(namespace).manage(found, { action: "close", adminId }));
4416
5220
  }),
4417
5221
  "POST /conversations/:id/open": ({ params, body, namespace }) => guard(() => {
4418
5222
  const found = conversation(namespace, params.id);
4419
5223
  if (!found) return adminError4(404, `no conversation ${params.id}`);
4420
- const adminId = isRecord6(body) && typeof body.adminId === "string" ? body.adminId : defaultAdmin(namespace);
5224
+ const adminId = isRecord7(body) && typeof body.adminId === "string" ? body.adminId : defaultAdmin(namespace);
4421
5225
  return json6(200, api(namespace).manage(found, { action: "open", adminId }));
4422
5226
  }),
4423
5227
  "PUT /admins": ({ body, namespace }) => {
4424
- const list = Array.isArray(body) ? body : isRecord6(body) ? body.admins : void 0;
4425
- if (!Array.isArray(list) || !list.every((a) => isRecord6(a) && typeof a.id === "string")) {
5228
+ const list = Array.isArray(body) ? body : isRecord7(body) ? body.admins : void 0;
5229
+ if (!Array.isArray(list) || !list.every((a) => isRecord7(a) && typeof a.id === "string")) {
4426
5230
  return adminError4(400, "expected [{id, name, email}, \u2026]");
4427
5231
  }
4428
5232
  const state = api(namespace).state;
@@ -4444,7 +5248,7 @@ var adminRoutes = (runtime) => {
4444
5248
  },
4445
5249
  "GET /settings": ({ namespace }) => json6(200, api(namespace).state.current()),
4446
5250
  "PUT /settings": ({ body, namespace }) => {
4447
- if (!isRecord6(body)) return adminError4(400, "expected a JSON object");
5251
+ if (!isRecord7(body)) return adminError4(400, "expected a JSON object");
4448
5252
  const patch = {};
4449
5253
  if (body.tokens !== void 0) {
4450
5254
  if (!Array.isArray(body.tokens)) return adminError4(400, "tokens: string[]");
@@ -4481,7 +5285,7 @@ var createRuntime2 = (options = {}) => {
4481
5285
  });
4482
5286
  const runtime = createRuntime({
4483
5287
  name: INTERCOM_NAMESPACE,
4484
- document,
5288
+ document: document2,
4485
5289
  ...options.sqlite ? { sqlite: options.sqlite } : {},
4486
5290
  ...options.clock ? { clock: options.clock } : {},
4487
5291
  ...options.seed !== void 0 ? { seed: options.seed } : {},
@@ -4559,7 +5363,7 @@ var CONVERSATION_FIELDS = [
4559
5363
  "source.author.name",
4560
5364
  "source.author.email"
4561
5365
  ];
4562
- var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5366
+ var isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4563
5367
  var str = (value) => typeof value === "string" && value.length > 0 ? value : void 0;
4564
5368
  var IntercomError = class extends Error {
4565
5369
  constructor(status, code, message) {
@@ -4640,7 +5444,7 @@ var IntercomAPI = class {
4640
5444
  })
4641
5445
  });
4642
5446
  this.service = createService({
4643
- document,
5447
+ document: document2,
4644
5448
  handlers,
4645
5449
  sqlite,
4646
5450
  namespace,
@@ -4704,7 +5508,7 @@ var IntercomAPI = class {
4704
5508
  `${[first.path, missing[1]].filter(Boolean).join(".")} is required`
4705
5509
  ) : new IntercomError(400, "parameter_invalid", `${first.path || "body"} ${first.message}`);
4706
5510
  }
4707
- return context.body.kind === "json" && isRecord7(context.body.value) ? context.body.value : {};
5511
+ return context.body.kind === "json" && isRecord8(context.body.value) ? context.body.value : {};
4708
5512
  }
4709
5513
  // ─── Contacts ───────────────────────────────────────────────────────────────────────────
4710
5514
  contactBody(contact) {
@@ -4765,7 +5569,7 @@ var IntercomAPI = class {
4765
5569
  checkCustomAttributes(attributes) {
4766
5570
  if (attributes === void 0) return {};
4767
5571
  const defined = this.state.current().customAttributes;
4768
- const given = isRecord7(attributes) ? attributes : {};
5572
+ const given = isRecord8(attributes) ? attributes : {};
4769
5573
  if (defined !== null) {
4770
5574
  const unknown = Object.keys(given).find((key) => !defined.includes(key));
4771
5575
  if (unknown !== void 0) {
@@ -4999,7 +5803,7 @@ var IntercomAPI = class {
4999
5803
  ...typeof body.title === "string" ? { title: body.title } : {},
5000
5804
  custom_attributes: {
5001
5805
  ...conversation.custom_attributes,
5002
- ...isRecord7(body.custom_attributes) ? body.custom_attributes : {}
5806
+ ...isRecord8(body.custom_attributes) ? body.custom_attributes : {}
5003
5807
  },
5004
5808
  updated_at: this.seconds()
5005
5809
  };
@@ -5319,7 +6123,7 @@ var IntercomAPI = class {
5319
6123
  if (field.startsWith("source.")) {
5320
6124
  let value = conversation.source;
5321
6125
  for (const key of field.slice("source.".length).split(".")) {
5322
- value = isRecord7(value) ? value[key] : void 0;
6126
+ value = isRecord8(value) ? value[key] : void 0;
5323
6127
  }
5324
6128
  return value;
5325
6129
  }
@@ -5403,7 +6207,7 @@ var IntercomAPI = class {
5403
6207
  };
5404
6208
 
5405
6209
  export {
5406
- document,
6210
+ document2 as document,
5407
6211
  operationIds,
5408
6212
  supportedOperationIds,
5409
6213
  encodeCursor,
@@ -5421,4 +6225,4 @@ export {
5421
6225
  IntercomError,
5422
6226
  IntercomAPI
5423
6227
  };
5424
- //# sourceMappingURL=chunk-T56VJH23.js.map
6228
+ //# sourceMappingURL=chunk-O25E63FD.js.map