@crvouga/mockingbird-service-fullscript 1.3.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.
@@ -38,30 +38,30 @@ var createClock = (source = Date.now) => {
38
38
  var Collection = class {
39
39
  sqlite;
40
40
  namespace;
41
- name;
42
- constructor(sqlite, namespace, name) {
41
+ collectionName;
42
+ constructor(sqlite, namespace, collectionName) {
43
43
  this.sqlite = sqlite;
44
44
  this.namespace = namespace;
45
- this.name = name;
45
+ this.collectionName = collectionName;
46
46
  }
47
47
  bumpCollectionSeq() {
48
- const row = this.sqlite.prepare("SELECT value FROM mockingbird_sequences WHERE namespace = ? AND name = ? AND kind = 'collection'").get(this.namespace, this.name);
48
+ const row = this.sqlite.prepare("SELECT value FROM mockingbird_sequences WHERE namespace = ? AND name = ? AND kind = 'collection'").get(this.namespace, this.collectionName);
49
49
  const next = (row?.value ?? 0) + 1;
50
50
  this.sqlite.prepare(`INSERT INTO mockingbird_sequences (namespace, name, kind, value) VALUES (?, ?, 'collection', ?)
51
- ON CONFLICT(namespace, name, kind) DO UPDATE SET value = excluded.value`).run(this.namespace, this.name, next);
51
+ ON CONFLICT(namespace, name, kind) DO UPDATE SET value = excluded.value`).run(this.namespace, this.collectionName, next);
52
52
  return next;
53
53
  }
54
54
  nextSequence() {
55
55
  return this.sqlite.transaction(() => this.bumpCollectionSeq());
56
56
  }
57
57
  get(id) {
58
- const row = this.sqlite.prepare("SELECT value FROM mockingbird_records WHERE namespace = ? AND collection = ? AND id = ?").get(this.namespace, this.name, id);
58
+ const row = this.sqlite.prepare("SELECT value FROM mockingbird_records WHERE namespace = ? AND collection = ? AND id = ?").get(this.namespace, this.collectionName, id);
59
59
  if (!row)
60
60
  return void 0;
61
61
  return JSON.parse(row.value).value;
62
62
  }
63
63
  has(id) {
64
- const row = this.sqlite.prepare("SELECT 1 AS ok FROM mockingbird_records WHERE namespace = ? AND collection = ? AND id = ?").get(this.namespace, this.name, id);
64
+ const row = this.sqlite.prepare("SELECT 1 AS ok FROM mockingbird_records WHERE namespace = ? AND collection = ? AND id = ?").get(this.namespace, this.collectionName, id);
65
65
  return row !== void 0;
66
66
  }
67
67
  /** Insert a new record, assigning it the next sequence number. */
@@ -71,32 +71,32 @@ var Collection = class {
71
71
  const stored = { seq, value };
72
72
  this.sqlite.prepare(`INSERT INTO mockingbird_records (namespace, collection, id, seq, value)
73
73
  VALUES (?, ?, ?, ?, ?)
74
- ON CONFLICT(namespace, collection, id) DO UPDATE SET seq = excluded.seq, value = excluded.value`).run(this.namespace, this.name, id, seq, JSON.stringify(stored));
74
+ ON CONFLICT(namespace, collection, id) DO UPDATE SET seq = excluded.seq, value = excluded.value`).run(this.namespace, this.collectionName, id, seq, JSON.stringify(stored));
75
75
  return stored;
76
76
  });
77
77
  }
78
78
  /** Replace an existing record's value, keeping its position. */
79
79
  update(id, value) {
80
80
  return this.sqlite.transaction(() => {
81
- const row = this.sqlite.prepare("SELECT seq, value FROM mockingbird_records WHERE namespace = ? AND collection = ? AND id = ?").get(this.namespace, this.name, id);
81
+ const row = this.sqlite.prepare("SELECT seq, value FROM mockingbird_records WHERE namespace = ? AND collection = ? AND id = ?").get(this.namespace, this.collectionName, id);
82
82
  if (!row)
83
83
  return void 0;
84
84
  const stored = { seq: row.seq, value };
85
- this.sqlite.prepare("UPDATE mockingbird_records SET value = ? WHERE namespace = ? AND collection = ? AND id = ?").run(JSON.stringify(stored), this.namespace, this.name, id);
85
+ this.sqlite.prepare("UPDATE mockingbird_records SET value = ? WHERE namespace = ? AND collection = ? AND id = ?").run(JSON.stringify(stored), this.namespace, this.collectionName, id);
86
86
  return stored;
87
87
  });
88
88
  }
89
89
  delete(id) {
90
- const result = this.sqlite.prepare("DELETE FROM mockingbird_records WHERE namespace = ? AND collection = ? AND id = ?").run(this.namespace, this.name, id);
90
+ const result = this.sqlite.prepare("DELETE FROM mockingbird_records WHERE namespace = ? AND collection = ? AND id = ?").run(this.namespace, this.collectionName, id);
91
91
  return result.changes > 0;
92
92
  }
93
93
  /** How many records the collection holds, without reading them. */
94
94
  count() {
95
- const row = this.sqlite.prepare("SELECT COUNT(*) AS n FROM mockingbird_records WHERE namespace = ? AND collection = ?").get(this.namespace, this.name);
95
+ const row = this.sqlite.prepare("SELECT COUNT(*) AS n FROM mockingbird_records WHERE namespace = ? AND collection = ?").get(this.namespace, this.collectionName);
96
96
  return Number(row?.n ?? 0);
97
97
  }
98
98
  list(options = {}) {
99
- const rows = this.sqlite.prepare("SELECT id, seq, value FROM mockingbird_records WHERE namespace = ? AND collection = ?").all(this.namespace, this.name);
99
+ const rows = this.sqlite.prepare("SELECT id, seq, value FROM mockingbird_records WHERE namespace = ? AND collection = ?").all(this.namespace, this.collectionName);
100
100
  const out = [];
101
101
  for (const row of rows) {
102
102
  const stored = JSON.parse(row.value);
@@ -353,16 +353,18 @@ var createControlPlane = (context) => {
353
353
  uptimeMs: context.wallNow() - context.startedAt,
354
354
  clock: context.clock.state(),
355
355
  namespaces: context.namespaces().length,
356
+ adminUi: `${ADMIN_PREFIX}/ui`,
356
357
  ...context.describe()
357
358
  });
358
359
  }
359
360
  if (url.pathname !== ADMIN_PREFIX && !url.pathname.startsWith(`${ADMIN_PREFIX}/`)) {
360
361
  return void 0;
361
362
  }
