@crvouga/mockingbird-service-flex 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/{chunk-BQA5UCJS.js → chunk-NBZXQAFA.js} +2 -2
- package/dist/{chunk-UKBUYHZN.js → chunk-O3RZRWN7.js} +1438 -630
- package/dist/chunk-O3RZRWN7.js.map +7 -0
- package/dist/cli.js +2 -2
- package/dist/index.js +1 -1
- package/dist/server.js +2 -2
- package/package.json +4 -3
- package/dist/chunk-UKBUYHZN.js.map +0 -7
- /package/dist/{chunk-BQA5UCJS.js.map → chunk-NBZXQAFA.js.map} +0 -0
|
@@ -540,10 +540,10 @@ var OpenAPIReferenceError = class extends Error {
|
|
|
540
540
|
};
|
|
541
541
|
var unescapePointer = (segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
542
542
|
var isReference = (value) => typeof value === "object" && value !== null && typeof value.$ref === "string";
|
|
543
|
-
var resolveRef = (
|
|
543
|
+
var resolveRef = (document3, ref) => {
|
|
544
544
|
if (!ref.startsWith("#/"))
|
|
545
545
|
throw new OpenAPIReferenceError(ref);
|
|
546
|
-
let cursor =
|
|
546
|
+
let cursor = document3;
|
|
547
547
|
for (const raw of ref.slice(2).split("/")) {
|
|
548
548
|
const segment = unescapePointer(raw);
|
|
549
549
|
if (typeof cursor !== "object" || cursor === null || !(segment in cursor)) {
|
|
@@ -555,14 +555,14 @@ var resolveRef = (document2, ref) => {
|
|
|
555
555
|
throw new OpenAPIReferenceError(ref);
|
|
556
556
|
return cursor;
|
|
557
557
|
};
|
|
558
|
-
var deref = (
|
|
558
|
+
var deref = (document3, value) => {
|
|
559
559
|
let current = value;
|
|
560
560
|
const seen = /* @__PURE__ */ new Set();
|
|
561
561
|
while (isReference(current)) {
|
|
562
562
|
if (seen.has(current.$ref))
|
|
563
563
|
throw new OpenAPIReferenceError(`${current.$ref} (cycle)`);
|
|
564
564
|
seen.add(current.$ref);
|
|
565
|
-
current = resolveRef(
|
|
565
|
+
current = resolveRef(document3, current.$ref);
|
|
566
566
|
}
|
|
567
567
|
return current;
|
|
568
568
|
};
|
|
@@ -580,36 +580,36 @@ var HTTP_METHODS = [
|
|
|
580
580
|
];
|
|
581
581
|
|
|
582
582
|
// ../../openapi/core/dist/document.js
|
|
583
|
-
var mergeParameters = (
|
|
583
|
+
var mergeParameters = (document3, item, own) => {
|
|
584
584
|
const merged = /* @__PURE__ */ new Map();
|
|
585
585
|
for (const raw of item.parameters ?? []) {
|
|
586
|
-
const parameter = deref(
|
|
586
|
+
const parameter = deref(document3, raw);
|
|
587
587
|
merged.set(`${parameter.in}:${parameter.name}`, parameter);
|
|
588
588
|
}
|
|
589
589
|
for (const raw of own ?? []) {
|
|
590
|
-
const parameter = deref(
|
|
590
|
+
const parameter = deref(document3, raw);
|
|
591
591
|
merged.set(`${parameter.in}:${parameter.name}`, parameter);
|
|
592
592
|
}
|
|
593
593
|
return [...merged.values()];
|
|
594
594
|
};
|
|
595
|
-
var listOperations = (
|
|
595
|
+
var listOperations = (document3) => {
|
|
596
596
|
const operations = [];
|
|
597
|
-
for (const [path, item] of Object.entries(
|
|
597
|
+
for (const [path, item] of Object.entries(document3.paths)) {
|
|
598
598
|
for (const method of HTTP_METHODS) {
|
|
599
599
|
const operation = item[method];
|
|
600
600
|
if (operation?.operationId === void 0)
|
|
601
601
|
continue;
|
|
602
602
|
const responses = {};
|
|
603
603
|
for (const [status, response] of Object.entries(operation.responses)) {
|
|
604
|
-
responses[status] = deref(
|
|
604
|
+
responses[status] = deref(document3, response);
|
|
605
605
|
}
|
|
606
606
|
operations.push({
|
|
607
607
|
operationId: operation.operationId,
|
|
608
608
|
method,
|
|
609
609
|
path,
|
|
610
610
|
operation,
|
|
611
|
-
parameters: mergeParameters(
|
|
612
|
-
requestBody: operation.requestBody === void 0 ? void 0 : deref(
|
|
611
|
+
parameters: mergeParameters(document3, item, operation.parameters),
|
|
612
|
+
requestBody: operation.requestBody === void 0 ? void 0 : deref(document3, operation.requestBody),
|
|
613
613
|
responses
|
|
614
614
|
});
|
|
615
615
|
}
|
|
@@ -618,7 +618,7 @@ var listOperations = (document2) => {
|
|
|
618
618
|
};
|
|
619
619
|
|
|
620
620
|
// ../../openapi/core/dist/schema.js
|
|
621
|
-
var resolveSchema = (
|
|
621
|
+
var resolveSchema = (document3, schema) => {
|
|
622
622
|
let current = schema;
|
|
623
623
|
const seen = /* @__PURE__ */ new Set();
|
|
624
624
|
while (typeof current.$ref === "string") {
|
|
@@ -627,7 +627,7 @@ var resolveSchema = (document2, schema) => {
|
|
|
627
627
|
break;
|
|
628
628
|
seen.add(ref);
|
|
629
629
|
const { $ref: _ignored, ...siblings } = current;
|
|
630
|
-
const target = resolveRef(
|
|
630
|
+
const target = resolveRef(document3, ref);
|
|
631
631
|
current = { ...target, ...siblings };
|
|
632
632
|
}
|
|
633
633
|
if (current.nullable === true) {
|
|
@@ -698,9 +698,9 @@ var FORMAT_PATTERNS = {
|
|
|
698
698
|
ipv4: /^(25[0-5]|2[0-4]\d|1?\d?\d)(\.(25[0-5]|2[0-4]\d|1?\d?\d)){3}$/
|
|
699
699
|
};
|
|
700
700
|
var graphemeLength = (value) => [...value].length;
|
|
701
|
-
var validateValue = (
|
|
701
|
+
var validateValue = (document3, schema, value, path = []) => {
|
|
702
702
|
const errors = [];
|
|
703
|
-
const s = resolveSchema(
|
|
703
|
+
const s = resolveSchema(document3, schema);
|
|
704
704
|
const fail2 = (message) => errors.push({ path, message });
|
|
705
705
|
const actual = jsonTypeOf(value);
|
|
706
706
|
if (actual === "undefined") {
|
|
@@ -762,7 +762,7 @@ var validateValue = (document2, schema, value, path = []) => {
|
|
|
762
762
|
value.forEach((item, i) => {
|
|
763
763
|
const itemSchema = s.prefixItems?.[i] ?? s.items;
|
|
764
764
|
if (itemSchema)
|
|
765
|
-
errors.push(...validateValue(
|
|
765
|
+
errors.push(...validateValue(document3, itemSchema, item, [...path, i]));
|
|
766
766
|
});
|
|
767
767
|
}
|
|
768
768
|
if (actual === "object") {
|
|
@@ -778,16 +778,16 @@ var validateValue = (document2, schema, value, path = []) => {
|
|
|
778
778
|
for (const key of keys) {
|
|
779
779
|
const property = s.properties?.[key];
|
|
780
780
|
if (property) {
|
|
781
|
-
errors.push(...validateValue(
|
|
781
|
+
errors.push(...validateValue(document3, property, record[key], [...path, key]));
|
|
782
782
|
continue;
|
|
783
783
|
}
|
|
784
784
|
if (s.additionalProperties === false)
|
|
785
785
|
fail2(`unexpected property ${key}`);
|
|
786
786
|
else if (typeof s.additionalProperties === "object") {
|
|
787
|
-
errors.push(...validateValue(
|
|
787
|
+
errors.push(...validateValue(document3, s.additionalProperties, record[key], [...path, key]));
|
|
788
788
|
}
|
|
789
789
|
if (s.propertyNames) {
|
|
790
|
-
const nameErrors = validateValue(
|
|
790
|
+
const nameErrors = validateValue(document3, s.propertyNames, key, [...path, key]);
|
|
791
791
|
if (nameErrors.length > 0)
|
|
792
792
|
fail2(`property name ${key} is invalid: ${nameErrors[0]?.message}`);
|
|
793
793
|
}
|
|
@@ -795,15 +795,15 @@ var validateValue = (document2, schema, value, path = []) => {
|
|
|
795
795
|
}
|
|
796
796
|
if (s.allOf)
|
|
797
797
|
for (const branch of s.allOf)
|
|
798
|
-
errors.push(...validateValue(
|
|
799
|
-
if (s.anyOf && !s.anyOf.some((branch) => validateValue(
|
|
798
|
+
errors.push(...validateValue(document3, branch, value, path));
|
|
799
|
+
if (s.anyOf && !s.anyOf.some((branch) => validateValue(document3, branch, value).length === 0))
|
|
800
800
|
fail2("matches no anyOf branch");
|
|
801
801
|
if (s.oneOf) {
|
|
802
|
-
const matches2 = s.oneOf.filter((branch) => validateValue(
|
|
802
|
+
const matches2 = s.oneOf.filter((branch) => validateValue(document3, branch, value).length === 0).length;
|
|
803
803
|
if (matches2 !== 1)
|
|
804
804
|
fail2(`matches ${matches2} oneOf branches, expected exactly 1`);
|
|
805
805
|
}
|
|
806
|
-
if (s.not && validateValue(
|
|
806
|
+
if (s.not && validateValue(document3, s.not, value).length === 0)
|
|
807
807
|
fail2("matches forbidden `not` schema");
|
|
808
808
|
return errors;
|
|
809
809
|
};
|
|
@@ -1396,6 +1396,1232 @@ var clearNamespace = (sqlite, namespace) => {
|
|
|
1396
1396
|
});
|
|
1397
1397
|
};
|
|
1398
1398
|
|
|
1399
|
+
// ../../ui/dist/reset.js
|
|
1400
|
+
var CSS_RESET = `*,
|
|
1401
|
+
*::before,
|
|
1402
|
+
*::after {
|
|
1403
|
+
box-sizing: border-box;
|
|
1404
|
+
margin: 0;
|
|
1405
|
+
padding: 0;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
* {
|
|
1409
|
+
scrollbar-width: thin;
|
|
1410
|
+
scrollbar-color: color-mix(in srgb, CanvasText 35%, Canvas) Canvas;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
*::-webkit-scrollbar {
|
|
1414
|
+
width: 12px;
|
|
1415
|
+
height: 12px;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
*::-webkit-scrollbar-track,
|
|
1419
|
+
*::-webkit-scrollbar-corner {
|
|
1420
|
+
background: Canvas;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
*::-webkit-scrollbar-thumb {
|
|
1424
|
+
background: color-mix(in srgb, CanvasText 35%, Canvas);
|
|
1425
|
+
border: 3px solid Canvas;
|
|
1426
|
+
border-radius: 999px;
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
html {
|
|
1430
|
+
hanging-punctuation: first allow-end last;
|
|
1431
|
+
-webkit-text-size-adjust: none;
|
|
1432
|
+
text-size-adjust: none;
|
|
1433
|
+
tab-size: 4;
|
|
1434
|
+
font-family:
|
|
1435
|
+
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI",
|
|
1436
|
+
"Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
|
1437
|
+
font-size: 16px;
|
|
1438
|
+
line-height: 1.5;
|
|
1439
|
+
-webkit-font-smoothing: antialiased;
|
|
1440
|
+
-moz-osx-font-smoothing: grayscale;
|
|
1441
|
+
text-rendering: optimizeLegibility;
|
|
1442
|
+
-webkit-tap-highlight-color: transparent;
|
|
1443
|
+
scroll-behavior: smooth;
|
|
1444
|
+
background: Canvas;
|
|
1445
|
+
color: CanvasText;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
body {
|
|
1449
|
+
min-height: 100svh;
|
|
1450
|
+
line-height: 1.5;
|
|
1451
|
+
background: Canvas;
|
|
1452
|
+
color: CanvasText;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
h1 {
|
|
1456
|
+
font-size: 2em;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
h2 {
|
|
1460
|
+
font-size: 1.5em;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
h3 {
|
|
1464
|
+
font-size: 1.17em;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
h4 {
|
|
1468
|
+
font-size: 1em;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
h5 {
|
|
1472
|
+
font-size: 0.83em;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
h6 {
|
|
1476
|
+
font-size: 0.67em;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
h1,
|
|
1480
|
+
h2,
|
|
1481
|
+
h3,
|
|
1482
|
+
h4,
|
|
1483
|
+
h5,
|
|
1484
|
+
h6 {
|
|
1485
|
+
font-weight: 700;
|
|
1486
|
+
line-height: 1.2;
|
|
1487
|
+
text-wrap: balance;
|
|
1488
|
+
overflow-wrap: break-word;
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
p,
|
|
1492
|
+
li,
|
|
1493
|
+
figcaption,
|
|
1494
|
+
dd,
|
|
1495
|
+
dt,
|
|
1496
|
+
blockquote {
|
|
1497
|
+
overflow-wrap: break-word;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
p,
|
|
1501
|
+
li,
|
|
1502
|
+
figcaption,
|
|
1503
|
+
blockquote {
|
|
1504
|
+
text-wrap: pretty;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
ul,
|
|
1508
|
+
ol,
|
|
1509
|
+
menu {
|
|
1510
|
+
list-style: none;
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
a {
|
|
1514
|
+
color: inherit;
|
|
1515
|
+
text-decoration: underline;
|
|
1516
|
+
text-decoration-thickness: 1px;
|
|
1517
|
+
text-underline-offset: 0.15em;
|
|
1518
|
+
text-decoration-skip-ink: auto;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
b,
|
|
1522
|
+
strong {
|
|
1523
|
+
font-weight: 700;
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
small {
|
|
1527
|
+
font-size: 0.875em;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
code,
|
|
1531
|
+
kbd,
|
|
1532
|
+
samp,
|
|
1533
|
+
pre {
|
|
1534
|
+
font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
|
|
1535
|
+
font-size: 1em;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
sub,
|
|
1539
|
+
sup {
|
|
1540
|
+
font-size: 75%;
|
|
1541
|
+
line-height: 0;
|
|
1542
|
+
position: relative;
|
|
1543
|
+
vertical-align: baseline;
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
sub {
|
|
1547
|
+
bottom: -0.25em;
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
sup {
|
|
1551
|
+
top: -0.5em;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
abbr[title] {
|
|
1555
|
+
text-decoration: underline dotted;
|
|
1556
|
+
text-underline-offset: 0.15em;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
mark {
|
|
1560
|
+
background: Mark;
|
|
1561
|
+
color: MarkText;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
hr {
|
|
1565
|
+
height: 0;
|
|
1566
|
+
border: 0;
|
|
1567
|
+
border-top: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
|
|
1568
|
+
color: inherit;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
img,
|
|
1572
|
+
picture,
|
|
1573
|
+
video,
|
|
1574
|
+
canvas {
|
|
1575
|
+
display: block;
|
|
1576
|
+
max-width: 100%;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
img,
|
|
1580
|
+
video {
|
|
1581
|
+
height: auto;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
svg {
|
|
1585
|
+
overflow: hidden;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
iframe {
|
|
1589
|
+
border: 0;
|
|
1590
|
+
max-width: 100%;
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
table {
|
|
1594
|
+
border-collapse: collapse;
|
|
1595
|
+
border-spacing: 0;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
fieldset {
|
|
1599
|
+
border: 0;
|
|
1600
|
+
min-inline-size: 0;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
legend {
|
|
1604
|
+
padding: 0;
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
button,
|
|
1608
|
+
input,
|
|
1609
|
+
select,
|
|
1610
|
+
textarea,
|
|
1611
|
+
optgroup {
|
|
1612
|
+
font: inherit;
|
|
1613
|
+
letter-spacing: inherit;
|
|
1614
|
+
color: inherit;
|
|
1615
|
+
margin: 0;
|
|
1616
|
+
vertical-align: middle;
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
button,
|
|
1620
|
+
input[type="button"],
|
|
1621
|
+
input[type="submit"],
|
|
1622
|
+
input[type="reset"] {
|
|
1623
|
+
appearance: none;
|
|
1624
|
+
background: none;
|
|
1625
|
+
border: 0;
|
|
1626
|
+
padding: 0;
|
|
1627
|
+
cursor: pointer;
|
|
1628
|
+
text-align: center;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
input,
|
|
1632
|
+
textarea,
|
|
1633
|
+
select {
|
|
1634
|
+
background-color: Canvas;
|
|
1635
|
+
border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
|
|
1636
|
+
border-radius: 0;
|
|
1637
|
+
padding: 0.5em 0.65em;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
input[type="checkbox"],
|
|
1641
|
+
input[type="radio"],
|
|
1642
|
+
input[type="range"],
|
|
1643
|
+
input[type="file"],
|
|
1644
|
+
input[type="color"],
|
|
1645
|
+
input[type="button"],
|
|
1646
|
+
input[type="submit"],
|
|
1647
|
+
input[type="reset"],
|
|
1648
|
+
input[type="image"],
|
|
1649
|
+
input[type="hidden"] {
|
|
1650
|
+
background: none;
|
|
1651
|
+
border: 0;
|
|
1652
|
+
padding: 0;
|
|
1653
|
+
border-radius: 0;
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
input[type="checkbox"],
|
|
1657
|
+
input[type="radio"] {
|
|
1658
|
+
accent-color: CanvasText;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
input[type="search"] {
|
|
1662
|
+
appearance: none;
|
|
1663
|
+
background-color: Canvas;
|
|
1664
|
+
border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
input[type="search"]::-webkit-search-decoration,
|
|
1668
|
+
input[type="search"]::-webkit-search-cancel-button,
|
|
1669
|
+
input[type="search"]::-webkit-search-results-button,
|
|
1670
|
+
input[type="search"]::-webkit-search-results-decoration {
|
|
1671
|
+
-webkit-appearance: none;
|
|
1672
|
+
display: none;
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
input[type="number"] {
|
|
1676
|
+
appearance: textfield;
|
|
1677
|
+
background-color: Canvas;
|
|
1678
|
+
border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
input[type="number"]::-webkit-inner-spin-button,
|
|
1682
|
+
input[type="number"]::-webkit-outer-spin-button {
|
|
1683
|
+
-webkit-appearance: none;
|
|
1684
|
+
margin: 0;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
textarea {
|
|
1688
|
+
resize: vertical;
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
::placeholder {
|
|
1692
|
+
opacity: 1;
|
|
1693
|
+
color: color-mix(in srgb, CanvasText 45%, transparent);
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
:focus-visible {
|
|
1697
|
+
outline: 2px solid CanvasText;
|
|
1698
|
+
outline-offset: 2px;
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
:focus:not(:focus-visible) {
|
|
1702
|
+
outline: none;
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
:disabled,
|
|
1706
|
+
[disabled] {
|
|
1707
|
+
cursor: default;
|
|
1708
|
+
opacity: 0.6;
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
label,
|
|
1712
|
+
summary,
|
|
1713
|
+
select,
|
|
1714
|
+
[role="button"] {
|
|
1715
|
+
cursor: pointer;
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
summary {
|
|
1719
|
+
display: list-item;
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
details {
|
|
1723
|
+
display: block;
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
dialog {
|
|
1727
|
+
margin: auto;
|
|
1728
|
+
padding: 1rem;
|
|
1729
|
+
border: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
|
|
1730
|
+
background: Canvas;
|
|
1731
|
+
color: CanvasText;
|
|
1732
|
+
max-width: calc(100vw - 2rem);
|
|
1733
|
+
max-height: calc(100svh - 2rem);
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
dialog:not([open]) {
|
|
1737
|
+
display: none;
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
::backdrop {
|
|
1741
|
+
background: color-mix(in srgb, CanvasText 40%, transparent);
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
[hidden] {
|
|
1745
|
+
display: none !important;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
::selection {
|
|
1749
|
+
background: Highlight;
|
|
1750
|
+
color: HighlightText;
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1754
|
+
*,
|
|
1755
|
+
*::before,
|
|
1756
|
+
*::after {
|
|
1757
|
+
animation-duration: 0.01ms !important;
|
|
1758
|
+
animation-iteration-count: 1 !important;
|
|
1759
|
+
transition-duration: 0.01ms !important;
|
|
1760
|
+
scroll-behavior: auto !important;
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
@media (pointer: coarse) {
|
|
1765
|
+
input,
|
|
1766
|
+
select,
|
|
1767
|
+
textarea {
|
|
1768
|
+
font-size: max(16px, 1em);
|
|
1769
|
+
}
|
|
1770
|
+
}`;
|
|
1771
|
+
|
|
1772
|
+
// ../core/dist/admin-client.js
|
|
1773
|
+
function isRecord2(value) {
|
|
1774
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1775
|
+
}
|
|
1776
|
+
function mountAdminBrand(doc, host, service, catalog) {
|
|
1777
|
+
const clip = (value, max) => {
|
|
1778
|
+
if (typeof value !== "string")
|
|
1779
|
+
return null;
|
|
1780
|
+
const trimmed = value.trim();
|
|
1781
|
+
if (trimmed === "")
|
|
1782
|
+
return null;
|
|
1783
|
+
return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
|
|
1784
|
+
};
|
|
1785
|
+
const httpUrl = (value) => {
|
|
1786
|
+
if (typeof value !== "string" || value.length > 2e3)
|
|
1787
|
+
return null;
|
|
1788
|
+
try {
|
|
1789
|
+
const url = new URL(value);
|
|
1790
|
+
if (url.protocol !== "https:" && url.protocol !== "http:")
|
|
1791
|
+
return null;
|
|
1792
|
+
return url.href;
|
|
1793
|
+
} catch {
|
|
1794
|
+
return null;
|
|
1795
|
+
}
|
|
1796
|
+
};
|
|
1797
|
+
const hexColor = (value) => {
|
|
1798
|
+
if (typeof value !== "string")
|
|
1799
|
+
return null;
|
|
1800
|
+
const raw = value.trim();
|
|
1801
|
+
return /^#[0-9a-fA-F]{3,8}$/.test(raw) ? raw : null;
|
|
1802
|
+
};
|
|
1803
|
+
const hostname = (value) => {
|
|
1804
|
+
try {
|
|
1805
|
+
return new URL(value).hostname.replace(/^www\./, "");
|
|
1806
|
+
} catch {
|
|
1807
|
+
return value;
|
|
1808
|
+
}
|
|
1809
|
+
};
|
|
1810
|
+
host.replaceChildren();
|
|
1811
|
+
host.hidden = true;
|
|
1812
|
+
if (!isRecord2(catalog) || !Object.hasOwn(catalog, service))
|
|
1813
|
+
return;
|
|
1814
|
+
const row = catalog[service];
|
|
1815
|
+
if (!isRecord2(row))
|
|
1816
|
+
return;
|
|
1817
|
+
const vendor = clip(row.vendor, 80);
|
|
1818
|
+
const website = httpUrl(row.website);
|
|
1819
|
+
const guide = httpUrl(row.guide);
|
|
1820
|
+
const docs = httpUrl(row.docs);
|
|
1821
|
+
const logo = httpUrl(row.logo);
|
|
1822
|
+
const color = hexColor(row.color);
|
|
1823
|
+
const description = clip(row.description, 280);
|
|
1824
|
+
if (vendor === null || website === null && guide === null && docs === null)
|
|
1825
|
+
return;
|
|
1826
|
+
const add = (node) => {
|
|
1827
|
+
host.append(node);
|
|
1828
|
+
return node;
|
|
1829
|
+
};
|
|
1830
|
+
if (logo !== null) {
|
|
1831
|
+
const img = doc.createElement("img");
|
|
1832
|
+
img.alt = "";
|
|
1833
|
+
img.src = logo;
|
|
1834
|
+
img.width = 16;
|
|
1835
|
+
img.height = 16;
|
|
1836
|
+
img.decoding = "async";
|
|
1837
|
+
img.referrerPolicy = "no-referrer";
|
|
1838
|
+
if (color !== null)
|
|
1839
|
+
img.style.borderColor = color;
|
|
1840
|
+
add(img);
|
|
1841
|
+
}
|
|
1842
|
+
const name = doc.createElement("span");
|
|
1843
|
+
name.className = "vendor-name";
|
|
1844
|
+
name.textContent = vendor;
|
|
1845
|
+
if (description !== null)
|
|
1846
|
+
name.title = description;
|
|
1847
|
+
add(name);
|
|
1848
|
+
const sep = () => {
|
|
1849
|
+
const node = doc.createElement("span");
|
|
1850
|
+
node.className = "vendor-sep";
|
|
1851
|
+
node.setAttribute("aria-hidden", "true");
|
|
1852
|
+
node.textContent = "\xB7";
|
|
1853
|
+
return node;
|
|
1854
|
+
};
|
|
1855
|
+
const link = (href, label, title) => {
|
|
1856
|
+
const node = doc.createElement("a");
|
|
1857
|
+
node.className = "vendor-link";
|
|
1858
|
+
node.href = href;
|
|
1859
|
+
node.target = "_blank";
|
|
1860
|
+
node.rel = "noopener noreferrer";
|
|
1861
|
+
node.referrerPolicy = "no-referrer";
|
|
1862
|
+
node.textContent = label;
|
|
1863
|
+
node.title = title;
|
|
1864
|
+
return node;
|
|
1865
|
+
};
|
|
1866
|
+
if (website !== null) {
|
|
1867
|
+
add(sep());
|
|
1868
|
+
add(link(website, hostname(website), `${vendor} website`));
|
|
1869
|
+
}
|
|
1870
|
+
if (guide !== null) {
|
|
1871
|
+
add(sep());
|
|
1872
|
+
add(link(guide, "Docs", `Docs for ${vendor}`));
|
|
1873
|
+
}
|
|
1874
|
+
if (docs !== null) {
|
|
1875
|
+
add(sep());
|
|
1876
|
+
add(link(docs, "API", `${vendor} API reference`));
|
|
1877
|
+
}
|
|
1878
|
+
host.removeAttribute("hidden");
|
|
1879
|
+
host.hidden = false;
|
|
1880
|
+
}
|
|
1881
|
+
function startAdminBrand(env) {
|
|
1882
|
+
const requested = env.params.get("brands");
|
|
1883
|
+
let brandsUrl = env.brandsUrl;
|
|
1884
|
+
if (requested) {
|
|
1885
|
+
try {
|
|
1886
|
+
const url = new URL(requested, env.location.href);
|
|
1887
|
+
const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]" || url.origin === env.location.origin;
|
|
1888
|
+
if ((url.protocol === "https:" || url.protocol === "http:") && local)
|
|
1889
|
+
brandsUrl = url.href;
|
|
1890
|
+
} catch {
|
|
1891
|
+
brandsUrl = env.brandsUrl;
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
const vendorHost = env.$("vendor");
|
|
1895
|
+
const brandRoot = env.document.querySelector("[data-mockingbird-admin]");
|
|
1896
|
+
const brandService = brandRoot?.getAttribute("data-service") || "";
|
|
1897
|
+
if (vendorHost === null)
|
|
1898
|
+
return;
|
|
1899
|
+
env.fetch(brandsUrl, {
|
|
1900
|
+
headers: { accept: "application/json" },
|
|
1901
|
+
signal: AbortSignal.timeout(5e3)
|
|
1902
|
+
}).then(async (response) => {
|
|
1903
|
+
if (!response.ok)
|
|
1904
|
+
return null;
|
|
1905
|
+
const catalog = await response.json();
|
|
1906
|
+
return catalog;
|
|
1907
|
+
}).then((catalog) => {
|
|
1908
|
+
mountAdminBrand(env.document, vendorHost, brandService, catalog);
|
|
1909
|
+
}).catch(() => {
|
|
1910
|
+
});
|
|
1911
|
+
}
|
|
1912
|
+
function faultPresetList(value) {
|
|
1913
|
+
if (!isRecord2(value))
|
|
1914
|
+
throw new Error("Fault presets response was not an object");
|
|
1915
|
+
if (!Object.hasOwn(value, "presets") || value.presets === void 0 || value.presets === null) {
|
|
1916
|
+
return { presets: [] };
|
|
1917
|
+
}
|
|
1918
|
+
if (!Array.isArray(value.presets))
|
|
1919
|
+
throw new Error("Fault presets response was not a list");
|
|
1920
|
+
return {
|
|
1921
|
+
presets: value.presets.flatMap((item) => {
|
|
1922
|
+
if (!isRecord2(item) || typeof item.name !== "string")
|
|
1923
|
+
return [];
|
|
1924
|
+
return [{ name: item.name }];
|
|
1925
|
+
})
|
|
1926
|
+
};
|
|
1927
|
+
}
|
|
1928
|
+
function bootAdmin(config) {
|
|
1929
|
+
const fieldKinds = ["string", "number", "boolean", "null", "object", "array", "unknown"];
|
|
1930
|
+
const sources = ["declared", "inferred", "mixed"];
|
|
1931
|
+
const isFieldKind = (value) => typeof value === "string" && fieldKinds.some((kind) => kind === value);
|
|
1932
|
+
const isSource = (value) => typeof value === "string" && sources.some((source) => source === value);
|
|
1933
|
+
const readJson2 = (text) => {
|
|
1934
|
+
const value = JSON.parse(text);
|
|
1935
|
+
return value;
|
|
1936
|
+
};
|
|
1937
|
+
const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => {
|
|
1938
|
+
switch (char) {
|
|
1939
|
+
case "&":
|
|
1940
|
+
return "&";
|
|
1941
|
+
case "<":
|
|
1942
|
+
return "<";
|
|
1943
|
+
case ">":
|
|
1944
|
+
return ">";
|
|
1945
|
+
case '"':
|
|
1946
|
+
return """;
|
|
1947
|
+
default:
|
|
1948
|
+
return "'";
|
|
1949
|
+
}
|
|
1950
|
+
});
|
|
1951
|
+
const el = (id, kind) => {
|
|
1952
|
+
const node = document.getElementById(id);
|
|
1953
|
+
if (!(node instanceof kind))
|
|
1954
|
+
throw new Error(`admin shell is missing #${id}`);
|
|
1955
|
+
return node;
|
|
1956
|
+
};
|
|
1957
|
+
const errorMessage = (data, fallback) => {
|
|
1958
|
+
if (!isRecord2(data))
|
|
1959
|
+
return fallback;
|
|
1960
|
+
const error2 = data.error;
|
|
1961
|
+
if (!isRecord2(error2) || typeof error2.message !== "string" || error2.message === "") {
|
|
1962
|
+
return fallback;
|
|
1963
|
+
}
|
|
1964
|
+
return error2.message;
|
|
1965
|
+
};
|
|
1966
|
+
const requireList = (value, label) => {
|
|
1967
|
+
if (value === void 0 || value === null)
|
|
1968
|
+
return [];
|
|
1969
|
+
if (!Array.isArray(value))
|
|
1970
|
+
throw new Error(`${label} was not a list`);
|
|
1971
|
+
return value;
|
|
1972
|
+
};
|
|
1973
|
+
const state = {
|
|
1974
|
+
namespace: "default",
|
|
1975
|
+
key: sessionStorage.getItem("mockingbird-admin-key") || "",
|
|
1976
|
+
view: "overview",
|
|
1977
|
+
collection: "",
|
|
1978
|
+
shape: null,
|
|
1979
|
+
route: null,
|
|
1980
|
+
editing: null
|
|
1981
|
+
};
|
|
1982
|
+
const standard = new Set(config.standardRoutes);
|
|
1983
|
+
const banner = el("banner", HTMLElement);
|
|
1984
|
+
const keyInput = el("admin-key", HTMLInputElement);
|
|
1985
|
+
keyInput.value = state.key;
|
|
1986
|
+
const params = new URLSearchParams(location.search);
|
|
1987
|
+
const queryKey = params.get("key");
|
|
1988
|
+
if (queryKey) {
|
|
1989
|
+
state.key = queryKey;
|
|
1990
|
+
keyInput.value = state.key;
|
|
1991
|
+
sessionStorage.setItem("mockingbird-admin-key", state.key);
|
|
1992
|
+
params.delete("key");
|
|
1993
|
+
const next = params.toString();
|
|
1994
|
+
history.replaceState(null, "", location.pathname + (next ? `?${next}` : ""));
|
|
1995
|
+
}
|
|
1996
|
+
const queryNamespace = params.get("namespace");
|
|
1997
|
+
if (queryNamespace)
|
|
1998
|
+
state.namespace = queryNamespace;
|
|
1999
|
+
const showError = (error2) => {
|
|
2000
|
+
banner.textContent = error2 instanceof Error ? error2.message : String(error2);
|
|
2001
|
+
banner.classList.add("show");
|
|
2002
|
+
};
|
|
2003
|
+
const clearError = () => {
|
|
2004
|
+
banner.classList.remove("show");
|
|
2005
|
+
};
|
|
2006
|
+
const api = async (method, path, body) => {
|
|
2007
|
+
const headers = { accept: "application/json" };
|
|
2008
|
+
if (state.key !== "")
|
|
2009
|
+
headers[config.adminKeyHeader] = state.key;
|
|
2010
|
+
if (state.namespace !== "")
|
|
2011
|
+
headers["x-mockingbird-namespace"] = state.namespace;
|
|
2012
|
+
if (body !== void 0)
|
|
2013
|
+
headers["content-type"] = "application/json";
|
|
2014
|
+
const init = { method, headers };
|
|
2015
|
+
if (body !== void 0)
|
|
2016
|
+
init.body = JSON.stringify(body);
|
|
2017
|
+
const response = await fetch(config.adminPrefix + path, init);
|
|
2018
|
+
const text = await response.text();
|
|
2019
|
+
let data = null;
|
|
2020
|
+
if (text !== "") {
|
|
2021
|
+
try {
|
|
2022
|
+
data = readJson2(text);
|
|
2023
|
+
} catch {
|
|
2024
|
+
data = { raw: text };
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
if (response.status === 401) {
|
|
2028
|
+
keyInput.focus();
|
|
2029
|
+
throw new Error(errorMessage(data, "Admin key required"));
|
|
2030
|
+
}
|
|
2031
|
+
if (!response.ok) {
|
|
2032
|
+
throw new Error(errorMessage(data, `${response.status} ${method} ${path}`));
|
|
2033
|
+
}
|
|
2034
|
+
return data;
|
|
2035
|
+
};
|
|
2036
|
+
const originGet = async (path) => {
|
|
2037
|
+
const response = await fetch(path, { headers: { accept: "application/json" } });
|
|
2038
|
+
if (!response.ok)
|
|
2039
|
+
throw new Error(`${response.status} ${path}`);
|
|
2040
|
+
const value = await response.json();
|
|
2041
|
+
return value;
|
|
2042
|
+
};
|
|
2043
|
+
const panelApi = {
|
|
2044
|
+
get: (path) => api("GET", path),
|
|
2045
|
+
send: (method, path, body) => api(method, path, body),
|
|
2046
|
+
namespace: () => state.namespace
|
|
2047
|
+
};
|
|
2048
|
+
const isPanelScript = (value) => typeof value === "function";
|
|
2049
|
+
const setTheme = (theme) => {
|
|
2050
|
+
if (theme)
|
|
2051
|
+
document.documentElement.dataset.theme = theme;
|
|
2052
|
+
else
|
|
2053
|
+
delete document.documentElement.dataset.theme;
|
|
2054
|
+
};
|
|
2055
|
+
const storedTheme = localStorage.getItem("mockingbird-admin-theme");
|
|
2056
|
+
if (storedTheme)
|
|
2057
|
+
setTheme(storedTheme);
|
|
2058
|
+
el("theme", HTMLButtonElement).addEventListener("click", () => {
|
|
2059
|
+
const current = document.documentElement.dataset.theme;
|
|
2060
|
+
const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
|
|
2061
|
+
const next = dark ? "light" : "dark";
|
|
2062
|
+
localStorage.setItem("mockingbird-admin-theme", next);
|
|
2063
|
+
setTheme(next);
|
|
2064
|
+
});
|
|
2065
|
+
const show = (view) => {
|
|
2066
|
+
state.view = view;
|
|
2067
|
+
for (const button of document.querySelectorAll("#nav button")) {
|
|
2068
|
+
if (!(button instanceof HTMLButtonElement))
|
|
2069
|
+
continue;
|
|
2070
|
+
button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false");
|
|
2071
|
+
}
|
|
2072
|
+
for (const section of document.querySelectorAll("main .view")) {
|
|
2073
|
+
if (!(section instanceof HTMLElement))
|
|
2074
|
+
continue;
|
|
2075
|
+
section.hidden = section.id !== `view-${view}`;
|
|
2076
|
+
}
|
|
2077
|
+
};
|
|
2078
|
+
el("nav", HTMLElement).addEventListener("click", (event) => {
|
|
2079
|
+
const target = event.target;
|
|
2080
|
+
if (!(target instanceof Element))
|
|
2081
|
+
return;
|
|
2082
|
+
const button = target.closest("button[data-view]");
|
|
2083
|
+
if (!(button instanceof HTMLButtonElement))
|
|
2084
|
+
return;
|
|
2085
|
+
const view = button.dataset.view;
|
|
2086
|
+
if (view === void 0)
|
|
2087
|
+
return;
|
|
2088
|
+
show(view);
|
|
2089
|
+
refresh().catch(showError);
|
|
2090
|
+
});
|
|
2091
|
+
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>`;
|
|
2092
|
+
const table = (columns, rows) => {
|
|
2093
|
+
if (rows.length === 0)
|
|
2094
|
+
return '<p class="empty">Nothing here yet.</p>';
|
|
2095
|
+
const head = `<tr>${columns.map((column) => `<th>${column.label}</th>`).join("")}</tr>`;
|
|
2096
|
+
const body = rows.map((row) => {
|
|
2097
|
+
const cells = columns.map((column) => `<td data-label="${column.label}">${column.cell(row)}</td>`).join("");
|
|
2098
|
+
const attrs = "attrs" in row && typeof row.attrs === "string" ? row.attrs : "";
|
|
2099
|
+
return `<tr${attrs}>${cells}</tr>`;
|
|
2100
|
+
}).join("");
|
|
2101
|
+
return `<table><thead>${head}</thead><tbody>${body}</tbody></table>`;
|
|
2102
|
+
};
|
|
2103
|
+
const preview = (value) => {
|
|
2104
|
+
const text = JSON.stringify(value) ?? "null";
|
|
2105
|
+
return esc(text.length > 180 ? `${text.slice(0, 180)}\u2026` : text);
|
|
2106
|
+
};
|
|
2107
|
+
const when = (epoch) => {
|
|
2108
|
+
const date = new Date(epoch);
|
|
2109
|
+
return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString();
|
|
2110
|
+
};
|
|
2111
|
+
const clockCard = (epoch) => {
|
|
2112
|
+
const date = new Date(epoch);
|
|
2113
|
+
if (Number.isNaN(date.getTime()))
|
|
2114
|
+
return esc(String(epoch));
|
|
2115
|
+
const iso = date.toISOString();
|
|
2116
|
+
return `${esc(iso.slice(0, 10))}<span class="clock-time">${esc(iso.slice(11, 19))} UTC</span>`;
|
|
2117
|
+
};
|
|
2118
|
+
const decodeClock = (value) => {
|
|
2119
|
+
if (!isRecord2(value) || typeof value.now !== "number" || typeof value.offsetMs !== "number" || typeof value.frozen !== "boolean") {
|
|
2120
|
+
throw new Error("Clock response was not a clock");
|
|
2121
|
+
}
|
|
2122
|
+
return { now: value.now, offsetMs: value.offsetMs, frozen: value.frozen };
|
|
2123
|
+
};
|
|
2124
|
+
const decodeField = (value) => {
|
|
2125
|
+
if (!isRecord2(value) || typeof value.name !== "string" || !isFieldKind(value.kind)) {
|
|
2126
|
+
return void 0;
|
|
2127
|
+
}
|
|
2128
|
+
const field = {
|
|
2129
|
+
name: value.name,
|
|
2130
|
+
kind: value.kind,
|
|
2131
|
+
optional: value.optional === true
|
|
2132
|
+
};
|
|
2133
|
+
if (typeof value.description === "string")
|
|
2134
|
+
field.description = value.description;
|
|
2135
|
+
if (Array.isArray(value.fields)) {
|
|
2136
|
+
field.fields = value.fields.flatMap((item) => {
|
|
2137
|
+
const child = decodeField(item);
|
|
2138
|
+
return child === void 0 ? [] : [child];
|
|
2139
|
+
});
|
|
2140
|
+
}
|
|
2141
|
+
return field;
|
|
2142
|
+
};
|
|
2143
|
+
const decodeCollection = (value) => {
|
|
2144
|
+
if (!isRecord2(value) || typeof value.name !== "string")
|
|
2145
|
+
return void 0;
|
|
2146
|
+
const fields = Array.isArray(value.fields) ? value.fields.flatMap((item) => {
|
|
2147
|
+
const field = decodeField(item);
|
|
2148
|
+
return field === void 0 ? [] : [field];
|
|
2149
|
+
}) : [];
|
|
2150
|
+
const collection = {
|
|
2151
|
+
name: value.name,
|
|
2152
|
+
label: typeof value.label === "string" ? value.label : value.name,
|
|
2153
|
+
count: typeof value.count === "number" ? value.count : 0,
|
|
2154
|
+
source: isSource(value.source) ? value.source : "inferred",
|
|
2155
|
+
fields
|
|
2156
|
+
};
|
|
2157
|
+
if (typeof value.description === "string")
|
|
2158
|
+
collection.description = value.description;
|
|
2159
|
+
return collection;
|
|
2160
|
+
};
|
|
2161
|
+
const decodeState = (value) => {
|
|
2162
|
+
if (!isRecord2(value) || !Array.isArray(value.collections)) {
|
|
2163
|
+
throw new Error("State response has no collection list");
|
|
2164
|
+
}
|
|
2165
|
+
return {
|
|
2166
|
+
namespace: typeof value.namespace === "string" ? value.namespace : state.namespace,
|
|
2167
|
+
storageNamespace: typeof value.storageNamespace === "string" ? value.storageNamespace : "",
|
|
2168
|
+
collections: value.collections.flatMap((item) => {
|
|
2169
|
+
const collection = decodeCollection(item);
|
|
2170
|
+
return collection === void 0 ? [] : [collection];
|
|
2171
|
+
})
|
|
2172
|
+
};
|
|
2173
|
+
};
|
|
2174
|
+
const decodeRecords = (value) => requireList(isRecord2(value) ? value.records : void 0, "Records").flatMap((item) => {
|
|
2175
|
+
if (!isRecord2(item) || typeof item.id !== "string")
|
|
2176
|
+
return [];
|
|
2177
|
+
return [
|
|
2178
|
+
{
|
|
2179
|
+
id: item.id,
|
|
2180
|
+
seq: typeof item.seq === "number" ? item.seq : 0,
|
|
2181
|
+
value: item.value,
|
|
2182
|
+
attrs: ` class="clickable" data-id="${esc(item.id)}"`
|
|
2183
|
+
}
|
|
2184
|
+
];
|
|
2185
|
+
});
|
|
2186
|
+
const textOf = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : "";
|
|
2187
|
+
const decodeJournal = (value) => requireList(isRecord2(value) ? value.requests : void 0, "Journal").flatMap((item) => {
|
|
2188
|
+
if (!isRecord2(item))
|
|
2189
|
+
return [];
|
|
2190
|
+
return [
|
|
2191
|
+
{
|
|
2192
|
+
at: textOf(item.at),
|
|
2193
|
+
method: textOf(item.method),
|
|
2194
|
+
path: textOf(item.path),
|
|
2195
|
+
status: textOf(item.status),
|
|
2196
|
+
durationMs: textOf(item.durationMs),
|
|
2197
|
+
operationId: textOf(item.operationId)
|
|
2198
|
+
}
|
|
2199
|
+
];
|
|
2200
|
+
});
|
|
2201
|
+
const decodeFaults = (value) => requireList(isRecord2(value) ? value.faults : void 0, "Faults").flatMap((item) => {
|
|
2202
|
+
if (!isRecord2(item))
|
|
2203
|
+
return [];
|
|
2204
|
+
return [
|
|
2205
|
+
{
|
|
2206
|
+
id: textOf(item.id),
|
|
2207
|
+
method: typeof item.method === "string" ? item.method : void 0,
|
|
2208
|
+
pathPrefix: typeof item.pathPrefix === "string" ? item.pathPrefix : void 0,
|
|
2209
|
+
operationId: typeof item.operationId === "string" ? item.operationId : void 0,
|
|
2210
|
+
status: typeof item.status === "number" ? item.status : void 0,
|
|
2211
|
+
effect: typeof item.effect === "string" ? item.effect : void 0,
|
|
2212
|
+
drop: typeof item.drop === "boolean" ? item.drop : void 0,
|
|
2213
|
+
latencyMs: typeof item.latencyMs === "number" ? item.latencyMs : void 0
|
|
2214
|
+
}
|
|
2215
|
+
];
|
|
2216
|
+
});
|
|
2217
|
+
const loadNamespaces = async () => {
|
|
2218
|
+
const data = await api("GET", "/namespaces");
|
|
2219
|
+
const select = el("namespace", HTMLSelectElement);
|
|
2220
|
+
const listed = requireList(isRecord2(data) ? data.namespaces : void 0, "Namespaces");
|
|
2221
|
+
const fallback = isRecord2(data) && typeof data.default === "string" && data.default !== "" ? data.default : "default";
|
|
2222
|
+
const names = (listed.length > 0 ? listed.map((name) => String(name)) : [fallback]).slice();
|
|
2223
|
+
if (!names.includes(state.namespace))
|
|
2224
|
+
names.unshift(state.namespace);
|
|
2225
|
+
select.innerHTML = names.map((name) => `<option value="${esc(name)}">${esc(name)}</option>`).join("");
|
|
2226
|
+
select.value = state.namespace;
|
|
2227
|
+
};
|
|
2228
|
+
const renderOverview = async () => {
|
|
2229
|
+
const [health, shape, clock, journal] = await Promise.all([
|
|
2230
|
+
originGet("/health"),
|
|
2231
|
+
api("GET", "/state"),
|
|
2232
|
+
api("GET", "/clock"),
|
|
2233
|
+
api("GET", "/requests?limit=8")
|
|
2234
|
+
]);
|
|
2235
|
+
if (!isRecord2(health))
|
|
2236
|
+
throw new Error("Health response was not an object");
|
|
2237
|
+
const decoded = decodeState(shape);
|
|
2238
|
+
state.shape = decoded;
|
|
2239
|
+
const time = decodeClock(clock);
|
|
2240
|
+
el("overview-cards", HTMLElement).innerHTML = cards([
|
|
2241
|
+
{
|
|
2242
|
+
k: "Service",
|
|
2243
|
+
v: esc(typeof health.service === "string" && health.service !== "" ? health.service : config.service)
|
|
2244
|
+
},
|
|
2245
|
+
{
|
|
2246
|
+
k: "Status",
|
|
2247
|
+
v: esc(typeof health.status === "string" && health.status !== "" ? health.status : "ok")
|
|
2248
|
+
},
|
|
2249
|
+
{ k: "Namespace", v: esc(state.namespace) },
|
|
2250
|
+
{ k: "Collections", v: String(decoded.collections.length) },
|
|
2251
|
+
{
|
|
2252
|
+
k: "Records",
|
|
2253
|
+
v: String(decoded.collections.reduce((sum, item) => sum + item.count, 0))
|
|
2254
|
+
},
|
|
2255
|
+
{ k: "Clock", v: clockCard(time.now) }
|
|
2256
|
+
]);
|
|
2257
|
+
el("overview-requests", HTMLElement).innerHTML = table([
|
|
2258
|
+
{ label: "Method", cell: (row) => esc(row.method) },
|
|
2259
|
+
{ label: "Path", cell: (row) => esc(row.path) },
|
|
2260
|
+
{
|
|
2261
|
+
label: "Status",
|
|
2262
|
+
cell: (row) => `<span class="status-${row.status.charAt(0)}">${esc(row.status)}</span>`
|
|
2263
|
+
},
|
|
2264
|
+
{ label: "Operation", cell: (row) => esc(row.operationId) }
|
|
2265
|
+
], decodeJournal(journal));
|
|
2266
|
+
};
|
|
2267
|
+
const schemaChips = (collection) => {
|
|
2268
|
+
if (collection.fields.length === 0) {
|
|
2269
|
+
return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>';
|
|
2270
|
+
}
|
|
2271
|
+
return `<div class="chips">${collection.fields.map((field) => `<span class="chip">${esc(field.name)} <i>${esc(field.kind)}${field.optional ? "?" : ""}</i></span>`).join("")}</div>`;
|
|
2272
|
+
};
|
|
2273
|
+
const renderState = async () => {
|
|
2274
|
+
const shape = decodeState(await api("GET", "/state"));
|
|
2275
|
+
state.shape = shape;
|
|
2276
|
+
const host = el("collections", HTMLElement);
|
|
2277
|
+
if (shape.collections.length === 0) {
|
|
2278
|
+
host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>';
|
|
2279
|
+
el("schema", HTMLElement).innerHTML = "";
|
|
2280
|
+
el("records", HTMLElement).innerHTML = "";
|
|
2281
|
+
return;
|
|
2282
|
+
}
|
|
2283
|
+
if (!shape.collections.some((item) => item.name === state.collection)) {
|
|
2284
|
+
const first = shape.collections[0];
|
|
2285
|
+
if (first === void 0)
|
|
2286
|
+
return;
|
|
2287
|
+
state.collection = first.name;
|
|
2288
|
+
}
|
|
2289
|
+
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("");
|
|
2290
|
+
const selected = shape.collections.find((item) => item.name === state.collection);
|
|
2291
|
+
if (selected === void 0)
|
|
2292
|
+
return;
|
|
2293
|
+
const description = selected.description ? `<p class="lede">${esc(selected.description)}</p>` : "";
|
|
2294
|
+
el("schema", HTMLElement).innerHTML = `<h3>${esc(selected.label)}</h3>${description}${schemaChips(selected)}`;
|
|
2295
|
+
const page = await api("GET", `/state/${encodeURIComponent(state.collection)}?limit=50`);
|
|
2296
|
+
el("records", HTMLElement).innerHTML = table([
|
|
2297
|
+
{ label: "Id", cell: (row) => `<code>${esc(row.id)}</code>` },
|
|
2298
|
+
{ label: "Seq", cell: (row) => esc(row.seq) },
|
|
2299
|
+
{ label: "Value", cell: (row) => preview(row.value) }
|
|
2300
|
+
], decodeRecords(page));
|
|
2301
|
+
};
|
|
2302
|
+
const renderClock = async () => {
|
|
2303
|
+
const clock = decodeClock(await api("GET", "/clock"));
|
|
2304
|
+
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>`;
|
|
2305
|
+
const freeze = el("freeze", HTMLButtonElement);
|
|
2306
|
+
freeze.textContent = clock.frozen ? "Unfreeze" : "Freeze";
|
|
2307
|
+
freeze.dataset.frozen = clock.frozen ? "1" : "0";
|
|
2308
|
+
};
|
|
2309
|
+
const renderFaults = async () => {
|
|
2310
|
+
const [faults, presets] = await Promise.all([
|
|
2311
|
+
api("GET", "/faults"),
|
|
2312
|
+
api("GET", "/faults/presets").catch(() => ({ presets: [] }))
|
|
2313
|
+
]);
|
|
2314
|
+
const select = el("fault-preset", HTMLSelectElement);
|
|
2315
|
+
const current = select.value;
|
|
2316
|
+
const list = faultPresetList(presets).presets;
|
|
2317
|
+
select.innerHTML = `<option value="">Custom rule</option>${list.map((preset) => `<option value="${esc(preset.name)}">${esc(preset.name)}</option>`).join("")}`;
|
|
2318
|
+
select.value = current;
|
|
2319
|
+
el("fault-list", HTMLElement).innerHTML = table([
|
|
2320
|
+
{ label: "Id", cell: (row) => esc(row.id) },
|
|
2321
|
+
{
|
|
2322
|
+
label: "Match",
|
|
2323
|
+
cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter((part) => Boolean(part)).join(" ") || "all")
|
|
2324
|
+
},
|
|
2325
|
+
{
|
|
2326
|
+
label: "Effect",
|
|
2327
|
+
cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || ""))
|
|
2328
|
+
}
|
|
2329
|
+
], decodeFaults(faults));
|
|
2330
|
+
};
|
|
2331
|
+
const renderJournal = async () => {
|
|
2332
|
+
const journal = await api("GET", "/requests?limit=100");
|
|
2333
|
+
el("journal", HTMLElement).innerHTML = table([
|
|
2334
|
+
{ label: "When", cell: (row) => esc(row.at) },
|
|
2335
|
+
{ label: "Method", cell: (row) => esc(row.method) },
|
|
2336
|
+
{ label: "Path", cell: (row) => esc(row.path) },
|
|
2337
|
+
{ label: "Status", cell: (row) => esc(row.status) },
|
|
2338
|
+
{ label: "ms", cell: (row) => esc(row.durationMs) }
|
|
2339
|
+
], decodeJournal(journal));
|
|
2340
|
+
};
|
|
2341
|
+
const renderRoutes = async () => {
|
|
2342
|
+
const data = await api("GET", "/");
|
|
2343
|
+
if (!isRecord2(data))
|
|
2344
|
+
throw new Error("Route list was not an object");
|
|
2345
|
+
const routes = requireList(data.routes, "Routes").map((route) => String(route));
|
|
2346
|
+
el("route-list", HTMLElement).innerHTML = routes.map((route) => {
|
|
2347
|
+
const extra = standard.has(route) ? "" : " <span class='badge'>extension</span>";
|
|
2348
|
+
const parts = route.split(" ");
|
|
2349
|
+
const method = parts[0] ?? "";
|
|
2350
|
+
const path = parts.slice(1).join(" ");
|
|
2351
|
+
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>`;
|
|
2352
|
+
}).join("");
|
|
2353
|
+
};
|
|
2354
|
+
const renderers = {
|
|
2355
|
+
overview: renderOverview,
|
|
2356
|
+
state: renderState,
|
|
2357
|
+
clock: renderClock,
|
|
2358
|
+
faults: renderFaults,
|
|
2359
|
+
journal: renderJournal,
|
|
2360
|
+
routes: renderRoutes
|
|
2361
|
+
};
|
|
2362
|
+
async function refresh() {
|
|
2363
|
+
clearError();
|
|
2364
|
+
const render = renderers[state.view];
|
|
2365
|
+
if (render)
|
|
2366
|
+
await render();
|
|
2367
|
+
}
|
|
2368
|
+
el("namespace", HTMLSelectElement).addEventListener("change", () => {
|
|
2369
|
+
state.namespace = el("namespace", HTMLSelectElement).value;
|
|
2370
|
+
refresh().catch(showError);
|
|
2371
|
+
});
|
|
2372
|
+
keyInput.addEventListener("change", () => {
|
|
2373
|
+
state.key = keyInput.value.trim();
|
|
2374
|
+
sessionStorage.setItem("mockingbird-admin-key", state.key);
|
|
2375
|
+
refresh().catch(showError);
|
|
2376
|
+
});
|
|
2377
|
+
el("refresh", HTMLButtonElement).addEventListener("click", () => {
|
|
2378
|
+
refresh().catch(showError);
|
|
2379
|
+
});
|
|
2380
|
+
el("collections", HTMLElement).addEventListener("click", (event) => {
|
|
2381
|
+
const target = event.target;
|
|
2382
|
+
if (!(target instanceof Element))
|
|
2383
|
+
return;
|
|
2384
|
+
const button = target.closest("[data-collection]");
|
|
2385
|
+
if (!(button instanceof HTMLElement))
|
|
2386
|
+
return;
|
|
2387
|
+
const collection = button.dataset.collection;
|
|
2388
|
+
if (collection === void 0)
|
|
2389
|
+
return;
|
|
2390
|
+
state.collection = collection;
|
|
2391
|
+
renderState().catch(showError);
|
|
2392
|
+
});
|
|
2393
|
+
el("records", HTMLElement).addEventListener("click", (event) => {
|
|
2394
|
+
const target = event.target;
|
|
2395
|
+
if (!(target instanceof Element))
|
|
2396
|
+
return;
|
|
2397
|
+
const row = target.closest("[data-id]");
|
|
2398
|
+
if (!(row instanceof HTMLElement))
|
|
2399
|
+
return;
|
|
2400
|
+
const id = row.dataset.id;
|
|
2401
|
+
if (id === void 0)
|
|
2402
|
+
return;
|
|
2403
|
+
openEditor(id).catch(showError);
|
|
2404
|
+
});
|
|
2405
|
+
const editor = el("editor", HTMLDialogElement);
|
|
2406
|
+
const editorId = el("editor-id", HTMLInputElement);
|
|
2407
|
+
const editorValue = el("editor-value", HTMLTextAreaElement);
|
|
2408
|
+
const editorDelete = el("editor-delete", HTMLButtonElement);
|
|
2409
|
+
async function openEditor(id) {
|
|
2410
|
+
state.editing = id;
|
|
2411
|
+
el("editor-title", HTMLElement).textContent = id ? "Edit record" : "New record";
|
|
2412
|
+
editorId.value = id ?? "";
|
|
2413
|
+
editorId.readOnly = id !== null;
|
|
2414
|
+
editorDelete.hidden = id === null;
|
|
2415
|
+
if (id) {
|
|
2416
|
+
const record = await api("GET", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`);
|
|
2417
|
+
if (!isRecord2(record))
|
|
2418
|
+
throw new Error("Record response was not an object");
|
|
2419
|
+
editorValue.value = JSON.stringify(record.value, null, 2) ?? "";
|
|
2420
|
+
} else {
|
|
2421
|
+
editorValue.value = "{\n \n}";
|
|
2422
|
+
}
|
|
2423
|
+
editor.showModal();
|
|
2424
|
+
}
|
|
2425
|
+
el("new-record", HTMLButtonElement).addEventListener("click", () => {
|
|
2426
|
+
openEditor(null).catch(showError);
|
|
2427
|
+
});
|
|
2428
|
+
el("editor-form", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2429
|
+
const submitter = event.submitter;
|
|
2430
|
+
if (!(submitter instanceof HTMLButtonElement) || submitter.value !== "save")
|
|
2431
|
+
return;
|
|
2432
|
+
event.preventDefault();
|
|
2433
|
+
let value;
|
|
2434
|
+
try {
|
|
2435
|
+
value = readJson2(editorValue.value);
|
|
2436
|
+
} catch {
|
|
2437
|
+
showError(new Error("Value is not JSON"));
|
|
2438
|
+
return;
|
|
2439
|
+
}
|
|
2440
|
+
const id = editorId.value.trim();
|
|
2441
|
+
const path = `/state/${encodeURIComponent(state.collection)}`;
|
|
2442
|
+
const request = state.editing ? api("PUT", `${path}/${encodeURIComponent(id)}`, { value }) : api("POST", path, { id: id || void 0, value });
|
|
2443
|
+
request.then(() => {
|
|
2444
|
+
editor.close();
|
|
2445
|
+
return renderState();
|
|
2446
|
+
}).catch(showError);
|
|
2447
|
+
});
|
|
2448
|
+
editorDelete.addEventListener("click", () => {
|
|
2449
|
+
const id = state.editing;
|
|
2450
|
+
if (!id)
|
|
2451
|
+
return;
|
|
2452
|
+
api("DELETE", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`).then(() => {
|
|
2453
|
+
editor.close();
|
|
2454
|
+
return renderState();
|
|
2455
|
+
}).catch(showError);
|
|
2456
|
+
});
|
|
2457
|
+
for (const button of document.querySelectorAll("[data-advance]")) {
|
|
2458
|
+
if (!(button instanceof HTMLButtonElement))
|
|
2459
|
+
continue;
|
|
2460
|
+
button.addEventListener("click", () => {
|
|
2461
|
+
const raw = button.dataset.advance;
|
|
2462
|
+
if (raw === void 0)
|
|
2463
|
+
return;
|
|
2464
|
+
api("POST", "/clock", { advance: Number(raw) }).then(() => renderClock()).catch(showError);
|
|
2465
|
+
});
|
|
2466
|
+
}
|
|
2467
|
+
el("freeze", HTMLButtonElement).addEventListener("click", () => {
|
|
2468
|
+
const frozen = el("freeze", HTMLButtonElement).dataset.frozen === "1";
|
|
2469
|
+
api("POST", "/clock", { freeze: !frozen }).then(() => renderClock()).catch(showError);
|
|
2470
|
+
});
|
|
2471
|
+
el("clock-reset", HTMLButtonElement).addEventListener("click", () => {
|
|
2472
|
+
api("POST", "/clock", { reset: true }).then(() => renderClock()).catch(showError);
|
|
2473
|
+
});
|
|
2474
|
+
el("clock-set", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2475
|
+
event.preventDefault();
|
|
2476
|
+
const raw = el("clock-instant", HTMLInputElement).value.trim();
|
|
2477
|
+
const set = /^-?\d+$/.test(raw) ? Number(raw) : raw;
|
|
2478
|
+
api("POST", "/clock", { set }).then(() => renderClock()).catch(showError);
|
|
2479
|
+
});
|
|
2480
|
+
el("fault-form", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2481
|
+
event.preventDefault();
|
|
2482
|
+
const preset = el("fault-preset", HTMLSelectElement).value;
|
|
2483
|
+
if (preset !== "") {
|
|
2484
|
+
api("POST", "/faults", { preset }).then(() => renderFaults()).catch(showError);
|
|
2485
|
+
return;
|
|
2486
|
+
}
|
|
2487
|
+
const status = Number(el("fault-status", HTMLInputElement).value);
|
|
2488
|
+
if (!Number.isFinite(status)) {
|
|
2489
|
+
showError(new Error("Status must be a number, or choose a preset"));
|
|
2490
|
+
return;
|
|
2491
|
+
}
|
|
2492
|
+
const path = el("fault-path", HTMLInputElement).value;
|
|
2493
|
+
const method = el("fault-method", HTMLInputElement).value;
|
|
2494
|
+
api("POST", "/faults", {
|
|
2495
|
+
status,
|
|
2496
|
+
...path !== "" ? { pathPrefix: path } : {},
|
|
2497
|
+
...method !== "" ? { method } : {}
|
|
2498
|
+
}).then(() => renderFaults()).catch(showError);
|
|
2499
|
+
});
|
|
2500
|
+
el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
|
|
2501
|
+
api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
|
|
2502
|
+
});
|
|
2503
|
+
el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
|
|
2504
|
+
api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
|
|
2505
|
+
});
|
|
2506
|
+
el("route-list", HTMLElement).addEventListener("click", (event) => {
|
|
2507
|
+
const target = event.target;
|
|
2508
|
+
if (!(target instanceof Element))
|
|
2509
|
+
return;
|
|
2510
|
+
const button = target.closest("[data-route]");
|
|
2511
|
+
if (!(button instanceof HTMLElement))
|
|
2512
|
+
return;
|
|
2513
|
+
const route = button.dataset.route;
|
|
2514
|
+
if (route === void 0)
|
|
2515
|
+
return;
|
|
2516
|
+
state.route = route;
|
|
2517
|
+
el("route-title", HTMLElement).textContent = route;
|
|
2518
|
+
const method = route.split(" ")[0] ?? "";
|
|
2519
|
+
el("route-body", HTMLTextAreaElement).disabled = method === "GET" || method === "DELETE";
|
|
2520
|
+
});
|
|
2521
|
+
el("route-form", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2522
|
+
event.preventDefault();
|
|
2523
|
+
const send = async () => {
|
|
2524
|
+
if (state.route === null)
|
|
2525
|
+
throw new Error("Choose a route first");
|
|
2526
|
+
const parts = state.route.split(" ");
|
|
2527
|
+
const method = parts[0];
|
|
2528
|
+
const path = parts.slice(1).join(" ");
|
|
2529
|
+
if (method === void 0)
|
|
2530
|
+
throw new Error("Choose a route first");
|
|
2531
|
+
const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_match, key) => prompt(`Value for ${key}`) || "");
|
|
2532
|
+
let body;
|
|
2533
|
+
const routeBody = el("route-body", HTMLTextAreaElement);
|
|
2534
|
+
if (method !== "GET" && method !== "DELETE" && routeBody.value.trim() !== "") {
|
|
2535
|
+
try {
|
|
2536
|
+
body = readJson2(routeBody.value);
|
|
2537
|
+
} catch {
|
|
2538
|
+
throw new Error("Body is not JSON");
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
const data = await api(method, concrete, body);
|
|
2542
|
+
el("route-result", HTMLElement).textContent = JSON.stringify(data, null, 2);
|
|
2543
|
+
};
|
|
2544
|
+
send().catch(showError);
|
|
2545
|
+
});
|
|
2546
|
+
const decodePanel = (value) => {
|
|
2547
|
+
if (!isRecord2(value) || typeof value.id !== "string" || typeof value.title !== "string") {
|
|
2548
|
+
return void 0;
|
|
2549
|
+
}
|
|
2550
|
+
const panel = {
|
|
2551
|
+
id: value.id,
|
|
2552
|
+
title: value.title,
|
|
2553
|
+
description: typeof value.description === "string" ? value.description : "",
|
|
2554
|
+
html: typeof value.html === "string" ? value.html : ""
|
|
2555
|
+
};
|
|
2556
|
+
if (typeof value.script === "string")
|
|
2557
|
+
panel.script = value.script;
|
|
2558
|
+
return panel;
|
|
2559
|
+
};
|
|
2560
|
+
const mountPanels = (panels) => {
|
|
2561
|
+
const nav = el("nav", HTMLElement);
|
|
2562
|
+
const views = el("views", HTMLElement);
|
|
2563
|
+
for (const panel of panels) {
|
|
2564
|
+
const button = document.createElement("button");
|
|
2565
|
+
button.type = "button";
|
|
2566
|
+
button.dataset.view = panel.id;
|
|
2567
|
+
button.textContent = panel.title;
|
|
2568
|
+
nav.append(button);
|
|
2569
|
+
const section = document.createElement("section");
|
|
2570
|
+
section.className = "view";
|
|
2571
|
+
section.id = `view-${panel.id}`;
|
|
2572
|
+
section.hidden = true;
|
|
2573
|
+
const title = document.createElement("h2");
|
|
2574
|
+
title.textContent = panel.title;
|
|
2575
|
+
const lede = document.createElement("p");
|
|
2576
|
+
lede.className = "lede";
|
|
2577
|
+
lede.textContent = panel.description;
|
|
2578
|
+
const body = document.createElement("div");
|
|
2579
|
+
body.className = "panel-body";
|
|
2580
|
+
body.id = `panel-${panel.id}`;
|
|
2581
|
+
body.innerHTML = panel.html;
|
|
2582
|
+
section.append(title, lede, body);
|
|
2583
|
+
views.append(section);
|
|
2584
|
+
if (panel.script === void 0)
|
|
2585
|
+
continue;
|
|
2586
|
+
try {
|
|
2587
|
+
const compiled = new Function("root", "api", panel.script);
|
|
2588
|
+
if (!isPanelScript(compiled))
|
|
2589
|
+
throw new Error("panel script did not compile");
|
|
2590
|
+
compiled(body, panelApi);
|
|
2591
|
+
} catch (error2) {
|
|
2592
|
+
const note = document.createElement("p");
|
|
2593
|
+
note.className = "banner show";
|
|
2594
|
+
note.textContent = error2 instanceof Error ? error2.message : String(error2);
|
|
2595
|
+
body.append(note);
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
};
|
|
2599
|
+
startAdminBrand({
|
|
2600
|
+
document,
|
|
2601
|
+
$: (id) => document.getElementById(id),
|
|
2602
|
+
params,
|
|
2603
|
+
location: { href: location.href, origin: location.origin },
|
|
2604
|
+
fetch,
|
|
2605
|
+
brandsUrl: config.brandsUrl
|
|
2606
|
+
});
|
|
2607
|
+
loadNamespaces().then(() => api("GET", "/ui/manifest")).then((manifest) => {
|
|
2608
|
+
const panels = requireList(isRecord2(manifest) ? manifest.panels : void 0, "Panels").flatMap((panel) => {
|
|
2609
|
+
const decoded = decodePanel(panel);
|
|
2610
|
+
return decoded === void 0 ? [] : [decoded];
|
|
2611
|
+
});
|
|
2612
|
+
mountPanels(panels);
|
|
2613
|
+
return refresh();
|
|
2614
|
+
}).catch(showError);
|
|
2615
|
+
}
|
|
2616
|
+
var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
|
|
2617
|
+
var adminClientSource = (config) => {
|
|
2618
|
+
const payload = JSON.stringify(config).replace(/</g, "\\u003c");
|
|
2619
|
+
return `<script>
|
|
2620
|
+
${clientPrelude}
|
|
2621
|
+
bootAdmin(${payload});
|
|
2622
|
+
</script>`;
|
|
2623
|
+
};
|
|
2624
|
+
|
|
1399
2625
|
// ../core/dist/surface.js
|
|
1400
2626
|
var defineMock = (runtime) => runtime;
|
|
1401
2627
|
var STANDARD_ADMIN_ROUTES = [
|
|
@@ -1464,94 +2690,106 @@ var escapeHtml = (value) => value.replace(/[&<>"']/g, (char) => {
|
|
|
1464
2690
|
return "'";
|
|
1465
2691
|
}
|
|
1466
2692
|
});
|
|
2693
|
+
var ADMIN_BRANDS_URL = "https://mockingbird.chrisvouga.dev/brands.json";
|
|
1467
2694
|
var renderAdminDocument = (service) => {
|
|
1468
2695
|
const name = escapeHtml(service);
|
|
1469
|
-
const
|
|
2696
|
+
const script = adminClientSource({
|
|
2697
|
+
standardRoutes: STANDARD_ADMIN_ROUTES,
|
|
2698
|
+
adminPrefix: ADMIN_PREFIX,
|
|
2699
|
+
adminKeyHeader: ADMIN_KEY_HEADER,
|
|
2700
|
+
brandsUrl: ADMIN_BRANDS_URL,
|
|
2701
|
+
service
|
|
2702
|
+
});
|
|
1470
2703
|
return `<!DOCTYPE html>
|
|
1471
2704
|
<html lang="en">
|
|
1472
2705
|
<head>
|
|
1473
2706
|
<meta charset="utf-8">
|
|
1474
2707
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1475
2708
|
<meta name="color-scheme" content="light dark">
|
|
1476
|
-
<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='
|
|
2709
|
+
<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">
|
|
1477
2710
|
<title>${name} admin</title>
|
|
1478
2711
|
<style>
|
|
2712
|
+
${CSS_RESET}
|
|
1479
2713
|
:root {
|
|
1480
2714
|
color-scheme: light;
|
|
1481
|
-
--bg: #
|
|
2715
|
+
--bg: #fafafa;
|
|
1482
2716
|
--bg-elev: #ffffff;
|
|
1483
|
-
--bg-muted: #
|
|
1484
|
-
--bg-inset: #
|
|
1485
|
-
--line: #
|
|
1486
|
-
--line-strong: #
|
|
1487
|
-
--ink: #
|
|
1488
|
-
--muted: #
|
|
1489
|
-
--faint: #
|
|
1490
|
-
--accent: #
|
|
1491
|
-
--accent-ink: #
|
|
1492
|
-
--accent-soft: #
|
|
1493
|
-
--ok: #
|
|
1494
|
-
--ok-soft: #
|
|
1495
|
-
--warn: #
|
|
1496
|
-
--warn-soft: #
|
|
1497
|
-
--err: #
|
|
1498
|
-
--err-soft: #
|
|
1499
|
-
--shadow:
|
|
1500
|
-
--sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
2717
|
+
--bg-muted: #f4f4f5;
|
|
2718
|
+
--bg-inset: #f4f4f5;
|
|
2719
|
+
--line: #dedee3;
|
|
2720
|
+
--line-strong: #c4c4cc;
|
|
2721
|
+
--ink: #18181b;
|
|
2722
|
+
--muted: #62626b;
|
|
2723
|
+
--faint: #71717a;
|
|
2724
|
+
--accent: #27272a;
|
|
2725
|
+
--accent-ink: #ffffff;
|
|
2726
|
+
--accent-soft: #f4f4f5;
|
|
2727
|
+
--ok: #166534;
|
|
2728
|
+
--ok-soft: #f0fdf4;
|
|
2729
|
+
--warn: #a16207;
|
|
2730
|
+
--warn-soft: #fefce8;
|
|
2731
|
+
--err: #a82d32;
|
|
2732
|
+
--err-soft: #fef2f2;
|
|
2733
|
+
--shadow: none;
|
|
2734
|
+
--sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
1501
2735
|
--mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
|
|
1502
|
-
--radius:
|
|
1503
|
-
--header:
|
|
1504
|
-
--nav:
|
|
2736
|
+
--radius: 10px;
|
|
2737
|
+
--header: 56px;
|
|
2738
|
+
--nav: 200px;
|
|
1505
2739
|
}
|
|
1506
2740
|
:root[data-theme="dark"] {
|
|
1507
2741
|
color-scheme: dark;
|
|
1508
|
-
--bg: #
|
|
1509
|
-
--bg-elev: #
|
|
1510
|
-
--bg-muted: #
|
|
1511
|
-
--bg-inset: #
|
|
1512
|
-
--line: #
|
|
1513
|
-
--line-strong: #
|
|
1514
|
-
--ink: #
|
|
1515
|
-
--muted: #
|
|
1516
|
-
--faint: #
|
|
1517
|
-
--accent: #
|
|
1518
|
-
--accent-ink: #
|
|
1519
|
-
--accent-soft: #
|
|
1520
|
-
--ok: #
|
|
1521
|
-
--ok-soft: #
|
|
1522
|
-
--warn: #
|
|
1523
|
-
--warn-soft: #
|
|
1524
|
-
--err: #
|
|
1525
|
-
--err-soft: #
|
|
1526
|
-
--shadow:
|
|
2742
|
+
--bg: #111113;
|
|
2743
|
+
--bg-elev: #19191c;
|
|
2744
|
+
--bg-muted: #242428;
|
|
2745
|
+
--bg-inset: #111113;
|
|
2746
|
+
--line: #36363c;
|
|
2747
|
+
--line-strong: #52525b;
|
|
2748
|
+
--ink: #f4f4f5;
|
|
2749
|
+
--muted: #a9a9b2;
|
|
2750
|
+
--faint: #71717a;
|
|
2751
|
+
--accent: #e4e4e7;
|
|
2752
|
+
--accent-ink: #18181b;
|
|
2753
|
+
--accent-soft: #242428;
|
|
2754
|
+
--ok: #86efac;
|
|
2755
|
+
--ok-soft: #14241b;
|
|
2756
|
+
--warn: #fde68a;
|
|
2757
|
+
--warn-soft: #2a2410;
|
|
2758
|
+
--err: #ffaaaa;
|
|
2759
|
+
--err-soft: #2c1516;
|
|
2760
|
+
--shadow: none;
|
|
1527
2761
|
}
|
|
1528
2762
|
@media (prefers-color-scheme: dark) {
|
|
1529
2763
|
:root:not([data-theme="light"]) {
|
|
1530
2764
|
color-scheme: dark;
|
|
1531
|
-
--bg: #
|
|
1532
|
-
--bg-elev: #
|
|
1533
|
-
--bg-muted: #
|
|
1534
|
-
--bg-inset: #
|
|
1535
|
-
--line: #
|
|
1536
|
-
--line-strong: #
|
|
1537
|
-
--ink: #
|
|
1538
|
-
--muted: #
|
|
1539
|
-
--faint: #
|
|
1540
|
-
--accent: #
|
|
1541
|
-
--accent-ink: #
|
|
1542
|
-
--accent-soft: #
|
|
1543
|
-
--ok: #
|
|
1544
|
-
--ok-soft: #
|
|
1545
|
-
--warn: #
|
|
1546
|
-
--warn-soft: #
|
|
1547
|
-
--err: #
|
|
1548
|
-
--err-soft: #
|
|
1549
|
-
--shadow:
|
|
2765
|
+
--bg: #111113;
|
|
2766
|
+
--bg-elev: #19191c;
|
|
2767
|
+
--bg-muted: #242428;
|
|
2768
|
+
--bg-inset: #111113;
|
|
2769
|
+
--line: #36363c;
|
|
2770
|
+
--line-strong: #52525b;
|
|
2771
|
+
--ink: #f4f4f5;
|
|
2772
|
+
--muted: #a9a9b2;
|
|
2773
|
+
--faint: #71717a;
|
|
2774
|
+
--accent: #e4e4e7;
|
|
2775
|
+
--accent-ink: #18181b;
|
|
2776
|
+
--accent-soft: #242428;
|
|
2777
|
+
--ok: #86efac;
|
|
2778
|
+
--ok-soft: #14241b;
|
|
2779
|
+
--warn: #fde68a;
|
|
2780
|
+
--warn-soft: #2a2410;
|
|
2781
|
+
--err: #ffaaaa;
|
|
2782
|
+
--err-soft: #2c1516;
|
|
2783
|
+
--shadow: none;
|
|
1550
2784
|
}
|
|
1551
2785
|
}
|
|
1552
|
-
|
|
1553
|
-
html
|
|
2786
|
+
/* Fill the viewport, then grow with the content so the background covers the scroll. */
|
|
2787
|
+
html { height: 100%; }
|
|
1554
2788
|
body {
|
|
2789
|
+
margin: 0;
|
|
2790
|
+
min-height: 100%;
|
|
2791
|
+
display: flex;
|
|
2792
|
+
flex-direction: column;
|
|
1555
2793
|
font-family: var(--sans);
|
|
1556
2794
|
background: var(--bg);
|
|
1557
2795
|
color: var(--ink);
|
|
@@ -1560,24 +2798,37 @@ var renderAdminDocument = (service) => {
|
|
|
1560
2798
|
padding-right: env(safe-area-inset-right);
|
|
1561
2799
|
-webkit-text-size-adjust: 100%;
|
|
1562
2800
|
}
|
|
1563
|
-
button, input, select, textarea { font: inherit; color: inherit; }
|
|
1564
|
-
button { cursor: pointer; }
|
|
1565
2801
|
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
1566
|
-
|
|
1567
|
-
* { scroll-behavior: auto !important; transition: none !important; }
|
|
1568
|
-
}
|
|
1569
|
-
.app { min-height: 100%; display: flex; flex-direction: column; }
|
|
2802
|
+
.app { flex: 1 0 auto; display: flex; flex-direction: column; min-width: 0; width: 100%; }
|
|
1570
2803
|
header.top {
|
|
1571
2804
|
position: sticky; top: 0; z-index: 5;
|
|
1572
2805
|
display: flex; align-items: center; gap: 12px;
|
|
1573
2806
|
min-height: var(--header); padding: 10px 16px;
|
|
1574
|
-
background:
|
|
2807
|
+
background: var(--bg-elev);
|
|
1575
2808
|
border-bottom: 1px solid var(--line);
|
|
1576
|
-
|
|
1577
|
-
}
|
|
1578
|
-
.brand {
|
|
1579
|
-
|
|
1580
|
-
|
|
2809
|
+
min-width: 0; max-width: 100%;
|
|
2810
|
+
}
|
|
2811
|
+
.brand {
|
|
2812
|
+
display: flex; flex-flow: row wrap; align-items: center; gap: 14px;
|
|
2813
|
+
min-width: 0; max-width: 100%; margin-right: auto;
|
|
2814
|
+
}
|
|
2815
|
+
.brand-id { display: flex; flex-direction: column; min-width: 0; flex: none; }
|
|
2816
|
+
.brand-id strong { font-size: 15px; letter-spacing: -0.01em; }
|
|
2817
|
+
.brand-id span { color: var(--faint); font-size: 12px; }
|
|
2818
|
+
.vendor {
|
|
2819
|
+
display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
|
|
2820
|
+
min-width: 0; max-width: 100%; font-size: 12px; color: var(--muted);
|
|
2821
|
+
}
|
|
2822
|
+
.vendor[hidden] { display: none; }
|
|
2823
|
+
/* Logos are drawn for a light tile, same as the docs site, so the plate stays white. */
|
|
2824
|
+
.vendor img {
|
|
2825
|
+
width: 16px; height: 16px; padding: 2px; box-sizing: content-box;
|
|
2826
|
+
border-radius: 4px; border: 1px solid var(--line); background: #fff; object-fit: contain;
|
|
2827
|
+
}
|
|
2828
|
+
.vendor-name { color: var(--ink); font-weight: 600; }
|
|
2829
|
+
.vendor-sep { color: var(--faint); }
|
|
2830
|
+
.vendor-link { color: inherit; text-decoration: none; }
|
|
2831
|
+
.vendor-link:hover { color: var(--ink); text-decoration: underline; }
|
|
1581
2832
|
.controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
|
1582
2833
|
label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
|
|
1583
2834
|
label.field input, label.field select, .controls input[type="password"] {
|
|
@@ -1595,7 +2846,7 @@ var renderAdminDocument = (service) => {
|
|
|
1595
2846
|
record or the full nav row would widen the page past the viewport. */
|
|
1596
2847
|
.layout {
|
|
1597
2848
|
display: grid; grid-template-columns: var(--nav) minmax(0, 1fr);
|
|
1598
|
-
flex: 1
|
|
2849
|
+
flex: 1 0 auto; min-width: 0;
|
|
1599
2850
|
}
|
|
1600
2851
|
nav.side {
|
|
1601
2852
|
border-right: 1px solid var(--line); padding: 12px;
|
|
@@ -1610,7 +2861,7 @@ var renderAdminDocument = (service) => {
|
|
|
1610
2861
|
main { padding: 20px; max-width: 1100px; width: 100%; min-width: 0; }
|
|
1611
2862
|
.view h2 { margin: 0 0 4px; font-size: 22px; letter-spacing: -0.02em; }
|
|
1612
2863
|
.lede { margin: 0 0 16px; color: var(--muted); }
|
|
1613
|
-
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
|
|
2864
|
+
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr)); gap: 12px; }
|
|
1614
2865
|
.card {
|
|
1615
2866
|
background: var(--bg-elev); border: 1px solid var(--line); border-radius: var(--radius);
|
|
1616
2867
|
padding: 14px 16px; box-shadow: var(--shadow);
|
|
@@ -1649,8 +2900,8 @@ var renderAdminDocument = (service) => {
|
|
|
1649
2900
|
.banner.show { display: block; }
|
|
1650
2901
|
.empty { color: var(--muted); padding: 24px 8px; }
|
|
1651
2902
|
dialog {
|
|
1652
|
-
border: 1px solid var(--line); border-radius:
|
|
1653
|
-
color: var(--ink); width: min(640px, calc(
|
|
2903
|
+
border: 1px solid var(--line); border-radius: var(--radius); padding: 0; background: var(--bg-elev);
|
|
2904
|
+
color: var(--ink); width: min(640px, calc(100% - 24px)); max-width: calc(100vw - 24px); box-shadow: none;
|
|
1654
2905
|
}
|
|
1655
2906
|
dialog::backdrop { background: rgb(20 19 17 / 0.45); }
|
|
1656
2907
|
dialog form, .dialog-body { padding: 16px; display: flex; flex-direction: column; gap: 10px; }
|
|
@@ -1677,7 +2928,7 @@ var renderAdminDocument = (service) => {
|
|
|
1677
2928
|
padding-top: max(10px, env(safe-area-inset-top));
|
|
1678
2929
|
}
|
|
1679
2930
|
input, select, textarea { font-size: 16px; }
|
|
1680
|
-
.controls { width: 100%; }
|
|
2931
|
+
.brand, .vendor, .controls { width: 100%; }
|
|
1681
2932
|
label.field, label.field select, label.field input { flex: 1; min-width: 0; }
|
|
1682
2933
|
.layout { grid-template-columns: minmax(0, 1fr); }
|
|
1683
2934
|
nav.side {
|
|
@@ -1708,8 +2959,11 @@ var renderAdminDocument = (service) => {
|
|
|
1708
2959
|
<div class="app" data-service="${name}" data-mockingbird-admin>
|
|
1709
2960
|
<header class="top">
|
|
1710
2961
|
<div class="brand">
|
|
1711
|
-
<
|
|
1712
|
-
|
|
2962
|
+
<div class="brand-id">
|
|
2963
|
+
<strong>${name}</strong>
|
|
2964
|
+
<span>Admin</span>
|
|
2965
|
+
</div>
|
|
2966
|
+
<div class="vendor" id="vendor" hidden></div>
|
|
1713
2967
|
</div>
|
|
1714
2968
|
<div class="controls">
|
|
1715
2969
|
<label class="field">Namespace
|
|
@@ -1839,460 +3093,7 @@ var renderAdminDocument = (service) => {
|
|
|
1839
3093
|
</div>
|
|
1840
3094
|
</form>
|
|
1841
3095
|
</dialog>
|
|
1842
|
-
|
|
1843
|
-
(() => {
|
|
1844
|
-
const STANDARD = new Set(${standard})
|
|
1845
|
-
const state = {
|
|
1846
|
-
namespace: "default",
|
|
1847
|
-
key: sessionStorage.getItem("mockingbird-admin-key") || "",
|
|
1848
|
-
view: "overview",
|
|
1849
|
-
collection: "",
|
|
1850
|
-
shape: null,
|
|
1851
|
-
route: null,
|
|
1852
|
-
editing: null
|
|
1853
|
-
}
|
|
1854
|
-
const $ = (id) => document.getElementById(id)
|
|
1855
|
-
const banner = $("banner")
|
|
1856
|
-
const keyInput = $("admin-key")
|
|
1857
|
-
keyInput.value = state.key
|
|
1858
|
-
const params = new URLSearchParams(location.search)
|
|
1859
|
-
if (params.get("key")) {
|
|
1860
|
-
state.key = params.get("key")
|
|
1861
|
-
keyInput.value = state.key
|
|
1862
|
-
sessionStorage.setItem("mockingbird-admin-key", state.key)
|
|
1863
|
-
params.delete("key")
|
|
1864
|
-
const next = params.toString()
|
|
1865
|
-
history.replaceState(null, "", location.pathname + (next ? "?" + next : ""))
|
|
1866
|
-
}
|
|
1867
|
-
if (params.get("namespace")) state.namespace = params.get("namespace")
|
|
1868
|
-
|
|
1869
|
-
const showError = (error) => {
|
|
1870
|
-
banner.textContent = error instanceof Error ? error.message : String(error)
|
|
1871
|
-
banner.classList.add("show")
|
|
1872
|
-
}
|
|
1873
|
-
const clearError = () => banner.classList.remove("show")
|
|
1874
|
-
|
|
1875
|
-
async function originGet(path) {
|
|
1876
|
-
const response = await fetch(path, { headers: { accept: "application/json" } })
|
|
1877
|
-
if (!response.ok) throw new Error(response.status + " " + path)
|
|
1878
|
-
return response.json()
|
|
1879
|
-
}
|
|
1880
|
-
async function api(method, path, body) {
|
|
1881
|
-
const headers = { accept: "application/json" }
|
|
1882
|
-
if (state.key) headers["${ADMIN_KEY_HEADER}"] = state.key
|
|
1883
|
-
if (state.namespace) headers["x-mockingbird-namespace"] = state.namespace
|
|
1884
|
-
if (body !== undefined) headers["content-type"] = "application/json"
|
|
1885
|
-
const response = await fetch("${ADMIN_PREFIX}" + path, {
|
|
1886
|
-
method,
|
|
1887
|
-
headers,
|
|
1888
|
-
body: body === undefined ? undefined : JSON.stringify(body)
|
|
1889
|
-
})
|
|
1890
|
-
const text = await response.text()
|
|
1891
|
-
let data = null
|
|
1892
|
-
if (text) {
|
|
1893
|
-
try { data = JSON.parse(text) } catch { data = { raw: text } }
|
|
1894
|
-
}
|
|
1895
|
-
if (response.status === 401) {
|
|
1896
|
-
keyInput.focus()
|
|
1897
|
-
throw new Error((data && data.error && data.error.message) || "Admin key required")
|
|
1898
|
-
}
|
|
1899
|
-
if (!response.ok) {
|
|
1900
|
-
throw new Error((data && data.error && data.error.message) || (response.status + " " + method + " " + path))
|
|
1901
|
-
}
|
|
1902
|
-
return data
|
|
1903
|
-
}
|
|
1904
|
-
const panelApi = {
|
|
1905
|
-
get: (path) => api("GET", path),
|
|
1906
|
-
send: (method, path, body) => api(method, path, body),
|
|
1907
|
-
namespace: () => state.namespace
|
|
1908
|
-
}
|
|
1909
|
-
|
|
1910
|
-
function setTheme(theme) {
|
|
1911
|
-
if (theme) document.documentElement.dataset.theme = theme
|
|
1912
|
-
else delete document.documentElement.dataset.theme
|
|
1913
|
-
}
|
|
1914
|
-
const storedTheme = localStorage.getItem("mockingbird-admin-theme")
|
|
1915
|
-
if (storedTheme) setTheme(storedTheme)
|
|
1916
|
-
$("theme").addEventListener("click", () => {
|
|
1917
|
-
const current = document.documentElement.dataset.theme
|
|
1918
|
-
const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches
|
|
1919
|
-
const next = dark ? "light" : "dark"
|
|
1920
|
-
localStorage.setItem("mockingbird-admin-theme", next)
|
|
1921
|
-
setTheme(next)
|
|
1922
|
-
})
|
|
1923
|
-
|
|
1924
|
-
function show(view) {
|
|
1925
|
-
state.view = view
|
|
1926
|
-
for (const button of document.querySelectorAll("#nav button")) {
|
|
1927
|
-
button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false")
|
|
1928
|
-
}
|
|
1929
|
-
for (const section of document.querySelectorAll("main .view")) {
|
|
1930
|
-
section.hidden = section.id !== "view-" + view
|
|
1931
|
-
}
|
|
1932
|
-
}
|
|
1933
|
-
document.getElementById("nav").addEventListener("click", (event) => {
|
|
1934
|
-
const button = event.target.closest("button[data-view]")
|
|
1935
|
-
if (!button) return
|
|
1936
|
-
show(button.dataset.view)
|
|
1937
|
-
refresh().catch(showError)
|
|
1938
|
-
})
|
|
1939
|
-
|
|
1940
|
-
function cards(items) {
|
|
1941
|
-
return '<div class="grid">' + items.map((item) =>
|
|
1942
|
-
'<article class="card"><div class="k">' + item.k + '</div><div class="v">' + item.v + '</div></article>'
|
|
1943
|
-
).join("") + "</div>"
|
|
1944
|
-
}
|
|
1945
|
-
function table(columns, rows) {
|
|
1946
|
-
if (rows.length === 0) return '<p class="empty">Nothing here yet.</p>'
|
|
1947
|
-
const head = "<tr>" + columns.map((column) => "<th>" + column.label + "</th>").join("") + "</tr>"
|
|
1948
|
-
const body = rows.map((row) => "<tr" + (row.attrs || "") + ">" + columns.map((column) =>
|
|
1949
|
-
'<td data-label="' + column.label + '">' + column.cell(row) + "</td>"
|
|
1950
|
-
).join("") + "</tr>").join("")
|
|
1951
|
-
return "<table><thead>" + head + "</thead><tbody>" + body + "</tbody></table>"
|
|
1952
|
-
}
|
|
1953
|
-
const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({
|
|
1954
|
-
"&": "&", "<": "<", ">": ">", '"': """, "'": "'"
|
|
1955
|
-
}[char]))
|
|
1956
|
-
const preview = (value) => {
|
|
1957
|
-
const text = JSON.stringify(value)
|
|
1958
|
-
return esc(text.length > 180 ? text.slice(0, 180) + "\u2026" : text)
|
|
1959
|
-
}
|
|
1960
|
-
const when = (epoch) => {
|
|
1961
|
-
const date = new Date(epoch)
|
|
1962
|
-
return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString()
|
|
1963
|
-
}
|
|
1964
|
-
const clockCard = (epoch) => {
|
|
1965
|
-
const date = new Date(epoch)
|
|
1966
|
-
if (Number.isNaN(date.getTime())) return esc(String(epoch))
|
|
1967
|
-
const iso = date.toISOString()
|
|
1968
|
-
return esc(iso.slice(0, 10)) + '<span class="clock-time">' + esc(iso.slice(11, 19)) + " UTC</span>"
|
|
1969
|
-
}
|
|
1970
|
-
|
|
1971
|
-
async function loadNamespaces() {
|
|
1972
|
-
const data = await api("GET", "/namespaces")
|
|
1973
|
-
const select = $("namespace")
|
|
1974
|
-
const names = data.namespaces && data.namespaces.length ? data.namespaces : [data.default || "default"]
|
|
1975
|
-
if (!names.includes(state.namespace)) names.unshift(state.namespace)
|
|
1976
|
-
select.innerHTML = names.map((name) =>
|
|
1977
|
-
'<option value="' + esc(name) + '">' + esc(name) + "</option>"
|
|
1978
|
-
).join("")
|
|
1979
|
-
select.value = state.namespace
|
|
1980
|
-
}
|
|
1981
|
-
|
|
1982
|
-
async function renderOverview() {
|
|
1983
|
-
const [health, shape, clock, journal] = await Promise.all([
|
|
1984
|
-
originGet("/health"),
|
|
1985
|
-
api("GET", "/state"),
|
|
1986
|
-
api("GET", "/clock"),
|
|
1987
|
-
api("GET", "/requests?limit=8")
|
|
1988
|
-
])
|
|
1989
|
-
state.shape = shape
|
|
1990
|
-
$("overview-cards").innerHTML = cards([
|
|
1991
|
-
{ k: "Service", v: esc(health.service || "${name}") },
|
|
1992
|
-
{ k: "Status", v: esc(health.status || "ok") },
|
|
1993
|
-
{ k: "Namespace", v: esc(state.namespace) },
|
|
1994
|
-
{ k: "Collections", v: String(shape.collections.length) },
|
|
1995
|
-
{ k: "Records", v: String(shape.collections.reduce((sum, item) => sum + item.count, 0)) },
|
|
1996
|
-
{ k: "Clock", v: clockCard(clock.now) }
|
|
1997
|
-
])
|
|
1998
|
-
const requests = (journal && journal.requests) || []
|
|
1999
|
-
$("overview-requests").innerHTML = table(
|
|
2000
|
-
[
|
|
2001
|
-
{ label: "Method", cell: (row) => esc(row.method) },
|
|
2002
|
-
{ label: "Path", cell: (row) => esc(row.path) },
|
|
2003
|
-
{ label: "Status", cell: (row) => '<span class="status-' + String(row.status).charAt(0) + '">' + esc(row.status) + "</span>" },
|
|
2004
|
-
{ label: "Operation", cell: (row) => esc(row.operationId || "") }
|
|
2005
|
-
],
|
|
2006
|
-
requests
|
|
2007
|
-
)
|
|
2008
|
-
}
|
|
2009
|
-
|
|
2010
|
-
function schemaChips(collection) {
|
|
2011
|
-
if (!collection || collection.fields.length === 0) {
|
|
2012
|
-
return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>'
|
|
2013
|
-
}
|
|
2014
|
-
return '<div class="chips">' + collection.fields.map((field) =>
|
|
2015
|
-
'<span class="chip">' + esc(field.name) + ' <i>' + esc(field.kind) + (field.optional ? "?" : "") + "</i></span>"
|
|
2016
|
-
).join("") + "</div>"
|
|
2017
|
-
}
|
|
2018
|
-
|
|
2019
|
-
async function renderState() {
|
|
2020
|
-
const shape = await api("GET", "/state")
|
|
2021
|
-
state.shape = shape
|
|
2022
|
-
const host = $("collections")
|
|
2023
|
-
if (shape.collections.length === 0) {
|
|
2024
|
-
host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>'
|
|
2025
|
-
$("schema").innerHTML = ""
|
|
2026
|
-
$("records").innerHTML = ""
|
|
2027
|
-
return
|
|
2028
|
-
}
|
|
2029
|
-
if (!shape.collections.some((item) => item.name === state.collection)) {
|
|
2030
|
-
state.collection = shape.collections[0].name
|
|
2031
|
-
}
|
|
2032
|
-
host.innerHTML = shape.collections.map((item) =>
|
|
2033
|
-
'<button type="button" class="collection" data-collection="' + esc(item.name) + '" aria-current="' +
|
|
2034
|
-
(item.name === state.collection ? "true" : "false") + '"><strong>' + esc(item.label) +
|
|
2035
|
-
'</strong><small>' + esc(item.count) + " \xB7 " + esc(item.source) + "</small></button>"
|
|
2036
|
-
).join("")
|
|
2037
|
-
const selected = shape.collections.find((item) => item.name === state.collection)
|
|
2038
|
-
$("schema").innerHTML = "<h3>" + esc(selected.label) + "</h3>" +
|
|
2039
|
-
(selected.description ? "<p class=" + '"lede">' + esc(selected.description) + "</p>" : "") +
|
|
2040
|
-
schemaChips(selected)
|
|
2041
|
-
const page = await api("GET", "/state/" + encodeURIComponent(state.collection) + "?limit=50")
|
|
2042
|
-
const rows = page.records.map((record) => ({
|
|
2043
|
-
...record,
|
|
2044
|
-
attrs: ' class="clickable" data-id="' + esc(record.id) + '"'
|
|
2045
|
-
}))
|
|
2046
|
-
$("records").innerHTML = table(
|
|
2047
|
-
[
|
|
2048
|
-
{ label: "Id", cell: (row) => "<code>" + esc(row.id) + "</code>" },
|
|
2049
|
-
{ label: "Seq", cell: (row) => esc(row.seq) },
|
|
2050
|
-
{ label: "Value", cell: (row) => preview(row.value) }
|
|
2051
|
-
],
|
|
2052
|
-
rows
|
|
2053
|
-
)
|
|
2054
|
-
}
|
|
2055
|
-
|
|
2056
|
-
async function renderClock() {
|
|
2057
|
-
const clock = await api("GET", "/clock")
|
|
2058
|
-
$("clock-readout").innerHTML = "<div class='k'>Now</div><div class='v'>" + esc(when(clock.now)) +
|
|
2059
|
-
"</div><p>" + esc(clock.now) + " ms \xB7 offset " + esc(clock.offsetMs) + " ms " +
|
|
2060
|
-
(clock.frozen ? "<span class='badge warn'>frozen</span>" : "<span class='badge ok'>live</span>") + "</p>"
|
|
2061
|
-
$("freeze").textContent = clock.frozen ? "Unfreeze" : "Freeze"
|
|
2062
|
-
$("freeze").dataset.frozen = clock.frozen ? "1" : "0"
|
|
2063
|
-
}
|
|
2064
|
-
|
|
2065
|
-
async function renderFaults() {
|
|
2066
|
-
const [faults, presets] = await Promise.all([
|
|
2067
|
-
api("GET", "/faults"),
|
|
2068
|
-
api("GET", "/faults/presets").catch(() => ({ presets: [] }))
|
|
2069
|
-
])
|
|
2070
|
-
const select = $("fault-preset")
|
|
2071
|
-
const current = select.value
|
|
2072
|
-
const list = presets.presets || []
|
|
2073
|
-
select.innerHTML = '<option value="">Custom rule</option>' + list.map((preset) =>
|
|
2074
|
-
'<option value="' + esc(preset.name) + '">' + esc(preset.name) + "</option>"
|
|
2075
|
-
).join("")
|
|
2076
|
-
select.value = current
|
|
2077
|
-
const rules = faults.faults || []
|
|
2078
|
-
$("fault-list").innerHTML = table(
|
|
2079
|
-
[
|
|
2080
|
-
{ label: "Id", cell: (row) => esc(row.id) },
|
|
2081
|
-
{ label: "Match", cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter(Boolean).join(" ") || "all") },
|
|
2082
|
-
{ label: "Effect", cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || "")) }
|
|
2083
|
-
],
|
|
2084
|
-
rules
|
|
2085
|
-
)
|
|
2086
|
-
}
|
|
2087
|
-
|
|
2088
|
-
async function renderJournal() {
|
|
2089
|
-
const journal = await api("GET", "/requests?limit=100")
|
|
2090
|
-
$("journal").innerHTML = table(
|
|
2091
|
-
[
|
|
2092
|
-
{ label: "When", cell: (row) => esc(row.at || "") },
|
|
2093
|
-
{ label: "Method", cell: (row) => esc(row.method) },
|
|
2094
|
-
{ label: "Path", cell: (row) => esc(row.path) },
|
|
2095
|
-
{ label: "Status", cell: (row) => esc(row.status) },
|
|
2096
|
-
{ label: "ms", cell: (row) => esc(row.durationMs) }
|
|
2097
|
-
],
|
|
2098
|
-
journal.requests || []
|
|
2099
|
-
)
|
|
2100
|
-
}
|
|
2101
|
-
|
|
2102
|
-
async function renderRoutes() {
|
|
2103
|
-
const data = await api("GET", "/")
|
|
2104
|
-
const routes = data.routes || []
|
|
2105
|
-
$("route-list").innerHTML = routes.map((route) => {
|
|
2106
|
-
const extra = STANDARD.has(route) ? "" : " <span class='badge'>extension</span>"
|
|
2107
|
-
return '<div class="route"><span class="method">' + esc(route.split(" ")[0]) +
|
|
2108
|
-
'</span><span>' + esc(route.split(" ").slice(1).join(" ")) + extra +
|
|
2109
|
-
'</span><button class="btn" type="button" data-route="' + esc(route) + '">Use</button></div>'
|
|
2110
|
-
}).join("")
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2113
|
-
const renderers = {
|
|
2114
|
-
overview: renderOverview,
|
|
2115
|
-
state: renderState,
|
|
2116
|
-
clock: renderClock,
|
|
2117
|
-
faults: renderFaults,
|
|
2118
|
-
journal: renderJournal,
|
|
2119
|
-
routes: renderRoutes
|
|
2120
|
-
}
|
|
2121
|
-
|
|
2122
|
-
async function refresh() {
|
|
2123
|
-
clearError()
|
|
2124
|
-
if (state.view in renderers) await renderers[state.view]()
|
|
2125
|
-
}
|
|
2126
|
-
|
|
2127
|
-
$("namespace").addEventListener("change", () => {
|
|
2128
|
-
state.namespace = $("namespace").value
|
|
2129
|
-
refresh().catch(showError)
|
|
2130
|
-
})
|
|
2131
|
-
keyInput.addEventListener("change", () => {
|
|
2132
|
-
state.key = keyInput.value.trim()
|
|
2133
|
-
sessionStorage.setItem("mockingbird-admin-key", state.key)
|
|
2134
|
-
refresh().catch(showError)
|
|
2135
|
-
})
|
|
2136
|
-
$("refresh").addEventListener("click", () => refresh().catch(showError))
|
|
2137
|
-
|
|
2138
|
-
$("collections").addEventListener("click", (event) => {
|
|
2139
|
-
const button = event.target.closest("[data-collection]")
|
|
2140
|
-
if (!button) return
|
|
2141
|
-
state.collection = button.dataset.collection
|
|
2142
|
-
renderState().catch(showError)
|
|
2143
|
-
})
|
|
2144
|
-
$("records").addEventListener("click", (event) => {
|
|
2145
|
-
const row = event.target.closest("[data-id]")
|
|
2146
|
-
if (!row) return
|
|
2147
|
-
openEditor(row.dataset.id).catch(showError)
|
|
2148
|
-
})
|
|
2149
|
-
|
|
2150
|
-
const editor = $("editor")
|
|
2151
|
-
async function openEditor(id) {
|
|
2152
|
-
state.editing = id || null
|
|
2153
|
-
$("editor-title").textContent = id ? "Edit record" : "New record"
|
|
2154
|
-
$("editor-id").value = id || ""
|
|
2155
|
-
$("editor-id").readOnly = Boolean(id)
|
|
2156
|
-
$("editor-delete").hidden = !id
|
|
2157
|
-
if (id) {
|
|
2158
|
-
const record = await api("GET", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
|
|
2159
|
-
$("editor-value").value = JSON.stringify(record.value, null, 2)
|
|
2160
|
-
} else {
|
|
2161
|
-
$("editor-value").value = "{\\n \\n}"
|
|
2162
|
-
}
|
|
2163
|
-
editor.showModal()
|
|
2164
|
-
}
|
|
2165
|
-
$("new-record").addEventListener("click", () => openEditor(null).catch(showError))
|
|
2166
|
-
$("editor-form").addEventListener("submit", (event) => {
|
|
2167
|
-
const submitter = event.submitter
|
|
2168
|
-
if (!submitter || submitter.value !== "save") return
|
|
2169
|
-
event.preventDefault()
|
|
2170
|
-
let value
|
|
2171
|
-
try { value = JSON.parse($("editor-value").value) }
|
|
2172
|
-
catch { showError(new Error("Value is not JSON")); return }
|
|
2173
|
-
const id = $("editor-id").value.trim()
|
|
2174
|
-
const path = "/state/" + encodeURIComponent(state.collection)
|
|
2175
|
-
const request = state.editing
|
|
2176
|
-
? api("PUT", path + "/" + encodeURIComponent(id), { value })
|
|
2177
|
-
: api("POST", path, { id: id || undefined, value })
|
|
2178
|
-
request.then(() => { editor.close(); return renderState() }).catch(showError)
|
|
2179
|
-
})
|
|
2180
|
-
$("editor-delete").addEventListener("click", () => {
|
|
2181
|
-
const id = state.editing
|
|
2182
|
-
if (!id) return
|
|
2183
|
-
api("DELETE", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
|
|
2184
|
-
.then(() => { editor.close(); return renderState() })
|
|
2185
|
-
.catch(showError)
|
|
2186
|
-
})
|
|
2187
|
-
|
|
2188
|
-
document.querySelectorAll("[data-advance]").forEach((button) => {
|
|
2189
|
-
button.addEventListener("click", () => {
|
|
2190
|
-
api("POST", "/clock", { advance: Number(button.dataset.advance) }).then(renderClock).catch(showError)
|
|
2191
|
-
})
|
|
2192
|
-
})
|
|
2193
|
-
$("freeze").addEventListener("click", () => {
|
|
2194
|
-
const frozen = $("freeze").dataset.frozen === "1"
|
|
2195
|
-
api("POST", "/clock", { freeze: !frozen }).then(renderClock).catch(showError)
|
|
2196
|
-
})
|
|
2197
|
-
$("clock-reset").addEventListener("click", () => {
|
|
2198
|
-
api("POST", "/clock", { reset: true }).then(renderClock).catch(showError)
|
|
2199
|
-
})
|
|
2200
|
-
$("clock-set").addEventListener("submit", (event) => {
|
|
2201
|
-
event.preventDefault()
|
|
2202
|
-
const raw = $("clock-instant").value.trim()
|
|
2203
|
-
const set = /^-?\\d+$/.test(raw) ? Number(raw) : raw
|
|
2204
|
-
api("POST", "/clock", { set }).then(renderClock).catch(showError)
|
|
2205
|
-
})
|
|
2206
|
-
|
|
2207
|
-
$("fault-form").addEventListener("submit", (event) => {
|
|
2208
|
-
event.preventDefault()
|
|
2209
|
-
const preset = $("fault-preset").value
|
|
2210
|
-
const body = preset
|
|
2211
|
-
? { preset }
|
|
2212
|
-
: {
|
|
2213
|
-
status: Number($("fault-status").value),
|
|
2214
|
-
...($("fault-path").value ? { pathPrefix: $("fault-path").value } : {}),
|
|
2215
|
-
...($("fault-method").value ? { method: $("fault-method").value } : {})
|
|
2216
|
-
}
|
|
2217
|
-
if (!preset && !Number.isFinite(body.status)) {
|
|
2218
|
-
showError(new Error("Status must be a number, or choose a preset"))
|
|
2219
|
-
return
|
|
2220
|
-
}
|
|
2221
|
-
api("POST", "/faults", body).then(renderFaults).catch(showError)
|
|
2222
|
-
})
|
|
2223
|
-
$("fault-clear").addEventListener("click", () => api("DELETE", "/faults").then(renderFaults).catch(showError))
|
|
2224
|
-
$("journal-clear").addEventListener("click", () => api("DELETE", "/requests").then(renderJournal).catch(showError))
|
|
2225
|
-
|
|
2226
|
-
$("route-list").addEventListener("click", (event) => {
|
|
2227
|
-
const button = event.target.closest("[data-route]")
|
|
2228
|
-
if (!button) return
|
|
2229
|
-
state.route = button.dataset.route
|
|
2230
|
-
$("route-title").textContent = state.route
|
|
2231
|
-
const method = state.route.split(" ")[0]
|
|
2232
|
-
$("route-body").disabled = method === "GET" || method === "DELETE"
|
|
2233
|
-
})
|
|
2234
|
-
$("route-form").addEventListener("submit", async (event) => {
|
|
2235
|
-
event.preventDefault()
|
|
2236
|
-
if (!state.route) return showError(new Error("Choose a route first"))
|
|
2237
|
-
const [method, path] = [state.route.split(" ")[0], state.route.split(" ").slice(1).join(" ")]
|
|
2238
|
-
const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_, key) => prompt("Value for " + key) || "")
|
|
2239
|
-
let body
|
|
2240
|
-
if (method !== "GET" && method !== "DELETE" && $("route-body").value.trim()) {
|
|
2241
|
-
try { body = JSON.parse($("route-body").value) }
|
|
2242
|
-
catch { return showError(new Error("Body is not JSON")) }
|
|
2243
|
-
}
|
|
2244
|
-
try {
|
|
2245
|
-
const data = await api(method, concrete, body)
|
|
2246
|
-
$("route-result").textContent = JSON.stringify(data, null, 2)
|
|
2247
|
-
} catch (error) {
|
|
2248
|
-
showError(error)
|
|
2249
|
-
}
|
|
2250
|
-
})
|
|
2251
|
-
|
|
2252
|
-
function mountPanels(panels) {
|
|
2253
|
-
const nav = $("nav")
|
|
2254
|
-
const views = $("views")
|
|
2255
|
-
for (const panel of panels) {
|
|
2256
|
-
const button = document.createElement("button")
|
|
2257
|
-
button.type = "button"
|
|
2258
|
-
button.dataset.view = panel.id
|
|
2259
|
-
button.textContent = panel.title
|
|
2260
|
-
nav.append(button)
|
|
2261
|
-
const section = document.createElement("section")
|
|
2262
|
-
section.className = "view"
|
|
2263
|
-
section.id = "view-" + panel.id
|
|
2264
|
-
section.hidden = true
|
|
2265
|
-
const title = document.createElement("h2")
|
|
2266
|
-
title.textContent = panel.title
|
|
2267
|
-
const lede = document.createElement("p")
|
|
2268
|
-
lede.className = "lede"
|
|
2269
|
-
lede.textContent = panel.description || ""
|
|
2270
|
-
const body = document.createElement("div")
|
|
2271
|
-
body.className = "panel-body"
|
|
2272
|
-
body.id = "panel-" + panel.id
|
|
2273
|
-
body.innerHTML = panel.html || ""
|
|
2274
|
-
section.append(title, lede, body)
|
|
2275
|
-
views.append(section)
|
|
2276
|
-
if (panel.script) {
|
|
2277
|
-
try {
|
|
2278
|
-
const run = new Function("root", "api", panel.script)
|
|
2279
|
-
run(body, panelApi)
|
|
2280
|
-
} catch (error) {
|
|
2281
|
-
const note = document.createElement("p")
|
|
2282
|
-
note.className = "banner show"
|
|
2283
|
-
note.textContent = error instanceof Error ? error.message : String(error)
|
|
2284
|
-
body.append(note)
|
|
2285
|
-
}
|
|
2286
|
-
}
|
|
2287
|
-
}
|
|
2288
|
-
}
|
|
2289
|
-
|
|
2290
|
-
loadNamespaces()
|
|
2291
|
-
.then(() => api("GET", "/ui/manifest"))
|
|
2292
|
-
.then((manifest) => { mountPanels((manifest && manifest.panels) || []); return refresh() })
|
|
2293
|
-
.catch(showError)
|
|
2294
|
-
})()
|
|
2295
|
-
</script>
|
|
3096
|
+
${script}
|
|
2296
3097
|
</body>
|
|
2297
3098
|
</html>`;
|
|
2298
3099
|
};
|
|
@@ -2309,10 +3110,10 @@ var json2 = (body) => new Response(JSON.stringify(body), {
|
|
|
2309
3110
|
});
|
|
2310
3111
|
var adminUiRoutes = (service, ui) => {
|
|
2311
3112
|
const shell = () => renderAdminDocument(service);
|
|
2312
|
-
const
|
|
3113
|
+
const document3 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
|
|
2313
3114
|
return {
|
|
2314
|
-
"GET /ui": () => htmlResponse(
|
|
2315
|
-
"GET /ui/": () => htmlResponse(
|
|
3115
|
+
"GET /ui": () => htmlResponse(document3()),
|
|
3116
|
+
"GET /ui/": () => htmlResponse(document3()),
|
|
2316
3117
|
"GET /ui/manifest": () => json2({
|
|
2317
3118
|
service,
|
|
2318
3119
|
panels: (ui?.panels ?? []).map((panel) => ({
|
|
@@ -2338,11 +3139,11 @@ var EXTENSION_KEYS = {
|
|
|
2338
3139
|
};
|
|
2339
3140
|
|
|
2340
3141
|
// ../../openapi/metadata/dist/read.js
|
|
2341
|
-
var
|
|
3142
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2342
3143
|
var extensionOf = (holder, key) => holder[key];
|
|
2343
3144
|
var operationMetadata = (operation) => {
|
|
2344
3145
|
const raw = extensionOf(operation, EXTENSION_KEYS.operation);
|
|
2345
|
-
const ext =
|
|
3146
|
+
const ext = isRecord3(raw) ? raw : {};
|
|
2346
3147
|
const supported = ext.supported ?? true;
|
|
2347
3148
|
const parity = ext.parity ?? {};
|
|
2348
3149
|
return {
|
|
@@ -2452,9 +3253,9 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
2452
3253
|
this.name = "OperationRegistryError";
|
|
2453
3254
|
}
|
|
2454
3255
|
};
|
|
2455
|
-
var verifyOperations = (
|
|
3256
|
+
var verifyOperations = (document3, handlers) => {
|
|
2456
3257
|
const problems = [];
|
|
2457
|
-
const operations = listOperations(
|
|
3258
|
+
const operations = listOperations(document3);
|
|
2458
3259
|
const seen = /* @__PURE__ */ new Set();
|
|
2459
3260
|
for (const operation of operations) {
|
|
2460
3261
|
if (seen.has(operation.operationId))
|
|
@@ -2570,7 +3371,7 @@ var SAMPLE = 25;
|
|
|
2570
3371
|
var WALK_LIMIT = 1e4;
|
|
2571
3372
|
var SKIP_CTORS = /* @__PURE__ */ new Set(["Hono", "HonoRequest", "Router"]);
|
|
2572
3373
|
var fail = (status, message) => ({ ok: false, status, message });
|
|
2573
|
-
var
|
|
3374
|
+
var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2574
3375
|
var kindOf = (value) => {
|
|
2575
3376
|
if (value === null)
|
|
2576
3377
|
return "null";
|
|
@@ -2669,7 +3470,7 @@ var absorb = (accum, value) => {
|
|
|
2669
3470
|
const kind = kindOf(value);
|
|
2670
3471
|
accum.kinds.add(kind);
|
|
2671
3472
|
accum.present++;
|
|
2672
|
-
if (!
|
|
3473
|
+
if (!isRecord4(value))
|
|
2673
3474
|
return;
|
|
2674
3475
|
for (const [key, child] of Object.entries(value)) {
|
|
2675
3476
|
const nested = accum.nested.get(key) ?? blankAccum(key);
|
|
@@ -2694,7 +3495,7 @@ var inferFields = (records) => {
|
|
|
2694
3495
|
if (records.length === 0)
|
|
2695
3496
|
return [];
|
|
2696
3497
|
const sample = records.slice(0, SAMPLE);
|
|
2697
|
-
const objects = sample.filter((record) =>
|
|
3498
|
+
const objects = sample.filter((record) => isRecord4(record.value));
|
|
2698
3499
|
if (objects.length === 0) {
|
|
2699
3500
|
const kinds = new Set(sample.map((record) => kindOf(record.value)));
|
|
2700
3501
|
const list = [...kinds];
|
|
@@ -2881,7 +3682,7 @@ var patchStateRecord = (scope, name, id, value) => {
|
|
|
2881
3682
|
const parsed = jsonValue(value);
|
|
2882
3683
|
if (!parsed.ok)
|
|
2883
3684
|
return parsed;
|
|
2884
|
-
const next =
|
|
3685
|
+
const next = isRecord4(current.value.value) && isRecord4(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
|
|
2885
3686
|
return replaceStateRecord(scope, name, id, next);
|
|
2886
3687
|
};
|
|
2887
3688
|
var deleteStateRecord = (scope, name, id) => {
|
|
@@ -2949,7 +3750,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
2949
3750
|
const scope = open(namespace);
|
|
2950
3751
|
if (!scope.ok)
|
|
2951
3752
|
return fromResult(scope, 201);
|
|
2952
|
-
if (!
|
|
3753
|
+
if (!isRecord4(body) || !("value" in body)) {
|
|
2953
3754
|
return adminError2(400, 'expected {"id?": "<id>", "value": <json>}');
|
|
2954
3755
|
}
|
|
2955
3756
|
const id = body.id;
|
|
@@ -2967,7 +3768,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
2967
3768
|
const scope = open(namespace);
|
|
2968
3769
|
if (!scope.ok)
|
|
2969
3770
|
return fromResult(scope, 200);
|
|
2970
|
-
if (!
|
|
3771
|
+
if (!isRecord4(body) || !("value" in body))
|
|
2971
3772
|
return adminError2(400, 'expected {"value": <json>}');
|
|
2972
3773
|
const replaced = replaceStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
|
|
2973
3774
|
if (replaced.ok)
|
|
@@ -2978,7 +3779,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
2978
3779
|
const scope = open(namespace);
|
|
2979
3780
|
if (!scope.ok)
|
|
2980
3781
|
return fromResult(scope, 200);
|
|
2981
|
-
if (!
|
|
3782
|
+
if (!isRecord4(body) || !("value" in body))
|
|
2982
3783
|
return adminError2(400, 'expected {"value": <json>}');
|
|
2983
3784
|
const patched = patchStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
|
|
2984
3785
|
if (patched.ok)
|
|
@@ -2998,7 +3799,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
2998
3799
|
};
|
|
2999
3800
|
|
|
3000
3801
|
// ../core/dist/version.js
|
|
3001
|
-
var PACKAGE_VERSION = true ? "1.
|
|
3802
|
+
var PACKAGE_VERSION = true ? "1.5.1" : UNRELEASED_VERSION;
|
|
3002
3803
|
|
|
3003
3804
|
// ../core/dist/signing.js
|
|
3004
3805
|
var encoder = new TextEncoder();
|
|
@@ -3335,9 +4136,9 @@ var createWebhookHub = (options) => {
|
|
|
3335
4136
|
};
|
|
3336
4137
|
var json4 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
3337
4138
|
var adminError3 = (status, message) => json4(status, { error: { type: "mockingbird_admin", message } });
|
|
3338
|
-
var
|
|
4139
|
+
var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3339
4140
|
var parseEndpoint = (value) => {
|
|
3340
|
-
if (!
|
|
4141
|
+
if (!isRecord5(value) || typeof value.url !== "string")
|
|
3341
4142
|
return "each endpoint needs a url";
|
|
3342
4143
|
try {
|
|
3343
4144
|
new URL(value.url);
|
|
@@ -3354,12 +4155,12 @@ var parseEndpoint = (value) => {
|
|
|
3354
4155
|
const events = value.events ?? value.enabledEvents;
|
|
3355
4156
|
if (Array.isArray(events))
|
|
3356
4157
|
endpoint.events = events.map(String);
|
|
3357
|
-
if (
|
|
4158
|
+
if (isRecord5(value.tags)) {
|
|
3358
4159
|
endpoint.tags = Object.fromEntries(Object.entries(value.tags).map(([k, v]) => [k, String(v)]));
|
|
3359
4160
|
}
|
|
3360
4161
|
if (typeof value.account === "string")
|
|
3361
4162
|
endpoint.tags = { ...endpoint.tags, account: value.account };
|
|
3362
|
-
if (
|
|
4163
|
+
if (isRecord5(value.headers)) {
|
|
3363
4164
|
endpoint.headers = Object.fromEntries(Object.entries(value.headers).map(([k, v]) => [k, String(v)]));
|
|
3364
4165
|
}
|
|
3365
4166
|
return endpoint;
|
|
@@ -3386,7 +4187,7 @@ var webhookAdminRoutes = (hub) => ({
|
|
|
3386
4187
|
return json4(200, { status: "ok" });
|
|
3387
4188
|
},
|
|
3388
4189
|
"POST /webhooks/faults": ({ body, namespace }) => {
|
|
3389
|
-
if (!
|
|
4190
|
+
if (!isRecord5(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
|
|
3390
4191
|
return adminError3(400, "mode must be duplicate, reorder or drop");
|
|
3391
4192
|
}
|
|
3392
4193
|
const fault = { mode: body.mode };
|
|
@@ -3402,7 +4203,7 @@ var webhookAdminRoutes = (hub) => ({
|
|
|
3402
4203
|
}))
|
|
3403
4204
|
}),
|
|
3404
4205
|
"PUT /webhook-endpoints": ({ body, namespace }) => {
|
|
3405
|
-
const list = Array.isArray(body) ? body :
|
|
4206
|
+
const list = Array.isArray(body) ? body : isRecord5(body) ? body.endpoints : void 0;
|
|
3406
4207
|
if (!Array.isArray(list))
|
|
3407
4208
|
return adminError3(400, "expected [{url, secret?, events?}]");
|
|
3408
4209
|
const parsed = [];
|
|
@@ -3472,8 +4273,8 @@ var DroppedConnectionError = class extends TypeError {
|
|
|
3472
4273
|
this.name = "TypeError";
|
|
3473
4274
|
}
|
|
3474
4275
|
};
|
|
3475
|
-
var operationMatcher = (
|
|
3476
|
-
const matchers = listOperations(
|
|
4276
|
+
var operationMatcher = (document3) => {
|
|
4277
|
+
const matchers = listOperations(document3).map((operation) => ({
|
|
3477
4278
|
operationId: operation.operationId,
|
|
3478
4279
|
method: operation.method.toUpperCase(),
|
|
3479
4280
|
pattern: new RegExp(`^${operation.path.split("/").map((segment) => segment.startsWith("{") ? "[^/]+" : segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("/")}/?$`),
|
|
@@ -3568,10 +4369,10 @@ var createRuntime = (options) => {
|
|
|
3568
4369
|
const ensureBranch = (namespace, branch2, at) => {
|
|
3569
4370
|
if (!BRANCH_PATTERN2.test(branch2))
|
|
3570
4371
|
throw new RangeError(`branch must match ${BRANCH_PATTERN2}`);
|
|
3571
|
-
const
|
|
4372
|
+
const history2 = timeline(namespace);
|
|
3572
4373
|
if (branch2 === "main") {
|
|
3573
4374
|
if (at !== void 0) {
|
|
3574
|
-
const point =
|
|
4375
|
+
const point = history2.checkout("main", at);
|
|
3575
4376
|
restoreNamespace(sqlite, storageNamespace(namespace), point.value.snapshot);
|
|
3576
4377
|
captured.set(namespace, point.value.snapshot);
|
|
3577
4378
|
rng.setState(point.value.rngState);
|
|
@@ -3584,10 +4385,10 @@ var createRuntime = (options) => {
|
|
|
3584
4385
|
return namespace;
|
|
3585
4386
|
}
|
|
3586
4387
|
const storage = physicalBranch(namespace, branch2);
|
|
3587
|
-
if (!
|
|
4388
|
+
if (!history2.hasBranch(branch2)) {
|
|
3588
4389
|
if (at === void 0)
|
|
3589
|
-
|
|
3590
|
-
const point =
|
|
4390
|
+
history2.commit(capture(namespace));
|
|
4391
|
+
const point = history2.fork(branch2, at === void 0 ? {} : { from: at });
|
|
3591
4392
|
const branchRng = createRng(options.seed ?? 0);
|
|
3592
4393
|
if (point)
|
|
3593
4394
|
branchRng.setState(point.value.rngState);
|
|
@@ -3596,8 +4397,8 @@ var createRuntime = (options) => {
|
|
|
3596
4397
|
restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
|
|
3597
4398
|
if (point)
|
|
3598
4399
|
captured.set(storage, point.value.snapshot);
|
|
3599
|
-
} else if (at !== void 0 &&
|
|
3600
|
-
const point =
|
|
4400
|
+
} else if (at !== void 0 && history2.head(branch2)?.id !== at) {
|
|
4401
|
+
const point = history2.checkout(branch2, at);
|
|
3601
4402
|
if (!instances.has(storage)) {
|
|
3602
4403
|
const branchRng = createRng(options.seed ?? 0);
|
|
3603
4404
|
branchRng.setState(point.value.rngState);
|
|
@@ -3608,7 +4409,7 @@ var createRuntime = (options) => {
|
|
|
3608
4409
|
captured.set(storage, point.value.snapshot);
|
|
3609
4410
|
} else {
|
|
3610
4411
|
if (!instances.has(storage)) {
|
|
3611
|
-
const point =
|
|
4412
|
+
const point = history2.head(branch2);
|
|
3612
4413
|
const branchRng = createRng(options.seed ?? 0);
|
|
3613
4414
|
if (point)
|
|
3614
4415
|
branchRng.setState(point.value.rngState);
|
|
@@ -3632,8 +4433,8 @@ var createRuntime = (options) => {
|
|
|
3632
4433
|
const checkout = (checkpointId, checkoutOptions = {}) => {
|
|
3633
4434
|
const namespace = checkoutOptions.namespace ?? DEFAULT_NAMESPACE;
|
|
3634
4435
|
const branchName = checkoutOptions.branch ?? "main";
|
|
3635
|
-
const
|
|
3636
|
-
const point =
|
|
4436
|
+
const history2 = timeline(namespace);
|
|
4437
|
+
const point = history2.checkout(branchName, checkpointId);
|
|
3637
4438
|
const storage = ensureBranch(namespace, branchName);
|
|
3638
4439
|
restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
|
|
3639
4440
|
captured.set(storage, point.value.snapshot);
|
|
@@ -3683,9 +4484,9 @@ var createRuntime = (options) => {
|
|
|
3683
4484
|
instance(name);
|
|
3684
4485
|
restoreNamespace(sqlite, storageNamespace(name), from);
|
|
3685
4486
|
captured.set(name, from);
|
|
3686
|
-
const
|
|
3687
|
-
if (
|
|
3688
|
-
|
|
4487
|
+
const history2 = timelines.get(name);
|
|
4488
|
+
if (history2)
|
|
4489
|
+
history2.commit(capture(name), { branch: "main" });
|
|
3689
4490
|
else
|
|
3690
4491
|
timeline(name);
|
|
3691
4492
|
};
|
|
@@ -3914,10 +4715,10 @@ var createRuntime = (options) => {
|
|
|
3914
4715
|
},
|
|
3915
4716
|
release: (name, checkpointId) => timeline(name).release(checkpointId),
|
|
3916
4717
|
inspect: (name) => {
|
|
3917
|
-
const
|
|
4718
|
+
const history2 = timeline(name);
|
|
3918
4719
|
return {
|
|
3919
|
-
branches:
|
|
3920
|
-
checkpoints:
|
|
4720
|
+
branches: history2.branches(),
|
|
4721
|
+
checkpoints: history2.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
|
|
3921
4722
|
id,
|
|
3922
4723
|
branch: branchName,
|
|
3923
4724
|
parent,
|
|
@@ -3932,11 +4733,11 @@ var createRuntime = (options) => {
|
|
|
3932
4733
|
} : {},
|
|
3933
4734
|
routes: {
|
|
3934
4735
|
...credentialRoutes(credentials),
|
|
3935
|
-
...options.presets ? presetRoutes(options.presets, runtime) : {},
|
|
3936
4736
|
...options.webhooks ? webhookAdminRoutes(options.webhooks) : {},
|
|
3937
4737
|
...options.admin?.(runtime) ?? {},
|
|
3938
|
-
//
|
|
3939
|
-
//
|
|
4738
|
+
// Shell routes win a colliding key. Fault presets are one of them: the admin page
|
|
4739
|
+
// maps `presets`, and a service route must not replace that list with a record.
|
|
4740
|
+
...options.presets ? presetRoutes(options.presets, runtime) : {},
|
|
3940
4741
|
...stateAdminRoutes({
|
|
3941
4742
|
open: stateScope,
|
|
3942
4743
|
afterWrite: (namespace) => {
|
|
@@ -4011,9 +4812,12 @@ var credentialRoutes = (registry) => ({
|
|
|
4011
4812
|
}
|
|
4012
4813
|
});
|
|
4013
4814
|
var presetRoutes = (presets, runtime) => ({
|
|
4014
|
-
"GET /faults/presets": () =>
|
|
4015
|
-
|
|
4016
|
-
|
|
4815
|
+
"GET /faults/presets": () => {
|
|
4816
|
+
const body = {
|
|
4817
|
+
presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
|
|
4818
|
+
};
|
|
4819
|
+
return adminJson(200, body);
|
|
4820
|
+
},
|
|
4017
4821
|
"POST /faults/presets/:name": ({ params, body, namespace }) => {
|
|
4018
4822
|
const name = params.name;
|
|
4019
4823
|
if (!presets[name])
|
|
@@ -4024,7 +4828,7 @@ var presetRoutes = (presets, runtime) => ({
|
|
|
4024
4828
|
});
|
|
4025
4829
|
|
|
4026
4830
|
// src/generated/openapi.ts
|
|
4027
|
-
var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Flex HSA/FSA payments API (Mockingbird subset)","description":"Stateful mock subset of the Flex (withflex.com) payments API: products, checkout sessions\\n(payment, subscription, off-session and setup modes), subscriptions, customers, setup\\nintents, refunds, and the hosted checkout page. Request and response bodies are wrapped in an envelope named after the\\nobject (\`{product: {...}}\`, \`{checkout_session: {...}}\`, \`{products: [...]}\`).\\nHand-authored from the consumer's wire shapes and the Flex API reference\\n(https://docs.withflex.com/api-reference); Flex publishes no machine-readable spec.\\n","version":"1","x-mockingbird-upstream":{"note":"Hand-authored from the wire shapes our consumer reads and writes (flex-api.client.ts, flex-payment.types.ts, flex-product.types.ts, flex-webhook.service.ts, do-flex-hosted-checkout.ts). Subscription mode, recurring price_data and the Subscription object follow https://docs.withflex.com/api-reference/checkout-sessions/create-checkout-session, https://docs.withflex.com/api-reference/subscriptions/get-subscription and https://docs.withflex.com/api-reference/prices/create-price."}},"servers":[{"url":"https://api.withflex.com"}],"security":[{"bearerAuth":[]}],"paths":{"/v1/products":{"get":{"operationId":"ListProducts","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"starting_after","in":"query","required":false,"description":"The last product_id of the previous page.","schema":{"type":"string","x-mockingbird-resource-ref":{"type":"product","missing":"fprod_00000000000000000000000000"}}}],"responses":{"200":{"description":"A page of products, oldest first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductList"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"}}},"post":{"operationId":"CreateProduct","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProductBody"}}}},"responses":{"200":{"description":"The created product","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductEnvelope"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"}}}},"/v1/products/{productId}":{"parameters":[{"name":"productId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"product","missing":"fprod_00000000000000000000000000"}}}],"get":{"operationId":"GetProduct","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The product","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductEnvelope"}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}},"patch":{"operationId":"UpdateProduct","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProductBody"}}}},"responses":{"200":{"description":"The updated product (emits product.updated)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductEnvelope"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/v1/checkout/sessions":{"get":{"operationId":"ListCheckoutSessions","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"client_reference_id","in":"query","required":false,"schema":{"type":"string","maxLength":255}},{"name":"expand_customer","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}},{"name":"expand_payment_intent","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"starting_after","in":"query","required":false,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"checkout_session","missing":"fcs_00000000000000000000000000"}}}],"responses":{"200":{"description":"Sessions, newest first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutSessionList"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"}}},"post":{"operationId":"CreateCheckoutSession","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[A-Za-z0-9:_.-]+$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCheckoutSessionBody"}}}},"responses":{"200":{"description":"The created session. Off-session mode charges the saved payment method before answering, so the session is already complete (or its payment intent failed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutSessionEnvelope"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}}}},"/v1/checkout/sessions/{sessionId}":{"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"checkout_session","missing":"fcs_00000000000000000000000000"}}}],"get":{"operationId":"GetCheckoutSession","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"expand_customer","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}},{"name":"expand_payment_intent","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}}],"responses":{"200":{"description":"The session","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutSessionEnvelope"}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/v1/checkout/sessions/{sessionId}/refund":{"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"checkout_session","missing":"fcs_00000000000000000000000000"}}}],"post":{"operationId":"RefundCheckoutSession","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[A-Za-z0-9:_.-]+$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefundCheckoutSessionBody"}}}},"responses":{"200":{"description":"The session with amount_refunded updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutSessionEnvelope"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"}}}},"/v1/customers":{"post":{"operationId":"CreateCustomer","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[A-Za-z0-9:_.-]+$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCustomerBody"}}}},"responses":{"200":{"description":"The created customer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerEnvelope"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"}}}},"/v1/setup_intents/{setupIntentId}":{"parameters":[{"name":"setupIntentId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"setup_intent","missing":"fseti_00000000000000000000000000"}}}],"get":{"operationId":"GetSetupIntent","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"expand","in":"query","required":false,"description":"Comma-separated fields to expand (customer, payment_method).","schema":{"type":"string","enum":["customer","payment_method","customer,payment_method"]}}],"responses":{"200":{"description":"The setup intent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetupIntentEnvelope"}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/v1/subscriptions/{subscriptionId}":{"parameters":[{"name":"subscriptionId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"subscription","missing":"fsub_00000000000000000000000000"}}}],"get":{"operationId":"GetSubscription","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The subscription a completed subscription-mode checkout session created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionEnvelope"}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/pay/{sessionId}":{"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"operationId":"HostedCheckoutPage","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":false,"safe":true,"reason":"A browser-facing HTML page; covered by the acceptance suite, not by JSON walks."}},"responses":{"200":{"description":"The card form (or the letter-of-medical-necessity step)","content":{"text/html":{"schema":{"type":"string"}}}},"404":{"description":"Unknown session","content":{"text/html":{"schema":{"type":"string"}}}},"409":{"description":"The session is no longer payable","content":{"text/html":{"schema":{"type":"string"}}}}}},"post":{"operationId":"SubmitHostedCheckout","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":false,"safe":false,"reason":"A browser form post; covered by the acceptance suite."}},"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"step":{"type":"string","enum":["card","lmn"]},"cardNumber":{"type":"string"},"expiry":{"type":"string"},"cvc":{"type":"string"},"postalCode":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"phone":{"type":"string"}}}}}},"responses":{"302":{"description":"Paid; redirects to success_url (or to the next action's page)"},"400":{"description":"The card form is incomplete or the card number is invalid","content":{"text/html":{"schema":{"type":"string"}}}},"402":{"description":"The card was declined","content":{"text/html":{"schema":{"type":"string"}}}},"404":{"description":"Unknown session","content":{"text/html":{"schema":{"type":"string"}}}},"409":{"description":"The session is no longer payable","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/pay/{sessionId}/cancel":{"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"operationId":"CancelHostedCheckout","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":false,"safe":true,"reason":"A browser navigation to cancel_url; covered by the acceptance suite."}},"responses":{"302":{"description":"Redirects to cancel_url; the session stays open"},"404":{"description":"Unknown session","content":{"text/html":{"schema":{"type":"string"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"A secret key, \`fsk_test_\u2026\` (test mode) or \`fsk_\u2026\` (live mode)."}},"responses":{"Error":{"description":"An error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"schemas":{"ErrorBody":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["type","message"],"properties":{"type":{"type":"string","enum":["invalid_request_error","authentication_error","idempotency_error","api_error","card_error"]},"code":{"type":"string"},"message":{"type":"string"},"param":{"type":"string"}}}}},"StringMap":{"type":"object","maxProperties":50,"additionalProperties":{"type":"string","maxLength":500}},"Product":{"type":"object","required":["product_id","name","active","test_mode","created_at"],"properties":{"product_id":{"type":"string","x-mockingbird-resource":{"type":"product","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"name":{"type":"string"},"description":{"type":["string","null"]},"url":{"type":["string","null"]},"client_reference_id":{"type":["string","null"]},"hsa_fsa_eligibility":{"description":"not_eligible, auto_substantiation, private_label, letter_of_medical_necessity, prescription, vision or service; null until Flex classifies a new product.","type":["string","null"]},"visit_type":{"type":["string","null"]},"active":{"type":"boolean"},"test_mode":{"type":"boolean"},"metadata":{"type":["object","null"],"additionalProperties":{"type":"string"}},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"ProductEnvelope":{"type":"object","required":["product"],"properties":{"product":{"$ref":"#/components/schemas/Product"}}},"ProductList":{"type":"object","required":["products","has_more"],"properties":{"products":{"type":"array","items":{"$ref":"#/components/schemas/Product"}},"has_more":{"type":"boolean"}}},"CreateProductBody":{"type":"object","required":["product"],"properties":{"product":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":250},"description":{"type":"string","maxLength":1000},"url":{"type":"string","maxLength":2048},"client_reference_id":{"type":"string","minLength":1,"maxLength":255},"metadata":{"$ref":"#/components/schemas/StringMap"}}}}},"UpdateProductBody":{"type":"object","required":["product"],"properties":{"product":{"type":"object","properties":{"active":{"type":"boolean"},"name":{"type":"string","minLength":1,"maxLength":250},"description":{"type":"string","maxLength":1000},"url":{"type":"string","maxLength":2048},"metadata":{"$ref":"#/components/schemas/StringMap"}}}}},"LineItem":{"type":"object","required":["price_data","quantity"],"properties":{"price_data":{"type":"object","required":["product","unit_amount"],"properties":{"product":{"type":"string","x-mockingbird-resource-ref":{"type":"product","missing":"fprod_00000000000000000000000000"}},"unit_amount":{"type":"integer","minimum":0,"maximum":10000000},"recurring":{"$ref":"#/components/schemas/Recurring"}}},"quantity":{"type":"integer","minimum":1,"maximum":100}}},"Recurring":{"type":"object","description":"Makes the line item's price recurring; a subscription-mode session needs at least one.","required":["interval"],"properties":{"interval":{"type":"string","enum":["day","week","month","year"]},"interval_count":{"type":"integer","minimum":1,"maximum":12}}},"SubscriptionData":{"type":"object","properties":{"cancel_at_period_end":{"type":"boolean"},"metadata":{"$ref":"#/components/schemas/StringMap"}}},"CreateCheckoutSessionBody":{"type":"object","required":["checkout_session"],"properties":{"checkout_session":{"type":"object","required":["success_url","line_items"],"properties":{"allow_promotion_codes":{"type":"boolean"},"capture_method":{"type":"string","enum":["automatic"]},"cancel_url":{"type":"string","minLength":1,"maxLength":2048},"success_url":{"type":"string","minLength":1,"maxLength":2048},"client_reference_id":{"type":"string","minLength":1,"maxLength":255},"line_items":{"type":"array","maxItems":20,"items":{"$ref":"#/components/schemas/LineItem"}},"metadata":{"$ref":"#/components/schemas/StringMap"},"mode":{"type":"string","enum":["payment","subscription","off_session","setup"]},"subscription_data":{"$ref":"#/components/schemas/SubscriptionData"},"payment_method":{"type":"string","maxLength":255},"customer":{"type":"string","x-mockingbird-resource-ref":{"type":"customer","missing":"fcus_00000000000000000000000000"}},"setup_future_use":{"type":"string","enum":["off_session"]}}}}},"RefundCheckoutSessionBody":{"type":"object","required":["checkout_session"],"properties":{"checkout_session":{"type":"object","properties":{"amount":{"type":"integer","minimum":1,"maximum":10000000}}}}},"CreateCustomerBody":{"type":"object","required":["customer"],"properties":{"customer":{"type":"object","required":["first_name","last_name","email","phone"],"properties":{"first_name":{"type":"string","minLength":1,"maxLength":100},"last_name":{"type":"string","minLength":1,"maxLength":100},"email":{"type":"string","format":"email","maxLength":254},"phone":{"type":"string","minLength":1,"maxLength":32}}}}},"Customer":{"type":"object","required":["customer_id","test_mode","created_at"],"properties":{"customer_id":{"type":"string","x-mockingbird-resource":{"type":"customer","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"first_name":{"type":["string","null"]},"last_name":{"type":["string","null"]},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"test_mode":{"type":"boolean"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"CustomerEnvelope":{"type":"object","required":["customer"],"properties":{"customer":{"$ref":"#/components/schemas/Customer"}}},"PaymentMethod":{"type":"object","required":["payment_method_id","type"],"properties":{"payment_method_id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"type":{"type":"string","enum":["card"]},"hsa_fsa":{"type":"boolean"},"customer":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"id"}}}},"PaymentIntent":{"type":"object","required":["payment_intent_id","amount","status"],"properties":{"payment_intent_id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"amount":{"type":"integer"},"amount_received":{"type":["integer","null"]},"customer":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"id"}},"payment_method":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"id"}},"status":{"type":"string","enum":["requires_payment_method","requires_action","processing","succeeded","canceled"]},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"SetupIntent":{"type":"object","required":["setup_intent_id","status"],"properties":{"setup_intent_id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"status":{"type":"string","enum":["requires_payment_method","requires_action","processing","succeeded","canceled"]},"customer":{"anyOf":[{"type":"string"},{"$ref":"#/components/schemas/Customer"},{"type":"null"}]},"payment_method":{"anyOf":[{"type":"string"},{"$ref":"#/components/schemas/PaymentMethod"},{"type":"null"}]},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"SetupIntentEnvelope":{"type":"object","required":["setup_intent"],"properties":{"setup_intent":{"$ref":"#/components/schemas/SetupIntent"}}},"NextAction":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["collect_letter_of_medical_necessity","provide_second_payment_method","provide_alternative_payment_method","payment_failed"]},"collect_letter_of_medical_necessity":{"type":"object","required":["url"],"properties":{"url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}}}},"provide_second_payment_method":{"type":"object","required":["url"],"properties":{"url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}}}},"provide_alternative_payment_method":{"type":"object","required":["url"],"properties":{"url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}}}},"payment_failed":{"type":"object"}}},"ResponseLineItem":{"type":"object","required":["price_data","quantity","amount_total"],"properties":{"price_data":{"type":"object","required":["product","unit_amount"],"properties":{"product":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"unit_amount":{"type":"integer"},"recurring":{"anyOf":[{"$ref":"#/components/schemas/Recurring"},{"type":"null"}]}}},"quantity":{"type":"integer"},"amount_total":{"type":"integer"}}},"CheckoutSession":{"type":"object","required":["checkout_session_id","amount_total","status","mode","redirect_url","url","test_mode","created_at"],"properties":{"checkout_session_id":{"type":"string","x-mockingbird-resource":{"type":"checkout_session","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"client_reference_id":{"type":["string","null"]},"amount_total":{"type":"integer","minimum":0},"amount_received":{"type":["integer","null"]},"amount_refunded":{"type":"integer","minimum":0},"customer":{"anyOf":[{"type":"string","x-mockingbird-volatile":{"kind":"id"}},{"$ref":"#/components/schemas/Customer"},{"type":"null"}]},"payment_intent":{"anyOf":[{"type":"string","x-mockingbird-volatile":{"kind":"id"}},{"$ref":"#/components/schemas/PaymentIntent"},{"type":"null"}]},"setup_intent":{"type":["string","null"],"x-mockingbird-resource":{"type":"setup_intent","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"mode":{"type":"string","enum":["payment","subscription","off_session","setup"]},"subscription":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"id"}},"subscription_data":{"anyOf":[{"$ref":"#/components/schemas/SubscriptionData"},{"type":"null"}]},"status":{"type":"string","enum":["open","paid","complete","canceled","expired"]},"redirect_url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}},"url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}},"success_url":{"type":"string"},"cancel_url":{"type":["string","null"]},"next_action":{"anyOf":[{"$ref":"#/components/schemas/NextAction"},{"type":"null"}]},"visit_type":{"type":["string","null"]},"metadata":{"type":["object","null"],"additionalProperties":{"type":"string"}},"line_items":{"type":"array","items":{"$ref":"#/components/schemas/ResponseLineItem"}},"allow_promotion_codes":{"type":"boolean"},"capture_method":{"type":"string"},"setup_future_use":{"type":["string","null"]},"test_mode":{"type":"boolean"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"expires_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"CheckoutSessionEnvelope":{"type":"object","required":["checkout_session"],"properties":{"checkout_session":{"$ref":"#/components/schemas/CheckoutSession"}}},"CheckoutSessionList":{"type":"object","required":["checkout_sessions","has_more"],"properties":{"checkout_sessions":{"type":"array","items":{"$ref":"#/components/schemas/CheckoutSession"}},"has_more":{"type":"boolean"}}},"SubscriptionItem":{"type":"object","required":["price_data","quantity"],"properties":{"price_data":{"type":"object","required":["product","unit_amount","recurring"],"properties":{"product":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"unit_amount":{"type":"integer"},"recurring":{"$ref":"#/components/schemas/Recurring"}}},"quantity":{"type":"integer"}}},"Subscription":{"type":"object","required":["subscription_id","created_at","items","status","cancel_at_period_end","test_mode"],"properties":{"subscription_id":{"type":"string","x-mockingbird-resource":{"type":"subscription","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"status":{"type":"string","enum":["incomplete","incomplete_expired","trialing","active","past_due","canceled","unpaid","paused"]},"items":{"type":"array","items":{"$ref":"#/components/schemas/SubscriptionItem"}},"customer":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"id"}},"default_payment_method":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"id"}},"cancel_at_period_end":{"type":"boolean"},"current_period_start":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"current_period_end":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"canceled_at":{"type":["string","null"]},"metadata":{"type":["object","null"],"additionalProperties":{"type":"string"}},"test_mode":{"type":"boolean"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"SubscriptionEnvelope":{"type":"object","required":["subscription"],"properties":{"subscription":{"$ref":"#/components/schemas/Subscription"}}}}}}`);
|
|
4831
|
+
var document2 = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Flex HSA/FSA payments API (Mockingbird subset)","description":"Stateful mock subset of the Flex (withflex.com) payments API: products, checkout sessions\\n(payment, subscription, off-session and setup modes), subscriptions, customers, setup\\nintents, refunds, and the hosted checkout page. Request and response bodies are wrapped in an envelope named after the\\nobject (\`{product: {...}}\`, \`{checkout_session: {...}}\`, \`{products: [...]}\`).\\nHand-authored from the consumer's wire shapes and the Flex API reference\\n(https://docs.withflex.com/api-reference); Flex publishes no machine-readable spec.\\n","version":"1","x-mockingbird-upstream":{"note":"Hand-authored from the wire shapes our consumer reads and writes (flex-api.client.ts, flex-payment.types.ts, flex-product.types.ts, flex-webhook.service.ts, do-flex-hosted-checkout.ts). Subscription mode, recurring price_data and the Subscription object follow https://docs.withflex.com/api-reference/checkout-sessions/create-checkout-session, https://docs.withflex.com/api-reference/subscriptions/get-subscription and https://docs.withflex.com/api-reference/prices/create-price."}},"servers":[{"url":"https://api.withflex.com"}],"security":[{"bearerAuth":[]}],"paths":{"/v1/products":{"get":{"operationId":"ListProducts","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"starting_after","in":"query","required":false,"description":"The last product_id of the previous page.","schema":{"type":"string","x-mockingbird-resource-ref":{"type":"product","missing":"fprod_00000000000000000000000000"}}}],"responses":{"200":{"description":"A page of products, oldest first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductList"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"}}},"post":{"operationId":"CreateProduct","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProductBody"}}}},"responses":{"200":{"description":"The created product","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductEnvelope"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"}}}},"/v1/products/{productId}":{"parameters":[{"name":"productId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"product","missing":"fprod_00000000000000000000000000"}}}],"get":{"operationId":"GetProduct","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The product","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductEnvelope"}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}},"patch":{"operationId":"UpdateProduct","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProductBody"}}}},"responses":{"200":{"description":"The updated product (emits product.updated)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductEnvelope"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/v1/checkout/sessions":{"get":{"operationId":"ListCheckoutSessions","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"client_reference_id","in":"query","required":false,"schema":{"type":"string","maxLength":255}},{"name":"expand_customer","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}},{"name":"expand_payment_intent","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"starting_after","in":"query","required":false,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"checkout_session","missing":"fcs_00000000000000000000000000"}}}],"responses":{"200":{"description":"Sessions, newest first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutSessionList"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"}}},"post":{"operationId":"CreateCheckoutSession","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[A-Za-z0-9:_.-]+$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCheckoutSessionBody"}}}},"responses":{"200":{"description":"The created session. Off-session mode charges the saved payment method before answering, so the session is already complete (or its payment intent failed).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutSessionEnvelope"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"}}}},"/v1/checkout/sessions/{sessionId}":{"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"checkout_session","missing":"fcs_00000000000000000000000000"}}}],"get":{"operationId":"GetCheckoutSession","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"expand_customer","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}},{"name":"expand_payment_intent","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}}],"responses":{"200":{"description":"The session","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutSessionEnvelope"}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/v1/checkout/sessions/{sessionId}/refund":{"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"checkout_session","missing":"fcs_00000000000000000000000000"}}}],"post":{"operationId":"RefundCheckoutSession","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[A-Za-z0-9:_.-]+$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefundCheckoutSessionBody"}}}},"responses":{"200":{"description":"The session with amount_refunded updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutSessionEnvelope"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"}}}},"/v1/customers":{"post":{"operationId":"CreateCustomer","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[A-Za-z0-9:_.-]+$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCustomerBody"}}}},"responses":{"200":{"description":"The created customer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerEnvelope"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"}}}},"/v1/setup_intents/{setupIntentId}":{"parameters":[{"name":"setupIntentId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"setup_intent","missing":"fseti_00000000000000000000000000"}}}],"get":{"operationId":"GetSetupIntent","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"expand","in":"query","required":false,"description":"Comma-separated fields to expand (customer, payment_method).","schema":{"type":"string","enum":["customer","payment_method","customer,payment_method"]}}],"responses":{"200":{"description":"The setup intent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetupIntentEnvelope"}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/v1/subscriptions/{subscriptionId}":{"parameters":[{"name":"subscriptionId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"subscription","missing":"fsub_00000000000000000000000000"}}}],"get":{"operationId":"GetSubscription","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The subscription a completed subscription-mode checkout session created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionEnvelope"}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/pay/{sessionId}":{"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"operationId":"HostedCheckoutPage","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":false,"safe":true,"reason":"A browser-facing HTML page; covered by the acceptance suite, not by JSON walks."}},"responses":{"200":{"description":"The card form (or the letter-of-medical-necessity step)","content":{"text/html":{"schema":{"type":"string"}}}},"404":{"description":"Unknown session","content":{"text/html":{"schema":{"type":"string"}}}},"409":{"description":"The session is no longer payable","content":{"text/html":{"schema":{"type":"string"}}}}}},"post":{"operationId":"SubmitHostedCheckout","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":false,"safe":false,"reason":"A browser form post; covered by the acceptance suite."}},"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"step":{"type":"string","enum":["card","lmn"]},"cardNumber":{"type":"string"},"expiry":{"type":"string"},"cvc":{"type":"string"},"postalCode":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"phone":{"type":"string"}}}}}},"responses":{"302":{"description":"Paid; redirects to success_url (or to the next action's page)"},"400":{"description":"The card form is incomplete or the card number is invalid","content":{"text/html":{"schema":{"type":"string"}}}},"402":{"description":"The card was declined","content":{"text/html":{"schema":{"type":"string"}}}},"404":{"description":"Unknown session","content":{"text/html":{"schema":{"type":"string"}}}},"409":{"description":"The session is no longer payable","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/pay/{sessionId}/cancel":{"parameters":[{"name":"sessionId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"operationId":"CancelHostedCheckout","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":false,"safe":true,"reason":"A browser navigation to cancel_url; covered by the acceptance suite."}},"responses":{"302":{"description":"Redirects to cancel_url; the session stays open"},"404":{"description":"Unknown session","content":{"text/html":{"schema":{"type":"string"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"A secret key, \`fsk_test_\u2026\` (test mode) or \`fsk_\u2026\` (live mode)."}},"responses":{"Error":{"description":"An error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"schemas":{"ErrorBody":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["type","message"],"properties":{"type":{"type":"string","enum":["invalid_request_error","authentication_error","idempotency_error","api_error","card_error"]},"code":{"type":"string"},"message":{"type":"string"},"param":{"type":"string"}}}}},"StringMap":{"type":"object","maxProperties":50,"additionalProperties":{"type":"string","maxLength":500}},"Product":{"type":"object","required":["product_id","name","active","test_mode","created_at"],"properties":{"product_id":{"type":"string","x-mockingbird-resource":{"type":"product","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"name":{"type":"string"},"description":{"type":["string","null"]},"url":{"type":["string","null"]},"client_reference_id":{"type":["string","null"]},"hsa_fsa_eligibility":{"description":"not_eligible, auto_substantiation, private_label, letter_of_medical_necessity, prescription, vision or service; null until Flex classifies a new product.","type":["string","null"]},"visit_type":{"type":["string","null"]},"active":{"type":"boolean"},"test_mode":{"type":"boolean"},"metadata":{"type":["object","null"],"additionalProperties":{"type":"string"}},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"ProductEnvelope":{"type":"object","required":["product"],"properties":{"product":{"$ref":"#/components/schemas/Product"}}},"ProductList":{"type":"object","required":["products","has_more"],"properties":{"products":{"type":"array","items":{"$ref":"#/components/schemas/Product"}},"has_more":{"type":"boolean"}}},"CreateProductBody":{"type":"object","required":["product"],"properties":{"product":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":250},"description":{"type":"string","maxLength":1000},"url":{"type":"string","maxLength":2048},"client_reference_id":{"type":"string","minLength":1,"maxLength":255},"metadata":{"$ref":"#/components/schemas/StringMap"}}}}},"UpdateProductBody":{"type":"object","required":["product"],"properties":{"product":{"type":"object","properties":{"active":{"type":"boolean"},"name":{"type":"string","minLength":1,"maxLength":250},"description":{"type":"string","maxLength":1000},"url":{"type":"string","maxLength":2048},"metadata":{"$ref":"#/components/schemas/StringMap"}}}}},"LineItem":{"type":"object","required":["price_data","quantity"],"properties":{"price_data":{"type":"object","required":["product","unit_amount"],"properties":{"product":{"type":"string","x-mockingbird-resource-ref":{"type":"product","missing":"fprod_00000000000000000000000000"}},"unit_amount":{"type":"integer","minimum":0,"maximum":10000000},"recurring":{"$ref":"#/components/schemas/Recurring"}}},"quantity":{"type":"integer","minimum":1,"maximum":100}}},"Recurring":{"type":"object","description":"Makes the line item's price recurring; a subscription-mode session needs at least one.","required":["interval"],"properties":{"interval":{"type":"string","enum":["day","week","month","year"]},"interval_count":{"type":"integer","minimum":1,"maximum":12}}},"SubscriptionData":{"type":"object","properties":{"cancel_at_period_end":{"type":"boolean"},"metadata":{"$ref":"#/components/schemas/StringMap"}}},"CreateCheckoutSessionBody":{"type":"object","required":["checkout_session"],"properties":{"checkout_session":{"type":"object","required":["success_url","line_items"],"properties":{"allow_promotion_codes":{"type":"boolean"},"capture_method":{"type":"string","enum":["automatic"]},"cancel_url":{"type":"string","minLength":1,"maxLength":2048},"success_url":{"type":"string","minLength":1,"maxLength":2048},"client_reference_id":{"type":"string","minLength":1,"maxLength":255},"line_items":{"type":"array","maxItems":20,"items":{"$ref":"#/components/schemas/LineItem"}},"metadata":{"$ref":"#/components/schemas/StringMap"},"mode":{"type":"string","enum":["payment","subscription","off_session","setup"]},"subscription_data":{"$ref":"#/components/schemas/SubscriptionData"},"payment_method":{"type":"string","maxLength":255},"customer":{"type":"string","x-mockingbird-resource-ref":{"type":"customer","missing":"fcus_00000000000000000000000000"}},"setup_future_use":{"type":"string","enum":["off_session"]}}}}},"RefundCheckoutSessionBody":{"type":"object","required":["checkout_session"],"properties":{"checkout_session":{"type":"object","properties":{"amount":{"type":"integer","minimum":1,"maximum":10000000}}}}},"CreateCustomerBody":{"type":"object","required":["customer"],"properties":{"customer":{"type":"object","required":["first_name","last_name","email","phone"],"properties":{"first_name":{"type":"string","minLength":1,"maxLength":100},"last_name":{"type":"string","minLength":1,"maxLength":100},"email":{"type":"string","format":"email","maxLength":254},"phone":{"type":"string","minLength":1,"maxLength":32}}}}},"Customer":{"type":"object","required":["customer_id","test_mode","created_at"],"properties":{"customer_id":{"type":"string","x-mockingbird-resource":{"type":"customer","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"first_name":{"type":["string","null"]},"last_name":{"type":["string","null"]},"email":{"type":["string","null"]},"phone":{"type":["string","null"]},"test_mode":{"type":"boolean"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"CustomerEnvelope":{"type":"object","required":["customer"],"properties":{"customer":{"$ref":"#/components/schemas/Customer"}}},"PaymentMethod":{"type":"object","required":["payment_method_id","type"],"properties":{"payment_method_id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"type":{"type":"string","enum":["card"]},"hsa_fsa":{"type":"boolean"},"customer":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"id"}}}},"PaymentIntent":{"type":"object","required":["payment_intent_id","amount","status"],"properties":{"payment_intent_id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"amount":{"type":"integer"},"amount_received":{"type":["integer","null"]},"customer":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"id"}},"payment_method":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"id"}},"status":{"type":"string","enum":["requires_payment_method","requires_action","processing","succeeded","canceled"]},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"SetupIntent":{"type":"object","required":["setup_intent_id","status"],"properties":{"setup_intent_id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"status":{"type":"string","enum":["requires_payment_method","requires_action","processing","succeeded","canceled"]},"customer":{"anyOf":[{"type":"string"},{"$ref":"#/components/schemas/Customer"},{"type":"null"}]},"payment_method":{"anyOf":[{"type":"string"},{"$ref":"#/components/schemas/PaymentMethod"},{"type":"null"}]},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"SetupIntentEnvelope":{"type":"object","required":["setup_intent"],"properties":{"setup_intent":{"$ref":"#/components/schemas/SetupIntent"}}},"NextAction":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["collect_letter_of_medical_necessity","provide_second_payment_method","provide_alternative_payment_method","payment_failed"]},"collect_letter_of_medical_necessity":{"type":"object","required":["url"],"properties":{"url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}}}},"provide_second_payment_method":{"type":"object","required":["url"],"properties":{"url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}}}},"provide_alternative_payment_method":{"type":"object","required":["url"],"properties":{"url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}}}},"payment_failed":{"type":"object"}}},"ResponseLineItem":{"type":"object","required":["price_data","quantity","amount_total"],"properties":{"price_data":{"type":"object","required":["product","unit_amount"],"properties":{"product":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"unit_amount":{"type":"integer"},"recurring":{"anyOf":[{"$ref":"#/components/schemas/Recurring"},{"type":"null"}]}}},"quantity":{"type":"integer"},"amount_total":{"type":"integer"}}},"CheckoutSession":{"type":"object","required":["checkout_session_id","amount_total","status","mode","redirect_url","url","test_mode","created_at"],"properties":{"checkout_session_id":{"type":"string","x-mockingbird-resource":{"type":"checkout_session","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"client_reference_id":{"type":["string","null"]},"amount_total":{"type":"integer","minimum":0},"amount_received":{"type":["integer","null"]},"amount_refunded":{"type":"integer","minimum":0},"customer":{"anyOf":[{"type":"string","x-mockingbird-volatile":{"kind":"id"}},{"$ref":"#/components/schemas/Customer"},{"type":"null"}]},"payment_intent":{"anyOf":[{"type":"string","x-mockingbird-volatile":{"kind":"id"}},{"$ref":"#/components/schemas/PaymentIntent"},{"type":"null"}]},"setup_intent":{"type":["string","null"],"x-mockingbird-resource":{"type":"setup_intent","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"mode":{"type":"string","enum":["payment","subscription","off_session","setup"]},"subscription":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"id"}},"subscription_data":{"anyOf":[{"$ref":"#/components/schemas/SubscriptionData"},{"type":"null"}]},"status":{"type":"string","enum":["open","paid","complete","canceled","expired"]},"redirect_url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}},"url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}},"success_url":{"type":"string"},"cancel_url":{"type":["string","null"]},"next_action":{"anyOf":[{"$ref":"#/components/schemas/NextAction"},{"type":"null"}]},"visit_type":{"type":["string","null"]},"metadata":{"type":["object","null"],"additionalProperties":{"type":"string"}},"line_items":{"type":"array","items":{"$ref":"#/components/schemas/ResponseLineItem"}},"allow_promotion_codes":{"type":"boolean"},"capture_method":{"type":"string"},"setup_future_use":{"type":["string","null"]},"test_mode":{"type":"boolean"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"expires_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"CheckoutSessionEnvelope":{"type":"object","required":["checkout_session"],"properties":{"checkout_session":{"$ref":"#/components/schemas/CheckoutSession"}}},"CheckoutSessionList":{"type":"object","required":["checkout_sessions","has_more"],"properties":{"checkout_sessions":{"type":"array","items":{"$ref":"#/components/schemas/CheckoutSession"}},"has_more":{"type":"boolean"}}},"SubscriptionItem":{"type":"object","required":["price_data","quantity"],"properties":{"price_data":{"type":"object","required":["product","unit_amount","recurring"],"properties":{"product":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"unit_amount":{"type":"integer"},"recurring":{"$ref":"#/components/schemas/Recurring"}}},"quantity":{"type":"integer"}}},"Subscription":{"type":"object","required":["subscription_id","created_at","items","status","cancel_at_period_end","test_mode"],"properties":{"subscription_id":{"type":"string","x-mockingbird-resource":{"type":"subscription","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"status":{"type":"string","enum":["incomplete","incomplete_expired","trialing","active","past_due","canceled","unpaid","paused"]},"items":{"type":"array","items":{"$ref":"#/components/schemas/SubscriptionItem"}},"customer":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"id"}},"default_payment_method":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"id"}},"cancel_at_period_end":{"type":"boolean"},"current_period_start":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"current_period_end":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"canceled_at":{"type":["string","null"]},"metadata":{"type":["object","null"],"additionalProperties":{"type":"string"}},"test_mode":{"type":"boolean"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"SubscriptionEnvelope":{"type":"object","required":["subscription"],"properties":{"subscription":{"$ref":"#/components/schemas/Subscription"}}}}}}`);
|
|
4028
4832
|
var operationIds = ["ListProducts", "CreateProduct", "GetProduct", "UpdateProduct", "ListCheckoutSessions", "CreateCheckoutSession", "GetCheckoutSession", "RefundCheckoutSession", "CreateCustomer", "GetSetupIntent", "GetSubscription", "HostedCheckoutPage", "SubmitHostedCheckout", "CancelHostedCheckout"];
|
|
4029
4833
|
var supportedOperationIds = ["ListProducts", "CreateProduct", "GetProduct", "UpdateProduct", "ListCheckoutSessions", "CreateCheckoutSession", "GetCheckoutSession", "RefundCheckoutSession", "CreateCustomer", "GetSetupIntent", "GetSubscription", "HostedCheckoutPage", "SubmitHostedCheckout", "CancelHostedCheckout"];
|
|
4030
4834
|
|
|
@@ -4074,12 +4878,16 @@ var layout = (title, body) => `<!doctype html>
|
|
|
4074
4878
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
4075
4879
|
<title>${escapeHtml2(title)}</title>
|
|
4076
4880
|
<style>
|
|
4077
|
-
|
|
4881
|
+
${CSS_RESET}
|
|
4882
|
+
body{font-family:system-ui,sans-serif;max-width:28rem;margin:2rem auto;padding:0 1rem;color:#1a1a1a;background:#fff}
|
|
4883
|
+
h1{font-size:1.5rem;font-weight:650;margin:0 0 .75rem}
|
|
4884
|
+
p{margin:0 0 .75rem}
|
|
4885
|
+
ul{list-style:disc;padding-left:1.25rem}
|
|
4078
4886
|
label{display:block;margin:.75rem 0 .25rem;font-size:.9rem}
|
|
4079
|
-
input{width:100%;
|
|
4080
|
-
button{margin-top:1rem;width:100%;padding:.75rem;font-size:1rem}
|
|
4887
|
+
input{width:100%;min-width:0;max-width:100%;padding:.5rem;font-size:1rem;border:1px solid #c8c8c8;border-radius:6px;background:#fff}
|
|
4888
|
+
button{margin-top:1rem;width:100%;padding:.75rem;font-size:1rem;background:#1a1a1a;color:#fff;border-radius:6px}
|
|
4081
4889
|
[role=alert]{color:#b00020;margin:1rem 0}
|
|
4082
|
-
.row{display:flex;gap:.5rem}.row>div{flex:1}
|
|
4890
|
+
.row{display:flex;flex-wrap:wrap;gap:.5rem;min-width:0}.row>div{flex:1;min-width:min(100%,8rem)}
|
|
4083
4891
|
</style>
|
|
4084
4892
|
</head>
|
|
4085
4893
|
<body>
|
|
@@ -5044,9 +5852,9 @@ var FLEX_PRESETS = {
|
|
|
5044
5852
|
};
|
|
5045
5853
|
var json5 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
5046
5854
|
var adminError4 = (status, message) => json5(status, { error: { type: "mockingbird_admin", message } });
|
|
5047
|
-
var
|
|
5855
|
+
var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5048
5856
|
var parseSettings = (body) => {
|
|
5049
|
-
if (!
|
|
5857
|
+
if (!isRecord6(body)) return "expected a JSON object";
|
|
5050
5858
|
const patch = {};
|
|
5051
5859
|
if (body.eventNaming !== void 0) {
|
|
5052
5860
|
if (body.eventNaming !== "dotted" && body.eventNaming !== "underscored") {
|
|
@@ -5091,7 +5899,7 @@ var adminRoutes = (runtime) => {
|
|
|
5091
5899
|
"POST /sessions/:id/complete": ({ namespace, params, body }) => {
|
|
5092
5900
|
const closed = notOpen(namespace, params.id);
|
|
5093
5901
|
if (closed) return closed;
|
|
5094
|
-
const hsa = !(
|
|
5902
|
+
const hsa = !(isRecord6(body) && body.card === "4242424242424242");
|
|
5095
5903
|
return found(
|
|
5096
5904
|
runtime.instance(namespace).settle(params.id, { hsa }),
|
|
5097
5905
|
`session ${params.id}`
|
|
@@ -5102,7 +5910,7 @@ var adminRoutes = (runtime) => {
|
|
|
5102
5910
|
"POST /sessions/:id/require_action": ({ namespace, params, body }) => {
|
|
5103
5911
|
const closed = notOpen(namespace, params.id);
|
|
5104
5912
|
if (closed) return closed;
|
|
5105
|
-
const type =
|
|
5913
|
+
const type = isRecord6(body) ? body.next_action_type : void 0;
|
|
5106
5914
|
if (type !== void 0 && !isNextActionType(type)) {
|
|
5107
5915
|
return adminError4(
|
|
5108
5916
|
400,
|
|
@@ -5115,7 +5923,7 @@ var adminRoutes = (runtime) => {
|
|
|
5115
5923
|
);
|
|
5116
5924
|
},
|
|
5117
5925
|
"PUT /sessions/:id/payment-intent": ({ namespace, params, body }) => {
|
|
5118
|
-
if (!
|
|
5926
|
+
if (!isRecord6(body) || !PAYMENT_INTENT_STATUSES.includes(String(body.status))) {
|
|
5119
5927
|
return adminError4(400, `status: ${PAYMENT_INTENT_STATUSES.join(" | ")}`);
|
|
5120
5928
|
}
|
|
5121
5929
|
const api = runtime.instance(namespace);
|
|
@@ -5126,7 +5934,7 @@ var adminRoutes = (runtime) => {
|
|
|
5126
5934
|
return found(intent, `session ${params.id}`);
|
|
5127
5935
|
},
|
|
5128
5936
|
"PUT /products/:id": ({ namespace, params, body }) => {
|
|
5129
|
-
if (!
|
|
5937
|
+
if (!isRecord6(body)) return adminError4(400, "expected a JSON object");
|
|
5130
5938
|
const api = runtime.instance(namespace);
|
|
5131
5939
|
const product = api.state.products.get(params.id);
|
|
5132
5940
|
if (!product) return adminError4(404, `no product ${params.id}`);
|
|
@@ -5149,14 +5957,14 @@ var adminRoutes = (runtime) => {
|
|
|
5149
5957
|
next.name = body.name;
|
|
5150
5958
|
}
|
|
5151
5959
|
if (body.metadata !== void 0) {
|
|
5152
|
-
if (body.metadata !== null && !
|
|
5960
|
+
if (body.metadata !== null && !isRecord6(body.metadata))
|
|
5153
5961
|
return adminError4(400, "metadata: object | null");
|
|
5154
5962
|
next.metadata = body.metadata;
|
|
5155
5963
|
}
|
|
5156
5964
|
return json5(200, api.putProduct(next));
|
|
5157
5965
|
},
|
|
5158
5966
|
"POST /events": ({ namespace, body }) => {
|
|
5159
|
-
if (!
|
|
5967
|
+
if (!isRecord6(body) || typeof body.type !== "string") {
|
|
5160
5968
|
return adminError4(400, 'expected {"type": "<event type>", "session"?: id, "product"?: id}');
|
|
5161
5969
|
}
|
|
5162
5970
|
const event = runtime.instance(namespace).emitFor(body.type, {
|
|
@@ -5189,7 +5997,7 @@ var createRuntime2 = (options = {}) => {
|
|
|
5189
5997
|
});
|
|
5190
5998
|
const runtime = createRuntime({
|
|
5191
5999
|
name: FLEX_NAMESPACE,
|
|
5192
|
-
document,
|
|
6000
|
+
document: document2,
|
|
5193
6001
|
...options.sqlite ? { sqlite: options.sqlite } : {},
|
|
5194
6002
|
...options.clock ? { clock: options.clock } : {},
|
|
5195
6003
|
...options.seed !== void 0 ? { seed: options.seed } : {},
|
|
@@ -5260,11 +6068,11 @@ var keyMode = (key) => {
|
|
|
5260
6068
|
var HOSTED = /* @__PURE__ */ new Set(["HostedCheckoutPage", "SubmitHostedCheckout", "CancelHostedCheckout"]);
|
|
5261
6069
|
var error = (status, type, message, extra = {}) => jsonRes(status, { error: { type, message, ...extra } });
|
|
5262
6070
|
var invalid = (message, param) => error(400, "invalid_request_error", message, param ? { param } : {});
|
|
5263
|
-
var
|
|
6071
|
+
var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5264
6072
|
var envelope = (context, name) => {
|
|
5265
6073
|
const body = context.body.kind === "json" ? context.body.value : void 0;
|
|
5266
|
-
const inner =
|
|
5267
|
-
return
|
|
6074
|
+
const inner = isRecord7(body) ? body[name] : void 0;
|
|
6075
|
+
return isRecord7(inner) ? inner : {};
|
|
5268
6076
|
};
|
|
5269
6077
|
var flag = (value) => value === "true";
|
|
5270
6078
|
var sleep = (ms, signal) => new Promise((resolve) => {
|
|
@@ -5332,7 +6140,7 @@ var FlexAPI = class {
|
|
|
5332
6140
|
CancelHostedCheckout: (context) => this.cancelHosted(context)
|
|
5333
6141
|
});
|
|
5334
6142
|
this.service = createService({
|
|
5335
|
-
document,
|
|
6143
|
+
document: document2,
|
|
5336
6144
|
handlers,
|
|
5337
6145
|
sqlite,
|
|
5338
6146
|
namespace,
|
|
@@ -5434,7 +6242,7 @@ var FlexAPI = class {
|
|
|
5434
6242
|
visit_type: null,
|
|
5435
6243
|
active: true,
|
|
5436
6244
|
test_mode: this.testMode(context),
|
|
5437
|
-
metadata:
|
|
6245
|
+
metadata: isRecord7(input.metadata) ? input.metadata : null,
|
|
5438
6246
|
created_at: this.iso()
|
|
5439
6247
|
};
|
|
5440
6248
|
this.state.products.put(product);
|
|
@@ -5459,7 +6267,7 @@ var FlexAPI = class {
|
|
|
5459
6267
|
...typeof input.name === "string" ? { name: input.name } : {},
|
|
5460
6268
|
...typeof input.description === "string" ? { description: input.description } : {},
|
|
5461
6269
|
...typeof input.url === "string" ? { url: input.url } : {},
|
|
5462
|
-
...
|
|
6270
|
+
...isRecord7(input.metadata) ? { metadata: input.metadata } : {}
|
|
5463
6271
|
};
|
|
5464
6272
|
this.putProduct(next);
|
|
5465
6273
|
return annotateResponse(jsonRes(200, { product: next }), {
|
|
@@ -5591,7 +6399,7 @@ var FlexAPI = class {
|
|
|
5591
6399
|
payment_intent: null,
|
|
5592
6400
|
setup_intent: setupIntent,
|
|
5593
6401
|
subscription: null,
|
|
5594
|
-
subscription_data: mode === "subscription" ?
|
|
6402
|
+
subscription_data: mode === "subscription" ? isRecord7(input.subscription_data) ? input.subscription_data : {} : null,
|
|
5595
6403
|
mode,
|
|
5596
6404
|
status: "open",
|
|
5597
6405
|
url: `${this.pageBase(context)}/pay/${encodeURIComponent(id)}`,
|
|
@@ -5599,7 +6407,7 @@ var FlexAPI = class {
|
|
|
5599
6407
|
cancel_url: typeof input.cancel_url === "string" ? input.cancel_url : null,
|
|
5600
6408
|
next_action: null,
|
|
5601
6409
|
visit_type: products.map((p) => p.visit_type).find((v) => v && v !== "notApplicable") ?? products[0]?.visit_type ?? null,
|
|
5602
|
-
metadata:
|
|
6410
|
+
metadata: isRecord7(input.metadata) ? input.metadata : null,
|
|
5603
6411
|
line_items: items,
|
|
5604
6412
|
allow_promotion_codes: input.allow_promotion_codes === true,
|
|
5605
6413
|
capture_method: typeof input.capture_method === "string" ? input.capture_method : "automatic",
|
|
@@ -6101,7 +6909,7 @@ var FlexAPI = class {
|
|
|
6101
6909
|
var isNextActionType = (value) => typeof value === "string" && NEXT_ACTION_TYPES.includes(value);
|
|
6102
6910
|
|
|
6103
6911
|
export {
|
|
6104
|
-
document,
|
|
6912
|
+
document2 as document,
|
|
6105
6913
|
operationIds,
|
|
6106
6914
|
supportedOperationIds,
|
|
6107
6915
|
CARDS,
|
|
@@ -6123,4 +6931,4 @@ export {
|
|
|
6123
6931
|
FlexAPI,
|
|
6124
6932
|
isNextActionType
|
|
6125
6933
|
};
|
|
6126
|
-
//# sourceMappingURL=chunk-
|
|
6934
|
+
//# sourceMappingURL=chunk-O3RZRWN7.js.map
|