@crvouga/mockingbird-service-fullscript 1.4.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -540,10 +540,10 @@ var OpenAPIReferenceError = class extends Error {
540
540
  };
541
541
  var unescapePointer = (segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~");
542
542
  var isReference = (value) => typeof value === "object" && value !== null && typeof value.$ref === "string";
543
- var resolveRef = (document2, ref) => {
543
+ var resolveRef = (document3, ref) => {
544
544
  if (!ref.startsWith("#/"))
545
545
  throw new OpenAPIReferenceError(ref);
546
- let cursor = document2;
546
+ let cursor = document3;
547
547
  for (const raw of ref.slice(2).split("/")) {
548
548
  const segment = unescapePointer(raw);
549
549
  if (typeof cursor !== "object" || cursor === null || !(segment in cursor)) {
@@ -555,14 +555,14 @@ var resolveRef = (document2, ref) => {
555
555
  throw new OpenAPIReferenceError(ref);
556
556
  return cursor;
557
557
  };
558
- var deref = (document2, value) => {
558
+ var deref = (document3, value) => {
559
559
  let current = value;
560
560
  const seen = /* @__PURE__ */ new Set();
561
561
  while (isReference(current)) {
562
562
  if (seen.has(current.$ref))
563
563
  throw new OpenAPIReferenceError(`${current.$ref} (cycle)`);
564
564
  seen.add(current.$ref);
565
- current = resolveRef(document2, current.$ref);
565
+ current = resolveRef(document3, current.$ref);
566
566
  }
567
567
  return current;
568
568
  };
@@ -580,36 +580,36 @@ var HTTP_METHODS = [
580
580
  ];
581
581
 
582
582
  // ../../openapi/core/dist/document.js
583
- var mergeParameters = (document2, item, own) => {
583
+ var mergeParameters = (document3, item, own) => {
584
584
  const merged = /* @__PURE__ */ new Map();
585
585
  for (const raw of item.parameters ?? []) {
586
- const parameter = deref(document2, raw);
586
+ const parameter = deref(document3, raw);
587
587
  merged.set(`${parameter.in}:${parameter.name}`, parameter);
588
588
  }
589
589
  for (const raw of own ?? []) {
590
- const parameter = deref(document2, raw);
590
+ const parameter = deref(document3, raw);
591
591
  merged.set(`${parameter.in}:${parameter.name}`, parameter);
592
592
  }
593
593
  return [...merged.values()];
594
594
  };
595
- var listOperations = (document2) => {
595
+ var listOperations = (document3) => {
596
596
  const operations = [];
597
- for (const [path, item] of Object.entries(document2.paths)) {
597
+ for (const [path, item] of Object.entries(document3.paths)) {
598
598
  for (const method of HTTP_METHODS) {
599
599
  const operation = item[method];
600
600
  if (operation?.operationId === void 0)
601
601
  continue;
602
602
  const responses = {};
603
603
  for (const [status, response] of Object.entries(operation.responses)) {
604
- responses[status] = deref(document2, response);
604
+ responses[status] = deref(document3, response);
605
605
  }
606
606
  operations.push({
607
607
  operationId: operation.operationId,
608
608
  method,
609
609
  path,
610
610
  operation,
611
- parameters: mergeParameters(document2, item, operation.parameters),
612
- requestBody: operation.requestBody === void 0 ? void 0 : deref(document2, operation.requestBody),
611
+ parameters: mergeParameters(document3, item, operation.parameters),
612
+ requestBody: operation.requestBody === void 0 ? void 0 : deref(document3, operation.requestBody),
613
613
  responses
614
614
  });
615
615
  }
@@ -618,7 +618,7 @@ var listOperations = (document2) => {
618
618
  };
619
619
 
620
620
  // ../../openapi/core/dist/schema.js
621
- var resolveSchema = (document2, schema) => {
621
+ var resolveSchema = (document3, schema) => {
622
622
  let current = schema;
623
623
  const seen = /* @__PURE__ */ new Set();
624
624
  while (typeof current.$ref === "string") {
@@ -627,7 +627,7 @@ var resolveSchema = (document2, schema) => {
627
627
  break;
628
628
  seen.add(ref);
629
629
  const { $ref: _ignored, ...siblings } = current;
630
- const target = resolveRef(document2, ref);
630
+ const target = resolveRef(document3, ref);
631
631
  current = { ...target, ...siblings };
632
632
  }
633
633
  if (current.nullable === true) {
@@ -698,9 +698,9 @@ var FORMAT_PATTERNS = {
698
698
  ipv4: /^(25[0-5]|2[0-4]\d|1?\d?\d)(\.(25[0-5]|2[0-4]\d|1?\d?\d)){3}$/
699
699
  };
700
700
  var graphemeLength = (value) => [...value].length;
701
- var validateValue = (document2, schema, value, path = []) => {
701
+ var validateValue = (document3, schema, value, path = []) => {
702
702
  const errors = [];
703
- const s = resolveSchema(document2, schema);
703
+ const s = resolveSchema(document3, schema);
704
704
  const fail3 = (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
  fail3(`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
  fail3(`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
  fail3("matches no anyOf branch");
801
801
  if (s.oneOf) {
802
- const matches2 = s.oneOf.filter((branch) => validateValue(document2, branch, value).length === 0).length;
802
+ const matches2 = s.oneOf.filter((branch) => validateValue(document3, branch, value).length === 0).length;
803
803
  if (matches2 !== 1)
804
804
  fail3(`matches ${matches2} oneOf branches, expected exactly 1`);
805
805
  }
806
- if (s.not && validateValue(document2, s.not, value).length === 0)
806
+ if (s.not && validateValue(document3, s.not, value).length === 0)
807
807
  fail3("matches forbidden `not` schema");
808
808
  return errors;
809
809
  };
@@ -1358,6 +1358,1232 @@ var clearNamespace = (sqlite, namespace) => {
1358
1358
  });
1359
1359
  };
1360
1360
 
1361
+ // ../../ui/dist/reset.js
1362
+ var CSS_RESET = `*,
1363
+ *::before,
1364
+ *::after {
1365
+ box-sizing: border-box;
1366
+ margin: 0;
1367
+ padding: 0;
1368
+ }
1369
+
1370
+ * {
1371
+ scrollbar-width: thin;
1372
+ scrollbar-color: color-mix(in srgb, CanvasText 35%, Canvas) Canvas;
1373
+ }
1374
+
1375
+ *::-webkit-scrollbar {
1376
+ width: 12px;
1377
+ height: 12px;
1378
+ }
1379
+
1380
+ *::-webkit-scrollbar-track,
1381
+ *::-webkit-scrollbar-corner {
1382
+ background: Canvas;
1383
+ }
1384
+
1385
+ *::-webkit-scrollbar-thumb {
1386
+ background: color-mix(in srgb, CanvasText 35%, Canvas);
1387
+ border: 3px solid Canvas;
1388
+ border-radius: 999px;
1389
+ }
1390
+
1391
+ html {
1392
+ hanging-punctuation: first allow-end last;
1393
+ -webkit-text-size-adjust: none;
1394
+ text-size-adjust: none;
1395
+ tab-size: 4;
1396
+ font-family:
1397
+ ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI",
1398
+ "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
1399
+ font-size: 16px;
1400
+ line-height: 1.5;
1401
+ -webkit-font-smoothing: antialiased;
1402
+ -moz-osx-font-smoothing: grayscale;
1403
+ text-rendering: optimizeLegibility;
1404
+ -webkit-tap-highlight-color: transparent;
1405
+ scroll-behavior: smooth;
1406
+ background: Canvas;
1407
+ color: CanvasText;
1408
+ }
1409
+
1410
+ body {
1411
+ min-height: 100svh;
1412
+ line-height: 1.5;
1413
+ background: Canvas;
1414
+ color: CanvasText;
1415
+ }
1416
+
1417
+ h1 {
1418
+ font-size: 2em;
1419
+ }
1420
+
1421
+ h2 {
1422
+ font-size: 1.5em;
1423
+ }
1424
+
1425
+ h3 {
1426
+ font-size: 1.17em;
1427
+ }
1428
+
1429
+ h4 {
1430
+ font-size: 1em;
1431
+ }
1432
+
1433
+ h5 {
1434
+ font-size: 0.83em;
1435
+ }
1436
+
1437
+ h6 {
1438
+ font-size: 0.67em;
1439
+ }
1440
+
1441
+ h1,
1442
+ h2,
1443
+ h3,
1444
+ h4,
1445
+ h5,
1446
+ h6 {
1447
+ font-weight: 700;
1448
+ line-height: 1.2;
1449
+ text-wrap: balance;
1450
+ overflow-wrap: break-word;
1451
+ }
1452
+
1453
+ p,
1454
+ li,
1455
+ figcaption,
1456
+ dd,
1457
+ dt,
1458
+ blockquote {
1459
+ overflow-wrap: break-word;
1460
+ }
1461
+
1462
+ p,
1463
+ li,
1464
+ figcaption,
1465
+ blockquote {
1466
+ text-wrap: pretty;
1467
+ }
1468
+
1469
+ ul,
1470
+ ol,
1471
+ menu {
1472
+ list-style: none;
1473
+ }
1474
+
1475
+ a {
1476
+ color: inherit;
1477
+ text-decoration: underline;
1478
+ text-decoration-thickness: 1px;
1479
+ text-underline-offset: 0.15em;
1480
+ text-decoration-skip-ink: auto;
1481
+ }
1482
+
1483
+ b,
1484
+ strong {
1485
+ font-weight: 700;
1486
+ }
1487
+
1488
+ small {
1489
+ font-size: 0.875em;
1490
+ }
1491
+
1492
+ code,
1493
+ kbd,
1494
+ samp,
1495
+ pre {
1496
+ font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
1497
+ font-size: 1em;
1498
+ }
1499
+
1500
+ sub,
1501
+ sup {
1502
+ font-size: 75%;
1503
+ line-height: 0;
1504
+ position: relative;
1505
+ vertical-align: baseline;
1506
+ }
1507
+
1508
+ sub {
1509
+ bottom: -0.25em;
1510
+ }
1511
+
1512
+ sup {
1513
+ top: -0.5em;
1514
+ }
1515
+
1516
+ abbr[title] {
1517
+ text-decoration: underline dotted;
1518
+ text-underline-offset: 0.15em;
1519
+ }
1520
+
1521
+ mark {
1522
+ background: Mark;
1523
+ color: MarkText;
1524
+ }
1525
+
1526
+ hr {
1527
+ height: 0;
1528
+ border: 0;
1529
+ border-top: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
1530
+ color: inherit;
1531
+ }
1532
+
1533
+ img,
1534
+ picture,
1535
+ video,
1536
+ canvas {
1537
+ display: block;
1538
+ max-width: 100%;
1539
+ }
1540
+
1541
+ img,
1542
+ video {
1543
+ height: auto;
1544
+ }
1545
+
1546
+ svg {
1547
+ overflow: hidden;
1548
+ }
1549
+
1550
+ iframe {
1551
+ border: 0;
1552
+ max-width: 100%;
1553
+ }
1554
+
1555
+ table {
1556
+ border-collapse: collapse;
1557
+ border-spacing: 0;
1558
+ }
1559
+
1560
+ fieldset {
1561
+ border: 0;
1562
+ min-inline-size: 0;
1563
+ }
1564
+
1565
+ legend {
1566
+ padding: 0;
1567
+ }
1568
+
1569
+ button,
1570
+ input,
1571
+ select,
1572
+ textarea,
1573
+ optgroup {
1574
+ font: inherit;
1575
+ letter-spacing: inherit;
1576
+ color: inherit;
1577
+ margin: 0;
1578
+ vertical-align: middle;
1579
+ }
1580
+
1581
+ button,
1582
+ input[type="button"],
1583
+ input[type="submit"],
1584
+ input[type="reset"] {
1585
+ appearance: none;
1586
+ background: none;
1587
+ border: 0;
1588
+ padding: 0;
1589
+ cursor: pointer;
1590
+ text-align: center;
1591
+ }
1592
+
1593
+ input,
1594
+ textarea,
1595
+ select {
1596
+ background-color: Canvas;
1597
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1598
+ border-radius: 0;
1599
+ padding: 0.5em 0.65em;
1600
+ }
1601
+
1602
+ input[type="checkbox"],
1603
+ input[type="radio"],
1604
+ input[type="range"],
1605
+ input[type="file"],
1606
+ input[type="color"],
1607
+ input[type="button"],
1608
+ input[type="submit"],
1609
+ input[type="reset"],
1610
+ input[type="image"],
1611
+ input[type="hidden"] {
1612
+ background: none;
1613
+ border: 0;
1614
+ padding: 0;
1615
+ border-radius: 0;
1616
+ }
1617
+
1618
+ input[type="checkbox"],
1619
+ input[type="radio"] {
1620
+ accent-color: CanvasText;
1621
+ }
1622
+
1623
+ input[type="search"] {
1624
+ appearance: none;
1625
+ background-color: Canvas;
1626
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1627
+ }
1628
+
1629
+ input[type="search"]::-webkit-search-decoration,
1630
+ input[type="search"]::-webkit-search-cancel-button,
1631
+ input[type="search"]::-webkit-search-results-button,
1632
+ input[type="search"]::-webkit-search-results-decoration {
1633
+ -webkit-appearance: none;
1634
+ display: none;
1635
+ }
1636
+
1637
+ input[type="number"] {
1638
+ appearance: textfield;
1639
+ background-color: Canvas;
1640
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1641
+ }
1642
+
1643
+ input[type="number"]::-webkit-inner-spin-button,
1644
+ input[type="number"]::-webkit-outer-spin-button {
1645
+ -webkit-appearance: none;
1646
+ margin: 0;
1647
+ }
1648
+
1649
+ textarea {
1650
+ resize: vertical;
1651
+ }
1652
+
1653
+ ::placeholder {
1654
+ opacity: 1;
1655
+ color: color-mix(in srgb, CanvasText 45%, transparent);
1656
+ }
1657
+
1658
+ :focus-visible {
1659
+ outline: 2px solid CanvasText;
1660
+ outline-offset: 2px;
1661
+ }
1662
+
1663
+ :focus:not(:focus-visible) {
1664
+ outline: none;
1665
+ }
1666
+
1667
+ :disabled,
1668
+ [disabled] {
1669
+ cursor: default;
1670
+ opacity: 0.6;
1671
+ }
1672
+
1673
+ label,
1674
+ summary,
1675
+ select,
1676
+ [role="button"] {
1677
+ cursor: pointer;
1678
+ }
1679
+
1680
+ summary {
1681
+ display: list-item;
1682
+ }
1683
+
1684
+ details {
1685
+ display: block;
1686
+ }
1687
+
1688
+ dialog {
1689
+ margin: auto;
1690
+ padding: 1rem;
1691
+ border: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
1692
+ background: Canvas;
1693
+ color: CanvasText;
1694
+ max-width: calc(100vw - 2rem);
1695
+ max-height: calc(100svh - 2rem);
1696
+ }
1697
+
1698
+ dialog:not([open]) {
1699
+ display: none;
1700
+ }
1701
+
1702
+ ::backdrop {
1703
+ background: color-mix(in srgb, CanvasText 40%, transparent);
1704
+ }
1705
+
1706
+ [hidden] {
1707
+ display: none !important;
1708
+ }
1709
+
1710
+ ::selection {
1711
+ background: Highlight;
1712
+ color: HighlightText;
1713
+ }
1714
+
1715
+ @media (prefers-reduced-motion: reduce) {
1716
+ *,
1717
+ *::before,
1718
+ *::after {
1719
+ animation-duration: 0.01ms !important;
1720
+ animation-iteration-count: 1 !important;
1721
+ transition-duration: 0.01ms !important;
1722
+ scroll-behavior: auto !important;
1723
+ }
1724
+ }
1725
+
1726
+ @media (pointer: coarse) {
1727
+ input,
1728
+ select,
1729
+ textarea {
1730
+ font-size: max(16px, 1em);
1731
+ }
1732
+ }`;
1733
+
1734
+ // ../core/dist/admin-client.js
1735
+ function isRecord2(value) {
1736
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1737
+ }
1738
+ function mountAdminBrand(doc, host, service, catalog) {
1739
+ const clip = (value, max) => {
1740
+ if (typeof value !== "string")
1741
+ return null;
1742
+ const trimmed = value.trim();
1743
+ if (trimmed === "")
1744
+ return null;
1745
+ return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
1746
+ };
1747
+ const httpUrl = (value) => {
1748
+ if (typeof value !== "string" || value.length > 2e3)
1749
+ return null;
1750
+ try {
1751
+ const url = new URL(value);
1752
+ if (url.protocol !== "https:" && url.protocol !== "http:")
1753
+ return null;
1754
+ return url.href;
1755
+ } catch {
1756
+ return null;
1757
+ }
1758
+ };
1759
+ const hexColor = (value) => {
1760
+ if (typeof value !== "string")
1761
+ return null;
1762
+ const raw = value.trim();
1763
+ return /^#[0-9a-fA-F]{3,8}$/.test(raw) ? raw : null;
1764
+ };
1765
+ const hostname = (value) => {
1766
+ try {
1767
+ return new URL(value).hostname.replace(/^www\./, "");
1768
+ } catch {
1769
+ return value;
1770
+ }
1771
+ };
1772
+ host.replaceChildren();
1773
+ host.hidden = true;
1774
+ if (!isRecord2(catalog) || !Object.hasOwn(catalog, service))
1775
+ return;
1776
+ const row = catalog[service];
1777
+ if (!isRecord2(row))
1778
+ return;
1779
+ const vendor = clip(row.vendor, 80);
1780
+ const website = httpUrl(row.website);
1781
+ const guide = httpUrl(row.guide);
1782
+ const docs = httpUrl(row.docs);
1783
+ const logo = httpUrl(row.logo);
1784
+ const color = hexColor(row.color);
1785
+ const description = clip(row.description, 280);
1786
+ if (vendor === null || website === null && guide === null && docs === null)
1787
+ return;
1788
+ const add = (node) => {
1789
+ host.append(node);
1790
+ return node;
1791
+ };
1792
+ if (logo !== null) {
1793
+ const img = doc.createElement("img");
1794
+ img.alt = "";
1795
+ img.src = logo;
1796
+ img.width = 16;
1797
+ img.height = 16;
1798
+ img.decoding = "async";
1799
+ img.referrerPolicy = "no-referrer";
1800
+ if (color !== null)
1801
+ img.style.borderColor = color;
1802
+ add(img);
1803
+ }
1804
+ const name = doc.createElement("span");
1805
+ name.className = "vendor-name";
1806
+ name.textContent = vendor;
1807
+ if (description !== null)
1808
+ name.title = description;
1809
+ add(name);
1810
+ const sep = () => {
1811
+ const node = doc.createElement("span");
1812
+ node.className = "vendor-sep";
1813
+ node.setAttribute("aria-hidden", "true");
1814
+ node.textContent = "\xB7";
1815
+ return node;
1816
+ };
1817
+ const link = (href, label, title) => {
1818
+ const node = doc.createElement("a");
1819
+ node.className = "vendor-link";
1820
+ node.href = href;
1821
+ node.target = "_blank";
1822
+ node.rel = "noopener noreferrer";
1823
+ node.referrerPolicy = "no-referrer";
1824
+ node.textContent = label;
1825
+ node.title = title;
1826
+ return node;
1827
+ };
1828
+ if (website !== null) {
1829
+ add(sep());
1830
+ add(link(website, hostname(website), `${vendor} website`));
1831
+ }
1832
+ if (guide !== null) {
1833
+ add(sep());
1834
+ add(link(guide, "Docs", `Docs for ${vendor}`));
1835
+ }
1836
+ if (docs !== null) {
1837
+ add(sep());
1838
+ add(link(docs, "API", `${vendor} API reference`));
1839
+ }
1840
+ host.removeAttribute("hidden");
1841
+ host.hidden = false;
1842
+ }
1843
+ function startAdminBrand(env) {
1844
+ const requested = env.params.get("brands");
1845
+ let brandsUrl = env.brandsUrl;
1846
+ if (requested) {
1847
+ try {
1848
+ const url = new URL(requested, env.location.href);
1849
+ const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]" || url.origin === env.location.origin;
1850
+ if ((url.protocol === "https:" || url.protocol === "http:") && local)
1851
+ brandsUrl = url.href;
1852
+ } catch {
1853
+ brandsUrl = env.brandsUrl;
1854
+ }
1855
+ }
1856
+ const vendorHost = env.$("vendor");
1857
+ const brandRoot = env.document.querySelector("[data-mockingbird-admin]");
1858
+ const brandService = brandRoot?.getAttribute("data-service") || "";
1859
+ if (vendorHost === null)
1860
+ return;
1861
+ env.fetch(brandsUrl, {
1862
+ headers: { accept: "application/json" },
1863
+ signal: AbortSignal.timeout(5e3)
1864
+ }).then(async (response) => {
1865
+ if (!response.ok)
1866
+ return null;
1867
+ const catalog = await response.json();
1868
+ return catalog;
1869
+ }).then((catalog) => {
1870
+ mountAdminBrand(env.document, vendorHost, brandService, catalog);
1871
+ }).catch(() => {
1872
+ });
1873
+ }
1874
+ function faultPresetList(value) {
1875
+ if (!isRecord2(value))
1876
+ throw new Error("Fault presets response was not an object");
1877
+ if (!Object.hasOwn(value, "presets") || value.presets === void 0 || value.presets === null) {
1878
+ return { presets: [] };
1879
+ }
1880
+ if (!Array.isArray(value.presets))
1881
+ throw new Error("Fault presets response was not a list");
1882
+ return {
1883
+ presets: value.presets.flatMap((item) => {
1884
+ if (!isRecord2(item) || typeof item.name !== "string")
1885
+ return [];
1886
+ return [{ name: item.name }];
1887
+ })
1888
+ };
1889
+ }
1890
+ function bootAdmin(config) {
1891
+ const fieldKinds = ["string", "number", "boolean", "null", "object", "array", "unknown"];
1892
+ const sources = ["declared", "inferred", "mixed"];
1893
+ const isFieldKind = (value) => typeof value === "string" && fieldKinds.some((kind) => kind === value);
1894
+ const isSource = (value) => typeof value === "string" && sources.some((source) => source === value);
1895
+ const readJson2 = (text) => {
1896
+ const value = JSON.parse(text);
1897
+ return value;
1898
+ };
1899
+ const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => {
1900
+ switch (char) {
1901
+ case "&":
1902
+ return "&amp;";
1903
+ case "<":
1904
+ return "&lt;";
1905
+ case ">":
1906
+ return "&gt;";
1907
+ case '"':
1908
+ return "&quot;";
1909
+ default:
1910
+ return "&#39;";
1911
+ }
1912
+ });
1913
+ const el = (id, kind) => {
1914
+ const node = document.getElementById(id);
1915
+ if (!(node instanceof kind))
1916
+ throw new Error(`admin shell is missing #${id}`);
1917
+ return node;
1918
+ };
1919
+ const errorMessage = (data, fallback) => {
1920
+ if (!isRecord2(data))
1921
+ return fallback;
1922
+ const error = data.error;
1923
+ if (!isRecord2(error) || typeof error.message !== "string" || error.message === "") {
1924
+ return fallback;
1925
+ }
1926
+ return error.message;
1927
+ };
1928
+ const requireList = (value, label) => {
1929
+ if (value === void 0 || value === null)
1930
+ return [];
1931
+ if (!Array.isArray(value))
1932
+ throw new Error(`${label} was not a list`);
1933
+ return value;
1934
+ };
1935
+ const state = {
1936
+ namespace: "default",
1937
+ key: sessionStorage.getItem("mockingbird-admin-key") || "",
1938
+ view: "overview",
1939
+ collection: "",
1940
+ shape: null,
1941
+ route: null,
1942
+ editing: null
1943
+ };
1944
+ const standard = new Set(config.standardRoutes);
1945
+ const banner = el("banner", HTMLElement);
1946
+ const keyInput = el("admin-key", HTMLInputElement);
1947
+ keyInput.value = state.key;
1948
+ const params = new URLSearchParams(location.search);
1949
+ const queryKey = params.get("key");
1950
+ if (queryKey) {
1951
+ state.key = queryKey;
1952
+ keyInput.value = state.key;
1953
+ sessionStorage.setItem("mockingbird-admin-key", state.key);
1954
+ params.delete("key");
1955
+ const next = params.toString();
1956
+ history.replaceState(null, "", location.pathname + (next ? `?${next}` : ""));
1957
+ }
1958
+ const queryNamespace = params.get("namespace");
1959
+ if (queryNamespace)
1960
+ state.namespace = queryNamespace;
1961
+ const showError = (error) => {
1962
+ banner.textContent = error instanceof Error ? error.message : String(error);
1963
+ banner.classList.add("show");
1964
+ };
1965
+ const clearError = () => {
1966
+ banner.classList.remove("show");
1967
+ };
1968
+ const api = async (method, path, body) => {
1969
+ const headers = { accept: "application/json" };
1970
+ if (state.key !== "")
1971
+ headers[config.adminKeyHeader] = state.key;
1972
+ if (state.namespace !== "")
1973
+ headers["x-mockingbird-namespace"] = state.namespace;
1974
+ if (body !== void 0)
1975
+ headers["content-type"] = "application/json";
1976
+ const init = { method, headers };
1977
+ if (body !== void 0)
1978
+ init.body = JSON.stringify(body);
1979
+ const response = await fetch(config.adminPrefix + path, init);
1980
+ const text = await response.text();
1981
+ let data = null;
1982
+ if (text !== "") {
1983
+ try {
1984
+ data = readJson2(text);
1985
+ } catch {
1986
+ data = { raw: text };
1987
+ }
1988
+ }
1989
+ if (response.status === 401) {
1990
+ keyInput.focus();
1991
+ throw new Error(errorMessage(data, "Admin key required"));
1992
+ }
1993
+ if (!response.ok) {
1994
+ throw new Error(errorMessage(data, `${response.status} ${method} ${path}`));
1995
+ }
1996
+ return data;
1997
+ };
1998
+ const originGet = async (path) => {
1999
+ const response = await fetch(path, { headers: { accept: "application/json" } });
2000
+ if (!response.ok)
2001
+ throw new Error(`${response.status} ${path}`);
2002
+ const value = await response.json();
2003
+ return value;
2004
+ };
2005
+ const panelApi = {
2006
+ get: (path) => api("GET", path),
2007
+ send: (method, path, body) => api(method, path, body),
2008
+ namespace: () => state.namespace
2009
+ };
2010
+ const isPanelScript = (value) => typeof value === "function";
2011
+ const setTheme = (theme) => {
2012
+ if (theme)
2013
+ document.documentElement.dataset.theme = theme;
2014
+ else
2015
+ delete document.documentElement.dataset.theme;
2016
+ };
2017
+ const storedTheme = localStorage.getItem("mockingbird-admin-theme");
2018
+ if (storedTheme)
2019
+ setTheme(storedTheme);
2020
+ el("theme", HTMLButtonElement).addEventListener("click", () => {
2021
+ const current = document.documentElement.dataset.theme;
2022
+ const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
2023
+ const next = dark ? "light" : "dark";
2024
+ localStorage.setItem("mockingbird-admin-theme", next);
2025
+ setTheme(next);
2026
+ });
2027
+ const show = (view) => {
2028
+ state.view = view;
2029
+ for (const button of document.querySelectorAll("#nav button")) {
2030
+ if (!(button instanceof HTMLButtonElement))
2031
+ continue;
2032
+ button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false");
2033
+ }
2034
+ for (const section of document.querySelectorAll("main .view")) {
2035
+ if (!(section instanceof HTMLElement))
2036
+ continue;
2037
+ section.hidden = section.id !== `view-${view}`;
2038
+ }
2039
+ };
2040
+ el("nav", HTMLElement).addEventListener("click", (event) => {
2041
+ const target = event.target;
2042
+ if (!(target instanceof Element))
2043
+ return;
2044
+ const button = target.closest("button[data-view]");
2045
+ if (!(button instanceof HTMLButtonElement))
2046
+ return;
2047
+ const view = button.dataset.view;
2048
+ if (view === void 0)
2049
+ return;
2050
+ show(view);
2051
+ refresh().catch(showError);
2052
+ });
2053
+ 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>`;
2054
+ const table = (columns, rows) => {
2055
+ if (rows.length === 0)
2056
+ return '<p class="empty">Nothing here yet.</p>';
2057
+ const head = `<tr>${columns.map((column) => `<th>${column.label}</th>`).join("")}</tr>`;
2058
+ const body = rows.map((row) => {
2059
+ const cells = columns.map((column) => `<td data-label="${column.label}">${column.cell(row)}</td>`).join("");
2060
+ const attrs = "attrs" in row && typeof row.attrs === "string" ? row.attrs : "";
2061
+ return `<tr${attrs}>${cells}</tr>`;
2062
+ }).join("");
2063
+ return `<table><thead>${head}</thead><tbody>${body}</tbody></table>`;
2064
+ };
2065
+ const preview = (value) => {
2066
+ const text = JSON.stringify(value) ?? "null";
2067
+ return esc(text.length > 180 ? `${text.slice(0, 180)}\u2026` : text);
2068
+ };
2069
+ const when = (epoch) => {
2070
+ const date = new Date(epoch);
2071
+ return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString();
2072
+ };
2073
+ const clockCard = (epoch) => {
2074
+ const date = new Date(epoch);
2075
+ if (Number.isNaN(date.getTime()))
2076
+ return esc(String(epoch));
2077
+ const iso = date.toISOString();
2078
+ return `${esc(iso.slice(0, 10))}<span class="clock-time">${esc(iso.slice(11, 19))} UTC</span>`;
2079
+ };
2080
+ const decodeClock = (value) => {
2081
+ if (!isRecord2(value) || typeof value.now !== "number" || typeof value.offsetMs !== "number" || typeof value.frozen !== "boolean") {
2082
+ throw new Error("Clock response was not a clock");
2083
+ }
2084
+ return { now: value.now, offsetMs: value.offsetMs, frozen: value.frozen };
2085
+ };
2086
+ const decodeField = (value) => {
2087
+ if (!isRecord2(value) || typeof value.name !== "string" || !isFieldKind(value.kind)) {
2088
+ return void 0;
2089
+ }
2090
+ const field = {
2091
+ name: value.name,
2092
+ kind: value.kind,
2093
+ optional: value.optional === true
2094
+ };
2095
+ if (typeof value.description === "string")
2096
+ field.description = value.description;
2097
+ if (Array.isArray(value.fields)) {
2098
+ field.fields = value.fields.flatMap((item) => {
2099
+ const child = decodeField(item);
2100
+ return child === void 0 ? [] : [child];
2101
+ });
2102
+ }
2103
+ return field;
2104
+ };
2105
+ const decodeCollection = (value) => {
2106
+ if (!isRecord2(value) || typeof value.name !== "string")
2107
+ return void 0;
2108
+ const fields = Array.isArray(value.fields) ? value.fields.flatMap((item) => {
2109
+ const field = decodeField(item);
2110
+ return field === void 0 ? [] : [field];
2111
+ }) : [];
2112
+ const collection = {
2113
+ name: value.name,
2114
+ label: typeof value.label === "string" ? value.label : value.name,
2115
+ count: typeof value.count === "number" ? value.count : 0,
2116
+ source: isSource(value.source) ? value.source : "inferred",
2117
+ fields
2118
+ };
2119
+ if (typeof value.description === "string")
2120
+ collection.description = value.description;
2121
+ return collection;
2122
+ };
2123
+ const decodeState = (value) => {
2124
+ if (!isRecord2(value) || !Array.isArray(value.collections)) {
2125
+ throw new Error("State response has no collection list");
2126
+ }
2127
+ return {
2128
+ namespace: typeof value.namespace === "string" ? value.namespace : state.namespace,
2129
+ storageNamespace: typeof value.storageNamespace === "string" ? value.storageNamespace : "",
2130
+ collections: value.collections.flatMap((item) => {
2131
+ const collection = decodeCollection(item);
2132
+ return collection === void 0 ? [] : [collection];
2133
+ })
2134
+ };
2135
+ };
2136
+ const decodeRecords = (value) => requireList(isRecord2(value) ? value.records : void 0, "Records").flatMap((item) => {
2137
+ if (!isRecord2(item) || typeof item.id !== "string")
2138
+ return [];
2139
+ return [
2140
+ {
2141
+ id: item.id,
2142
+ seq: typeof item.seq === "number" ? item.seq : 0,
2143
+ value: item.value,
2144
+ attrs: ` class="clickable" data-id="${esc(item.id)}"`
2145
+ }
2146
+ ];
2147
+ });
2148
+ const textOf = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : "";
2149
+ const decodeJournal = (value) => requireList(isRecord2(value) ? value.requests : void 0, "Journal").flatMap((item) => {
2150
+ if (!isRecord2(item))
2151
+ return [];
2152
+ return [
2153
+ {
2154
+ at: textOf(item.at),
2155
+ method: textOf(item.method),
2156
+ path: textOf(item.path),
2157
+ status: textOf(item.status),
2158
+ durationMs: textOf(item.durationMs),
2159
+ operationId: textOf(item.operationId)
2160
+ }
2161
+ ];
2162
+ });
2163
+ const decodeFaults = (value) => requireList(isRecord2(value) ? value.faults : void 0, "Faults").flatMap((item) => {
2164
+ if (!isRecord2(item))
2165
+ return [];
2166
+ return [
2167
+ {
2168
+ id: textOf(item.id),
2169
+ method: typeof item.method === "string" ? item.method : void 0,
2170
+ pathPrefix: typeof item.pathPrefix === "string" ? item.pathPrefix : void 0,
2171
+ operationId: typeof item.operationId === "string" ? item.operationId : void 0,
2172
+ status: typeof item.status === "number" ? item.status : void 0,
2173
+ effect: typeof item.effect === "string" ? item.effect : void 0,
2174
+ drop: typeof item.drop === "boolean" ? item.drop : void 0,
2175
+ latencyMs: typeof item.latencyMs === "number" ? item.latencyMs : void 0
2176
+ }
2177
+ ];
2178
+ });
2179
+ const loadNamespaces = async () => {
2180
+ const data = await api("GET", "/namespaces");
2181
+ const select = el("namespace", HTMLSelectElement);
2182
+ const listed = requireList(isRecord2(data) ? data.namespaces : void 0, "Namespaces");
2183
+ const fallback = isRecord2(data) && typeof data.default === "string" && data.default !== "" ? data.default : "default";
2184
+ const names = (listed.length > 0 ? listed.map((name) => String(name)) : [fallback]).slice();
2185
+ if (!names.includes(state.namespace))
2186
+ names.unshift(state.namespace);
2187
+ select.innerHTML = names.map((name) => `<option value="${esc(name)}">${esc(name)}</option>`).join("");
2188
+ select.value = state.namespace;
2189
+ };
2190
+ const renderOverview = async () => {
2191
+ const [health, shape, clock, journal] = await Promise.all([
2192
+ originGet("/health"),
2193
+ api("GET", "/state"),
2194
+ api("GET", "/clock"),
2195
+ api("GET", "/requests?limit=8")
2196
+ ]);
2197
+ if (!isRecord2(health))
2198
+ throw new Error("Health response was not an object");
2199
+ const decoded = decodeState(shape);
2200
+ state.shape = decoded;
2201
+ const time = decodeClock(clock);
2202
+ el("overview-cards", HTMLElement).innerHTML = cards([
2203
+ {
2204
+ k: "Service",
2205
+ v: esc(typeof health.service === "string" && health.service !== "" ? health.service : config.service)
2206
+ },
2207
+ {
2208
+ k: "Status",
2209
+ v: esc(typeof health.status === "string" && health.status !== "" ? health.status : "ok")
2210
+ },
2211
+ { k: "Namespace", v: esc(state.namespace) },
2212
+ { k: "Collections", v: String(decoded.collections.length) },
2213
+ {
2214
+ k: "Records",
2215
+ v: String(decoded.collections.reduce((sum, item) => sum + item.count, 0))
2216
+ },
2217
+ { k: "Clock", v: clockCard(time.now) }
2218
+ ]);
2219
+ el("overview-requests", HTMLElement).innerHTML = table([
2220
+ { label: "Method", cell: (row) => esc(row.method) },
2221
+ { label: "Path", cell: (row) => esc(row.path) },
2222
+ {
2223
+ label: "Status",
2224
+ cell: (row) => `<span class="status-${row.status.charAt(0)}">${esc(row.status)}</span>`
2225
+ },
2226
+ { label: "Operation", cell: (row) => esc(row.operationId) }
2227
+ ], decodeJournal(journal));
2228
+ };
2229
+ const schemaChips = (collection) => {
2230
+ if (collection.fields.length === 0) {
2231
+ return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>';
2232
+ }
2233
+ return `<div class="chips">${collection.fields.map((field) => `<span class="chip">${esc(field.name)} <i>${esc(field.kind)}${field.optional ? "?" : ""}</i></span>`).join("")}</div>`;
2234
+ };
2235
+ const renderState = async () => {
2236
+ const shape = decodeState(await api("GET", "/state"));
2237
+ state.shape = shape;
2238
+ const host = el("collections", HTMLElement);
2239
+ if (shape.collections.length === 0) {
2240
+ host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>';
2241
+ el("schema", HTMLElement).innerHTML = "";
2242
+ el("records", HTMLElement).innerHTML = "";
2243
+ return;
2244
+ }
2245
+ if (!shape.collections.some((item) => item.name === state.collection)) {
2246
+ const first = shape.collections[0];
2247
+ if (first === void 0)
2248
+ return;
2249
+ state.collection = first.name;
2250
+ }
2251
+ 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("");
2252
+ const selected = shape.collections.find((item) => item.name === state.collection);
2253
+ if (selected === void 0)
2254
+ return;
2255
+ const description = selected.description ? `<p class="lede">${esc(selected.description)}</p>` : "";
2256
+ el("schema", HTMLElement).innerHTML = `<h3>${esc(selected.label)}</h3>${description}${schemaChips(selected)}`;
2257
+ const page2 = await api("GET", `/state/${encodeURIComponent(state.collection)}?limit=50`);
2258
+ el("records", HTMLElement).innerHTML = table([
2259
+ { label: "Id", cell: (row) => `<code>${esc(row.id)}</code>` },
2260
+ { label: "Seq", cell: (row) => esc(row.seq) },
2261
+ { label: "Value", cell: (row) => preview(row.value) }
2262
+ ], decodeRecords(page2));
2263
+ };
2264
+ const renderClock = async () => {
2265
+ const clock = decodeClock(await api("GET", "/clock"));
2266
+ 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>`;
2267
+ const freeze = el("freeze", HTMLButtonElement);
2268
+ freeze.textContent = clock.frozen ? "Unfreeze" : "Freeze";
2269
+ freeze.dataset.frozen = clock.frozen ? "1" : "0";
2270
+ };
2271
+ const renderFaults = async () => {
2272
+ const [faults, presets] = await Promise.all([
2273
+ api("GET", "/faults"),
2274
+ api("GET", "/faults/presets").catch(() => ({ presets: [] }))
2275
+ ]);
2276
+ const select = el("fault-preset", HTMLSelectElement);
2277
+ const current = select.value;
2278
+ const list = faultPresetList(presets).presets;
2279
+ select.innerHTML = `<option value="">Custom rule</option>${list.map((preset) => `<option value="${esc(preset.name)}">${esc(preset.name)}</option>`).join("")}`;
2280
+ select.value = current;
2281
+ el("fault-list", HTMLElement).innerHTML = table([
2282
+ { label: "Id", cell: (row) => esc(row.id) },
2283
+ {
2284
+ label: "Match",
2285
+ cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter((part) => Boolean(part)).join(" ") || "all")
2286
+ },
2287
+ {
2288
+ label: "Effect",
2289
+ cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || ""))
2290
+ }
2291
+ ], decodeFaults(faults));
2292
+ };
2293
+ const renderJournal = async () => {
2294
+ const journal = await api("GET", "/requests?limit=100");
2295
+ el("journal", HTMLElement).innerHTML = table([
2296
+ { label: "When", cell: (row) => esc(row.at) },
2297
+ { label: "Method", cell: (row) => esc(row.method) },
2298
+ { label: "Path", cell: (row) => esc(row.path) },
2299
+ { label: "Status", cell: (row) => esc(row.status) },
2300
+ { label: "ms", cell: (row) => esc(row.durationMs) }
2301
+ ], decodeJournal(journal));
2302
+ };
2303
+ const renderRoutes = async () => {
2304
+ const data = await api("GET", "/");
2305
+ if (!isRecord2(data))
2306
+ throw new Error("Route list was not an object");
2307
+ const routes = requireList(data.routes, "Routes").map((route) => String(route));
2308
+ el("route-list", HTMLElement).innerHTML = routes.map((route) => {
2309
+ const extra = standard.has(route) ? "" : " <span class='badge'>extension</span>";
2310
+ const parts = route.split(" ");
2311
+ const method = parts[0] ?? "";
2312
+ const path = parts.slice(1).join(" ");
2313
+ 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>`;
2314
+ }).join("");
2315
+ };
2316
+ const renderers = {
2317
+ overview: renderOverview,
2318
+ state: renderState,
2319
+ clock: renderClock,
2320
+ faults: renderFaults,
2321
+ journal: renderJournal,
2322
+ routes: renderRoutes
2323
+ };
2324
+ async function refresh() {
2325
+ clearError();
2326
+ const render = renderers[state.view];
2327
+ if (render)
2328
+ await render();
2329
+ }
2330
+ el("namespace", HTMLSelectElement).addEventListener("change", () => {
2331
+ state.namespace = el("namespace", HTMLSelectElement).value;
2332
+ refresh().catch(showError);
2333
+ });
2334
+ keyInput.addEventListener("change", () => {
2335
+ state.key = keyInput.value.trim();
2336
+ sessionStorage.setItem("mockingbird-admin-key", state.key);
2337
+ refresh().catch(showError);
2338
+ });
2339
+ el("refresh", HTMLButtonElement).addEventListener("click", () => {
2340
+ refresh().catch(showError);
2341
+ });
2342
+ el("collections", HTMLElement).addEventListener("click", (event) => {
2343
+ const target = event.target;
2344
+ if (!(target instanceof Element))
2345
+ return;
2346
+ const button = target.closest("[data-collection]");
2347
+ if (!(button instanceof HTMLElement))
2348
+ return;
2349
+ const collection = button.dataset.collection;
2350
+ if (collection === void 0)
2351
+ return;
2352
+ state.collection = collection;
2353
+ renderState().catch(showError);
2354
+ });
2355
+ el("records", HTMLElement).addEventListener("click", (event) => {
2356
+ const target = event.target;
2357
+ if (!(target instanceof Element))
2358
+ return;
2359
+ const row = target.closest("[data-id]");
2360
+ if (!(row instanceof HTMLElement))
2361
+ return;
2362
+ const id = row.dataset.id;
2363
+ if (id === void 0)
2364
+ return;
2365
+ openEditor(id).catch(showError);
2366
+ });
2367
+ const editor = el("editor", HTMLDialogElement);
2368
+ const editorId = el("editor-id", HTMLInputElement);
2369
+ const editorValue = el("editor-value", HTMLTextAreaElement);
2370
+ const editorDelete = el("editor-delete", HTMLButtonElement);
2371
+ async function openEditor(id) {
2372
+ state.editing = id;
2373
+ el("editor-title", HTMLElement).textContent = id ? "Edit record" : "New record";
2374
+ editorId.value = id ?? "";
2375
+ editorId.readOnly = id !== null;
2376
+ editorDelete.hidden = id === null;
2377
+ if (id) {
2378
+ const record = await api("GET", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`);
2379
+ if (!isRecord2(record))
2380
+ throw new Error("Record response was not an object");
2381
+ editorValue.value = JSON.stringify(record.value, null, 2) ?? "";
2382
+ } else {
2383
+ editorValue.value = "{\n \n}";
2384
+ }
2385
+ editor.showModal();
2386
+ }
2387
+ el("new-record", HTMLButtonElement).addEventListener("click", () => {
2388
+ openEditor(null).catch(showError);
2389
+ });
2390
+ el("editor-form", HTMLFormElement).addEventListener("submit", (event) => {
2391
+ const submitter = event.submitter;
2392
+ if (!(submitter instanceof HTMLButtonElement) || submitter.value !== "save")
2393
+ return;
2394
+ event.preventDefault();
2395
+ let value;
2396
+ try {
2397
+ value = readJson2(editorValue.value);
2398
+ } catch {
2399
+ showError(new Error("Value is not JSON"));
2400
+ return;
2401
+ }
2402
+ const id = editorId.value.trim();
2403
+ const path = `/state/${encodeURIComponent(state.collection)}`;
2404
+ const request = state.editing ? api("PUT", `${path}/${encodeURIComponent(id)}`, { value }) : api("POST", path, { id: id || void 0, value });
2405
+ request.then(() => {
2406
+ editor.close();
2407
+ return renderState();
2408
+ }).catch(showError);
2409
+ });
2410
+ editorDelete.addEventListener("click", () => {
2411
+ const id = state.editing;
2412
+ if (!id)
2413
+ return;
2414
+ api("DELETE", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`).then(() => {
2415
+ editor.close();
2416
+ return renderState();
2417
+ }).catch(showError);
2418
+ });
2419
+ for (const button of document.querySelectorAll("[data-advance]")) {
2420
+ if (!(button instanceof HTMLButtonElement))
2421
+ continue;
2422
+ button.addEventListener("click", () => {
2423
+ const raw = button.dataset.advance;
2424
+ if (raw === void 0)
2425
+ return;
2426
+ api("POST", "/clock", { advance: Number(raw) }).then(() => renderClock()).catch(showError);
2427
+ });
2428
+ }
2429
+ el("freeze", HTMLButtonElement).addEventListener("click", () => {
2430
+ const frozen = el("freeze", HTMLButtonElement).dataset.frozen === "1";
2431
+ api("POST", "/clock", { freeze: !frozen }).then(() => renderClock()).catch(showError);
2432
+ });
2433
+ el("clock-reset", HTMLButtonElement).addEventListener("click", () => {
2434
+ api("POST", "/clock", { reset: true }).then(() => renderClock()).catch(showError);
2435
+ });
2436
+ el("clock-set", HTMLFormElement).addEventListener("submit", (event) => {
2437
+ event.preventDefault();
2438
+ const raw = el("clock-instant", HTMLInputElement).value.trim();
2439
+ const set = /^-?\d+$/.test(raw) ? Number(raw) : raw;
2440
+ api("POST", "/clock", { set }).then(() => renderClock()).catch(showError);
2441
+ });
2442
+ el("fault-form", HTMLFormElement).addEventListener("submit", (event) => {
2443
+ event.preventDefault();
2444
+ const preset = el("fault-preset", HTMLSelectElement).value;
2445
+ if (preset !== "") {
2446
+ api("POST", "/faults", { preset }).then(() => renderFaults()).catch(showError);
2447
+ return;
2448
+ }
2449
+ const status = Number(el("fault-status", HTMLInputElement).value);
2450
+ if (!Number.isFinite(status)) {
2451
+ showError(new Error("Status must be a number, or choose a preset"));
2452
+ return;
2453
+ }
2454
+ const path = el("fault-path", HTMLInputElement).value;
2455
+ const method = el("fault-method", HTMLInputElement).value;
2456
+ api("POST", "/faults", {
2457
+ status,
2458
+ ...path !== "" ? { pathPrefix: path } : {},
2459
+ ...method !== "" ? { method } : {}
2460
+ }).then(() => renderFaults()).catch(showError);
2461
+ });
2462
+ el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
2463
+ api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
2464
+ });
2465
+ el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
2466
+ api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
2467
+ });
2468
+ el("route-list", HTMLElement).addEventListener("click", (event) => {
2469
+ const target = event.target;
2470
+ if (!(target instanceof Element))
2471
+ return;
2472
+ const button = target.closest("[data-route]");
2473
+ if (!(button instanceof HTMLElement))
2474
+ return;
2475
+ const route = button.dataset.route;
2476
+ if (route === void 0)
2477
+ return;
2478
+ state.route = route;
2479
+ el("route-title", HTMLElement).textContent = route;
2480
+ const method = route.split(" ")[0] ?? "";
2481
+ el("route-body", HTMLTextAreaElement).disabled = method === "GET" || method === "DELETE";
2482
+ });
2483
+ el("route-form", HTMLFormElement).addEventListener("submit", (event) => {
2484
+ event.preventDefault();
2485
+ const send = async () => {
2486
+ if (state.route === null)
2487
+ throw new Error("Choose a route first");
2488
+ const parts = state.route.split(" ");
2489
+ const method = parts[0];
2490
+ const path = parts.slice(1).join(" ");
2491
+ if (method === void 0)
2492
+ throw new Error("Choose a route first");
2493
+ const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_match, key) => prompt(`Value for ${key}`) || "");
2494
+ let body;
2495
+ const routeBody = el("route-body", HTMLTextAreaElement);
2496
+ if (method !== "GET" && method !== "DELETE" && routeBody.value.trim() !== "") {
2497
+ try {
2498
+ body = readJson2(routeBody.value);
2499
+ } catch {
2500
+ throw new Error("Body is not JSON");
2501
+ }
2502
+ }
2503
+ const data = await api(method, concrete, body);
2504
+ el("route-result", HTMLElement).textContent = JSON.stringify(data, null, 2);
2505
+ };
2506
+ send().catch(showError);
2507
+ });
2508
+ const decodePanel = (value) => {
2509
+ if (!isRecord2(value) || typeof value.id !== "string" || typeof value.title !== "string") {
2510
+ return void 0;
2511
+ }
2512
+ const panel = {
2513
+ id: value.id,
2514
+ title: value.title,
2515
+ description: typeof value.description === "string" ? value.description : "",
2516
+ html: typeof value.html === "string" ? value.html : ""
2517
+ };
2518
+ if (typeof value.script === "string")
2519
+ panel.script = value.script;
2520
+ return panel;
2521
+ };
2522
+ const mountPanels = (panels) => {
2523
+ const nav = el("nav", HTMLElement);
2524
+ const views = el("views", HTMLElement);
2525
+ for (const panel of panels) {
2526
+ const button = document.createElement("button");
2527
+ button.type = "button";
2528
+ button.dataset.view = panel.id;
2529
+ button.textContent = panel.title;
2530
+ nav.append(button);
2531
+ const section = document.createElement("section");
2532
+ section.className = "view";
2533
+ section.id = `view-${panel.id}`;
2534
+ section.hidden = true;
2535
+ const title = document.createElement("h2");
2536
+ title.textContent = panel.title;
2537
+ const lede = document.createElement("p");
2538
+ lede.className = "lede";
2539
+ lede.textContent = panel.description;
2540
+ const body = document.createElement("div");
2541
+ body.className = "panel-body";
2542
+ body.id = `panel-${panel.id}`;
2543
+ body.innerHTML = panel.html;
2544
+ section.append(title, lede, body);
2545
+ views.append(section);
2546
+ if (panel.script === void 0)
2547
+ continue;
2548
+ try {
2549
+ const compiled = new Function("root", "api", panel.script);
2550
+ if (!isPanelScript(compiled))
2551
+ throw new Error("panel script did not compile");
2552
+ compiled(body, panelApi);
2553
+ } catch (error) {
2554
+ const note = document.createElement("p");
2555
+ note.className = "banner show";
2556
+ note.textContent = error instanceof Error ? error.message : String(error);
2557
+ body.append(note);
2558
+ }
2559
+ }
2560
+ };
2561
+ startAdminBrand({
2562
+ document,
2563
+ $: (id) => document.getElementById(id),
2564
+ params,
2565
+ location: { href: location.href, origin: location.origin },
2566
+ fetch,
2567
+ brandsUrl: config.brandsUrl
2568
+ });
2569
+ loadNamespaces().then(() => api("GET", "/ui/manifest")).then((manifest) => {
2570
+ const panels = requireList(isRecord2(manifest) ? manifest.panels : void 0, "Panels").flatMap((panel) => {
2571
+ const decoded = decodePanel(panel);
2572
+ return decoded === void 0 ? [] : [decoded];
2573
+ });
2574
+ mountPanels(panels);
2575
+ return refresh();
2576
+ }).catch(showError);
2577
+ }
2578
+ var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
2579
+ var adminClientSource = (config) => {
2580
+ const payload = JSON.stringify(config).replace(/</g, "\\u003c");
2581
+ return `<script>
2582
+ ${clientPrelude}
2583
+ bootAdmin(${payload});
2584
+ </script>`;
2585
+ };
2586
+
1361
2587
  // ../core/dist/surface.js
1362
2588
  var defineMock = (runtime) => runtime;
1363
2589
  var STANDARD_ADMIN_ROUTES = [
@@ -1426,94 +2652,106 @@ var escapeHtml = (value) => value.replace(/[&<>"']/g, (char) => {
1426
2652
  return "&#39;";
1427
2653
  }
1428
2654
  });
2655
+ var ADMIN_BRANDS_URL = "https://mockingbird.chrisvouga.dev/brands.json";
1429
2656
  var renderAdminDocument = (service) => {
1430
2657
  const name = escapeHtml(service);
1431
- const standard = JSON.stringify(STANDARD_ADMIN_ROUTES);
2658
+ const script = adminClientSource({
2659
+ standardRoutes: STANDARD_ADMIN_ROUTES,
2660
+ adminPrefix: ADMIN_PREFIX,
2661
+ adminKeyHeader: ADMIN_KEY_HEADER,
2662
+ brandsUrl: ADMIN_BRANDS_URL,
2663
+ service
2664
+ });
1432
2665
  return `<!DOCTYPE html>
1433
2666
  <html lang="en">
1434
2667
  <head>
1435
2668
  <meta charset="utf-8">
1436
2669
  <meta name="viewport" content="width=device-width, initial-scale=1">
1437
2670
  <meta name="color-scheme" content="light dark">
1438
- <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">
2671
+ <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">
1439
2672
  <title>${name} admin</title>
1440
2673
  <style>
2674
+ ${CSS_RESET}
1441
2675
  :root {
1442
2676
  color-scheme: light;
1443
- --bg: #f6f5f2;
2677
+ --bg: #fafafa;
1444
2678
  --bg-elev: #ffffff;
1445
- --bg-muted: #eeece6;
1446
- --bg-inset: #f3f1eb;
1447
- --line: #e2dfd6;
1448
- --line-strong: #d0ccc1;
1449
- --ink: #1c1915;
1450
- --muted: #5e584f;
1451
- --faint: #8a8378;
1452
- --accent: #3d4f3a;
1453
- --accent-ink: #f7f6f2;
1454
- --accent-soft: #e5ece2;
1455
- --ok: #1f7a45;
1456
- --ok-soft: #e5f4eb;
1457
- --warn: #9a6700;
1458
- --warn-soft: #fbf3df;
1459
- --err: #a33b32;
1460
- --err-soft: #fdecea;
1461
- --shadow: 0 1px 2px rgb(28 25 21 / 0.05), 0 12px 32px rgb(28 25 21 / 0.06);
1462
- --sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
2679
+ --bg-muted: #f4f4f5;
2680
+ --bg-inset: #f4f4f5;
2681
+ --line: #dedee3;
2682
+ --line-strong: #c4c4cc;
2683
+ --ink: #18181b;
2684
+ --muted: #62626b;
2685
+ --faint: #71717a;
2686
+ --accent: #27272a;
2687
+ --accent-ink: #ffffff;
2688
+ --accent-soft: #f4f4f5;
2689
+ --ok: #166534;
2690
+ --ok-soft: #f0fdf4;
2691
+ --warn: #a16207;
2692
+ --warn-soft: #fefce8;
2693
+ --err: #a82d32;
2694
+ --err-soft: #fef2f2;
2695
+ --shadow: none;
2696
+ --sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
1463
2697
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
1464
- --radius: 14px;
1465
- --header: 60px;
1466
- --nav: 220px;
2698
+ --radius: 10px;
2699
+ --header: 56px;
2700
+ --nav: 200px;
1467
2701
  }
1468
2702
  :root[data-theme="dark"] {
1469
2703
  color-scheme: dark;
1470
- --bg: #141311;
1471
- --bg-elev: #1e1c19;
1472
- --bg-muted: #26231f;
1473
- --bg-inset: #181715;
1474
- --line: #34302b;
1475
- --line-strong: #4a453d;
1476
- --ink: #f4f1ea;
1477
- --muted: #c4bdb1;
1478
- --faint: #8f877c;
1479
- --accent: #c5d5b8;
1480
- --accent-ink: #1a2118;
1481
- --accent-soft: #2a3328;
1482
- --ok: #8fd6a8;
1483
- --ok-soft: #1a2e22;
1484
- --warn: #f0cc78;
1485
- --warn-soft: #332911;
1486
- --err: #f0a8a2;
1487
- --err-soft: #3a201e;
1488
- --shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 16px 40px rgb(0 0 0 / 0.28);
2704
+ --bg: #111113;
2705
+ --bg-elev: #19191c;
2706
+ --bg-muted: #242428;
2707
+ --bg-inset: #111113;
2708
+ --line: #36363c;
2709
+ --line-strong: #52525b;
2710
+ --ink: #f4f4f5;
2711
+ --muted: #a9a9b2;
2712
+ --faint: #71717a;
2713
+ --accent: #e4e4e7;
2714
+ --accent-ink: #18181b;
2715
+ --accent-soft: #242428;
2716
+ --ok: #86efac;
2717
+ --ok-soft: #14241b;
2718
+ --warn: #fde68a;
2719
+ --warn-soft: #2a2410;
2720
+ --err: #ffaaaa;
2721
+ --err-soft: #2c1516;
2722
+ --shadow: none;
1489
2723
  }
1490
2724
  @media (prefers-color-scheme: dark) {
1491
2725
  :root:not([data-theme="light"]) {
1492
2726
  color-scheme: dark;
1493
- --bg: #141311;
1494
- --bg-elev: #1e1c19;
1495
- --bg-muted: #26231f;
1496
- --bg-inset: #181715;
1497
- --line: #34302b;
1498
- --line-strong: #4a453d;
1499
- --ink: #f4f1ea;
1500
- --muted: #c4bdb1;
1501
- --faint: #8f877c;
1502
- --accent: #c5d5b8;
1503
- --accent-ink: #1a2118;
1504
- --accent-soft: #2a3328;
1505
- --ok: #8fd6a8;
1506
- --ok-soft: #1a2e22;
1507
- --warn: #f0cc78;
1508
- --warn-soft: #332911;
1509
- --err: #f0a8a2;
1510
- --err-soft: #3a201e;
1511
- --shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 16px 40px rgb(0 0 0 / 0.28);
2727
+ --bg: #111113;
2728
+ --bg-elev: #19191c;
2729
+ --bg-muted: #242428;
2730
+ --bg-inset: #111113;
2731
+ --line: #36363c;
2732
+ --line-strong: #52525b;
2733
+ --ink: #f4f4f5;
2734
+ --muted: #a9a9b2;
2735
+ --faint: #71717a;
2736
+ --accent: #e4e4e7;
2737
+ --accent-ink: #18181b;
2738
+ --accent-soft: #242428;
2739
+ --ok: #86efac;
2740
+ --ok-soft: #14241b;
2741
+ --warn: #fde68a;
2742
+ --warn-soft: #2a2410;
2743
+ --err: #ffaaaa;
2744
+ --err-soft: #2c1516;
2745
+ --shadow: none;
1512
2746
  }
1513
2747
  }
1514
- * { box-sizing: border-box; }
1515
- html, body { margin: 0; height: 100%; }
2748
+ /* Fill the viewport, then grow with the content so the background covers the scroll. */
2749
+ html { height: 100%; }
1516
2750
  body {
2751
+ margin: 0;
2752
+ min-height: 100%;
2753
+ display: flex;
2754
+ flex-direction: column;
1517
2755
  font-family: var(--sans);
1518
2756
  background: var(--bg);
1519
2757
  color: var(--ink);
@@ -1522,24 +2760,37 @@ var renderAdminDocument = (service) => {
1522
2760
  padding-right: env(safe-area-inset-right);
1523
2761
  -webkit-text-size-adjust: 100%;
1524
2762
  }
1525
- button, input, select, textarea { font: inherit; color: inherit; }
1526
- button { cursor: pointer; }
1527
2763
  :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
1528
- @media (prefers-reduced-motion: reduce) {
1529
- * { scroll-behavior: auto !important; transition: none !important; }
1530
- }
1531
- .app { min-height: 100%; display: flex; flex-direction: column; }
2764
+ .app { flex: 1 0 auto; display: flex; flex-direction: column; min-width: 0; width: 100%; }
1532
2765
  header.top {
1533
2766
  position: sticky; top: 0; z-index: 5;
1534
2767
  display: flex; align-items: center; gap: 12px;
1535
2768
  min-height: var(--header); padding: 10px 16px;
1536
- background: color-mix(in srgb, var(--bg-elev) 92%, transparent);
2769
+ background: var(--bg-elev);
1537
2770
  border-bottom: 1px solid var(--line);
1538
- backdrop-filter: blur(10px);
1539
- }
1540
- .brand { display: flex; flex-direction: column; min-width: 0; margin-right: auto; }
1541
- .brand strong { font-size: 15px; letter-spacing: -0.01em; }
1542
- .brand span { color: var(--faint); font-size: 12px; }
2771
+ min-width: 0; max-width: 100%;
2772
+ }
2773
+ .brand {
2774
+ display: flex; flex-flow: row wrap; align-items: center; gap: 14px;
2775
+ min-width: 0; max-width: 100%; margin-right: auto;
2776
+ }
2777
+ .brand-id { display: flex; flex-direction: column; min-width: 0; flex: none; }
2778
+ .brand-id strong { font-size: 15px; letter-spacing: -0.01em; }
2779
+ .brand-id span { color: var(--faint); font-size: 12px; }
2780
+ .vendor {
2781
+ display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
2782
+ min-width: 0; max-width: 100%; font-size: 12px; color: var(--muted);
2783
+ }
2784
+ .vendor[hidden] { display: none; }
2785
+ /* Logos are drawn for a light tile, same as the docs site, so the plate stays white. */
2786
+ .vendor img {
2787
+ width: 16px; height: 16px; padding: 2px; box-sizing: content-box;
2788
+ border-radius: 4px; border: 1px solid var(--line); background: #fff; object-fit: contain;
2789
+ }
2790
+ .vendor-name { color: var(--ink); font-weight: 600; }
2791
+ .vendor-sep { color: var(--faint); }
2792
+ .vendor-link { color: inherit; text-decoration: none; }
2793
+ .vendor-link:hover { color: var(--ink); text-decoration: underline; }
1543
2794
  .controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
1544
2795
  label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
1545
2796
  label.field input, label.field select, .controls input[type="password"] {
@@ -1557,7 +2808,7 @@ var renderAdminDocument = (service) => {
1557
2808
  record or the full nav row would widen the page past the viewport. */
1558
2809
  .layout {
1559
2810
  display: grid; grid-template-columns: var(--nav) minmax(0, 1fr);
1560
- flex: 1; min-height: 0; min-width: 0;
2811
+ flex: 1 0 auto; min-width: 0;
1561
2812
  }
1562
2813
  nav.side {
1563
2814
  border-right: 1px solid var(--line); padding: 12px;
@@ -1572,7 +2823,7 @@ var renderAdminDocument = (service) => {
1572
2823
  main { padding: 20px; max-width: 1100px; width: 100%; min-width: 0; }
1573
2824
  .view h2 { margin: 0 0 4px; font-size: 22px; letter-spacing: -0.02em; }
1574
2825
  .lede { margin: 0 0 16px; color: var(--muted); }
1575
- .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
2826
+ .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr)); gap: 12px; }
1576
2827
  .card {
1577
2828
  background: var(--bg-elev); border: 1px solid var(--line); border-radius: var(--radius);
1578
2829
  padding: 14px 16px; box-shadow: var(--shadow);
@@ -1611,8 +2862,8 @@ var renderAdminDocument = (service) => {
1611
2862
  .banner.show { display: block; }
1612
2863
  .empty { color: var(--muted); padding: 24px 8px; }
1613
2864
  dialog {
1614
- border: 1px solid var(--line); border-radius: 16px; padding: 0; background: var(--bg-elev);
1615
- color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow: var(--shadow);
2865
+ border: 1px solid var(--line); border-radius: var(--radius); padding: 0; background: var(--bg-elev);
2866
+ color: var(--ink); width: min(640px, calc(100% - 24px)); max-width: calc(100vw - 24px); box-shadow: none;
1616
2867
  }
1617
2868
  dialog::backdrop { background: rgb(20 19 17 / 0.45); }
1618
2869
  dialog form, .dialog-body { padding: 16px; display: flex; flex-direction: column; gap: 10px; }
@@ -1639,7 +2890,7 @@ var renderAdminDocument = (service) => {
1639
2890
  padding-top: max(10px, env(safe-area-inset-top));
1640
2891
  }
1641
2892
  input, select, textarea { font-size: 16px; }
1642
- .controls { width: 100%; }
2893
+ .brand, .vendor, .controls { width: 100%; }
1643
2894
  label.field, label.field select, label.field input { flex: 1; min-width: 0; }
1644
2895
  .layout { grid-template-columns: minmax(0, 1fr); }
1645
2896
  nav.side {
@@ -1670,8 +2921,11 @@ var renderAdminDocument = (service) => {
1670
2921
  <div class="app" data-service="${name}" data-mockingbird-admin>
1671
2922
  <header class="top">
1672
2923
  <div class="brand">
1673
- <strong>${name}</strong>
1674
- <span>Mockingbird admin</span>
2924
+ <div class="brand-id">
2925
+ <strong>${name}</strong>
2926
+ <span>Admin</span>
2927
+ </div>
2928
+ <div class="vendor" id="vendor" hidden></div>
1675
2929
  </div>
1676
2930
  <div class="controls">
1677
2931
  <label class="field">Namespace
@@ -1801,460 +3055,7 @@ var renderAdminDocument = (service) => {
1801
3055
  </div>
1802
3056
  </form>
1803
3057
  </dialog>
1804
- <script>
1805
- (() => {
1806
- const STANDARD = new Set(${standard})
1807
- const state = {
1808
- namespace: "default",
1809
- key: sessionStorage.getItem("mockingbird-admin-key") || "",
1810
- view: "overview",
1811
- collection: "",
1812
- shape: null,
1813
- route: null,
1814
- editing: null
1815
- }
1816
- const $ = (id) => document.getElementById(id)
1817
- const banner = $("banner")
1818
- const keyInput = $("admin-key")
1819
- keyInput.value = state.key
1820
- const params = new URLSearchParams(location.search)
1821
- if (params.get("key")) {
1822
- state.key = params.get("key")
1823
- keyInput.value = state.key
1824
- sessionStorage.setItem("mockingbird-admin-key", state.key)
1825
- params.delete("key")
1826
- const next = params.toString()
1827
- history.replaceState(null, "", location.pathname + (next ? "?" + next : ""))
1828
- }
1829
- if (params.get("namespace")) state.namespace = params.get("namespace")
1830
-
1831
- const showError = (error) => {
1832
- banner.textContent = error instanceof Error ? error.message : String(error)
1833
- banner.classList.add("show")
1834
- }
1835
- const clearError = () => banner.classList.remove("show")
1836
-
1837
- async function originGet(path) {
1838
- const response = await fetch(path, { headers: { accept: "application/json" } })
1839
- if (!response.ok) throw new Error(response.status + " " + path)
1840
- return response.json()
1841
- }
1842
- async function api(method, path, body) {
1843
- const headers = { accept: "application/json" }
1844
- if (state.key) headers["${ADMIN_KEY_HEADER}"] = state.key
1845
- if (state.namespace) headers["x-mockingbird-namespace"] = state.namespace
1846
- if (body !== undefined) headers["content-type"] = "application/json"
1847
- const response = await fetch("${ADMIN_PREFIX}" + path, {
1848
- method,
1849
- headers,
1850
- body: body === undefined ? undefined : JSON.stringify(body)
1851
- })
1852
- const text = await response.text()
1853
- let data = null
1854
- if (text) {
1855
- try { data = JSON.parse(text) } catch { data = { raw: text } }
1856
- }
1857
- if (response.status === 401) {
1858
- keyInput.focus()
1859
- throw new Error((data && data.error && data.error.message) || "Admin key required")
1860
- }
1861
- if (!response.ok) {
1862
- throw new Error((data && data.error && data.error.message) || (response.status + " " + method + " " + path))
1863
- }
1864
- return data
1865
- }
1866
- const panelApi = {
1867
- get: (path) => api("GET", path),
1868
- send: (method, path, body) => api(method, path, body),
1869
- namespace: () => state.namespace
1870
- }
1871
-
1872
- function setTheme(theme) {
1873
- if (theme) document.documentElement.dataset.theme = theme
1874
- else delete document.documentElement.dataset.theme
1875
- }
1876
- const storedTheme = localStorage.getItem("mockingbird-admin-theme")
1877
- if (storedTheme) setTheme(storedTheme)
1878
- $("theme").addEventListener("click", () => {
1879
- const current = document.documentElement.dataset.theme
1880
- const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches
1881
- const next = dark ? "light" : "dark"
1882
- localStorage.setItem("mockingbird-admin-theme", next)
1883
- setTheme(next)
1884
- })
1885
-
1886
- function show(view) {
1887
- state.view = view
1888
- for (const button of document.querySelectorAll("#nav button")) {
1889
- button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false")
1890
- }
1891
- for (const section of document.querySelectorAll("main .view")) {
1892
- section.hidden = section.id !== "view-" + view
1893
- }
1894
- }
1895
- document.getElementById("nav").addEventListener("click", (event) => {
1896
- const button = event.target.closest("button[data-view]")
1897
- if (!button) return
1898
- show(button.dataset.view)
1899
- refresh().catch(showError)
1900
- })
1901
-
1902
- function cards(items) {
1903
- return '<div class="grid">' + items.map((item) =>
1904
- '<article class="card"><div class="k">' + item.k + '</div><div class="v">' + item.v + '</div></article>'
1905
- ).join("") + "</div>"
1906
- }
1907
- function table(columns, rows) {
1908
- if (rows.length === 0) return '<p class="empty">Nothing here yet.</p>'
1909
- const head = "<tr>" + columns.map((column) => "<th>" + column.label + "</th>").join("") + "</tr>"
1910
- const body = rows.map((row) => "<tr" + (row.attrs || "") + ">" + columns.map((column) =>
1911
- '<td data-label="' + column.label + '">' + column.cell(row) + "</td>"
1912
- ).join("") + "</tr>").join("")
1913
- return "<table><thead>" + head + "</thead><tbody>" + body + "</tbody></table>"
1914
- }
1915
- const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({
1916
- "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"
1917
- }[char]))
1918
- const preview = (value) => {
1919
- const text = JSON.stringify(value)
1920
- return esc(text.length > 180 ? text.slice(0, 180) + "\u2026" : text)
1921
- }
1922
- const when = (epoch) => {
1923
- const date = new Date(epoch)
1924
- return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString()
1925
- }
1926
- const clockCard = (epoch) => {
1927
- const date = new Date(epoch)
1928
- if (Number.isNaN(date.getTime())) return esc(String(epoch))
1929
- const iso = date.toISOString()
1930
- return esc(iso.slice(0, 10)) + '<span class="clock-time">' + esc(iso.slice(11, 19)) + " UTC</span>"
1931
- }
1932
-
1933
- async function loadNamespaces() {
1934
- const data = await api("GET", "/namespaces")
1935
- const select = $("namespace")
1936
- const names = data.namespaces && data.namespaces.length ? data.namespaces : [data.default || "default"]
1937
- if (!names.includes(state.namespace)) names.unshift(state.namespace)
1938
- select.innerHTML = names.map((name) =>
1939
- '<option value="' + esc(name) + '">' + esc(name) + "</option>"
1940
- ).join("")
1941
- select.value = state.namespace
1942
- }
1943
-
1944
- async function renderOverview() {
1945
- const [health, shape, clock, journal] = await Promise.all([
1946
- originGet("/health"),
1947
- api("GET", "/state"),
1948
- api("GET", "/clock"),
1949
- api("GET", "/requests?limit=8")
1950
- ])
1951
- state.shape = shape
1952
- $("overview-cards").innerHTML = cards([
1953
- { k: "Service", v: esc(health.service || "${name}") },
1954
- { k: "Status", v: esc(health.status || "ok") },
1955
- { k: "Namespace", v: esc(state.namespace) },
1956
- { k: "Collections", v: String(shape.collections.length) },
1957
- { k: "Records", v: String(shape.collections.reduce((sum, item) => sum + item.count, 0)) },
1958
- { k: "Clock", v: clockCard(clock.now) }
1959
- ])
1960
- const requests = (journal && journal.requests) || []
1961
- $("overview-requests").innerHTML = table(
1962
- [
1963
- { label: "Method", cell: (row) => esc(row.method) },
1964
- { label: "Path", cell: (row) => esc(row.path) },
1965
- { label: "Status", cell: (row) => '<span class="status-' + String(row.status).charAt(0) + '">' + esc(row.status) + "</span>" },
1966
- { label: "Operation", cell: (row) => esc(row.operationId || "") }
1967
- ],
1968
- requests
1969
- )
1970
- }
1971
-
1972
- function schemaChips(collection) {
1973
- if (!collection || collection.fields.length === 0) {
1974
- return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>'
1975
- }
1976
- return '<div class="chips">' + collection.fields.map((field) =>
1977
- '<span class="chip">' + esc(field.name) + ' <i>' + esc(field.kind) + (field.optional ? "?" : "") + "</i></span>"
1978
- ).join("") + "</div>"
1979
- }
1980
-
1981
- async function renderState() {
1982
- const shape = await api("GET", "/state")
1983
- state.shape = shape
1984
- const host = $("collections")
1985
- if (shape.collections.length === 0) {
1986
- host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>'
1987
- $("schema").innerHTML = ""
1988
- $("records").innerHTML = ""
1989
- return
1990
- }
1991
- if (!shape.collections.some((item) => item.name === state.collection)) {
1992
- state.collection = shape.collections[0].name
1993
- }
1994
- host.innerHTML = shape.collections.map((item) =>
1995
- '<button type="button" class="collection" data-collection="' + esc(item.name) + '" aria-current="' +
1996
- (item.name === state.collection ? "true" : "false") + '"><strong>' + esc(item.label) +
1997
- '</strong><small>' + esc(item.count) + " \xB7 " + esc(item.source) + "</small></button>"
1998
- ).join("")
1999
- const selected = shape.collections.find((item) => item.name === state.collection)
2000
- $("schema").innerHTML = "<h3>" + esc(selected.label) + "</h3>" +
2001
- (selected.description ? "<p class=" + '"lede">' + esc(selected.description) + "</p>" : "") +
2002
- schemaChips(selected)
2003
- const page = await api("GET", "/state/" + encodeURIComponent(state.collection) + "?limit=50")
2004
- const rows = page.records.map((record) => ({
2005
- ...record,
2006
- attrs: ' class="clickable" data-id="' + esc(record.id) + '"'
2007
- }))
2008
- $("records").innerHTML = table(
2009
- [
2010
- { label: "Id", cell: (row) => "<code>" + esc(row.id) + "</code>" },
2011
- { label: "Seq", cell: (row) => esc(row.seq) },
2012
- { label: "Value", cell: (row) => preview(row.value) }
2013
- ],
2014
- rows
2015
- )
2016
- }
2017
-
2018
- async function renderClock() {
2019
- const clock = await api("GET", "/clock")
2020
- $("clock-readout").innerHTML = "<div class='k'>Now</div><div class='v'>" + esc(when(clock.now)) +
2021
- "</div><p>" + esc(clock.now) + " ms \xB7 offset " + esc(clock.offsetMs) + " ms " +
2022
- (clock.frozen ? "<span class='badge warn'>frozen</span>" : "<span class='badge ok'>live</span>") + "</p>"
2023
- $("freeze").textContent = clock.frozen ? "Unfreeze" : "Freeze"
2024
- $("freeze").dataset.frozen = clock.frozen ? "1" : "0"
2025
- }
2026
-
2027
- async function renderFaults() {
2028
- const [faults, presets] = await Promise.all([
2029
- api("GET", "/faults"),
2030
- api("GET", "/faults/presets").catch(() => ({ presets: [] }))
2031
- ])
2032
- const select = $("fault-preset")
2033
- const current = select.value
2034
- const list = presets.presets || []
2035
- select.innerHTML = '<option value="">Custom rule</option>' + list.map((preset) =>
2036
- '<option value="' + esc(preset.name) + '">' + esc(preset.name) + "</option>"
2037
- ).join("")
2038
- select.value = current
2039
- const rules = faults.faults || []
2040
- $("fault-list").innerHTML = table(
2041
- [
2042
- { label: "Id", cell: (row) => esc(row.id) },
2043
- { label: "Match", cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter(Boolean).join(" ") || "all") },
2044
- { label: "Effect", cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || "")) }
2045
- ],
2046
- rules
2047
- )
2048
- }
2049
-
2050
- async function renderJournal() {
2051
- const journal = await api("GET", "/requests?limit=100")
2052
- $("journal").innerHTML = table(
2053
- [
2054
- { label: "When", cell: (row) => esc(row.at || "") },
2055
- { label: "Method", cell: (row) => esc(row.method) },
2056
- { label: "Path", cell: (row) => esc(row.path) },
2057
- { label: "Status", cell: (row) => esc(row.status) },
2058
- { label: "ms", cell: (row) => esc(row.durationMs) }
2059
- ],
2060
- journal.requests || []
2061
- )
2062
- }
2063
-
2064
- async function renderRoutes() {
2065
- const data = await api("GET", "/")
2066
- const routes = data.routes || []
2067
- $("route-list").innerHTML = routes.map((route) => {
2068
- const extra = STANDARD.has(route) ? "" : " <span class='badge'>extension</span>"
2069
- return '<div class="route"><span class="method">' + esc(route.split(" ")[0]) +
2070
- '</span><span>' + esc(route.split(" ").slice(1).join(" ")) + extra +
2071
- '</span><button class="btn" type="button" data-route="' + esc(route) + '">Use</button></div>'
2072
- }).join("")
2073
- }
2074
-
2075
- const renderers = {
2076
- overview: renderOverview,
2077
- state: renderState,
2078
- clock: renderClock,
2079
- faults: renderFaults,
2080
- journal: renderJournal,
2081
- routes: renderRoutes
2082
- }
2083
-
2084
- async function refresh() {
2085
- clearError()
2086
- if (state.view in renderers) await renderers[state.view]()
2087
- }
2088
-
2089
- $("namespace").addEventListener("change", () => {
2090
- state.namespace = $("namespace").value
2091
- refresh().catch(showError)
2092
- })
2093
- keyInput.addEventListener("change", () => {
2094
- state.key = keyInput.value.trim()
2095
- sessionStorage.setItem("mockingbird-admin-key", state.key)
2096
- refresh().catch(showError)
2097
- })
2098
- $("refresh").addEventListener("click", () => refresh().catch(showError))
2099
-
2100
- $("collections").addEventListener("click", (event) => {
2101
- const button = event.target.closest("[data-collection]")
2102
- if (!button) return
2103
- state.collection = button.dataset.collection
2104
- renderState().catch(showError)
2105
- })
2106
- $("records").addEventListener("click", (event) => {
2107
- const row = event.target.closest("[data-id]")
2108
- if (!row) return
2109
- openEditor(row.dataset.id).catch(showError)
2110
- })
2111
-
2112
- const editor = $("editor")
2113
- async function openEditor(id) {
2114
- state.editing = id || null
2115
- $("editor-title").textContent = id ? "Edit record" : "New record"
2116
- $("editor-id").value = id || ""
2117
- $("editor-id").readOnly = Boolean(id)
2118
- $("editor-delete").hidden = !id
2119
- if (id) {
2120
- const record = await api("GET", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
2121
- $("editor-value").value = JSON.stringify(record.value, null, 2)
2122
- } else {
2123
- $("editor-value").value = "{\\n \\n}"
2124
- }
2125
- editor.showModal()
2126
- }
2127
- $("new-record").addEventListener("click", () => openEditor(null).catch(showError))
2128
- $("editor-form").addEventListener("submit", (event) => {
2129
- const submitter = event.submitter
2130
- if (!submitter || submitter.value !== "save") return
2131
- event.preventDefault()
2132
- let value
2133
- try { value = JSON.parse($("editor-value").value) }
2134
- catch { showError(new Error("Value is not JSON")); return }
2135
- const id = $("editor-id").value.trim()
2136
- const path = "/state/" + encodeURIComponent(state.collection)
2137
- const request = state.editing
2138
- ? api("PUT", path + "/" + encodeURIComponent(id), { value })
2139
- : api("POST", path, { id: id || undefined, value })
2140
- request.then(() => { editor.close(); return renderState() }).catch(showError)
2141
- })
2142
- $("editor-delete").addEventListener("click", () => {
2143
- const id = state.editing
2144
- if (!id) return
2145
- api("DELETE", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
2146
- .then(() => { editor.close(); return renderState() })
2147
- .catch(showError)
2148
- })
2149
-
2150
- document.querySelectorAll("[data-advance]").forEach((button) => {
2151
- button.addEventListener("click", () => {
2152
- api("POST", "/clock", { advance: Number(button.dataset.advance) }).then(renderClock).catch(showError)
2153
- })
2154
- })
2155
- $("freeze").addEventListener("click", () => {
2156
- const frozen = $("freeze").dataset.frozen === "1"
2157
- api("POST", "/clock", { freeze: !frozen }).then(renderClock).catch(showError)
2158
- })
2159
- $("clock-reset").addEventListener("click", () => {
2160
- api("POST", "/clock", { reset: true }).then(renderClock).catch(showError)
2161
- })
2162
- $("clock-set").addEventListener("submit", (event) => {
2163
- event.preventDefault()
2164
- const raw = $("clock-instant").value.trim()
2165
- const set = /^-?\\d+$/.test(raw) ? Number(raw) : raw
2166
- api("POST", "/clock", { set }).then(renderClock).catch(showError)
2167
- })
2168
-
2169
- $("fault-form").addEventListener("submit", (event) => {
2170
- event.preventDefault()
2171
- const preset = $("fault-preset").value
2172
- const body = preset
2173
- ? { preset }
2174
- : {
2175
- status: Number($("fault-status").value),
2176
- ...($("fault-path").value ? { pathPrefix: $("fault-path").value } : {}),
2177
- ...($("fault-method").value ? { method: $("fault-method").value } : {})
2178
- }
2179
- if (!preset && !Number.isFinite(body.status)) {
2180
- showError(new Error("Status must be a number, or choose a preset"))
2181
- return
2182
- }
2183
- api("POST", "/faults", body).then(renderFaults).catch(showError)
2184
- })
2185
- $("fault-clear").addEventListener("click", () => api("DELETE", "/faults").then(renderFaults).catch(showError))
2186
- $("journal-clear").addEventListener("click", () => api("DELETE", "/requests").then(renderJournal).catch(showError))
2187
-
2188
- $("route-list").addEventListener("click", (event) => {
2189
- const button = event.target.closest("[data-route]")
2190
- if (!button) return
2191
- state.route = button.dataset.route
2192
- $("route-title").textContent = state.route
2193
- const method = state.route.split(" ")[0]
2194
- $("route-body").disabled = method === "GET" || method === "DELETE"
2195
- })
2196
- $("route-form").addEventListener("submit", async (event) => {
2197
- event.preventDefault()
2198
- if (!state.route) return showError(new Error("Choose a route first"))
2199
- const [method, path] = [state.route.split(" ")[0], state.route.split(" ").slice(1).join(" ")]
2200
- const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_, key) => prompt("Value for " + key) || "")
2201
- let body
2202
- if (method !== "GET" && method !== "DELETE" && $("route-body").value.trim()) {
2203
- try { body = JSON.parse($("route-body").value) }
2204
- catch { return showError(new Error("Body is not JSON")) }
2205
- }
2206
- try {
2207
- const data = await api(method, concrete, body)
2208
- $("route-result").textContent = JSON.stringify(data, null, 2)
2209
- } catch (error) {
2210
- showError(error)
2211
- }
2212
- })
2213
-
2214
- function mountPanels(panels) {
2215
- const nav = $("nav")
2216
- const views = $("views")
2217
- for (const panel of panels) {
2218
- const button = document.createElement("button")
2219
- button.type = "button"
2220
- button.dataset.view = panel.id
2221
- button.textContent = panel.title
2222
- nav.append(button)
2223
- const section = document.createElement("section")
2224
- section.className = "view"
2225
- section.id = "view-" + panel.id
2226
- section.hidden = true
2227
- const title = document.createElement("h2")
2228
- title.textContent = panel.title
2229
- const lede = document.createElement("p")
2230
- lede.className = "lede"
2231
- lede.textContent = panel.description || ""
2232
- const body = document.createElement("div")
2233
- body.className = "panel-body"
2234
- body.id = "panel-" + panel.id
2235
- body.innerHTML = panel.html || ""
2236
- section.append(title, lede, body)
2237
- views.append(section)
2238
- if (panel.script) {
2239
- try {
2240
- const run = new Function("root", "api", panel.script)
2241
- run(body, panelApi)
2242
- } catch (error) {
2243
- const note = document.createElement("p")
2244
- note.className = "banner show"
2245
- note.textContent = error instanceof Error ? error.message : String(error)
2246
- body.append(note)
2247
- }
2248
- }
2249
- }
2250
- }
2251
-
2252
- loadNamespaces()
2253
- .then(() => api("GET", "/ui/manifest"))
2254
- .then((manifest) => { mountPanels((manifest && manifest.panels) || []); return refresh() })
2255
- .catch(showError)
2256
- })()
2257
- </script>
3058
+ ${script}
2258
3059
  </body>
2259
3060
  </html>`;
2260
3061
  };
@@ -2271,10 +3072,10 @@ var json2 = (body) => new Response(JSON.stringify(body), {
2271
3072
  });
2272
3073
  var adminUiRoutes = (service, ui) => {
2273
3074
  const shell = () => renderAdminDocument(service);
2274
- const document2 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
3075
+ const document3 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
2275
3076
  return {
2276
- "GET /ui": () => htmlResponse(document2()),
2277
- "GET /ui/": () => htmlResponse(document2()),
3077
+ "GET /ui": () => htmlResponse(document3()),
3078
+ "GET /ui/": () => htmlResponse(document3()),
2278
3079
  "GET /ui/manifest": () => json2({
2279
3080
  service,
2280
3081
  panels: (ui?.panels ?? []).map((panel) => ({
@@ -2300,11 +3101,11 @@ var EXTENSION_KEYS = {
2300
3101
  };
2301
3102
 
2302
3103
  // ../../openapi/metadata/dist/read.js
2303
- var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3104
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2304
3105
  var extensionOf = (holder, key) => holder[key];
2305
3106
  var operationMetadata = (operation) => {
2306
3107
  const raw = extensionOf(operation, EXTENSION_KEYS.operation);
2307
- const ext = isRecord2(raw) ? raw : {};
3108
+ const ext = isRecord3(raw) ? raw : {};
2308
3109
  const supported = ext.supported ?? true;
2309
3110
  const parity = ext.parity ?? {};
2310
3111
  return {
@@ -2414,9 +3215,9 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
2414
3215
  this.name = "OperationRegistryError";
2415
3216
  }
2416
3217
  };
2417
- var verifyOperations = (document2, handlers) => {
3218
+ var verifyOperations = (document3, handlers) => {
2418
3219
  const problems = [];
2419
- const operations = listOperations(document2);
3220
+ const operations = listOperations(document3);
2420
3221
  const seen = /* @__PURE__ */ new Set();
2421
3222
  for (const operation of operations) {
2422
3223
  if (seen.has(operation.operationId))
@@ -2532,7 +3333,7 @@ var SAMPLE = 25;
2532
3333
  var WALK_LIMIT = 1e4;
2533
3334
  var SKIP_CTORS = /* @__PURE__ */ new Set(["Hono", "HonoRequest", "Router"]);
2534
3335
  var fail2 = (status, message) => ({ ok: false, status, message });
2535
- var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3336
+ var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2536
3337
  var kindOf = (value) => {
2537
3338
  if (value === null)
2538
3339
  return "null";
@@ -2631,7 +3432,7 @@ var absorb = (accum, value) => {
2631
3432
  const kind = kindOf(value);
2632
3433
  accum.kinds.add(kind);
2633
3434
  accum.present++;
2634
- if (!isRecord3(value))
3435
+ if (!isRecord4(value))
2635
3436
  return;
2636
3437
  for (const [key, child] of Object.entries(value)) {
2637
3438
  const nested = accum.nested.get(key) ?? blankAccum(key);
@@ -2656,7 +3457,7 @@ var inferFields = (records) => {
2656
3457
  if (records.length === 0)
2657
3458
  return [];
2658
3459
  const sample = records.slice(0, SAMPLE);
2659
- const objects = sample.filter((record) => isRecord3(record.value));
3460
+ const objects = sample.filter((record) => isRecord4(record.value));
2660
3461
  if (objects.length === 0) {
2661
3462
  const kinds = new Set(sample.map((record) => kindOf(record.value)));
2662
3463
  const list = [...kinds];
@@ -2843,7 +3644,7 @@ var patchStateRecord = (scope, name, id, value) => {
2843
3644
  const parsed = jsonValue(value);
2844
3645
  if (!parsed.ok)
2845
3646
  return parsed;
2846
- const next = isRecord3(current.value.value) && isRecord3(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
3647
+ const next = isRecord4(current.value.value) && isRecord4(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
2847
3648
  return replaceStateRecord(scope, name, id, next);
2848
3649
  };
2849
3650
  var deleteStateRecord = (scope, name, id) => {
@@ -2911,7 +3712,7 @@ var stateAdminRoutes = (routes) => {
2911
3712
  const scope = open(namespace);
2912
3713
  if (!scope.ok)
2913
3714
  return fromResult(scope, 201);
2914
- if (!isRecord3(body) || !("value" in body)) {
3715
+ if (!isRecord4(body) || !("value" in body)) {
2915
3716
  return adminError2(400, 'expected {"id?": "<id>", "value": <json>}');
2916
3717
  }
2917
3718
  const id = body.id;
@@ -2929,7 +3730,7 @@ var stateAdminRoutes = (routes) => {
2929
3730
  const scope = open(namespace);
2930
3731
  if (!scope.ok)
2931
3732
  return fromResult(scope, 200);
2932
- if (!isRecord3(body) || !("value" in body))
3733
+ if (!isRecord4(body) || !("value" in body))
2933
3734
  return adminError2(400, 'expected {"value": <json>}');
2934
3735
  const replaced = replaceStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
2935
3736
  if (replaced.ok)
@@ -2940,7 +3741,7 @@ var stateAdminRoutes = (routes) => {
2940
3741
  const scope = open(namespace);
2941
3742
  if (!scope.ok)
2942
3743
  return fromResult(scope, 200);
2943
- if (!isRecord3(body) || !("value" in body))
3744
+ if (!isRecord4(body) || !("value" in body))
2944
3745
  return adminError2(400, 'expected {"value": <json>}');
2945
3746
  const patched = patchStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
2946
3747
  if (patched.ok)
@@ -2960,7 +3761,7 @@ var stateAdminRoutes = (routes) => {
2960
3761
  };
2961
3762
 
2962
3763
  // ../core/dist/version.js
2963
- var PACKAGE_VERSION = true ? "1.4.0" : UNRELEASED_VERSION;
3764
+ var PACKAGE_VERSION = true ? "1.5.1" : UNRELEASED_VERSION;
2964
3765
 
2965
3766
  // ../core/dist/signing.js
2966
3767
  var encoder = new TextEncoder();
@@ -3297,9 +4098,9 @@ var createWebhookHub = (options) => {
3297
4098
  };
3298
4099
  var json4 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
3299
4100
  var adminError3 = (status, message) => json4(status, { error: { type: "mockingbird_admin", message } });
3300
- var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4101
+ var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3301
4102
  var parseEndpoint = (value) => {
3302
- if (!isRecord4(value) || typeof value.url !== "string")
4103
+ if (!isRecord5(value) || typeof value.url !== "string")
3303
4104
  return "each endpoint needs a url";
3304
4105
  try {
3305
4106
  new URL(value.url);
@@ -3316,12 +4117,12 @@ var parseEndpoint = (value) => {
3316
4117
  const events = value.events ?? value.enabledEvents;
3317
4118
  if (Array.isArray(events))
3318
4119
  endpoint.events = events.map(String);
3319
- if (isRecord4(value.tags)) {
4120
+ if (isRecord5(value.tags)) {
3320
4121
  endpoint.tags = Object.fromEntries(Object.entries(value.tags).map(([k, v]) => [k, String(v)]));
3321
4122
  }
3322
4123
  if (typeof value.account === "string")
3323
4124
  endpoint.tags = { ...endpoint.tags, account: value.account };
3324
- if (isRecord4(value.headers)) {
4125
+ if (isRecord5(value.headers)) {
3325
4126
  endpoint.headers = Object.fromEntries(Object.entries(value.headers).map(([k, v]) => [k, String(v)]));
3326
4127
  }
3327
4128
  return endpoint;
@@ -3348,7 +4149,7 @@ var webhookAdminRoutes = (hub) => ({
3348
4149
  return json4(200, { status: "ok" });
3349
4150
  },
3350
4151
  "POST /webhooks/faults": ({ body, namespace }) => {
3351
- if (!isRecord4(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
4152
+ if (!isRecord5(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
3352
4153
  return adminError3(400, "mode must be duplicate, reorder or drop");
3353
4154
  }
3354
4155
  const fault = { mode: body.mode };
@@ -3364,7 +4165,7 @@ var webhookAdminRoutes = (hub) => ({
3364
4165
  }))
3365
4166
  }),
3366
4167
  "PUT /webhook-endpoints": ({ body, namespace }) => {
3367
- const list = Array.isArray(body) ? body : isRecord4(body) ? body.endpoints : void 0;
4168
+ const list = Array.isArray(body) ? body : isRecord5(body) ? body.endpoints : void 0;
3368
4169
  if (!Array.isArray(list))
3369
4170
  return adminError3(400, "expected [{url, secret?, events?}]");
3370
4171
  const parsed = [];
@@ -3434,8 +4235,8 @@ var DroppedConnectionError = class extends TypeError {
3434
4235
  this.name = "TypeError";
3435
4236
  }
3436
4237
  };
3437
- var operationMatcher = (document2) => {
3438
- const matchers = listOperations(document2).map((operation) => ({
4238
+ var operationMatcher = (document3) => {
4239
+ const matchers = listOperations(document3).map((operation) => ({
3439
4240
  operationId: operation.operationId,
3440
4241
  method: operation.method.toUpperCase(),
3441
4242
  pattern: new RegExp(`^${operation.path.split("/").map((segment) => segment.startsWith("{") ? "[^/]+" : segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("/")}/?$`),
@@ -3530,10 +4331,10 @@ var createRuntime = (options) => {
3530
4331
  const ensureBranch = (namespace, branch2, at) => {
3531
4332
  if (!BRANCH_PATTERN2.test(branch2))
3532
4333
  throw new RangeError(`branch must match ${BRANCH_PATTERN2}`);
3533
- const history = timeline(namespace);
4334
+ const history2 = timeline(namespace);
3534
4335
  if (branch2 === "main") {
3535
4336
  if (at !== void 0) {
3536
- const point = history.checkout("main", at);
4337
+ const point = history2.checkout("main", at);
3537
4338
  restoreNamespace(sqlite, storageNamespace(namespace), point.value.snapshot);
3538
4339
  captured.set(namespace, point.value.snapshot);
3539
4340
  rng.setState(point.value.rngState);
@@ -3546,10 +4347,10 @@ var createRuntime = (options) => {
3546
4347
  return namespace;
3547
4348
  }
3548
4349
  const storage = physicalBranch(namespace, branch2);
3549
- if (!history.hasBranch(branch2)) {
4350
+ if (!history2.hasBranch(branch2)) {
3550
4351
  if (at === void 0)
3551
- history.commit(capture(namespace));
3552
- const point = history.fork(branch2, at === void 0 ? {} : { from: at });
4352
+ history2.commit(capture(namespace));
4353
+ const point = history2.fork(branch2, at === void 0 ? {} : { from: at });
3553
4354
  const branchRng = createRng(options.seed ?? 0);
3554
4355
  if (point)
3555
4356
  branchRng.setState(point.value.rngState);
@@ -3558,8 +4359,8 @@ var createRuntime = (options) => {
3558
4359
  restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
3559
4360
  if (point)
3560
4361
  captured.set(storage, point.value.snapshot);
3561
- } else if (at !== void 0 && history.head(branch2)?.id !== at) {
3562
- const point = history.checkout(branch2, at);
4362
+ } else if (at !== void 0 && history2.head(branch2)?.id !== at) {
4363
+ const point = history2.checkout(branch2, at);
3563
4364
  if (!instances.has(storage)) {
3564
4365
  const branchRng = createRng(options.seed ?? 0);
3565
4366
  branchRng.setState(point.value.rngState);
@@ -3570,7 +4371,7 @@ var createRuntime = (options) => {
3570
4371
  captured.set(storage, point.value.snapshot);
3571
4372
  } else {
3572
4373
  if (!instances.has(storage)) {
3573
- const point = history.head(branch2);
4374
+ const point = history2.head(branch2);
3574
4375
  const branchRng = createRng(options.seed ?? 0);
3575
4376
  if (point)
3576
4377
  branchRng.setState(point.value.rngState);
@@ -3594,8 +4395,8 @@ var createRuntime = (options) => {
3594
4395
  const checkout = (checkpointId, checkoutOptions = {}) => {
3595
4396
  const namespace = checkoutOptions.namespace ?? DEFAULT_NAMESPACE;
3596
4397
  const branchName = checkoutOptions.branch ?? "main";
3597
- const history = timeline(namespace);
3598
- const point = history.checkout(branchName, checkpointId);
4398
+ const history2 = timeline(namespace);
4399
+ const point = history2.checkout(branchName, checkpointId);
3599
4400
  const storage = ensureBranch(namespace, branchName);
3600
4401
  restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
3601
4402
  captured.set(storage, point.value.snapshot);
@@ -3645,9 +4446,9 @@ var createRuntime = (options) => {
3645
4446
  instance(name);
3646
4447
  restoreNamespace(sqlite, storageNamespace(name), from);
3647
4448
  captured.set(name, from);
3648
- const history = timelines.get(name);
3649
- if (history)
3650
- history.commit(capture(name), { branch: "main" });
4449
+ const history2 = timelines.get(name);
4450
+ if (history2)
4451
+ history2.commit(capture(name), { branch: "main" });
3651
4452
  else
3652
4453
  timeline(name);
3653
4454
  };
@@ -3876,10 +4677,10 @@ var createRuntime = (options) => {
3876
4677
  },
3877
4678
  release: (name, checkpointId) => timeline(name).release(checkpointId),
3878
4679
  inspect: (name) => {
3879
- const history = timeline(name);
4680
+ const history2 = timeline(name);
3880
4681
  return {
3881
- branches: history.branches(),
3882
- checkpoints: history.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
4682
+ branches: history2.branches(),
4683
+ checkpoints: history2.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
3883
4684
  id,
3884
4685
  branch: branchName,
3885
4686
  parent,
@@ -3894,11 +4695,11 @@ var createRuntime = (options) => {
3894
4695
  } : {},
3895
4696
  routes: {
3896
4697
  ...credentialRoutes(credentials),
3897
- ...options.presets ? presetRoutes(options.presets, runtime) : {},
3898
4698
  ...options.webhooks ? webhookAdminRoutes(options.webhooks) : {},
3899
4699
  ...options.admin?.(runtime) ?? {},
3900
- // Standard state and UI routes win a colliding key. A service adds its own keys
3901
- // beside these; it does not replace `/state` or `/ui`.
4700
+ // Shell routes win a colliding key. Fault presets are one of them: the admin page
4701
+ // maps `presets`, and a service route must not replace that list with a record.
4702
+ ...options.presets ? presetRoutes(options.presets, runtime) : {},
3902
4703
  ...stateAdminRoutes({
3903
4704
  open: stateScope,
3904
4705
  afterWrite: (namespace) => {
@@ -3973,9 +4774,12 @@ var credentialRoutes = (registry) => ({
3973
4774
  }
3974
4775
  });
3975
4776
  var presetRoutes = (presets, runtime) => ({
3976
- "GET /faults/presets": () => adminJson(200, {
3977
- presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
3978
- }),
4777
+ "GET /faults/presets": () => {
4778
+ const body = {
4779
+ presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
4780
+ };
4781
+ return adminJson(200, body);
4782
+ },
3979
4783
  "POST /faults/presets/:name": ({ params, body, namespace }) => {
3980
4784
  const name = params.name;
3981
4785
  if (!presets[name])
@@ -3986,7 +4790,7 @@ var presetRoutes = (presets, runtime) => ({
3986
4790
  });
3987
4791
 
3988
4792
  // src/generated/openapi.ts
3989
- var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Fullscript API (Mockingbird subset)","description":"Stateful mock subset of the Fullscript API for lab ordering: per-practitioner OAuth (authorize, token with authorization_code and refresh_token grants, revoke), clinic, embeddable session grants, lab orders, lab-order events, expiring result PDFs, and signed webhooks. Hand-authored from the EMR consumer (fullscript-api-client.ts, fullscript-event-model.ts, fullscript-webhook-signature.ts).\\n","version":"1","x-mockingbird-upstream":{"note":"Wire shapes from the consumer app's EMR backend (its Fullscript services and routers); the vendor's API reference is behind a partner login."}},"servers":[{"url":"https://api-us-snd.fullscript.io"}],"security":[{"bearerAuth":[]}],"paths":{"/api/oauth/token":{"post":{"operationId":"OAuthToken","description":"Exchange an authorization code, or rotate a refresh token (the old refresh token stops working).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthTokenResponse"}}}},"400":{"description":"invalid_grant / unsupported_grant_type / invalid_request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"401":{"description":"invalid_client","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenRequest"}}}}}},"/api/oauth/revoke":{"post":{"operationId":"OAuthRevoke","description":"Revoke an access or refresh token (RFC 7009: always 200).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"200":{"description":"Revoked","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"invalid_request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"401":{"description":"invalid_client","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevokeRequest"}}}}}},"/api/clinic":{"get":{"operationId":"GetClinic","description":"The token's clinic.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Clinic","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClinicResponse"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}}}},"/api/clinic/embeddable/session_grants":{"post":{"operationId":"CreateSessionGrant","description":"A short-lived secret for the embedded Fullscript widget.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"201":{"description":"Grant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionGrant"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}}}},"/api/clinic/labs/orders":{"get":{"operationId":"ListLabOrders","description":"The clinic's lab orders (optionally one patient's), paged.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LabOrdersPage"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"parameters":[{"name":"patient_id","in":"query","required":false,"schema":{"type":"string","enum":["pat_seed_1","pat_seed_2","pat_missing"]}},{"name":"page[number]","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":3}},{"name":"page[size]","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}}]}},"/api/clinic/labs/orders/{orderId}":{"get":{"operationId":"GetLabOrder","description":"One lab order with tests, per-test results and the aggregated result.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LabOrderResponse"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"404":{"description":"Unknown order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"parameters":[{"name":"orderId","in":"path","required":true,"schema":{"type":"string","enum":["lo_seed_1","lo_seed_2","lo_seed_3","lo_missing"]}}]}},"/api/events/lab_orders":{"get":{"operationId":"ListLabOrderEvents","description":"The clinic's lab_order.updated events, newest first by default.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventsPage"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"parameters":[{"name":"order_by","in":"query","required":false,"schema":{"type":"string","enum":["DESC","ASC"]}},{"name":"page[number]","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":3}},{"name":"page[size]","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}}]}},"/api/events/{eventId}":{"get":{"operationId":"GetEvent","description":"One event with its data.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Event","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventResponse"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"404":{"description":"Unknown event","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"parameters":[{"name":"eventId","in":"path","required":true,"schema":{"type":"string","enum":["evt_1","evt_2","evt_3","evt_missing"]}}]}},"/oauth/authorize":{"get":{"operationId":"Authorize","description":"The consent page: auto-approves as the requested (or default) practitioner and redirects to redirect_uri with code and state.","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":false,"reason":"Browser redirect flow; covered by acceptance tests."}},"parameters":[{"name":"client_id","in":"query","required":true,"schema":{"type":"string"}},{"name":"redirect_uri","in":"query","required":true,"schema":{"type":"string"}},{"name":"response_type","in":"query","required":true,"schema":{"type":"string","enum":["code"]}},{"name":"state","in":"query","required":false,"schema":{"type":"string"}},{"name":"scope","in":"query","required":false,"schema":{"type":"string"}},{"name":"practitioner_id","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"302":{"description":"Redirect back with ?code=&state="},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}}}}},"/results/{artifactId}":{"get":{"operationId":"GetResultPdf","description":"An expiring result PDF URL (what pdf_url points at).","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":false,"reason":"Binary PDF behind an expiring signed URL; covered by acceptance tests."}},"parameters":[{"name":"artifactId","in":"path","required":true,"schema":{"type":"string"}},{"name":"expires","in":"query","required":true,"schema":{"type":"string"}},{"name":"signature","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The PDF","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"403":{"description":"Expired or bad signature"},"404":{"description":"Unknown artifact"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"TokenRequest":{"type":"object","required":["grant_type","client_id","client_secret"],"properties":{"grant_type":{"type":"string","enum":["authorization_code","refresh_token"]},"client_id":{"type":"string","minLength":1,"maxLength":64,"examples":["parity-client"]},"client_secret":{"type":"string","minLength":1,"maxLength":64,"examples":["parity-secret"]},"code":{"type":"string","maxLength":64},"refresh_token":{"type":"string","maxLength":200},"redirect_uri":{"type":"string","maxLength":200,"examples":["https://emr.example.com/v1/fullscript/oauth/callback"]}}},"RevokeRequest":{"type":"object","required":["client_id","client_secret","token"],"properties":{"client_id":{"type":"string","minLength":1,"maxLength":64},"client_secret":{"type":"string","minLength":1,"maxLength":64},"token":{"type":"string","minLength":1,"maxLength":200}}},"OAuthTokenResponse":{"type":"object","required":["oauth"],"properties":{"oauth":{"type":"object","required":["access_token","token_type","expires_in","refresh_token","scope","created_at","resource_owner"],"properties":{"access_token":{"type":"string","x-mockingbird-volatile":{"kind":"token"}},"token_type":{"type":"string","enum":["Bearer"]},"expires_in":{"type":"integer"},"refresh_token":{"type":"string","x-mockingbird-volatile":{"kind":"token"}},"scope":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"resource_owner":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string"},"type":{"type":"string","enum":["Practitioner","Staff"]},"clinic_id":{"type":"string"}}}}}}},"OAuthError":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"error_description":{"type":"string"}}},"ApiErrors":{"type":"object","required":["errors"],"properties":{"errors":{"type":"array","items":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}},"ClinicResponse":{"type":"object","required":["clinic"],"properties":{"clinic":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}}}},"SessionGrant":{"type":"object","required":["secret_token","expires_at"],"properties":{"secret_token":{"type":"string","x-mockingbird-volatile":{"kind":"token"}},"expires_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"LabOrderSummary":{"type":"object","required":["id","state","treatment_plan_id","patient_id","created_at","updated_at"],"properties":{"id":{"type":"string"},"state":{"type":"string","enum":["not_purchased","purchased","schedule_appointment","upcoming_appointment","processing","partial_results","results_ready","interpretation_shared","results_amended"]},"treatment_plan_id":{"type":["string","null"]},"patient_id":{"type":"string"},"name":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"LabOrdersPage":{"type":"object","required":["orders","meta"],"properties":{"orders":{"type":"array","items":{"$ref":"#/components/schemas/LabOrderSummary"}},"meta":{"type":"object","required":["current_page","next_page","prev_page","total_pages","total_count"],"properties":{"current_page":{"type":"integer"},"next_page":{"type":["integer","null"]},"prev_page":{"type":["integer","null"]},"total_pages":{"type":"integer"},"total_count":{"type":"integer"}}}}},"LabOrderResponse":{"type":"object","required":["order"],"properties":{"order":{"type":"object","required":["id","state","treatment_plan_id","tests","results","latest_aggregated_result"],"properties":{"id":{"type":"string"},"state":{"type":"string","enum":["not_purchased","purchased","schedule_appointment","upcoming_appointment","processing","partial_results","results_ready","interpretation_shared","results_amended"]},"treatment_plan_id":{"type":["string","null"]},"patient_id":{"type":"string"},"name":{"type":"string"},"collection_method":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"tests":{"type":"array","items":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"lab_type":{"type":"string"}}}},"results":{"type":"array","items":{"type":"object","required":["id","name","state","pdf_url"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"state":{"type":"string"},"pdf_url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}}}}},"latest_aggregated_result":{"type":["object","null"],"properties":{"id":{"type":"string"},"artifact_id":{"type":"string"},"pdf_url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}},"status":{"type":"string"}}}}}}},"EventSummary":{"type":"object","required":["id","type","clinic_id","created_at"],"properties":{"id":{"type":"string"},"type":{"type":"string","enum":["lab_order.updated"]},"clinic_id":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"EventsPage":{"type":"object","required":["events","meta"],"properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/EventSummary"}},"meta":{"type":"object","required":["current_page","next_page","prev_page","total_pages","total_count"],"properties":{"current_page":{"type":"integer"},"next_page":{"type":["integer","null"]},"prev_page":{"type":["integer","null"]},"total_pages":{"type":"integer"},"total_count":{"type":"integer"}}}}},"EventResponse":{"type":"object","required":["event"],"properties":{"event":{"type":"object","required":["id","type","clinic_id","created_at","data"],"properties":{"id":{"type":"string"},"type":{"type":"string"},"clinic_id":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"data":{"type":"object"}}}}}}}}`);
4793
+ var document2 = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Fullscript API (Mockingbird subset)","description":"Stateful mock subset of the Fullscript API for lab ordering: per-practitioner OAuth (authorize, token with authorization_code and refresh_token grants, revoke), clinic, embeddable session grants, lab orders, lab-order events, expiring result PDFs, and signed webhooks. Hand-authored from the EMR consumer (fullscript-api-client.ts, fullscript-event-model.ts, fullscript-webhook-signature.ts).\\n","version":"1","x-mockingbird-upstream":{"note":"Wire shapes from the consumer app's EMR backend (its Fullscript services and routers); the vendor's API reference is behind a partner login."}},"servers":[{"url":"https://api-us-snd.fullscript.io"}],"security":[{"bearerAuth":[]}],"paths":{"/api/oauth/token":{"post":{"operationId":"OAuthToken","description":"Exchange an authorization code, or rotate a refresh token (the old refresh token stops working).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthTokenResponse"}}}},"400":{"description":"invalid_grant / unsupported_grant_type / invalid_request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"401":{"description":"invalid_client","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenRequest"}}}}}},"/api/oauth/revoke":{"post":{"operationId":"OAuthRevoke","description":"Revoke an access or refresh token (RFC 7009: always 200).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"200":{"description":"Revoked","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"invalid_request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"401":{"description":"invalid_client","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevokeRequest"}}}}}},"/api/clinic":{"get":{"operationId":"GetClinic","description":"The token's clinic.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Clinic","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClinicResponse"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}}}},"/api/clinic/embeddable/session_grants":{"post":{"operationId":"CreateSessionGrant","description":"A short-lived secret for the embedded Fullscript widget.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"201":{"description":"Grant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionGrant"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}}}},"/api/clinic/labs/orders":{"get":{"operationId":"ListLabOrders","description":"The clinic's lab orders (optionally one patient's), paged.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LabOrdersPage"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"parameters":[{"name":"patient_id","in":"query","required":false,"schema":{"type":"string","enum":["pat_seed_1","pat_seed_2","pat_missing"]}},{"name":"page[number]","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":3}},{"name":"page[size]","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}}]}},"/api/clinic/labs/orders/{orderId}":{"get":{"operationId":"GetLabOrder","description":"One lab order with tests, per-test results and the aggregated result.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LabOrderResponse"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"404":{"description":"Unknown order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"parameters":[{"name":"orderId","in":"path","required":true,"schema":{"type":"string","enum":["lo_seed_1","lo_seed_2","lo_seed_3","lo_missing"]}}]}},"/api/events/lab_orders":{"get":{"operationId":"ListLabOrderEvents","description":"The clinic's lab_order.updated events, newest first by default.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventsPage"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"parameters":[{"name":"order_by","in":"query","required":false,"schema":{"type":"string","enum":["DESC","ASC"]}},{"name":"page[number]","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":3}},{"name":"page[size]","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}}]}},"/api/events/{eventId}":{"get":{"operationId":"GetEvent","description":"One event with its data.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Event","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventResponse"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"404":{"description":"Unknown event","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"parameters":[{"name":"eventId","in":"path","required":true,"schema":{"type":"string","enum":["evt_1","evt_2","evt_3","evt_missing"]}}]}},"/oauth/authorize":{"get":{"operationId":"Authorize","description":"The consent page: auto-approves as the requested (or default) practitioner and redirects to redirect_uri with code and state.","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":false,"reason":"Browser redirect flow; covered by acceptance tests."}},"parameters":[{"name":"client_id","in":"query","required":true,"schema":{"type":"string"}},{"name":"redirect_uri","in":"query","required":true,"schema":{"type":"string"}},{"name":"response_type","in":"query","required":true,"schema":{"type":"string","enum":["code"]}},{"name":"state","in":"query","required":false,"schema":{"type":"string"}},{"name":"scope","in":"query","required":false,"schema":{"type":"string"}},{"name":"practitioner_id","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"302":{"description":"Redirect back with ?code=&state="},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}}}}},"/results/{artifactId}":{"get":{"operationId":"GetResultPdf","description":"An expiring result PDF URL (what pdf_url points at).","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":false,"reason":"Binary PDF behind an expiring signed URL; covered by acceptance tests."}},"parameters":[{"name":"artifactId","in":"path","required":true,"schema":{"type":"string"}},{"name":"expires","in":"query","required":true,"schema":{"type":"string"}},{"name":"signature","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The PDF","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"403":{"description":"Expired or bad signature"},"404":{"description":"Unknown artifact"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"TokenRequest":{"type":"object","required":["grant_type","client_id","client_secret"],"properties":{"grant_type":{"type":"string","enum":["authorization_code","refresh_token"]},"client_id":{"type":"string","minLength":1,"maxLength":64,"examples":["parity-client"]},"client_secret":{"type":"string","minLength":1,"maxLength":64,"examples":["parity-secret"]},"code":{"type":"string","maxLength":64},"refresh_token":{"type":"string","maxLength":200},"redirect_uri":{"type":"string","maxLength":200,"examples":["https://emr.example.com/v1/fullscript/oauth/callback"]}}},"RevokeRequest":{"type":"object","required":["client_id","client_secret","token"],"properties":{"client_id":{"type":"string","minLength":1,"maxLength":64},"client_secret":{"type":"string","minLength":1,"maxLength":64},"token":{"type":"string","minLength":1,"maxLength":200}}},"OAuthTokenResponse":{"type":"object","required":["oauth"],"properties":{"oauth":{"type":"object","required":["access_token","token_type","expires_in","refresh_token","scope","created_at","resource_owner"],"properties":{"access_token":{"type":"string","x-mockingbird-volatile":{"kind":"token"}},"token_type":{"type":"string","enum":["Bearer"]},"expires_in":{"type":"integer"},"refresh_token":{"type":"string","x-mockingbird-volatile":{"kind":"token"}},"scope":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"resource_owner":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string"},"type":{"type":"string","enum":["Practitioner","Staff"]},"clinic_id":{"type":"string"}}}}}}},"OAuthError":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"error_description":{"type":"string"}}},"ApiErrors":{"type":"object","required":["errors"],"properties":{"errors":{"type":"array","items":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}},"ClinicResponse":{"type":"object","required":["clinic"],"properties":{"clinic":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}}}},"SessionGrant":{"type":"object","required":["secret_token","expires_at"],"properties":{"secret_token":{"type":"string","x-mockingbird-volatile":{"kind":"token"}},"expires_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"LabOrderSummary":{"type":"object","required":["id","state","treatment_plan_id","patient_id","created_at","updated_at"],"properties":{"id":{"type":"string"},"state":{"type":"string","enum":["not_purchased","purchased","schedule_appointment","upcoming_appointment","processing","partial_results","results_ready","interpretation_shared","results_amended"]},"treatment_plan_id":{"type":["string","null"]},"patient_id":{"type":"string"},"name":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"LabOrdersPage":{"type":"object","required":["orders","meta"],"properties":{"orders":{"type":"array","items":{"$ref":"#/components/schemas/LabOrderSummary"}},"meta":{"type":"object","required":["current_page","next_page","prev_page","total_pages","total_count"],"properties":{"current_page":{"type":"integer"},"next_page":{"type":["integer","null"]},"prev_page":{"type":["integer","null"]},"total_pages":{"type":"integer"},"total_count":{"type":"integer"}}}}},"LabOrderResponse":{"type":"object","required":["order"],"properties":{"order":{"type":"object","required":["id","state","treatment_plan_id","tests","results","latest_aggregated_result"],"properties":{"id":{"type":"string"},"state":{"type":"string","enum":["not_purchased","purchased","schedule_appointment","upcoming_appointment","processing","partial_results","results_ready","interpretation_shared","results_amended"]},"treatment_plan_id":{"type":["string","null"]},"patient_id":{"type":"string"},"name":{"type":"string"},"collection_method":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"tests":{"type":"array","items":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"lab_type":{"type":"string"}}}},"results":{"type":"array","items":{"type":"object","required":["id","name","state","pdf_url"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"state":{"type":"string"},"pdf_url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}}}}},"latest_aggregated_result":{"type":["object","null"],"properties":{"id":{"type":"string"},"artifact_id":{"type":"string"},"pdf_url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}},"status":{"type":"string"}}}}}}},"EventSummary":{"type":"object","required":["id","type","clinic_id","created_at"],"properties":{"id":{"type":"string"},"type":{"type":"string","enum":["lab_order.updated"]},"clinic_id":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"EventsPage":{"type":"object","required":["events","meta"],"properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/EventSummary"}},"meta":{"type":"object","required":["current_page","next_page","prev_page","total_pages","total_count"],"properties":{"current_page":{"type":"integer"},"next_page":{"type":["integer","null"]},"prev_page":{"type":["integer","null"]},"total_pages":{"type":"integer"},"total_count":{"type":"integer"}}}}},"EventResponse":{"type":"object","required":["event"],"properties":{"event":{"type":"object","required":["id","type","clinic_id","created_at","data"],"properties":{"id":{"type":"string"},"type":{"type":"string"},"clinic_id":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"data":{"type":"object"}}}}}}}}`);
3990
4794
  var operationIds = ["OAuthToken", "OAuthRevoke", "GetClinic", "CreateSessionGrant", "ListLabOrders", "GetLabOrder", "ListLabOrderEvents", "GetEvent", "Authorize", "GetResultPdf"];
3991
4795
  var supportedOperationIds = ["OAuthToken", "OAuthRevoke", "GetClinic", "CreateSessionGrant", "ListLabOrders", "GetLabOrder", "ListLabOrderEvents", "GetEvent", "Authorize", "GetResultPdf"];
3992
4796
 
@@ -4173,7 +4977,7 @@ var FULLSCRIPT_PRESETS = {
4173
4977
  };
4174
4978
  var json5 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
4175
4979
  var adminError4 = (status, message) => json5(status, { error: { type: "mockingbird_admin", message } });
4176
- var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4980
+ var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4177
4981
  var echoes = async (response, challenge) => {
4178
4982
  try {
4179
4983
  const body = await response.clone().json();
@@ -4184,7 +4988,7 @@ var echoes = async (response, challenge) => {
4184
4988
  };
4185
4989
  var adminRoutes = (send, challenge) => (runtime) => ({
4186
4990
  "POST /oauth/codes": ({ body, namespace }) => {
4187
- const input = isRecord5(body) ? body : {};
4991
+ const input = isRecord6(body) ? body : {};
4188
4992
  const api = runtime.instance(namespace);
4189
4993
  const practitionerId = typeof input.practitionerId === "string" ? input.practitionerId : "prac_mock_1";
4190
4994
  if (!api.state.practitioners.get(practitionerId))
@@ -4197,7 +5001,7 @@ var adminRoutes = (send, challenge) => (runtime) => ({
4197
5001
  return json5(201, { code });
4198
5002
  },
4199
5003
  "POST /practitioners": ({ body, namespace }) => {
4200
- if (!isRecord5(body) || typeof body.id !== "string" || typeof body.clinicId !== "string") {
5004
+ if (!isRecord6(body) || typeof body.id !== "string" || typeof body.clinicId !== "string") {
4201
5005
  return adminError4(
4202
5006
  400,
4203
5007
  'expected {"id", "clinicId", "type"?: "Practitioner"|"Staff", "clinicName"?}'
@@ -4221,7 +5025,7 @@ var adminRoutes = (send, challenge) => (runtime) => ({
4221
5025
  },
4222
5026
  "GET /lab-orders": ({ namespace }) => json5(200, { orders: runtime.instance(namespace).labOrders() }),
4223
5027
  "POST /lab-orders": ({ body, namespace }) => {
4224
- if (!isRecord5(body) || typeof body.patientId !== "string") {
5028
+ if (!isRecord6(body) || typeof body.patientId !== "string") {
4225
5029
  return adminError4(
4226
5030
  400,
4227
5031
  'expected {"patientId", "id"?, "clinicId"?, "treatmentPlanId"?, "name"?, "collectionMethod"?, "tests"?: [names]}'
@@ -4240,7 +5044,7 @@ var adminRoutes = (send, challenge) => (runtime) => ({
4240
5044
  return json5(201, order);
4241
5045
  },
4242
5046
  "POST /lab-orders/:id/transition": ({ params, body, namespace }) => {
4243
- if (!isRecord5(body) || !isLabOrderState(body.to)) {
5047
+ if (!isRecord6(body) || !isLabOrderState(body.to)) {
4244
5048
  return adminError4(
4245
5049
  400,
4246
5050
  'expected {"to": "<purchased|schedule_appointment|upcoming_appointment|processing|partial_results|results_ready|interpretation_shared|results_amended>"}'
@@ -4283,7 +5087,7 @@ var adminRoutes = (send, challenge) => (runtime) => ({
4283
5087
  },
4284
5088
  "GET /settings": ({ namespace }) => json5(200, runtime.instance(namespace).state.current()),
4285
5089
  "PUT /settings": ({ body, namespace }) => {
4286
- if (!isRecord5(body)) return adminError4(400, "expected a JSON object");
5090
+ if (!isRecord6(body)) return adminError4(400, "expected a JSON object");
4287
5091
  const patch = {};
4288
5092
  for (const key of ["accessTokenTtlSeconds", "codeTtlSeconds", "pdfUrlTtlSeconds"]) {
4289
5093
  if (body[key] !== void 0) {
@@ -4299,7 +5103,7 @@ var adminRoutes = (send, challenge) => (runtime) => ({
4299
5103
  if (body.clients !== void 0) {
4300
5104
  if (!Array.isArray(body.clients))
4301
5105
  return adminError4(400, "clients: [{clientId, clientSecret}]");
4302
- patch.clients = body.clients.filter(isRecord5).map((c) => ({ clientId: String(c.clientId), clientSecret: String(c.clientSecret) }));
5106
+ patch.clients = body.clients.filter(isRecord6).map((c) => ({ clientId: String(c.clientId), clientSecret: String(c.clientSecret) }));
4303
5107
  }
4304
5108
  return json5(200, runtime.instance(namespace).state.update(patch));
4305
5109
  }
@@ -4321,7 +5125,7 @@ var createRuntime2 = (options = {}) => {
4321
5125
  });
4322
5126
  const runtime = createRuntime({
4323
5127
  name: FULLSCRIPT_NAMESPACE,
4324
- document,
5128
+ document: document2,
4325
5129
  ...options.sqlite ? { sqlite: options.sqlite } : {},
4326
5130
  ...options.clock ? { clock: options.clock } : {},
4327
5131
  ...options.seed !== void 0 ? { seed: options.seed } : {},
@@ -4512,7 +5316,7 @@ var FullscriptAPI = class {
4512
5316
  GetResultPdf: (context) => this.pdf(context)
4513
5317
  });
4514
5318
  this.service = createService({
4515
- document,
5319
+ document: document2,
4516
5320
  handlers,
4517
5321
  sqlite,
4518
5322
  namespace,
@@ -4679,11 +5483,11 @@ var FullscriptAPI = class {
4679
5483
  return oauthError(400, "invalid_request", `Unknown practitioner ${practitionerId}.`);
4680
5484
  }
4681
5485
  const code = this.issueCode(practitionerId, clientId, redirect);
4682
- const location = new URL(redirect);
4683
- location.searchParams.set("code", code);
5486
+ const location2 = new URL(redirect);
5487
+ location2.searchParams.set("code", code);
4684
5488
  const state = url.searchParams.get("state");
4685
- if (state !== null) location.searchParams.set("state", state);
4686
- return new Response(null, { status: 302, headers: { location: location.toString() } });
5489
+ if (state !== null) location2.searchParams.set("state", state);
5490
+ return new Response(null, { status: 302, headers: { location: location2.toString() } });
4687
5491
  }
4688
5492
  summary(order) {
4689
5493
  return {
@@ -4863,7 +5667,7 @@ var FullscriptAPI = class {
4863
5667
  };
4864
5668
 
4865
5669
  export {
4866
- document,
5670
+ document2 as document,
4867
5671
  operationIds,
4868
5672
  supportedOperationIds,
4869
5673
  RESULT_PDF,
@@ -4883,4 +5687,4 @@ export {
4883
5687
  envelope,
4884
5688
  FullscriptAPI
4885
5689
  };
4886
- //# sourceMappingURL=chunk-FLCK7ECX.js.map
5690
+ //# sourceMappingURL=chunk-OBQHBBFC.js.map