@crvouga/mockingbird-service-formbricks 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.
@@ -533,10 +533,10 @@ var OpenAPIReferenceError = class extends Error {
533
533
  };
534
534
  var unescapePointer = (segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~");
535
535
  var isReference = (value) => typeof value === "object" && value !== null && typeof value.$ref === "string";
536
- var resolveRef = (document2, ref) => {
536
+ var resolveRef = (document3, ref) => {
537
537
  if (!ref.startsWith("#/"))
538
538
  throw new OpenAPIReferenceError(ref);
539
- let cursor = document2;
539
+ let cursor = document3;
540
540
  for (const raw of ref.slice(2).split("/")) {
541
541
  const segment = unescapePointer(raw);
542
542
  if (typeof cursor !== "object" || cursor === null || !(segment in cursor)) {
@@ -548,14 +548,14 @@ var resolveRef = (document2, ref) => {
548
548
  throw new OpenAPIReferenceError(ref);
549
549
  return cursor;
550
550
  };
551
- var deref = (document2, value) => {
551
+ var deref = (document3, value) => {
552
552
  let current = value;
553
553
  const seen = /* @__PURE__ */ new Set();
554
554
  while (isReference(current)) {
555
555
  if (seen.has(current.$ref))
556
556
  throw new OpenAPIReferenceError(`${current.$ref} (cycle)`);
557
557
  seen.add(current.$ref);
558
- current = resolveRef(document2, current.$ref);
558
+ current = resolveRef(document3, current.$ref);
559
559
  }
560
560
  return current;
561
561
  };
@@ -573,36 +573,36 @@ var HTTP_METHODS = [
573
573
  ];
574
574
 
575
575
  // ../../openapi/core/dist/document.js
576
- var mergeParameters = (document2, item, own) => {
576
+ var mergeParameters = (document3, item, own) => {
577
577
  const merged = /* @__PURE__ */ new Map();
578
578
  for (const raw of item.parameters ?? []) {
579
- const parameter = deref(document2, raw);
579
+ const parameter = deref(document3, raw);
580
580
  merged.set(`${parameter.in}:${parameter.name}`, parameter);
581
581
  }
582
582
  for (const raw of own ?? []) {
583
- const parameter = deref(document2, raw);
583
+ const parameter = deref(document3, raw);
584
584
  merged.set(`${parameter.in}:${parameter.name}`, parameter);
585
585
  }
586
586
  return [...merged.values()];
587
587
  };
588
- var listOperations = (document2) => {
588
+ var listOperations = (document3) => {
589
589
  const operations = [];
590
- for (const [path, item] of Object.entries(document2.paths)) {
590
+ for (const [path, item] of Object.entries(document3.paths)) {
591
591
  for (const method of HTTP_METHODS) {
592
592
  const operation = item[method];
593
593
  if (operation?.operationId === void 0)
594
594
  continue;
595
595
  const responses = {};
596
596
  for (const [status, response] of Object.entries(operation.responses)) {
597
- responses[status] = deref(document2, response);
597
+ responses[status] = deref(document3, response);
598
598
  }
599
599
  operations.push({
600
600
  operationId: operation.operationId,
601
601
  method,
602
602
  path,
603
603
  operation,
604
- parameters: mergeParameters(document2, item, operation.parameters),
605
- requestBody: operation.requestBody === void 0 ? void 0 : deref(document2, operation.requestBody),
604
+ parameters: mergeParameters(document3, item, operation.parameters),
605
+ requestBody: operation.requestBody === void 0 ? void 0 : deref(document3, operation.requestBody),
606
606
  responses
607
607
  });
608
608
  }
@@ -1138,6 +1138,1232 @@ var clearNamespace = (sqlite, namespace) => {
1138
1138
  });
1139
1139
  };
1140
1140
 
1141
+ // ../../ui/dist/reset.js
1142
+ var CSS_RESET = `*,
1143
+ *::before,
1144
+ *::after {
1145
+ box-sizing: border-box;
1146
+ margin: 0;
1147
+ padding: 0;
1148
+ }
1149
+
1150
+ * {
1151
+ scrollbar-width: thin;
1152
+ scrollbar-color: color-mix(in srgb, CanvasText 35%, Canvas) Canvas;
1153
+ }
1154
+
1155
+ *::-webkit-scrollbar {
1156
+ width: 12px;
1157
+ height: 12px;
1158
+ }
1159
+
1160
+ *::-webkit-scrollbar-track,
1161
+ *::-webkit-scrollbar-corner {
1162
+ background: Canvas;
1163
+ }
1164
+
1165
+ *::-webkit-scrollbar-thumb {
1166
+ background: color-mix(in srgb, CanvasText 35%, Canvas);
1167
+ border: 3px solid Canvas;
1168
+ border-radius: 999px;
1169
+ }
1170
+
1171
+ html {
1172
+ hanging-punctuation: first allow-end last;
1173
+ -webkit-text-size-adjust: none;
1174
+ text-size-adjust: none;
1175
+ tab-size: 4;
1176
+ font-family:
1177
+ ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI",
1178
+ "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
1179
+ font-size: 16px;
1180
+ line-height: 1.5;
1181
+ -webkit-font-smoothing: antialiased;
1182
+ -moz-osx-font-smoothing: grayscale;
1183
+ text-rendering: optimizeLegibility;
1184
+ -webkit-tap-highlight-color: transparent;
1185
+ scroll-behavior: smooth;
1186
+ background: Canvas;
1187
+ color: CanvasText;
1188
+ }
1189
+
1190
+ body {
1191
+ min-height: 100svh;
1192
+ line-height: 1.5;
1193
+ background: Canvas;
1194
+ color: CanvasText;
1195
+ }
1196
+
1197
+ h1 {
1198
+ font-size: 2em;
1199
+ }
1200
+
1201
+ h2 {
1202
+ font-size: 1.5em;
1203
+ }
1204
+
1205
+ h3 {
1206
+ font-size: 1.17em;
1207
+ }
1208
+
1209
+ h4 {
1210
+ font-size: 1em;
1211
+ }
1212
+
1213
+ h5 {
1214
+ font-size: 0.83em;
1215
+ }
1216
+
1217
+ h6 {
1218
+ font-size: 0.67em;
1219
+ }
1220
+
1221
+ h1,
1222
+ h2,
1223
+ h3,
1224
+ h4,
1225
+ h5,
1226
+ h6 {
1227
+ font-weight: 700;
1228
+ line-height: 1.2;
1229
+ text-wrap: balance;
1230
+ overflow-wrap: break-word;
1231
+ }
1232
+
1233
+ p,
1234
+ li,
1235
+ figcaption,
1236
+ dd,
1237
+ dt,
1238
+ blockquote {
1239
+ overflow-wrap: break-word;
1240
+ }
1241
+
1242
+ p,
1243
+ li,
1244
+ figcaption,
1245
+ blockquote {
1246
+ text-wrap: pretty;
1247
+ }
1248
+
1249
+ ul,
1250
+ ol,
1251
+ menu {
1252
+ list-style: none;
1253
+ }
1254
+
1255
+ a {
1256
+ color: inherit;
1257
+ text-decoration: underline;
1258
+ text-decoration-thickness: 1px;
1259
+ text-underline-offset: 0.15em;
1260
+ text-decoration-skip-ink: auto;
1261
+ }
1262
+
1263
+ b,
1264
+ strong {
1265
+ font-weight: 700;
1266
+ }
1267
+
1268
+ small {
1269
+ font-size: 0.875em;
1270
+ }
1271
+
1272
+ code,
1273
+ kbd,
1274
+ samp,
1275
+ pre {
1276
+ font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
1277
+ font-size: 1em;
1278
+ }
1279
+
1280
+ sub,
1281
+ sup {
1282
+ font-size: 75%;
1283
+ line-height: 0;
1284
+ position: relative;
1285
+ vertical-align: baseline;
1286
+ }
1287
+
1288
+ sub {
1289
+ bottom: -0.25em;
1290
+ }
1291
+
1292
+ sup {
1293
+ top: -0.5em;
1294
+ }
1295
+
1296
+ abbr[title] {
1297
+ text-decoration: underline dotted;
1298
+ text-underline-offset: 0.15em;
1299
+ }
1300
+
1301
+ mark {
1302
+ background: Mark;
1303
+ color: MarkText;
1304
+ }
1305
+
1306
+ hr {
1307
+ height: 0;
1308
+ border: 0;
1309
+ border-top: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
1310
+ color: inherit;
1311
+ }
1312
+
1313
+ img,
1314
+ picture,
1315
+ video,
1316
+ canvas {
1317
+ display: block;
1318
+ max-width: 100%;
1319
+ }
1320
+
1321
+ img,
1322
+ video {
1323
+ height: auto;
1324
+ }
1325
+
1326
+ svg {
1327
+ overflow: hidden;
1328
+ }
1329
+
1330
+ iframe {
1331
+ border: 0;
1332
+ max-width: 100%;
1333
+ }
1334
+
1335
+ table {
1336
+ border-collapse: collapse;
1337
+ border-spacing: 0;
1338
+ }
1339
+
1340
+ fieldset {
1341
+ border: 0;
1342
+ min-inline-size: 0;
1343
+ }
1344
+
1345
+ legend {
1346
+ padding: 0;
1347
+ }
1348
+
1349
+ button,
1350
+ input,
1351
+ select,
1352
+ textarea,
1353
+ optgroup {
1354
+ font: inherit;
1355
+ letter-spacing: inherit;
1356
+ color: inherit;
1357
+ margin: 0;
1358
+ vertical-align: middle;
1359
+ }
1360
+
1361
+ button,
1362
+ input[type="button"],
1363
+ input[type="submit"],
1364
+ input[type="reset"] {
1365
+ appearance: none;
1366
+ background: none;
1367
+ border: 0;
1368
+ padding: 0;
1369
+ cursor: pointer;
1370
+ text-align: center;
1371
+ }
1372
+
1373
+ input,
1374
+ textarea,
1375
+ select {
1376
+ background-color: Canvas;
1377
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1378
+ border-radius: 0;
1379
+ padding: 0.5em 0.65em;
1380
+ }
1381
+
1382
+ input[type="checkbox"],
1383
+ input[type="radio"],
1384
+ input[type="range"],
1385
+ input[type="file"],
1386
+ input[type="color"],
1387
+ input[type="button"],
1388
+ input[type="submit"],
1389
+ input[type="reset"],
1390
+ input[type="image"],
1391
+ input[type="hidden"] {
1392
+ background: none;
1393
+ border: 0;
1394
+ padding: 0;
1395
+ border-radius: 0;
1396
+ }
1397
+
1398
+ input[type="checkbox"],
1399
+ input[type="radio"] {
1400
+ accent-color: CanvasText;
1401
+ }
1402
+
1403
+ input[type="search"] {
1404
+ appearance: none;
1405
+ background-color: Canvas;
1406
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1407
+ }
1408
+
1409
+ input[type="search"]::-webkit-search-decoration,
1410
+ input[type="search"]::-webkit-search-cancel-button,
1411
+ input[type="search"]::-webkit-search-results-button,
1412
+ input[type="search"]::-webkit-search-results-decoration {
1413
+ -webkit-appearance: none;
1414
+ display: none;
1415
+ }
1416
+
1417
+ input[type="number"] {
1418
+ appearance: textfield;
1419
+ background-color: Canvas;
1420
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1421
+ }
1422
+
1423
+ input[type="number"]::-webkit-inner-spin-button,
1424
+ input[type="number"]::-webkit-outer-spin-button {
1425
+ -webkit-appearance: none;
1426
+ margin: 0;
1427
+ }
1428
+
1429
+ textarea {
1430
+ resize: vertical;
1431
+ }
1432
+
1433
+ ::placeholder {
1434
+ opacity: 1;
1435
+ color: color-mix(in srgb, CanvasText 45%, transparent);
1436
+ }
1437
+
1438
+ :focus-visible {
1439
+ outline: 2px solid CanvasText;
1440
+ outline-offset: 2px;
1441
+ }
1442
+
1443
+ :focus:not(:focus-visible) {
1444
+ outline: none;
1445
+ }
1446
+
1447
+ :disabled,
1448
+ [disabled] {
1449
+ cursor: default;
1450
+ opacity: 0.6;
1451
+ }
1452
+
1453
+ label,
1454
+ summary,
1455
+ select,
1456
+ [role="button"] {
1457
+ cursor: pointer;
1458
+ }
1459
+
1460
+ summary {
1461
+ display: list-item;
1462
+ }
1463
+
1464
+ details {
1465
+ display: block;
1466
+ }
1467
+
1468
+ dialog {
1469
+ margin: auto;
1470
+ padding: 1rem;
1471
+ border: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
1472
+ background: Canvas;
1473
+ color: CanvasText;
1474
+ max-width: calc(100vw - 2rem);
1475
+ max-height: calc(100svh - 2rem);
1476
+ }
1477
+
1478
+ dialog:not([open]) {
1479
+ display: none;
1480
+ }
1481
+
1482
+ ::backdrop {
1483
+ background: color-mix(in srgb, CanvasText 40%, transparent);
1484
+ }
1485
+
1486
+ [hidden] {
1487
+ display: none !important;
1488
+ }
1489
+
1490
+ ::selection {
1491
+ background: Highlight;
1492
+ color: HighlightText;
1493
+ }
1494
+
1495
+ @media (prefers-reduced-motion: reduce) {
1496
+ *,
1497
+ *::before,
1498
+ *::after {
1499
+ animation-duration: 0.01ms !important;
1500
+ animation-iteration-count: 1 !important;
1501
+ transition-duration: 0.01ms !important;
1502
+ scroll-behavior: auto !important;
1503
+ }
1504
+ }
1505
+
1506
+ @media (pointer: coarse) {
1507
+ input,
1508
+ select,
1509
+ textarea {
1510
+ font-size: max(16px, 1em);
1511
+ }
1512
+ }`;
1513
+
1514
+ // ../core/dist/admin-client.js
1515
+ function isRecord2(value) {
1516
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1517
+ }
1518
+ function mountAdminBrand(doc, host, service, catalog) {
1519
+ const clip = (value, max) => {
1520
+ if (typeof value !== "string")
1521
+ return null;
1522
+ const trimmed = value.trim();
1523
+ if (trimmed === "")
1524
+ return null;
1525
+ return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
1526
+ };
1527
+ const httpUrl = (value) => {
1528
+ if (typeof value !== "string" || value.length > 2e3)
1529
+ return null;
1530
+ try {
1531
+ const url = new URL(value);
1532
+ if (url.protocol !== "https:" && url.protocol !== "http:")
1533
+ return null;
1534
+ return url.href;
1535
+ } catch {
1536
+ return null;
1537
+ }
1538
+ };
1539
+ const hexColor = (value) => {
1540
+ if (typeof value !== "string")
1541
+ return null;
1542
+ const raw = value.trim();
1543
+ return /^#[0-9a-fA-F]{3,8}$/.test(raw) ? raw : null;
1544
+ };
1545
+ const hostname = (value) => {
1546
+ try {
1547
+ return new URL(value).hostname.replace(/^www\./, "");
1548
+ } catch {
1549
+ return value;
1550
+ }
1551
+ };
1552
+ host.replaceChildren();
1553
+ host.hidden = true;
1554
+ if (!isRecord2(catalog) || !Object.hasOwn(catalog, service))
1555
+ return;
1556
+ const row = catalog[service];
1557
+ if (!isRecord2(row))
1558
+ return;
1559
+ const vendor = clip(row.vendor, 80);
1560
+ const website = httpUrl(row.website);
1561
+ const guide = httpUrl(row.guide);
1562
+ const docs = httpUrl(row.docs);
1563
+ const logo = httpUrl(row.logo);
1564
+ const color = hexColor(row.color);
1565
+ const description = clip(row.description, 280);
1566
+ if (vendor === null || website === null && guide === null && docs === null)
1567
+ return;
1568
+ const add = (node) => {
1569
+ host.append(node);
1570
+ return node;
1571
+ };
1572
+ if (logo !== null) {
1573
+ const img = doc.createElement("img");
1574
+ img.alt = "";
1575
+ img.src = logo;
1576
+ img.width = 16;
1577
+ img.height = 16;
1578
+ img.decoding = "async";
1579
+ img.referrerPolicy = "no-referrer";
1580
+ if (color !== null)
1581
+ img.style.borderColor = color;
1582
+ add(img);
1583
+ }
1584
+ const name = doc.createElement("span");
1585
+ name.className = "vendor-name";
1586
+ name.textContent = vendor;
1587
+ if (description !== null)
1588
+ name.title = description;
1589
+ add(name);
1590
+ const sep = () => {
1591
+ const node = doc.createElement("span");
1592
+ node.className = "vendor-sep";
1593
+ node.setAttribute("aria-hidden", "true");
1594
+ node.textContent = "\xB7";
1595
+ return node;
1596
+ };
1597
+ const link = (href, label, title) => {
1598
+ const node = doc.createElement("a");
1599
+ node.className = "vendor-link";
1600
+ node.href = href;
1601
+ node.target = "_blank";
1602
+ node.rel = "noopener noreferrer";
1603
+ node.referrerPolicy = "no-referrer";
1604
+ node.textContent = label;
1605
+ node.title = title;
1606
+ return node;
1607
+ };
1608
+ if (website !== null) {
1609
+ add(sep());
1610
+ add(link(website, hostname(website), `${vendor} website`));
1611
+ }
1612
+ if (guide !== null) {
1613
+ add(sep());
1614
+ add(link(guide, "Docs", `Docs for ${vendor}`));
1615
+ }
1616
+ if (docs !== null) {
1617
+ add(sep());
1618
+ add(link(docs, "API", `${vendor} API reference`));
1619
+ }
1620
+ host.removeAttribute("hidden");
1621
+ host.hidden = false;
1622
+ }
1623
+ function startAdminBrand(env) {
1624
+ const requested = env.params.get("brands");
1625
+ let brandsUrl = env.brandsUrl;
1626
+ if (requested) {
1627
+ try {
1628
+ const url = new URL(requested, env.location.href);
1629
+ const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]" || url.origin === env.location.origin;
1630
+ if ((url.protocol === "https:" || url.protocol === "http:") && local)
1631
+ brandsUrl = url.href;
1632
+ } catch {
1633
+ brandsUrl = env.brandsUrl;
1634
+ }
1635
+ }
1636
+ const vendorHost = env.$("vendor");
1637
+ const brandRoot = env.document.querySelector("[data-mockingbird-admin]");
1638
+ const brandService = brandRoot?.getAttribute("data-service") || "";
1639
+ if (vendorHost === null)
1640
+ return;
1641
+ env.fetch(brandsUrl, {
1642
+ headers: { accept: "application/json" },
1643
+ signal: AbortSignal.timeout(5e3)
1644
+ }).then(async (response) => {
1645
+ if (!response.ok)
1646
+ return null;
1647
+ const catalog = await response.json();
1648
+ return catalog;
1649
+ }).then((catalog) => {
1650
+ mountAdminBrand(env.document, vendorHost, brandService, catalog);
1651
+ }).catch(() => {
1652
+ });
1653
+ }
1654
+ function faultPresetList(value) {
1655
+ if (!isRecord2(value))
1656
+ throw new Error("Fault presets response was not an object");
1657
+ if (!Object.hasOwn(value, "presets") || value.presets === void 0 || value.presets === null) {
1658
+ return { presets: [] };
1659
+ }
1660
+ if (!Array.isArray(value.presets))
1661
+ throw new Error("Fault presets response was not a list");
1662
+ return {
1663
+ presets: value.presets.flatMap((item) => {
1664
+ if (!isRecord2(item) || typeof item.name !== "string")
1665
+ return [];
1666
+ return [{ name: item.name }];
1667
+ })
1668
+ };
1669
+ }
1670
+ function bootAdmin(config) {
1671
+ const fieldKinds = ["string", "number", "boolean", "null", "object", "array", "unknown"];
1672
+ const sources = ["declared", "inferred", "mixed"];
1673
+ const isFieldKind = (value) => typeof value === "string" && fieldKinds.some((kind) => kind === value);
1674
+ const isSource = (value) => typeof value === "string" && sources.some((source) => source === value);
1675
+ const readJson2 = (text) => {
1676
+ const value = JSON.parse(text);
1677
+ return value;
1678
+ };
1679
+ const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => {
1680
+ switch (char) {
1681
+ case "&":
1682
+ return "&amp;";
1683
+ case "<":
1684
+ return "&lt;";
1685
+ case ">":
1686
+ return "&gt;";
1687
+ case '"':
1688
+ return "&quot;";
1689
+ default:
1690
+ return "&#39;";
1691
+ }
1692
+ });
1693
+ const el = (id, kind) => {
1694
+ const node = document.getElementById(id);
1695
+ if (!(node instanceof kind))
1696
+ throw new Error(`admin shell is missing #${id}`);
1697
+ return node;
1698
+ };
1699
+ const errorMessage = (data, fallback) => {
1700
+ if (!isRecord2(data))
1701
+ return fallback;
1702
+ const error2 = data.error;
1703
+ if (!isRecord2(error2) || typeof error2.message !== "string" || error2.message === "") {
1704
+ return fallback;
1705
+ }
1706
+ return error2.message;
1707
+ };
1708
+ const requireList = (value, label) => {
1709
+ if (value === void 0 || value === null)
1710
+ return [];
1711
+ if (!Array.isArray(value))
1712
+ throw new Error(`${label} was not a list`);
1713
+ return value;
1714
+ };
1715
+ const state = {
1716
+ namespace: "default",
1717
+ key: sessionStorage.getItem("mockingbird-admin-key") || "",
1718
+ view: "overview",
1719
+ collection: "",
1720
+ shape: null,
1721
+ route: null,
1722
+ editing: null
1723
+ };
1724
+ const standard = new Set(config.standardRoutes);
1725
+ const banner = el("banner", HTMLElement);
1726
+ const keyInput = el("admin-key", HTMLInputElement);
1727
+ keyInput.value = state.key;
1728
+ const params = new URLSearchParams(location.search);
1729
+ const queryKey = params.get("key");
1730
+ if (queryKey) {
1731
+ state.key = queryKey;
1732
+ keyInput.value = state.key;
1733
+ sessionStorage.setItem("mockingbird-admin-key", state.key);
1734
+ params.delete("key");
1735
+ const next = params.toString();
1736
+ history.replaceState(null, "", location.pathname + (next ? `?${next}` : ""));
1737
+ }
1738
+ const queryNamespace = params.get("namespace");
1739
+ if (queryNamespace)
1740
+ state.namespace = queryNamespace;
1741
+ const showError = (error2) => {
1742
+ banner.textContent = error2 instanceof Error ? error2.message : String(error2);
1743
+ banner.classList.add("show");
1744
+ };
1745
+ const clearError = () => {
1746
+ banner.classList.remove("show");
1747
+ };
1748
+ const api = async (method, path, body) => {
1749
+ const headers = { accept: "application/json" };
1750
+ if (state.key !== "")
1751
+ headers[config.adminKeyHeader] = state.key;
1752
+ if (state.namespace !== "")
1753
+ headers["x-mockingbird-namespace"] = state.namespace;
1754
+ if (body !== void 0)
1755
+ headers["content-type"] = "application/json";
1756
+ const init = { method, headers };
1757
+ if (body !== void 0)
1758
+ init.body = JSON.stringify(body);
1759
+ const response = await fetch(config.adminPrefix + path, init);
1760
+ const text = await response.text();
1761
+ let data = null;
1762
+ if (text !== "") {
1763
+ try {
1764
+ data = readJson2(text);
1765
+ } catch {
1766
+ data = { raw: text };
1767
+ }
1768
+ }
1769
+ if (response.status === 401) {
1770
+ keyInput.focus();
1771
+ throw new Error(errorMessage(data, "Admin key required"));
1772
+ }
1773
+ if (!response.ok) {
1774
+ throw new Error(errorMessage(data, `${response.status} ${method} ${path}`));
1775
+ }
1776
+ return data;
1777
+ };
1778
+ const originGet = async (path) => {
1779
+ const response = await fetch(path, { headers: { accept: "application/json" } });
1780
+ if (!response.ok)
1781
+ throw new Error(`${response.status} ${path}`);
1782
+ const value = await response.json();
1783
+ return value;
1784
+ };
1785
+ const panelApi = {
1786
+ get: (path) => api("GET", path),
1787
+ send: (method, path, body) => api(method, path, body),
1788
+ namespace: () => state.namespace
1789
+ };
1790
+ const isPanelScript = (value) => typeof value === "function";
1791
+ const setTheme = (theme) => {
1792
+ if (theme)
1793
+ document.documentElement.dataset.theme = theme;
1794
+ else
1795
+ delete document.documentElement.dataset.theme;
1796
+ };
1797
+ const storedTheme = localStorage.getItem("mockingbird-admin-theme");
1798
+ if (storedTheme)
1799
+ setTheme(storedTheme);
1800
+ el("theme", HTMLButtonElement).addEventListener("click", () => {
1801
+ const current = document.documentElement.dataset.theme;
1802
+ const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
1803
+ const next = dark ? "light" : "dark";
1804
+ localStorage.setItem("mockingbird-admin-theme", next);
1805
+ setTheme(next);
1806
+ });
1807
+ const show = (view) => {
1808
+ state.view = view;
1809
+ for (const button of document.querySelectorAll("#nav button")) {
1810
+ if (!(button instanceof HTMLButtonElement))
1811
+ continue;
1812
+ button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false");
1813
+ }
1814
+ for (const section of document.querySelectorAll("main .view")) {
1815
+ if (!(section instanceof HTMLElement))
1816
+ continue;
1817
+ section.hidden = section.id !== `view-${view}`;
1818
+ }
1819
+ };
1820
+ el("nav", HTMLElement).addEventListener("click", (event) => {
1821
+ const target = event.target;
1822
+ if (!(target instanceof Element))
1823
+ return;
1824
+ const button = target.closest("button[data-view]");
1825
+ if (!(button instanceof HTMLButtonElement))
1826
+ return;
1827
+ const view = button.dataset.view;
1828
+ if (view === void 0)
1829
+ return;
1830
+ show(view);
1831
+ refresh().catch(showError);
1832
+ });
1833
+ 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>`;
1834
+ const table = (columns, rows) => {
1835
+ if (rows.length === 0)
1836
+ return '<p class="empty">Nothing here yet.</p>';
1837
+ const head = `<tr>${columns.map((column) => `<th>${column.label}</th>`).join("")}</tr>`;
1838
+ const body = rows.map((row) => {
1839
+ const cells = columns.map((column) => `<td data-label="${column.label}">${column.cell(row)}</td>`).join("");
1840
+ const attrs = "attrs" in row && typeof row.attrs === "string" ? row.attrs : "";
1841
+ return `<tr${attrs}>${cells}</tr>`;
1842
+ }).join("");
1843
+ return `<table><thead>${head}</thead><tbody>${body}</tbody></table>`;
1844
+ };
1845
+ const preview = (value) => {
1846
+ const text = JSON.stringify(value) ?? "null";
1847
+ return esc(text.length > 180 ? `${text.slice(0, 180)}\u2026` : text);
1848
+ };
1849
+ const when = (epoch) => {
1850
+ const date = new Date(epoch);
1851
+ return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString();
1852
+ };
1853
+ const clockCard = (epoch) => {
1854
+ const date = new Date(epoch);
1855
+ if (Number.isNaN(date.getTime()))
1856
+ return esc(String(epoch));
1857
+ const iso = date.toISOString();
1858
+ return `${esc(iso.slice(0, 10))}<span class="clock-time">${esc(iso.slice(11, 19))} UTC</span>`;
1859
+ };
1860
+ const decodeClock = (value) => {
1861
+ if (!isRecord2(value) || typeof value.now !== "number" || typeof value.offsetMs !== "number" || typeof value.frozen !== "boolean") {
1862
+ throw new Error("Clock response was not a clock");
1863
+ }
1864
+ return { now: value.now, offsetMs: value.offsetMs, frozen: value.frozen };
1865
+ };
1866
+ const decodeField = (value) => {
1867
+ if (!isRecord2(value) || typeof value.name !== "string" || !isFieldKind(value.kind)) {
1868
+ return void 0;
1869
+ }
1870
+ const field = {
1871
+ name: value.name,
1872
+ kind: value.kind,
1873
+ optional: value.optional === true
1874
+ };
1875
+ if (typeof value.description === "string")
1876
+ field.description = value.description;
1877
+ if (Array.isArray(value.fields)) {
1878
+ field.fields = value.fields.flatMap((item) => {
1879
+ const child = decodeField(item);
1880
+ return child === void 0 ? [] : [child];
1881
+ });
1882
+ }
1883
+ return field;
1884
+ };
1885
+ const decodeCollection = (value) => {
1886
+ if (!isRecord2(value) || typeof value.name !== "string")
1887
+ return void 0;
1888
+ const fields = Array.isArray(value.fields) ? value.fields.flatMap((item) => {
1889
+ const field = decodeField(item);
1890
+ return field === void 0 ? [] : [field];
1891
+ }) : [];
1892
+ const collection = {
1893
+ name: value.name,
1894
+ label: typeof value.label === "string" ? value.label : value.name,
1895
+ count: typeof value.count === "number" ? value.count : 0,
1896
+ source: isSource(value.source) ? value.source : "inferred",
1897
+ fields
1898
+ };
1899
+ if (typeof value.description === "string")
1900
+ collection.description = value.description;
1901
+ return collection;
1902
+ };
1903
+ const decodeState = (value) => {
1904
+ if (!isRecord2(value) || !Array.isArray(value.collections)) {
1905
+ throw new Error("State response has no collection list");
1906
+ }
1907
+ return {
1908
+ namespace: typeof value.namespace === "string" ? value.namespace : state.namespace,
1909
+ storageNamespace: typeof value.storageNamespace === "string" ? value.storageNamespace : "",
1910
+ collections: value.collections.flatMap((item) => {
1911
+ const collection = decodeCollection(item);
1912
+ return collection === void 0 ? [] : [collection];
1913
+ })
1914
+ };
1915
+ };
1916
+ const decodeRecords = (value) => requireList(isRecord2(value) ? value.records : void 0, "Records").flatMap((item) => {
1917
+ if (!isRecord2(item) || typeof item.id !== "string")
1918
+ return [];
1919
+ return [
1920
+ {
1921
+ id: item.id,
1922
+ seq: typeof item.seq === "number" ? item.seq : 0,
1923
+ value: item.value,
1924
+ attrs: ` class="clickable" data-id="${esc(item.id)}"`
1925
+ }
1926
+ ];
1927
+ });
1928
+ const textOf = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : "";
1929
+ const decodeJournal = (value) => requireList(isRecord2(value) ? value.requests : void 0, "Journal").flatMap((item) => {
1930
+ if (!isRecord2(item))
1931
+ return [];
1932
+ return [
1933
+ {
1934
+ at: textOf(item.at),
1935
+ method: textOf(item.method),
1936
+ path: textOf(item.path),
1937
+ status: textOf(item.status),
1938
+ durationMs: textOf(item.durationMs),
1939
+ operationId: textOf(item.operationId)
1940
+ }
1941
+ ];
1942
+ });
1943
+ const decodeFaults = (value) => requireList(isRecord2(value) ? value.faults : void 0, "Faults").flatMap((item) => {
1944
+ if (!isRecord2(item))
1945
+ return [];
1946
+ return [
1947
+ {
1948
+ id: textOf(item.id),
1949
+ method: typeof item.method === "string" ? item.method : void 0,
1950
+ pathPrefix: typeof item.pathPrefix === "string" ? item.pathPrefix : void 0,
1951
+ operationId: typeof item.operationId === "string" ? item.operationId : void 0,
1952
+ status: typeof item.status === "number" ? item.status : void 0,
1953
+ effect: typeof item.effect === "string" ? item.effect : void 0,
1954
+ drop: typeof item.drop === "boolean" ? item.drop : void 0,
1955
+ latencyMs: typeof item.latencyMs === "number" ? item.latencyMs : void 0
1956
+ }
1957
+ ];
1958
+ });
1959
+ const loadNamespaces = async () => {
1960
+ const data = await api("GET", "/namespaces");
1961
+ const select = el("namespace", HTMLSelectElement);
1962
+ const listed = requireList(isRecord2(data) ? data.namespaces : void 0, "Namespaces");
1963
+ const fallback = isRecord2(data) && typeof data.default === "string" && data.default !== "" ? data.default : "default";
1964
+ const names = (listed.length > 0 ? listed.map((name) => String(name)) : [fallback]).slice();
1965
+ if (!names.includes(state.namespace))
1966
+ names.unshift(state.namespace);
1967
+ select.innerHTML = names.map((name) => `<option value="${esc(name)}">${esc(name)}</option>`).join("");
1968
+ select.value = state.namespace;
1969
+ };
1970
+ const renderOverview = async () => {
1971
+ const [health, shape, clock, journal] = await Promise.all([
1972
+ originGet("/health"),
1973
+ api("GET", "/state"),
1974
+ api("GET", "/clock"),
1975
+ api("GET", "/requests?limit=8")
1976
+ ]);
1977
+ if (!isRecord2(health))
1978
+ throw new Error("Health response was not an object");
1979
+ const decoded = decodeState(shape);
1980
+ state.shape = decoded;
1981
+ const time = decodeClock(clock);
1982
+ el("overview-cards", HTMLElement).innerHTML = cards([
1983
+ {
1984
+ k: "Service",
1985
+ v: esc(typeof health.service === "string" && health.service !== "" ? health.service : config.service)
1986
+ },
1987
+ {
1988
+ k: "Status",
1989
+ v: esc(typeof health.status === "string" && health.status !== "" ? health.status : "ok")
1990
+ },
1991
+ { k: "Namespace", v: esc(state.namespace) },
1992
+ { k: "Collections", v: String(decoded.collections.length) },
1993
+ {
1994
+ k: "Records",
1995
+ v: String(decoded.collections.reduce((sum, item) => sum + item.count, 0))
1996
+ },
1997
+ { k: "Clock", v: clockCard(time.now) }
1998
+ ]);
1999
+ el("overview-requests", HTMLElement).innerHTML = table([
2000
+ { label: "Method", cell: (row) => esc(row.method) },
2001
+ { label: "Path", cell: (row) => esc(row.path) },
2002
+ {
2003
+ label: "Status",
2004
+ cell: (row) => `<span class="status-${row.status.charAt(0)}">${esc(row.status)}</span>`
2005
+ },
2006
+ { label: "Operation", cell: (row) => esc(row.operationId) }
2007
+ ], decodeJournal(journal));
2008
+ };
2009
+ const schemaChips = (collection) => {
2010
+ if (collection.fields.length === 0) {
2011
+ return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>';
2012
+ }
2013
+ return `<div class="chips">${collection.fields.map((field) => `<span class="chip">${esc(field.name)} <i>${esc(field.kind)}${field.optional ? "?" : ""}</i></span>`).join("")}</div>`;
2014
+ };
2015
+ const renderState = async () => {
2016
+ const shape = decodeState(await api("GET", "/state"));
2017
+ state.shape = shape;
2018
+ const host = el("collections", HTMLElement);
2019
+ if (shape.collections.length === 0) {
2020
+ host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>';
2021
+ el("schema", HTMLElement).innerHTML = "";
2022
+ el("records", HTMLElement).innerHTML = "";
2023
+ return;
2024
+ }
2025
+ if (!shape.collections.some((item) => item.name === state.collection)) {
2026
+ const first = shape.collections[0];
2027
+ if (first === void 0)
2028
+ return;
2029
+ state.collection = first.name;
2030
+ }
2031
+ 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("");
2032
+ const selected = shape.collections.find((item) => item.name === state.collection);
2033
+ if (selected === void 0)
2034
+ return;
2035
+ const description = selected.description ? `<p class="lede">${esc(selected.description)}</p>` : "";
2036
+ el("schema", HTMLElement).innerHTML = `<h3>${esc(selected.label)}</h3>${description}${schemaChips(selected)}`;
2037
+ const page = await api("GET", `/state/${encodeURIComponent(state.collection)}?limit=50`);
2038
+ el("records", HTMLElement).innerHTML = table([
2039
+ { label: "Id", cell: (row) => `<code>${esc(row.id)}</code>` },
2040
+ { label: "Seq", cell: (row) => esc(row.seq) },
2041
+ { label: "Value", cell: (row) => preview(row.value) }
2042
+ ], decodeRecords(page));
2043
+ };
2044
+ const renderClock = async () => {
2045
+ const clock = decodeClock(await api("GET", "/clock"));
2046
+ 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>`;
2047
+ const freeze = el("freeze", HTMLButtonElement);
2048
+ freeze.textContent = clock.frozen ? "Unfreeze" : "Freeze";
2049
+ freeze.dataset.frozen = clock.frozen ? "1" : "0";
2050
+ };
2051
+ const renderFaults = async () => {
2052
+ const [faults, presets] = await Promise.all([
2053
+ api("GET", "/faults"),
2054
+ api("GET", "/faults/presets").catch(() => ({ presets: [] }))
2055
+ ]);
2056
+ const select = el("fault-preset", HTMLSelectElement);
2057
+ const current = select.value;
2058
+ const list = faultPresetList(presets).presets;
2059
+ select.innerHTML = `<option value="">Custom rule</option>${list.map((preset) => `<option value="${esc(preset.name)}">${esc(preset.name)}</option>`).join("")}`;
2060
+ select.value = current;
2061
+ el("fault-list", HTMLElement).innerHTML = table([
2062
+ { label: "Id", cell: (row) => esc(row.id) },
2063
+ {
2064
+ label: "Match",
2065
+ cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter((part) => Boolean(part)).join(" ") || "all")
2066
+ },
2067
+ {
2068
+ label: "Effect",
2069
+ cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || ""))
2070
+ }
2071
+ ], decodeFaults(faults));
2072
+ };
2073
+ const renderJournal = async () => {
2074
+ const journal = await api("GET", "/requests?limit=100");
2075
+ el("journal", HTMLElement).innerHTML = table([
2076
+ { label: "When", cell: (row) => esc(row.at) },
2077
+ { label: "Method", cell: (row) => esc(row.method) },
2078
+ { label: "Path", cell: (row) => esc(row.path) },
2079
+ { label: "Status", cell: (row) => esc(row.status) },
2080
+ { label: "ms", cell: (row) => esc(row.durationMs) }
2081
+ ], decodeJournal(journal));
2082
+ };
2083
+ const renderRoutes = async () => {
2084
+ const data = await api("GET", "/");
2085
+ if (!isRecord2(data))
2086
+ throw new Error("Route list was not an object");
2087
+ const routes = requireList(data.routes, "Routes").map((route) => String(route));
2088
+ el("route-list", HTMLElement).innerHTML = routes.map((route) => {
2089
+ const extra = standard.has(route) ? "" : " <span class='badge'>extension</span>";
2090
+ const parts = route.split(" ");
2091
+ const method = parts[0] ?? "";
2092
+ const path = parts.slice(1).join(" ");
2093
+ 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>`;
2094
+ }).join("");
2095
+ };
2096
+ const renderers = {
2097
+ overview: renderOverview,
2098
+ state: renderState,
2099
+ clock: renderClock,
2100
+ faults: renderFaults,
2101
+ journal: renderJournal,
2102
+ routes: renderRoutes
2103
+ };
2104
+ async function refresh() {
2105
+ clearError();
2106
+ const render = renderers[state.view];
2107
+ if (render)
2108
+ await render();
2109
+ }
2110
+ el("namespace", HTMLSelectElement).addEventListener("change", () => {
2111
+ state.namespace = el("namespace", HTMLSelectElement).value;
2112
+ refresh().catch(showError);
2113
+ });
2114
+ keyInput.addEventListener("change", () => {
2115
+ state.key = keyInput.value.trim();
2116
+ sessionStorage.setItem("mockingbird-admin-key", state.key);
2117
+ refresh().catch(showError);
2118
+ });
2119
+ el("refresh", HTMLButtonElement).addEventListener("click", () => {
2120
+ refresh().catch(showError);
2121
+ });
2122
+ el("collections", HTMLElement).addEventListener("click", (event) => {
2123
+ const target = event.target;
2124
+ if (!(target instanceof Element))
2125
+ return;
2126
+ const button = target.closest("[data-collection]");
2127
+ if (!(button instanceof HTMLElement))
2128
+ return;
2129
+ const collection = button.dataset.collection;
2130
+ if (collection === void 0)
2131
+ return;
2132
+ state.collection = collection;
2133
+ renderState().catch(showError);
2134
+ });
2135
+ el("records", HTMLElement).addEventListener("click", (event) => {
2136
+ const target = event.target;
2137
+ if (!(target instanceof Element))
2138
+ return;
2139
+ const row = target.closest("[data-id]");
2140
+ if (!(row instanceof HTMLElement))
2141
+ return;
2142
+ const id = row.dataset.id;
2143
+ if (id === void 0)
2144
+ return;
2145
+ openEditor(id).catch(showError);
2146
+ });
2147
+ const editor = el("editor", HTMLDialogElement);
2148
+ const editorId = el("editor-id", HTMLInputElement);
2149
+ const editorValue = el("editor-value", HTMLTextAreaElement);
2150
+ const editorDelete = el("editor-delete", HTMLButtonElement);
2151
+ async function openEditor(id) {
2152
+ state.editing = id;
2153
+ el("editor-title", HTMLElement).textContent = id ? "Edit record" : "New record";
2154
+ editorId.value = id ?? "";
2155
+ editorId.readOnly = id !== null;
2156
+ editorDelete.hidden = id === null;
2157
+ if (id) {
2158
+ const record = await api("GET", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`);
2159
+ if (!isRecord2(record))
2160
+ throw new Error("Record response was not an object");
2161
+ editorValue.value = JSON.stringify(record.value, null, 2) ?? "";
2162
+ } else {
2163
+ editorValue.value = "{\n \n}";
2164
+ }
2165
+ editor.showModal();
2166
+ }
2167
+ el("new-record", HTMLButtonElement).addEventListener("click", () => {
2168
+ openEditor(null).catch(showError);
2169
+ });
2170
+ el("editor-form", HTMLFormElement).addEventListener("submit", (event) => {
2171
+ const submitter = event.submitter;
2172
+ if (!(submitter instanceof HTMLButtonElement) || submitter.value !== "save")
2173
+ return;
2174
+ event.preventDefault();
2175
+ let value;
2176
+ try {
2177
+ value = readJson2(editorValue.value);
2178
+ } catch {
2179
+ showError(new Error("Value is not JSON"));
2180
+ return;
2181
+ }
2182
+ const id = editorId.value.trim();
2183
+ const path = `/state/${encodeURIComponent(state.collection)}`;
2184
+ const request = state.editing ? api("PUT", `${path}/${encodeURIComponent(id)}`, { value }) : api("POST", path, { id: id || void 0, value });
2185
+ request.then(() => {
2186
+ editor.close();
2187
+ return renderState();
2188
+ }).catch(showError);
2189
+ });
2190
+ editorDelete.addEventListener("click", () => {
2191
+ const id = state.editing;
2192
+ if (!id)
2193
+ return;
2194
+ api("DELETE", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`).then(() => {
2195
+ editor.close();
2196
+ return renderState();
2197
+ }).catch(showError);
2198
+ });
2199
+ for (const button of document.querySelectorAll("[data-advance]")) {
2200
+ if (!(button instanceof HTMLButtonElement))
2201
+ continue;
2202
+ button.addEventListener("click", () => {
2203
+ const raw = button.dataset.advance;
2204
+ if (raw === void 0)
2205
+ return;
2206
+ api("POST", "/clock", { advance: Number(raw) }).then(() => renderClock()).catch(showError);
2207
+ });
2208
+ }
2209
+ el("freeze", HTMLButtonElement).addEventListener("click", () => {
2210
+ const frozen = el("freeze", HTMLButtonElement).dataset.frozen === "1";
2211
+ api("POST", "/clock", { freeze: !frozen }).then(() => renderClock()).catch(showError);
2212
+ });
2213
+ el("clock-reset", HTMLButtonElement).addEventListener("click", () => {
2214
+ api("POST", "/clock", { reset: true }).then(() => renderClock()).catch(showError);
2215
+ });
2216
+ el("clock-set", HTMLFormElement).addEventListener("submit", (event) => {
2217
+ event.preventDefault();
2218
+ const raw = el("clock-instant", HTMLInputElement).value.trim();
2219
+ const set = /^-?\d+$/.test(raw) ? Number(raw) : raw;
2220
+ api("POST", "/clock", { set }).then(() => renderClock()).catch(showError);
2221
+ });
2222
+ el("fault-form", HTMLFormElement).addEventListener("submit", (event) => {
2223
+ event.preventDefault();
2224
+ const preset = el("fault-preset", HTMLSelectElement).value;
2225
+ if (preset !== "") {
2226
+ api("POST", "/faults", { preset }).then(() => renderFaults()).catch(showError);
2227
+ return;
2228
+ }
2229
+ const status = Number(el("fault-status", HTMLInputElement).value);
2230
+ if (!Number.isFinite(status)) {
2231
+ showError(new Error("Status must be a number, or choose a preset"));
2232
+ return;
2233
+ }
2234
+ const path = el("fault-path", HTMLInputElement).value;
2235
+ const method = el("fault-method", HTMLInputElement).value;
2236
+ api("POST", "/faults", {
2237
+ status,
2238
+ ...path !== "" ? { pathPrefix: path } : {},
2239
+ ...method !== "" ? { method } : {}
2240
+ }).then(() => renderFaults()).catch(showError);
2241
+ });
2242
+ el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
2243
+ api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
2244
+ });
2245
+ el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
2246
+ api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
2247
+ });
2248
+ el("route-list", HTMLElement).addEventListener("click", (event) => {
2249
+ const target = event.target;
2250
+ if (!(target instanceof Element))
2251
+ return;
2252
+ const button = target.closest("[data-route]");
2253
+ if (!(button instanceof HTMLElement))
2254
+ return;
2255
+ const route = button.dataset.route;
2256
+ if (route === void 0)
2257
+ return;
2258
+ state.route = route;
2259
+ el("route-title", HTMLElement).textContent = route;
2260
+ const method = route.split(" ")[0] ?? "";
2261
+ el("route-body", HTMLTextAreaElement).disabled = method === "GET" || method === "DELETE";
2262
+ });
2263
+ el("route-form", HTMLFormElement).addEventListener("submit", (event) => {
2264
+ event.preventDefault();
2265
+ const send = async () => {
2266
+ if (state.route === null)
2267
+ throw new Error("Choose a route first");
2268
+ const parts = state.route.split(" ");
2269
+ const method = parts[0];
2270
+ const path = parts.slice(1).join(" ");
2271
+ if (method === void 0)
2272
+ throw new Error("Choose a route first");
2273
+ const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_match, key) => prompt(`Value for ${key}`) || "");
2274
+ let body;
2275
+ const routeBody = el("route-body", HTMLTextAreaElement);
2276
+ if (method !== "GET" && method !== "DELETE" && routeBody.value.trim() !== "") {
2277
+ try {
2278
+ body = readJson2(routeBody.value);
2279
+ } catch {
2280
+ throw new Error("Body is not JSON");
2281
+ }
2282
+ }
2283
+ const data = await api(method, concrete, body);
2284
+ el("route-result", HTMLElement).textContent = JSON.stringify(data, null, 2);
2285
+ };
2286
+ send().catch(showError);
2287
+ });
2288
+ const decodePanel = (value) => {
2289
+ if (!isRecord2(value) || typeof value.id !== "string" || typeof value.title !== "string") {
2290
+ return void 0;
2291
+ }
2292
+ const panel = {
2293
+ id: value.id,
2294
+ title: value.title,
2295
+ description: typeof value.description === "string" ? value.description : "",
2296
+ html: typeof value.html === "string" ? value.html : ""
2297
+ };
2298
+ if (typeof value.script === "string")
2299
+ panel.script = value.script;
2300
+ return panel;
2301
+ };
2302
+ const mountPanels = (panels) => {
2303
+ const nav = el("nav", HTMLElement);
2304
+ const views = el("views", HTMLElement);
2305
+ for (const panel of panels) {
2306
+ const button = document.createElement("button");
2307
+ button.type = "button";
2308
+ button.dataset.view = panel.id;
2309
+ button.textContent = panel.title;
2310
+ nav.append(button);
2311
+ const section = document.createElement("section");
2312
+ section.className = "view";
2313
+ section.id = `view-${panel.id}`;
2314
+ section.hidden = true;
2315
+ const title = document.createElement("h2");
2316
+ title.textContent = panel.title;
2317
+ const lede = document.createElement("p");
2318
+ lede.className = "lede";
2319
+ lede.textContent = panel.description;
2320
+ const body = document.createElement("div");
2321
+ body.className = "panel-body";
2322
+ body.id = `panel-${panel.id}`;
2323
+ body.innerHTML = panel.html;
2324
+ section.append(title, lede, body);
2325
+ views.append(section);
2326
+ if (panel.script === void 0)
2327
+ continue;
2328
+ try {
2329
+ const compiled = new Function("root", "api", panel.script);
2330
+ if (!isPanelScript(compiled))
2331
+ throw new Error("panel script did not compile");
2332
+ compiled(body, panelApi);
2333
+ } catch (error2) {
2334
+ const note = document.createElement("p");
2335
+ note.className = "banner show";
2336
+ note.textContent = error2 instanceof Error ? error2.message : String(error2);
2337
+ body.append(note);
2338
+ }
2339
+ }
2340
+ };
2341
+ startAdminBrand({
2342
+ document,
2343
+ $: (id) => document.getElementById(id),
2344
+ params,
2345
+ location: { href: location.href, origin: location.origin },
2346
+ fetch,
2347
+ brandsUrl: config.brandsUrl
2348
+ });
2349
+ loadNamespaces().then(() => api("GET", "/ui/manifest")).then((manifest) => {
2350
+ const panels = requireList(isRecord2(manifest) ? manifest.panels : void 0, "Panels").flatMap((panel) => {
2351
+ const decoded = decodePanel(panel);
2352
+ return decoded === void 0 ? [] : [decoded];
2353
+ });
2354
+ mountPanels(panels);
2355
+ return refresh();
2356
+ }).catch(showError);
2357
+ }
2358
+ var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
2359
+ var adminClientSource = (config) => {
2360
+ const payload = JSON.stringify(config).replace(/</g, "\\u003c");
2361
+ return `<script>
2362
+ ${clientPrelude}
2363
+ bootAdmin(${payload});
2364
+ </script>`;
2365
+ };
2366
+
1141
2367
  // ../core/dist/surface.js
1142
2368
  var defineMock = (runtime) => runtime;
1143
2369
  var STANDARD_ADMIN_ROUTES = [
@@ -1206,94 +2432,106 @@ var escapeHtml = (value) => value.replace(/[&<>"']/g, (char) => {
1206
2432
  return "&#39;";
1207
2433
  }
1208
2434
  });
2435
+ var ADMIN_BRANDS_URL = "https://mockingbird.chrisvouga.dev/brands.json";
1209
2436
  var renderAdminDocument = (service) => {
1210
2437
  const name = escapeHtml(service);
1211
- const standard = JSON.stringify(STANDARD_ADMIN_ROUTES);
2438
+ const script = adminClientSource({
2439
+ standardRoutes: STANDARD_ADMIN_ROUTES,
2440
+ adminPrefix: ADMIN_PREFIX,
2441
+ adminKeyHeader: ADMIN_KEY_HEADER,
2442
+ brandsUrl: ADMIN_BRANDS_URL,
2443
+ service
2444
+ });
1212
2445
  return `<!DOCTYPE html>
1213
2446
  <html lang="en">
1214
2447
  <head>
1215
2448
  <meta charset="utf-8">
1216
2449
  <meta name="viewport" content="width=device-width, initial-scale=1">
1217
2450
  <meta name="color-scheme" content="light dark">
1218
- <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">
2451
+ <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">
1219
2452
  <title>${name} admin</title>
1220
2453
  <style>
2454
+ ${CSS_RESET}
1221
2455
  :root {
1222
2456
  color-scheme: light;
1223
- --bg: #f6f5f2;
2457
+ --bg: #fafafa;
1224
2458
  --bg-elev: #ffffff;
1225
- --bg-muted: #eeece6;
1226
- --bg-inset: #f3f1eb;
1227
- --line: #e2dfd6;
1228
- --line-strong: #d0ccc1;
1229
- --ink: #1c1915;
1230
- --muted: #5e584f;
1231
- --faint: #8a8378;
1232
- --accent: #3d4f3a;
1233
- --accent-ink: #f7f6f2;
1234
- --accent-soft: #e5ece2;
1235
- --ok: #1f7a45;
1236
- --ok-soft: #e5f4eb;
1237
- --warn: #9a6700;
1238
- --warn-soft: #fbf3df;
1239
- --err: #a33b32;
1240
- --err-soft: #fdecea;
1241
- --shadow: 0 1px 2px rgb(28 25 21 / 0.05), 0 12px 32px rgb(28 25 21 / 0.06);
1242
- --sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
2459
+ --bg-muted: #f4f4f5;
2460
+ --bg-inset: #f4f4f5;
2461
+ --line: #dedee3;
2462
+ --line-strong: #c4c4cc;
2463
+ --ink: #18181b;
2464
+ --muted: #62626b;
2465
+ --faint: #71717a;
2466
+ --accent: #27272a;
2467
+ --accent-ink: #ffffff;
2468
+ --accent-soft: #f4f4f5;
2469
+ --ok: #166534;
2470
+ --ok-soft: #f0fdf4;
2471
+ --warn: #a16207;
2472
+ --warn-soft: #fefce8;
2473
+ --err: #a82d32;
2474
+ --err-soft: #fef2f2;
2475
+ --shadow: none;
2476
+ --sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
1243
2477
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
1244
- --radius: 14px;
1245
- --header: 60px;
1246
- --nav: 220px;
2478
+ --radius: 10px;
2479
+ --header: 56px;
2480
+ --nav: 200px;
1247
2481
  }
1248
2482
  :root[data-theme="dark"] {
1249
2483
  color-scheme: dark;
1250
- --bg: #141311;
1251
- --bg-elev: #1e1c19;
1252
- --bg-muted: #26231f;
1253
- --bg-inset: #181715;
1254
- --line: #34302b;
1255
- --line-strong: #4a453d;
1256
- --ink: #f4f1ea;
1257
- --muted: #c4bdb1;
1258
- --faint: #8f877c;
1259
- --accent: #c5d5b8;
1260
- --accent-ink: #1a2118;
1261
- --accent-soft: #2a3328;
1262
- --ok: #8fd6a8;
1263
- --ok-soft: #1a2e22;
1264
- --warn: #f0cc78;
1265
- --warn-soft: #332911;
1266
- --err: #f0a8a2;
1267
- --err-soft: #3a201e;
1268
- --shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 16px 40px rgb(0 0 0 / 0.28);
2484
+ --bg: #111113;
2485
+ --bg-elev: #19191c;
2486
+ --bg-muted: #242428;
2487
+ --bg-inset: #111113;
2488
+ --line: #36363c;
2489
+ --line-strong: #52525b;
2490
+ --ink: #f4f4f5;
2491
+ --muted: #a9a9b2;
2492
+ --faint: #71717a;
2493
+ --accent: #e4e4e7;
2494
+ --accent-ink: #18181b;
2495
+ --accent-soft: #242428;
2496
+ --ok: #86efac;
2497
+ --ok-soft: #14241b;
2498
+ --warn: #fde68a;
2499
+ --warn-soft: #2a2410;
2500
+ --err: #ffaaaa;
2501
+ --err-soft: #2c1516;
2502
+ --shadow: none;
1269
2503
  }
1270
2504
  @media (prefers-color-scheme: dark) {
1271
2505
  :root:not([data-theme="light"]) {
1272
2506
  color-scheme: dark;
1273
- --bg: #141311;
1274
- --bg-elev: #1e1c19;
1275
- --bg-muted: #26231f;
1276
- --bg-inset: #181715;
1277
- --line: #34302b;
1278
- --line-strong: #4a453d;
1279
- --ink: #f4f1ea;
1280
- --muted: #c4bdb1;
1281
- --faint: #8f877c;
1282
- --accent: #c5d5b8;
1283
- --accent-ink: #1a2118;
1284
- --accent-soft: #2a3328;
1285
- --ok: #8fd6a8;
1286
- --ok-soft: #1a2e22;
1287
- --warn: #f0cc78;
1288
- --warn-soft: #332911;
1289
- --err: #f0a8a2;
1290
- --err-soft: #3a201e;
1291
- --shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 16px 40px rgb(0 0 0 / 0.28);
2507
+ --bg: #111113;
2508
+ --bg-elev: #19191c;
2509
+ --bg-muted: #242428;
2510
+ --bg-inset: #111113;
2511
+ --line: #36363c;
2512
+ --line-strong: #52525b;
2513
+ --ink: #f4f4f5;
2514
+ --muted: #a9a9b2;
2515
+ --faint: #71717a;
2516
+ --accent: #e4e4e7;
2517
+ --accent-ink: #18181b;
2518
+ --accent-soft: #242428;
2519
+ --ok: #86efac;
2520
+ --ok-soft: #14241b;
2521
+ --warn: #fde68a;
2522
+ --warn-soft: #2a2410;
2523
+ --err: #ffaaaa;
2524
+ --err-soft: #2c1516;
2525
+ --shadow: none;
1292
2526
  }
1293
2527
  }
1294
- * { box-sizing: border-box; }
1295
- html, body { margin: 0; height: 100%; }
2528
+ /* Fill the viewport, then grow with the content so the background covers the scroll. */
2529
+ html { height: 100%; }
1296
2530
  body {
2531
+ margin: 0;
2532
+ min-height: 100%;
2533
+ display: flex;
2534
+ flex-direction: column;
1297
2535
  font-family: var(--sans);
1298
2536
  background: var(--bg);
1299
2537
  color: var(--ink);
@@ -1302,24 +2540,37 @@ var renderAdminDocument = (service) => {
1302
2540
  padding-right: env(safe-area-inset-right);
1303
2541
  -webkit-text-size-adjust: 100%;
1304
2542
  }
1305
- button, input, select, textarea { font: inherit; color: inherit; }
1306
- button { cursor: pointer; }
1307
2543
  :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
1308
- @media (prefers-reduced-motion: reduce) {
1309
- * { scroll-behavior: auto !important; transition: none !important; }
1310
- }
1311
- .app { min-height: 100%; display: flex; flex-direction: column; }
2544
+ .app { flex: 1 0 auto; display: flex; flex-direction: column; min-width: 0; width: 100%; }
1312
2545
  header.top {
1313
2546
  position: sticky; top: 0; z-index: 5;
1314
2547
  display: flex; align-items: center; gap: 12px;
1315
2548
  min-height: var(--header); padding: 10px 16px;
1316
- background: color-mix(in srgb, var(--bg-elev) 92%, transparent);
2549
+ background: var(--bg-elev);
1317
2550
  border-bottom: 1px solid var(--line);
1318
- backdrop-filter: blur(10px);
1319
- }
1320
- .brand { display: flex; flex-direction: column; min-width: 0; margin-right: auto; }
1321
- .brand strong { font-size: 15px; letter-spacing: -0.01em; }
1322
- .brand span { color: var(--faint); font-size: 12px; }
2551
+ min-width: 0; max-width: 100%;
2552
+ }
2553
+ .brand {
2554
+ display: flex; flex-flow: row wrap; align-items: center; gap: 14px;
2555
+ min-width: 0; max-width: 100%; margin-right: auto;
2556
+ }
2557
+ .brand-id { display: flex; flex-direction: column; min-width: 0; flex: none; }
2558
+ .brand-id strong { font-size: 15px; letter-spacing: -0.01em; }
2559
+ .brand-id span { color: var(--faint); font-size: 12px; }
2560
+ .vendor {
2561
+ display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
2562
+ min-width: 0; max-width: 100%; font-size: 12px; color: var(--muted);
2563
+ }
2564
+ .vendor[hidden] { display: none; }
2565
+ /* Logos are drawn for a light tile, same as the docs site, so the plate stays white. */
2566
+ .vendor img {
2567
+ width: 16px; height: 16px; padding: 2px; box-sizing: content-box;
2568
+ border-radius: 4px; border: 1px solid var(--line); background: #fff; object-fit: contain;
2569
+ }
2570
+ .vendor-name { color: var(--ink); font-weight: 600; }
2571
+ .vendor-sep { color: var(--faint); }
2572
+ .vendor-link { color: inherit; text-decoration: none; }
2573
+ .vendor-link:hover { color: var(--ink); text-decoration: underline; }
1323
2574
  .controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
1324
2575
  label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
1325
2576
  label.field input, label.field select, .controls input[type="password"] {
@@ -1337,7 +2588,7 @@ var renderAdminDocument = (service) => {
1337
2588
  record or the full nav row would widen the page past the viewport. */
1338
2589
  .layout {
1339
2590
  display: grid; grid-template-columns: var(--nav) minmax(0, 1fr);
1340
- flex: 1; min-height: 0; min-width: 0;
2591
+ flex: 1 0 auto; min-width: 0;
1341
2592
  }
1342
2593
  nav.side {
1343
2594
  border-right: 1px solid var(--line); padding: 12px;
@@ -1391,8 +2642,8 @@ var renderAdminDocument = (service) => {
1391
2642
  .banner.show { display: block; }
1392
2643
  .empty { color: var(--muted); padding: 24px 8px; }
1393
2644
  dialog {
1394
- border: 1px solid var(--line); border-radius: 16px; padding: 0; background: var(--bg-elev);
1395
- color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow: var(--shadow);
2645
+ border: 1px solid var(--line); border-radius: var(--radius); padding: 0; background: var(--bg-elev);
2646
+ color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow: none;
1396
2647
  }
1397
2648
  dialog::backdrop { background: rgb(20 19 17 / 0.45); }
1398
2649
  dialog form, .dialog-body { padding: 16px; display: flex; flex-direction: column; gap: 10px; }
@@ -1419,7 +2670,7 @@ var renderAdminDocument = (service) => {
1419
2670
  padding-top: max(10px, env(safe-area-inset-top));
1420
2671
  }
1421
2672
  input, select, textarea { font-size: 16px; }
1422
- .controls { width: 100%; }
2673
+ .brand, .vendor, .controls { width: 100%; }
1423
2674
  label.field, label.field select, label.field input { flex: 1; min-width: 0; }
1424
2675
  .layout { grid-template-columns: minmax(0, 1fr); }
1425
2676
  nav.side {
@@ -1450,8 +2701,11 @@ var renderAdminDocument = (service) => {
1450
2701
  <div class="app" data-service="${name}" data-mockingbird-admin>
1451
2702
  <header class="top">
1452
2703
  <div class="brand">
1453
- <strong>${name}</strong>
1454
- <span>Mockingbird admin</span>
2704
+ <div class="brand-id">
2705
+ <strong>${name}</strong>
2706
+ <span>Admin</span>
2707
+ </div>
2708
+ <div class="vendor" id="vendor" hidden></div>
1455
2709
  </div>
1456
2710
  <div class="controls">
1457
2711
  <label class="field">Namespace
@@ -1581,460 +2835,7 @@ var renderAdminDocument = (service) => {
1581
2835
  </div>
1582
2836
  </form>
1583
2837
  </dialog>
1584
- <script>
1585
- (() => {
1586
- const STANDARD = new Set(${standard})
1587
- const state = {
1588
- namespace: "default",
1589
- key: sessionStorage.getItem("mockingbird-admin-key") || "",
1590
- view: "overview",
1591
- collection: "",
1592
- shape: null,
1593
- route: null,
1594
- editing: null
1595
- }
1596
- const $ = (id) => document.getElementById(id)
1597
- const banner = $("banner")
1598
- const keyInput = $("admin-key")
1599
- keyInput.value = state.key
1600
- const params = new URLSearchParams(location.search)
1601
- if (params.get("key")) {
1602
- state.key = params.get("key")
1603
- keyInput.value = state.key
1604
- sessionStorage.setItem("mockingbird-admin-key", state.key)
1605
- params.delete("key")
1606
- const next = params.toString()
1607
- history.replaceState(null, "", location.pathname + (next ? "?" + next : ""))
1608
- }
1609
- if (params.get("namespace")) state.namespace = params.get("namespace")
1610
-
1611
- const showError = (error) => {
1612
- banner.textContent = error instanceof Error ? error.message : String(error)
1613
- banner.classList.add("show")
1614
- }
1615
- const clearError = () => banner.classList.remove("show")
1616
-
1617
- async function originGet(path) {
1618
- const response = await fetch(path, { headers: { accept: "application/json" } })
1619
- if (!response.ok) throw new Error(response.status + " " + path)
1620
- return response.json()
1621
- }
1622
- async function api(method, path, body) {
1623
- const headers = { accept: "application/json" }
1624
- if (state.key) headers["${ADMIN_KEY_HEADER}"] = state.key
1625
- if (state.namespace) headers["x-mockingbird-namespace"] = state.namespace
1626
- if (body !== undefined) headers["content-type"] = "application/json"
1627
- const response = await fetch("${ADMIN_PREFIX}" + path, {
1628
- method,
1629
- headers,
1630
- body: body === undefined ? undefined : JSON.stringify(body)
1631
- })
1632
- const text = await response.text()
1633
- let data = null
1634
- if (text) {
1635
- try { data = JSON.parse(text) } catch { data = { raw: text } }
1636
- }
1637
- if (response.status === 401) {
1638
- keyInput.focus()
1639
- throw new Error((data && data.error && data.error.message) || "Admin key required")
1640
- }
1641
- if (!response.ok) {
1642
- throw new Error((data && data.error && data.error.message) || (response.status + " " + method + " " + path))
1643
- }
1644
- return data
1645
- }
1646
- const panelApi = {
1647
- get: (path) => api("GET", path),
1648
- send: (method, path, body) => api(method, path, body),
1649
- namespace: () => state.namespace
1650
- }
1651
-
1652
- function setTheme(theme) {
1653
- if (theme) document.documentElement.dataset.theme = theme
1654
- else delete document.documentElement.dataset.theme
1655
- }
1656
- const storedTheme = localStorage.getItem("mockingbird-admin-theme")
1657
- if (storedTheme) setTheme(storedTheme)
1658
- $("theme").addEventListener("click", () => {
1659
- const current = document.documentElement.dataset.theme
1660
- const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches
1661
- const next = dark ? "light" : "dark"
1662
- localStorage.setItem("mockingbird-admin-theme", next)
1663
- setTheme(next)
1664
- })
1665
-
1666
- function show(view) {
1667
- state.view = view
1668
- for (const button of document.querySelectorAll("#nav button")) {
1669
- button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false")
1670
- }
1671
- for (const section of document.querySelectorAll("main .view")) {
1672
- section.hidden = section.id !== "view-" + view
1673
- }
1674
- }
1675
- document.getElementById("nav").addEventListener("click", (event) => {
1676
- const button = event.target.closest("button[data-view]")
1677
- if (!button) return
1678
- show(button.dataset.view)
1679
- refresh().catch(showError)
1680
- })
1681
-
1682
- function cards(items) {
1683
- return '<div class="grid">' + items.map((item) =>
1684
- '<article class="card"><div class="k">' + item.k + '</div><div class="v">' + item.v + '</div></article>'
1685
- ).join("") + "</div>"
1686
- }
1687
- function table(columns, rows) {
1688
- if (rows.length === 0) return '<p class="empty">Nothing here yet.</p>'
1689
- const head = "<tr>" + columns.map((column) => "<th>" + column.label + "</th>").join("") + "</tr>"
1690
- const body = rows.map((row) => "<tr" + (row.attrs || "") + ">" + columns.map((column) =>
1691
- '<td data-label="' + column.label + '">' + column.cell(row) + "</td>"
1692
- ).join("") + "</tr>").join("")
1693
- return "<table><thead>" + head + "</thead><tbody>" + body + "</tbody></table>"
1694
- }
1695
- const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({
1696
- "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"
1697
- }[char]))
1698
- const preview = (value) => {
1699
- const text = JSON.stringify(value)
1700
- return esc(text.length > 180 ? text.slice(0, 180) + "\u2026" : text)
1701
- }
1702
- const when = (epoch) => {
1703
- const date = new Date(epoch)
1704
- return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString()
1705
- }
1706
- const clockCard = (epoch) => {
1707
- const date = new Date(epoch)
1708
- if (Number.isNaN(date.getTime())) return esc(String(epoch))
1709
- const iso = date.toISOString()
1710
- return esc(iso.slice(0, 10)) + '<span class="clock-time">' + esc(iso.slice(11, 19)) + " UTC</span>"
1711
- }
1712
-
1713
- async function loadNamespaces() {
1714
- const data = await api("GET", "/namespaces")
1715
- const select = $("namespace")
1716
- const names = data.namespaces && data.namespaces.length ? data.namespaces : [data.default || "default"]
1717
- if (!names.includes(state.namespace)) names.unshift(state.namespace)
1718
- select.innerHTML = names.map((name) =>
1719
- '<option value="' + esc(name) + '">' + esc(name) + "</option>"
1720
- ).join("")
1721
- select.value = state.namespace
1722
- }
1723
-
1724
- async function renderOverview() {
1725
- const [health, shape, clock, journal] = await Promise.all([
1726
- originGet("/health"),
1727
- api("GET", "/state"),
1728
- api("GET", "/clock"),
1729
- api("GET", "/requests?limit=8")
1730
- ])
1731
- state.shape = shape
1732
- $("overview-cards").innerHTML = cards([
1733
- { k: "Service", v: esc(health.service || "${name}") },
1734
- { k: "Status", v: esc(health.status || "ok") },
1735
- { k: "Namespace", v: esc(state.namespace) },
1736
- { k: "Collections", v: String(shape.collections.length) },
1737
- { k: "Records", v: String(shape.collections.reduce((sum, item) => sum + item.count, 0)) },
1738
- { k: "Clock", v: clockCard(clock.now) }
1739
- ])
1740
- const requests = (journal && journal.requests) || []
1741
- $("overview-requests").innerHTML = table(
1742
- [
1743
- { label: "Method", cell: (row) => esc(row.method) },
1744
- { label: "Path", cell: (row) => esc(row.path) },
1745
- { label: "Status", cell: (row) => '<span class="status-' + String(row.status).charAt(0) + '">' + esc(row.status) + "</span>" },
1746
- { label: "Operation", cell: (row) => esc(row.operationId || "") }
1747
- ],
1748
- requests
1749
- )
1750
- }
1751
-
1752
- function schemaChips(collection) {
1753
- if (!collection || collection.fields.length === 0) {
1754
- return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>'
1755
- }
1756
- return '<div class="chips">' + collection.fields.map((field) =>
1757
- '<span class="chip">' + esc(field.name) + ' <i>' + esc(field.kind) + (field.optional ? "?" : "") + "</i></span>"
1758
- ).join("") + "</div>"
1759
- }
1760
-
1761
- async function renderState() {
1762
- const shape = await api("GET", "/state")
1763
- state.shape = shape
1764
- const host = $("collections")
1765
- if (shape.collections.length === 0) {
1766
- host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>'
1767
- $("schema").innerHTML = ""
1768
- $("records").innerHTML = ""
1769
- return
1770
- }
1771
- if (!shape.collections.some((item) => item.name === state.collection)) {
1772
- state.collection = shape.collections[0].name
1773
- }
1774
- host.innerHTML = shape.collections.map((item) =>
1775
- '<button type="button" class="collection" data-collection="' + esc(item.name) + '" aria-current="' +
1776
- (item.name === state.collection ? "true" : "false") + '"><strong>' + esc(item.label) +
1777
- '</strong><small>' + esc(item.count) + " \xB7 " + esc(item.source) + "</small></button>"
1778
- ).join("")
1779
- const selected = shape.collections.find((item) => item.name === state.collection)
1780
- $("schema").innerHTML = "<h3>" + esc(selected.label) + "</h3>" +
1781
- (selected.description ? "<p class=" + '"lede">' + esc(selected.description) + "</p>" : "") +
1782
- schemaChips(selected)
1783
- const page = await api("GET", "/state/" + encodeURIComponent(state.collection) + "?limit=50")
1784
- const rows = page.records.map((record) => ({
1785
- ...record,
1786
- attrs: ' class="clickable" data-id="' + esc(record.id) + '"'
1787
- }))
1788
- $("records").innerHTML = table(
1789
- [
1790
- { label: "Id", cell: (row) => "<code>" + esc(row.id) + "</code>" },
1791
- { label: "Seq", cell: (row) => esc(row.seq) },
1792
- { label: "Value", cell: (row) => preview(row.value) }
1793
- ],
1794
- rows
1795
- )
1796
- }
1797
-
1798
- async function renderClock() {
1799
- const clock = await api("GET", "/clock")
1800
- $("clock-readout").innerHTML = "<div class='k'>Now</div><div class='v'>" + esc(when(clock.now)) +
1801
- "</div><p>" + esc(clock.now) + " ms \xB7 offset " + esc(clock.offsetMs) + " ms " +
1802
- (clock.frozen ? "<span class='badge warn'>frozen</span>" : "<span class='badge ok'>live</span>") + "</p>"
1803
- $("freeze").textContent = clock.frozen ? "Unfreeze" : "Freeze"
1804
- $("freeze").dataset.frozen = clock.frozen ? "1" : "0"
1805
- }
1806
-
1807
- async function renderFaults() {
1808
- const [faults, presets] = await Promise.all([
1809
- api("GET", "/faults"),
1810
- api("GET", "/faults/presets").catch(() => ({ presets: [] }))
1811
- ])
1812
- const select = $("fault-preset")
1813
- const current = select.value
1814
- const list = presets.presets || []
1815
- select.innerHTML = '<option value="">Custom rule</option>' + list.map((preset) =>
1816
- '<option value="' + esc(preset.name) + '">' + esc(preset.name) + "</option>"
1817
- ).join("")
1818
- select.value = current
1819
- const rules = faults.faults || []
1820
- $("fault-list").innerHTML = table(
1821
- [
1822
- { label: "Id", cell: (row) => esc(row.id) },
1823
- { label: "Match", cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter(Boolean).join(" ") || "all") },
1824
- { label: "Effect", cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || "")) }
1825
- ],
1826
- rules
1827
- )
1828
- }
1829
-
1830
- async function renderJournal() {
1831
- const journal = await api("GET", "/requests?limit=100")
1832
- $("journal").innerHTML = table(
1833
- [
1834
- { label: "When", cell: (row) => esc(row.at || "") },
1835
- { label: "Method", cell: (row) => esc(row.method) },
1836
- { label: "Path", cell: (row) => esc(row.path) },
1837
- { label: "Status", cell: (row) => esc(row.status) },
1838
- { label: "ms", cell: (row) => esc(row.durationMs) }
1839
- ],
1840
- journal.requests || []
1841
- )
1842
- }
1843
-
1844
- async function renderRoutes() {
1845
- const data = await api("GET", "/")
1846
- const routes = data.routes || []
1847
- $("route-list").innerHTML = routes.map((route) => {
1848
- const extra = STANDARD.has(route) ? "" : " <span class='badge'>extension</span>"
1849
- return '<div class="route"><span class="method">' + esc(route.split(" ")[0]) +
1850
- '</span><span>' + esc(route.split(" ").slice(1).join(" ")) + extra +
1851
- '</span><button class="btn" type="button" data-route="' + esc(route) + '">Use</button></div>'
1852
- }).join("")
1853
- }
1854
-
1855
- const renderers = {
1856
- overview: renderOverview,
1857
- state: renderState,
1858
- clock: renderClock,
1859
- faults: renderFaults,
1860
- journal: renderJournal,
1861
- routes: renderRoutes
1862
- }
1863
-
1864
- async function refresh() {
1865
- clearError()
1866
- if (state.view in renderers) await renderers[state.view]()
1867
- }
1868
-
1869
- $("namespace").addEventListener("change", () => {
1870
- state.namespace = $("namespace").value
1871
- refresh().catch(showError)
1872
- })
1873
- keyInput.addEventListener("change", () => {
1874
- state.key = keyInput.value.trim()
1875
- sessionStorage.setItem("mockingbird-admin-key", state.key)
1876
- refresh().catch(showError)
1877
- })
1878
- $("refresh").addEventListener("click", () => refresh().catch(showError))
1879
-
1880
- $("collections").addEventListener("click", (event) => {
1881
- const button = event.target.closest("[data-collection]")
1882
- if (!button) return
1883
- state.collection = button.dataset.collection
1884
- renderState().catch(showError)
1885
- })
1886
- $("records").addEventListener("click", (event) => {
1887
- const row = event.target.closest("[data-id]")
1888
- if (!row) return
1889
- openEditor(row.dataset.id).catch(showError)
1890
- })
1891
-
1892
- const editor = $("editor")
1893
- async function openEditor(id) {
1894
- state.editing = id || null
1895
- $("editor-title").textContent = id ? "Edit record" : "New record"
1896
- $("editor-id").value = id || ""
1897
- $("editor-id").readOnly = Boolean(id)
1898
- $("editor-delete").hidden = !id
1899
- if (id) {
1900
- const record = await api("GET", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
1901
- $("editor-value").value = JSON.stringify(record.value, null, 2)
1902
- } else {
1903
- $("editor-value").value = "{\\n \\n}"
1904
- }
1905
- editor.showModal()
1906
- }
1907
- $("new-record").addEventListener("click", () => openEditor(null).catch(showError))
1908
- $("editor-form").addEventListener("submit", (event) => {
1909
- const submitter = event.submitter
1910
- if (!submitter || submitter.value !== "save") return
1911
- event.preventDefault()
1912
- let value
1913
- try { value = JSON.parse($("editor-value").value) }
1914
- catch { showError(new Error("Value is not JSON")); return }
1915
- const id = $("editor-id").value.trim()
1916
- const path = "/state/" + encodeURIComponent(state.collection)
1917
- const request = state.editing
1918
- ? api("PUT", path + "/" + encodeURIComponent(id), { value })
1919
- : api("POST", path, { id: id || undefined, value })
1920
- request.then(() => { editor.close(); return renderState() }).catch(showError)
1921
- })
1922
- $("editor-delete").addEventListener("click", () => {
1923
- const id = state.editing
1924
- if (!id) return
1925
- api("DELETE", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
1926
- .then(() => { editor.close(); return renderState() })
1927
- .catch(showError)
1928
- })
1929
-
1930
- document.querySelectorAll("[data-advance]").forEach((button) => {
1931
- button.addEventListener("click", () => {
1932
- api("POST", "/clock", { advance: Number(button.dataset.advance) }).then(renderClock).catch(showError)
1933
- })
1934
- })
1935
- $("freeze").addEventListener("click", () => {
1936
- const frozen = $("freeze").dataset.frozen === "1"
1937
- api("POST", "/clock", { freeze: !frozen }).then(renderClock).catch(showError)
1938
- })
1939
- $("clock-reset").addEventListener("click", () => {
1940
- api("POST", "/clock", { reset: true }).then(renderClock).catch(showError)
1941
- })
1942
- $("clock-set").addEventListener("submit", (event) => {
1943
- event.preventDefault()
1944
- const raw = $("clock-instant").value.trim()
1945
- const set = /^-?\\d+$/.test(raw) ? Number(raw) : raw
1946
- api("POST", "/clock", { set }).then(renderClock).catch(showError)
1947
- })
1948
-
1949
- $("fault-form").addEventListener("submit", (event) => {
1950
- event.preventDefault()
1951
- const preset = $("fault-preset").value
1952
- const body = preset
1953
- ? { preset }
1954
- : {
1955
- status: Number($("fault-status").value),
1956
- ...($("fault-path").value ? { pathPrefix: $("fault-path").value } : {}),
1957
- ...($("fault-method").value ? { method: $("fault-method").value } : {})
1958
- }
1959
- if (!preset && !Number.isFinite(body.status)) {
1960
- showError(new Error("Status must be a number, or choose a preset"))
1961
- return
1962
- }
1963
- api("POST", "/faults", body).then(renderFaults).catch(showError)
1964
- })
1965
- $("fault-clear").addEventListener("click", () => api("DELETE", "/faults").then(renderFaults).catch(showError))
1966
- $("journal-clear").addEventListener("click", () => api("DELETE", "/requests").then(renderJournal).catch(showError))
1967
-
1968
- $("route-list").addEventListener("click", (event) => {
1969
- const button = event.target.closest("[data-route]")
1970
- if (!button) return
1971
- state.route = button.dataset.route
1972
- $("route-title").textContent = state.route
1973
- const method = state.route.split(" ")[0]
1974
- $("route-body").disabled = method === "GET" || method === "DELETE"
1975
- })
1976
- $("route-form").addEventListener("submit", async (event) => {
1977
- event.preventDefault()
1978
- if (!state.route) return showError(new Error("Choose a route first"))
1979
- const [method, path] = [state.route.split(" ")[0], state.route.split(" ").slice(1).join(" ")]
1980
- const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_, key) => prompt("Value for " + key) || "")
1981
- let body
1982
- if (method !== "GET" && method !== "DELETE" && $("route-body").value.trim()) {
1983
- try { body = JSON.parse($("route-body").value) }
1984
- catch { return showError(new Error("Body is not JSON")) }
1985
- }
1986
- try {
1987
- const data = await api(method, concrete, body)
1988
- $("route-result").textContent = JSON.stringify(data, null, 2)
1989
- } catch (error) {
1990
- showError(error)
1991
- }
1992
- })
1993
-
1994
- function mountPanels(panels) {
1995
- const nav = $("nav")
1996
- const views = $("views")
1997
- for (const panel of panels) {
1998
- const button = document.createElement("button")
1999
- button.type = "button"
2000
- button.dataset.view = panel.id
2001
- button.textContent = panel.title
2002
- nav.append(button)
2003
- const section = document.createElement("section")
2004
- section.className = "view"
2005
- section.id = "view-" + panel.id
2006
- section.hidden = true
2007
- const title = document.createElement("h2")
2008
- title.textContent = panel.title
2009
- const lede = document.createElement("p")
2010
- lede.className = "lede"
2011
- lede.textContent = panel.description || ""
2012
- const body = document.createElement("div")
2013
- body.className = "panel-body"
2014
- body.id = "panel-" + panel.id
2015
- body.innerHTML = panel.html || ""
2016
- section.append(title, lede, body)
2017
- views.append(section)
2018
- if (panel.script) {
2019
- try {
2020
- const run = new Function("root", "api", panel.script)
2021
- run(body, panelApi)
2022
- } catch (error) {
2023
- const note = document.createElement("p")
2024
- note.className = "banner show"
2025
- note.textContent = error instanceof Error ? error.message : String(error)
2026
- body.append(note)
2027
- }
2028
- }
2029
- }
2030
- }
2031
-
2032
- loadNamespaces()
2033
- .then(() => api("GET", "/ui/manifest"))
2034
- .then((manifest) => { mountPanels((manifest && manifest.panels) || []); return refresh() })
2035
- .catch(showError)
2036
- })()
2037
- </script>
2838
+ ${script}
2038
2839
  </body>
2039
2840
  </html>`;
2040
2841
  };
@@ -2051,10 +2852,10 @@ var json2 = (body) => new Response(JSON.stringify(body), {
2051
2852
  });
2052
2853
  var adminUiRoutes = (service, ui) => {
2053
2854
  const shell = () => renderAdminDocument(service);
2054
- const document2 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
2855
+ const document3 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
2055
2856
  return {
2056
- "GET /ui": () => htmlResponse(document2()),
2057
- "GET /ui/": () => htmlResponse(document2()),
2857
+ "GET /ui": () => htmlResponse(document3()),
2858
+ "GET /ui/": () => htmlResponse(document3()),
2058
2859
  "GET /ui/manifest": () => json2({
2059
2860
  service,
2060
2861
  panels: (ui?.panels ?? []).map((panel) => ({
@@ -2080,11 +2881,11 @@ var EXTENSION_KEYS = {
2080
2881
  };
2081
2882
 
2082
2883
  // ../../openapi/metadata/dist/read.js
2083
- var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2884
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2084
2885
  var extensionOf = (holder, key) => holder[key];
2085
2886
  var operationMetadata = (operation) => {
2086
2887
  const raw = extensionOf(operation, EXTENSION_KEYS.operation);
2087
- const ext = isRecord2(raw) ? raw : {};
2888
+ const ext = isRecord3(raw) ? raw : {};
2088
2889
  const supported = ext.supported ?? true;
2089
2890
  const parity = ext.parity ?? {};
2090
2891
  return {
@@ -2120,9 +2921,9 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
2120
2921
  this.name = "OperationRegistryError";
2121
2922
  }
2122
2923
  };
2123
- var verifyOperations = (document2, handlers) => {
2924
+ var verifyOperations = (document3, handlers) => {
2124
2925
  const problems = [];
2125
- const operations = listOperations(document2);
2926
+ const operations = listOperations(document3);
2126
2927
  const seen = /* @__PURE__ */ new Set();
2127
2928
  for (const operation of operations) {
2128
2929
  if (seen.has(operation.operationId))
@@ -2238,7 +3039,7 @@ var SAMPLE = 25;
2238
3039
  var WALK_LIMIT = 1e4;
2239
3040
  var SKIP_CTORS = /* @__PURE__ */ new Set(["Hono", "HonoRequest", "Router"]);
2240
3041
  var fail = (status, message) => ({ ok: false, status, message });
2241
- var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3042
+ var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2242
3043
  var kindOf = (value) => {
2243
3044
  if (value === null)
2244
3045
  return "null";
@@ -2337,7 +3138,7 @@ var absorb = (accum, value) => {
2337
3138
  const kind = kindOf(value);
2338
3139
  accum.kinds.add(kind);
2339
3140
  accum.present++;
2340
- if (!isRecord3(value))
3141
+ if (!isRecord4(value))
2341
3142
  return;
2342
3143
  for (const [key, child] of Object.entries(value)) {
2343
3144
  const nested = accum.nested.get(key) ?? blankAccum(key);
@@ -2362,7 +3163,7 @@ var inferFields = (records) => {
2362
3163
  if (records.length === 0)
2363
3164
  return [];
2364
3165
  const sample = records.slice(0, SAMPLE);
2365
- const objects = sample.filter((record) => isRecord3(record.value));
3166
+ const objects = sample.filter((record) => isRecord4(record.value));
2366
3167
  if (objects.length === 0) {
2367
3168
  const kinds = new Set(sample.map((record) => kindOf(record.value)));
2368
3169
  const list = [...kinds];
@@ -2549,7 +3350,7 @@ var patchStateRecord = (scope, name, id, value) => {
2549
3350
  const parsed = jsonValue(value);
2550
3351
  if (!parsed.ok)
2551
3352
  return parsed;
2552
- const next = isRecord3(current.value.value) && isRecord3(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
3353
+ const next = isRecord4(current.value.value) && isRecord4(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
2553
3354
  return replaceStateRecord(scope, name, id, next);
2554
3355
  };
2555
3356
  var deleteStateRecord = (scope, name, id) => {
@@ -2617,7 +3418,7 @@ var stateAdminRoutes = (routes) => {
2617
3418
  const scope = open(namespace);
2618
3419
  if (!scope.ok)
2619
3420
  return fromResult(scope, 201);
2620
- if (!isRecord3(body) || !("value" in body)) {
3421
+ if (!isRecord4(body) || !("value" in body)) {
2621
3422
  return adminError2(400, 'expected {"id?": "<id>", "value": <json>}');
2622
3423
  }
2623
3424
  const id = body.id;
@@ -2635,7 +3436,7 @@ var stateAdminRoutes = (routes) => {
2635
3436
  const scope = open(namespace);
2636
3437
  if (!scope.ok)
2637
3438
  return fromResult(scope, 200);
2638
- if (!isRecord3(body) || !("value" in body))
3439
+ if (!isRecord4(body) || !("value" in body))
2639
3440
  return adminError2(400, 'expected {"value": <json>}');
2640
3441
  const replaced = replaceStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
2641
3442
  if (replaced.ok)
@@ -2646,7 +3447,7 @@ var stateAdminRoutes = (routes) => {
2646
3447
  const scope = open(namespace);
2647
3448
  if (!scope.ok)
2648
3449
  return fromResult(scope, 200);
2649
- if (!isRecord3(body) || !("value" in body))
3450
+ if (!isRecord4(body) || !("value" in body))
2650
3451
  return adminError2(400, 'expected {"value": <json>}');
2651
3452
  const patched = patchStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
2652
3453
  if (patched.ok)
@@ -2666,7 +3467,7 @@ var stateAdminRoutes = (routes) => {
2666
3467
  };
2667
3468
 
2668
3469
  // ../core/dist/version.js
2669
- var PACKAGE_VERSION = true ? "1.4.0" : UNRELEASED_VERSION;
3470
+ var PACKAGE_VERSION = true ? "1.5.0" : UNRELEASED_VERSION;
2670
3471
 
2671
3472
  // ../core/dist/signing.js
2672
3473
  var encoder = new TextEncoder();
@@ -3003,9 +3804,9 @@ var createWebhookHub = (options) => {
3003
3804
  };
3004
3805
  var json4 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
3005
3806
  var adminError3 = (status, message) => json4(status, { error: { type: "mockingbird_admin", message } });
3006
- var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3807
+ var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3007
3808
  var parseEndpoint = (value) => {
3008
- if (!isRecord4(value) || typeof value.url !== "string")
3809
+ if (!isRecord5(value) || typeof value.url !== "string")
3009
3810
  return "each endpoint needs a url";
3010
3811
  try {
3011
3812
  new URL(value.url);
@@ -3022,12 +3823,12 @@ var parseEndpoint = (value) => {
3022
3823
  const events = value.events ?? value.enabledEvents;
3023
3824
  if (Array.isArray(events))
3024
3825
  endpoint.events = events.map(String);
3025
- if (isRecord4(value.tags)) {
3826
+ if (isRecord5(value.tags)) {
3026
3827
  endpoint.tags = Object.fromEntries(Object.entries(value.tags).map(([k, v]) => [k, String(v)]));
3027
3828
  }
3028
3829
  if (typeof value.account === "string")
3029
3830
  endpoint.tags = { ...endpoint.tags, account: value.account };
3030
- if (isRecord4(value.headers)) {
3831
+ if (isRecord5(value.headers)) {
3031
3832
  endpoint.headers = Object.fromEntries(Object.entries(value.headers).map(([k, v]) => [k, String(v)]));
3032
3833
  }
3033
3834
  return endpoint;
@@ -3054,7 +3855,7 @@ var webhookAdminRoutes = (hub) => ({
3054
3855
  return json4(200, { status: "ok" });
3055
3856
  },
3056
3857
  "POST /webhooks/faults": ({ body, namespace }) => {
3057
- if (!isRecord4(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
3858
+ if (!isRecord5(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
3058
3859
  return adminError3(400, "mode must be duplicate, reorder or drop");
3059
3860
  }
3060
3861
  const fault = { mode: body.mode };
@@ -3070,7 +3871,7 @@ var webhookAdminRoutes = (hub) => ({
3070
3871
  }))
3071
3872
  }),
3072
3873
  "PUT /webhook-endpoints": ({ body, namespace }) => {
3073
- const list = Array.isArray(body) ? body : isRecord4(body) ? body.endpoints : void 0;
3874
+ const list = Array.isArray(body) ? body : isRecord5(body) ? body.endpoints : void 0;
3074
3875
  if (!Array.isArray(list))
3075
3876
  return adminError3(400, "expected [{url, secret?, events?}]");
3076
3877
  const parsed = [];
@@ -3140,8 +3941,8 @@ var DroppedConnectionError = class extends TypeError {
3140
3941
  this.name = "TypeError";
3141
3942
  }
3142
3943
  };
3143
- var operationMatcher = (document2) => {
3144
- const matchers = listOperations(document2).map((operation) => ({
3944
+ var operationMatcher = (document3) => {
3945
+ const matchers = listOperations(document3).map((operation) => ({
3145
3946
  operationId: operation.operationId,
3146
3947
  method: operation.method.toUpperCase(),
3147
3948
  pattern: new RegExp(`^${operation.path.split("/").map((segment) => segment.startsWith("{") ? "[^/]+" : segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("/")}/?$`),
@@ -3236,10 +4037,10 @@ var createRuntime = (options) => {
3236
4037
  const ensureBranch = (namespace, branch2, at) => {
3237
4038
  if (!BRANCH_PATTERN2.test(branch2))
3238
4039
  throw new RangeError(`branch must match ${BRANCH_PATTERN2}`);
3239
- const history = timeline(namespace);
4040
+ const history2 = timeline(namespace);
3240
4041
  if (branch2 === "main") {
3241
4042
  if (at !== void 0) {
3242
- const point = history.checkout("main", at);
4043
+ const point = history2.checkout("main", at);
3243
4044
  restoreNamespace(sqlite, storageNamespace(namespace), point.value.snapshot);
3244
4045
  captured.set(namespace, point.value.snapshot);
3245
4046
  rng.setState(point.value.rngState);
@@ -3252,10 +4053,10 @@ var createRuntime = (options) => {
3252
4053
  return namespace;
3253
4054
  }
3254
4055
  const storage = physicalBranch(namespace, branch2);
3255
- if (!history.hasBranch(branch2)) {
4056
+ if (!history2.hasBranch(branch2)) {
3256
4057
  if (at === void 0)
3257
- history.commit(capture(namespace));
3258
- const point = history.fork(branch2, at === void 0 ? {} : { from: at });
4058
+ history2.commit(capture(namespace));
4059
+ const point = history2.fork(branch2, at === void 0 ? {} : { from: at });
3259
4060
  const branchRng = createRng(options.seed ?? 0);
3260
4061
  if (point)
3261
4062
  branchRng.setState(point.value.rngState);
@@ -3264,8 +4065,8 @@ var createRuntime = (options) => {
3264
4065
  restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
3265
4066
  if (point)
3266
4067
  captured.set(storage, point.value.snapshot);
3267
- } else if (at !== void 0 && history.head(branch2)?.id !== at) {
3268
- const point = history.checkout(branch2, at);
4068
+ } else if (at !== void 0 && history2.head(branch2)?.id !== at) {
4069
+ const point = history2.checkout(branch2, at);
3269
4070
  if (!instances.has(storage)) {
3270
4071
  const branchRng = createRng(options.seed ?? 0);
3271
4072
  branchRng.setState(point.value.rngState);
@@ -3276,7 +4077,7 @@ var createRuntime = (options) => {
3276
4077
  captured.set(storage, point.value.snapshot);
3277
4078
  } else {
3278
4079
  if (!instances.has(storage)) {
3279
- const point = history.head(branch2);
4080
+ const point = history2.head(branch2);
3280
4081
  const branchRng = createRng(options.seed ?? 0);
3281
4082
  if (point)
3282
4083
  branchRng.setState(point.value.rngState);
@@ -3300,8 +4101,8 @@ var createRuntime = (options) => {
3300
4101
  const checkout = (checkpointId, checkoutOptions = {}) => {
3301
4102
  const namespace = checkoutOptions.namespace ?? DEFAULT_NAMESPACE;
3302
4103
  const branchName = checkoutOptions.branch ?? "main";
3303
- const history = timeline(namespace);
3304
- const point = history.checkout(branchName, checkpointId);
4104
+ const history2 = timeline(namespace);
4105
+ const point = history2.checkout(branchName, checkpointId);
3305
4106
  const storage = ensureBranch(namespace, branchName);
3306
4107
  restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
3307
4108
  captured.set(storage, point.value.snapshot);
@@ -3351,9 +4152,9 @@ var createRuntime = (options) => {
3351
4152
  instance(name);
3352
4153
  restoreNamespace(sqlite, storageNamespace(name), from);
3353
4154
  captured.set(name, from);
3354
- const history = timelines.get(name);
3355
- if (history)
3356
- history.commit(capture(name), { branch: "main" });
4155
+ const history2 = timelines.get(name);
4156
+ if (history2)
4157
+ history2.commit(capture(name), { branch: "main" });
3357
4158
  else
3358
4159
  timeline(name);
3359
4160
  };
@@ -3582,10 +4383,10 @@ var createRuntime = (options) => {
3582
4383
  },
3583
4384
  release: (name, checkpointId) => timeline(name).release(checkpointId),
3584
4385
  inspect: (name) => {
3585
- const history = timeline(name);
4386
+ const history2 = timeline(name);
3586
4387
  return {
3587
- branches: history.branches(),
3588
- checkpoints: history.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
4388
+ branches: history2.branches(),
4389
+ checkpoints: history2.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
3589
4390
  id,
3590
4391
  branch: branchName,
3591
4392
  parent,
@@ -3600,11 +4401,11 @@ var createRuntime = (options) => {
3600
4401
  } : {},
3601
4402
  routes: {
3602
4403
  ...credentialRoutes(credentials),
3603
- ...options.presets ? presetRoutes(options.presets, runtime) : {},
3604
4404
  ...options.webhooks ? webhookAdminRoutes(options.webhooks) : {},
3605
4405
  ...options.admin?.(runtime) ?? {},
3606
- // Standard state and UI routes win a colliding key. A service adds its own keys
3607
- // beside these; it does not replace `/state` or `/ui`.
4406
+ // Shell routes win a colliding key. Fault presets are one of them: the admin page
4407
+ // maps `presets`, and a service route must not replace that list with a record.
4408
+ ...options.presets ? presetRoutes(options.presets, runtime) : {},
3608
4409
  ...stateAdminRoutes({
3609
4410
  open: stateScope,
3610
4411
  afterWrite: (namespace) => {
@@ -3679,9 +4480,12 @@ var credentialRoutes = (registry) => ({
3679
4480
  }
3680
4481
  });
3681
4482
  var presetRoutes = (presets, runtime) => ({
3682
- "GET /faults/presets": () => adminJson(200, {
3683
- presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
3684
- }),
4483
+ "GET /faults/presets": () => {
4484
+ const body = {
4485
+ presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
4486
+ };
4487
+ return adminJson(200, body);
4488
+ },
3685
4489
  "POST /faults/presets/:name": ({ params, body, namespace }) => {
3686
4490
  const name = params.name;
3687
4491
  if (!presets[name])
@@ -3692,7 +4496,7 @@ var presetRoutes = (presets, runtime) => ({
3692
4496
  });
3693
4497
 
3694
4498
  // src/generated/openapi.ts
3695
- var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Formbricks client + management API (Mockingbird subset)","description":"Stateful mock subset of open-source Formbricks (6.x): the client environment state (surveys)\\nand response creation the JS SDK uses, the v1 management API for responses and surveys\\n(\`x-api-key\`), and the widget script the web SDK loads. Errors use Formbricks' v1\\n\`{code, message, details}\` shape (\`apps/web/app/lib/api/response.ts\`). Client routes take a\\nworkspace id or a legacy (pre-5) environment id in the path.\\n","version":"1","x-mockingbird-upstream":{"note":"Hand-authored from upstream Formbricks 6.0.0 (https://github.com/formbricks/formbricks): apps/web/app/api/v1/client/[workspaceId]/environment, apps/web/app/api/v2/client/[workspaceId]/responses, apps/web/app/api/v1/management/{responses,surveys}, and the response pipeline's webhook body (apps/web/modules/response-pipeline). Docs: https://formbricks.com/docs"}},"servers":[{"url":"https://app.formbricks.com"}],"paths":{"/api/v1/client/{workspaceId}/environment":{"parameters":[{"$ref":"#/components/parameters/WorkspaceId"}],"get":{"operationId":"GetEnvironmentState","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The environment state the SDK caches (surveys, action classes, project)","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","required":["data","expiresAt"],"properties":{"data":{"type":"object","required":["surveys","actionClasses","project"],"properties":{"surveys":{"type":"array","items":{"$ref":"#/components/schemas/Survey"}},"actionClasses":{"type":"array","items":{"type":"object"}},"project":{"type":"object"}}},"expiresAt":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}}}}}}},"400":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/api/v2/client/{workspaceId}/responses":{"parameters":[{"$ref":"#/components/parameters/WorkspaceId"}],"post":{"operationId":"CreateClientResponse","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["surveyId","finished","data"],"properties":{"surveyId":{"type":"string","x-mockingbird-resource-ref":{"type":"survey","missing":"czzzzzzzzzzzzzzzzzzzzzzzz"}},"finished":{"type":"boolean"},"data":{"$ref":"#/components/schemas/ResponseData"},"contactId":{"type":["string","null"]},"displayId":{"type":["string","null"]},"singleUseId":{"type":["string","null"]},"endingId":{"type":["string","null"]},"language":{"type":"string","maxLength":10},"variables":{"type":"object"},"ttc":{"type":"object","additionalProperties":{"type":"number"}},"meta":{"type":"object","properties":{"source":{"type":"string"},"url":{"type":"string"},"action":{"type":"string"},"pagePath":{"type":"string"},"utmSource":{"type":"string"}}}}}}}},"responses":{"200":{"description":"The created response's id","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"response","identity":true}},"quotaFull":{"type":"boolean"}}}}}}}},"400":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/api/v1/management/responses":{"get":{"operationId":"ListResponses","security":[{"apiKey":[]}],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"surveyId","in":"query","schema":{"type":"string","x-mockingbird-resource-ref":{"type":"survey","missing":"czzzzzzzzzzzzzzzzzzzzzzzz"}}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"skip","in":"query","schema":{"type":"integer","minimum":0,"maximum":100}}],"responses":{"200":{"description":"Responses, newest first","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Response"}}}}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/api/v1/management/responses/{responseId}":{"parameters":[{"name":"responseId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"response","missing":"czzzzzzzzzzzzzzzzzzzzzzzy"}}}],"get":{"operationId":"GetResponse","security":[{"apiKey":[]}],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"One response","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Response"}}}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/api/v1/management/surveys":{"get":{"operationId":"ListSurveys","security":[{"apiKey":[]}],"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":100}}],"responses":{"200":{"description":"Surveys, most recently updated first","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Survey"}}}}}}},"401":{"$ref":"#/components/responses/Error"}}},"post":{"operationId":"CreateSurvey","security":[{"apiKey":[]}],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["workspaceId","name","blocks"],"properties":{"workspaceId":{"description":"A workspace id, or (as \`environmentId\`) a legacy environment id.","type":"string","enum":["cworkspace000000000000001","cenvironment0000000000001"]},"name":{"type":"string","minLength":1,"maxLength":80},"type":{"type":"string","enum":["app","link"]},"status":{"type":"string","enum":["draft","inProgress","paused","completed"]},"blocks":{"description":"The survey's blocks of elements. The legacy \`questions\` array (one element per block) is also accepted, but not together with \`blocks\`.","type":"array","minItems":1,"maxItems":3,"items":{"type":"object","required":["id","elements"],"properties":{"id":{"type":"string","minLength":1,"maxLength":30},"name":{"type":"string","maxLength":30},"elements":{"type":"array","minItems":1,"maxItems":3,"items":{"$ref":"#/components/schemas/Element"}}}}}}}}}},"responses":{"200":{"description":"The created survey","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Survey"}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/api/v1/management/surveys/{surveyId}":{"parameters":[{"name":"surveyId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"survey","missing":"czzzzzzzzzzzzzzzzzzzzzzzz"}}}],"get":{"operationId":"GetSurvey","security":[{"apiKey":[]}],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"One survey","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Survey"}}}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/js/formbricks.umd.cjs":{"get":{"operationId":"GetWidgetScript","description":"The widget bundle the web SDK loads; served as a no-op script.","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"A no-op script defining \`window.formbricks\`","content":{"application/javascript":{"schema":{"type":"string"}}}}}}}},"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"x-api-key"}},"parameters":{"WorkspaceId":{"name":"workspaceId","in":"path","required":true,"description":"A workspace id, or a legacy (pre-Formbricks-5) environment id.","schema":{"type":"string","enum":["cworkspace000000000000001","cenvironment0000000000001"]}}},"responses":{"Error":{"description":"Formbricks' v1 \`{code, message, details}\` error","content":{"application/json":{"schema":{"type":"object","required":["code","message","details"],"properties":{"code":{"type":"string","enum":["not_found","gone","bad_request","internal_server_error","unauthorized","method_not_allowed","not_authenticated","forbidden","too_many_requests","conflict","payload_too_large"]},"message":{"type":"string"},"details":{"type":"object"}}}}}}},"schemas":{"ResponseData":{"type":"object","maxProperties":5,"additionalProperties":{"anyOf":[{"type":"string","maxLength":60},{"type":"number"},{"type":"array","maxItems":3,"items":{"type":"string","maxLength":30}},{"type":"object","maxProperties":3,"additionalProperties":{"type":"string","maxLength":30}}]}},"Element":{"type":"object","required":["id","type","headline"],"properties":{"id":{"type":"string","minLength":1,"maxLength":30},"type":{"type":"string","enum":["openText","multipleChoiceSingle","multipleChoiceMulti","nps","rating","date","cta","consent"]},"headline":{"type":"object","required":["default"],"properties":{"default":{"type":"string","maxLength":80}}},"required":{"type":"boolean"},"inputType":{"type":"string","enum":["text","email","url","number","phone"]}}},"Survey":{"type":"object","required":["id","name","type","status"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"survey","identity":true}},"name":{"type":"string"},"type":{"type":"string"},"status":{"type":"string"},"workspaceId":{"type":"string"},"environmentId":{"description":"The legacy environment id of the survey's workspace (v1 compatibility).","type":"string"},"questions":{"type":["array","object"]},"blocks":{"type":"array"},"createdAt":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updatedAt":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"Response":{"type":"object","required":["id","createdAt","updatedAt","surveyId","finished","data"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"response","identity":true}},"createdAt":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updatedAt":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"surveyId":{"type":"string"},"displayId":{"type":["string","null"]},"contact":{"type":["object","null"],"properties":{"id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"userId":{"type":"string"}}},"contactAttributes":{"type":["object","null"]},"finished":{"type":"boolean"},"endingId":{"type":["string","null"]},"data":{"description":"An object, or (for some deployments) the same object as a JSON string.","type":["object","string"]},"variables":{"type":"object"},"ttc":{"type":"object"},"tags":{"type":"array"},"meta":{"type":"object"},"singleUseId":{"type":["string","null"]},"language":{"type":["string","null"]}}}}}}`);
4499
+ var document2 = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Formbricks client + management API (Mockingbird subset)","description":"Stateful mock subset of open-source Formbricks (6.x): the client environment state (surveys)\\nand response creation the JS SDK uses, the v1 management API for responses and surveys\\n(\`x-api-key\`), and the widget script the web SDK loads. Errors use Formbricks' v1\\n\`{code, message, details}\` shape (\`apps/web/app/lib/api/response.ts\`). Client routes take a\\nworkspace id or a legacy (pre-5) environment id in the path.\\n","version":"1","x-mockingbird-upstream":{"note":"Hand-authored from upstream Formbricks 6.0.0 (https://github.com/formbricks/formbricks): apps/web/app/api/v1/client/[workspaceId]/environment, apps/web/app/api/v2/client/[workspaceId]/responses, apps/web/app/api/v1/management/{responses,surveys}, and the response pipeline's webhook body (apps/web/modules/response-pipeline). Docs: https://formbricks.com/docs"}},"servers":[{"url":"https://app.formbricks.com"}],"paths":{"/api/v1/client/{workspaceId}/environment":{"parameters":[{"$ref":"#/components/parameters/WorkspaceId"}],"get":{"operationId":"GetEnvironmentState","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The environment state the SDK caches (surveys, action classes, project)","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","required":["data","expiresAt"],"properties":{"data":{"type":"object","required":["surveys","actionClasses","project"],"properties":{"surveys":{"type":"array","items":{"$ref":"#/components/schemas/Survey"}},"actionClasses":{"type":"array","items":{"type":"object"}},"project":{"type":"object"}}},"expiresAt":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}}}}}}},"400":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/api/v2/client/{workspaceId}/responses":{"parameters":[{"$ref":"#/components/parameters/WorkspaceId"}],"post":{"operationId":"CreateClientResponse","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["surveyId","finished","data"],"properties":{"surveyId":{"type":"string","x-mockingbird-resource-ref":{"type":"survey","missing":"czzzzzzzzzzzzzzzzzzzzzzzz"}},"finished":{"type":"boolean"},"data":{"$ref":"#/components/schemas/ResponseData"},"contactId":{"type":["string","null"]},"displayId":{"type":["string","null"]},"singleUseId":{"type":["string","null"]},"endingId":{"type":["string","null"]},"language":{"type":"string","maxLength":10},"variables":{"type":"object"},"ttc":{"type":"object","additionalProperties":{"type":"number"}},"meta":{"type":"object","properties":{"source":{"type":"string"},"url":{"type":"string"},"action":{"type":"string"},"pagePath":{"type":"string"},"utmSource":{"type":"string"}}}}}}}},"responses":{"200":{"description":"The created response's id","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"response","identity":true}},"quotaFull":{"type":"boolean"}}}}}}}},"400":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/api/v1/management/responses":{"get":{"operationId":"ListResponses","security":[{"apiKey":[]}],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"surveyId","in":"query","schema":{"type":"string","x-mockingbird-resource-ref":{"type":"survey","missing":"czzzzzzzzzzzzzzzzzzzzzzzz"}}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"skip","in":"query","schema":{"type":"integer","minimum":0,"maximum":100}}],"responses":{"200":{"description":"Responses, newest first","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Response"}}}}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/api/v1/management/responses/{responseId}":{"parameters":[{"name":"responseId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"response","missing":"czzzzzzzzzzzzzzzzzzzzzzzy"}}}],"get":{"operationId":"GetResponse","security":[{"apiKey":[]}],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"One response","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Response"}}}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/api/v1/management/surveys":{"get":{"operationId":"ListSurveys","security":[{"apiKey":[]}],"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":100}}],"responses":{"200":{"description":"Surveys, most recently updated first","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Survey"}}}}}}},"401":{"$ref":"#/components/responses/Error"}}},"post":{"operationId":"CreateSurvey","security":[{"apiKey":[]}],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["workspaceId","name","blocks"],"properties":{"workspaceId":{"description":"A workspace id, or (as \`environmentId\`) a legacy environment id.","type":"string","enum":["cworkspace000000000000001","cenvironment0000000000001"]},"name":{"type":"string","minLength":1,"maxLength":80},"type":{"type":"string","enum":["app","link"]},"status":{"type":"string","enum":["draft","inProgress","paused","completed"]},"blocks":{"description":"The survey's blocks of elements. The legacy \`questions\` array (one element per block) is also accepted, but not together with \`blocks\`.","type":"array","minItems":1,"maxItems":3,"items":{"type":"object","required":["id","elements"],"properties":{"id":{"type":"string","minLength":1,"maxLength":30},"name":{"type":"string","maxLength":30},"elements":{"type":"array","minItems":1,"maxItems":3,"items":{"$ref":"#/components/schemas/Element"}}}}}}}}}},"responses":{"200":{"description":"The created survey","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Survey"}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/api/v1/management/surveys/{surveyId}":{"parameters":[{"name":"surveyId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"survey","missing":"czzzzzzzzzzzzzzzzzzzzzzzz"}}}],"get":{"operationId":"GetSurvey","security":[{"apiKey":[]}],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"One survey","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Survey"}}}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/js/formbricks.umd.cjs":{"get":{"operationId":"GetWidgetScript","description":"The widget bundle the web SDK loads; served as a no-op script.","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"A no-op script defining \`window.formbricks\`","content":{"application/javascript":{"schema":{"type":"string"}}}}}}}},"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"x-api-key"}},"parameters":{"WorkspaceId":{"name":"workspaceId","in":"path","required":true,"description":"A workspace id, or a legacy (pre-Formbricks-5) environment id.","schema":{"type":"string","enum":["cworkspace000000000000001","cenvironment0000000000001"]}}},"responses":{"Error":{"description":"Formbricks' v1 \`{code, message, details}\` error","content":{"application/json":{"schema":{"type":"object","required":["code","message","details"],"properties":{"code":{"type":"string","enum":["not_found","gone","bad_request","internal_server_error","unauthorized","method_not_allowed","not_authenticated","forbidden","too_many_requests","conflict","payload_too_large"]},"message":{"type":"string"},"details":{"type":"object"}}}}}}},"schemas":{"ResponseData":{"type":"object","maxProperties":5,"additionalProperties":{"anyOf":[{"type":"string","maxLength":60},{"type":"number"},{"type":"array","maxItems":3,"items":{"type":"string","maxLength":30}},{"type":"object","maxProperties":3,"additionalProperties":{"type":"string","maxLength":30}}]}},"Element":{"type":"object","required":["id","type","headline"],"properties":{"id":{"type":"string","minLength":1,"maxLength":30},"type":{"type":"string","enum":["openText","multipleChoiceSingle","multipleChoiceMulti","nps","rating","date","cta","consent"]},"headline":{"type":"object","required":["default"],"properties":{"default":{"type":"string","maxLength":80}}},"required":{"type":"boolean"},"inputType":{"type":"string","enum":["text","email","url","number","phone"]}}},"Survey":{"type":"object","required":["id","name","type","status"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"survey","identity":true}},"name":{"type":"string"},"type":{"type":"string"},"status":{"type":"string"},"workspaceId":{"type":"string"},"environmentId":{"description":"The legacy environment id of the survey's workspace (v1 compatibility).","type":"string"},"questions":{"type":["array","object"]},"blocks":{"type":"array"},"createdAt":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updatedAt":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"Response":{"type":"object","required":["id","createdAt","updatedAt","surveyId","finished","data"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"response","identity":true}},"createdAt":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updatedAt":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"surveyId":{"type":"string"},"displayId":{"type":["string","null"]},"contact":{"type":["object","null"],"properties":{"id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"userId":{"type":"string"}}},"contactAttributes":{"type":["object","null"]},"finished":{"type":"boolean"},"endingId":{"type":["string","null"]},"data":{"description":"An object, or (for some deployments) the same object as a JSON string.","type":["object","string"]},"variables":{"type":"object"},"ttc":{"type":"object"},"tags":{"type":"array"},"meta":{"type":"object"},"singleUseId":{"type":["string","null"]},"language":{"type":["string","null"]}}}}}}`);
3696
4500
  var operationIds = ["GetEnvironmentState", "CreateClientResponse", "ListResponses", "GetResponse", "ListSurveys", "CreateSurvey", "GetSurvey", "GetWidgetScript"];
3697
4501
  var supportedOperationIds = ["GetEnvironmentState", "CreateClientResponse", "ListResponses", "GetResponse", "ListSurveys", "CreateSurvey", "GetSurvey", "GetWidgetScript"];
3698
4502
 
@@ -4663,7 +5467,7 @@ var FORMBRICKS_PRESETS = {
4663
5467
  };
4664
5468
  var json5 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
4665
5469
  var adminError4 = (status, message) => json5(status, { error: { type: "mockingbird_admin", message } });
4666
- var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5470
+ var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4667
5471
  var adminRoutes = (runtime) => ({
4668
5472
  "GET /responses": ({ url, namespace }) => {
4669
5473
  const surveyId = url.searchParams.get("surveyId");
@@ -4673,11 +5477,11 @@ var adminRoutes = (runtime) => ({
4673
5477
  },
4674
5478
  "GET /surveys": ({ namespace }) => json5(200, { surveys: runtime.instance(namespace).state.allSurveys() }),
4675
5479
  "PUT /surveys": ({ body, namespace }) => {
4676
- const list = Array.isArray(body) ? body : isRecord5(body) ? body.surveys : void 0;
5480
+ const list = Array.isArray(body) ? body : isRecord6(body) ? body.surveys : void 0;
4677
5481
  if (!Array.isArray(list)) return adminError4(400, "expected [survey] or {surveys: [survey]}");
4678
5482
  const state = runtime.instance(namespace).state;
4679
5483
  for (const [index, survey] of list.entries()) {
4680
- if (!isRecord5(survey) || typeof survey.id !== "string" || typeof survey.name !== "string") {
5484
+ if (!isRecord6(survey) || typeof survey.id !== "string" || typeof survey.name !== "string") {
4681
5485
  return adminError4(400, `surveys[${index}]: id and name are required`);
4682
5486
  }
4683
5487
  }
@@ -4697,16 +5501,16 @@ var adminRoutes = (runtime) => ({
4697
5501
  contacts: runtime.instance(namespace).state.contacts.list({ order: "oldest" }).map((row) => row.value)
4698
5502
  }),
4699
5503
  "PUT /contacts": ({ body, namespace }) => {
4700
- const list = Array.isArray(body) ? body : isRecord5(body) ? body.contacts : void 0;
5504
+ const list = Array.isArray(body) ? body : isRecord6(body) ? body.contacts : void 0;
4701
5505
  if (!Array.isArray(list)) return adminError4(400, "expected [contact] or {contacts: [contact]}");
4702
5506
  const state = runtime.instance(namespace).state;
4703
5507
  for (const [index, contact] of list.entries()) {
4704
- if (!isRecord5(contact) || typeof contact.id !== "string") {
5508
+ if (!isRecord6(contact) || typeof contact.id !== "string") {
4705
5509
  return adminError4(400, `contacts[${index}]: id is required`);
4706
5510
  }
4707
5511
  }
4708
5512
  for (const contact of list) {
4709
- const attributes = isRecord5(contact.attributes) ? Object.fromEntries(Object.entries(contact.attributes).map(([k, v]) => [k, String(v)])) : {};
5513
+ const attributes = isRecord6(contact.attributes) ? Object.fromEntries(Object.entries(contact.attributes).map(([k, v]) => [k, String(v)])) : {};
4710
5514
  if (typeof contact.userId === "string") attributes.userId = contact.userId;
4711
5515
  state.contacts.insert(contact.id, {
4712
5516
  id: contact.id,
@@ -4718,10 +5522,10 @@ var adminRoutes = (runtime) => ({
4718
5522
  },
4719
5523
  "GET /settings": ({ namespace }) => json5(200, runtime.instance(namespace).state.current()),
4720
5524
  "PUT /settings": ({ body, namespace }) => {
4721
- if (!isRecord5(body)) return adminError4(400, "expected a JSON object");
5525
+ if (!isRecord6(body)) return adminError4(400, "expected a JSON object");
4722
5526
  const patch = {};
4723
5527
  if (Array.isArray(body.workspaces)) patch.workspaces = body.workspaces.map(String);
4724
- if (isRecord5(body.legacyEnvironmentIds)) {
5528
+ if (isRecord6(body.legacyEnvironmentIds)) {
4725
5529
  patch.legacyEnvironmentIds = Object.fromEntries(
4726
5530
  Object.entries(body.legacyEnvironmentIds).map(([k, v]) => [k, String(v)])
4727
5531
  );
@@ -4753,7 +5557,7 @@ var createRuntime2 = (options = {}) => {
4753
5557
  });
4754
5558
  const runtime = createRuntime({
4755
5559
  name: FORMBRICKS_NAMESPACE,
4756
- document,
5560
+ document: document2,
4757
5561
  ...options.sqlite ? { sqlite: options.sqlite } : {},
4758
5562
  ...options.clock ? { clock: options.clock } : {},
4759
5563
  ...options.seed !== void 0 ? { seed: options.seed } : {},
@@ -5033,7 +5837,7 @@ var FormbricksAPI = class {
5033
5837
  })
5034
5838
  });
5035
5839
  this.service = createService({
5036
- document,
5840
+ document: document2,
5037
5841
  handlers,
5038
5842
  sqlite,
5039
5843
  namespace,
@@ -5380,7 +6184,7 @@ var FormbricksAPI = class {
5380
6184
  };
5381
6185
 
5382
6186
  export {
5383
- document,
6187
+ document2 as document,
5384
6188
  operationIds,
5385
6189
  supportedOperationIds,
5386
6190
  WORKSPACE_ID,
@@ -5396,4 +6200,4 @@ export {
5396
6200
  formbricksError,
5397
6201
  FormbricksAPI
5398
6202
  };
5399
- //# sourceMappingURL=chunk-GPMHAIYZ.js.map
6203
+ //# sourceMappingURL=chunk-TNGXNIQB.js.map