@crvouga/mockingbird-service-posthog 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -540,10 +540,10 @@ var OpenAPIReferenceError = class extends Error {
540
540
  };
541
541
  var unescapePointer = (segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~");
542
542
  var isReference = (value) => typeof value === "object" && value !== null && typeof value.$ref === "string";
543
- var resolveRef = (document2, ref) => {
543
+ var resolveRef = (document3, ref) => {
544
544
  if (!ref.startsWith("#/"))
545
545
  throw new OpenAPIReferenceError(ref);
546
- let cursor = document2;
546
+ let cursor = document3;
547
547
  for (const raw of ref.slice(2).split("/")) {
548
548
  const segment = unescapePointer(raw);
549
549
  if (typeof cursor !== "object" || cursor === null || !(segment in cursor)) {
@@ -555,14 +555,14 @@ var resolveRef = (document2, ref) => {
555
555
  throw new OpenAPIReferenceError(ref);
556
556
  return cursor;
557
557
  };
558
- var deref = (document2, value) => {
558
+ var deref = (document3, value) => {
559
559
  let current = value;
560
560
  const seen = /* @__PURE__ */ new Set();
561
561
  while (isReference(current)) {
562
562
  if (seen.has(current.$ref))
563
563
  throw new OpenAPIReferenceError(`${current.$ref} (cycle)`);
564
564
  seen.add(current.$ref);
565
- current = resolveRef(document2, current.$ref);
565
+ current = resolveRef(document3, current.$ref);
566
566
  }
567
567
  return current;
568
568
  };
@@ -580,36 +580,36 @@ var HTTP_METHODS = [
580
580
  ];
581
581
 
582
582
  // ../../openapi/core/dist/document.js
583
- var mergeParameters = (document2, item, own) => {
583
+ var mergeParameters = (document3, item, own) => {
584
584
  const merged = /* @__PURE__ */ new Map();
585
585
  for (const raw of item.parameters ?? []) {
586
- const parameter = deref(document2, raw);
586
+ const parameter = deref(document3, raw);
587
587
  merged.set(`${parameter.in}:${parameter.name}`, parameter);
588
588
  }
589
589
  for (const raw of own ?? []) {
590
- const parameter = deref(document2, raw);
590
+ const parameter = deref(document3, raw);
591
591
  merged.set(`${parameter.in}:${parameter.name}`, parameter);
592
592
  }
593
593
  return [...merged.values()];
594
594
  };
595
- var listOperations = (document2) => {
595
+ var listOperations = (document3) => {
596
596
  const operations = [];
597
- for (const [path, item] of Object.entries(document2.paths)) {
597
+ for (const [path, item] of Object.entries(document3.paths)) {
598
598
  for (const method of HTTP_METHODS) {
599
599
  const operation = item[method];
600
600
  if (operation?.operationId === void 0)
601
601
  continue;
602
602
  const responses = {};
603
603
  for (const [status, response] of Object.entries(operation.responses)) {
604
- responses[status] = deref(document2, response);
604
+ responses[status] = deref(document3, response);
605
605
  }
606
606
  operations.push({
607
607
  operationId: operation.operationId,
608
608
  method,
609
609
  path,
610
610
  operation,
611
- parameters: mergeParameters(document2, item, operation.parameters),
612
- requestBody: operation.requestBody === void 0 ? void 0 : deref(document2, operation.requestBody),
611
+ parameters: mergeParameters(document3, item, operation.parameters),
612
+ requestBody: operation.requestBody === void 0 ? void 0 : deref(document3, operation.requestBody),
613
613
  responses
614
614
  });
615
615
  }
@@ -1153,6 +1153,1232 @@ var clearNamespace = (sqlite, namespace) => {
1153
1153
  });
1154
1154
  };
1155
1155
 
1156
+ // ../../ui/dist/reset.js
1157
+ var CSS_RESET = `*,
1158
+ *::before,
1159
+ *::after {
1160
+ box-sizing: border-box;
1161
+ margin: 0;
1162
+ padding: 0;
1163
+ }
1164
+
1165
+ * {
1166
+ scrollbar-width: thin;
1167
+ scrollbar-color: color-mix(in srgb, CanvasText 35%, Canvas) Canvas;
1168
+ }
1169
+
1170
+ *::-webkit-scrollbar {
1171
+ width: 12px;
1172
+ height: 12px;
1173
+ }
1174
+
1175
+ *::-webkit-scrollbar-track,
1176
+ *::-webkit-scrollbar-corner {
1177
+ background: Canvas;
1178
+ }
1179
+
1180
+ *::-webkit-scrollbar-thumb {
1181
+ background: color-mix(in srgb, CanvasText 35%, Canvas);
1182
+ border: 3px solid Canvas;
1183
+ border-radius: 999px;
1184
+ }
1185
+
1186
+ html {
1187
+ hanging-punctuation: first allow-end last;
1188
+ -webkit-text-size-adjust: none;
1189
+ text-size-adjust: none;
1190
+ tab-size: 4;
1191
+ font-family:
1192
+ ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI",
1193
+ "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
1194
+ font-size: 16px;
1195
+ line-height: 1.5;
1196
+ -webkit-font-smoothing: antialiased;
1197
+ -moz-osx-font-smoothing: grayscale;
1198
+ text-rendering: optimizeLegibility;
1199
+ -webkit-tap-highlight-color: transparent;
1200
+ scroll-behavior: smooth;
1201
+ background: Canvas;
1202
+ color: CanvasText;
1203
+ }
1204
+
1205
+ body {
1206
+ min-height: 100svh;
1207
+ line-height: 1.5;
1208
+ background: Canvas;
1209
+ color: CanvasText;
1210
+ }
1211
+
1212
+ h1 {
1213
+ font-size: 2em;
1214
+ }
1215
+
1216
+ h2 {
1217
+ font-size: 1.5em;
1218
+ }
1219
+
1220
+ h3 {
1221
+ font-size: 1.17em;
1222
+ }
1223
+
1224
+ h4 {
1225
+ font-size: 1em;
1226
+ }
1227
+
1228
+ h5 {
1229
+ font-size: 0.83em;
1230
+ }
1231
+
1232
+ h6 {
1233
+ font-size: 0.67em;
1234
+ }
1235
+
1236
+ h1,
1237
+ h2,
1238
+ h3,
1239
+ h4,
1240
+ h5,
1241
+ h6 {
1242
+ font-weight: 700;
1243
+ line-height: 1.2;
1244
+ text-wrap: balance;
1245
+ overflow-wrap: break-word;
1246
+ }
1247
+
1248
+ p,
1249
+ li,
1250
+ figcaption,
1251
+ dd,
1252
+ dt,
1253
+ blockquote {
1254
+ overflow-wrap: break-word;
1255
+ }
1256
+
1257
+ p,
1258
+ li,
1259
+ figcaption,
1260
+ blockquote {
1261
+ text-wrap: pretty;
1262
+ }
1263
+
1264
+ ul,
1265
+ ol,
1266
+ menu {
1267
+ list-style: none;
1268
+ }
1269
+
1270
+ a {
1271
+ color: inherit;
1272
+ text-decoration: underline;
1273
+ text-decoration-thickness: 1px;
1274
+ text-underline-offset: 0.15em;
1275
+ text-decoration-skip-ink: auto;
1276
+ }
1277
+
1278
+ b,
1279
+ strong {
1280
+ font-weight: 700;
1281
+ }
1282
+
1283
+ small {
1284
+ font-size: 0.875em;
1285
+ }
1286
+
1287
+ code,
1288
+ kbd,
1289
+ samp,
1290
+ pre {
1291
+ font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
1292
+ font-size: 1em;
1293
+ }
1294
+
1295
+ sub,
1296
+ sup {
1297
+ font-size: 75%;
1298
+ line-height: 0;
1299
+ position: relative;
1300
+ vertical-align: baseline;
1301
+ }
1302
+
1303
+ sub {
1304
+ bottom: -0.25em;
1305
+ }
1306
+
1307
+ sup {
1308
+ top: -0.5em;
1309
+ }
1310
+
1311
+ abbr[title] {
1312
+ text-decoration: underline dotted;
1313
+ text-underline-offset: 0.15em;
1314
+ }
1315
+
1316
+ mark {
1317
+ background: Mark;
1318
+ color: MarkText;
1319
+ }
1320
+
1321
+ hr {
1322
+ height: 0;
1323
+ border: 0;
1324
+ border-top: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
1325
+ color: inherit;
1326
+ }
1327
+
1328
+ img,
1329
+ picture,
1330
+ video,
1331
+ canvas {
1332
+ display: block;
1333
+ max-width: 100%;
1334
+ }
1335
+
1336
+ img,
1337
+ video {
1338
+ height: auto;
1339
+ }
1340
+
1341
+ svg {
1342
+ overflow: hidden;
1343
+ }
1344
+
1345
+ iframe {
1346
+ border: 0;
1347
+ max-width: 100%;
1348
+ }
1349
+
1350
+ table {
1351
+ border-collapse: collapse;
1352
+ border-spacing: 0;
1353
+ }
1354
+
1355
+ fieldset {
1356
+ border: 0;
1357
+ min-inline-size: 0;
1358
+ }
1359
+
1360
+ legend {
1361
+ padding: 0;
1362
+ }
1363
+
1364
+ button,
1365
+ input,
1366
+ select,
1367
+ textarea,
1368
+ optgroup {
1369
+ font: inherit;
1370
+ letter-spacing: inherit;
1371
+ color: inherit;
1372
+ margin: 0;
1373
+ vertical-align: middle;
1374
+ }
1375
+
1376
+ button,
1377
+ input[type="button"],
1378
+ input[type="submit"],
1379
+ input[type="reset"] {
1380
+ appearance: none;
1381
+ background: none;
1382
+ border: 0;
1383
+ padding: 0;
1384
+ cursor: pointer;
1385
+ text-align: center;
1386
+ }
1387
+
1388
+ input,
1389
+ textarea,
1390
+ select {
1391
+ background-color: Canvas;
1392
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1393
+ border-radius: 0;
1394
+ padding: 0.5em 0.65em;
1395
+ }
1396
+
1397
+ input[type="checkbox"],
1398
+ input[type="radio"],
1399
+ input[type="range"],
1400
+ input[type="file"],
1401
+ input[type="color"],
1402
+ input[type="button"],
1403
+ input[type="submit"],
1404
+ input[type="reset"],
1405
+ input[type="image"],
1406
+ input[type="hidden"] {
1407
+ background: none;
1408
+ border: 0;
1409
+ padding: 0;
1410
+ border-radius: 0;
1411
+ }
1412
+
1413
+ input[type="checkbox"],
1414
+ input[type="radio"] {
1415
+ accent-color: CanvasText;
1416
+ }
1417
+
1418
+ input[type="search"] {
1419
+ appearance: none;
1420
+ background-color: Canvas;
1421
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1422
+ }
1423
+
1424
+ input[type="search"]::-webkit-search-decoration,
1425
+ input[type="search"]::-webkit-search-cancel-button,
1426
+ input[type="search"]::-webkit-search-results-button,
1427
+ input[type="search"]::-webkit-search-results-decoration {
1428
+ -webkit-appearance: none;
1429
+ display: none;
1430
+ }
1431
+
1432
+ input[type="number"] {
1433
+ appearance: textfield;
1434
+ background-color: Canvas;
1435
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1436
+ }
1437
+
1438
+ input[type="number"]::-webkit-inner-spin-button,
1439
+ input[type="number"]::-webkit-outer-spin-button {
1440
+ -webkit-appearance: none;
1441
+ margin: 0;
1442
+ }
1443
+
1444
+ textarea {
1445
+ resize: vertical;
1446
+ }
1447
+
1448
+ ::placeholder {
1449
+ opacity: 1;
1450
+ color: color-mix(in srgb, CanvasText 45%, transparent);
1451
+ }
1452
+
1453
+ :focus-visible {
1454
+ outline: 2px solid CanvasText;
1455
+ outline-offset: 2px;
1456
+ }
1457
+
1458
+ :focus:not(:focus-visible) {
1459
+ outline: none;
1460
+ }
1461
+
1462
+ :disabled,
1463
+ [disabled] {
1464
+ cursor: default;
1465
+ opacity: 0.6;
1466
+ }
1467
+
1468
+ label,
1469
+ summary,
1470
+ select,
1471
+ [role="button"] {
1472
+ cursor: pointer;
1473
+ }
1474
+
1475
+ summary {
1476
+ display: list-item;
1477
+ }
1478
+
1479
+ details {
1480
+ display: block;
1481
+ }
1482
+
1483
+ dialog {
1484
+ margin: auto;
1485
+ padding: 1rem;
1486
+ border: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
1487
+ background: Canvas;
1488
+ color: CanvasText;
1489
+ max-width: calc(100vw - 2rem);
1490
+ max-height: calc(100svh - 2rem);
1491
+ }
1492
+
1493
+ dialog:not([open]) {
1494
+ display: none;
1495
+ }
1496
+
1497
+ ::backdrop {
1498
+ background: color-mix(in srgb, CanvasText 40%, transparent);
1499
+ }
1500
+
1501
+ [hidden] {
1502
+ display: none !important;
1503
+ }
1504
+
1505
+ ::selection {
1506
+ background: Highlight;
1507
+ color: HighlightText;
1508
+ }
1509
+
1510
+ @media (prefers-reduced-motion: reduce) {
1511
+ *,
1512
+ *::before,
1513
+ *::after {
1514
+ animation-duration: 0.01ms !important;
1515
+ animation-iteration-count: 1 !important;
1516
+ transition-duration: 0.01ms !important;
1517
+ scroll-behavior: auto !important;
1518
+ }
1519
+ }
1520
+
1521
+ @media (pointer: coarse) {
1522
+ input,
1523
+ select,
1524
+ textarea {
1525
+ font-size: max(16px, 1em);
1526
+ }
1527
+ }`;
1528
+
1529
+ // ../core/dist/admin-client.js
1530
+ function isRecord2(value) {
1531
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1532
+ }
1533
+ function mountAdminBrand(doc, host, service, catalog) {
1534
+ const clip = (value, max) => {
1535
+ if (typeof value !== "string")
1536
+ return null;
1537
+ const trimmed = value.trim();
1538
+ if (trimmed === "")
1539
+ return null;
1540
+ return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
1541
+ };
1542
+ const httpUrl = (value) => {
1543
+ if (typeof value !== "string" || value.length > 2e3)
1544
+ return null;
1545
+ try {
1546
+ const url = new URL(value);
1547
+ if (url.protocol !== "https:" && url.protocol !== "http:")
1548
+ return null;
1549
+ return url.href;
1550
+ } catch {
1551
+ return null;
1552
+ }
1553
+ };
1554
+ const hexColor = (value) => {
1555
+ if (typeof value !== "string")
1556
+ return null;
1557
+ const raw = value.trim();
1558
+ return /^#[0-9a-fA-F]{3,8}$/.test(raw) ? raw : null;
1559
+ };
1560
+ const hostname = (value) => {
1561
+ try {
1562
+ return new URL(value).hostname.replace(/^www\./, "");
1563
+ } catch {
1564
+ return value;
1565
+ }
1566
+ };
1567
+ host.replaceChildren();
1568
+ host.hidden = true;
1569
+ if (!isRecord2(catalog) || !Object.hasOwn(catalog, service))
1570
+ return;
1571
+ const row = catalog[service];
1572
+ if (!isRecord2(row))
1573
+ return;
1574
+ const vendor = clip(row.vendor, 80);
1575
+ const website = httpUrl(row.website);
1576
+ const guide = httpUrl(row.guide);
1577
+ const docs = httpUrl(row.docs);
1578
+ const logo = httpUrl(row.logo);
1579
+ const color = hexColor(row.color);
1580
+ const description = clip(row.description, 280);
1581
+ if (vendor === null || website === null && guide === null && docs === null)
1582
+ return;
1583
+ const add = (node) => {
1584
+ host.append(node);
1585
+ return node;
1586
+ };
1587
+ if (logo !== null) {
1588
+ const img = doc.createElement("img");
1589
+ img.alt = "";
1590
+ img.src = logo;
1591
+ img.width = 16;
1592
+ img.height = 16;
1593
+ img.decoding = "async";
1594
+ img.referrerPolicy = "no-referrer";
1595
+ if (color !== null)
1596
+ img.style.borderColor = color;
1597
+ add(img);
1598
+ }
1599
+ const name = doc.createElement("span");
1600
+ name.className = "vendor-name";
1601
+ name.textContent = vendor;
1602
+ if (description !== null)
1603
+ name.title = description;
1604
+ add(name);
1605
+ const sep = () => {
1606
+ const node = doc.createElement("span");
1607
+ node.className = "vendor-sep";
1608
+ node.setAttribute("aria-hidden", "true");
1609
+ node.textContent = "\xB7";
1610
+ return node;
1611
+ };
1612
+ const link = (href, label, title) => {
1613
+ const node = doc.createElement("a");
1614
+ node.className = "vendor-link";
1615
+ node.href = href;
1616
+ node.target = "_blank";
1617
+ node.rel = "noopener noreferrer";
1618
+ node.referrerPolicy = "no-referrer";
1619
+ node.textContent = label;
1620
+ node.title = title;
1621
+ return node;
1622
+ };
1623
+ if (website !== null) {
1624
+ add(sep());
1625
+ add(link(website, hostname(website), `${vendor} website`));
1626
+ }
1627
+ if (guide !== null) {
1628
+ add(sep());
1629
+ add(link(guide, "Docs", `Docs for ${vendor}`));
1630
+ }
1631
+ if (docs !== null) {
1632
+ add(sep());
1633
+ add(link(docs, "API", `${vendor} API reference`));
1634
+ }
1635
+ host.removeAttribute("hidden");
1636
+ host.hidden = false;
1637
+ }
1638
+ function startAdminBrand(env) {
1639
+ const requested = env.params.get("brands");
1640
+ let brandsUrl = env.brandsUrl;
1641
+ if (requested) {
1642
+ try {
1643
+ const url = new URL(requested, env.location.href);
1644
+ const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]" || url.origin === env.location.origin;
1645
+ if ((url.protocol === "https:" || url.protocol === "http:") && local)
1646
+ brandsUrl = url.href;
1647
+ } catch {
1648
+ brandsUrl = env.brandsUrl;
1649
+ }
1650
+ }
1651
+ const vendorHost = env.$("vendor");
1652
+ const brandRoot = env.document.querySelector("[data-mockingbird-admin]");
1653
+ const brandService = brandRoot?.getAttribute("data-service") || "";
1654
+ if (vendorHost === null)
1655
+ return;
1656
+ env.fetch(brandsUrl, {
1657
+ headers: { accept: "application/json" },
1658
+ signal: AbortSignal.timeout(5e3)
1659
+ }).then(async (response) => {
1660
+ if (!response.ok)
1661
+ return null;
1662
+ const catalog = await response.json();
1663
+ return catalog;
1664
+ }).then((catalog) => {
1665
+ mountAdminBrand(env.document, vendorHost, brandService, catalog);
1666
+ }).catch(() => {
1667
+ });
1668
+ }
1669
+ function faultPresetList(value) {
1670
+ if (!isRecord2(value))
1671
+ throw new Error("Fault presets response was not an object");
1672
+ if (!Object.hasOwn(value, "presets") || value.presets === void 0 || value.presets === null) {
1673
+ return { presets: [] };
1674
+ }
1675
+ if (!Array.isArray(value.presets))
1676
+ throw new Error("Fault presets response was not a list");
1677
+ return {
1678
+ presets: value.presets.flatMap((item) => {
1679
+ if (!isRecord2(item) || typeof item.name !== "string")
1680
+ return [];
1681
+ return [{ name: item.name }];
1682
+ })
1683
+ };
1684
+ }
1685
+ function bootAdmin(config) {
1686
+ const fieldKinds = ["string", "number", "boolean", "null", "object", "array", "unknown"];
1687
+ const sources = ["declared", "inferred", "mixed"];
1688
+ const isFieldKind = (value) => typeof value === "string" && fieldKinds.some((kind) => kind === value);
1689
+ const isSource = (value) => typeof value === "string" && sources.some((source) => source === value);
1690
+ const readJson2 = (text) => {
1691
+ const value = JSON.parse(text);
1692
+ return value;
1693
+ };
1694
+ const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => {
1695
+ switch (char) {
1696
+ case "&":
1697
+ return "&amp;";
1698
+ case "<":
1699
+ return "&lt;";
1700
+ case ">":
1701
+ return "&gt;";
1702
+ case '"':
1703
+ return "&quot;";
1704
+ default:
1705
+ return "&#39;";
1706
+ }
1707
+ });
1708
+ const el = (id, kind) => {
1709
+ const node = document.getElementById(id);
1710
+ if (!(node instanceof kind))
1711
+ throw new Error(`admin shell is missing #${id}`);
1712
+ return node;
1713
+ };
1714
+ const errorMessage = (data, fallback) => {
1715
+ if (!isRecord2(data))
1716
+ return fallback;
1717
+ const error2 = data.error;
1718
+ if (!isRecord2(error2) || typeof error2.message !== "string" || error2.message === "") {
1719
+ return fallback;
1720
+ }
1721
+ return error2.message;
1722
+ };
1723
+ const requireList = (value, label) => {
1724
+ if (value === void 0 || value === null)
1725
+ return [];
1726
+ if (!Array.isArray(value))
1727
+ throw new Error(`${label} was not a list`);
1728
+ return value;
1729
+ };
1730
+ const state = {
1731
+ namespace: "default",
1732
+ key: sessionStorage.getItem("mockingbird-admin-key") || "",
1733
+ view: "overview",
1734
+ collection: "",
1735
+ shape: null,
1736
+ route: null,
1737
+ editing: null
1738
+ };
1739
+ const standard = new Set(config.standardRoutes);
1740
+ const banner = el("banner", HTMLElement);
1741
+ const keyInput = el("admin-key", HTMLInputElement);
1742
+ keyInput.value = state.key;
1743
+ const params = new URLSearchParams(location.search);
1744
+ const queryKey = params.get("key");
1745
+ if (queryKey) {
1746
+ state.key = queryKey;
1747
+ keyInput.value = state.key;
1748
+ sessionStorage.setItem("mockingbird-admin-key", state.key);
1749
+ params.delete("key");
1750
+ const next = params.toString();
1751
+ history.replaceState(null, "", location.pathname + (next ? `?${next}` : ""));
1752
+ }
1753
+ const queryNamespace = params.get("namespace");
1754
+ if (queryNamespace)
1755
+ state.namespace = queryNamespace;
1756
+ const showError = (error2) => {
1757
+ banner.textContent = error2 instanceof Error ? error2.message : String(error2);
1758
+ banner.classList.add("show");
1759
+ };
1760
+ const clearError = () => {
1761
+ banner.classList.remove("show");
1762
+ };
1763
+ const api = async (method, path, body) => {
1764
+ const headers = { accept: "application/json" };
1765
+ if (state.key !== "")
1766
+ headers[config.adminKeyHeader] = state.key;
1767
+ if (state.namespace !== "")
1768
+ headers["x-mockingbird-namespace"] = state.namespace;
1769
+ if (body !== void 0)
1770
+ headers["content-type"] = "application/json";
1771
+ const init = { method, headers };
1772
+ if (body !== void 0)
1773
+ init.body = JSON.stringify(body);
1774
+ const response = await fetch(config.adminPrefix + path, init);
1775
+ const text = await response.text();
1776
+ let data = null;
1777
+ if (text !== "") {
1778
+ try {
1779
+ data = readJson2(text);
1780
+ } catch {
1781
+ data = { raw: text };
1782
+ }
1783
+ }
1784
+ if (response.status === 401) {
1785
+ keyInput.focus();
1786
+ throw new Error(errorMessage(data, "Admin key required"));
1787
+ }
1788
+ if (!response.ok) {
1789
+ throw new Error(errorMessage(data, `${response.status} ${method} ${path}`));
1790
+ }
1791
+ return data;
1792
+ };
1793
+ const originGet = async (path) => {
1794
+ const response = await fetch(path, { headers: { accept: "application/json" } });
1795
+ if (!response.ok)
1796
+ throw new Error(`${response.status} ${path}`);
1797
+ const value = await response.json();
1798
+ return value;
1799
+ };
1800
+ const panelApi = {
1801
+ get: (path) => api("GET", path),
1802
+ send: (method, path, body) => api(method, path, body),
1803
+ namespace: () => state.namespace
1804
+ };
1805
+ const isPanelScript = (value) => typeof value === "function";
1806
+ const setTheme = (theme) => {
1807
+ if (theme)
1808
+ document.documentElement.dataset.theme = theme;
1809
+ else
1810
+ delete document.documentElement.dataset.theme;
1811
+ };
1812
+ const storedTheme = localStorage.getItem("mockingbird-admin-theme");
1813
+ if (storedTheme)
1814
+ setTheme(storedTheme);
1815
+ el("theme", HTMLButtonElement).addEventListener("click", () => {
1816
+ const current = document.documentElement.dataset.theme;
1817
+ const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
1818
+ const next = dark ? "light" : "dark";
1819
+ localStorage.setItem("mockingbird-admin-theme", next);
1820
+ setTheme(next);
1821
+ });
1822
+ const show = (view) => {
1823
+ state.view = view;
1824
+ for (const button of document.querySelectorAll("#nav button")) {
1825
+ if (!(button instanceof HTMLButtonElement))
1826
+ continue;
1827
+ button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false");
1828
+ }
1829
+ for (const section of document.querySelectorAll("main .view")) {
1830
+ if (!(section instanceof HTMLElement))
1831
+ continue;
1832
+ section.hidden = section.id !== `view-${view}`;
1833
+ }
1834
+ };
1835
+ el("nav", HTMLElement).addEventListener("click", (event) => {
1836
+ const target = event.target;
1837
+ if (!(target instanceof Element))
1838
+ return;
1839
+ const button = target.closest("button[data-view]");
1840
+ if (!(button instanceof HTMLButtonElement))
1841
+ return;
1842
+ const view = button.dataset.view;
1843
+ if (view === void 0)
1844
+ return;
1845
+ show(view);
1846
+ refresh().catch(showError);
1847
+ });
1848
+ 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>`;
1849
+ const table = (columns, rows) => {
1850
+ if (rows.length === 0)
1851
+ return '<p class="empty">Nothing here yet.</p>';
1852
+ const head = `<tr>${columns.map((column) => `<th>${column.label}</th>`).join("")}</tr>`;
1853
+ const body = rows.map((row) => {
1854
+ const cells = columns.map((column) => `<td data-label="${column.label}">${column.cell(row)}</td>`).join("");
1855
+ const attrs = "attrs" in row && typeof row.attrs === "string" ? row.attrs : "";
1856
+ return `<tr${attrs}>${cells}</tr>`;
1857
+ }).join("");
1858
+ return `<table><thead>${head}</thead><tbody>${body}</tbody></table>`;
1859
+ };
1860
+ const preview = (value) => {
1861
+ const text = JSON.stringify(value) ?? "null";
1862
+ return esc(text.length > 180 ? `${text.slice(0, 180)}\u2026` : text);
1863
+ };
1864
+ const when = (epoch) => {
1865
+ const date = new Date(epoch);
1866
+ return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString();
1867
+ };
1868
+ const clockCard = (epoch) => {
1869
+ const date = new Date(epoch);
1870
+ if (Number.isNaN(date.getTime()))
1871
+ return esc(String(epoch));
1872
+ const iso = date.toISOString();
1873
+ return `${esc(iso.slice(0, 10))}<span class="clock-time">${esc(iso.slice(11, 19))} UTC</span>`;
1874
+ };
1875
+ const decodeClock = (value) => {
1876
+ if (!isRecord2(value) || typeof value.now !== "number" || typeof value.offsetMs !== "number" || typeof value.frozen !== "boolean") {
1877
+ throw new Error("Clock response was not a clock");
1878
+ }
1879
+ return { now: value.now, offsetMs: value.offsetMs, frozen: value.frozen };
1880
+ };
1881
+ const decodeField = (value) => {
1882
+ if (!isRecord2(value) || typeof value.name !== "string" || !isFieldKind(value.kind)) {
1883
+ return void 0;
1884
+ }
1885
+ const field = {
1886
+ name: value.name,
1887
+ kind: value.kind,
1888
+ optional: value.optional === true
1889
+ };
1890
+ if (typeof value.description === "string")
1891
+ field.description = value.description;
1892
+ if (Array.isArray(value.fields)) {
1893
+ field.fields = value.fields.flatMap((item) => {
1894
+ const child = decodeField(item);
1895
+ return child === void 0 ? [] : [child];
1896
+ });
1897
+ }
1898
+ return field;
1899
+ };
1900
+ const decodeCollection = (value) => {
1901
+ if (!isRecord2(value) || typeof value.name !== "string")
1902
+ return void 0;
1903
+ const fields = Array.isArray(value.fields) ? value.fields.flatMap((item) => {
1904
+ const field = decodeField(item);
1905
+ return field === void 0 ? [] : [field];
1906
+ }) : [];
1907
+ const collection = {
1908
+ name: value.name,
1909
+ label: typeof value.label === "string" ? value.label : value.name,
1910
+ count: typeof value.count === "number" ? value.count : 0,
1911
+ source: isSource(value.source) ? value.source : "inferred",
1912
+ fields
1913
+ };
1914
+ if (typeof value.description === "string")
1915
+ collection.description = value.description;
1916
+ return collection;
1917
+ };
1918
+ const decodeState = (value) => {
1919
+ if (!isRecord2(value) || !Array.isArray(value.collections)) {
1920
+ throw new Error("State response has no collection list");
1921
+ }
1922
+ return {
1923
+ namespace: typeof value.namespace === "string" ? value.namespace : state.namespace,
1924
+ storageNamespace: typeof value.storageNamespace === "string" ? value.storageNamespace : "",
1925
+ collections: value.collections.flatMap((item) => {
1926
+ const collection = decodeCollection(item);
1927
+ return collection === void 0 ? [] : [collection];
1928
+ })
1929
+ };
1930
+ };
1931
+ const decodeRecords = (value) => requireList(isRecord2(value) ? value.records : void 0, "Records").flatMap((item) => {
1932
+ if (!isRecord2(item) || typeof item.id !== "string")
1933
+ return [];
1934
+ return [
1935
+ {
1936
+ id: item.id,
1937
+ seq: typeof item.seq === "number" ? item.seq : 0,
1938
+ value: item.value,
1939
+ attrs: ` class="clickable" data-id="${esc(item.id)}"`
1940
+ }
1941
+ ];
1942
+ });
1943
+ const textOf = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : "";
1944
+ const decodeJournal = (value) => requireList(isRecord2(value) ? value.requests : void 0, "Journal").flatMap((item) => {
1945
+ if (!isRecord2(item))
1946
+ return [];
1947
+ return [
1948
+ {
1949
+ at: textOf(item.at),
1950
+ method: textOf(item.method),
1951
+ path: textOf(item.path),
1952
+ status: textOf(item.status),
1953
+ durationMs: textOf(item.durationMs),
1954
+ operationId: textOf(item.operationId)
1955
+ }
1956
+ ];
1957
+ });
1958
+ const decodeFaults = (value) => requireList(isRecord2(value) ? value.faults : void 0, "Faults").flatMap((item) => {
1959
+ if (!isRecord2(item))
1960
+ return [];
1961
+ return [
1962
+ {
1963
+ id: textOf(item.id),
1964
+ method: typeof item.method === "string" ? item.method : void 0,
1965
+ pathPrefix: typeof item.pathPrefix === "string" ? item.pathPrefix : void 0,
1966
+ operationId: typeof item.operationId === "string" ? item.operationId : void 0,
1967
+ status: typeof item.status === "number" ? item.status : void 0,
1968
+ effect: typeof item.effect === "string" ? item.effect : void 0,
1969
+ drop: typeof item.drop === "boolean" ? item.drop : void 0,
1970
+ latencyMs: typeof item.latencyMs === "number" ? item.latencyMs : void 0
1971
+ }
1972
+ ];
1973
+ });
1974
+ const loadNamespaces = async () => {
1975
+ const data = await api("GET", "/namespaces");
1976
+ const select = el("namespace", HTMLSelectElement);
1977
+ const listed = requireList(isRecord2(data) ? data.namespaces : void 0, "Namespaces");
1978
+ const fallback = isRecord2(data) && typeof data.default === "string" && data.default !== "" ? data.default : "default";
1979
+ const names = (listed.length > 0 ? listed.map((name) => String(name)) : [fallback]).slice();
1980
+ if (!names.includes(state.namespace))
1981
+ names.unshift(state.namespace);
1982
+ select.innerHTML = names.map((name) => `<option value="${esc(name)}">${esc(name)}</option>`).join("");
1983
+ select.value = state.namespace;
1984
+ };
1985
+ const renderOverview = async () => {
1986
+ const [health, shape, clock, journal] = await Promise.all([
1987
+ originGet("/health"),
1988
+ api("GET", "/state"),
1989
+ api("GET", "/clock"),
1990
+ api("GET", "/requests?limit=8")
1991
+ ]);
1992
+ if (!isRecord2(health))
1993
+ throw new Error("Health response was not an object");
1994
+ const decoded2 = decodeState(shape);
1995
+ state.shape = decoded2;
1996
+ const time = decodeClock(clock);
1997
+ el("overview-cards", HTMLElement).innerHTML = cards([
1998
+ {
1999
+ k: "Service",
2000
+ v: esc(typeof health.service === "string" && health.service !== "" ? health.service : config.service)
2001
+ },
2002
+ {
2003
+ k: "Status",
2004
+ v: esc(typeof health.status === "string" && health.status !== "" ? health.status : "ok")
2005
+ },
2006
+ { k: "Namespace", v: esc(state.namespace) },
2007
+ { k: "Collections", v: String(decoded2.collections.length) },
2008
+ {
2009
+ k: "Records",
2010
+ v: String(decoded2.collections.reduce((sum, item) => sum + item.count, 0))
2011
+ },
2012
+ { k: "Clock", v: clockCard(time.now) }
2013
+ ]);
2014
+ el("overview-requests", HTMLElement).innerHTML = table([
2015
+ { label: "Method", cell: (row) => esc(row.method) },
2016
+ { label: "Path", cell: (row) => esc(row.path) },
2017
+ {
2018
+ label: "Status",
2019
+ cell: (row) => `<span class="status-${row.status.charAt(0)}">${esc(row.status)}</span>`
2020
+ },
2021
+ { label: "Operation", cell: (row) => esc(row.operationId) }
2022
+ ], decodeJournal(journal));
2023
+ };
2024
+ const schemaChips = (collection) => {
2025
+ if (collection.fields.length === 0) {
2026
+ return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>';
2027
+ }
2028
+ return `<div class="chips">${collection.fields.map((field) => `<span class="chip">${esc(field.name)} <i>${esc(field.kind)}${field.optional ? "?" : ""}</i></span>`).join("")}</div>`;
2029
+ };
2030
+ const renderState = async () => {
2031
+ const shape = decodeState(await api("GET", "/state"));
2032
+ state.shape = shape;
2033
+ const host = el("collections", HTMLElement);
2034
+ if (shape.collections.length === 0) {
2035
+ host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>';
2036
+ el("schema", HTMLElement).innerHTML = "";
2037
+ el("records", HTMLElement).innerHTML = "";
2038
+ return;
2039
+ }
2040
+ if (!shape.collections.some((item) => item.name === state.collection)) {
2041
+ const first = shape.collections[0];
2042
+ if (first === void 0)
2043
+ return;
2044
+ state.collection = first.name;
2045
+ }
2046
+ 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("");
2047
+ const selected = shape.collections.find((item) => item.name === state.collection);
2048
+ if (selected === void 0)
2049
+ return;
2050
+ const description = selected.description ? `<p class="lede">${esc(selected.description)}</p>` : "";
2051
+ el("schema", HTMLElement).innerHTML = `<h3>${esc(selected.label)}</h3>${description}${schemaChips(selected)}`;
2052
+ const page = await api("GET", `/state/${encodeURIComponent(state.collection)}?limit=50`);
2053
+ el("records", HTMLElement).innerHTML = table([
2054
+ { label: "Id", cell: (row) => `<code>${esc(row.id)}</code>` },
2055
+ { label: "Seq", cell: (row) => esc(row.seq) },
2056
+ { label: "Value", cell: (row) => preview(row.value) }
2057
+ ], decodeRecords(page));
2058
+ };
2059
+ const renderClock = async () => {
2060
+ const clock = decodeClock(await api("GET", "/clock"));
2061
+ 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>`;
2062
+ const freeze = el("freeze", HTMLButtonElement);
2063
+ freeze.textContent = clock.frozen ? "Unfreeze" : "Freeze";
2064
+ freeze.dataset.frozen = clock.frozen ? "1" : "0";
2065
+ };
2066
+ const renderFaults = async () => {
2067
+ const [faults, presets] = await Promise.all([
2068
+ api("GET", "/faults"),
2069
+ api("GET", "/faults/presets").catch(() => ({ presets: [] }))
2070
+ ]);
2071
+ const select = el("fault-preset", HTMLSelectElement);
2072
+ const current = select.value;
2073
+ const list = faultPresetList(presets).presets;
2074
+ select.innerHTML = `<option value="">Custom rule</option>${list.map((preset) => `<option value="${esc(preset.name)}">${esc(preset.name)}</option>`).join("")}`;
2075
+ select.value = current;
2076
+ el("fault-list", HTMLElement).innerHTML = table([
2077
+ { label: "Id", cell: (row) => esc(row.id) },
2078
+ {
2079
+ label: "Match",
2080
+ cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter((part) => Boolean(part)).join(" ") || "all")
2081
+ },
2082
+ {
2083
+ label: "Effect",
2084
+ cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || ""))
2085
+ }
2086
+ ], decodeFaults(faults));
2087
+ };
2088
+ const renderJournal = async () => {
2089
+ const journal = await api("GET", "/requests?limit=100");
2090
+ el("journal", HTMLElement).innerHTML = table([
2091
+ { label: "When", cell: (row) => esc(row.at) },
2092
+ { label: "Method", cell: (row) => esc(row.method) },
2093
+ { label: "Path", cell: (row) => esc(row.path) },
2094
+ { label: "Status", cell: (row) => esc(row.status) },
2095
+ { label: "ms", cell: (row) => esc(row.durationMs) }
2096
+ ], decodeJournal(journal));
2097
+ };
2098
+ const renderRoutes = async () => {
2099
+ const data = await api("GET", "/");
2100
+ if (!isRecord2(data))
2101
+ throw new Error("Route list was not an object");
2102
+ const routes = requireList(data.routes, "Routes").map((route) => String(route));
2103
+ el("route-list", HTMLElement).innerHTML = routes.map((route) => {
2104
+ const extra = standard.has(route) ? "" : " <span class='badge'>extension</span>";
2105
+ const parts = route.split(" ");
2106
+ const method = parts[0] ?? "";
2107
+ const path = parts.slice(1).join(" ");
2108
+ 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>`;
2109
+ }).join("");
2110
+ };
2111
+ const renderers = {
2112
+ overview: renderOverview,
2113
+ state: renderState,
2114
+ clock: renderClock,
2115
+ faults: renderFaults,
2116
+ journal: renderJournal,
2117
+ routes: renderRoutes
2118
+ };
2119
+ async function refresh() {
2120
+ clearError();
2121
+ const render = renderers[state.view];
2122
+ if (render)
2123
+ await render();
2124
+ }
2125
+ el("namespace", HTMLSelectElement).addEventListener("change", () => {
2126
+ state.namespace = el("namespace", HTMLSelectElement).value;
2127
+ refresh().catch(showError);
2128
+ });
2129
+ keyInput.addEventListener("change", () => {
2130
+ state.key = keyInput.value.trim();
2131
+ sessionStorage.setItem("mockingbird-admin-key", state.key);
2132
+ refresh().catch(showError);
2133
+ });
2134
+ el("refresh", HTMLButtonElement).addEventListener("click", () => {
2135
+ refresh().catch(showError);
2136
+ });
2137
+ el("collections", HTMLElement).addEventListener("click", (event) => {
2138
+ const target = event.target;
2139
+ if (!(target instanceof Element))
2140
+ return;
2141
+ const button = target.closest("[data-collection]");
2142
+ if (!(button instanceof HTMLElement))
2143
+ return;
2144
+ const collection = button.dataset.collection;
2145
+ if (collection === void 0)
2146
+ return;
2147
+ state.collection = collection;
2148
+ renderState().catch(showError);
2149
+ });
2150
+ el("records", HTMLElement).addEventListener("click", (event) => {
2151
+ const target = event.target;
2152
+ if (!(target instanceof Element))
2153
+ return;
2154
+ const row = target.closest("[data-id]");
2155
+ if (!(row instanceof HTMLElement))
2156
+ return;
2157
+ const id = row.dataset.id;
2158
+ if (id === void 0)
2159
+ return;
2160
+ openEditor(id).catch(showError);
2161
+ });
2162
+ const editor = el("editor", HTMLDialogElement);
2163
+ const editorId = el("editor-id", HTMLInputElement);
2164
+ const editorValue = el("editor-value", HTMLTextAreaElement);
2165
+ const editorDelete = el("editor-delete", HTMLButtonElement);
2166
+ async function openEditor(id) {
2167
+ state.editing = id;
2168
+ el("editor-title", HTMLElement).textContent = id ? "Edit record" : "New record";
2169
+ editorId.value = id ?? "";
2170
+ editorId.readOnly = id !== null;
2171
+ editorDelete.hidden = id === null;
2172
+ if (id) {
2173
+ const record = await api("GET", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`);
2174
+ if (!isRecord2(record))
2175
+ throw new Error("Record response was not an object");
2176
+ editorValue.value = JSON.stringify(record.value, null, 2) ?? "";
2177
+ } else {
2178
+ editorValue.value = "{\n \n}";
2179
+ }
2180
+ editor.showModal();
2181
+ }
2182
+ el("new-record", HTMLButtonElement).addEventListener("click", () => {
2183
+ openEditor(null).catch(showError);
2184
+ });
2185
+ el("editor-form", HTMLFormElement).addEventListener("submit", (event) => {
2186
+ const submitter = event.submitter;
2187
+ if (!(submitter instanceof HTMLButtonElement) || submitter.value !== "save")
2188
+ return;
2189
+ event.preventDefault();
2190
+ let value;
2191
+ try {
2192
+ value = readJson2(editorValue.value);
2193
+ } catch {
2194
+ showError(new Error("Value is not JSON"));
2195
+ return;
2196
+ }
2197
+ const id = editorId.value.trim();
2198
+ const path = `/state/${encodeURIComponent(state.collection)}`;
2199
+ const request = state.editing ? api("PUT", `${path}/${encodeURIComponent(id)}`, { value }) : api("POST", path, { id: id || void 0, value });
2200
+ request.then(() => {
2201
+ editor.close();
2202
+ return renderState();
2203
+ }).catch(showError);
2204
+ });
2205
+ editorDelete.addEventListener("click", () => {
2206
+ const id = state.editing;
2207
+ if (!id)
2208
+ return;
2209
+ api("DELETE", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`).then(() => {
2210
+ editor.close();
2211
+ return renderState();
2212
+ }).catch(showError);
2213
+ });
2214
+ for (const button of document.querySelectorAll("[data-advance]")) {
2215
+ if (!(button instanceof HTMLButtonElement))
2216
+ continue;
2217
+ button.addEventListener("click", () => {
2218
+ const raw = button.dataset.advance;
2219
+ if (raw === void 0)
2220
+ return;
2221
+ api("POST", "/clock", { advance: Number(raw) }).then(() => renderClock()).catch(showError);
2222
+ });
2223
+ }
2224
+ el("freeze", HTMLButtonElement).addEventListener("click", () => {
2225
+ const frozen = el("freeze", HTMLButtonElement).dataset.frozen === "1";
2226
+ api("POST", "/clock", { freeze: !frozen }).then(() => renderClock()).catch(showError);
2227
+ });
2228
+ el("clock-reset", HTMLButtonElement).addEventListener("click", () => {
2229
+ api("POST", "/clock", { reset: true }).then(() => renderClock()).catch(showError);
2230
+ });
2231
+ el("clock-set", HTMLFormElement).addEventListener("submit", (event) => {
2232
+ event.preventDefault();
2233
+ const raw = el("clock-instant", HTMLInputElement).value.trim();
2234
+ const set = /^-?\d+$/.test(raw) ? Number(raw) : raw;
2235
+ api("POST", "/clock", { set }).then(() => renderClock()).catch(showError);
2236
+ });
2237
+ el("fault-form", HTMLFormElement).addEventListener("submit", (event) => {
2238
+ event.preventDefault();
2239
+ const preset = el("fault-preset", HTMLSelectElement).value;
2240
+ if (preset !== "") {
2241
+ api("POST", "/faults", { preset }).then(() => renderFaults()).catch(showError);
2242
+ return;
2243
+ }
2244
+ const status = Number(el("fault-status", HTMLInputElement).value);
2245
+ if (!Number.isFinite(status)) {
2246
+ showError(new Error("Status must be a number, or choose a preset"));
2247
+ return;
2248
+ }
2249
+ const path = el("fault-path", HTMLInputElement).value;
2250
+ const method = el("fault-method", HTMLInputElement).value;
2251
+ api("POST", "/faults", {
2252
+ status,
2253
+ ...path !== "" ? { pathPrefix: path } : {},
2254
+ ...method !== "" ? { method } : {}
2255
+ }).then(() => renderFaults()).catch(showError);
2256
+ });
2257
+ el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
2258
+ api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
2259
+ });
2260
+ el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
2261
+ api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
2262
+ });
2263
+ el("route-list", HTMLElement).addEventListener("click", (event) => {
2264
+ const target = event.target;
2265
+ if (!(target instanceof Element))
2266
+ return;
2267
+ const button = target.closest("[data-route]");
2268
+ if (!(button instanceof HTMLElement))
2269
+ return;
2270
+ const route = button.dataset.route;
2271
+ if (route === void 0)
2272
+ return;
2273
+ state.route = route;
2274
+ el("route-title", HTMLElement).textContent = route;
2275
+ const method = route.split(" ")[0] ?? "";
2276
+ el("route-body", HTMLTextAreaElement).disabled = method === "GET" || method === "DELETE";
2277
+ });
2278
+ el("route-form", HTMLFormElement).addEventListener("submit", (event) => {
2279
+ event.preventDefault();
2280
+ const send = async () => {
2281
+ if (state.route === null)
2282
+ throw new Error("Choose a route first");
2283
+ const parts = state.route.split(" ");
2284
+ const method = parts[0];
2285
+ const path = parts.slice(1).join(" ");
2286
+ if (method === void 0)
2287
+ throw new Error("Choose a route first");
2288
+ const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_match, key) => prompt(`Value for ${key}`) || "");
2289
+ let body;
2290
+ const routeBody = el("route-body", HTMLTextAreaElement);
2291
+ if (method !== "GET" && method !== "DELETE" && routeBody.value.trim() !== "") {
2292
+ try {
2293
+ body = readJson2(routeBody.value);
2294
+ } catch {
2295
+ throw new Error("Body is not JSON");
2296
+ }
2297
+ }
2298
+ const data = await api(method, concrete, body);
2299
+ el("route-result", HTMLElement).textContent = JSON.stringify(data, null, 2);
2300
+ };
2301
+ send().catch(showError);
2302
+ });
2303
+ const decodePanel = (value) => {
2304
+ if (!isRecord2(value) || typeof value.id !== "string" || typeof value.title !== "string") {
2305
+ return void 0;
2306
+ }
2307
+ const panel = {
2308
+ id: value.id,
2309
+ title: value.title,
2310
+ description: typeof value.description === "string" ? value.description : "",
2311
+ html: typeof value.html === "string" ? value.html : ""
2312
+ };
2313
+ if (typeof value.script === "string")
2314
+ panel.script = value.script;
2315
+ return panel;
2316
+ };
2317
+ const mountPanels = (panels) => {
2318
+ const nav = el("nav", HTMLElement);
2319
+ const views = el("views", HTMLElement);
2320
+ for (const panel of panels) {
2321
+ const button = document.createElement("button");
2322
+ button.type = "button";
2323
+ button.dataset.view = panel.id;
2324
+ button.textContent = panel.title;
2325
+ nav.append(button);
2326
+ const section = document.createElement("section");
2327
+ section.className = "view";
2328
+ section.id = `view-${panel.id}`;
2329
+ section.hidden = true;
2330
+ const title = document.createElement("h2");
2331
+ title.textContent = panel.title;
2332
+ const lede = document.createElement("p");
2333
+ lede.className = "lede";
2334
+ lede.textContent = panel.description;
2335
+ const body = document.createElement("div");
2336
+ body.className = "panel-body";
2337
+ body.id = `panel-${panel.id}`;
2338
+ body.innerHTML = panel.html;
2339
+ section.append(title, lede, body);
2340
+ views.append(section);
2341
+ if (panel.script === void 0)
2342
+ continue;
2343
+ try {
2344
+ const compiled = new Function("root", "api", panel.script);
2345
+ if (!isPanelScript(compiled))
2346
+ throw new Error("panel script did not compile");
2347
+ compiled(body, panelApi);
2348
+ } catch (error2) {
2349
+ const note = document.createElement("p");
2350
+ note.className = "banner show";
2351
+ note.textContent = error2 instanceof Error ? error2.message : String(error2);
2352
+ body.append(note);
2353
+ }
2354
+ }
2355
+ };
2356
+ startAdminBrand({
2357
+ document,
2358
+ $: (id) => document.getElementById(id),
2359
+ params,
2360
+ location: { href: location.href, origin: location.origin },
2361
+ fetch,
2362
+ brandsUrl: config.brandsUrl
2363
+ });
2364
+ loadNamespaces().then(() => api("GET", "/ui/manifest")).then((manifest) => {
2365
+ const panels = requireList(isRecord2(manifest) ? manifest.panels : void 0, "Panels").flatMap((panel) => {
2366
+ const decoded2 = decodePanel(panel);
2367
+ return decoded2 === void 0 ? [] : [decoded2];
2368
+ });
2369
+ mountPanels(panels);
2370
+ return refresh();
2371
+ }).catch(showError);
2372
+ }
2373
+ var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
2374
+ var adminClientSource = (config) => {
2375
+ const payload = JSON.stringify(config).replace(/</g, "\\u003c");
2376
+ return `<script>
2377
+ ${clientPrelude}
2378
+ bootAdmin(${payload});
2379
+ </script>`;
2380
+ };
2381
+
1156
2382
  // ../core/dist/surface.js
1157
2383
  var defineMock = (runtime) => runtime;
1158
2384
  var STANDARD_ADMIN_ROUTES = [
@@ -1221,94 +2447,106 @@ var escapeHtml = (value) => value.replace(/[&<>"']/g, (char) => {
1221
2447
  return "&#39;";
1222
2448
  }
1223
2449
  });
2450
+ var ADMIN_BRANDS_URL = "https://mockingbird.chrisvouga.dev/brands.json";
1224
2451
  var renderAdminDocument = (service) => {
1225
2452
  const name = escapeHtml(service);
1226
- const standard = JSON.stringify(STANDARD_ADMIN_ROUTES);
2453
+ const script = adminClientSource({
2454
+ standardRoutes: STANDARD_ADMIN_ROUTES,
2455
+ adminPrefix: ADMIN_PREFIX,
2456
+ adminKeyHeader: ADMIN_KEY_HEADER,
2457
+ brandsUrl: ADMIN_BRANDS_URL,
2458
+ service
2459
+ });
1227
2460
  return `<!DOCTYPE html>
1228
2461
  <html lang="en">
1229
2462
  <head>
1230
2463
  <meta charset="utf-8">
1231
2464
  <meta name="viewport" content="width=device-width, initial-scale=1">
1232
2465
  <meta name="color-scheme" content="light dark">
1233
- <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">
2466
+ <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">
1234
2467
  <title>${name} admin</title>
1235
2468
  <style>
2469
+ ${CSS_RESET}
1236
2470
  :root {
1237
2471
  color-scheme: light;
1238
- --bg: #f6f5f2;
2472
+ --bg: #fafafa;
1239
2473
  --bg-elev: #ffffff;
1240
- --bg-muted: #eeece6;
1241
- --bg-inset: #f3f1eb;
1242
- --line: #e2dfd6;
1243
- --line-strong: #d0ccc1;
1244
- --ink: #1c1915;
1245
- --muted: #5e584f;
1246
- --faint: #8a8378;
1247
- --accent: #3d4f3a;
1248
- --accent-ink: #f7f6f2;
1249
- --accent-soft: #e5ece2;
1250
- --ok: #1f7a45;
1251
- --ok-soft: #e5f4eb;
1252
- --warn: #9a6700;
1253
- --warn-soft: #fbf3df;
1254
- --err: #a33b32;
1255
- --err-soft: #fdecea;
1256
- --shadow: 0 1px 2px rgb(28 25 21 / 0.05), 0 12px 32px rgb(28 25 21 / 0.06);
1257
- --sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
2474
+ --bg-muted: #f4f4f5;
2475
+ --bg-inset: #f4f4f5;
2476
+ --line: #dedee3;
2477
+ --line-strong: #c4c4cc;
2478
+ --ink: #18181b;
2479
+ --muted: #62626b;
2480
+ --faint: #71717a;
2481
+ --accent: #27272a;
2482
+ --accent-ink: #ffffff;
2483
+ --accent-soft: #f4f4f5;
2484
+ --ok: #166534;
2485
+ --ok-soft: #f0fdf4;
2486
+ --warn: #a16207;
2487
+ --warn-soft: #fefce8;
2488
+ --err: #a82d32;
2489
+ --err-soft: #fef2f2;
2490
+ --shadow: none;
2491
+ --sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
1258
2492
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
1259
- --radius: 14px;
1260
- --header: 60px;
1261
- --nav: 220px;
2493
+ --radius: 10px;
2494
+ --header: 56px;
2495
+ --nav: 200px;
1262
2496
  }
1263
2497
  :root[data-theme="dark"] {
1264
2498
  color-scheme: dark;
1265
- --bg: #141311;
1266
- --bg-elev: #1e1c19;
1267
- --bg-muted: #26231f;
1268
- --bg-inset: #181715;
1269
- --line: #34302b;
1270
- --line-strong: #4a453d;
1271
- --ink: #f4f1ea;
1272
- --muted: #c4bdb1;
1273
- --faint: #8f877c;
1274
- --accent: #c5d5b8;
1275
- --accent-ink: #1a2118;
1276
- --accent-soft: #2a3328;
1277
- --ok: #8fd6a8;
1278
- --ok-soft: #1a2e22;
1279
- --warn: #f0cc78;
1280
- --warn-soft: #332911;
1281
- --err: #f0a8a2;
1282
- --err-soft: #3a201e;
1283
- --shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 16px 40px rgb(0 0 0 / 0.28);
2499
+ --bg: #111113;
2500
+ --bg-elev: #19191c;
2501
+ --bg-muted: #242428;
2502
+ --bg-inset: #111113;
2503
+ --line: #36363c;
2504
+ --line-strong: #52525b;
2505
+ --ink: #f4f4f5;
2506
+ --muted: #a9a9b2;
2507
+ --faint: #71717a;
2508
+ --accent: #e4e4e7;
2509
+ --accent-ink: #18181b;
2510
+ --accent-soft: #242428;
2511
+ --ok: #86efac;
2512
+ --ok-soft: #14241b;
2513
+ --warn: #fde68a;
2514
+ --warn-soft: #2a2410;
2515
+ --err: #ffaaaa;
2516
+ --err-soft: #2c1516;
2517
+ --shadow: none;
1284
2518
  }
1285
2519
  @media (prefers-color-scheme: dark) {
1286
2520
  :root:not([data-theme="light"]) {
1287
2521
  color-scheme: dark;
1288
- --bg: #141311;
1289
- --bg-elev: #1e1c19;
1290
- --bg-muted: #26231f;
1291
- --bg-inset: #181715;
1292
- --line: #34302b;
1293
- --line-strong: #4a453d;
1294
- --ink: #f4f1ea;
1295
- --muted: #c4bdb1;
1296
- --faint: #8f877c;
1297
- --accent: #c5d5b8;
1298
- --accent-ink: #1a2118;
1299
- --accent-soft: #2a3328;
1300
- --ok: #8fd6a8;
1301
- --ok-soft: #1a2e22;
1302
- --warn: #f0cc78;
1303
- --warn-soft: #332911;
1304
- --err: #f0a8a2;
1305
- --err-soft: #3a201e;
1306
- --shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 16px 40px rgb(0 0 0 / 0.28);
2522
+ --bg: #111113;
2523
+ --bg-elev: #19191c;
2524
+ --bg-muted: #242428;
2525
+ --bg-inset: #111113;
2526
+ --line: #36363c;
2527
+ --line-strong: #52525b;
2528
+ --ink: #f4f4f5;
2529
+ --muted: #a9a9b2;
2530
+ --faint: #71717a;
2531
+ --accent: #e4e4e7;
2532
+ --accent-ink: #18181b;
2533
+ --accent-soft: #242428;
2534
+ --ok: #86efac;
2535
+ --ok-soft: #14241b;
2536
+ --warn: #fde68a;
2537
+ --warn-soft: #2a2410;
2538
+ --err: #ffaaaa;
2539
+ --err-soft: #2c1516;
2540
+ --shadow: none;
1307
2541
  }
1308
2542
  }
1309
- * { box-sizing: border-box; }
1310
- html, body { margin: 0; height: 100%; }
2543
+ /* Fill the viewport, then grow with the content so the background covers the scroll. */
2544
+ html { height: 100%; }
1311
2545
  body {
2546
+ margin: 0;
2547
+ min-height: 100%;
2548
+ display: flex;
2549
+ flex-direction: column;
1312
2550
  font-family: var(--sans);
1313
2551
  background: var(--bg);
1314
2552
  color: var(--ink);
@@ -1317,24 +2555,37 @@ var renderAdminDocument = (service) => {
1317
2555
  padding-right: env(safe-area-inset-right);
1318
2556
  -webkit-text-size-adjust: 100%;
1319
2557
  }
1320
- button, input, select, textarea { font: inherit; color: inherit; }
1321
- button { cursor: pointer; }
1322
2558
  :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
1323
- @media (prefers-reduced-motion: reduce) {
1324
- * { scroll-behavior: auto !important; transition: none !important; }
1325
- }
1326
- .app { min-height: 100%; display: flex; flex-direction: column; }
2559
+ .app { flex: 1 0 auto; display: flex; flex-direction: column; min-width: 0; width: 100%; }
1327
2560
  header.top {
1328
2561
  position: sticky; top: 0; z-index: 5;
1329
2562
  display: flex; align-items: center; gap: 12px;
1330
2563
  min-height: var(--header); padding: 10px 16px;
1331
- background: color-mix(in srgb, var(--bg-elev) 92%, transparent);
2564
+ background: var(--bg-elev);
1332
2565
  border-bottom: 1px solid var(--line);
1333
- backdrop-filter: blur(10px);
1334
- }
1335
- .brand { display: flex; flex-direction: column; min-width: 0; margin-right: auto; }
1336
- .brand strong { font-size: 15px; letter-spacing: -0.01em; }
1337
- .brand span { color: var(--faint); font-size: 12px; }
2566
+ min-width: 0; max-width: 100%;
2567
+ }
2568
+ .brand {
2569
+ display: flex; flex-flow: row wrap; align-items: center; gap: 14px;
2570
+ min-width: 0; max-width: 100%; margin-right: auto;
2571
+ }
2572
+ .brand-id { display: flex; flex-direction: column; min-width: 0; flex: none; }
2573
+ .brand-id strong { font-size: 15px; letter-spacing: -0.01em; }
2574
+ .brand-id span { color: var(--faint); font-size: 12px; }
2575
+ .vendor {
2576
+ display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
2577
+ min-width: 0; max-width: 100%; font-size: 12px; color: var(--muted);
2578
+ }
2579
+ .vendor[hidden] { display: none; }
2580
+ /* Logos are drawn for a light tile, same as the docs site, so the plate stays white. */
2581
+ .vendor img {
2582
+ width: 16px; height: 16px; padding: 2px; box-sizing: content-box;
2583
+ border-radius: 4px; border: 1px solid var(--line); background: #fff; object-fit: contain;
2584
+ }
2585
+ .vendor-name { color: var(--ink); font-weight: 600; }
2586
+ .vendor-sep { color: var(--faint); }
2587
+ .vendor-link { color: inherit; text-decoration: none; }
2588
+ .vendor-link:hover { color: var(--ink); text-decoration: underline; }
1338
2589
  .controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
1339
2590
  label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
1340
2591
  label.field input, label.field select, .controls input[type="password"] {
@@ -1352,7 +2603,7 @@ var renderAdminDocument = (service) => {
1352
2603
  record or the full nav row would widen the page past the viewport. */
1353
2604
  .layout {
1354
2605
  display: grid; grid-template-columns: var(--nav) minmax(0, 1fr);
1355
- flex: 1; min-height: 0; min-width: 0;
2606
+ flex: 1 0 auto; min-width: 0;
1356
2607
  }
1357
2608
  nav.side {
1358
2609
  border-right: 1px solid var(--line); padding: 12px;
@@ -1406,8 +2657,8 @@ var renderAdminDocument = (service) => {
1406
2657
  .banner.show { display: block; }
1407
2658
  .empty { color: var(--muted); padding: 24px 8px; }
1408
2659
  dialog {
1409
- border: 1px solid var(--line); border-radius: 16px; padding: 0; background: var(--bg-elev);
1410
- color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow: var(--shadow);
2660
+ border: 1px solid var(--line); border-radius: var(--radius); padding: 0; background: var(--bg-elev);
2661
+ color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow: none;
1411
2662
  }
1412
2663
  dialog::backdrop { background: rgb(20 19 17 / 0.45); }
1413
2664
  dialog form, .dialog-body { padding: 16px; display: flex; flex-direction: column; gap: 10px; }
@@ -1434,7 +2685,7 @@ var renderAdminDocument = (service) => {
1434
2685
  padding-top: max(10px, env(safe-area-inset-top));
1435
2686
  }
1436
2687
  input, select, textarea { font-size: 16px; }
1437
- .controls { width: 100%; }
2688
+ .brand, .vendor, .controls { width: 100%; }
1438
2689
  label.field, label.field select, label.field input { flex: 1; min-width: 0; }
1439
2690
  .layout { grid-template-columns: minmax(0, 1fr); }
1440
2691
  nav.side {
@@ -1465,8 +2716,11 @@ var renderAdminDocument = (service) => {
1465
2716
  <div class="app" data-service="${name}" data-mockingbird-admin>
1466
2717
  <header class="top">
1467
2718
  <div class="brand">
1468
- <strong>${name}</strong>
1469
- <span>Mockingbird admin</span>
2719
+ <div class="brand-id">
2720
+ <strong>${name}</strong>
2721
+ <span>Admin</span>
2722
+ </div>
2723
+ <div class="vendor" id="vendor" hidden></div>
1470
2724
  </div>
1471
2725
  <div class="controls">
1472
2726
  <label class="field">Namespace
@@ -1596,460 +2850,7 @@ var renderAdminDocument = (service) => {
1596
2850
  </div>
1597
2851
  </form>
1598
2852
  </dialog>
1599
- <script>
1600
- (() => {
1601
- const STANDARD = new Set(${standard})
1602
- const state = {
1603
- namespace: "default",
1604
- key: sessionStorage.getItem("mockingbird-admin-key") || "",
1605
- view: "overview",
1606
- collection: "",
1607
- shape: null,
1608
- route: null,
1609
- editing: null
1610
- }
1611
- const $ = (id) => document.getElementById(id)
1612
- const banner = $("banner")
1613
- const keyInput = $("admin-key")
1614
- keyInput.value = state.key
1615
- const params = new URLSearchParams(location.search)
1616
- if (params.get("key")) {
1617
- state.key = params.get("key")
1618
- keyInput.value = state.key
1619
- sessionStorage.setItem("mockingbird-admin-key", state.key)
1620
- params.delete("key")
1621
- const next = params.toString()
1622
- history.replaceState(null, "", location.pathname + (next ? "?" + next : ""))
1623
- }
1624
- if (params.get("namespace")) state.namespace = params.get("namespace")
1625
-
1626
- const showError = (error) => {
1627
- banner.textContent = error instanceof Error ? error.message : String(error)
1628
- banner.classList.add("show")
1629
- }
1630
- const clearError = () => banner.classList.remove("show")
1631
-
1632
- async function originGet(path) {
1633
- const response = await fetch(path, { headers: { accept: "application/json" } })
1634
- if (!response.ok) throw new Error(response.status + " " + path)
1635
- return response.json()
1636
- }
1637
- async function api(method, path, body) {
1638
- const headers = { accept: "application/json" }
1639
- if (state.key) headers["${ADMIN_KEY_HEADER}"] = state.key
1640
- if (state.namespace) headers["x-mockingbird-namespace"] = state.namespace
1641
- if (body !== undefined) headers["content-type"] = "application/json"
1642
- const response = await fetch("${ADMIN_PREFIX}" + path, {
1643
- method,
1644
- headers,
1645
- body: body === undefined ? undefined : JSON.stringify(body)
1646
- })
1647
- const text = await response.text()
1648
- let data = null
1649
- if (text) {
1650
- try { data = JSON.parse(text) } catch { data = { raw: text } }
1651
- }
1652
- if (response.status === 401) {
1653
- keyInput.focus()
1654
- throw new Error((data && data.error && data.error.message) || "Admin key required")
1655
- }
1656
- if (!response.ok) {
1657
- throw new Error((data && data.error && data.error.message) || (response.status + " " + method + " " + path))
1658
- }
1659
- return data
1660
- }
1661
- const panelApi = {
1662
- get: (path) => api("GET", path),
1663
- send: (method, path, body) => api(method, path, body),
1664
- namespace: () => state.namespace
1665
- }
1666
-
1667
- function setTheme(theme) {
1668
- if (theme) document.documentElement.dataset.theme = theme
1669
- else delete document.documentElement.dataset.theme
1670
- }
1671
- const storedTheme = localStorage.getItem("mockingbird-admin-theme")
1672
- if (storedTheme) setTheme(storedTheme)
1673
- $("theme").addEventListener("click", () => {
1674
- const current = document.documentElement.dataset.theme
1675
- const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches
1676
- const next = dark ? "light" : "dark"
1677
- localStorage.setItem("mockingbird-admin-theme", next)
1678
- setTheme(next)
1679
- })
1680
-
1681
- function show(view) {
1682
- state.view = view
1683
- for (const button of document.querySelectorAll("#nav button")) {
1684
- button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false")
1685
- }
1686
- for (const section of document.querySelectorAll("main .view")) {
1687
- section.hidden = section.id !== "view-" + view
1688
- }
1689
- }
1690
- document.getElementById("nav").addEventListener("click", (event) => {
1691
- const button = event.target.closest("button[data-view]")
1692
- if (!button) return
1693
- show(button.dataset.view)
1694
- refresh().catch(showError)
1695
- })
1696
-
1697
- function cards(items) {
1698
- return '<div class="grid">' + items.map((item) =>
1699
- '<article class="card"><div class="k">' + item.k + '</div><div class="v">' + item.v + '</div></article>'
1700
- ).join("") + "</div>"
1701
- }
1702
- function table(columns, rows) {
1703
- if (rows.length === 0) return '<p class="empty">Nothing here yet.</p>'
1704
- const head = "<tr>" + columns.map((column) => "<th>" + column.label + "</th>").join("") + "</tr>"
1705
- const body = rows.map((row) => "<tr" + (row.attrs || "") + ">" + columns.map((column) =>
1706
- '<td data-label="' + column.label + '">' + column.cell(row) + "</td>"
1707
- ).join("") + "</tr>").join("")
1708
- return "<table><thead>" + head + "</thead><tbody>" + body + "</tbody></table>"
1709
- }
1710
- const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({
1711
- "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"
1712
- }[char]))
1713
- const preview = (value) => {
1714
- const text = JSON.stringify(value)
1715
- return esc(text.length > 180 ? text.slice(0, 180) + "\u2026" : text)
1716
- }
1717
- const when = (epoch) => {
1718
- const date = new Date(epoch)
1719
- return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString()
1720
- }
1721
- const clockCard = (epoch) => {
1722
- const date = new Date(epoch)
1723
- if (Number.isNaN(date.getTime())) return esc(String(epoch))
1724
- const iso = date.toISOString()
1725
- return esc(iso.slice(0, 10)) + '<span class="clock-time">' + esc(iso.slice(11, 19)) + " UTC</span>"
1726
- }
1727
-
1728
- async function loadNamespaces() {
1729
- const data = await api("GET", "/namespaces")
1730
- const select = $("namespace")
1731
- const names = data.namespaces && data.namespaces.length ? data.namespaces : [data.default || "default"]
1732
- if (!names.includes(state.namespace)) names.unshift(state.namespace)
1733
- select.innerHTML = names.map((name) =>
1734
- '<option value="' + esc(name) + '">' + esc(name) + "</option>"
1735
- ).join("")
1736
- select.value = state.namespace
1737
- }
1738
-
1739
- async function renderOverview() {
1740
- const [health, shape, clock, journal] = await Promise.all([
1741
- originGet("/health"),
1742
- api("GET", "/state"),
1743
- api("GET", "/clock"),
1744
- api("GET", "/requests?limit=8")
1745
- ])
1746
- state.shape = shape
1747
- $("overview-cards").innerHTML = cards([
1748
- { k: "Service", v: esc(health.service || "${name}") },
1749
- { k: "Status", v: esc(health.status || "ok") },
1750
- { k: "Namespace", v: esc(state.namespace) },
1751
- { k: "Collections", v: String(shape.collections.length) },
1752
- { k: "Records", v: String(shape.collections.reduce((sum, item) => sum + item.count, 0)) },
1753
- { k: "Clock", v: clockCard(clock.now) }
1754
- ])
1755
- const requests = (journal && journal.requests) || []
1756
- $("overview-requests").innerHTML = table(
1757
- [
1758
- { label: "Method", cell: (row) => esc(row.method) },
1759
- { label: "Path", cell: (row) => esc(row.path) },
1760
- { label: "Status", cell: (row) => '<span class="status-' + String(row.status).charAt(0) + '">' + esc(row.status) + "</span>" },
1761
- { label: "Operation", cell: (row) => esc(row.operationId || "") }
1762
- ],
1763
- requests
1764
- )
1765
- }
1766
-
1767
- function schemaChips(collection) {
1768
- if (!collection || collection.fields.length === 0) {
1769
- return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>'
1770
- }
1771
- return '<div class="chips">' + collection.fields.map((field) =>
1772
- '<span class="chip">' + esc(field.name) + ' <i>' + esc(field.kind) + (field.optional ? "?" : "") + "</i></span>"
1773
- ).join("") + "</div>"
1774
- }
1775
-
1776
- async function renderState() {
1777
- const shape = await api("GET", "/state")
1778
- state.shape = shape
1779
- const host = $("collections")
1780
- if (shape.collections.length === 0) {
1781
- host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>'
1782
- $("schema").innerHTML = ""
1783
- $("records").innerHTML = ""
1784
- return
1785
- }
1786
- if (!shape.collections.some((item) => item.name === state.collection)) {
1787
- state.collection = shape.collections[0].name
1788
- }
1789
- host.innerHTML = shape.collections.map((item) =>
1790
- '<button type="button" class="collection" data-collection="' + esc(item.name) + '" aria-current="' +
1791
- (item.name === state.collection ? "true" : "false") + '"><strong>' + esc(item.label) +
1792
- '</strong><small>' + esc(item.count) + " \xB7 " + esc(item.source) + "</small></button>"
1793
- ).join("")
1794
- const selected = shape.collections.find((item) => item.name === state.collection)
1795
- $("schema").innerHTML = "<h3>" + esc(selected.label) + "</h3>" +
1796
- (selected.description ? "<p class=" + '"lede">' + esc(selected.description) + "</p>" : "") +
1797
- schemaChips(selected)
1798
- const page = await api("GET", "/state/" + encodeURIComponent(state.collection) + "?limit=50")
1799
- const rows = page.records.map((record) => ({
1800
- ...record,
1801
- attrs: ' class="clickable" data-id="' + esc(record.id) + '"'
1802
- }))
1803
- $("records").innerHTML = table(
1804
- [
1805
- { label: "Id", cell: (row) => "<code>" + esc(row.id) + "</code>" },
1806
- { label: "Seq", cell: (row) => esc(row.seq) },
1807
- { label: "Value", cell: (row) => preview(row.value) }
1808
- ],
1809
- rows
1810
- )
1811
- }
1812
-
1813
- async function renderClock() {
1814
- const clock = await api("GET", "/clock")
1815
- $("clock-readout").innerHTML = "<div class='k'>Now</div><div class='v'>" + esc(when(clock.now)) +
1816
- "</div><p>" + esc(clock.now) + " ms \xB7 offset " + esc(clock.offsetMs) + " ms " +
1817
- (clock.frozen ? "<span class='badge warn'>frozen</span>" : "<span class='badge ok'>live</span>") + "</p>"
1818
- $("freeze").textContent = clock.frozen ? "Unfreeze" : "Freeze"
1819
- $("freeze").dataset.frozen = clock.frozen ? "1" : "0"
1820
- }
1821
-
1822
- async function renderFaults() {
1823
- const [faults, presets] = await Promise.all([
1824
- api("GET", "/faults"),
1825
- api("GET", "/faults/presets").catch(() => ({ presets: [] }))
1826
- ])
1827
- const select = $("fault-preset")
1828
- const current = select.value
1829
- const list = presets.presets || []
1830
- select.innerHTML = '<option value="">Custom rule</option>' + list.map((preset) =>
1831
- '<option value="' + esc(preset.name) + '">' + esc(preset.name) + "</option>"
1832
- ).join("")
1833
- select.value = current
1834
- const rules = faults.faults || []
1835
- $("fault-list").innerHTML = table(
1836
- [
1837
- { label: "Id", cell: (row) => esc(row.id) },
1838
- { label: "Match", cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter(Boolean).join(" ") || "all") },
1839
- { label: "Effect", cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || "")) }
1840
- ],
1841
- rules
1842
- )
1843
- }
1844
-
1845
- async function renderJournal() {
1846
- const journal = await api("GET", "/requests?limit=100")
1847
- $("journal").innerHTML = table(
1848
- [
1849
- { label: "When", cell: (row) => esc(row.at || "") },
1850
- { label: "Method", cell: (row) => esc(row.method) },
1851
- { label: "Path", cell: (row) => esc(row.path) },
1852
- { label: "Status", cell: (row) => esc(row.status) },
1853
- { label: "ms", cell: (row) => esc(row.durationMs) }
1854
- ],
1855
- journal.requests || []
1856
- )
1857
- }
1858
-
1859
- async function renderRoutes() {
1860
- const data = await api("GET", "/")
1861
- const routes = data.routes || []
1862
- $("route-list").innerHTML = routes.map((route) => {
1863
- const extra = STANDARD.has(route) ? "" : " <span class='badge'>extension</span>"
1864
- return '<div class="route"><span class="method">' + esc(route.split(" ")[0]) +
1865
- '</span><span>' + esc(route.split(" ").slice(1).join(" ")) + extra +
1866
- '</span><button class="btn" type="button" data-route="' + esc(route) + '">Use</button></div>'
1867
- }).join("")
1868
- }
1869
-
1870
- const renderers = {
1871
- overview: renderOverview,
1872
- state: renderState,
1873
- clock: renderClock,
1874
- faults: renderFaults,
1875
- journal: renderJournal,
1876
- routes: renderRoutes
1877
- }
1878
-
1879
- async function refresh() {
1880
- clearError()
1881
- if (state.view in renderers) await renderers[state.view]()
1882
- }
1883
-
1884
- $("namespace").addEventListener("change", () => {
1885
- state.namespace = $("namespace").value
1886
- refresh().catch(showError)
1887
- })
1888
- keyInput.addEventListener("change", () => {
1889
- state.key = keyInput.value.trim()
1890
- sessionStorage.setItem("mockingbird-admin-key", state.key)
1891
- refresh().catch(showError)
1892
- })
1893
- $("refresh").addEventListener("click", () => refresh().catch(showError))
1894
-
1895
- $("collections").addEventListener("click", (event) => {
1896
- const button = event.target.closest("[data-collection]")
1897
- if (!button) return
1898
- state.collection = button.dataset.collection
1899
- renderState().catch(showError)
1900
- })
1901
- $("records").addEventListener("click", (event) => {
1902
- const row = event.target.closest("[data-id]")
1903
- if (!row) return
1904
- openEditor(row.dataset.id).catch(showError)
1905
- })
1906
-
1907
- const editor = $("editor")
1908
- async function openEditor(id) {
1909
- state.editing = id || null
1910
- $("editor-title").textContent = id ? "Edit record" : "New record"
1911
- $("editor-id").value = id || ""
1912
- $("editor-id").readOnly = Boolean(id)
1913
- $("editor-delete").hidden = !id
1914
- if (id) {
1915
- const record = await api("GET", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
1916
- $("editor-value").value = JSON.stringify(record.value, null, 2)
1917
- } else {
1918
- $("editor-value").value = "{\\n \\n}"
1919
- }
1920
- editor.showModal()
1921
- }
1922
- $("new-record").addEventListener("click", () => openEditor(null).catch(showError))
1923
- $("editor-form").addEventListener("submit", (event) => {
1924
- const submitter = event.submitter
1925
- if (!submitter || submitter.value !== "save") return
1926
- event.preventDefault()
1927
- let value
1928
- try { value = JSON.parse($("editor-value").value) }
1929
- catch { showError(new Error("Value is not JSON")); return }
1930
- const id = $("editor-id").value.trim()
1931
- const path = "/state/" + encodeURIComponent(state.collection)
1932
- const request = state.editing
1933
- ? api("PUT", path + "/" + encodeURIComponent(id), { value })
1934
- : api("POST", path, { id: id || undefined, value })
1935
- request.then(() => { editor.close(); return renderState() }).catch(showError)
1936
- })
1937
- $("editor-delete").addEventListener("click", () => {
1938
- const id = state.editing
1939
- if (!id) return
1940
- api("DELETE", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
1941
- .then(() => { editor.close(); return renderState() })
1942
- .catch(showError)
1943
- })
1944
-
1945
- document.querySelectorAll("[data-advance]").forEach((button) => {
1946
- button.addEventListener("click", () => {
1947
- api("POST", "/clock", { advance: Number(button.dataset.advance) }).then(renderClock).catch(showError)
1948
- })
1949
- })
1950
- $("freeze").addEventListener("click", () => {
1951
- const frozen = $("freeze").dataset.frozen === "1"
1952
- api("POST", "/clock", { freeze: !frozen }).then(renderClock).catch(showError)
1953
- })
1954
- $("clock-reset").addEventListener("click", () => {
1955
- api("POST", "/clock", { reset: true }).then(renderClock).catch(showError)
1956
- })
1957
- $("clock-set").addEventListener("submit", (event) => {
1958
- event.preventDefault()
1959
- const raw = $("clock-instant").value.trim()
1960
- const set = /^-?\\d+$/.test(raw) ? Number(raw) : raw
1961
- api("POST", "/clock", { set }).then(renderClock).catch(showError)
1962
- })
1963
-
1964
- $("fault-form").addEventListener("submit", (event) => {
1965
- event.preventDefault()
1966
- const preset = $("fault-preset").value
1967
- const body = preset
1968
- ? { preset }
1969
- : {
1970
- status: Number($("fault-status").value),
1971
- ...($("fault-path").value ? { pathPrefix: $("fault-path").value } : {}),
1972
- ...($("fault-method").value ? { method: $("fault-method").value } : {})
1973
- }
1974
- if (!preset && !Number.isFinite(body.status)) {
1975
- showError(new Error("Status must be a number, or choose a preset"))
1976
- return
1977
- }
1978
- api("POST", "/faults", body).then(renderFaults).catch(showError)
1979
- })
1980
- $("fault-clear").addEventListener("click", () => api("DELETE", "/faults").then(renderFaults).catch(showError))
1981
- $("journal-clear").addEventListener("click", () => api("DELETE", "/requests").then(renderJournal).catch(showError))
1982
-
1983
- $("route-list").addEventListener("click", (event) => {
1984
- const button = event.target.closest("[data-route]")
1985
- if (!button) return
1986
- state.route = button.dataset.route
1987
- $("route-title").textContent = state.route
1988
- const method = state.route.split(" ")[0]
1989
- $("route-body").disabled = method === "GET" || method === "DELETE"
1990
- })
1991
- $("route-form").addEventListener("submit", async (event) => {
1992
- event.preventDefault()
1993
- if (!state.route) return showError(new Error("Choose a route first"))
1994
- const [method, path] = [state.route.split(" ")[0], state.route.split(" ").slice(1).join(" ")]
1995
- const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_, key) => prompt("Value for " + key) || "")
1996
- let body
1997
- if (method !== "GET" && method !== "DELETE" && $("route-body").value.trim()) {
1998
- try { body = JSON.parse($("route-body").value) }
1999
- catch { return showError(new Error("Body is not JSON")) }
2000
- }
2001
- try {
2002
- const data = await api(method, concrete, body)
2003
- $("route-result").textContent = JSON.stringify(data, null, 2)
2004
- } catch (error) {
2005
- showError(error)
2006
- }
2007
- })
2008
-
2009
- function mountPanels(panels) {
2010
- const nav = $("nav")
2011
- const views = $("views")
2012
- for (const panel of panels) {
2013
- const button = document.createElement("button")
2014
- button.type = "button"
2015
- button.dataset.view = panel.id
2016
- button.textContent = panel.title
2017
- nav.append(button)
2018
- const section = document.createElement("section")
2019
- section.className = "view"
2020
- section.id = "view-" + panel.id
2021
- section.hidden = true
2022
- const title = document.createElement("h2")
2023
- title.textContent = panel.title
2024
- const lede = document.createElement("p")
2025
- lede.className = "lede"
2026
- lede.textContent = panel.description || ""
2027
- const body = document.createElement("div")
2028
- body.className = "panel-body"
2029
- body.id = "panel-" + panel.id
2030
- body.innerHTML = panel.html || ""
2031
- section.append(title, lede, body)
2032
- views.append(section)
2033
- if (panel.script) {
2034
- try {
2035
- const run = new Function("root", "api", panel.script)
2036
- run(body, panelApi)
2037
- } catch (error) {
2038
- const note = document.createElement("p")
2039
- note.className = "banner show"
2040
- note.textContent = error instanceof Error ? error.message : String(error)
2041
- body.append(note)
2042
- }
2043
- }
2044
- }
2045
- }
2046
-
2047
- loadNamespaces()
2048
- .then(() => api("GET", "/ui/manifest"))
2049
- .then((manifest) => { mountPanels((manifest && manifest.panels) || []); return refresh() })
2050
- .catch(showError)
2051
- })()
2052
- </script>
2853
+ ${script}
2053
2854
  </body>
2054
2855
  </html>`;
2055
2856
  };
@@ -2066,10 +2867,10 @@ var json2 = (body) => new Response(JSON.stringify(body), {
2066
2867
  });
2067
2868
  var adminUiRoutes = (service, ui) => {
2068
2869
  const shell = () => renderAdminDocument(service);
2069
- const document2 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
2870
+ const document3 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
2070
2871
  return {
2071
- "GET /ui": () => htmlResponse(document2()),
2072
- "GET /ui/": () => htmlResponse(document2()),
2872
+ "GET /ui": () => htmlResponse(document3()),
2873
+ "GET /ui/": () => htmlResponse(document3()),
2073
2874
  "GET /ui/manifest": () => json2({
2074
2875
  service,
2075
2876
  panels: (ui?.panels ?? []).map((panel) => ({
@@ -2095,11 +2896,11 @@ var EXTENSION_KEYS = {
2095
2896
  };
2096
2897
 
2097
2898
  // ../../openapi/metadata/dist/read.js
2098
- var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2899
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2099
2900
  var extensionOf = (holder, key) => holder[key];
2100
2901
  var operationMetadata = (operation) => {
2101
2902
  const raw = extensionOf(operation, EXTENSION_KEYS.operation);
2102
- const ext = isRecord2(raw) ? raw : {};
2903
+ const ext = isRecord3(raw) ? raw : {};
2103
2904
  const supported = ext.supported ?? true;
2104
2905
  const parity = ext.parity ?? {};
2105
2906
  return {
@@ -2135,9 +2936,9 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
2135
2936
  this.name = "OperationRegistryError";
2136
2937
  }
2137
2938
  };
2138
- var verifyOperations = (document2, handlers) => {
2939
+ var verifyOperations = (document3, handlers) => {
2139
2940
  const problems = [];
2140
- const operations = listOperations(document2);
2941
+ const operations = listOperations(document3);
2141
2942
  const seen = /* @__PURE__ */ new Set();
2142
2943
  for (const operation of operations) {
2143
2944
  if (seen.has(operation.operationId))
@@ -2253,7 +3054,7 @@ var SAMPLE = 25;
2253
3054
  var WALK_LIMIT = 1e4;
2254
3055
  var SKIP_CTORS = /* @__PURE__ */ new Set(["Hono", "HonoRequest", "Router"]);
2255
3056
  var fail = (status, message) => ({ ok: false, status, message });
2256
- var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3057
+ var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2257
3058
  var kindOf = (value) => {
2258
3059
  if (value === null)
2259
3060
  return "null";
@@ -2352,7 +3153,7 @@ var absorb = (accum, value) => {
2352
3153
  const kind = kindOf(value);
2353
3154
  accum.kinds.add(kind);
2354
3155
  accum.present++;
2355
- if (!isRecord3(value))
3156
+ if (!isRecord4(value))
2356
3157
  return;
2357
3158
  for (const [key, child] of Object.entries(value)) {
2358
3159
  const nested = accum.nested.get(key) ?? blankAccum(key);
@@ -2377,7 +3178,7 @@ var inferFields = (records) => {
2377
3178
  if (records.length === 0)
2378
3179
  return [];
2379
3180
  const sample = records.slice(0, SAMPLE);
2380
- const objects = sample.filter((record) => isRecord3(record.value));
3181
+ const objects = sample.filter((record) => isRecord4(record.value));
2381
3182
  if (objects.length === 0) {
2382
3183
  const kinds = new Set(sample.map((record) => kindOf(record.value)));
2383
3184
  const list = [...kinds];
@@ -2564,7 +3365,7 @@ var patchStateRecord = (scope, name, id, value) => {
2564
3365
  const parsed = jsonValue(value);
2565
3366
  if (!parsed.ok)
2566
3367
  return parsed;
2567
- const next = isRecord3(current.value.value) && isRecord3(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
3368
+ const next = isRecord4(current.value.value) && isRecord4(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
2568
3369
  return replaceStateRecord(scope, name, id, next);
2569
3370
  };
2570
3371
  var deleteStateRecord = (scope, name, id) => {
@@ -2632,7 +3433,7 @@ var stateAdminRoutes = (routes) => {
2632
3433
  const scope = open(namespace);
2633
3434
  if (!scope.ok)
2634
3435
  return fromResult(scope, 201);
2635
- if (!isRecord3(body) || !("value" in body)) {
3436
+ if (!isRecord4(body) || !("value" in body)) {
2636
3437
  return adminError2(400, 'expected {"id?": "<id>", "value": <json>}');
2637
3438
  }
2638
3439
  const id = body.id;
@@ -2650,7 +3451,7 @@ var stateAdminRoutes = (routes) => {
2650
3451
  const scope = open(namespace);
2651
3452
  if (!scope.ok)
2652
3453
  return fromResult(scope, 200);
2653
- if (!isRecord3(body) || !("value" in body))
3454
+ if (!isRecord4(body) || !("value" in body))
2654
3455
  return adminError2(400, 'expected {"value": <json>}');
2655
3456
  const replaced = replaceStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
2656
3457
  if (replaced.ok)
@@ -2661,7 +3462,7 @@ var stateAdminRoutes = (routes) => {
2661
3462
  const scope = open(namespace);
2662
3463
  if (!scope.ok)
2663
3464
  return fromResult(scope, 200);
2664
- if (!isRecord3(body) || !("value" in body))
3465
+ if (!isRecord4(body) || !("value" in body))
2665
3466
  return adminError2(400, 'expected {"value": <json>}');
2666
3467
  const patched = patchStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
2667
3468
  if (patched.ok)
@@ -2681,7 +3482,7 @@ var stateAdminRoutes = (routes) => {
2681
3482
  };
2682
3483
 
2683
3484
  // ../core/dist/version.js
2684
- var PACKAGE_VERSION = true ? "1.4.0" : UNRELEASED_VERSION;
3485
+ var PACKAGE_VERSION = true ? "1.5.0" : UNRELEASED_VERSION;
2685
3486
 
2686
3487
  // ../core/dist/signing.js
2687
3488
  var encoder = new TextEncoder();
@@ -2690,9 +3491,9 @@ var fromBase64 = (value) => Uint8Array.from(atob(value), (char) => char.charCode
2690
3491
  // ../core/dist/webhooks.js
2691
3492
  var json4 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
2692
3493
  var adminError3 = (status, message) => json4(status, { error: { type: "mockingbird_admin", message } });
2693
- var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3494
+ var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2694
3495
  var parseEndpoint = (value) => {
2695
- if (!isRecord4(value) || typeof value.url !== "string")
3496
+ if (!isRecord5(value) || typeof value.url !== "string")
2696
3497
  return "each endpoint needs a url";
2697
3498
  try {
2698
3499
  new URL(value.url);
@@ -2709,12 +3510,12 @@ var parseEndpoint = (value) => {
2709
3510
  const events = value.events ?? value.enabledEvents;
2710
3511
  if (Array.isArray(events))
2711
3512
  endpoint.events = events.map(String);
2712
- if (isRecord4(value.tags)) {
3513
+ if (isRecord5(value.tags)) {
2713
3514
  endpoint.tags = Object.fromEntries(Object.entries(value.tags).map(([k, v]) => [k, String(v)]));
2714
3515
  }
2715
3516
  if (typeof value.account === "string")
2716
3517
  endpoint.tags = { ...endpoint.tags, account: value.account };
2717
- if (isRecord4(value.headers)) {
3518
+ if (isRecord5(value.headers)) {
2718
3519
  endpoint.headers = Object.fromEntries(Object.entries(value.headers).map(([k, v]) => [k, String(v)]));
2719
3520
  }
2720
3521
  return endpoint;
@@ -2741,7 +3542,7 @@ var webhookAdminRoutes = (hub) => ({
2741
3542
  return json4(200, { status: "ok" });
2742
3543
  },
2743
3544
  "POST /webhooks/faults": ({ body, namespace }) => {
2744
- if (!isRecord4(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
3545
+ if (!isRecord5(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
2745
3546
  return adminError3(400, "mode must be duplicate, reorder or drop");
2746
3547
  }
2747
3548
  const fault = { mode: body.mode };
@@ -2757,7 +3558,7 @@ var webhookAdminRoutes = (hub) => ({
2757
3558
  }))
2758
3559
  }),
2759
3560
  "PUT /webhook-endpoints": ({ body, namespace }) => {
2760
- const list = Array.isArray(body) ? body : isRecord4(body) ? body.endpoints : void 0;
3561
+ const list = Array.isArray(body) ? body : isRecord5(body) ? body.endpoints : void 0;
2761
3562
  if (!Array.isArray(list))
2762
3563
  return adminError3(400, "expected [{url, secret?, events?}]");
2763
3564
  const parsed = [];
@@ -2826,8 +3627,8 @@ var DroppedConnectionError = class extends TypeError {
2826
3627
  this.name = "TypeError";
2827
3628
  }
2828
3629
  };
2829
- var operationMatcher = (document2) => {
2830
- const matchers = listOperations(document2).map((operation) => ({
3630
+ var operationMatcher = (document3) => {
3631
+ const matchers = listOperations(document3).map((operation) => ({
2831
3632
  operationId: operation.operationId,
2832
3633
  method: operation.method.toUpperCase(),
2833
3634
  pattern: new RegExp(`^${operation.path.split("/").map((segment) => segment.startsWith("{") ? "[^/]+" : segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("/")}/?$`),
@@ -2922,10 +3723,10 @@ var createRuntime = (options) => {
2922
3723
  const ensureBranch = (namespace, branch2, at) => {
2923
3724
  if (!BRANCH_PATTERN2.test(branch2))
2924
3725
  throw new RangeError(`branch must match ${BRANCH_PATTERN2}`);
2925
- const history = timeline(namespace);
3726
+ const history2 = timeline(namespace);
2926
3727
  if (branch2 === "main") {
2927
3728
  if (at !== void 0) {
2928
- const point = history.checkout("main", at);
3729
+ const point = history2.checkout("main", at);
2929
3730
  restoreNamespace(sqlite, storageNamespace(namespace), point.value.snapshot);
2930
3731
  captured.set(namespace, point.value.snapshot);
2931
3732
  rng.setState(point.value.rngState);
@@ -2938,10 +3739,10 @@ var createRuntime = (options) => {
2938
3739
  return namespace;
2939
3740
  }
2940
3741
  const storage = physicalBranch(namespace, branch2);
2941
- if (!history.hasBranch(branch2)) {
3742
+ if (!history2.hasBranch(branch2)) {
2942
3743
  if (at === void 0)
2943
- history.commit(capture(namespace));
2944
- const point = history.fork(branch2, at === void 0 ? {} : { from: at });
3744
+ history2.commit(capture(namespace));
3745
+ const point = history2.fork(branch2, at === void 0 ? {} : { from: at });
2945
3746
  const branchRng = createRng(options.seed ?? 0);
2946
3747
  if (point)
2947
3748
  branchRng.setState(point.value.rngState);
@@ -2950,8 +3751,8 @@ var createRuntime = (options) => {
2950
3751
  restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
2951
3752
  if (point)
2952
3753
  captured.set(storage, point.value.snapshot);
2953
- } else if (at !== void 0 && history.head(branch2)?.id !== at) {
2954
- const point = history.checkout(branch2, at);
3754
+ } else if (at !== void 0 && history2.head(branch2)?.id !== at) {
3755
+ const point = history2.checkout(branch2, at);
2955
3756
  if (!instances.has(storage)) {
2956
3757
  const branchRng = createRng(options.seed ?? 0);
2957
3758
  branchRng.setState(point.value.rngState);
@@ -2962,7 +3763,7 @@ var createRuntime = (options) => {
2962
3763
  captured.set(storage, point.value.snapshot);
2963
3764
  } else {
2964
3765
  if (!instances.has(storage)) {
2965
- const point = history.head(branch2);
3766
+ const point = history2.head(branch2);
2966
3767
  const branchRng = createRng(options.seed ?? 0);
2967
3768
  if (point)
2968
3769
  branchRng.setState(point.value.rngState);
@@ -2986,8 +3787,8 @@ var createRuntime = (options) => {
2986
3787
  const checkout = (checkpointId, checkoutOptions = {}) => {
2987
3788
  const namespace = checkoutOptions.namespace ?? DEFAULT_NAMESPACE;
2988
3789
  const branchName = checkoutOptions.branch ?? "main";
2989
- const history = timeline(namespace);
2990
- const point = history.checkout(branchName, checkpointId);
3790
+ const history2 = timeline(namespace);
3791
+ const point = history2.checkout(branchName, checkpointId);
2991
3792
  const storage = ensureBranch(namespace, branchName);
2992
3793
  restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
2993
3794
  captured.set(storage, point.value.snapshot);
@@ -3037,9 +3838,9 @@ var createRuntime = (options) => {
3037
3838
  instance(name);
3038
3839
  restoreNamespace(sqlite, storageNamespace(name), from);
3039
3840
  captured.set(name, from);
3040
- const history = timelines.get(name);
3041
- if (history)
3042
- history.commit(capture(name), { branch: "main" });
3841
+ const history2 = timelines.get(name);
3842
+ if (history2)
3843
+ history2.commit(capture(name), { branch: "main" });
3043
3844
  else
3044
3845
  timeline(name);
3045
3846
  };
@@ -3268,10 +4069,10 @@ var createRuntime = (options) => {
3268
4069
  },
3269
4070
  release: (name, checkpointId) => timeline(name).release(checkpointId),
3270
4071
  inspect: (name) => {
3271
- const history = timeline(name);
4072
+ const history2 = timeline(name);
3272
4073
  return {
3273
- branches: history.branches(),
3274
- checkpoints: history.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
4074
+ branches: history2.branches(),
4075
+ checkpoints: history2.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
3275
4076
  id,
3276
4077
  branch: branchName,
3277
4078
  parent,
@@ -3286,11 +4087,11 @@ var createRuntime = (options) => {
3286
4087
  } : {},
3287
4088
  routes: {
3288
4089
  ...credentialRoutes(credentials),
3289
- ...options.presets ? presetRoutes(options.presets, runtime) : {},
3290
4090
  ...options.webhooks ? webhookAdminRoutes(options.webhooks) : {},
3291
4091
  ...options.admin?.(runtime) ?? {},
3292
- // Standard state and UI routes win a colliding key. A service adds its own keys
3293
- // beside these; it does not replace `/state` or `/ui`.
4092
+ // Shell routes win a colliding key. Fault presets are one of them: the admin page
4093
+ // maps `presets`, and a service route must not replace that list with a record.
4094
+ ...options.presets ? presetRoutes(options.presets, runtime) : {},
3294
4095
  ...stateAdminRoutes({
3295
4096
  open: stateScope,
3296
4097
  afterWrite: (namespace) => {
@@ -3365,9 +4166,12 @@ var credentialRoutes = (registry) => ({
3365
4166
  }
3366
4167
  });
3367
4168
  var presetRoutes = (presets, runtime) => ({
3368
- "GET /faults/presets": () => adminJson(200, {
3369
- presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
3370
- }),
4169
+ "GET /faults/presets": () => {
4170
+ const body = {
4171
+ presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
4172
+ };
4173
+ return adminJson(200, body);
4174
+ },
3371
4175
  "POST /faults/presets/:name": ({ params, body, namespace }) => {
3372
4176
  const name = params.name;
3373
4177
  if (!presets[name])
@@ -3438,15 +4242,15 @@ var decodePostHogBody = (request) => {
3438
4242
  decoded.set(request, pending);
3439
4243
  return pending;
3440
4244
  };
3441
- var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4245
+ var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3442
4246
  var nonEmpty = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
3443
4247
  var tokenFromBody = (body) => {
3444
4248
  if (Array.isArray(body)) return tokenFromBody(body[0]);
3445
- if (!isRecord5(body)) return void 0;
4249
+ if (!isRecord6(body)) return void 0;
3446
4250
  const direct = nonEmpty(body.token) ?? nonEmpty(body.api_key);
3447
4251
  if (direct) return direct;
3448
4252
  if (Array.isArray(body.batch)) return tokenFromBody(body.batch[0]);
3449
- if (isRecord5(body.properties)) return nonEmpty(body.properties.token);
4253
+ if (isRecord6(body.properties)) return nonEmpty(body.properties.token);
3450
4254
  return void 0;
3451
4255
  };
3452
4256
  var ARRAY_PATH = /^\/array\/([^/]+)\/config(\.js)?\/?$/;
@@ -3520,7 +4324,7 @@ var legacyMaps = (evaluations) => {
3520
4324
  }
3521
4325
  return { featureFlags, featureFlagPayloads };
3522
4326
  };
3523
- var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4327
+ var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3524
4328
  var payloadString = (payload) => payload === void 0 || payload === null ? null : JSON.stringify(payload);
3525
4329
  var parsePayload2 = (payload) => {
3526
4330
  if (payload === null) return null;
@@ -3549,7 +4353,7 @@ var adminView = (flag) => ({
3549
4353
  var isValue = (value) => typeof value === "boolean" || typeof value === "string" && value.length > 0;
3550
4354
  var parseFlagSpec = (body) => {
3551
4355
  if (body === void 0) return { default: null, payload: null, overrides: [] };
3552
- if (!isRecord6(body)) return "expected a JSON object";
4356
+ if (!isRecord7(body)) return "expected a JSON object";
3553
4357
  const value = body.default ?? body.value;
3554
4358
  if (value !== void 0 && value !== null && !isValue(value)) {
3555
4359
  return "default must be true, false, a variant string, or null (absent)";
@@ -3558,7 +4362,7 @@ var parseFlagSpec = (body) => {
3558
4362
  if (body.overrides !== void 0) {
3559
4363
  if (!Array.isArray(body.overrides)) return "overrides must be a list";
3560
4364
  for (const each of body.overrides) {
3561
- if (!isRecord6(each)) return "each override is {distinct_id?|email?, value, payload?}";
4365
+ if (!isRecord7(each)) return "each override is {distinct_id?|email?, value, payload?}";
3562
4366
  const id = each.distinct_id ?? each.distinctId;
3563
4367
  const email = each.email;
3564
4368
  if (id === void 0 && email === void 0) return "each override needs distinct_id or email";
@@ -3639,10 +4443,10 @@ var restView = (flag) => {
3639
4443
  };
3640
4444
  var propertyValues = (value) => Array.isArray(value) ? value.filter((v) => typeof v === "string" || typeof v === "number").map(String) : typeof value === "string" || typeof value === "number" ? [String(value)] : [];
3641
4445
  var fromFilters = (filters) => {
3642
- const record = isRecord6(filters) ? filters : {};
3643
- const groups = Array.isArray(record.groups) ? record.groups.filter(isRecord6) : [];
3644
- const multivariate = isRecord6(record.multivariate) ? record.multivariate : void 0;
3645
- const variants = Array.isArray(multivariate?.variants) ? multivariate.variants.filter(isRecord6) : [];
4446
+ const record = isRecord7(filters) ? filters : {};
4447
+ const groups = Array.isArray(record.groups) ? record.groups.filter(isRecord7) : [];
4448
+ const multivariate = isRecord7(record.multivariate) ? record.multivariate : void 0;
4449
+ const variants = Array.isArray(multivariate?.variants) ? multivariate.variants.filter(isRecord7) : [];
3646
4450
  const topVariant = [...variants].sort((a, b) => Number(b.rollout_percentage ?? 0) - Number(a.rollout_percentage ?? 0)).map((v) => v.key).find((key) => typeof key === "string" && key.length > 0);
3647
4451
  const on = (group) => {
3648
4452
  const rollout = group.rollout_percentage ?? 100;
@@ -3653,7 +4457,7 @@ var fromFilters = (filters) => {
3653
4457
  const overrides = [];
3654
4458
  let fallback = groups.length === 0 ? false : null;
3655
4459
  for (const group of groups) {
3656
- const properties = Array.isArray(group.properties) ? group.properties.filter(isRecord6) : [];
4460
+ const properties = Array.isArray(group.properties) ? group.properties.filter(isRecord7) : [];
3657
4461
  if (properties.length === 0) {
3658
4462
  const rollout = group.rollout_percentage ?? 100;
3659
4463
  const value = typeof rollout === "number" && rollout < 100 ? false : on(group);
@@ -3670,14 +4474,14 @@ var fromFilters = (filters) => {
3670
4474
  }
3671
4475
  }
3672
4476
  if (fallback === null) fallback = false;
3673
- const payloads = isRecord6(record.payloads) ? record.payloads : {};
4477
+ const payloads = isRecord7(record.payloads) ? record.payloads : {};
3674
4478
  const rawPayload = typeof fallback === "string" && fallback in payloads ? payloads[fallback] : payloads.true;
3675
4479
  const payload = rawPayload === void 0 || rawPayload === null ? null : typeof rawPayload === "string" ? rawPayload : JSON.stringify(rawPayload);
3676
4480
  return { default: fallback, payload, overrides };
3677
4481
  };
3678
4482
 
3679
4483
  // src/generated/openapi.ts
3680
- var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"PostHog feature flags, capture and management API (Mockingbird subset)","description":"Stateful mock subset of PostHog: remote flag evaluation (\`/flags\` v2 and the legacy\\n\`/decide\` shape), remote config, event capture (\`/batch/\`, \`/e/\`, \`/i/v0/e/\`), session\\nrecording intake, the posthog-js asset and survey endpoints, and the slice of the\\nmanagement API our tooling and crons call (feature-flag list/create/patch, HogQL query).\\nHand-authored from the wire shapes of posthog-node 5.52.2 / @posthog/core 1.54.0,\\nposthog-js 1.433.2 and our own raw fetches.\\n\\nPaths are written without the trailing slash PostHog uses; the mock answers both\\n(\`/flags/?v=2\` from the SDKs and \`/flags?v=2\` from the EMR frontend's raw fetch).\\nCapture and flag bodies may arrive gzip-compressed (\`Content-Encoding: gzip\`, or raw gzip\\nbytes from posthog-js), base64 (\`data=\` form bodies), or as plain JSON; the contract\\ndeclares the decoded JSON.\\n","version":"1","x-mockingbird-upstream":{"note":"Hand-authored from @posthog/core posthog-core-stateless (getFlags, sendBatch, getRemoteConfig, getSurveysStateless), posthog-js request.ts / posthog-featureflags.ts, and the consumer's posthog-server.adapter.ts, posthog-server.ts (EMR frontend), website-posthog-purchase.sink.ts, posthog-hogql.client.ts and feature-flags-cli."}},"servers":[{"url":"https://us.i.posthog.com"}],"paths":{"/flags":{"post":{"operationId":"EvaluateFlags","description":"Remote flag evaluation. \`v=2\` (what every current SDK sends) answers the \`flags\` detail\\nmap; no \`v\` or \`v=1\` answers the legacy \`featureFlags\`/\`featureFlagPayloads\` maps.\\n\`config=true\` adds the remote-config fields. The project key is \`token\` (SDKs) or\\n\`api_key\` (the EMR frontend's raw fetch).\\n","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Version"},{"$ref":"#/components/parameters/Config"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlagsRequest"}}}},"responses":{"200":{"description":"Evaluated flags","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlagsResponse"}}}},"400":{"description":"Malformed body or missing distinct_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No project API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Rate limited (fault preset flags_429)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"500":{"description":"Server error (fault preset flags_5xx)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/decide":{"post":{"operationId":"Decide","description":"The legacy endpoint (the Python client sends \`POST /decide/?v=3\` with\\n\`{api_key, distinct_id}\`). \`v\` < 4 answers the legacy maps; \`v=4\` the \`flags\` map.\\n","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Version"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlagsRequest"}}}},"responses":{"200":{"description":"Evaluated flags (legacy shape)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlagsResponse"}}}},"400":{"description":"Malformed body or missing distinct_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No project API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Rate limited (fault preset flags_429)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"500":{"description":"Server error (fault preset flags_5xx)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/array/{token}/config":{"parameters":[{"$ref":"#/components/parameters/TokenPath"}],"get":{"operationId":"GetRemoteConfig","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Remote config (posthog-react-native, @posthog/core)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoteConfig"}}}}}}},"/array/{token}/config.js":{"parameters":[{"$ref":"#/components/parameters/TokenPath"}],"get":{"operationId":"GetRemoteConfigScript","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Remote config as a script that sets \`window._POSTHOG_REMOTE_CONFIG[token]\` (posthog-js)","content":{"application/javascript":{"schema":{"type":"string"}}}}}}},"/batch":{"post":{"operationId":"CaptureBatch","description":"posthog-node and posthog-react-native flushes (Content-Encoding: gzip).","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchRequest"}}}},"responses":{"200":{"description":"Accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaptureResponse"}}}},"400":{"description":"Malformed body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No project API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"500":{"description":"Server error (fault)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/e":{"post":{"operationId":"CaptureEvent","description":"One event, an array of events, or \`{api_key, batch}\` (posthog-js).","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaptureRequest"}}}},"responses":{"200":{"description":"Accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaptureResponse"}}}},"400":{"description":"Malformed body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No project API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"500":{"description":"Server error (fault)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/i/v0/e":{"post":{"operationId":"CaptureEventV0","description":"The capture endpoint remote config advertises (\`analytics.endpoint\`); also our backend's website purchase sink (\`{api_key, event, distinct_id, timestamp, uuid, properties}\`).","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaptureRequest"}}}},"responses":{"200":{"description":"Accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaptureResponse"}}}},"400":{"description":"Malformed body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No project API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"500":{"description":"Server error (fault)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/s":{"post":{"operationId":"CaptureRecording","description":"Session-recording snapshots; counted, never stored.","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaptureRequest"}}}},"responses":{"200":{"description":"Accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaptureResponse"}}}}}}},"/static/recorder.js":{"get":{"operationId":"GetRecorderScript","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The session recorder bundle (an inert script here)","content":{"application/javascript":{"schema":{"type":"string"}}}}}}},"/static/{version}/recorder.js":{"parameters":[{"name":"version","in":"path","required":true,"schema":{"type":"string","pattern":"^[0-9]+\\\\.[0-9]+\\\\.[0-9]+$"}}],"get":{"operationId":"GetVersionedRecorderScript","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The versioned session recorder bundle (an inert script here)","content":{"application/javascript":{"schema":{"type":"string"}}}}}}},"/api/surveys":{"get":{"operationId":"ListSurveys","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/TokenQuery"}],"responses":{"200":{"description":"No surveys","content":{"application/json":{"schema":{"type":"object","required":["surveys"],"properties":{"surveys":{"type":"array","maxItems":0}}}}}},"401":{"description":"No project API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/web_experiments":{"get":{"operationId":"ListWebExperiments","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/TokenQuery"}],"responses":{"200":{"description":"No web experiments","content":{"application/json":{"schema":{"type":"object","required":["experiments"],"properties":{"experiments":{"type":"array","maxItems":0}}}}}},"401":{"description":"No project API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/projects/{projectId}/feature_flags":{"parameters":[{"$ref":"#/components/parameters/ProjectId"}],"get":{"operationId":"ListFeatureFlags","security":[{"bearerAuth":[]}],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"maximum":1000}}],"responses":{"200":{"description":"One page of flags (feature-flags-cli follows \`next\`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeatureFlagPage"}}}},"401":{"description":"No personal API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}},"post":{"operationId":"CreateFeatureFlag","security":[{"bearerAuth":[]}],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFeatureFlagBody"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeatureFlag"}}}},"400":{"description":"Invalid, or the key already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No personal API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/projects/{projectId}/feature_flags/{flagId}":{"parameters":[{"$ref":"#/components/parameters/ProjectId"},{"name":"flagId","in":"path","required":true,"description":"The flag's numeric id. The mock also accepts the flag key (how self-parity walks reference flags they created, since resource identities are strings).","schema":{"type":"string","x-mockingbird-resource-ref":{"type":"feature_flag","missing":"999999"}}}],"patch":{"operationId":"UpdateFeatureFlag","security":[{"bearerAuth":[]}],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFeatureFlagBody"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeatureFlag"}}}},"400":{"description":"Invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No personal API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"No such flag","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/projects/{projectId}/query":{"parameters":[{"$ref":"#/components/parameters/ProjectId"}],"post":{"operationId":"RunQuery","description":"HogQL (the marketing-metrics crons). Answers canned results set with \`PUT /__admin/settings {\\"queryResults\\": [...]}\`; \`{results: []}\` otherwise.","security":[{"bearerAuth":[]}],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequest"}}}},"responses":{"200":{"description":"Query results","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponse"}}}},"400":{"description":"Not a HogQL query","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No personal API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"parameters":{"Version":{"name":"v","in":"query","schema":{"type":"integer","minimum":1,"maximum":4}},"Config":{"name":"config","in":"query","schema":{"type":"string","enum":["true","false"]}},"TokenPath":{"name":"token","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,64}$"}},"TokenQuery":{"name":"token","in":"query","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,64}$"}},"ProjectId":{"name":"projectId","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":999999999}}},"schemas":{"ErrorBody":{"type":"object","required":["type","code","detail"],"properties":{"type":{"type":"string"},"code":{"type":"string"},"detail":{"type":"string"},"attr":{"type":["string","null"]}}},"FlagsRequest":{"type":"object","required":["distinct_id"],"properties":{"token":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,64}$"},"api_key":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,64}$"},"distinct_id":{"type":"string","minLength":1,"maxLength":64},"groups":{"type":"object"},"person_properties":{"type":"object","properties":{"email":{"type":"string","maxLength":120}}},"group_properties":{"type":"object"},"$device_id":{"type":"string","maxLength":64},"$anon_distinct_id":{"type":"string","maxLength":64},"evaluation_contexts":{"type":"array","maxItems":5,"items":{"type":"string","maxLength":40}},"evaluation_runtime":{"type":"string","enum":["server","client","all"]},"flag_keys_to_evaluate":{"type":"array","maxItems":10,"items":{"type":"string","maxLength":64}},"geoip_disable":{"type":"boolean"},"disable_flags":{"type":"boolean"}}},"FlagDetail":{"type":"object","required":["key","enabled","variant","reason","metadata"],"properties":{"key":{"type":"string"},"enabled":{"type":"boolean"},"variant":{"type":["string","null"]},"failed":{"type":"boolean"},"reason":{"type":"object","required":["code","description"],"properties":{"code":{"type":"string"},"condition_index":{"type":["integer","null"]},"description":{"type":"string"}}},"metadata":{"type":"object","required":["id","version"],"properties":{"id":{"type":"integer"},"version":{"type":"integer"},"description":{"type":["string","null"]},"payload":{"type":"string","description":"The payload as a JSON string."}}}}},"FlagsResponse":{"type":"object","required":["errorsWhileComputingFlags"],"properties":{"flags":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/FlagDetail"}},"featureFlags":{"type":"object","additionalProperties":{"type":["boolean","string"]}},"featureFlagPayloads":{"type":"object","additionalProperties":{"type":"string"}},"errorsWhileComputingFlags":{"type":"boolean"},"quotaLimited":{"type":"array","items":{"type":"string"}},"requestId":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"evaluatedAt":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"config":{"type":"object"},"supportedCompression":{"type":"array","items":{"type":"string"}},"sessionRecording":{"oneOf":[{"type":"boolean"},{"type":"object"}]}}},"RemoteConfig":{"type":"object","required":["token","supportedCompression","hasFeatureFlags","analytics","sessionRecording"],"properties":{"token":{"type":"string"},"supportedCompression":{"type":"array","items":{"type":"string"}},"hasFeatureFlags":{"type":"boolean"},"analytics":{"type":"object","required":["endpoint"],"properties":{"endpoint":{"type":"string"}}},"sessionRecording":{"oneOf":[{"type":"boolean"},{"type":"object","required":["endpoint"],"properties":{"endpoint":{"type":"string"}}}]},"surveys":{"type":"boolean"},"heatmaps":{"type":"boolean"},"siteApps":{"type":"array"}}},"CapturedEventBody":{"type":"object","required":["event"],"properties":{"event":{"type":"string","minLength":1,"maxLength":64},"distinct_id":{"type":"string","minLength":1,"maxLength":64},"properties":{"type":"object"},"timestamp":{"type":"string","format":"date-time"},"uuid":{"type":"string","format":"uuid"}}},"BatchRequest":{"type":"object","required":["api_key","batch"],"properties":{"api_key":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,64}$"},"batch":{"type":"array","maxItems":5,"items":{"$ref":"#/components/schemas/CapturedEventBody"}},"sent_at":{"type":"string","format":"date-time"}}},"CaptureRequest":{"type":"object","required":["api_key","event"],"properties":{"api_key":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,64}$"},"event":{"type":"string","minLength":1,"maxLength":64},"distinct_id":{"type":"string","minLength":1,"maxLength":64},"properties":{"type":"object"},"timestamp":{"type":"string","format":"date-time"},"uuid":{"type":"string","format":"uuid"}}},"CaptureResponse":{"type":"object","required":["status"],"properties":{"status":{"oneOf":[{"type":"string"},{"type":"integer"}]}}},"FeatureFlag":{"type":"object","required":["id","key","name","active","filters"],"properties":{"id":{"type":"integer"},"key":{"type":"string","x-mockingbird-resource":{"type":"feature_flag","identity":true}},"name":{"type":"string"},"active":{"type":"boolean"},"deleted":{"type":"boolean"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"version":{"type":"integer"},"filters":{"type":"object","required":["groups"],"properties":{"groups":{"type":"array","items":{"type":"object"}},"multivariate":{"oneOf":[{"type":"null"},{"type":"object"}]},"payloads":{"type":"object"}}}}},"FeatureFlagPage":{"type":"object","required":["count","next","previous","results"],"properties":{"count":{"type":"integer"},"next":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"url"}},"previous":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"url"}},"results":{"type":"array","items":{"$ref":"#/components/schemas/FeatureFlag"}}}},"FlagGroup":{"type":"object","properties":{"properties":{"type":"array","maxItems":2,"items":{"type":"object","required":["key","value"],"properties":{"key":{"type":"string","enum":["distinct_id","email"]},"type":{"type":"string","enum":["person"]},"operator":{"type":"string","enum":["exact"]},"value":{"type":"array","maxItems":3,"items":{"type":"string","minLength":1,"maxLength":40}}}}},"rollout_percentage":{"type":"integer","minimum":0,"maximum":100},"variant":{"type":["string","null"],"maxLength":20}}},"FlagFilters":{"type":"object","properties":{"groups":{"type":"array","maxItems":3,"items":{"$ref":"#/components/schemas/FlagGroup"}},"payloads":{"type":"object","properties":{"true":{"type":"string","maxLength":200}}}}},"CreateFeatureFlagBody":{"type":"object","required":["key"],"properties":{"key":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,64}$"},"name":{"type":"string","maxLength":120},"active":{"type":"boolean"},"filters":{"$ref":"#/components/schemas/FlagFilters"}}},"UpdateFeatureFlagBody":{"type":"object","properties":{"name":{"type":"string","maxLength":120},"active":{"type":"boolean"},"deleted":{"type":"boolean"},"filters":{"$ref":"#/components/schemas/FlagFilters"}}},"QueryRequest":{"type":"object","required":["query"],"properties":{"query":{"type":"object","required":["kind","query"],"properties":{"kind":{"type":"string","enum":["HogQLQuery"]},"query":{"type":"string","minLength":1,"maxLength":500}}}}},"QueryResponse":{"type":"object","required":["results"],"properties":{"results":{"type":"array","items":{"type":"array"}},"columns":{"type":"array","items":{"type":"string"}},"is_cached":{"type":"boolean"}}}}}}`);
4484
+ var document2 = JSON.parse(`{"openapi":"3.1.0","info":{"title":"PostHog feature flags, capture and management API (Mockingbird subset)","description":"Stateful mock subset of PostHog: remote flag evaluation (\`/flags\` v2 and the legacy\\n\`/decide\` shape), remote config, event capture (\`/batch/\`, \`/e/\`, \`/i/v0/e/\`), session\\nrecording intake, the posthog-js asset and survey endpoints, and the slice of the\\nmanagement API our tooling and crons call (feature-flag list/create/patch, HogQL query).\\nHand-authored from the wire shapes of posthog-node 5.52.2 / @posthog/core 1.54.0,\\nposthog-js 1.433.2 and our own raw fetches.\\n\\nPaths are written without the trailing slash PostHog uses; the mock answers both\\n(\`/flags/?v=2\` from the SDKs and \`/flags?v=2\` from the EMR frontend's raw fetch).\\nCapture and flag bodies may arrive gzip-compressed (\`Content-Encoding: gzip\`, or raw gzip\\nbytes from posthog-js), base64 (\`data=\` form bodies), or as plain JSON; the contract\\ndeclares the decoded JSON.\\n","version":"1","x-mockingbird-upstream":{"note":"Hand-authored from @posthog/core posthog-core-stateless (getFlags, sendBatch, getRemoteConfig, getSurveysStateless), posthog-js request.ts / posthog-featureflags.ts, and the consumer's posthog-server.adapter.ts, posthog-server.ts (EMR frontend), website-posthog-purchase.sink.ts, posthog-hogql.client.ts and feature-flags-cli."}},"servers":[{"url":"https://us.i.posthog.com"}],"paths":{"/flags":{"post":{"operationId":"EvaluateFlags","description":"Remote flag evaluation. \`v=2\` (what every current SDK sends) answers the \`flags\` detail\\nmap; no \`v\` or \`v=1\` answers the legacy \`featureFlags\`/\`featureFlagPayloads\` maps.\\n\`config=true\` adds the remote-config fields. The project key is \`token\` (SDKs) or\\n\`api_key\` (the EMR frontend's raw fetch).\\n","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Version"},{"$ref":"#/components/parameters/Config"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlagsRequest"}}}},"responses":{"200":{"description":"Evaluated flags","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlagsResponse"}}}},"400":{"description":"Malformed body or missing distinct_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No project API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Rate limited (fault preset flags_429)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"500":{"description":"Server error (fault preset flags_5xx)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/decide":{"post":{"operationId":"Decide","description":"The legacy endpoint (the Python client sends \`POST /decide/?v=3\` with\\n\`{api_key, distinct_id}\`). \`v\` < 4 answers the legacy maps; \`v=4\` the \`flags\` map.\\n","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Version"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlagsRequest"}}}},"responses":{"200":{"description":"Evaluated flags (legacy shape)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FlagsResponse"}}}},"400":{"description":"Malformed body or missing distinct_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No project API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Rate limited (fault preset flags_429)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"500":{"description":"Server error (fault preset flags_5xx)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/array/{token}/config":{"parameters":[{"$ref":"#/components/parameters/TokenPath"}],"get":{"operationId":"GetRemoteConfig","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Remote config (posthog-react-native, @posthog/core)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoteConfig"}}}}}}},"/array/{token}/config.js":{"parameters":[{"$ref":"#/components/parameters/TokenPath"}],"get":{"operationId":"GetRemoteConfigScript","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Remote config as a script that sets \`window._POSTHOG_REMOTE_CONFIG[token]\` (posthog-js)","content":{"application/javascript":{"schema":{"type":"string"}}}}}}},"/batch":{"post":{"operationId":"CaptureBatch","description":"posthog-node and posthog-react-native flushes (Content-Encoding: gzip).","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchRequest"}}}},"responses":{"200":{"description":"Accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaptureResponse"}}}},"400":{"description":"Malformed body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No project API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"500":{"description":"Server error (fault)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/e":{"post":{"operationId":"CaptureEvent","description":"One event, an array of events, or \`{api_key, batch}\` (posthog-js).","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaptureRequest"}}}},"responses":{"200":{"description":"Accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaptureResponse"}}}},"400":{"description":"Malformed body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No project API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"500":{"description":"Server error (fault)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/i/v0/e":{"post":{"operationId":"CaptureEventV0","description":"The capture endpoint remote config advertises (\`analytics.endpoint\`); also our backend's website purchase sink (\`{api_key, event, distinct_id, timestamp, uuid, properties}\`).","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaptureRequest"}}}},"responses":{"200":{"description":"Accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaptureResponse"}}}},"400":{"description":"Malformed body","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No project API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"500":{"description":"Server error (fault)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/s":{"post":{"operationId":"CaptureRecording","description":"Session-recording snapshots; counted, never stored.","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaptureRequest"}}}},"responses":{"200":{"description":"Accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaptureResponse"}}}}}}},"/static/recorder.js":{"get":{"operationId":"GetRecorderScript","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The session recorder bundle (an inert script here)","content":{"application/javascript":{"schema":{"type":"string"}}}}}}},"/static/{version}/recorder.js":{"parameters":[{"name":"version","in":"path","required":true,"schema":{"type":"string","pattern":"^[0-9]+\\\\.[0-9]+\\\\.[0-9]+$"}}],"get":{"operationId":"GetVersionedRecorderScript","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The versioned session recorder bundle (an inert script here)","content":{"application/javascript":{"schema":{"type":"string"}}}}}}},"/api/surveys":{"get":{"operationId":"ListSurveys","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/TokenQuery"}],"responses":{"200":{"description":"No surveys","content":{"application/json":{"schema":{"type":"object","required":["surveys"],"properties":{"surveys":{"type":"array","maxItems":0}}}}}},"401":{"description":"No project API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/web_experiments":{"get":{"operationId":"ListWebExperiments","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/TokenQuery"}],"responses":{"200":{"description":"No web experiments","content":{"application/json":{"schema":{"type":"object","required":["experiments"],"properties":{"experiments":{"type":"array","maxItems":0}}}}}},"401":{"description":"No project API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/projects/{projectId}/feature_flags":{"parameters":[{"$ref":"#/components/parameters/ProjectId"}],"get":{"operationId":"ListFeatureFlags","security":[{"bearerAuth":[]}],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"maximum":1000}}],"responses":{"200":{"description":"One page of flags (feature-flags-cli follows \`next\`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeatureFlagPage"}}}},"401":{"description":"No personal API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}},"post":{"operationId":"CreateFeatureFlag","security":[{"bearerAuth":[]}],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFeatureFlagBody"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeatureFlag"}}}},"400":{"description":"Invalid, or the key already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No personal API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/projects/{projectId}/feature_flags/{flagId}":{"parameters":[{"$ref":"#/components/parameters/ProjectId"},{"name":"flagId","in":"path","required":true,"description":"The flag's numeric id. The mock also accepts the flag key (how self-parity walks reference flags they created, since resource identities are strings).","schema":{"type":"string","x-mockingbird-resource-ref":{"type":"feature_flag","missing":"999999"}}}],"patch":{"operationId":"UpdateFeatureFlag","security":[{"bearerAuth":[]}],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFeatureFlagBody"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeatureFlag"}}}},"400":{"description":"Invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No personal API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"No such flag","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/projects/{projectId}/query":{"parameters":[{"$ref":"#/components/parameters/ProjectId"}],"post":{"operationId":"RunQuery","description":"HogQL (the marketing-metrics crons). Answers canned results set with \`PUT /__admin/settings {\\"queryResults\\": [...]}\`; \`{results: []}\` otherwise.","security":[{"bearerAuth":[]}],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequest"}}}},"responses":{"200":{"description":"Query results","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponse"}}}},"400":{"description":"Not a HogQL query","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No personal API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"parameters":{"Version":{"name":"v","in":"query","schema":{"type":"integer","minimum":1,"maximum":4}},"Config":{"name":"config","in":"query","schema":{"type":"string","enum":["true","false"]}},"TokenPath":{"name":"token","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,64}$"}},"TokenQuery":{"name":"token","in":"query","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,64}$"}},"ProjectId":{"name":"projectId","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":999999999}}},"schemas":{"ErrorBody":{"type":"object","required":["type","code","detail"],"properties":{"type":{"type":"string"},"code":{"type":"string"},"detail":{"type":"string"},"attr":{"type":["string","null"]}}},"FlagsRequest":{"type":"object","required":["distinct_id"],"properties":{"token":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,64}$"},"api_key":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,64}$"},"distinct_id":{"type":"string","minLength":1,"maxLength":64},"groups":{"type":"object"},"person_properties":{"type":"object","properties":{"email":{"type":"string","maxLength":120}}},"group_properties":{"type":"object"},"$device_id":{"type":"string","maxLength":64},"$anon_distinct_id":{"type":"string","maxLength":64},"evaluation_contexts":{"type":"array","maxItems":5,"items":{"type":"string","maxLength":40}},"evaluation_runtime":{"type":"string","enum":["server","client","all"]},"flag_keys_to_evaluate":{"type":"array","maxItems":10,"items":{"type":"string","maxLength":64}},"geoip_disable":{"type":"boolean"},"disable_flags":{"type":"boolean"}}},"FlagDetail":{"type":"object","required":["key","enabled","variant","reason","metadata"],"properties":{"key":{"type":"string"},"enabled":{"type":"boolean"},"variant":{"type":["string","null"]},"failed":{"type":"boolean"},"reason":{"type":"object","required":["code","description"],"properties":{"code":{"type":"string"},"condition_index":{"type":["integer","null"]},"description":{"type":"string"}}},"metadata":{"type":"object","required":["id","version"],"properties":{"id":{"type":"integer"},"version":{"type":"integer"},"description":{"type":["string","null"]},"payload":{"type":"string","description":"The payload as a JSON string."}}}}},"FlagsResponse":{"type":"object","required":["errorsWhileComputingFlags"],"properties":{"flags":{"type":"object","additionalProperties":{"$ref":"#/components/schemas/FlagDetail"}},"featureFlags":{"type":"object","additionalProperties":{"type":["boolean","string"]}},"featureFlagPayloads":{"type":"object","additionalProperties":{"type":"string"}},"errorsWhileComputingFlags":{"type":"boolean"},"quotaLimited":{"type":"array","items":{"type":"string"}},"requestId":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"evaluatedAt":{"type":"integer","x-mockingbird-volatile":{"kind":"timestamp"}},"config":{"type":"object"},"supportedCompression":{"type":"array","items":{"type":"string"}},"sessionRecording":{"oneOf":[{"type":"boolean"},{"type":"object"}]}}},"RemoteConfig":{"type":"object","required":["token","supportedCompression","hasFeatureFlags","analytics","sessionRecording"],"properties":{"token":{"type":"string"},"supportedCompression":{"type":"array","items":{"type":"string"}},"hasFeatureFlags":{"type":"boolean"},"analytics":{"type":"object","required":["endpoint"],"properties":{"endpoint":{"type":"string"}}},"sessionRecording":{"oneOf":[{"type":"boolean"},{"type":"object","required":["endpoint"],"properties":{"endpoint":{"type":"string"}}}]},"surveys":{"type":"boolean"},"heatmaps":{"type":"boolean"},"siteApps":{"type":"array"}}},"CapturedEventBody":{"type":"object","required":["event"],"properties":{"event":{"type":"string","minLength":1,"maxLength":64},"distinct_id":{"type":"string","minLength":1,"maxLength":64},"properties":{"type":"object"},"timestamp":{"type":"string","format":"date-time"},"uuid":{"type":"string","format":"uuid"}}},"BatchRequest":{"type":"object","required":["api_key","batch"],"properties":{"api_key":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,64}$"},"batch":{"type":"array","maxItems":5,"items":{"$ref":"#/components/schemas/CapturedEventBody"}},"sent_at":{"type":"string","format":"date-time"}}},"CaptureRequest":{"type":"object","required":["api_key","event"],"properties":{"api_key":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,64}$"},"event":{"type":"string","minLength":1,"maxLength":64},"distinct_id":{"type":"string","minLength":1,"maxLength":64},"properties":{"type":"object"},"timestamp":{"type":"string","format":"date-time"},"uuid":{"type":"string","format":"uuid"}}},"CaptureResponse":{"type":"object","required":["status"],"properties":{"status":{"oneOf":[{"type":"string"},{"type":"integer"}]}}},"FeatureFlag":{"type":"object","required":["id","key","name","active","filters"],"properties":{"id":{"type":"integer"},"key":{"type":"string","x-mockingbird-resource":{"type":"feature_flag","identity":true}},"name":{"type":"string"},"active":{"type":"boolean"},"deleted":{"type":"boolean"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"version":{"type":"integer"},"filters":{"type":"object","required":["groups"],"properties":{"groups":{"type":"array","items":{"type":"object"}},"multivariate":{"oneOf":[{"type":"null"},{"type":"object"}]},"payloads":{"type":"object"}}}}},"FeatureFlagPage":{"type":"object","required":["count","next","previous","results"],"properties":{"count":{"type":"integer"},"next":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"url"}},"previous":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"url"}},"results":{"type":"array","items":{"$ref":"#/components/schemas/FeatureFlag"}}}},"FlagGroup":{"type":"object","properties":{"properties":{"type":"array","maxItems":2,"items":{"type":"object","required":["key","value"],"properties":{"key":{"type":"string","enum":["distinct_id","email"]},"type":{"type":"string","enum":["person"]},"operator":{"type":"string","enum":["exact"]},"value":{"type":"array","maxItems":3,"items":{"type":"string","minLength":1,"maxLength":40}}}}},"rollout_percentage":{"type":"integer","minimum":0,"maximum":100},"variant":{"type":["string","null"],"maxLength":20}}},"FlagFilters":{"type":"object","properties":{"groups":{"type":"array","maxItems":3,"items":{"$ref":"#/components/schemas/FlagGroup"}},"payloads":{"type":"object","properties":{"true":{"type":"string","maxLength":200}}}}},"CreateFeatureFlagBody":{"type":"object","required":["key"],"properties":{"key":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,64}$"},"name":{"type":"string","maxLength":120},"active":{"type":"boolean"},"filters":{"$ref":"#/components/schemas/FlagFilters"}}},"UpdateFeatureFlagBody":{"type":"object","properties":{"name":{"type":"string","maxLength":120},"active":{"type":"boolean"},"deleted":{"type":"boolean"},"filters":{"$ref":"#/components/schemas/FlagFilters"}}},"QueryRequest":{"type":"object","required":["query"],"properties":{"query":{"type":"object","required":["kind","query"],"properties":{"kind":{"type":"string","enum":["HogQLQuery"]},"query":{"type":"string","minLength":1,"maxLength":500}}}}},"QueryResponse":{"type":"object","required":["results"],"properties":{"results":{"type":"array","items":{"type":"array"}},"columns":{"type":"array","items":{"type":"string"}},"is_cached":{"type":"boolean"}}}}}}`);
3681
4485
  var operationIds = ["EvaluateFlags", "Decide", "GetRemoteConfig", "GetRemoteConfigScript", "CaptureBatch", "CaptureEvent", "CaptureEventV0", "CaptureRecording", "GetRecorderScript", "GetVersionedRecorderScript", "ListSurveys", "ListWebExperiments", "ListFeatureFlags", "CreateFeatureFlag", "UpdateFeatureFlag", "RunQuery"];
3682
4486
  var supportedOperationIds = ["EvaluateFlags", "Decide", "GetRemoteConfig", "GetRemoteConfigScript", "CaptureBatch", "CaptureEvent", "CaptureEventV0", "CaptureRecording", "GetRecorderScript", "GetVersionedRecorderScript", "ListSurveys", "ListWebExperiments", "ListFeatureFlags", "CreateFeatureFlag", "UpdateFeatureFlag", "RunQuery"];
3683
4487
 
@@ -3845,11 +4649,11 @@ var POSTHOG_PRESETS = {
3845
4649
  };
3846
4650
  var json5 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
3847
4651
  var adminError4 = (status, message) => json5(status, { error: { type: "mockingbird_admin", message } });
3848
- var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4652
+ var isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3849
4653
  var parseBulk = (body) => {
3850
- const source = isRecord7(body) && isRecord7(body.flags) ? body.flags : body;
3851
- const entries = Array.isArray(source) ? source.map((each) => [isRecord7(each) ? String(each.key ?? "") : "", each]) : isRecord7(source) ? Object.entries(source).filter(([key]) => key !== "replace") : [];
3852
- if (!Array.isArray(source) && !isRecord7(source)) return "expected {flags: {<key>: spec}}";
4654
+ const source = isRecord8(body) && isRecord8(body.flags) ? body.flags : body;
4655
+ const entries = Array.isArray(source) ? source.map((each) => [isRecord8(each) ? String(each.key ?? "") : "", each]) : isRecord8(source) ? Object.entries(source).filter(([key]) => key !== "replace") : [];
4656
+ if (!Array.isArray(source) && !isRecord8(source)) return "expected {flags: {<key>: spec}}";
3853
4657
  const specs = {};
3854
4658
  for (const [key, value] of entries) {
3855
4659
  if (!key) return "every flag needs a key";
@@ -3863,7 +4667,7 @@ var parseQueryResults = (value) => {
3863
4667
  if (!Array.isArray(value)) return "queryResults: [{match?, columns?, results: [[\u2026]]}]";
3864
4668
  const out = [];
3865
4669
  for (const each of value) {
3866
- if (!isRecord7(each) || !Array.isArray(each.results) || !each.results.every(Array.isArray)) {
4670
+ if (!isRecord8(each) || !Array.isArray(each.results) || !each.results.every(Array.isArray)) {
3867
4671
  return "each queryResults entry needs results: unknown[][]";
3868
4672
  }
3869
4673
  out.push({
@@ -3904,19 +4708,19 @@ var adminRoutes = (runtime) => {
3904
4708
  const specs = parseBulk(body);
3905
4709
  if (typeof specs === "string") return adminError4(400, specs);
3906
4710
  const state = api(namespace).state;
3907
- if (isRecord7(body) && body.replace === true) {
4711
+ if (isRecord8(body) && body.replace === true) {
3908
4712
  for (const flag of state.listFlags()) state.flags.delete(flag.key);
3909
4713
  }
3910
4714
  for (const [key, spec] of Object.entries(specs)) state.putFlag(key, spec);
3911
4715
  return json5(200, { flags: api(namespace).flagList() });
3912
4716
  },
3913
4717
  "POST /flags/import": ({ body, namespace }) => {
3914
- const input = isRecord7(body) ? body : {};
4718
+ const input = isRecord8(body) ? body : {};
3915
4719
  const env = input.env ?? "dev";
3916
4720
  if (env !== "dev" && env !== "prod") return adminError4(400, 'env must be "dev" or "prod"');
3917
4721
  const from = input.from ?? "state.json";
3918
4722
  let file;
3919
- if (isRecord7(input.state) && Array.isArray(input.state.flags)) {
4723
+ if (isRecord8(input.state) && Array.isArray(input.state.flags)) {
3920
4724
  file = input.state;
3921
4725
  } else if (from === "state.json") {
3922
4726
  file = ACME_FLAG_STATE;
@@ -3960,7 +4764,7 @@ var adminRoutes = (runtime) => {
3960
4764
  "GET /recordings": ({ namespace }) => json5(200, { count: api(namespace).state.current().recordings }),
3961
4765
  "GET /settings": ({ namespace }) => json5(200, api(namespace).state.current()),
3962
4766
  "PUT /settings": ({ body, namespace }) => {
3963
- if (!isRecord7(body)) return adminError4(400, "expected a JSON object");
4767
+ if (!isRecord8(body)) return adminError4(400, "expected a JSON object");
3964
4768
  const patch = {};
3965
4769
  if (body.sessionRecording !== void 0) {
3966
4770
  if (typeof body.sessionRecording !== "boolean")
@@ -3980,7 +4784,7 @@ var createRuntime2 = (options = {}) => {
3980
4784
  const tokens = /* @__PURE__ */ new WeakMap();
3981
4785
  const runtime = createRuntime({
3982
4786
  name: POSTHOG_NAMESPACE,
3983
- document,
4787
+ document: document2,
3984
4788
  ...options.sqlite ? { sqlite: options.sqlite } : {},
3985
4789
  ...options.clock ? { clock: options.clock } : {},
3986
4790
  ...options.seed !== void 0 ? { seed: options.seed } : {},
@@ -4012,7 +4816,7 @@ var createRuntime2 = (options = {}) => {
4012
4816
 
4013
4817
  // src/index.ts
4014
4818
  var POSTHOG_NAMESPACE = "posthog";
4015
- var isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4819
+ var isRecord9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4016
4820
  var error = (status, type, code, detail, attr) => jsonRes(status, { type, code, detail, attr: attr ?? null });
4017
4821
  var invalidApiKey = () => error(
4018
4822
  401,
@@ -4050,7 +4854,7 @@ var scrubProperties = (event, properties) => {
4050
4854
  }
4051
4855
  const kept = scrubRecord(properties);
4052
4856
  for (const nested of ["$set", "$set_once"]) {
4053
- if (isRecord8(kept[nested])) kept[nested] = scrubRecord(kept[nested]);
4857
+ if (isRecord9(kept[nested])) kept[nested] = scrubRecord(kept[nested]);
4054
4858
  }
4055
4859
  return kept;
4056
4860
  };
@@ -4106,7 +4910,7 @@ var PostHogAPI = class {
4106
4910
  RunQuery: (context) => this.query(context)
4107
4911
  });
4108
4912
  this.service = createService({
4109
- document,
4913
+ document: document2,
4110
4914
  handlers,
4111
4915
  sqlite,
4112
4916
  namespace,
@@ -4171,7 +4975,7 @@ var PostHogAPI = class {
4171
4975
  }
4172
4976
  flags(context, route) {
4173
4977
  const body = this.body(context);
4174
- if (!isRecord8(body)) return malformed();
4978
+ if (!isRecord9(body)) return malformed();
4175
4979
  if (!tokenFromBody(body)) return invalidApiKey();
4176
4980
  const distinctId = body.distinct_id;
4177
4981
  if (typeof distinctId !== "string" && typeof distinctId !== "number") {
@@ -4181,7 +4985,7 @@ var PostHogAPI = class {
4181
4985
  const detailed = route === "decide" ? version >= 4 : version >= 2;
4182
4986
  const subject = {
4183
4987
  distinct_id: String(distinctId),
4184
- ...isRecord8(body.person_properties) ? { person_properties: body.person_properties } : {}
4988
+ ...isRecord9(body.person_properties) ? { person_properties: body.person_properties } : {}
4185
4989
  };
4186
4990
  const keys = [body.flag_keys_to_evaluate, body.flag_keys].find(Array.isArray);
4187
4991
  const quotaLimited = this.effect(context, "quota_limited");
@@ -4214,15 +5018,15 @@ var PostHogAPI = class {
4214
5018
  }
4215
5019
  const token = tokenFromBody(body);
4216
5020
  if (!token) return invalidApiKey();
4217
- const items = Array.isArray(body) ? body : isRecord8(body) && Array.isArray(body.batch) ? body.batch : [body];
5021
+ const items = Array.isArray(body) ? body : isRecord9(body) && Array.isArray(body.batch) ? body.batch : [body];
4218
5022
  const events = items.filter(
4219
- (item) => isRecord8(item) && typeof item.event === "string" && item.event.length > 0
5023
+ (item) => isRecord9(item) && typeof item.event === "string" && item.event.length > 0
4220
5024
  );
4221
5025
  if (events.length === 0 && items.length > 0) return malformed("Invalid payload: no event name");
4222
5026
  const stored = [];
4223
5027
  for (const raw of events) {
4224
5028
  const event = raw.event;
4225
- const properties = isRecord8(raw.properties) ? raw.properties : {};
5029
+ const properties = isRecord9(raw.properties) ? raw.properties : {};
4226
5030
  const distinct = raw.distinct_id ?? properties.distinct_id ?? raw.$distinct_id ?? properties.$distinct_id;
4227
5031
  const uuid = typeof raw.uuid === "string" && raw.uuid ? raw.uuid : this.state.nextEventId();
4228
5032
  if (this.state.events.has(uuid)) continue;
@@ -4264,7 +5068,7 @@ var PostHogAPI = class {
4264
5068
  }
4265
5069
  createFlag(context) {
4266
5070
  const body = this.body(context);
4267
- if (!isRecord8(body)) return malformed();
5071
+ if (!isRecord9(body)) return malformed();
4268
5072
  const key = body.key;
4269
5073
  if (typeof key !== "string" || !/^[A-Za-z0-9_-]+$/.test(key)) {
4270
5074
  return error(
@@ -4296,7 +5100,7 @@ var PostHogAPI = class {
4296
5100
  const flag = this.state.findFlag(context.params.flagId ?? "");
4297
5101
  if (!flag) return error(404, "invalid_request", "not_found", "Not found.");
4298
5102
  const body = this.body(context);
4299
- if (!isRecord8(body)) return malformed();
5103
+ if (!isRecord9(body)) return malformed();
4300
5104
  const patch = {};
4301
5105
  if (typeof body.name === "string") patch.name = body.name;
4302
5106
  if (typeof body.active === "boolean") patch.active = body.active;
@@ -4307,7 +5111,7 @@ var PostHogAPI = class {
4307
5111
  }
4308
5112
  query(context) {
4309
5113
  const body = this.body(context);
4310
- const query = isRecord8(body) && isRecord8(body.query) ? body.query : void 0;
5114
+ const query = isRecord9(body) && isRecord9(body.query) ? body.query : void 0;
4311
5115
  if (query?.kind !== "HogQLQuery" || typeof query.query !== "string") {
4312
5116
  return error(400, "validation_error", "invalid_input", "Expected a HogQLQuery.", "query");
4313
5117
  }
@@ -4337,7 +5141,7 @@ export {
4337
5141
  evaluateFlag,
4338
5142
  payloadString,
4339
5143
  parseFlagSpec,
4340
- document,
5144
+ document2 as document,
4341
5145
  operationIds,
4342
5146
  supportedOperationIds,
4343
5147
  ACME_FLAG_STATE,
@@ -4349,4 +5153,4 @@ export {
4349
5153
  scrubProperties,
4350
5154
  PostHogAPI
4351
5155
  };
4352
- //# sourceMappingURL=chunk-UJEAOIXF.js.map
5156
+ //# sourceMappingURL=chunk-A7ITZA26.js.map