362
- if (context.adminKey !== void 0 && request.headers.get(ADMIN_KEY_HEADER) !== context.adminKey) {
363
+ const path = url.pathname.slice(ADMIN_PREFIX.length) || "/";
364
+ const shell = request.method === "GET" && (path === "/ui" || path === "/ui/");
365
+ if (!shell && context.adminKey !== void 0 && request.headers.get(ADMIN_KEY_HEADER) !== context.adminKey) {
363
366
  return adminError(401, `missing or wrong ${ADMIN_KEY_HEADER}`);
364
367
  }
365
- const path = url.pathname.slice(ADMIN_PREFIX.length) || "/";
366
368
  for (const route of routes) {
367
369
  if (route.method !== request.method)
368
370
  continue;
@@ -538,10 +540,10 @@ var OpenAPIReferenceError = class extends Error {
538
540
  };
539
541
  var unescapePointer = (segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~");
540
542
  var isReference = (value) => typeof value === "object" && value !== null && typeof value.$ref === "string";
541
- var resolveRef = (document2, ref) => {
543
+ var resolveRef = (document3, ref) => {
542
544
  if (!ref.startsWith("#/"))
543
545
  throw new OpenAPIReferenceError(ref);
544
- let cursor = document2;
546
+ let cursor = document3;
545
547
  for (const raw of ref.slice(2).split("/")) {
546
548
  const segment = unescapePointer(raw);
547
549
  if (typeof cursor !== "object" || cursor === null || !(segment in cursor)) {
@@ -553,14 +555,14 @@ var resolveRef = (document2, ref) => {
553
555
  throw new OpenAPIReferenceError(ref);
554
556
  return cursor;
555
557
  };
556
- var deref = (document2, value) => {
558
+ var deref = (document3, value) => {
557
559
  let current = value;
558
560
  const seen = /* @__PURE__ */ new Set();
559
561
  while (isReference(current)) {
560
562
  if (seen.has(current.$ref))
561
563
  throw new OpenAPIReferenceError(`${current.$ref} (cycle)`);
562
564
  seen.add(current.$ref);
563
- current = resolveRef(document2, current.$ref);
565
+ current = resolveRef(document3, current.$ref);
564
566
  }
565
567
  return current;
566
568
  };
@@ -578,36 +580,36 @@ var HTTP_METHODS = [
578
580
  ];
579
581
 
580
582
  // ../../openapi/core/dist/document.js
581
- var mergeParameters = (document2, item, own) => {
583
+ var mergeParameters = (document3, item, own) => {
582
584
  const merged = /* @__PURE__ */ new Map();
583
585
  for (const raw of item.parameters ?? []) {
584
- const parameter = deref(document2, raw);
586
+ const parameter = deref(document3, raw);
585
587
  merged.set(`${parameter.in}:${parameter.name}`, parameter);
586
588
  }
587
589
  for (const raw of own ?? []) {
588
- const parameter = deref(document2, raw);
590
+ const parameter = deref(document3, raw);
589
591
  merged.set(`${parameter.in}:${parameter.name}`, parameter);
590
592
  }
591
593
  return [...merged.values()];
592
594
  };
593
- var listOperations = (document2) => {
595
+ var listOperations = (document3) => {
594
596
  const operations = [];
595
- for (const [path, item] of Object.entries(document2.paths)) {
597
+ for (const [path, item] of Object.entries(document3.paths)) {
596
598
  for (const method of HTTP_METHODS) {
597
599
  const operation = item[method];
598
600
  if (operation?.operationId === void 0)
599
601
  continue;
600
602
  const responses = {};
601
603
  for (const [status, response] of Object.entries(operation.responses)) {
602
- responses[status] = deref(document2, response);
604
+ responses[status] = deref(document3, response);
603
605
  }
604
606
  operations.push({
605
607
  operationId: operation.operationId,
606
608
  method,
607
609
  path,
608
610
  operation,
609
- parameters: mergeParameters(document2, item, operation.parameters),
610
- requestBody: operation.requestBody === void 0 ? void 0 : deref(document2, operation.requestBody),
611
+ parameters: mergeParameters(document3, item, operation.parameters),
612
+ requestBody: operation.requestBody === void 0 ? void 0 : deref(document3, operation.requestBody),
611
613
  responses
612
614
  });
613
615
  }
@@ -616,7 +618,7 @@ var listOperations = (document2) => {
616
618
  };
617
619
 
618
620
  // ../../openapi/core/dist/schema.js
619
- var resolveSchema = (document2, schema) => {
621
+ var resolveSchema = (document3, schema) => {
620
622
  let current = schema;
621
623
  const seen = /* @__PURE__ */ new Set();
622
624
  while (typeof current.$ref === "string") {
@@ -625,7 +627,7 @@ var resolveSchema = (document2, schema) => {
625
627
  break;
626
628
  seen.add(ref);
627
629
  const { $ref: _ignored, ...siblings } = current;
628
- const target = resolveRef(document2, ref);
630
+ const target = resolveRef(document3, ref);
629
631
  current = { ...target, ...siblings };
630
632
  }
631
633
  if (current.nullable === true) {
@@ -696,71 +698,71 @@ var FORMAT_PATTERNS = {
696
698
  ipv4: /^(25[0-5]|2[0-4]\d|1?\d?\d)(\.(25[0-5]|2[0-4]\d|1?\d?\d)){3}$/
697
699
  };
698
700
  var graphemeLength = (value) => [...value].length;
699
- var validateValue = (document2, schema, value, path = []) => {
701
+ var validateValue = (document3, schema, value, path = []) => {
700
702
  const errors = [];
701
- const s = resolveSchema(document2, schema);
702
- const fail2 = (message) => errors.push({ path, message });
703
+ const s = resolveSchema(document3, schema);
704
+ const fail3 = (message) => errors.push({ path, message });
703
705
  const actual = jsonTypeOf(value);
704
706
  if (actual === "undefined") {
705
- fail2("value is undefined");
707
+ fail3("value is undefined");
706
708
  return errors;
707
709
  }
708
710
  const types = schemaTypes(s);
709
711
  if (types.length > 0) {
710
712
  const ok2 = types.some((t) => t === actual || t === "number" && actual === "integer");
711
713
  if (!ok2) {
712
- fail2(`expected type ${types.join("|")}, got ${actual}`);
714
+ fail3(`expected type ${types.join("|")}, got ${actual}`);
713
715
  return errors;
714
716
  }
715
717
  }
716
718
  if (s.enum && !(value === null && types.includes("null")) && !s.enum.some((candidate) => deepEqual(candidate, value))) {
717
- fail2("value not in enum");
719
+ fail3("value not in enum");
718
720
  }
719
721
  if (s.const !== void 0 && !deepEqual(s.const, value))
720
- fail2("value does not equal const");
722
+ fail3("value does not equal const");
721
723
  if (typeof value === "string") {
722
724
  const length = graphemeLength(value);
723
725
  if (s.minLength !== void 0 && length < s.minLength)
724
- fail2(`length ${length} < minLength ${s.minLength}`);
726
+ fail3(`length ${length} < minLength ${s.minLength}`);
725
727
  if (s.maxLength !== void 0 && length > s.maxLength)
726
- fail2(`length ${length} > maxLength ${s.maxLength}`);
728
+ fail3(`length ${length} > maxLength ${s.maxLength}`);
727
729
  if (s.pattern !== void 0) {
728
730
  try {
729
731
  if (!new RegExp(s.pattern, "u").test(value))
730
- fail2(`does not match pattern ${s.pattern}`);
732
+ fail3(`does not match pattern ${s.pattern}`);
731
733
  } catch {
732
734
  }
733
735
  }
734
736
  if (s.format !== void 0) {
735
737
  const pattern = FORMAT_PATTERNS[s.format];
736
738
  if (pattern && !pattern.test(value))
737
- fail2(`does not match format ${s.format}`);
739
+ fail3(`does not match format ${s.format}`);
738
740
  }
739
741
  }
740
742
  if (typeof value === "number") {
741
743
  if (s.minimum !== void 0 && value < s.minimum)
742
- fail2(`${value} < minimum ${s.minimum}`);
744
+ fail3(`${value} < minimum ${s.minimum}`);
743
745
  if (s.maximum !== void 0 && value > s.maximum)
744
- fail2(`${value} > maximum ${s.maximum}`);
746
+ fail3(`${value} > maximum ${s.maximum}`);
745
747
  if (s.exclusiveMinimum !== void 0 && value <= s.exclusiveMinimum)
746
- fail2(`${value} <= exclusiveMinimum ${s.exclusiveMinimum}`);
748
+ fail3(`${value} <= exclusiveMinimum ${s.exclusiveMinimum}`);
747
749
  if (s.exclusiveMaximum !== void 0 && value >= s.exclusiveMaximum)
748
- fail2(`${value} >= exclusiveMaximum ${s.exclusiveMaximum}`);
750
+ fail3(`${value} >= exclusiveMaximum ${s.exclusiveMaximum}`);
749
751
  if (s.multipleOf !== void 0 && Math.abs(value / s.multipleOf - Math.round(value / s.multipleOf)) > 1e-9) {
750
- fail2(`${value} is not a multiple of ${s.multipleOf}`);
752
+ fail3(`${value} is not a multiple of ${s.multipleOf}`);
751
753
  }
752
754
  }
753
755
  if (Array.isArray(value)) {
754
756
  if (s.minItems !== void 0 && value.length < s.minItems)
755
- fail2(`${value.length} items < minItems ${s.minItems}`);
757
+ fail3(`${value.length} items < minItems ${s.minItems}`);
756
758
  if (s.maxItems !== void 0 && value.length > s.maxItems)
757
- fail2(`${value.length} items > maxItems ${s.maxItems}`);
759
+ fail3(`${value.length} items > maxItems ${s.maxItems}`);
758
760
  if (s.uniqueItems && value.some((item, i) => value.slice(0, i).some((prev) => deepEqual(prev, item))))
759
- fail2("items are not unique");
761
+ fail3("items are not unique");
760
762
  value.forEach((item, i) => {
761
763
  const itemSchema = s.prefixItems?.[i] ?? s.items;
762
764
  if (itemSchema)
763
- errors.push(...validateValue(document2, itemSchema, item, [...path, i]));
765
+ errors.push(...validateValue(document3, itemSchema, item, [...path, i]));
764
766
  });
765
767
  }
766
768
  if (actual === "object") {
@@ -768,41 +770,41 @@ var validateValue = (document2, schema, value, path = []) => {
768
770
  const keys = Object.keys(record);
769
771
  for (const name of s.required ?? [])
770
772
  if (!(name in record))
771
- fail2(`missing required property ${name}`);
773
+ fail3(`missing required property ${name}`);
772
774
  if (s.minProperties !== void 0 && keys.length < s.minProperties)
773
- fail2(`${keys.length} properties < minProperties ${s.minProperties}`);
775
+ fail3(`${keys.length} properties < minProperties ${s.minProperties}`);
774
776
  if (s.maxProperties !== void 0 && keys.length > s.maxProperties)
775
- fail2(`${keys.length} properties > maxProperties ${s.maxProperties}`);
777
+ fail3(`${keys.length} properties > maxProperties ${s.maxProperties}`);
776
778
  for (const key of keys) {
777
779
  const property = s.properties?.[key];
778
780
  if (property) {
779
- errors.push(...validateValue(document2, property, record[key], [...path, key]));
781
+ errors.push(...validateValue(document3, property, record[key], [...path, key]));
780
782
  continue;
781
783
  }
782
784
  if (s.additionalProperties === false)
783
- fail2(`unexpected property ${key}`);
785
+ fail3(`unexpected property ${key}`);
784
786
  else if (typeof s.additionalProperties === "object") {
785
- errors.push(...validateValue(document2, s.additionalProperties, record[key], [...path, key]));
787
+ errors.push(...validateValue(document3, s.additionalProperties, record[key], [...path, key]));
786
788
  }
787
789
  if (s.propertyNames) {
788
- const nameErrors = validateValue(document2, s.propertyNames, key, [...path, key]);
790
+ const nameErrors = validateValue(document3, s.propertyNames, key, [...path, key]);
789
791
  if (nameErrors.length > 0)
790
- fail2(`property name ${key} is invalid: ${nameErrors[0]?.message}`);
792
+ fail3(`property name ${key} is invalid: ${nameErrors[0]?.message}`);
791
793
  }
792
794
  }
793
795
  }
794
796
  if (s.allOf)
795
797
  for (const branch of s.allOf)
796
- errors.push(...validateValue(document2, branch, value, path));
797
- if (s.anyOf && !s.anyOf.some((branch) => validateValue(document2, branch, value).length === 0))
798
- fail2("matches no anyOf branch");
798
+ errors.push(...validateValue(document3, branch, value, path));
799
+ if (s.anyOf && !s.anyOf.some((branch) => validateValue(document3, branch, value).length === 0))
800
+ fail3("matches no anyOf branch");
799
801
  if (s.oneOf) {
800
- const matches2 = s.oneOf.filter((branch) => validateValue(document2, branch, value).length === 0).length;
802
+ const matches2 = s.oneOf.filter((branch) => validateValue(document3, branch, value).length === 0).length;
801
803
  if (matches2 !== 1)
802
- fail2(`matches ${matches2} oneOf branches, expected exactly 1`);
804
+ fail3(`matches ${matches2} oneOf branches, expected exactly 1`);
803
805
  }
804
- if (s.not && validateValue(document2, s.not, value).length === 0)
805
- fail2("matches forbidden `not` schema");
806
+ if (s.not && validateValue(document3, s.not, value).length === 0)
807
+ fail3("matches forbidden `not` schema");
806
808
  return errors;
807
809
  };
808
810
 
@@ -1356,6 +1358,1737 @@ var clearNamespace = (sqlite, namespace) => {
1356
1358
  });
1357
1359
  };
1358
1360
 
1361
+ // ../../ui/dist/reset.js
1362
+ var CSS_RESET = `*,
1363
+ *::before,
1364
+ *::after {
1365
+ box-sizing: border-box;
1366
+ margin: 0;
1367
+ padding: 0;
1368
+ }
1369
+
1370
+ * {
1371
+ scrollbar-width: thin;
1372
+ scrollbar-color: color-mix(in srgb, CanvasText 35%, Canvas) Canvas;
1373
+ }
1374
+
1375
+ *::-webkit-scrollbar {
1376
+ width: 12px;
1377
+ height: 12px;
1378
+ }
1379
+
1380
+ *::-webkit-scrollbar-track,
1381
+ *::-webkit-scrollbar-corner {
1382
+ background: Canvas;
1383
+ }
1384
+
1385
+ *::-webkit-scrollbar-thumb {
1386
+ background: color-mix(in srgb, CanvasText 35%, Canvas);
1387
+ border: 3px solid Canvas;
1388
+ border-radius: 999px;
1389
+ }
1390
+
1391
+ html {
1392
+ hanging-punctuation: first allow-end last;
1393
+ -webkit-text-size-adjust: none;
1394
+ text-size-adjust: none;
1395
+ tab-size: 4;
1396
+ font-family:
1397
+ ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI",
1398
+ "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
1399
+ font-size: 16px;
1400
+ line-height: 1.5;
1401
+ -webkit-font-smoothing: antialiased;
1402
+ -moz-osx-font-smoothing: grayscale;
1403
+ text-rendering: optimizeLegibility;
1404
+ -webkit-tap-highlight-color: transparent;
1405
+ scroll-behavior: smooth;
1406
+ background: Canvas;
1407
+ color: CanvasText;
1408
+ }
1409
+
1410
+ body {
1411
+ min-height: 100svh;
1412
+ line-height: 1.5;
1413
+ background: Canvas;
1414
+ color: CanvasText;
1415
+ }
1416
+
1417
+ h1 {
1418
+ font-size: 2em;
1419
+ }
1420
+
1421
+ h2 {
1422
+ font-size: 1.5em;
1423
+ }
1424
+
1425
+ h3 {
1426
+ font-size: 1.17em;
1427
+ }
1428
+
1429
+ h4 {
1430
+ font-size: 1em;
1431
+ }
1432
+
1433
+ h5 {
1434
+ font-size: 0.83em;
1435
+ }
1436
+
1437
+ h6 {
1438
+ font-size: 0.67em;
1439
+ }
1440
+
1441
+ h1,
1442
+ h2,
1443
+ h3,
1444
+ h4,
1445
+ h5,
1446
+ h6 {
1447
+ font-weight: 700;
1448
+ line-height: 1.2;
1449
+ text-wrap: balance;
1450
+ overflow-wrap: break-word;
1451
+ }
1452
+
1453
+ p,
1454
+ li,
1455
+ figcaption,
1456
+ dd,
1457
+ dt,
1458
+ blockquote {
1459
+ overflow-wrap: break-word;
1460
+ }
1461
+
1462
+ p,
1463
+ li,
1464
+ figcaption,
1465
+ blockquote {
1466
+ text-wrap: pretty;
1467
+ }
1468
+
1469
+ ul,
1470
+ ol,
1471
+ menu {
1472
+ list-style: none;
1473
+ }
1474
+
1475
+ a {
1476
+ color: inherit;
1477
+ text-decoration: underline;
1478
+ text-decoration-thickness: 1px;
1479
+ text-underline-offset: 0.15em;
1480
+ text-decoration-skip-ink: auto;
1481
+ }
1482
+
1483
+ b,
1484
+ strong {
1485
+ font-weight: 700;
1486
+ }
1487
+
1488
+ small {
1489
+ font-size: 0.875em;
1490
+ }
1491
+
1492
+ code,
1493
+ kbd,
1494
+ samp,
1495
+ pre {
1496
+ font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
1497
+ font-size: 1em;
1498
+ }
1499
+
1500
+ sub,
1501
+ sup {
1502
+ font-size: 75%;
1503
+ line-height: 0;
1504
+ position: relative;
1505
+ vertical-align: baseline;
1506
+ }
1507
+
1508
+ sub {
1509
+ bottom: -0.25em;
1510
+ }
1511
+
1512
+ sup {
1513
+ top: -0.5em;
1514
+ }
1515
+
1516
+ abbr[title] {
1517
+ text-decoration: underline dotted;
1518
+ text-underline-offset: 0.15em;
1519
+ }
1520
+
1521
+ mark {
1522
+ background: Mark;
1523
+ color: MarkText;
1524
+ }
1525
+
1526
+ hr {
1527
+ height: 0;
1528
+ border: 0;
1529
+ border-top: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
1530
+ color: inherit;
1531
+ }
1532
+
1533
+ img,
1534
+ picture,
1535
+ video,
1536
+ canvas {
1537
+ display: block;
1538
+ max-width: 100%;
1539
+ }
1540
+
1541
+ img,
1542
+ video {
1543
+ height: auto;
1544
+ }
1545
+
1546
+ svg {
1547
+ overflow: hidden;
1548
+ }
1549
+
1550
+ iframe {
1551
+ border: 0;
1552
+ max-width: 100%;
1553
+ }
1554
+
1555
+ table {
1556
+ border-collapse: collapse;
1557
+ border-spacing: 0;
1558
+ }
1559
+
1560
+ fieldset {
1561
+ border: 0;
1562
+ min-inline-size: 0;
1563
+ }
1564
+
1565
+ legend {
1566
+ padding: 0;
1567
+ }
1568
+
1569
+ button,
1570
+ input,
1571
+ select,
1572
+ textarea,
1573
+ optgroup {
1574
+ font: inherit;
1575
+ letter-spacing: inherit;
1576
+ color: inherit;
1577
+ margin: 0;
1578
+ vertical-align: middle;
1579
+ }
1580
+
1581
+ button,
1582
+ input[type="button"],
1583
+ input[type="submit"],
1584
+ input[type="reset"] {
1585
+ appearance: none;
1586
+ background: none;
1587
+ border: 0;
1588
+ padding: 0;
1589
+ cursor: pointer;
1590
+ text-align: center;
1591
+ }
1592
+
1593
+ input,
1594
+ textarea,
1595
+ select {
1596
+ background-color: Canvas;
1597
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1598
+ border-radius: 0;
1599
+ padding: 0.5em 0.65em;
1600
+ }
1601
+
1602
+ input[type="checkbox"],
1603
+ input[type="radio"],
1604
+ input[type="range"],
1605
+ input[type="file"],
1606
+ input[type="color"],
1607
+ input[type="button"],
1608
+ input[type="submit"],
1609
+ input[type="reset"],
1610
+ input[type="image"],
1611
+ input[type="hidden"] {
1612
+ background: none;
1613
+ border: 0;
1614
+ padding: 0;
1615
+ border-radius: 0;
1616
+ }
1617
+
1618
+ input[type="checkbox"],
1619
+ input[type="radio"] {
1620
+ accent-color: CanvasText;
1621
+ }
1622
+
1623
+ input[type="search"] {
1624
+ appearance: none;
1625
+ background-color: Canvas;
1626
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1627
+ }
1628
+
1629
+ input[type="search"]::-webkit-search-decoration,
1630
+ input[type="search"]::-webkit-search-cancel-button,
1631
+ input[type="search"]::-webkit-search-results-button,
1632
+ input[type="search"]::-webkit-search-results-decoration {
1633
+ -webkit-appearance: none;
1634
+ display: none;
1635
+ }
1636
+
1637
+ input[type="number"] {
1638
+ appearance: textfield;
1639
+ background-color: Canvas;
1640
+ border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
1641
+ }
1642
+
1643
+ input[type="number"]::-webkit-inner-spin-button,
1644
+ input[type="number"]::-webkit-outer-spin-button {
1645
+ -webkit-appearance: none;
1646
+ margin: 0;
1647
+ }
1648
+
1649
+ textarea {
1650
+ resize: vertical;
1651
+ }
1652
+
1653
+ ::placeholder {
1654
+ opacity: 1;
1655
+ color: color-mix(in srgb, CanvasText 45%, transparent);
1656
+ }
1657
+
1658
+ :focus-visible {
1659
+ outline: 2px solid CanvasText;
1660
+ outline-offset: 2px;
1661
+ }
1662
+
1663
+ :focus:not(:focus-visible) {
1664
+ outline: none;
1665
+ }
1666
+
1667
+ :disabled,
1668
+ [disabled] {
1669
+ cursor: default;
1670
+ opacity: 0.6;
1671
+ }
1672
+
1673
+ label,
1674
+ summary,
1675
+ select,
1676
+ [role="button"] {
1677
+ cursor: pointer;
1678
+ }
1679
+
1680
+ summary {
1681
+ display: list-item;
1682
+ }
1683
+
1684
+ details {
1685
+ display: block;
1686
+ }
1687
+
1688
+ dialog {
1689
+ margin: auto;
1690
+ padding: 1rem;
1691
+ border: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
1692
+ background: Canvas;
1693
+ color: CanvasText;
1694
+ max-width: calc(100vw - 2rem);
1695
+ max-height: calc(100svh - 2rem);
1696
+ }
1697
+
1698
+ dialog:not([open]) {
1699
+ display: none;
1700
+ }
1701
+
1702
+ ::backdrop {
1703
+ background: color-mix(in srgb, CanvasText 40%, transparent);
1704
+ }
1705
+
1706
+ [hidden] {
1707
+ display: none !important;
1708
+ }
1709
+
1710
+ ::selection {
1711
+ background: Highlight;
1712
+ color: HighlightText;
1713
+ }
1714
+
1715
+ @media (prefers-reduced-motion: reduce) {
1716
+ *,
1717
+ *::before,
1718
+ *::after {
1719
+ animation-duration: 0.01ms !important;
1720
+ animation-iteration-count: 1 !important;
1721
+ transition-duration: 0.01ms !important;
1722
+ scroll-behavior: auto !important;
1723
+ }
1724
+ }
1725
+
1726
+ @media (pointer: coarse) {
1727
+ input,
1728
+ select,
1729
+ textarea {
1730
+ font-size: max(16px, 1em);
1731
+ }
1732
+ }`;
1733
+
1734
+ // ../core/dist/admin-client.js
1735
+ function isRecord2(value) {
1736
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1737
+ }
1738
+ function mountAdminBrand(doc, host, service, catalog) {
1739
+ const clip = (value, max) => {
1740
+ if (typeof value !== "string")
1741
+ return null;
1742
+ const trimmed = value.trim();
1743
+ if (trimmed === "")
1744
+ return null;
1745
+ return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
1746
+ };
1747
+ const httpUrl = (value) => {
1748
+ if (typeof value !== "string" || value.length > 2e3)
1749
+ return null;
1750
+ try {
1751
+ const url = new URL(value);
1752
+ if (url.protocol !== "https:" && url.protocol !== "http:")
1753
+ return null;
1754
+ return url.href;
1755
+ } catch {
1756
+ return null;
1757
+ }
1758
+ };
1759
+ const hexColor = (value) => {
1760
+ if (typeof value !== "string")
1761
+ return null;
1762
+ const raw = value.trim();
1763
+ return /^#[0-9a-fA-F]{3,8}$/.test(raw) ? raw : null;
1764
+ };
1765
+ const hostname = (value) => {
1766
+ try {
1767
+ return new URL(value).hostname.replace(/^www\./, "");
1768
+ } catch {
1769
+ return value;
1770
+ }
1771
+ };
1772
+ host.replaceChildren();
1773
+ host.hidden = true;
1774
+ if (!isRecord2(catalog) || !Object.hasOwn(catalog, service))
1775
+ return;
1776
+ const row = catalog[service];
1777
+ if (!isRecord2(row))
1778
+ return;
1779
+ const vendor = clip(row.vendor, 80);
1780
+ const website = httpUrl(row.website);
1781
+ const guide = httpUrl(row.guide);
1782
+ const docs = httpUrl(row.docs);
1783
+ const logo = httpUrl(row.logo);
1784
+ const color = hexColor(row.color);
1785
+ const description = clip(row.description, 280);
1786
+ if (vendor === null || website === null && guide === null && docs === null)
1787
+ return;
1788
+ const add = (node) => {
1789
+ host.append(node);
1790
+ return node;
1791
+ };
1792
+ if (logo !== null) {
1793
+ const img = doc.createElement("img");
1794
+ img.alt = "";
1795
+ img.src = logo;
1796
+ img.width = 16;
1797
+ img.height = 16;
1798
+ img.decoding = "async";
1799
+ img.referrerPolicy = "no-referrer";
1800
+ if (color !== null)
1801
+ img.style.borderColor = color;
1802
+ add(img);
1803
+ }
1804
+ const name = doc.createElement("span");
1805
+ name.className = "vendor-name";
1806
+ name.textContent = vendor;
1807
+ if (description !== null)
1808
+ name.title = description;
1809
+ add(name);
1810
+ const sep = () => {
1811
+ const node = doc.createElement("span");
1812
+ node.className = "vendor-sep";
1813
+ node.setAttribute("aria-hidden", "true");
1814
+ node.textContent = "\xB7";
1815
+ return node;
1816
+ };
1817
+ const link = (href, label, title) => {
1818
+ const node = doc.createElement("a");
1819
+ node.className = "vendor-link";
1820
+ node.href = href;
1821
+ node.target = "_blank";
1822
+ node.rel = "noopener noreferrer";
1823
+ node.referrerPolicy = "no-referrer";
1824
+ node.textContent = label;
1825
+ node.title = title;
1826
+ return node;
1827
+ };
1828
+ if (website !== null) {
1829
+ add(sep());
1830
+ add(link(website, hostname(website), `${vendor} website`));
1831
+ }
1832
+ if (guide !== null) {
1833
+ add(sep());
1834
+ add(link(guide, "Docs", `Docs for ${vendor}`));
1835
+ }
1836
+ if (docs !== null) {
1837
+ add(sep());
1838
+ add(link(docs, "API", `${vendor} API reference`));
1839
+ }
1840
+ host.removeAttribute("hidden");
1841
+ host.hidden = false;
1842
+ }
1843
+ function startAdminBrand(env) {
1844
+ const requested = env.params.get("brands");
1845
+ let brandsUrl = env.brandsUrl;
1846
+ if (requested) {
1847
+ try {
1848
+ const url = new URL(requested, env.location.href);
1849
+ const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]" || url.origin === env.location.origin;
1850
+ if ((url.protocol === "https:" || url.protocol === "http:") && local)
1851
+ brandsUrl = url.href;
1852
+ } catch {
1853
+ brandsUrl = env.brandsUrl;
1854
+ }
1855
+ }
1856
+ const vendorHost = env.$("vendor");
1857
+ const brandRoot = env.document.querySelector("[data-mockingbird-admin]");
1858
+ const brandService = brandRoot?.getAttribute("data-service") || "";
1859
+ if (vendorHost === null)
1860
+ return;
1861
+ env.fetch(brandsUrl, {
1862
+ headers: { accept: "application/json" },
1863
+ signal: AbortSignal.timeout(5e3)
1864
+ }).then(async (response) => {
1865
+ if (!response.ok)
1866
+ return null;
1867
+ const catalog = await response.json();
1868
+ return catalog;
1869
+ }).then((catalog) => {
1870
+ mountAdminBrand(env.document, vendorHost, brandService, catalog);
1871
+ }).catch(() => {
1872
+ });
1873
+ }
1874
+ function faultPresetList(value) {
1875
+ if (!isRecord2(value))
1876
+ throw new Error("Fault presets response was not an object");
1877
+ if (!Object.hasOwn(value, "presets") || value.presets === void 0 || value.presets === null) {
1878
+ return { presets: [] };
1879
+ }
1880
+ if (!Array.isArray(value.presets))
1881
+ throw new Error("Fault presets response was not a list");
1882
+ return {
1883
+ presets: value.presets.flatMap((item) => {
1884
+ if (!isRecord2(item) || typeof item.name !== "string")
1885
+ return [];
1886
+ return [{ name: item.name }];
1887
+ })
1888
+ };
1889
+ }
1890
+ function bootAdmin(config) {
1891
+ const fieldKinds = ["string", "number", "boolean", "null", "object", "array", "unknown"];
1892
+ const sources = ["declared", "inferred", "mixed"];
1893
+ const isFieldKind = (value) => typeof value === "string" && fieldKinds.some((kind) => kind === value);
1894
+ const isSource = (value) => typeof value === "string" && sources.some((source) => source === value);
1895
+ const readJson2 = (text) => {
1896
+ const value = JSON.parse(text);
1897
+ return value;
1898
+ };
1899
+ const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => {
1900
+ switch (char) {
1901
+ case "&":
1902
+ return "&amp;";
1903
+ case "<":
1904
+ return "&lt;";
1905
+ case ">":
1906
+ return "&gt;";
1907
+ case '"':
1908
+ return "&quot;";
1909
+ default:
1910
+ return "&#39;";
1911
+ }
1912
+ });
1913
+ const el = (id, kind) => {
1914
+ const node = document.getElementById(id);
1915
+ if (!(node instanceof kind))
1916
+ throw new Error(`admin shell is missing #${id}`);
1917
+ return node;
1918
+ };
1919
+ const errorMessage = (data, fallback) => {
1920
+ if (!isRecord2(data))
1921
+ return fallback;
1922
+ const error = data.error;
1923
+ if (!isRecord2(error) || typeof error.message !== "string" || error.message === "") {
1924
+ return fallback;
1925
+ }
1926
+ return error.message;
1927
+ };
1928
+ const requireList = (value, label) => {
1929
+ if (value === void 0 || value === null)
1930
+ return [];
1931
+ if (!Array.isArray(value))
1932
+ throw new Error(`${label} was not a list`);
1933
+ return value;
1934
+ };
1935
+ const state = {
1936
+ namespace: "default",
1937
+ key: sessionStorage.getItem("mockingbird-admin-key") || "",
1938
+ view: "overview",
1939
+ collection: "",
1940
+ shape: null,
1941
+ route: null,
1942
+ editing: null
1943
+ };
1944
+ const standard = new Set(config.standardRoutes);
1945
+ const banner = el("banner", HTMLElement);
1946
+ const keyInput = el("admin-key", HTMLInputElement);
1947
+ keyInput.value = state.key;
1948
+ const params = new URLSearchParams(location.search);
1949
+ const queryKey = params.get("key");
1950
+ if (queryKey) {
1951
+ state.key = queryKey;
1952
+ keyInput.value = state.key;
1953
+ sessionStorage.setItem("mockingbird-admin-key", state.key);
1954
+ params.delete("key");
1955
+ const next = params.toString();
1956
+ history.replaceState(null, "", location.pathname + (next ? `?${next}` : ""));
1957
+ }
1958
+ const queryNamespace = params.get("namespace");
1959
+ if (queryNamespace)
1960
+ state.namespace = queryNamespace;
1961
+ const showError = (error) => {
1962
+ banner.textContent = error instanceof Error ? error.message : String(error);
1963
+ banner.classList.add("show");
1964
+ };
1965
+ const clearError = () => {
1966
+ banner.classList.remove("show");
1967
+ };
1968
+ const api = async (method, path, body) => {
1969
+ const headers = { accept: "application/json" };
1970
+ if (state.key !== "")
1971
+ headers[config.adminKeyHeader] = state.key;
1972
+ if (state.namespace !== "")
1973
+ headers["x-mockingbird-namespace"] = state.namespace;
1974
+ if (body !== void 0)
1975
+ headers["content-type"] = "application/json";
1976
+ const init = { method, headers };
1977
+ if (body !== void 0)
1978
+ init.body = JSON.stringify(body);
1979
+ const response = await fetch(config.adminPrefix + path, init);
1980
+ const text = await response.text();
1981
+ let data = null;
1982
+ if (text !== "") {
1983
+ try {
1984
+ data = readJson2(text);
1985
+ } catch {
1986
+ data = { raw: text };
1987
+ }
1988
+ }
1989
+ if (response.status === 401) {
1990
+ keyInput.focus();
1991
+ throw new Error(errorMessage(data, "Admin key required"));
1992
+ }
1993
+ if (!response.ok) {
1994
+ throw new Error(errorMessage(data, `${response.status} ${method} ${path}`));
1995
+ }
1996
+ return data;
1997
+ };
1998
+ const originGet = async (path) => {
1999
+ const response = await fetch(path, { headers: { accept: "application/json" } });
2000
+ if (!response.ok)
2001
+ throw new Error(`${response.status} ${path}`);
2002
+ const value = await response.json();
2003
+ return value;
2004
+ };
2005
+ const panelApi = {
2006
+ get: (path) => api("GET", path),
2007
+ send: (method, path, body) => api(method, path, body),
2008
+ namespace: () => state.namespace
2009
+ };
2010
+ const isPanelScript = (value) => typeof value === "function";
2011
+ const setTheme = (theme) => {
2012
+ if (theme)
2013
+ document.documentElement.dataset.theme = theme;
2014
+ else
2015
+ delete document.documentElement.dataset.theme;
2016
+ };
2017
+ const storedTheme = localStorage.getItem("mockingbird-admin-theme");
2018
+ if (storedTheme)
2019
+ setTheme(storedTheme);
2020
+ el("theme", HTMLButtonElement).addEventListener("click", () => {
2021
+ const current = document.documentElement.dataset.theme;
2022
+ const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
2023
+ const next = dark ? "light" : "dark";
2024
+ localStorage.setItem("mockingbird-admin-theme", next);
2025
+ setTheme(next);
2026
+ });
2027
+ const show = (view) => {
2028
+ state.view = view;
2029
+ for (const button of document.querySelectorAll("#nav button")) {
2030
+ if (!(button instanceof HTMLButtonElement))
2031
+ continue;
2032
+ button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false");
2033
+ }
2034
+ for (const section of document.querySelectorAll("main .view")) {
2035
+ if (!(section instanceof HTMLElement))
2036
+ continue;
2037
+ section.hidden = section.id !== `view-${view}`;
2038
+ }
2039
+ };
2040
+ el("nav", HTMLElement).addEventListener("click", (event) => {
2041
+ const target = event.target;
2042
+ if (!(target instanceof Element))
2043
+ return;
2044
+ const button = target.closest("button[data-view]");
2045
+ if (!(button instanceof HTMLButtonElement))
2046
+ return;
2047
+ const view = button.dataset.view;
2048
+ if (view === void 0)
2049
+ return;
2050
+ show(view);
2051
+ refresh().catch(showError);
2052
+ });
2053
+ const cards = (items) => `<div class="grid">${items.map((item) => `<article class="card"><div class="k">${item.k}</div><div class="v">${item.v}</div></article>`).join("")}</div>`;
2054
+ const table = (columns, rows) => {
2055
+ if (rows.length === 0)
2056
+ return '<p class="empty">Nothing here yet.</p>';
2057
+ const head = `<tr>${columns.map((column) => `<th>${column.label}</th>`).join("")}</tr>`;
2058
+ const body = rows.map((row) => {
2059
+ const cells = columns.map((column) => `<td data-label="${column.label}">${column.cell(row)}</td>`).join("");
2060
+ const attrs = "attrs" in row && typeof row.attrs === "string" ? row.attrs : "";
2061
+ return `<tr${attrs}>${cells}</tr>`;
2062
+ }).join("");
2063
+ return `<table><thead>${head}</thead><tbody>${body}</tbody></table>`;
2064
+ };
2065
+ const preview = (value) => {
2066
+ const text = JSON.stringify(value) ?? "null";
2067
+ return esc(text.length > 180 ? `${text.slice(0, 180)}\u2026` : text);
2068
+ };
2069
+ const when = (epoch) => {
2070
+ const date = new Date(epoch);
2071
+ return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString();
2072
+ };
2073
+ const clockCard = (epoch) => {
2074
+ const date = new Date(epoch);
2075
+ if (Number.isNaN(date.getTime()))
2076
+ return esc(String(epoch));
2077
+ const iso = date.toISOString();
2078
+ return `${esc(iso.slice(0, 10))}<span class="clock-time">${esc(iso.slice(11, 19))} UTC</span>`;
2079
+ };
2080
+ const decodeClock = (value) => {
2081
+ if (!isRecord2(value) || typeof value.now !== "number" || typeof value.offsetMs !== "number" || typeof value.frozen !== "boolean") {
2082
+ throw new Error("Clock response was not a clock");
2083
+ }
2084
+ return { now: value.now, offsetMs: value.offsetMs, frozen: value.frozen };
2085
+ };
2086
+ const decodeField = (value) => {
2087
+ if (!isRecord2(value) || typeof value.name !== "string" || !isFieldKind(value.kind)) {
2088
+ return void 0;
2089
+ }
2090
+ const field = {
2091
+ name: value.name,
2092
+ kind: value.kind,
2093
+ optional: value.optional === true
2094
+ };
2095
+ if (typeof value.description === "string")
2096
+ field.description = value.description;
2097
+ if (Array.isArray(value.fields)) {
2098
+ field.fields = value.fields.flatMap((item) => {
2099
+ const child = decodeField(item);
2100
+ return child === void 0 ? [] : [child];
2101
+ });
2102
+ }
2103
+ return field;
2104
+ };
2105
+ const decodeCollection = (value) => {
2106
+ if (!isRecord2(value) || typeof value.name !== "string")
2107
+ return void 0;
2108
+ const fields = Array.isArray(value.fields) ? value.fields.flatMap((item) => {
2109
+ const field = decodeField(item);
2110
+ return field === void 0 ? [] : [field];
2111
+ }) : [];
2112
+ const collection = {
2113
+ name: value.name,
2114
+ label: typeof value.label === "string" ? value.label : value.name,
2115
+ count: typeof value.count === "number" ? value.count : 0,
2116
+ source: isSource(value.source) ? value.source : "inferred",
2117
+ fields
2118
+ };
2119
+ if (typeof value.description === "string")
2120
+ collection.description = value.description;
2121
+ return collection;
2122
+ };
2123
+ const decodeState = (value) => {
2124
+ if (!isRecord2(value) || !Array.isArray(value.collections)) {
2125
+ throw new Error("State response has no collection list");
2126
+ }
2127
+ return {
2128
+ namespace: typeof value.namespace === "string" ? value.namespace : state.namespace,
2129
+ storageNamespace: typeof value.storageNamespace === "string" ? value.storageNamespace : "",
2130
+ collections: value.collections.flatMap((item) => {
2131
+ const collection = decodeCollection(item);
2132
+ return collection === void 0 ? [] : [collection];
2133
+ })
2134
+ };
2135
+ };
2136
+ const decodeRecords = (value) => requireList(isRecord2(value) ? value.records : void 0, "Records").flatMap((item) => {
2137
+ if (!isRecord2(item) || typeof item.id !== "string")
2138
+ return [];
2139
+ return [
2140
+ {
2141
+ id: item.id,
2142
+ seq: typeof item.seq === "number" ? item.seq : 0,
2143
+ value: item.value,
2144
+ attrs: ` class="clickable" data-id="${esc(item.id)}"`
2145
+ }
2146
+ ];
2147
+ });
2148
+ const textOf = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : "";
2149
+ const decodeJournal = (value) => requireList(isRecord2(value) ? value.requests : void 0, "Journal").flatMap((item) => {
2150
+ if (!isRecord2(item))
2151
+ return [];
2152
+ return [
2153
+ {
2154
+ at: textOf(item.at),
2155
+ method: textOf(item.method),
2156
+ path: textOf(item.path),
2157
+ status: textOf(item.status),
2158
+ durationMs: textOf(item.durationMs),
2159
+ operationId: textOf(item.operationId)
2160
+ }
2161
+ ];
2162
+ });
2163
+ const decodeFaults = (value) => requireList(isRecord2(value) ? value.faults : void 0, "Faults").flatMap((item) => {
2164
+ if (!isRecord2(item))
2165
+ return [];
2166
+ return [
2167
+ {
2168
+ id: textOf(item.id),
2169
+ method: typeof item.method === "string" ? item.method : void 0,
2170
+ pathPrefix: typeof item.pathPrefix === "string" ? item.pathPrefix : void 0,
2171
+ operationId: typeof item.operationId === "string" ? item.operationId : void 0,
2172
+ status: typeof item.status === "number" ? item.status : void 0,
2173
+ effect: typeof item.effect === "string" ? item.effect : void 0,
2174
+ drop: typeof item.drop === "boolean" ? item.drop : void 0,
2175
+ latencyMs: typeof item.latencyMs === "number" ? item.latencyMs : void 0
2176
+ }
2177
+ ];
2178
+ });
2179
+ const loadNamespaces = async () => {
2180
+ const data = await api("GET", "/namespaces");
2181
+ const select = el("namespace", HTMLSelectElement);
2182
+ const listed = requireList(isRecord2(data) ? data.namespaces : void 0, "Namespaces");
2183
+ const fallback = isRecord2(data) && typeof data.default === "string" && data.default !== "" ? data.default : "default";
2184
+ const names = (listed.length > 0 ? listed.map((name) => String(name)) : [fallback]).slice();
2185
+ if (!names.includes(state.namespace))
2186
+ names.unshift(state.namespace);
2187
+ select.innerHTML = names.map((name) => `<option value="${esc(name)}">${esc(name)}</option>`).join("");
2188
+ select.value = state.namespace;
2189
+ };
2190
+ const renderOverview = async () => {
2191
+ const [health, shape, clock, journal] = await Promise.all([
2192
+ originGet("/health"),
2193
+ api("GET", "/state"),
2194
+ api("GET", "/clock"),
2195
+ api("GET", "/requests?limit=8")
2196
+ ]);
2197
+ if (!isRecord2(health))
2198
+ throw new Error("Health response was not an object");
2199
+ const decoded = decodeState(shape);
2200
+ state.shape = decoded;
2201
+ const time = decodeClock(clock);
2202
+ el("overview-cards", HTMLElement).innerHTML = cards([
2203
+ {
2204
+ k: "Service",
2205
+ v: esc(typeof health.service === "string" && health.service !== "" ? health.service : config.service)
2206
+ },
2207
+ {
2208
+ k: "Status",
2209
+ v: esc(typeof health.status === "string" && health.status !== "" ? health.status : "ok")
2210
+ },
2211
+ { k: "Namespace", v: esc(state.namespace) },
2212
+ { k: "Collections", v: String(decoded.collections.length) },
2213
+ {
2214
+ k: "Records",
2215
+ v: String(decoded.collections.reduce((sum, item) => sum + item.count, 0))
2216
+ },
2217
+ { k: "Clock", v: clockCard(time.now) }
2218
+ ]);
2219
+ el("overview-requests", HTMLElement).innerHTML = table([
2220
+ { label: "Method", cell: (row) => esc(row.method) },
2221
+ { label: "Path", cell: (row) => esc(row.path) },
2222
+ {
2223
+ label: "Status",
2224
+ cell: (row) => `<span class="status-${row.status.charAt(0)}">${esc(row.status)}</span>`
2225
+ },
2226
+ { label: "Operation", cell: (row) => esc(row.operationId) }
2227
+ ], decodeJournal(journal));
2228
+ };
2229
+ const schemaChips = (collection) => {
2230
+ if (collection.fields.length === 0) {
2231
+ return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>';
2232
+ }
2233
+ return `<div class="chips">${collection.fields.map((field) => `<span class="chip">${esc(field.name)} <i>${esc(field.kind)}${field.optional ? "?" : ""}</i></span>`).join("")}</div>`;
2234
+ };
2235
+ const renderState = async () => {
2236
+ const shape = decodeState(await api("GET", "/state"));
2237
+ state.shape = shape;
2238
+ const host = el("collections", HTMLElement);
2239
+ if (shape.collections.length === 0) {
2240
+ host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>';
2241
+ el("schema", HTMLElement).innerHTML = "";
2242
+ el("records", HTMLElement).innerHTML = "";
2243
+ return;
2244
+ }
2245
+ if (!shape.collections.some((item) => item.name === state.collection)) {
2246
+ const first = shape.collections[0];
2247
+ if (first === void 0)
2248
+ return;
2249
+ state.collection = first.name;
2250
+ }
2251
+ host.innerHTML = shape.collections.map((item) => `<button type="button" class="collection" data-collection="${esc(item.name)}" aria-current="${item.name === state.collection ? "true" : "false"}"><strong>${esc(item.label)}</strong><small>${esc(item.count)} \xB7 ${esc(item.source)}</small></button>`).join("");
2252
+ const selected = shape.collections.find((item) => item.name === state.collection);
2253
+ if (selected === void 0)
2254
+ return;
2255
+ const description = selected.description ? `<p class="lede">${esc(selected.description)}</p>` : "";
2256
+ el("schema", HTMLElement).innerHTML = `<h3>${esc(selected.label)}</h3>${description}${schemaChips(selected)}`;
2257
+ const page2 = await api("GET", `/state/${encodeURIComponent(state.collection)}?limit=50`);
2258
+ el("records", HTMLElement).innerHTML = table([
2259
+ { label: "Id", cell: (row) => `<code>${esc(row.id)}</code>` },
2260
+ { label: "Seq", cell: (row) => esc(row.seq) },
2261
+ { label: "Value", cell: (row) => preview(row.value) }
2262
+ ], decodeRecords(page2));
2263
+ };
2264
+ const renderClock = async () => {
2265
+ const clock = decodeClock(await api("GET", "/clock"));
2266
+ el("clock-readout", HTMLElement).innerHTML = `<div class="k">Now</div><div class="v">${esc(when(clock.now))}</div><p>${esc(clock.now)} ms \xB7 offset ${esc(clock.offsetMs)} ms ${clock.frozen ? "<span class='badge warn'>frozen</span>" : "<span class='badge ok'>live</span>"}</p>`;
2267
+ const freeze = el("freeze", HTMLButtonElement);
2268
+ freeze.textContent = clock.frozen ? "Unfreeze" : "Freeze";
2269
+ freeze.dataset.frozen = clock.frozen ? "1" : "0";
2270
+ };
2271
+ const renderFaults = async () => {
2272
+ const [faults, presets] = await Promise.all([
2273
+ api("GET", "/faults"),
2274
+ api("GET", "/faults/presets").catch(() => ({ presets: [] }))
2275
+ ]);
2276
+ const select = el("fault-preset", HTMLSelectElement);
2277
+ const current = select.value;
2278
+ const list = faultPresetList(presets).presets;
2279
+ select.innerHTML = `<option value="">Custom rule</option>${list.map((preset) => `<option value="${esc(preset.name)}">${esc(preset.name)}</option>`).join("")}`;
2280
+ select.value = current;
2281
+ el("fault-list", HTMLElement).innerHTML = table([
2282
+ { label: "Id", cell: (row) => esc(row.id) },
2283
+ {
2284
+ label: "Match",
2285
+ cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter((part) => Boolean(part)).join(" ") || "all")
2286
+ },
2287
+ {
2288
+ label: "Effect",
2289
+ cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || ""))
2290
+ }
2291
+ ], decodeFaults(faults));
2292
+ };
2293
+ const renderJournal = async () => {
2294
+ const journal = await api("GET", "/requests?limit=100");
2295
+ el("journal", HTMLElement).innerHTML = table([
2296
+ { label: "When", cell: (row) => esc(row.at) },
2297
+ { label: "Method", cell: (row) => esc(row.method) },
2298
+ { label: "Path", cell: (row) => esc(row.path) },
2299
+ { label: "Status", cell: (row) => esc(row.status) },
2300
+ { label: "ms", cell: (row) => esc(row.durationMs) }
2301
+ ], decodeJournal(journal));
2302
+ };
2303
+ const renderRoutes = async () => {
2304
+ const data = await api("GET", "/");
2305
+ if (!isRecord2(data))
2306
+ throw new Error("Route list was not an object");
2307
+ const routes = requireList(data.routes, "Routes").map((route) => String(route));
2308
+ el("route-list", HTMLElement).innerHTML = routes.map((route) => {
2309
+ const extra = standard.has(route) ? "" : " <span class='badge'>extension</span>";
2310
+ const parts = route.split(" ");
2311
+ const method = parts[0] ?? "";
2312
+ const path = parts.slice(1).join(" ");
2313
+ return `<div class="route"><span class="method">${esc(method)}</span><span>${esc(path)}${extra}</span><button class="btn" type="button" data-route="${esc(route)}">Use</button></div>`;
2314
+ }).join("");
2315
+ };
2316
+ const renderers = {
2317
+ overview: renderOverview,
2318
+ state: renderState,
2319
+ clock: renderClock,
2320
+ faults: renderFaults,
2321
+ journal: renderJournal,
2322
+ routes: renderRoutes
2323
+ };
2324
+ async function refresh() {
2325
+ clearError();
2326
+ const render = renderers[state.view];
2327
+ if (render)
2328
+ await render();
2329
+ }
2330
+ el("namespace", HTMLSelectElement).addEventListener("change", () => {
2331
+ state.namespace = el("namespace", HTMLSelectElement).value;
2332
+ refresh().catch(showError);
2333
+ });
2334
+ keyInput.addEventListener("change", () => {
2335
+ state.key = keyInput.value.trim();
2336
+ sessionStorage.setItem("mockingbird-admin-key", state.key);
2337
+ refresh().catch(showError);
2338
+ });
2339
+ el("refresh", HTMLButtonElement).addEventListener("click", () => {
2340
+ refresh().catch(showError);
2341
+ });
2342
+ el("collections", HTMLElement).addEventListener("click", (event) => {
2343
+ const target = event.target;
2344
+ if (!(target instanceof Element))
2345
+ return;
2346
+ const button = target.closest("[data-collection]");
2347
+ if (!(button instanceof HTMLElement))
2348
+ return;
2349
+ const collection = button.dataset.collection;
2350
+ if (collection === void 0)
2351
+ return;
2352
+ state.collection = collection;
2353
+ renderState().catch(showError);
2354
+ });
2355
+ el("records", HTMLElement).addEventListener("click", (event) => {
2356
+ const target = event.target;
2357
+ if (!(target instanceof Element))
2358
+ return;
2359
+ const row = target.closest("[data-id]");
2360
+ if (!(row instanceof HTMLElement))
2361
+ return;
2362
+ const id = row.dataset.id;
2363
+ if (id === void 0)
2364
+ return;
2365
+ openEditor(id).catch(showError);
2366
+ });
2367
+ const editor = el("editor", HTMLDialogElement);
2368
+ const editorId = el("editor-id", HTMLInputElement);
2369
+ const editorValue = el("editor-value", HTMLTextAreaElement);
2370
+ const editorDelete = el("editor-delete", HTMLButtonElement);
2371
+ async function openEditor(id) {
2372
+ state.editing = id;
2373
+ el("editor-title", HTMLElement).textContent = id ? "Edit record" : "New record";
2374
+ editorId.value = id ?? "";
2375
+ editorId.readOnly = id !== null;
2376
+ editorDelete.hidden = id === null;
2377
+ if (id) {
2378
+ const record = await api("GET", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`);
2379
+ if (!isRecord2(record))
2380
+ throw new Error("Record response was not an object");
2381
+ editorValue.value = JSON.stringify(record.value, null, 2) ?? "";
2382
+ } else {
2383
+ editorValue.value = "{\n \n}";
2384
+ }
2385
+ editor.showModal();
2386
+ }
2387
+ el("new-record", HTMLButtonElement).addEventListener("click", () => {
2388
+ openEditor(null).catch(showError);
2389
+ });
2390
+ el("editor-form", HTMLFormElement).addEventListener("submit", (event) => {
2391
+ const submitter = event.submitter;
2392
+ if (!(submitter instanceof HTMLButtonElement) || submitter.value !== "save")
2393
+ return;
2394
+ event.preventDefault();
2395
+ let value;
2396
+ try {
2397
+ value = readJson2(editorValue.value);
2398
+ } catch {
2399
+ showError(new Error("Value is not JSON"));
2400
+ return;
2401
+ }
2402
+ const id = editorId.value.trim();
2403
+ const path = `/state/${encodeURIComponent(state.collection)}`;
2404
+ const request = state.editing ? api("PUT", `${path}/${encodeURIComponent(id)}`, { value }) : api("POST", path, { id: id || void 0, value });
2405
+ request.then(() => {
2406
+ editor.close();
2407
+ return renderState();
2408
+ }).catch(showError);
2409
+ });
2410
+ editorDelete.addEventListener("click", () => {
2411
+ const id = state.editing;
2412
+ if (!id)
2413
+ return;
2414
+ api("DELETE", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`).then(() => {
2415
+ editor.close();
2416
+ return renderState();
2417
+ }).catch(showError);
2418
+ });
2419
+ for (const button of document.querySelectorAll("[data-advance]")) {
2420
+ if (!(button instanceof HTMLButtonElement))
2421
+ continue;
2422
+ button.addEventListener("click", () => {
2423
+ const raw = button.dataset.advance;
2424
+ if (raw === void 0)
2425
+ return;
2426
+ api("POST", "/clock", { advance: Number(raw) }).then(() => renderClock()).catch(showError);
2427
+ });
2428
+ }
2429
+ el("freeze", HTMLButtonElement).addEventListener("click", () => {
2430
+ const frozen = el("freeze", HTMLButtonElement).dataset.frozen === "1";
2431
+ api("POST", "/clock", { freeze: !frozen }).then(() => renderClock()).catch(showError);
2432
+ });
2433
+ el("clock-reset", HTMLButtonElement).addEventListener("click", () => {
2434
+ api("POST", "/clock", { reset: true }).then(() => renderClock()).catch(showError);
2435
+ });
2436
+ el("clock-set", HTMLFormElement).addEventListener("submit", (event) => {
2437
+ event.preventDefault();
2438
+ const raw = el("clock-instant", HTMLInputElement).value.trim();
2439
+ const set = /^-?\d+$/.test(raw) ? Number(raw) : raw;
2440
+ api("POST", "/clock", { set }).then(() => renderClock()).catch(showError);
2441
+ });
2442
+ el("fault-form", HTMLFormElement).addEventListener("submit", (event) => {
2443
+ event.preventDefault();
2444
+ const preset = el("fault-preset", HTMLSelectElement).value;
2445
+ if (preset !== "") {
2446
+ api("POST", "/faults", { preset }).then(() => renderFaults()).catch(showError);
2447
+ return;
2448
+ }
2449
+ const status = Number(el("fault-status", HTMLInputElement).value);
2450
+ if (!Number.isFinite(status)) {
2451
+ showError(new Error("Status must be a number, or choose a preset"));
2452
+ return;
2453
+ }
2454
+ const path = el("fault-path", HTMLInputElement).value;
2455
+ const method = el("fault-method", HTMLInputElement).value;
2456
+ api("POST", "/faults", {
2457
+ status,
2458
+ ...path !== "" ? { pathPrefix: path } : {},
2459
+ ...method !== "" ? { method } : {}
2460
+ }).then(() => renderFaults()).catch(showError);
2461
+ });
2462
+ el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
2463
+ api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
2464
+ });
2465
+ el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
2466
+ api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
2467
+ });
2468
+ el("route-list", HTMLElement).addEventListener("click", (event) => {
2469
+ const target = event.target;
2470
+ if (!(target instanceof Element))
2471
+ return;
2472
+ const button = target.closest("[data-route]");
2473
+ if (!(button instanceof HTMLElement))
2474
+ return;
2475
+ const route = button.dataset.route;
2476
+ if (route === void 0)
2477
+ return;
2478
+ state.route = route;
2479
+ el("route-title", HTMLElement).textContent = route;
2480
+ const method = route.split(" ")[0] ?? "";
2481
+ el("route-body", HTMLTextAreaElement).disabled = method === "GET" || method === "DELETE";
2482
+ });
2483
+ el("route-form", HTMLFormElement).addEventListener("submit", (event) => {
2484
+ event.preventDefault();
2485
+ const send = async () => {
2486
+ if (state.route === null)
2487
+ throw new Error("Choose a route first");
2488
+ const parts = state.route.split(" ");
2489
+ const method = parts[0];
2490
+ const path = parts.slice(1).join(" ");
2491
+ if (method === void 0)
2492
+ throw new Error("Choose a route first");
2493
+ const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_match, key) => prompt(`Value for ${key}`) || "");
2494
+ let body;
2495
+ const routeBody = el("route-body", HTMLTextAreaElement);
2496
+ if (method !== "GET" && method !== "DELETE" && routeBody.value.trim() !== "") {
2497
+ try {
2498
+ body = readJson2(routeBody.value);
2499
+ } catch {
2500
+ throw new Error("Body is not JSON");
2501
+ }
2502
+ }
2503
+ const data = await api(method, concrete, body);
2504
+ el("route-result", HTMLElement).textContent = JSON.stringify(data, null, 2);
2505
+ };
2506
+ send().catch(showError);
2507
+ });
2508
+ const decodePanel = (value) => {
2509
+ if (!isRecord2(value) || typeof value.id !== "string" || typeof value.title !== "string") {
2510
+ return void 0;
2511
+ }
2512
+ const panel = {
2513
+ id: value.id,
2514
+ title: value.title,
2515
+ description: typeof value.description === "string" ? value.description : "",
2516
+ html: typeof value.html === "string" ? value.html : ""
2517
+ };
2518
+ if (typeof value.script === "string")
2519
+ panel.script = value.script;
2520
+ return panel;
2521
+ };
2522
+ const mountPanels = (panels) => {
2523
+ const nav = el("nav", HTMLElement);
2524
+ const views = el("views", HTMLElement);
2525
+ for (const panel of panels) {
2526
+ const button = document.createElement("button");
2527
+ button.type = "button";
2528
+ button.dataset.view = panel.id;
2529
+ button.textContent = panel.title;
2530
+ nav.append(button);
2531
+ const section = document.createElement("section");
2532
+ section.className = "view";
2533
+ section.id = `view-${panel.id}`;
2534
+ section.hidden = true;
2535
+ const title = document.createElement("h2");
2536
+ title.textContent = panel.title;
2537
+ const lede = document.createElement("p");
2538
+ lede.className = "lede";
2539
+ lede.textContent = panel.description;
2540
+ const body = document.createElement("div");
2541
+ body.className = "panel-body";
2542
+ body.id = `panel-${panel.id}`;
2543
+ body.innerHTML = panel.html;
2544
+ section.append(title, lede, body);
2545
+ views.append(section);
2546
+ if (panel.script === void 0)
2547
+ continue;
2548
+ try {
2549
+ const compiled = new Function("root", "api", panel.script);
2550
+ if (!isPanelScript(compiled))
2551
+ throw new Error("panel script did not compile");
2552
+ compiled(body, panelApi);
2553
+ } catch (error) {
2554
+ const note = document.createElement("p");
2555
+ note.className = "banner show";
2556
+ note.textContent = error instanceof Error ? error.message : String(error);
2557
+ body.append(note);
2558
+ }
2559
+ }
2560
+ };
2561
+ startAdminBrand({
2562
+ document,
2563
+ $: (id) => document.getElementById(id),
2564
+ params,
2565
+ location: { href: location.href, origin: location.origin },
2566
+ fetch,
2567
+ brandsUrl: config.brandsUrl
2568
+ });
2569
+ loadNamespaces().then(() => api("GET", "/ui/manifest")).then((manifest) => {
2570
+ const panels = requireList(isRecord2(manifest) ? manifest.panels : void 0, "Panels").flatMap((panel) => {
2571
+ const decoded = decodePanel(panel);
2572
+ return decoded === void 0 ? [] : [decoded];
2573
+ });
2574
+ mountPanels(panels);
2575
+ return refresh();
2576
+ }).catch(showError);
2577
+ }
2578
+ var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
2579
+ var adminClientSource = (config) => {
2580
+ const payload = JSON.stringify(config).replace(/</g, "\\u003c");
2581
+ return `<script>
2582
+ ${clientPrelude}
2583
+ bootAdmin(${payload});
2584
+ </script>`;
2585
+ };
2586
+
2587
+ // ../core/dist/surface.js
2588
+ var defineMock = (runtime) => runtime;
2589
+ var STANDARD_ADMIN_ROUTES = [
2590
+ "GET /",
2591
+ "POST /reset",
2592
+ "GET /namespaces",
2593
+ "GET /clock",
2594
+ "POST /clock",
2595
+ "GET /faults",
2596
+ "POST /faults",
2597
+ "DELETE /faults",
2598
+ "POST /snapshots",
2599
+ "POST /snapshots/:id/restore",
2600
+ "DELETE /snapshots/:id",
2601
+ "GET /timeline",
2602
+ "POST /checkpoints",
2603
+ "POST /branches/:name",
2604
+ "POST /branches/:name/checkout",
2605
+ "GET /requests",
2606
+ "DELETE /requests",
2607
+ "GET /metrics",
2608
+ "DELETE /metrics",
2609
+ "GET /credentials",
2610
+ "PUT /credentials",
2611
+ "DELETE /credentials",
2612
+ "GET /state",
2613
+ "GET /state/:collection",
2614
+ "GET /state/:collection/:id",
2615
+ "POST /state/:collection",
2616
+ "PUT /state/:collection/:id",
2617
+ "PATCH /state/:collection/:id",
2618
+ "DELETE /state/:collection/:id",
2619
+ "GET /ui",
2620
+ "GET /ui/",
2621
+ "GET /ui/manifest"
2622
+ ];
2623
+ var PANEL_ID = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;
2624
+ var assertAdminUi = (ui) => {
2625
+ if (!ui?.panels)
2626
+ return;
2627
+ const seen = /* @__PURE__ */ new Set();
2628
+ for (const panel of ui.panels) {
2629
+ if (!PANEL_ID.test(panel.id)) {
2630
+ throw new RangeError(`admin panel id must match ${PANEL_ID}: ${JSON.stringify(panel.id)}`);
2631
+ }
2632
+ if (seen.has(panel.id))
2633
+ throw new RangeError(`duplicate admin panel id ${panel.id}`);
2634
+ seen.add(panel.id);
2635
+ if (panel.title.trim() === "")
2636
+ throw new RangeError(`admin panel ${panel.id} needs a title`);
2637
+ }
2638
+ };
2639
+
2640
+ // ../core/dist/admin-ui.js
2641
+ var escapeHtml = (value) => value.replace(/[&<>"']/g, (char) => {
2642
+ switch (char) {
2643
+ case "&":
2644
+ return "&amp;";
2645
+ case "<":
2646
+ return "&lt;";
2647
+ case ">":
2648
+ return "&gt;";
2649
+ case '"':
2650
+ return "&quot;";
2651
+ default:
2652
+ return "&#39;";
2653
+ }
2654
+ });
2655
+ var ADMIN_BRANDS_URL = "https://mockingbird.chrisvouga.dev/brands.json";
2656
+ var renderAdminDocument = (service) => {
2657
+ const name = escapeHtml(service);
2658
+ const script = adminClientSource({
2659
+ standardRoutes: STANDARD_ADMIN_ROUTES,
2660
+ adminPrefix: ADMIN_PREFIX,
2661
+ adminKeyHeader: ADMIN_KEY_HEADER,
2662
+ brandsUrl: ADMIN_BRANDS_URL,
2663
+ service
2664
+ });
2665
+ return `<!DOCTYPE html>
2666
+ <html lang="en">
2667
+ <head>
2668
+ <meta charset="utf-8">
2669
+ <meta name="viewport" content="width=device-width, initial-scale=1">
2670
+ <meta name="color-scheme" content="light dark">
2671
+ <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='2' fill='%2327272a'/%3E%3C/svg%3E">
2672
+ <title>${name} admin</title>
2673
+ <style>
2674
+ ${CSS_RESET}
2675
+ :root {
2676
+ color-scheme: light;
2677
+ --bg: #fafafa;
2678
+ --bg-elev: #ffffff;
2679
+ --bg-muted: #f4f4f5;
2680
+ --bg-inset: #f4f4f5;
2681
+ --line: #dedee3;
2682
+ --line-strong: #c4c4cc;
2683
+ --ink: #18181b;
2684
+ --muted: #62626b;
2685
+ --faint: #71717a;
2686
+ --accent: #27272a;
2687
+ --accent-ink: #ffffff;
2688
+ --accent-soft: #f4f4f5;
2689
+ --ok: #166534;
2690
+ --ok-soft: #f0fdf4;
2691
+ --warn: #a16207;
2692
+ --warn-soft: #fefce8;
2693
+ --err: #a82d32;
2694
+ --err-soft: #fef2f2;
2695
+ --shadow: none;
2696
+ --sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
2697
+ --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
2698
+ --radius: 10px;
2699
+ --header: 56px;
2700
+ --nav: 200px;
2701
+ }
2702
+ :root[data-theme="dark"] {
2703
+ color-scheme: dark;
2704
+ --bg: #111113;
2705
+ --bg-elev: #19191c;
2706
+ --bg-muted: #242428;
2707
+ --bg-inset: #111113;
2708
+ --line: #36363c;
2709
+ --line-strong: #52525b;
2710
+ --ink: #f4f4f5;
2711
+ --muted: #a9a9b2;
2712
+ --faint: #71717a;
2713
+ --accent: #e4e4e7;
2714
+ --accent-ink: #18181b;
2715
+ --accent-soft: #242428;
2716
+ --ok: #86efac;
2717
+ --ok-soft: #14241b;
2718
+ --warn: #fde68a;
2719
+ --warn-soft: #2a2410;
2720
+ --err: #ffaaaa;
2721
+ --err-soft: #2c1516;
2722
+ --shadow: none;
2723
+ }
2724
+ @media (prefers-color-scheme: dark) {
2725
+ :root:not([data-theme="light"]) {
2726
+ color-scheme: dark;
2727
+ --bg: #111113;
2728
+ --bg-elev: #19191c;
2729
+ --bg-muted: #242428;
2730
+ --bg-inset: #111113;
2731
+ --line: #36363c;
2732
+ --line-strong: #52525b;
2733
+ --ink: #f4f4f5;
2734
+ --muted: #a9a9b2;
2735
+ --faint: #71717a;
2736
+ --accent: #e4e4e7;
2737
+ --accent-ink: #18181b;
2738
+ --accent-soft: #242428;
2739
+ --ok: #86efac;
2740
+ --ok-soft: #14241b;
2741
+ --warn: #fde68a;
2742
+ --warn-soft: #2a2410;
2743
+ --err: #ffaaaa;
2744
+ --err-soft: #2c1516;
2745
+ --shadow: none;
2746
+ }
2747
+ }
2748
+ /* Fill the viewport, then grow with the content so the background covers the scroll. */
2749
+ html { height: 100%; }
2750
+ body {
2751
+ margin: 0;
2752
+ min-height: 100%;
2753
+ display: flex;
2754
+ flex-direction: column;
2755
+ font-family: var(--sans);
2756
+ background: var(--bg);
2757
+ color: var(--ink);
2758
+ line-height: 1.45;
2759
+ padding-left: env(safe-area-inset-left);
2760
+ padding-right: env(safe-area-inset-right);
2761
+ -webkit-text-size-adjust: 100%;
2762
+ }
2763
+ :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
2764
+ .app { flex: 1 0 auto; display: flex; flex-direction: column; min-width: 0; width: 100%; }
2765
+ header.top {
2766
+ position: sticky; top: 0; z-index: 5;
2767
+ display: flex; align-items: center; gap: 12px;
2768
+ min-height: var(--header); padding: 10px 16px;
2769
+ background: var(--bg-elev);
2770
+ border-bottom: 1px solid var(--line);
2771
+ min-width: 0; max-width: 100%;
2772
+ }
2773
+ .brand {
2774
+ display: flex; flex-flow: row wrap; align-items: center; gap: 14px;
2775
+ min-width: 0; max-width: 100%; margin-right: auto;
2776
+ }
2777
+ .brand-id { display: flex; flex-direction: column; min-width: 0; flex: none; }
2778
+ .brand-id strong { font-size: 15px; letter-spacing: -0.01em; }
2779
+ .brand-id span { color: var(--faint); font-size: 12px; }
2780
+ .vendor {
2781
+ display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
2782
+ min-width: 0; max-width: 100%; font-size: 12px; color: var(--muted);
2783
+ }
2784
+ .vendor[hidden] { display: none; }
2785
+ /* Logos are drawn for a light tile, same as the docs site, so the plate stays white. */
2786
+ .vendor img {
2787
+ width: 16px; height: 16px; padding: 2px; box-sizing: content-box;
2788
+ border-radius: 4px; border: 1px solid var(--line); background: #fff; object-fit: contain;
2789
+ }
2790
+ .vendor-name { color: var(--ink); font-weight: 600; }
2791
+ .vendor-sep { color: var(--faint); }
2792
+ .vendor-link { color: inherit; text-decoration: none; }
2793
+ .vendor-link:hover { color: var(--ink); text-decoration: underline; }
2794
+ .controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
2795
+ label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
2796
+ label.field input, label.field select, .controls input[type="password"] {
2797
+ background: var(--bg-inset); border: 1px solid var(--line); border-radius: 8px;
2798
+ padding: 7px 8px; min-height: 36px;
2799
+ }
2800
+ .icon-btn, .btn, .btn-primary, .btn-quiet, .btn-danger {
2801
+ border-radius: 9px; min-height: 36px; padding: 0 12px; border: 1px solid var(--line);
2802
+ background: var(--bg-elev);
2803
+ }
2804
+ .btn-primary { background: var(--accent); color: var(--accent-ink); border-color: transparent; }
2805
+ .btn-quiet { background: transparent; }
2806
+ .btn-danger { color: var(--err); }
2807
+ /* minmax(0, 1fr): a 1fr column will not shrink below its content, so a long
2808
+ record or the full nav row would widen the page past the viewport. */
2809
+ .layout {
2810
+ display: grid; grid-template-columns: var(--nav) minmax(0, 1fr);
2811
+ flex: 1 0 auto; min-width: 0;
2812
+ }
2813
+ nav.side {
2814
+ border-right: 1px solid var(--line); padding: 12px;
2815
+ display: flex; flex-direction: column; gap: 4px;
2816
+ background: var(--bg-elev);
2817
+ }
2818
+ nav.side button {
2819
+ text-align: left; border: 0; background: transparent; border-radius: 9px;
2820
+ padding: 9px 10px; min-height: 40px; color: var(--muted);
2821
+ }
2822
+ nav.side button[aria-current="true"] { background: var(--accent-soft); color: var(--ink); font-weight: 600; }
2823
+ main { padding: 20px; max-width: 1100px; width: 100%; min-width: 0; }
2824
+ .view h2 { margin: 0 0 4px; font-size: 22px; letter-spacing: -0.02em; }
2825
+ .lede { margin: 0 0 16px; color: var(--muted); }
2826
+ .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
2827
+ .card {
2828
+ background: var(--bg-elev); border: 1px solid var(--line); border-radius: var(--radius);
2829
+ padding: 14px 16px; box-shadow: var(--shadow);
2830
+ }
2831
+ .card .k { color: var(--faint); font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em; }
2832
+ .card .v { display: flex; flex-direction: column; gap: 2px; font-size: 22px; margin-top: 4px; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
2833
+ .card .v .clock-time { font-size: 14px; color: var(--muted); }
2834
+ .split { display: grid; grid-template-columns: minmax(0, 240px) minmax(0, 1fr); gap: 16px; align-items: start; }
2835
+ .collection {
2836
+ width: 100%; text-align: left; border: 1px solid transparent; background: transparent;
2837
+ border-radius: 10px; padding: 8px 10px; min-height: 44px;
2838
+ }
2839
+ .collection[aria-current="true"] { background: var(--bg-elev); border-color: var(--line); }
2840
+ .collection small { display: block; color: var(--faint); }
2841
+ .chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0 14px; }
2842
+ .chip {
2843
+ font-family: var(--mono); font-size: 12px; background: var(--bg-muted);
2844
+ border-radius: 999px; padding: 4px 8px;
2845
+ }
2846
+ .chip i { color: var(--faint); font-style: normal; }
2847
+ table { width: 100%; border-collapse: collapse; font-size: 14px; }
2848
+ th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--line); vertical-align: top; }
2849
+ th { color: var(--faint); font-weight: 600; font-size: 12px; }
2850
+ tr.clickable { cursor: pointer; }
2851
+ tr.clickable:hover { background: var(--bg-muted); }
2852
+ pre, code, .mono { font-family: var(--mono); overflow-wrap: anywhere; }
2853
+ pre {
2854
+ margin: 0; white-space: pre-wrap; word-break: break-word;
2855
+ background: var(--bg-inset); border-radius: 10px; padding: 12px; font-size: 12px;
2856
+ }
2857
+ .row-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0; }
2858
+ .banner {
2859
+ display: none; margin-bottom: 12px; padding: 10px 12px; border-radius: 10px;
2860
+ background: var(--err-soft); color: var(--err);
2861
+ }
2862
+ .banner.show { display: block; }
2863
+ .empty { color: var(--muted); padding: 24px 8px; }
2864
+ dialog {
2865
+ border: 1px solid var(--line); border-radius: var(--radius); padding: 0; background: var(--bg-elev);
2866
+ color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow: none;
2867
+ }
2868
+ dialog::backdrop { background: rgb(20 19 17 / 0.45); }
2869
+ dialog form, .dialog-body { padding: 16px; display: flex; flex-direction: column; gap: 10px; }
2870
+ textarea {
2871
+ width: 100%; min-height: 180px; resize: vertical; border-radius: 10px;
2872
+ border: 1px solid var(--line); background: var(--bg-inset); padding: 10px;
2873
+ font-family: var(--mono); font-size: 12px;
2874
+ }
2875
+ .dialog-actions { display: flex; justify-content: flex-end; gap: 8px; }
2876
+ .badge { font-size: 11px; border-radius: 999px; padding: 2px 7px; background: var(--bg-muted); color: var(--muted); }
2877
+ .badge.ok { background: var(--ok-soft); color: var(--ok); }
2878
+ .badge.warn { background: var(--warn-soft); color: var(--warn); }
2879
+ .status-2 { color: var(--ok); }
2880
+ .status-4, .status-5, .status-0 { color: var(--err); }
2881
+ .stack { display: flex; flex-direction: column; gap: 12px; }
2882
+ .route {
2883
+ display: grid; grid-template-columns: 88px 1fr auto; gap: 8px; align-items: center;
2884
+ padding: 8px 0; border-bottom: 1px solid var(--line);
2885
+ }
2886
+ .method { font-family: var(--mono); font-size: 12px; font-weight: 700; }
2887
+ @media (max-width: 800px) {
2888
+ header.top {
2889
+ align-items: stretch; flex-direction: column;
2890
+ padding-top: max(10px, env(safe-area-inset-top));
2891
+ }
2892
+ input, select, textarea { font-size: 16px; }
2893
+ .brand, .vendor, .controls { width: 100%; }
2894
+ label.field, label.field select, label.field input { flex: 1; min-width: 0; }
2895
+ .layout { grid-template-columns: minmax(0, 1fr); }
2896
+ nav.side {
2897
+ flex-direction: row; align-items: center; overflow-x: auto; min-width: 0;
2898
+ border-right: 0; border-bottom: 1px solid var(--line);
2899
+ padding: 8px;
2900
+ }
2901
+ nav.side button { white-space: nowrap; flex: none; }
2902
+ .split { grid-template-columns: minmax(0, 1fr); }
2903
+ main { padding: 14px; }
2904
+ table, thead, tbody, tr, th, td { display: block; }
2905
+ thead { display: none; }
2906
+ tr {
2907
+ border: 1px solid var(--line); border-radius: 12px; margin-bottom: 8px; padding: 6px;
2908
+ background: var(--bg-elev); min-width: 0;
2909
+ }
2910
+ td {
2911
+ border: 0; display: flex; flex-wrap: wrap; justify-content: space-between; gap: 4px 12px;
2912
+ min-width: 0; overflow-wrap: anywhere;
2913
+ }
2914
+ td::before { content: attr(data-label); color: var(--faint); font-size: 12px; flex: none; }
2915
+ .route { grid-template-columns: 72px 1fr; }
2916
+ .route button { grid-column: 1 / -1; }
2917
+ }
2918
+ </style>
2919
+ </head>
2920
+ <body>
2921
+ <div class="app" data-service="${name}" data-mockingbird-admin>
2922
+ <header class="top">
2923
+ <div class="brand">
2924
+ <div class="brand-id">
2925
+ <strong>${name}</strong>
2926
+ <span>Admin</span>
2927
+ </div>
2928
+ <div class="vendor" id="vendor" hidden></div>
2929
+ </div>
2930
+ <div class="controls">
2931
+ <label class="field">Namespace
2932
+ <select id="namespace" aria-label="Namespace"></select>
2933
+ </label>
2934
+ <label class="field">Admin key
2935
+ <input id="admin-key" type="password" autocomplete="off" spellcheck="false" aria-label="Admin key">
2936
+ </label>
2937
+ <button class="icon-btn" id="theme" type="button" aria-label="Toggle color theme">Theme</button>
2938
+ <button class="btn" id="refresh" type="button">Refresh</button>
2939
+ </div>
2940
+ </header>
2941
+ <div class="layout">
2942
+ <nav class="side" id="nav" aria-label="Admin sections">
2943
+ <button type="button" data-view="overview" aria-current="true">Overview</button>
2944
+ <button type="button" data-view="state">State</button>
2945
+ <button type="button" data-view="clock">Clock</button>
2946
+ <button type="button" data-view="faults">Faults</button>
2947
+ <button type="button" data-view="journal">Journal</button>
2948
+ <button type="button" data-view="routes">Routes</button>
2949
+ </nav>
2950
+ <main id="views">
2951
+ <p class="banner" id="banner" role="alert"></p>
2952
+ <section class="view" id="view-overview">
2953
+ <h2>Overview</h2>
2954
+ <p class="lede">Health, clock, and the collections this namespace is holding.</p>
2955
+ <div class="grid" id="overview-cards"></div>
2956
+ <h3>Recent requests</h3>
2957
+ <div id="overview-requests"></div>
2958
+ </section>
2959
+ <section class="view" id="view-state" hidden>
2960
+ <h2>State</h2>
2961
+ <p class="lede">Every collection in this namespace. Declared fields stay visible before any row exists; stored rows add whatever else they contain.</p>
2962
+ <div class="split">
2963
+ <div id="collections"></div>
2964
+ <div>
2965
+ <div id="schema"></div>
2966
+ <div class="row-actions">
2967
+ <button class="btn-primary" id="new-record" type="button">New record</button>
2968
+ </div>
2969
+ <div id="records"></div>
2970
+ </div>
2971
+ </div>
2972
+ </section>
2973
+ <section class="view" id="view-clock" hidden>
2974
+ <h2>Clock</h2>
2975
+ <p class="lede">The clock every timestamp in this mock reads.</p>
2976
+ <div class="card" id="clock-readout"></div>
2977
+ <div class="row-actions">
2978
+ <button class="btn" type="button" data-advance="60000">+1m</button>
2979
+ <button class="btn" type="button" data-advance="900000">+15m</button>
2980
+ <button class="btn" type="button" data-advance="3600000">+1h</button>
2981
+ <button class="btn" type="button" data-advance="86400000">+1d</button>
2982
+ <button class="btn" type="button" data-advance="-3600000">\u22121h</button>
2983
+ <button class="btn" id="freeze" type="button">Freeze</button>
2984
+ <button class="btn-quiet" id="clock-reset" type="button">Reset</button>
2985
+ </div>
2986
+ <form id="clock-set" class="row-actions">
2987
+ <label class="field">Set instant
2988
+ <input id="clock-instant" type="text" placeholder="2026-01-01T00:00:00Z or epoch ms" size="32">
2989
+ </label>
2990
+ <button class="btn-primary" type="submit">Set</button>
2991
+ </form>
2992
+ </section>
2993
+ <section class="view" id="view-faults" hidden>
2994
+ <h2>Faults</h2>
2995
+ <p class="lede">Inject a response, a delay, or a named preset. Rules apply to this namespace.</p>
2996
+ <form id="fault-form" class="card stack">
2997
+ <label class="field">Preset
2998
+ <select id="fault-preset"><option value="">Custom rule</option></select>
2999
+ </label>
3000
+ <label class="field">Status
3001
+ <input id="fault-status" inputmode="numeric" placeholder="503">
3002
+ </label>
3003
+ <label class="field">Path prefix
3004
+ <input id="fault-path" placeholder="/v1">
3005
+ </label>
3006
+ <label class="field">Method
3007
+ <input id="fault-method" placeholder="GET">
3008
+ </label>
3009
+ <div class="dialog-actions">
3010
+ <button class="btn-primary" type="submit">Add fault</button>
3011
+ <button class="btn-danger" id="fault-clear" type="button">Clear all</button>
3012
+ </div>
3013
+ </form>
3014
+ <div id="fault-list"></div>
3015
+ </section>
3016
+ <section class="view" id="view-journal" hidden>
3017
+ <h2>Journal</h2>
3018
+ <p class="lede">Requests this namespace has seen. Bodies are not stored.</p>
3019
+ <div class="row-actions">
3020
+ <button class="btn-danger" id="journal-clear" type="button">Clear journal</button>
3021
+ </div>
3022
+ <div id="journal"></div>
3023
+ </section>
3024
+ <section class="view" id="view-routes" hidden>
3025
+ <h2>Routes</h2>
3026
+ <p class="lede">Shared controls, plus any routes this mock adds. Extra routes are how a bespoke workflow shows up beside the default UI.</p>
3027
+ <div id="route-list"></div>
3028
+ <form id="route-form" class="card stack">
3029
+ <strong id="route-title">Select a route</strong>
3030
+ <label class="field">JSON body
3031
+ <textarea id="route-body" placeholder="{ }"></textarea>
3032
+ </label>
3033
+ <div class="dialog-actions">
3034
+ <button class="btn-primary" type="submit">Send</button>
3035
+ </div>
3036
+ <pre id="route-result">Response will show here.</pre>
3037
+ </form>
3038
+ </section>
3039
+ </main>
3040
+ </div>
3041
+ </div>
3042
+ <dialog id="editor">
3043
+ <form method="dialog" id="editor-form">
3044
+ <strong id="editor-title">Record</strong>
3045
+ <label class="field">Id
3046
+ <input id="editor-id" required>
3047
+ </label>
3048
+ <label class="field">JSON value
3049
+ <textarea id="editor-value"></textarea>
3050
+ </label>
3051
+ <div class="dialog-actions">
3052
+ <button class="btn-danger" id="editor-delete" type="button" value="delete">Delete</button>
3053
+ <button class="btn-quiet" type="submit" value="cancel">Cancel</button>
3054
+ <button class="btn-primary" type="submit" value="save">Save</button>
3055
+ </div>
3056
+ </form>
3057
+ </dialog>
3058
+ ${script}
3059
+ </body>
3060
+ </html>`;
3061
+ };
3062
+ var htmlResponse = (body) => new Response(body, {
3063
+ status: 200,
3064
+ headers: {
3065
+ "content-type": "text/html; charset=utf-8",
3066
+ "cache-control": "no-store"
3067
+ }
3068
+ });
3069
+ var json2 = (body) => new Response(JSON.stringify(body), {
3070
+ status: 200,
3071
+ headers: { "content-type": "application/json; charset=utf-8" }
3072
+ });
3073
+ var adminUiRoutes = (service, ui) => {
3074
+ const shell = () => renderAdminDocument(service);
3075
+ const document3 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
3076
+ return {
3077
+ "GET /ui": () => htmlResponse(document3()),
3078
+ "GET /ui/": () => htmlResponse(document3()),
3079
+ "GET /ui/manifest": () => json2({
3080
+ service,
3081
+ panels: (ui?.panels ?? []).map((panel) => ({
3082
+ id: panel.id,
3083
+ title: panel.title,
3084
+ ...panel.description !== void 0 ? { description: panel.description } : {},
3085
+ html: panel.html,
3086
+ ...panel.script !== void 0 ? { script: panel.script } : {}
3087
+ }))
3088
+ })
3089
+ };
3090
+ };
3091
+
1359
3092
  // ../../openapi/metadata/dist/types.js
1360
3093
  var EXTENSION_KEYS = {
1361
3094
  operation: "x-mockingbird",
@@ -1368,11 +3101,11 @@ var EXTENSION_KEYS = {
1368
3101
  };
1369
3102
 
1370
3103
  // ../../openapi/metadata/dist/read.js
1371
- var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3104
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1372
3105
  var extensionOf = (holder, key) => holder[key];
1373
3106
  var operationMetadata = (operation) => {
1374
3107
  const raw = extensionOf(operation, EXTENSION_KEYS.operation);
1375
- const ext = isRecord2(raw) ? raw : {};
3108
+ const ext = isRecord3(raw) ? raw : {};
1376
3109
  const supported = ext.supported ?? true;
1377
3110
  const parity = ext.parity ?? {};
1378
3111
  return {
@@ -1482,9 +3215,9 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
1482
3215
  this.name = "OperationRegistryError";
1483
3216
  }
1484
3217
  };
1485
- var verifyOperations = (document2, handlers) => {
3218
+ var verifyOperations = (document3, handlers) => {
1486
3219
  const problems = [];
1487
- const operations = listOperations(document2);
3220
+ const operations = listOperations(document3);
1488
3221
  const seen = /* @__PURE__ */ new Set();
1489
3222
  for (const operation of operations) {
1490
3223
  if (seen.has(operation.operationId))
@@ -1593,8 +3326,442 @@ var restoreNamespace = (sqlite, namespace, snapshot) => {
1593
3326
  });
1594
3327
  };
1595
3328
 
3329
+ // ../core/dist/state-view.js
3330
+ var COLLECTION_NAME = /^[A-Za-z0-9_.@:-]{1,200}$/;
3331
+ var RECORD_ID = /^[^\s/]{1,256}$/;
3332
+ var SAMPLE = 25;
3333
+ var WALK_LIMIT = 1e4;
3334
+ var SKIP_CTORS = /* @__PURE__ */ new Set(["Hono", "HonoRequest", "Router"]);
3335
+ var fail2 = (status, message) => ({ ok: false, status, message });
3336
+ var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3337
+ var kindOf = (value) => {
3338
+ if (value === null)
3339
+ return "null";
3340
+ if (Array.isArray(value))
3341
+ return "array";
3342
+ switch (typeof value) {
3343
+ case "string":
3344
+ return "string";
3345
+ case "number":
3346
+ return Number.isFinite(value) ? "number" : "unknown";
3347
+ case "boolean":
3348
+ return "boolean";
3349
+ case "object":
3350
+ return "object";
3351
+ default:
3352
+ return "unknown";
3353
+ }
3354
+ };
3355
+ var humanize = (name) => {
3356
+ const words = name.replace(/[@:_-]+/g, " ").trim();
3357
+ if (words === "")
3358
+ return name;
3359
+ return words.charAt(0).toUpperCase() + words.slice(1);
3360
+ };
3361
+ var jsonValue = (value) => {
3362
+ if (value === void 0)
3363
+ return fail2(400, "value is required");
3364
+ try {
3365
+ return { ok: true, value: JSON.parse(JSON.stringify(value)) };
3366
+ } catch {
3367
+ return fail2(400, "value must be JSON");
3368
+ }
3369
+ };
3370
+ var ctorName = (value) => {
3371
+ const ctor = value.constructor;
3372
+ return typeof ctor?.name === "string" ? ctor.name : "";
3373
+ };
3374
+ var isSqlite = (value) => "prepare" in value && typeof value.prepare === "function" && "transaction" in value;
3375
+ var discoverCollections = (root) => {
3376
+ const found = [];
3377
+ const seen = /* @__PURE__ */ new Set();
3378
+ const visit = (value, depth) => {
3379
+ if (found.length + seen.size > WALK_LIMIT)
3380
+ return;
3381
+ if (value === null || typeof value !== "object")
3382
+ return;
3383
+ if (seen.has(value))
3384
+ return;
3385
+ seen.add(value);
3386
+ if (value instanceof Collection) {
3387
+ found.push(value);
3388
+ return;
3389
+ }
3390
+ if (value instanceof Map) {
3391
+ for (const child of value.values())
3392
+ visit(child, depth + 1);
3393
+ return;
3394
+ }
3395
+ if (value instanceof Set) {
3396
+ for (const child of value.values())
3397
+ visit(child, depth + 1);
3398
+ return;
3399
+ }
3400
+ if (depth > 6 || isSqlite(value) || SKIP_CTORS.has(ctorName(value)))
3401
+ return;
3402
+ if (Array.isArray(value)) {
3403
+ const cap = Math.min(value.length, 32);
3404
+ for (let i = 0; i < cap; i++)
3405
+ visit(value[i], depth + 1);
3406
+ return;
3407
+ }
3408
+ let children;
3409
+ try {
3410
+ children = Object.values(value);
3411
+ } catch {
3412
+ return;
3413
+ }
3414
+ for (const child of children)
3415
+ visit(child, depth + 1);
3416
+ };
3417
+ visit(root, 0);
3418
+ return found;
3419
+ };
3420
+ var storedNames = (sqlite, namespace) => {
3421
+ const records = sqlite.prepare("SELECT DISTINCT collection AS name FROM mockingbird_records WHERE namespace = ?").all(namespace);
3422
+ const sequences = sqlite.prepare("SELECT DISTINCT name FROM mockingbird_sequences WHERE namespace = ? AND kind = 'collection'").all(namespace);
3423
+ return [...records, ...sequences].map((row) => row.name);
3424
+ };
3425
+ var blankAccum = (name) => ({
3426
+ name,
3427
+ kinds: /* @__PURE__ */ new Set(),
3428
+ present: 0,
3429
+ nested: /* @__PURE__ */ new Map()
3430
+ });
3431
+ var absorb = (accum, value) => {
3432
+ const kind = kindOf(value);
3433
+ accum.kinds.add(kind);
3434
+ accum.present++;
3435
+ if (!isRecord4(value))
3436
+ return;
3437
+ for (const [key, child] of Object.entries(value)) {
3438
+ const nested = accum.nested.get(key) ?? blankAccum(key);
3439
+ absorb(nested, child);
3440
+ accum.nested.set(key, nested);
3441
+ }
3442
+ };
3443
+ var finishField = (accum, samples) => {
3444
+ const kinds = [...accum.kinds];
3445
+ const kind = kinds.length === 1 ? kinds[0] : "unknown";
3446
+ const field = {
3447
+ name: accum.name,
3448
+ kind,
3449
+ optional: accum.present < samples || kinds.includes("null")
3450
+ };
3451
+ if (accum.nested.size > 0) {
3452
+ field.fields = [...accum.nested.values()].sort((a, b) => a.name.localeCompare(b.name)).map((child) => finishField(child, accum.present));
3453
+ }
3454
+ return field;
3455
+ };
3456
+ var inferFields = (records) => {
3457
+ if (records.length === 0)
3458
+ return [];
3459
+ const sample = records.slice(0, SAMPLE);
3460
+ const objects = sample.filter((record) => isRecord4(record.value));
3461
+ if (objects.length === 0) {
3462
+ const kinds = new Set(sample.map((record) => kindOf(record.value)));
3463
+ const list = [...kinds];
3464
+ return [
3465
+ {
3466
+ name: "(value)",
3467
+ kind: list.length === 1 ? list[0] : "unknown",
3468
+ optional: false
3469
+ }
3470
+ ];
3471
+ }
3472
+ const root = /* @__PURE__ */ new Map();
3473
+ for (const record of objects) {
3474
+ for (const [key, value] of Object.entries(record.value)) {
3475
+ const accum = root.get(key) ?? blankAccum(key);
3476
+ absorb(accum, value);
3477
+ root.set(key, accum);
3478
+ }
3479
+ }
3480
+ return [...root.values()].sort((a, b) => a.name.localeCompare(b.name)).map((accum) => finishField(accum, objects.length));
3481
+ };
3482
+ var mergeFields = (declared, inferred) => {
3483
+ const byName = /* @__PURE__ */ new Map();
3484
+ for (const field of inferred)
3485
+ byName.set(field.name, field);
3486
+ const ordered = [];
3487
+ const seen = /* @__PURE__ */ new Set();
3488
+ for (const field of declared ?? []) {
3489
+ const found = byName.get(field.name);
3490
+ seen.add(field.name);
3491
+ if (!found) {
3492
+ ordered.push({ ...field, optional: field.optional });
3493
+ continue;
3494
+ }
3495
+ ordered.push({
3496
+ ...found,
3497
+ kind: field.kind === "unknown" ? found.kind : field.kind,
3498
+ optional: field.optional || found.optional,
3499
+ ...field.description !== void 0 ? { description: field.description } : {},
3500
+ ...field.meta !== void 0 ? { meta: field.meta } : {},
3501
+ ...field.fields !== void 0 ? { fields: mergeFields(field.fields, found.fields ?? []) } : {}
3502
+ });
3503
+ }
3504
+ for (const field of inferred) {
3505
+ if (!seen.has(field.name))
3506
+ ordered.push(field);
3507
+ }
3508
+ return ordered;
3509
+ };
3510
+ var collectionOf = (sqlite, namespace, name) => new Collection(sqlite, namespace, name);
3511
+ var readRecord = (collection, id) => {
3512
+ const rows = collection.list({ where: (_value, _seq) => true, order: "oldest" });
3513
+ const row = rows.find((entry) => entry.id === id);
3514
+ return row ? { id: row.id, seq: row.seq, value: row.value } : void 0;
3515
+ };
3516
+ var inspectState = (scope) => {
3517
+ const declared = /* @__PURE__ */ new Map();
3518
+ for (const declaration of scope.declarations ?? []) {
3519
+ if (COLLECTION_NAME.test(declaration.name))
3520
+ declared.set(declaration.name, declaration);
3521
+ }
3522
+ const names = new Set(declared.keys());
3523
+ for (const name of storedNames(scope.sqlite, scope.storageNamespace))
3524
+ names.add(name);
3525
+ for (const collection of discoverCollections(scope.root)) {
3526
+ if (collection.namespace === scope.storageNamespace)
3527
+ names.add(collection.collectionName);
3528
+ }
3529
+ const collections = [];
3530
+ for (const name of names) {
3531
+ if (!COLLECTION_NAME.test(name))
3532
+ continue;
3533
+ const declaration = declared.get(name);
3534
+ const collection = collectionOf(scope.sqlite, scope.storageNamespace, name);
3535
+ const records = collection.list({ order: "newest" }).slice(0, SAMPLE).map((row) => ({ id: row.id, seq: row.seq, value: row.value }));
3536
+ const inferred = inferFields(records);
3537
+ const fields = mergeFields(declaration?.fields, inferred);
3538
+ const source = declaration && records.length > 0 ? "mixed" : declaration ? "declared" : "inferred";
3539
+ collections.push({
3540
+ name,
3541
+ label: declaration?.label ?? humanize(name),
3542
+ ...declaration?.description !== void 0 ? { description: declaration.description } : {},
3543
+ count: collection.count(),
3544
+ source,
3545
+ fields,
3546
+ ...declaration?.meta !== void 0 ? { meta: declaration.meta } : {}
3547
+ });
3548
+ }
3549
+ const order = /* @__PURE__ */ new Map();
3550
+ for (const [index, item] of (scope.declarations ?? []).entries())
3551
+ order.set(item.name, index);
3552
+ collections.sort((a, b) => {
3553
+ const left = order.get(a.name);
3554
+ const right = order.get(b.name);
3555
+ if (left !== void 0 && right !== void 0)
3556
+ return left - right;
3557
+ if (left !== void 0)
3558
+ return -1;
3559
+ if (right !== void 0)
3560
+ return 1;
3561
+ return a.name.localeCompare(b.name);
3562
+ });
3563
+ return {
3564
+ namespace: scope.namespace,
3565
+ storageNamespace: scope.storageNamespace,
3566
+ collections
3567
+ };
3568
+ };
3569
+ var pageOf = (sqlite, storageNamespace, name, query) => {
3570
+ if (!COLLECTION_NAME.test(name))
3571
+ return fail2(400, "collection name is not allowed");
3572
+ const limit = query.limit ?? 50;
3573
+ if (!Number.isInteger(limit) || limit < 1 || limit > 200) {
3574
+ return fail2(400, "limit must be an integer from 1 to 200");
3575
+ }
3576
+ const order = query.order ?? "newest";
3577
+ if (order !== "newest" && order !== "oldest")
3578
+ return fail2(400, 'order must be "newest" or "oldest"');
3579
+ const collection = collectionOf(sqlite, storageNamespace, name);
3580
+ const rows = collection.list({ order }).filter((row) => {
3581
+ if (query.after === void 0)
3582
+ return true;
3583
+ return order === "newest" ? row.seq < query.after : row.seq > query.after;
3584
+ });
3585
+ const slice = rows.slice(0, limit);
3586
+ const last = slice.at(-1);
3587
+ return {
3588
+ ok: true,
3589
+ value: {
3590
+ collection: name,
3591
+ count: collection.count(),
3592
+ records: slice.map((row) => ({ id: row.id, seq: row.seq, value: row.value })),
3593
+ next: rows.length > limit && last ? last.seq : null
3594
+ }
3595
+ };
3596
+ };
3597
+ var readStatePage = (scope, name, query = {}) => pageOf(scope.sqlite, scope.storageNamespace, name, query);
3598
+ var readStateRecord = (scope, name, id) => {
3599
+ if (!COLLECTION_NAME.test(name))
3600
+ return fail2(400, "collection name is not allowed");
3601
+ if (!RECORD_ID.test(id))
3602
+ return fail2(400, "record id is not allowed");
3603
+ const record = readRecord(collectionOf(scope.sqlite, scope.storageNamespace, name), id);
3604
+ return record ? { ok: true, value: record } : fail2(404, `no record ${id} in ${name}`);
3605
+ };
3606
+ var requireId = (id) => RECORD_ID.test(id) ? { ok: true, value: id } : fail2(400, "id must be a single path segment");
3607
+ var createStateRecord = (scope, name, input) => {
3608
+ if (!COLLECTION_NAME.test(name))
3609
+ return fail2(400, "collection name is not allowed");
3610
+ const parsed = jsonValue(input.value);
3611
+ if (!parsed.ok)
3612
+ return parsed;
3613
+ const collection = collectionOf(scope.sqlite, scope.storageNamespace, name);
3614
+ const id = input.id ?? `${name}_${collection.nextSequence()}`;
3615
+ const checked = requireId(id);
3616
+ if (!checked.ok)
3617
+ return checked;
3618
+ if (collection.has(checked.value))
3619
+ return fail2(409, `record ${checked.value} already exists`);
3620
+ const stored = collection.insert(checked.value, parsed.value);
3621
+ return { ok: true, value: { id: checked.value, seq: stored.seq, value: stored.value } };
3622
+ };
3623
+ var replaceStateRecord = (scope, name, id, value) => {
3624
+ if (!COLLECTION_NAME.test(name))
3625
+ return fail2(400, "collection name is not allowed");
3626
+ const checked = requireId(id);
3627
+ if (!checked.ok)
3628
+ return checked;
3629
+ const parsed = jsonValue(value);
3630
+ if (!parsed.ok)
3631
+ return parsed;
3632
+ const collection = collectionOf(scope.sqlite, scope.storageNamespace, name);
3633
+ if (!collection.has(checked.value))
3634
+ return fail2(404, `no record ${checked.value} in ${name}`);
3635
+ const stored = collection.update(checked.value, parsed.value);
3636
+ if (!stored)
3637
+ return fail2(404, `no record ${checked.value} in ${name}`);
3638
+ return { ok: true, value: { id: checked.value, seq: stored.seq, value: stored.value } };
3639
+ };
3640
+ var patchStateRecord = (scope, name, id, value) => {
3641
+ const current = readStateRecord(scope, name, id);
3642
+ if (!current.ok)
3643
+ return current;
3644
+ const parsed = jsonValue(value);
3645
+ if (!parsed.ok)
3646
+ return parsed;
3647
+ const next = isRecord4(current.value.value) && isRecord4(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
3648
+ return replaceStateRecord(scope, name, id, next);
3649
+ };
3650
+ var deleteStateRecord = (scope, name, id) => {
3651
+ if (!COLLECTION_NAME.test(name))
3652
+ return fail2(400, "collection name is not allowed");
3653
+ const checked = requireId(id);
3654
+ if (!checked.ok)
3655
+ return checked;
3656
+ const collection = collectionOf(scope.sqlite, scope.storageNamespace, name);
3657
+ return collection.delete(checked.value) ? { ok: true, value: { id: checked.value } } : fail2(404, `no record ${checked.value} in ${name}`);
3658
+ };
3659
+ var json3 = (status, body) => new Response(JSON.stringify(body), {
3660
+ status,
3661
+ headers: { "content-type": "application/json; charset=utf-8" }
3662
+ });
3663
+ var adminError2 = (status, message) => json3(status, { error: { type: "mockingbird_admin", message } });
3664
+ var fromResult = (result, status) => result.ok ? json3(status, result.value) : adminError2(result.status, result.message);
3665
+ var integerParam = (value) => {
3666
+ if (value === null)
3667
+ return void 0;
3668
+ if (!/^-?\d+$/.test(value))
3669
+ return "bad";
3670
+ return Number(value);
3671
+ };
3672
+ var stateAdminRoutes = (routes) => {
3673
+ const open = (namespace) => {
3674
+ try {
3675
+ return { ok: true, value: routes.open(namespace) };
3676
+ } catch (error) {
3677
+ return fail2(400, error instanceof Error ? error.message : String(error));
3678
+ }
3679
+ };
3680
+ const wrote = (namespace) => {
3681
+ routes.afterWrite?.(namespace);
3682
+ };
3683
+ return {
3684
+ "GET /state": ({ namespace }) => {
3685
+ const scope = open(namespace);
3686
+ return fromResult(scope.ok ? { ok: true, value: inspectState(scope.value) } : scope, 200);
3687
+ },
3688
+ "GET /state/:collection": ({ namespace, params, url }) => {
3689
+ const scope = open(namespace);
3690
+ if (!scope.ok)
3691
+ return fromResult(scope, 200);
3692
+ const limit = integerParam(url.searchParams.get("limit"));
3693
+ const after = integerParam(url.searchParams.get("after"));
3694
+ if (limit === "bad")
3695
+ return adminError2(400, "limit: expected an integer");
3696
+ if (after === "bad")
3697
+ return adminError2(400, "after: expected a sequence number");
3698
+ const order = url.searchParams.get("order");
3699
+ return fromResult(readStatePage(scope.value, params.collection ?? "", {
3700
+ ...limit !== void 0 ? { limit } : {},
3701
+ ...after !== void 0 ? { after } : {},
3702
+ ...order === "newest" || order === "oldest" ? { order } : {}
3703
+ }), 200);
3704
+ },
3705
+ "GET /state/:collection/:id": ({ namespace, params }) => {
3706
+ const scope = open(namespace);
3707
+ if (!scope.ok)
3708
+ return fromResult(scope, 200);
3709
+ return fromResult(readStateRecord(scope.value, params.collection ?? "", params.id ?? ""), 200);
3710
+ },
3711
+ "POST /state/:collection": ({ namespace, params, body }) => {
3712
+ const scope = open(namespace);
3713
+ if (!scope.ok)
3714
+ return fromResult(scope, 201);
3715
+ if (!isRecord4(body) || !("value" in body)) {
3716
+ return adminError2(400, 'expected {"id?": "<id>", "value": <json>}');
3717
+ }
3718
+ const id = body.id;
3719
+ if (id !== void 0 && typeof id !== "string")
3720
+ return adminError2(400, "id must be a string");
3721
+ const created = createStateRecord(scope.value, params.collection ?? "", {
3722
+ ...typeof id === "string" ? { id } : {},
3723
+ value: body.value
3724
+ });
3725
+ if (created.ok)
3726
+ wrote(namespace);
3727
+ return fromResult(created, 201);
3728
+ },
3729
+ "PUT /state/:collection/:id": ({ namespace, params, body }) => {
3730
+ const scope = open(namespace);
3731
+ if (!scope.ok)
3732
+ return fromResult(scope, 200);
3733
+ if (!isRecord4(body) || !("value" in body))
3734
+ return adminError2(400, 'expected {"value": <json>}');
3735
+ const replaced = replaceStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
3736
+ if (replaced.ok)
3737
+ wrote(namespace);
3738
+ return fromResult(replaced, 200);
3739
+ },
3740
+ "PATCH /state/:collection/:id": ({ namespace, params, body }) => {
3741
+ const scope = open(namespace);
3742
+ if (!scope.ok)
3743
+ return fromResult(scope, 200);
3744
+ if (!isRecord4(body) || !("value" in body))
3745
+ return adminError2(400, 'expected {"value": <json>}');
3746
+ const patched = patchStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
3747
+ if (patched.ok)
3748
+ wrote(namespace);
3749
+ return fromResult(patched, 200);
3750
+ },
3751
+ "DELETE /state/:collection/:id": ({ namespace, params }) => {
3752
+ const scope = open(namespace);
3753
+ if (!scope.ok)
3754
+ return fromResult(scope, 200);
3755
+ const removed = deleteStateRecord(scope.value, params.collection ?? "", params.id ?? "");
3756
+ if (removed.ok)
3757
+ wrote(namespace);
3758
+ return fromResult(removed, 200);
3759
+ }
3760
+ };
3761
+ };
3762
+
1596
3763
  // ../core/dist/version.js
1597
- var PACKAGE_VERSION = true ? "1.3.0" : UNRELEASED_VERSION;
3764
+ var PACKAGE_VERSION = true ? "1.5.0" : UNRELEASED_VERSION;
1598
3765
 
1599
3766
  // ../core/dist/signing.js
1600
3767
  var encoder = new TextEncoder();
@@ -1929,11 +4096,11 @@ var createWebhookHub = (options) => {
1929
4096
  };
1930
4097
  return hub;
1931
4098
  };
1932
- var json2 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
1933
- var adminError2 = (status, message) => json2(status, { error: { type: "mockingbird_admin", message } });
1934
- var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4099
+ var json4 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
4100
+ var adminError3 = (status, message) => json4(status, { error: { type: "mockingbird_admin", message } });
4101
+ var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1935
4102
  var parseEndpoint = (value) => {
1936
- if (!isRecord3(value) || typeof value.url !== "string")
4103
+ if (!isRecord5(value) || typeof value.url !== "string")
1937
4104
  return "each endpoint needs a url";
1938
4105
  try {
1939
4106
  new URL(value.url);
@@ -1950,18 +4117,18 @@ var parseEndpoint = (value) => {
1950
4117
  const events = value.events ?? value.enabledEvents;
1951
4118
  if (Array.isArray(events))
1952
4119
  endpoint.events = events.map(String);
1953
- if (isRecord3(value.tags)) {
4120
+ if (isRecord5(value.tags)) {
1954
4121
  endpoint.tags = Object.fromEntries(Object.entries(value.tags).map(([k, v]) => [k, String(v)]));
1955
4122
  }
1956
4123
  if (typeof value.account === "string")
1957
4124
  endpoint.tags = { ...endpoint.tags, account: value.account };
1958
- if (isRecord3(value.headers)) {
4125
+ if (isRecord5(value.headers)) {
1959
4126
  endpoint.headers = Object.fromEntries(Object.entries(value.headers).map(([k, v]) => [k, String(v)]));
1960
4127
  }
1961
4128
  return endpoint;
1962
4129
  };
1963
4130
  var webhookAdminRoutes = (hub) => ({
1964
- "GET /webhooks": ({ url, namespace }) => json2(200, {
4131
+ "GET /webhooks": ({ url, namespace }) => json4(200, {
1965
4132
  deliveries: hub.deliveries(url.searchParams.get("all") === "1" ? void 0 : namespace).filter((d) => {
1966
4133
  const type = url.searchParams.get("type");
1967
4134
  return type === null || d.type === type;
@@ -1969,51 +4136,51 @@ var webhookAdminRoutes = (hub) => ({
1969
4136
  }),
1970
4137
  "GET /webhooks/events": ({ url, namespace }) => {
1971
4138
  const type = url.searchParams.get("type");
1972
- return json2(200, {
4139
+ return json4(200, {
1973
4140
  events: hub.messages(url.searchParams.get("all") === "1" ? void 0 : namespace).filter((m) => type === null || m.type === type).map((m) => ({ ...m, payload: parsePayload(m) }))
1974
4141
  });
1975
4142
  },
1976
4143
  "POST /webhooks/:id/replay": async ({ params }) => {
1977
4144
  const replayed = await hub.replay(params.id);
1978
- return replayed ? json2(200, replayed) : adminError2(404, `no delivery ${params.id}`);
4145
+ return replayed ? json4(200, replayed) : adminError3(404, `no delivery ${params.id}`);
1979
4146
  },
1980
4147
  "POST /webhooks/flush": async () => {
1981
4148
  await hub.flush();
1982
- return json2(200, { status: "ok" });
4149
+ return json4(200, { status: "ok" });
1983
4150
  },
1984
4151
  "POST /webhooks/faults": ({ body, namespace }) => {
1985
- if (!isRecord3(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
1986
- return adminError2(400, "mode must be duplicate, reorder or drop");
4152
+ if (!isRecord5(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
4153
+ return adminError3(400, "mode must be duplicate, reorder or drop");
1987
4154
  }
1988
4155
  const fault = { mode: body.mode };
1989
4156
  if (typeof body.count === "number")
1990
4157
  fault.count = body.count;
1991
4158
  hub.fault(namespace, fault);
1992
- return json2(201, { namespace, ...fault });
4159
+ return json4(201, { namespace, ...fault });
1993
4160
  },
1994
- "GET /webhook-endpoints": ({ namespace }) => json2(200, {
4161
+ "GET /webhook-endpoints": ({ namespace }) => json4(200, {
1995
4162
  endpoints: hub.endpoints(namespace).map(({ secret, ...rest }) => ({
1996
4163
  ...rest,
1997
4164
  secret: secret ? "(set)" : null
1998
4165
  }))
1999
4166
  }),
2000
4167
  "PUT /webhook-endpoints": ({ body, namespace }) => {
2001
- const list = Array.isArray(body) ? body : isRecord3(body) ? body.endpoints : void 0;
4168
+ const list = Array.isArray(body) ? body : isRecord5(body) ? body.endpoints : void 0;
2002
4169
  if (!Array.isArray(list))
2003
- return adminError2(400, "expected [{url, secret?, events?}]");
4170
+ return adminError3(400, "expected [{url, secret?, events?}]");
2004
4171
  const parsed = [];
2005
4172
  for (const each of list) {
2006
4173
  const endpoint = parseEndpoint(each);
2007
4174
  if (typeof endpoint === "string")
2008
- return adminError2(400, endpoint);
4175
+ return adminError3(400, endpoint);
2009
4176
  parsed.push(endpoint);
2010
4177
  }
2011
4178
  const set = hub.setEndpoints(namespace, parsed);
2012
- return json2(200, { endpoints: set.map((e) => ({ ...e, secret: e.secret ? "(set)" : null })) });
4179
+ return json4(200, { endpoints: set.map((e) => ({ ...e, secret: e.secret ? "(set)" : null })) });
2013
4180
  },
2014
4181
  "DELETE /webhook-endpoints": ({ namespace }) => {
2015
4182
  hub.setEndpoints(namespace, []);
2016
- return json2(200, { status: "ok" });
4183
+ return json4(200, { status: "ok" });
2017
4184
  }
2018
4185
  });
2019
4186
  var parsePayload = (message) => {
@@ -2068,8 +4235,8 @@ var DroppedConnectionError = class extends TypeError {
2068
4235
  this.name = "TypeError";
2069
4236
  }
2070
4237
  };
2071
- var operationMatcher = (document2) => {
2072
- const matchers = listOperations(document2).map((operation) => ({
4238
+ var operationMatcher = (document3) => {
4239
+ const matchers = listOperations(document3).map((operation) => ({
2073
4240
  operationId: operation.operationId,
2074
4241
  method: operation.method.toUpperCase(),
2075
4242
  pattern: new RegExp(`^${operation.path.split("/").map((segment) => segment.startsWith("{") ? "[^/]+" : segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("/")}/?$`),
@@ -2078,6 +4245,7 @@ var operationMatcher = (document2) => {
2078
4245
  return (request, path) => matchers.find((m) => m.method === request.method && m.pattern.test(path))?.operationId;
2079
4246
  };
2080
4247
  var createRuntime = (options) => {
4248
+ assertAdminUi(options.adminUi);
2081
4249
  const sqlite = bootSqlite(options.sqlite);
2082
4250
  const clock = options.clock ?? createClock();
2083
4251
  const rng = createRng(options.seed ?? 0);
@@ -2163,10 +4331,10 @@ var createRuntime = (options) => {
2163
4331
  const ensureBranch = (namespace, branch2, at) => {
2164
4332
  if (!BRANCH_PATTERN2.test(branch2))
2165
4333
  throw new RangeError(`branch must match ${BRANCH_PATTERN2}`);
2166
- const history = timeline(namespace);
4334
+ const history2 = timeline(namespace);
2167
4335
  if (branch2 === "main") {
2168
4336
  if (at !== void 0) {
2169
- const point = history.checkout("main", at);
4337
+ const point = history2.checkout("main", at);
2170
4338
  restoreNamespace(sqlite, storageNamespace(namespace), point.value.snapshot);
2171
4339
  captured.set(namespace, point.value.snapshot);
2172
4340
  rng.setState(point.value.rngState);
@@ -2179,10 +4347,10 @@ var createRuntime = (options) => {
2179
4347
  return namespace;
2180
4348
  }
2181
4349
  const storage = physicalBranch(namespace, branch2);
2182
- if (!history.hasBranch(branch2)) {
4350
+ if (!history2.hasBranch(branch2)) {
2183
4351
  if (at === void 0)
2184
- history.commit(capture(namespace));
2185
- const point = history.fork(branch2, at === void 0 ? {} : { from: at });
4352
+ history2.commit(capture(namespace));
4353
+ const point = history2.fork(branch2, at === void 0 ? {} : { from: at });
2186
4354
  const branchRng = createRng(options.seed ?? 0);
2187
4355
  if (point)
2188
4356
  branchRng.setState(point.value.rngState);
@@ -2191,8 +4359,8 @@ var createRuntime = (options) => {
2191
4359
  restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
2192
4360
  if (point)
2193
4361
  captured.set(storage, point.value.snapshot);
2194
- } else if (at !== void 0 && history.head(branch2)?.id !== at) {
2195
- const point = history.checkout(branch2, at);
4362
+ } else if (at !== void 0 && history2.head(branch2)?.id !== at) {
4363
+ const point = history2.checkout(branch2, at);
2196
4364
  if (!instances.has(storage)) {
2197
4365
  const branchRng = createRng(options.seed ?? 0);
2198
4366
  branchRng.setState(point.value.rngState);
@@ -2203,7 +4371,7 @@ var createRuntime = (options) => {
2203
4371
  captured.set(storage, point.value.snapshot);
2204
4372
  } else {
2205
4373
  if (!instances.has(storage)) {
2206
- const point = history.head(branch2);
4374
+ const point = history2.head(branch2);
2207
4375
  const branchRng = createRng(options.seed ?? 0);
2208
4376
  if (point)
2209
4377
  branchRng.setState(point.value.rngState);
@@ -2227,8 +4395,8 @@ var createRuntime = (options) => {
2227
4395
  const checkout = (checkpointId, checkoutOptions = {}) => {
2228
4396
  const namespace = checkoutOptions.namespace ?? DEFAULT_NAMESPACE;
2229
4397
  const branchName = checkoutOptions.branch ?? "main";
2230
- const history = timeline(namespace);
2231
- const point = history.checkout(branchName, checkpointId);
4398
+ const history2 = timeline(namespace);
4399
+ const point = history2.checkout(branchName, checkpointId);
2232
4400
  const storage = ensureBranch(namespace, branchName);
2233
4401
  restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
2234
4402
  captured.set(storage, point.value.snapshot);
@@ -2278,12 +4446,25 @@ var createRuntime = (options) => {
2278
4446
  instance(name);
2279
4447
  restoreNamespace(sqlite, storageNamespace(name), from);
2280
4448
  captured.set(name, from);
2281
- const history = timelines.get(name);
2282
- if (history)
2283
- history.commit(capture(name), { branch: "main" });
4449
+ const history2 = timelines.get(name);
4450
+ if (history2)
4451
+ history2.commit(capture(name), { branch: "main" });
2284
4452
  else
2285
4453
  timeline(name);
2286
4454
  };
4455
+ const declarationsFor = (namespace) => {
4456
+ const declared = options.state;
4457
+ if (!declared)
4458
+ return [];
4459
+ return typeof declared === "function" ? declared(instance(namespace), namespace) : declared;
4460
+ };
4461
+ const stateScope = (namespace) => ({
4462
+ sqlite,
4463
+ namespace,
4464
+ storageNamespace: storageNamespace(namespace),
4465
+ root: instance(namespace),
4466
+ declarations: declarationsFor(namespace)
4467
+ });
2287
4468
  const runtime = {
2288
4469
  name: options.name,
2289
4470
  sqlite,
@@ -2322,6 +4503,7 @@ var createRuntime = (options) => {
2322
4503
  branch,
2323
4504
  checkout,
2324
4505
  timeline,
4506
+ state: (namespace = DEFAULT_NAMESPACE) => inspectState(stateScope(namespace)),
2325
4507
  fetch: async (incoming) => {
2326
4508
  let request = incoming;
2327
4509
  const prefixed = PATH_PREFIX.exec(new URL(request.url).pathname);
@@ -2495,10 +4677,10 @@ var createRuntime = (options) => {
2495
4677
  },
2496
4678
  release: (name, checkpointId) => timeline(name).release(checkpointId),
2497
4679
  inspect: (name) => {
2498
- const history = timeline(name);
4680
+ const history2 = timeline(name);
2499
4681
  return {
2500
- branches: history.branches(),
2501
- checkpoints: history.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
4682
+ branches: history2.branches(),
4683
+ checkpoints: history2.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
2502
4684
  id,
2503
4685
  branch: branchName,
2504
4686
  parent,
@@ -2513,13 +4695,22 @@ var createRuntime = (options) => {
2513
4695
  } : {},
2514
4696
  routes: {
2515
4697
  ...credentialRoutes(credentials),
2516
- ...options.presets ? presetRoutes(options.presets, runtime) : {},
2517
4698
  ...options.webhooks ? webhookAdminRoutes(options.webhooks) : {},
2518
- ...options.admin?.(runtime) ?? {}
4699
+ ...options.admin?.(runtime) ?? {},
4700
+ // Shell routes win a colliding key. Fault presets are one of them: the admin page
4701
+ // maps `presets`, and a service route must not replace that list with a record.
4702
+ ...options.presets ? presetRoutes(options.presets, runtime) : {},
4703
+ ...stateAdminRoutes({
4704
+ open: stateScope,
4705
+ afterWrite: (namespace) => {
4706
+ checkpoint(namespace, "main");
4707
+ }
4708
+ }),
4709
+ ...adminUiRoutes(options.name, options.adminUi)
2519
4710
  },
2520
4711
  adminKey: options.adminKey
2521
4712
  });
2522
- return runtime;
4713
+ return defineMock(runtime);
2523
4714
  };
2524
4715
  var mutableResponse = (response) => {
2525
4716
  try {
@@ -2583,9 +4774,12 @@ var credentialRoutes = (registry) => ({
2583
4774
  }
2584
4775
  });
2585
4776
  var presetRoutes = (presets, runtime) => ({
2586
- "GET /faults/presets": () => adminJson(200, {
2587
- presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
2588
- }),
4777
+ "GET /faults/presets": () => {
4778
+ const body = {
4779
+ presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
4780
+ };
4781
+ return adminJson(200, body);
4782
+ },
2589
4783
  "POST /faults/presets/:name": ({ params, body, namespace }) => {
2590
4784
  const name = params.name;
2591
4785
  if (!presets[name])
@@ -2596,7 +4790,7 @@ var presetRoutes = (presets, runtime) => ({
2596
4790
  });
2597
4791
 
2598
4792
  // src/generated/openapi.ts
2599
- var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Fullscript API (Mockingbird subset)","description":"Stateful mock subset of the Fullscript API for lab ordering: per-practitioner OAuth (authorize, token with authorization_code and refresh_token grants, revoke), clinic, embeddable session grants, lab orders, lab-order events, expiring result PDFs, and signed webhooks. Hand-authored from the EMR consumer (fullscript-api-client.ts, fullscript-event-model.ts, fullscript-webhook-signature.ts).\\n","version":"1","x-mockingbird-upstream":{"note":"Wire shapes from the consumer app's EMR backend (its Fullscript services and routers); the vendor's API reference is behind a partner login."}},"servers":[{"url":"https://api-us-snd.fullscript.io"}],"security":[{"bearerAuth":[]}],"paths":{"/api/oauth/token":{"post":{"operationId":"OAuthToken","description":"Exchange an authorization code, or rotate a refresh token (the old refresh token stops working).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthTokenResponse"}}}},"400":{"description":"invalid_grant / unsupported_grant_type / invalid_request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"401":{"description":"invalid_client","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenRequest"}}}}}},"/api/oauth/revoke":{"post":{"operationId":"OAuthRevoke","description":"Revoke an access or refresh token (RFC 7009: always 200).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"200":{"description":"Revoked","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"invalid_request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"401":{"description":"invalid_client","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevokeRequest"}}}}}},"/api/clinic":{"get":{"operationId":"GetClinic","description":"The token's clinic.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Clinic","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClinicResponse"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}}}},"/api/clinic/embeddable/session_grants":{"post":{"operationId":"CreateSessionGrant","description":"A short-lived secret for the embedded Fullscript widget.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"201":{"description":"Grant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionGrant"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}}}},"/api/clinic/labs/orders":{"get":{"operationId":"ListLabOrders","description":"The clinic's lab orders (optionally one patient's), paged.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LabOrdersPage"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"parameters":[{"name":"patient_id","in":"query","required":false,"schema":{"type":"string","enum":["pat_seed_1","pat_seed_2","pat_missing"]}},{"name":"page[number]","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":3}},{"name":"page[size]","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}}]}},"/api/clinic/labs/orders/{orderId}":{"get":{"operationId":"GetLabOrder","description":"One lab order with tests, per-test results and the aggregated result.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LabOrderResponse"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"404":{"description":"Unknown order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"parameters":[{"name":"orderId","in":"path","required":true,"schema":{"type":"string","enum":["lo_seed_1","lo_seed_2","lo_seed_3","lo_missing"]}}]}},"/api/events/lab_orders":{"get":{"operationId":"ListLabOrderEvents","description":"The clinic's lab_order.updated events, newest first by default.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventsPage"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"parameters":[{"name":"order_by","in":"query","required":false,"schema":{"type":"string","enum":["DESC","ASC"]}},{"name":"page[number]","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":3}},{"name":"page[size]","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}}]}},"/api/events/{eventId}":{"get":{"operationId":"GetEvent","description":"One event with its data.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Event","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventResponse"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"404":{"description":"Unknown event","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"parameters":[{"name":"eventId","in":"path","required":true,"schema":{"type":"string","enum":["evt_1","evt_2","evt_3","evt_missing"]}}]}},"/oauth/authorize":{"get":{"operationId":"Authorize","description":"The consent page: auto-approves as the requested (or default) practitioner and redirects to redirect_uri with code and state.","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":false,"reason":"Browser redirect flow; covered by acceptance tests."}},"parameters":[{"name":"client_id","in":"query","required":true,"schema":{"type":"string"}},{"name":"redirect_uri","in":"query","required":true,"schema":{"type":"string"}},{"name":"response_type","in":"query","required":true,"schema":{"type":"string","enum":["code"]}},{"name":"state","in":"query","required":false,"schema":{"type":"string"}},{"name":"scope","in":"query","required":false,"schema":{"type":"string"}},{"name":"practitioner_id","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"302":{"description":"Redirect back with ?code=&state="},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}}}}},"/results/{artifactId}":{"get":{"operationId":"GetResultPdf","description":"An expiring result PDF URL (what pdf_url points at).","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":false,"reason":"Binary PDF behind an expiring signed URL; covered by acceptance tests."}},"parameters":[{"name":"artifactId","in":"path","required":true,"schema":{"type":"string"}},{"name":"expires","in":"query","required":true,"schema":{"type":"string"}},{"name":"signature","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The PDF","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"403":{"description":"Expired or bad signature"},"404":{"description":"Unknown artifact"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"TokenRequest":{"type":"object","required":["grant_type","client_id","client_secret"],"properties":{"grant_type":{"type":"string","enum":["authorization_code","refresh_token"]},"client_id":{"type":"string","minLength":1,"maxLength":64,"examples":["parity-client"]},"client_secret":{"type":"string","minLength":1,"maxLength":64,"examples":["parity-secret"]},"code":{"type":"string","maxLength":64},"refresh_token":{"type":"string","maxLength":200},"redirect_uri":{"type":"string","maxLength":200,"examples":["https://emr.example.com/v1/fullscript/oauth/callback"]}}},"RevokeRequest":{"type":"object","required":["client_id","client_secret","token"],"properties":{"client_id":{"type":"string","minLength":1,"maxLength":64},"client_secret":{"type":"string","minLength":1,"maxLength":64},"token":{"type":"string","minLength":1,"maxLength":200}}},"OAuthTokenResponse":{"type":"object","required":["oauth"],"properties":{"oauth":{"type":"object","required":["access_token","token_type","expires_in","refresh_token","scope","created_at","resource_owner"],"properties":{"access_token":{"type":"string","x-mockingbird-volatile":{"kind":"token"}},"token_type":{"type":"string","enum":["Bearer"]},"expires_in":{"type":"integer"},"refresh_token":{"type":"string","x-mockingbird-volatile":{"kind":"token"}},"scope":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"resource_owner":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string"},"type":{"type":"string","enum":["Practitioner","Staff"]},"clinic_id":{"type":"string"}}}}}}},"OAuthError":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"error_description":{"type":"string"}}},"ApiErrors":{"type":"object","required":["errors"],"properties":{"errors":{"type":"array","items":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}},"ClinicResponse":{"type":"object","required":["clinic"],"properties":{"clinic":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}}}},"SessionGrant":{"type":"object","required":["secret_token","expires_at"],"properties":{"secret_token":{"type":"string","x-mockingbird-volatile":{"kind":"token"}},"expires_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"LabOrderSummary":{"type":"object","required":["id","state","treatment_plan_id","patient_id","created_at","updated_at"],"properties":{"id":{"type":"string"},"state":{"type":"string","enum":["not_purchased","purchased","schedule_appointment","upcoming_appointment","processing","partial_results","results_ready","interpretation_shared","results_amended"]},"treatment_plan_id":{"type":["string","null"]},"patient_id":{"type":"string"},"name":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"LabOrdersPage":{"type":"object","required":["orders","meta"],"properties":{"orders":{"type":"array","items":{"$ref":"#/components/schemas/LabOrderSummary"}},"meta":{"type":"object","required":["current_page","next_page","prev_page","total_pages","total_count"],"properties":{"current_page":{"type":"integer"},"next_page":{"type":["integer","null"]},"prev_page":{"type":["integer","null"]},"total_pages":{"type":"integer"},"total_count":{"type":"integer"}}}}},"LabOrderResponse":{"type":"object","required":["order"],"properties":{"order":{"type":"object","required":["id","state","treatment_plan_id","tests","results","latest_aggregated_result"],"properties":{"id":{"type":"string"},"state":{"type":"string","enum":["not_purchased","purchased","schedule_appointment","upcoming_appointment","processing","partial_results","results_ready","interpretation_shared","results_amended"]},"treatment_plan_id":{"type":["string","null"]},"patient_id":{"type":"string"},"name":{"type":"string"},"collection_method":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"tests":{"type":"array","items":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"lab_type":{"type":"string"}}}},"results":{"type":"array","items":{"type":"object","required":["id","name","state","pdf_url"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"state":{"type":"string"},"pdf_url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}}}}},"latest_aggregated_result":{"type":["object","null"],"properties":{"id":{"type":"string"},"artifact_id":{"type":"string"},"pdf_url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}},"status":{"type":"string"}}}}}}},"EventSummary":{"type":"object","required":["id","type","clinic_id","created_at"],"properties":{"id":{"type":"string"},"type":{"type":"string","enum":["lab_order.updated"]},"clinic_id":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"EventsPage":{"type":"object","required":["events","meta"],"properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/EventSummary"}},"meta":{"type":"object","required":["current_page","next_page","prev_page","total_pages","total_count"],"properties":{"current_page":{"type":"integer"},"next_page":{"type":["integer","null"]},"prev_page":{"type":["integer","null"]},"total_pages":{"type":"integer"},"total_count":{"type":"integer"}}}}},"EventResponse":{"type":"object","required":["event"],"properties":{"event":{"type":"object","required":["id","type","clinic_id","created_at","data"],"properties":{"id":{"type":"string"},"type":{"type":"string"},"clinic_id":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"data":{"type":"object"}}}}}}}}`);
4793
+ var document2 = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Fullscript API (Mockingbird subset)","description":"Stateful mock subset of the Fullscript API for lab ordering: per-practitioner OAuth (authorize, token with authorization_code and refresh_token grants, revoke), clinic, embeddable session grants, lab orders, lab-order events, expiring result PDFs, and signed webhooks. Hand-authored from the EMR consumer (fullscript-api-client.ts, fullscript-event-model.ts, fullscript-webhook-signature.ts).\\n","version":"1","x-mockingbird-upstream":{"note":"Wire shapes from the consumer app's EMR backend (its Fullscript services and routers); the vendor's API reference is behind a partner login."}},"servers":[{"url":"https://api-us-snd.fullscript.io"}],"security":[{"bearerAuth":[]}],"paths":{"/api/oauth/token":{"post":{"operationId":"OAuthToken","description":"Exchange an authorization code, or rotate a refresh token (the old refresh token stops working).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthTokenResponse"}}}},"400":{"description":"invalid_grant / unsupported_grant_type / invalid_request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"401":{"description":"invalid_client","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenRequest"}}}}}},"/api/oauth/revoke":{"post":{"operationId":"OAuthRevoke","description":"Revoke an access or refresh token (RFC 7009: always 200).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"200":{"description":"Revoked","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"invalid_request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"401":{"description":"invalid_client","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevokeRequest"}}}}}},"/api/clinic":{"get":{"operationId":"GetClinic","description":"The token's clinic.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Clinic","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClinicResponse"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}}}},"/api/clinic/embeddable/session_grants":{"post":{"operationId":"CreateSessionGrant","description":"A short-lived secret for the embedded Fullscript widget.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"201":{"description":"Grant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionGrant"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}}}},"/api/clinic/labs/orders":{"get":{"operationId":"ListLabOrders","description":"The clinic's lab orders (optionally one patient's), paged.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LabOrdersPage"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"parameters":[{"name":"patient_id","in":"query","required":false,"schema":{"type":"string","enum":["pat_seed_1","pat_seed_2","pat_missing"]}},{"name":"page[number]","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":3}},{"name":"page[size]","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}}]}},"/api/clinic/labs/orders/{orderId}":{"get":{"operationId":"GetLabOrder","description":"One lab order with tests, per-test results and the aggregated result.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LabOrderResponse"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"404":{"description":"Unknown order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"parameters":[{"name":"orderId","in":"path","required":true,"schema":{"type":"string","enum":["lo_seed_1","lo_seed_2","lo_seed_3","lo_missing"]}}]}},"/api/events/lab_orders":{"get":{"operationId":"ListLabOrderEvents","description":"The clinic's lab_order.updated events, newest first by default.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventsPage"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"parameters":[{"name":"order_by","in":"query","required":false,"schema":{"type":"string","enum":["DESC","ASC"]}},{"name":"page[number]","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":3}},{"name":"page[size]","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}}]}},"/api/events/{eventId}":{"get":{"operationId":"GetEvent","description":"One event with its data.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"Event","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventResponse"}}}},"401":{"description":"Missing, invalid, expired or revoked access token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"404":{"description":"Unknown event","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiErrors"}}}}},"parameters":[{"name":"eventId","in":"path","required":true,"schema":{"type":"string","enum":["evt_1","evt_2","evt_3","evt_missing"]}}]}},"/oauth/authorize":{"get":{"operationId":"Authorize","description":"The consent page: auto-approves as the requested (or default) practitioner and redirects to redirect_uri with code and state.","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":false,"reason":"Browser redirect flow; covered by acceptance tests."}},"parameters":[{"name":"client_id","in":"query","required":true,"schema":{"type":"string"}},{"name":"redirect_uri","in":"query","required":true,"schema":{"type":"string"}},{"name":"response_type","in":"query","required":true,"schema":{"type":"string","enum":["code"]}},{"name":"state","in":"query","required":false,"schema":{"type":"string"}},{"name":"scope","in":"query","required":false,"schema":{"type":"string"}},{"name":"practitioner_id","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"302":{"description":"Redirect back with ?code=&state="},"400":{"description":"Bad request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}}}}},"/results/{artifactId}":{"get":{"operationId":"GetResultPdf","description":"An expiring result PDF URL (what pdf_url points at).","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":false,"reason":"Binary PDF behind an expiring signed URL; covered by acceptance tests."}},"parameters":[{"name":"artifactId","in":"path","required":true,"schema":{"type":"string"}},{"name":"expires","in":"query","required":true,"schema":{"type":"string"}},{"name":"signature","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The PDF","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"403":{"description":"Expired or bad signature"},"404":{"description":"Unknown artifact"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"TokenRequest":{"type":"object","required":["grant_type","client_id","client_secret"],"properties":{"grant_type":{"type":"string","enum":["authorization_code","refresh_token"]},"client_id":{"type":"string","minLength":1,"maxLength":64,"examples":["parity-client"]},"client_secret":{"type":"string","minLength":1,"maxLength":64,"examples":["parity-secret"]},"code":{"type":"string","maxLength":64},"refresh_token":{"type":"string","maxLength":200},"redirect_uri":{"type":"string","maxLength":200,"examples":["https://emr.example.com/v1/fullscript/oauth/callback"]}}},"RevokeRequest":{"type":"object","required":["client_id","client_secret","token"],"properties":{"client_id":{"type":"string","minLength":1,"maxLength":64},"client_secret":{"type":"string","minLength":1,"maxLength":64},"token":{"type":"string","minLength":1,"maxLength":200}}},"OAuthTokenResponse":{"type":"object","required":["oauth"],"properties":{"oauth":{"type":"object","required":["access_token","token_type","expires_in","refresh_token","scope","created_at","resource_owner"],"properties":{"access_token":{"type":"string","x-mockingbird-volatile":{"kind":"token"}},"token_type":{"type":"string","enum":["Bearer"]},"expires_in":{"type":"integer"},"refresh_token":{"type":"string","x-mockingbird-volatile":{"kind":"token"}},"scope":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"resource_owner":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string"},"type":{"type":"string","enum":["Practitioner","Staff"]},"clinic_id":{"type":"string"}}}}}}},"OAuthError":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"error_description":{"type":"string"}}},"ApiErrors":{"type":"object","required":["errors"],"properties":{"errors":{"type":"array","items":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}},"ClinicResponse":{"type":"object","required":["clinic"],"properties":{"clinic":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}}}},"SessionGrant":{"type":"object","required":["secret_token","expires_at"],"properties":{"secret_token":{"type":"string","x-mockingbird-volatile":{"kind":"token"}},"expires_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"LabOrderSummary":{"type":"object","required":["id","state","treatment_plan_id","patient_id","created_at","updated_at"],"properties":{"id":{"type":"string"},"state":{"type":"string","enum":["not_purchased","purchased","schedule_appointment","upcoming_appointment","processing","partial_results","results_ready","interpretation_shared","results_amended"]},"treatment_plan_id":{"type":["string","null"]},"patient_id":{"type":"string"},"name":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"LabOrdersPage":{"type":"object","required":["orders","meta"],"properties":{"orders":{"type":"array","items":{"$ref":"#/components/schemas/LabOrderSummary"}},"meta":{"type":"object","required":["current_page","next_page","prev_page","total_pages","total_count"],"properties":{"current_page":{"type":"integer"},"next_page":{"type":["integer","null"]},"prev_page":{"type":["integer","null"]},"total_pages":{"type":"integer"},"total_count":{"type":"integer"}}}}},"LabOrderResponse":{"type":"object","required":["order"],"properties":{"order":{"type":"object","required":["id","state","treatment_plan_id","tests","results","latest_aggregated_result"],"properties":{"id":{"type":"string"},"state":{"type":"string","enum":["not_purchased","purchased","schedule_appointment","upcoming_appointment","processing","partial_results","results_ready","interpretation_shared","results_amended"]},"treatment_plan_id":{"type":["string","null"]},"patient_id":{"type":"string"},"name":{"type":"string"},"collection_method":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"tests":{"type":"array","items":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"lab_type":{"type":"string"}}}},"results":{"type":"array","items":{"type":"object","required":["id","name","state","pdf_url"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"state":{"type":"string"},"pdf_url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}}}}},"latest_aggregated_result":{"type":["object","null"],"properties":{"id":{"type":"string"},"artifact_id":{"type":"string"},"pdf_url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}},"status":{"type":"string"}}}}}}},"EventSummary":{"type":"object","required":["id","type","clinic_id","created_at"],"properties":{"id":{"type":"string"},"type":{"type":"string","enum":["lab_order.updated"]},"clinic_id":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"EventsPage":{"type":"object","required":["events","meta"],"properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/EventSummary"}},"meta":{"type":"object","required":["current_page","next_page","prev_page","total_pages","total_count"],"properties":{"current_page":{"type":"integer"},"next_page":{"type":["integer","null"]},"prev_page":{"type":["integer","null"]},"total_pages":{"type":"integer"},"total_count":{"type":"integer"}}}}},"EventResponse":{"type":"object","required":["event"],"properties":{"event":{"type":"object","required":["id","type","clinic_id","created_at","data"],"properties":{"id":{"type":"string"},"type":{"type":"string"},"clinic_id":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"data":{"type":"object"}}}}}}}}`);
2600
4794
  var operationIds = ["OAuthToken", "OAuthRevoke", "GetClinic", "CreateSessionGrant", "ListLabOrders", "GetLabOrder", "ListLabOrderEvents", "GetEvent", "Authorize", "GetResultPdf"];
2601
4795
  var supportedOperationIds = ["OAuthToken", "OAuthRevoke", "GetClinic", "CreateSessionGrant", "ListLabOrders", "GetLabOrder", "ListLabOrderEvents", "GetEvent", "Authorize", "GetResultPdf"];
2602
4796
 
@@ -2781,9 +4975,9 @@ var FULLSCRIPT_PRESETS = {
2781
4975
  },
2782
4976
  webhook_drop: { description: "The next webhook is never delivered", webhook: { mode: "drop" } }
2783
4977
  };
2784
- var json3 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
2785
- var adminError3 = (status, message) => json3(status, { error: { type: "mockingbird_admin", message } });
2786
- var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4978
+ var json5 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
4979
+ var adminError4 = (status, message) => json5(status, { error: { type: "mockingbird_admin", message } });
4980
+ var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2787
4981
  var echoes = async (response, challenge) => {
2788
4982
  try {
2789
4983
  const body = await response.clone().json();
@@ -2794,21 +4988,21 @@ var echoes = async (response, challenge) => {
2794
4988
  };
2795
4989
  var adminRoutes = (send, challenge) => (runtime) => ({
2796
4990
  "POST /oauth/codes": ({ body, namespace }) => {
2797
- const input = isRecord4(body) ? body : {};
4991
+ const input = isRecord6(body) ? body : {};
2798
4992
  const api = runtime.instance(namespace);
2799
4993
  const practitionerId = typeof input.practitionerId === "string" ? input.practitionerId : "prac_mock_1";
2800
4994
  if (!api.state.practitioners.get(practitionerId))
2801
- return adminError3(404, `no practitioner ${practitionerId}`);
4995
+ return adminError4(404, `no practitioner ${practitionerId}`);
2802
4996
  const code = api.issueCode(
2803
4997
  practitionerId,
2804
4998
  typeof input.clientId === "string" ? input.clientId : "any",
2805
4999
  typeof input.redirectUri === "string" ? input.redirectUri : null
2806
5000
  );
2807
- return json3(201, { code });
5001
+ return json5(201, { code });
2808
5002
  },
2809
5003
  "POST /practitioners": ({ body, namespace }) => {
2810
- if (!isRecord4(body) || typeof body.id !== "string" || typeof body.clinicId !== "string") {
2811
- return adminError3(
5004
+ if (!isRecord6(body) || typeof body.id !== "string" || typeof body.clinicId !== "string") {
5005
+ return adminError4(
2812
5006
  400,
2813
5007
  'expected {"id", "clinicId", "type"?: "Practitioner"|"Staff", "clinicName"?}'
2814
5008
  );
@@ -2827,12 +5021,12 @@ var adminRoutes = (send, challenge) => (runtime) => ({
2827
5021
  type: body.type === "Staff" ? "Staff" : "Practitioner"
2828
5022
  };
2829
5023
  api.state.practitioners.insert(practitioner.id, practitioner);
2830
- return json3(201, practitioner);
5024
+ return json5(201, practitioner);
2831
5025
  },
2832
- "GET /lab-orders": ({ namespace }) => json3(200, { orders: runtime.instance(namespace).labOrders() }),
5026
+ "GET /lab-orders": ({ namespace }) => json5(200, { orders: runtime.instance(namespace).labOrders() }),
2833
5027
  "POST /lab-orders": ({ body, namespace }) => {
2834
- if (!isRecord4(body) || typeof body.patientId !== "string") {
2835
- return adminError3(
5028
+ if (!isRecord6(body) || typeof body.patientId !== "string") {
5029
+ return adminError4(
2836
5030
  400,
2837
5031
  'expected {"patientId", "id"?, "clinicId"?, "treatmentPlanId"?, "name"?, "collectionMethod"?, "tests"?: [names]}'
2838
5032
  );
@@ -2847,23 +5041,23 @@ var adminRoutes = (send, challenge) => (runtime) => ({
2847
5041
  ...text("collectionMethod"),
2848
5042
  ...Array.isArray(body.tests) ? { tests: body.tests.map(String) } : {}
2849
5043
  });
2850
- return json3(201, order);
5044
+ return json5(201, order);
2851
5045
  },
2852
5046
  "POST /lab-orders/:id/transition": ({ params, body, namespace }) => {
2853
- if (!isRecord4(body) || !isLabOrderState(body.to)) {
2854
- return adminError3(
5047
+ if (!isRecord6(body) || !isLabOrderState(body.to)) {
5048
+ return adminError4(
2855
5049
  400,
2856
5050
  'expected {"to": "<purchased|schedule_appointment|upcoming_appointment|processing|partial_results|results_ready|interpretation_shared|results_amended>"}'
2857
5051
  );
2858
5052
  }
2859
5053
  try {
2860
- return json3(200, runtime.instance(namespace).transition(params.id, body.to));
5054
+ return json5(200, runtime.instance(namespace).transition(params.id, body.to));
2861
5055
  } catch (error) {
2862
5056
  if (error instanceof HttpError) return error.toResponse();
2863
5057
  throw error;
2864
5058
  }
2865
5059
  },
2866
- "GET /events": ({ namespace }) => json3(200, { events: runtime.instance(namespace).eventsList() }),
5060
+ "GET /events": ({ namespace }) => json5(200, { events: runtime.instance(namespace).eventsList() }),
2867
5061
  "POST /webhooks/verify": async ({ namespace }) => {
2868
5062
  const endpoints = runtime.webhooks?.endpoints(namespace) ?? [];
2869
5063
  const results = await Promise.all(
@@ -2889,29 +5083,29 @@ var adminRoutes = (send, challenge) => (runtime) => ({
2889
5083
  }
2890
5084
  })
2891
5085
  );
2892
- return json3(200, { endpoints: results });
5086
+ return json5(200, { endpoints: results });
2893
5087
  },
2894
- "GET /settings": ({ namespace }) => json3(200, runtime.instance(namespace).state.current()),
5088
+ "GET /settings": ({ namespace }) => json5(200, runtime.instance(namespace).state.current()),
2895
5089
  "PUT /settings": ({ body, namespace }) => {
2896
- if (!isRecord4(body)) return adminError3(400, "expected a JSON object");
5090
+ if (!isRecord6(body)) return adminError4(400, "expected a JSON object");
2897
5091
  const patch = {};
2898
5092
  for (const key of ["accessTokenTtlSeconds", "codeTtlSeconds", "pdfUrlTtlSeconds"]) {
2899
5093
  if (body[key] !== void 0) {
2900
- if (typeof body[key] !== "number") return adminError3(400, `${key}: number`);
5094
+ if (typeof body[key] !== "number") return adminError4(400, `${key}: number`);
2901
5095
  patch[key] = body[key];
2902
5096
  }
2903
5097
  }
2904
5098
  if (body.resultsBaseUrl !== void 0) {
2905
5099
  if (body.resultsBaseUrl !== null && typeof body.resultsBaseUrl !== "string")
2906
- return adminError3(400, "resultsBaseUrl: string | null");
5100
+ return adminError4(400, "resultsBaseUrl: string | null");
2907
5101
  patch.resultsBaseUrl = body.resultsBaseUrl;
2908
5102
  }
2909
5103
  if (body.clients !== void 0) {
2910
5104
  if (!Array.isArray(body.clients))
2911
- return adminError3(400, "clients: [{clientId, clientSecret}]");
2912
- patch.clients = body.clients.filter(isRecord4).map((c) => ({ clientId: String(c.clientId), clientSecret: String(c.clientSecret) }));
5105
+ return adminError4(400, "clients: [{clientId, clientSecret}]");
5106
+ patch.clients = body.clients.filter(isRecord6).map((c) => ({ clientId: String(c.clientId), clientSecret: String(c.clientSecret) }));
2913
5107
  }
2914
- return json3(200, runtime.instance(namespace).state.update(patch));
5108
+ return json5(200, runtime.instance(namespace).state.update(patch));
2915
5109
  }
2916
5110
  });
2917
5111
  var createRuntime2 = (options = {}) => {
@@ -2931,7 +5125,7 @@ var createRuntime2 = (options = {}) => {
2931
5125
  });
2932
5126
  const runtime = createRuntime({
2933
5127
  name: FULLSCRIPT_NAMESPACE,
2934
- document,
5128
+ document: document2,
2935
5129
  ...options.sqlite ? { sqlite: options.sqlite } : {},
2936
5130
  ...options.clock ? { clock: options.clock } : {},
2937
5131
  ...options.seed !== void 0 ? { seed: options.seed } : {},
@@ -3122,7 +5316,7 @@ var FullscriptAPI = class {
3122
5316
  GetResultPdf: (context) => this.pdf(context)
3123
5317
  });
3124
5318
  this.service = createService({
3125
- document,
5319
+ document: document2,
3126
5320
  handlers,
3127
5321
  sqlite,
3128
5322
  namespace,
@@ -3289,11 +5483,11 @@ var FullscriptAPI = class {
3289
5483
  return oauthError(400, "invalid_request", `Unknown practitioner ${practitionerId}.`);
3290
5484
  }
3291
5485
  const code = this.issueCode(practitionerId, clientId, redirect);
3292
- const location = new URL(redirect);
3293
- location.searchParams.set("code", code);
5486
+ const location2 = new URL(redirect);
5487
+ location2.searchParams.set("code", code);
3294
5488
  const state = url.searchParams.get("state");
3295
- if (state !== null) location.searchParams.set("state", state);
3296
- return new Response(null, { status: 302, headers: { location: location.toString() } });
5489
+ if (state !== null) location2.searchParams.set("state", state);
5490
+ return new Response(null, { status: 302, headers: { location: location2.toString() } });
3297
5491
  }
3298
5492
  summary(order) {
3299
5493
  return {
@@ -3473,7 +5667,7 @@ var FullscriptAPI = class {
3473
5667
  };
3474
5668
 
3475
5669
  export {
3476
- document,
5670
+ document2 as document,
3477
5671
  operationIds,
3478
5672
  supportedOperationIds,
3479
5673
  RESULT_PDF,
@@ -3493,4 +5687,4 @@ export {
3493
5687
  envelope,
3494
5688
  FullscriptAPI
3495
5689
  };
3496
- //# sourceMappingURL=chunk-7353U527.js.map
5690
+ //# sourceMappingURL=chunk-HEWMSDJK.js.map