@crvouga/mockingbird-service-easypost 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/{chunk-JKZDMND6.js → chunk-DLVQBVAS.js} +1419 -615
- package/dist/chunk-DLVQBVAS.js.map +7 -0
- package/dist/{chunk-QDDDSZKB.js → chunk-LBRDXRJX.js} +2 -2
- package/dist/cli.js +2 -2
- package/dist/index.js +1 -1
- package/dist/server.js +2 -2
- package/package.json +3 -3
- package/dist/chunk-JKZDMND6.js.map +0 -7
- /package/dist/{chunk-QDDDSZKB.js.map → chunk-LBRDXRJX.js.map} +0 -0
|
@@ -551,10 +551,10 @@ var OpenAPIReferenceError = class extends Error {
|
|
|
551
551
|
};
|
|
552
552
|
var unescapePointer = (segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
553
553
|
var isReference = (value) => typeof value === "object" && value !== null && typeof value.$ref === "string";
|
|
554
|
-
var resolveRef = (
|
|
554
|
+
var resolveRef = (document3, ref) => {
|
|
555
555
|
if (!ref.startsWith("#/"))
|
|
556
556
|
throw new OpenAPIReferenceError(ref);
|
|
557
|
-
let cursor =
|
|
557
|
+
let cursor = document3;
|
|
558
558
|
for (const raw of ref.slice(2).split("/")) {
|
|
559
559
|
const segment = unescapePointer(raw);
|
|
560
560
|
if (typeof cursor !== "object" || cursor === null || !(segment in cursor)) {
|
|
@@ -566,14 +566,14 @@ var resolveRef = (document2, ref) => {
|
|
|
566
566
|
throw new OpenAPIReferenceError(ref);
|
|
567
567
|
return cursor;
|
|
568
568
|
};
|
|
569
|
-
var deref = (
|
|
569
|
+
var deref = (document3, value) => {
|
|
570
570
|
let current = value;
|
|
571
571
|
const seen = /* @__PURE__ */ new Set();
|
|
572
572
|
while (isReference(current)) {
|
|
573
573
|
if (seen.has(current.$ref))
|
|
574
574
|
throw new OpenAPIReferenceError(`${current.$ref} (cycle)`);
|
|
575
575
|
seen.add(current.$ref);
|
|
576
|
-
current = resolveRef(
|
|
576
|
+
current = resolveRef(document3, current.$ref);
|
|
577
577
|
}
|
|
578
578
|
return current;
|
|
579
579
|
};
|
|
@@ -591,36 +591,36 @@ var HTTP_METHODS = [
|
|
|
591
591
|
];
|
|
592
592
|
|
|
593
593
|
// ../../openapi/core/dist/document.js
|
|
594
|
-
var mergeParameters = (
|
|
594
|
+
var mergeParameters = (document3, item, own) => {
|
|
595
595
|
const merged = /* @__PURE__ */ new Map();
|
|
596
596
|
for (const raw of item.parameters ?? []) {
|
|
597
|
-
const parameter = deref(
|
|
597
|
+
const parameter = deref(document3, raw);
|
|
598
598
|
merged.set(`${parameter.in}:${parameter.name}`, parameter);
|
|
599
599
|
}
|
|
600
600
|
for (const raw of own ?? []) {
|
|
601
|
-
const parameter = deref(
|
|
601
|
+
const parameter = deref(document3, raw);
|
|
602
602
|
merged.set(`${parameter.in}:${parameter.name}`, parameter);
|
|
603
603
|
}
|
|
604
604
|
return [...merged.values()];
|
|
605
605
|
};
|
|
606
|
-
var listOperations = (
|
|
606
|
+
var listOperations = (document3) => {
|
|
607
607
|
const operations = [];
|
|
608
|
-
for (const [path, item] of Object.entries(
|
|
608
|
+
for (const [path, item] of Object.entries(document3.paths)) {
|
|
609
609
|
for (const method of HTTP_METHODS) {
|
|
610
610
|
const operation = item[method];
|
|
611
611
|
if (operation?.operationId === void 0)
|
|
612
612
|
continue;
|
|
613
613
|
const responses = {};
|
|
614
614
|
for (const [status, response] of Object.entries(operation.responses)) {
|
|
615
|
-
responses[status] = deref(
|
|
615
|
+
responses[status] = deref(document3, response);
|
|
616
616
|
}
|
|
617
617
|
operations.push({
|
|
618
618
|
operationId: operation.operationId,
|
|
619
619
|
method,
|
|
620
620
|
path,
|
|
621
621
|
operation,
|
|
622
|
-
parameters: mergeParameters(
|
|
623
|
-
requestBody: operation.requestBody === void 0 ? void 0 : deref(
|
|
622
|
+
parameters: mergeParameters(document3, item, operation.parameters),
|
|
623
|
+
requestBody: operation.requestBody === void 0 ? void 0 : deref(document3, operation.requestBody),
|
|
624
624
|
responses
|
|
625
625
|
});
|
|
626
626
|
}
|
|
@@ -629,7 +629,7 @@ var listOperations = (document2) => {
|
|
|
629
629
|
};
|
|
630
630
|
|
|
631
631
|
// ../../openapi/core/dist/schema.js
|
|
632
|
-
var resolveSchema = (
|
|
632
|
+
var resolveSchema = (document3, schema) => {
|
|
633
633
|
let current = schema;
|
|
634
634
|
const seen = /* @__PURE__ */ new Set();
|
|
635
635
|
while (typeof current.$ref === "string") {
|
|
@@ -638,7 +638,7 @@ var resolveSchema = (document2, schema) => {
|
|
|
638
638
|
break;
|
|
639
639
|
seen.add(ref);
|
|
640
640
|
const { $ref: _ignored, ...siblings } = current;
|
|
641
|
-
const target = resolveRef(
|
|
641
|
+
const target = resolveRef(document3, ref);
|
|
642
642
|
current = { ...target, ...siblings };
|
|
643
643
|
}
|
|
644
644
|
if (current.nullable === true) {
|
|
@@ -709,9 +709,9 @@ var FORMAT_PATTERNS = {
|
|
|
709
709
|
ipv4: /^(25[0-5]|2[0-4]\d|1?\d?\d)(\.(25[0-5]|2[0-4]\d|1?\d?\d)){3}$/
|
|
710
710
|
};
|
|
711
711
|
var graphemeLength = (value) => [...value].length;
|
|
712
|
-
var validateValue = (
|
|
712
|
+
var validateValue = (document3, schema, value, path = []) => {
|
|
713
713
|
const errors = [];
|
|
714
|
-
const s = resolveSchema(
|
|
714
|
+
const s = resolveSchema(document3, schema);
|
|
715
715
|
const fail3 = (message) => errors.push({ path, message });
|
|
716
716
|
const actual = jsonTypeOf(value);
|
|
717
717
|
if (actual === "undefined") {
|
|
@@ -773,7 +773,7 @@ var validateValue = (document2, schema, value, path = []) => {
|
|
|
773
773
|
value.forEach((item, i) => {
|
|
774
774
|
const itemSchema = s.prefixItems?.[i] ?? s.items;
|
|
775
775
|
if (itemSchema)
|
|
776
|
-
errors.push(...validateValue(
|
|
776
|
+
errors.push(...validateValue(document3, itemSchema, item, [...path, i]));
|
|
777
777
|
});
|
|
778
778
|
}
|
|
779
779
|
if (actual === "object") {
|
|
@@ -789,16 +789,16 @@ var validateValue = (document2, schema, value, path = []) => {
|
|
|
789
789
|
for (const key of keys) {
|
|
790
790
|
const property = s.properties?.[key];
|
|
791
791
|
if (property) {
|
|
792
|
-
errors.push(...validateValue(
|
|
792
|
+
errors.push(...validateValue(document3, property, record[key], [...path, key]));
|
|
793
793
|
continue;
|
|
794
794
|
}
|
|
795
795
|
if (s.additionalProperties === false)
|
|
796
796
|
fail3(`unexpected property ${key}`);
|
|
797
797
|
else if (typeof s.additionalProperties === "object") {
|
|
798
|
-
errors.push(...validateValue(
|
|
798
|
+
errors.push(...validateValue(document3, s.additionalProperties, record[key], [...path, key]));
|
|
799
799
|
}
|
|
800
800
|
if (s.propertyNames) {
|
|
801
|
-
const nameErrors = validateValue(
|
|
801
|
+
const nameErrors = validateValue(document3, s.propertyNames, key, [...path, key]);
|
|
802
802
|
if (nameErrors.length > 0)
|
|
803
803
|
fail3(`property name ${key} is invalid: ${nameErrors[0]?.message}`);
|
|
804
804
|
}
|
|
@@ -806,15 +806,15 @@ var validateValue = (document2, schema, value, path = []) => {
|
|
|
806
806
|
}
|
|
807
807
|
if (s.allOf)
|
|
808
808
|
for (const branch of s.allOf)
|
|
809
|
-
errors.push(...validateValue(
|
|
810
|
-
if (s.anyOf && !s.anyOf.some((branch) => validateValue(
|
|
809
|
+
errors.push(...validateValue(document3, branch, value, path));
|
|
810
|
+
if (s.anyOf && !s.anyOf.some((branch) => validateValue(document3, branch, value).length === 0))
|
|
811
811
|
fail3("matches no anyOf branch");
|
|
812
812
|
if (s.oneOf) {
|
|
813
|
-
const matches2 = s.oneOf.filter((branch) => validateValue(
|
|
813
|
+
const matches2 = s.oneOf.filter((branch) => validateValue(document3, branch, value).length === 0).length;
|
|
814
814
|
if (matches2 !== 1)
|
|
815
815
|
fail3(`matches ${matches2} oneOf branches, expected exactly 1`);
|
|
816
816
|
}
|
|
817
|
-
if (s.not && validateValue(
|
|
817
|
+
if (s.not && validateValue(document3, s.not, value).length === 0)
|
|
818
818
|
fail3("matches forbidden `not` schema");
|
|
819
819
|
return errors;
|
|
820
820
|
};
|
|
@@ -1388,6 +1388,1232 @@ var clearNamespace = (sqlite, namespace) => {
|
|
|
1388
1388
|
});
|
|
1389
1389
|
};
|
|
1390
1390
|
|
|
1391
|
+
// ../../ui/dist/reset.js
|
|
1392
|
+
var CSS_RESET = `*,
|
|
1393
|
+
*::before,
|
|
1394
|
+
*::after {
|
|
1395
|
+
box-sizing: border-box;
|
|
1396
|
+
margin: 0;
|
|
1397
|
+
padding: 0;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
* {
|
|
1401
|
+
scrollbar-width: thin;
|
|
1402
|
+
scrollbar-color: color-mix(in srgb, CanvasText 35%, Canvas) Canvas;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
*::-webkit-scrollbar {
|
|
1406
|
+
width: 12px;
|
|
1407
|
+
height: 12px;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
*::-webkit-scrollbar-track,
|
|
1411
|
+
*::-webkit-scrollbar-corner {
|
|
1412
|
+
background: Canvas;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
*::-webkit-scrollbar-thumb {
|
|
1416
|
+
background: color-mix(in srgb, CanvasText 35%, Canvas);
|
|
1417
|
+
border: 3px solid Canvas;
|
|
1418
|
+
border-radius: 999px;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
html {
|
|
1422
|
+
hanging-punctuation: first allow-end last;
|
|
1423
|
+
-webkit-text-size-adjust: none;
|
|
1424
|
+
text-size-adjust: none;
|
|
1425
|
+
tab-size: 4;
|
|
1426
|
+
font-family:
|
|
1427
|
+
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI",
|
|
1428
|
+
"Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
|
1429
|
+
font-size: 16px;
|
|
1430
|
+
line-height: 1.5;
|
|
1431
|
+
-webkit-font-smoothing: antialiased;
|
|
1432
|
+
-moz-osx-font-smoothing: grayscale;
|
|
1433
|
+
text-rendering: optimizeLegibility;
|
|
1434
|
+
-webkit-tap-highlight-color: transparent;
|
|
1435
|
+
scroll-behavior: smooth;
|
|
1436
|
+
background: Canvas;
|
|
1437
|
+
color: CanvasText;
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
body {
|
|
1441
|
+
min-height: 100svh;
|
|
1442
|
+
line-height: 1.5;
|
|
1443
|
+
background: Canvas;
|
|
1444
|
+
color: CanvasText;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
h1 {
|
|
1448
|
+
font-size: 2em;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
h2 {
|
|
1452
|
+
font-size: 1.5em;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
h3 {
|
|
1456
|
+
font-size: 1.17em;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
h4 {
|
|
1460
|
+
font-size: 1em;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
h5 {
|
|
1464
|
+
font-size: 0.83em;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
h6 {
|
|
1468
|
+
font-size: 0.67em;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
h1,
|
|
1472
|
+
h2,
|
|
1473
|
+
h3,
|
|
1474
|
+
h4,
|
|
1475
|
+
h5,
|
|
1476
|
+
h6 {
|
|
1477
|
+
font-weight: 700;
|
|
1478
|
+
line-height: 1.2;
|
|
1479
|
+
text-wrap: balance;
|
|
1480
|
+
overflow-wrap: break-word;
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
p,
|
|
1484
|
+
li,
|
|
1485
|
+
figcaption,
|
|
1486
|
+
dd,
|
|
1487
|
+
dt,
|
|
1488
|
+
blockquote {
|
|
1489
|
+
overflow-wrap: break-word;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
p,
|
|
1493
|
+
li,
|
|
1494
|
+
figcaption,
|
|
1495
|
+
blockquote {
|
|
1496
|
+
text-wrap: pretty;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
ul,
|
|
1500
|
+
ol,
|
|
1501
|
+
menu {
|
|
1502
|
+
list-style: none;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
a {
|
|
1506
|
+
color: inherit;
|
|
1507
|
+
text-decoration: underline;
|
|
1508
|
+
text-decoration-thickness: 1px;
|
|
1509
|
+
text-underline-offset: 0.15em;
|
|
1510
|
+
text-decoration-skip-ink: auto;
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
b,
|
|
1514
|
+
strong {
|
|
1515
|
+
font-weight: 700;
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
small {
|
|
1519
|
+
font-size: 0.875em;
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
code,
|
|
1523
|
+
kbd,
|
|
1524
|
+
samp,
|
|
1525
|
+
pre {
|
|
1526
|
+
font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
|
|
1527
|
+
font-size: 1em;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
sub,
|
|
1531
|
+
sup {
|
|
1532
|
+
font-size: 75%;
|
|
1533
|
+
line-height: 0;
|
|
1534
|
+
position: relative;
|
|
1535
|
+
vertical-align: baseline;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
sub {
|
|
1539
|
+
bottom: -0.25em;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
sup {
|
|
1543
|
+
top: -0.5em;
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
abbr[title] {
|
|
1547
|
+
text-decoration: underline dotted;
|
|
1548
|
+
text-underline-offset: 0.15em;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
mark {
|
|
1552
|
+
background: Mark;
|
|
1553
|
+
color: MarkText;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
hr {
|
|
1557
|
+
height: 0;
|
|
1558
|
+
border: 0;
|
|
1559
|
+
border-top: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
|
|
1560
|
+
color: inherit;
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
img,
|
|
1564
|
+
picture,
|
|
1565
|
+
video,
|
|
1566
|
+
canvas {
|
|
1567
|
+
display: block;
|
|
1568
|
+
max-width: 100%;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
img,
|
|
1572
|
+
video {
|
|
1573
|
+
height: auto;
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
svg {
|
|
1577
|
+
overflow: hidden;
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
iframe {
|
|
1581
|
+
border: 0;
|
|
1582
|
+
max-width: 100%;
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
table {
|
|
1586
|
+
border-collapse: collapse;
|
|
1587
|
+
border-spacing: 0;
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
fieldset {
|
|
1591
|
+
border: 0;
|
|
1592
|
+
min-inline-size: 0;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
legend {
|
|
1596
|
+
padding: 0;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
button,
|
|
1600
|
+
input,
|
|
1601
|
+
select,
|
|
1602
|
+
textarea,
|
|
1603
|
+
optgroup {
|
|
1604
|
+
font: inherit;
|
|
1605
|
+
letter-spacing: inherit;
|
|
1606
|
+
color: inherit;
|
|
1607
|
+
margin: 0;
|
|
1608
|
+
vertical-align: middle;
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
button,
|
|
1612
|
+
input[type="button"],
|
|
1613
|
+
input[type="submit"],
|
|
1614
|
+
input[type="reset"] {
|
|
1615
|
+
appearance: none;
|
|
1616
|
+
background: none;
|
|
1617
|
+
border: 0;
|
|
1618
|
+
padding: 0;
|
|
1619
|
+
cursor: pointer;
|
|
1620
|
+
text-align: center;
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
input,
|
|
1624
|
+
textarea,
|
|
1625
|
+
select {
|
|
1626
|
+
background-color: Canvas;
|
|
1627
|
+
border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
|
|
1628
|
+
border-radius: 0;
|
|
1629
|
+
padding: 0.5em 0.65em;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
input[type="checkbox"],
|
|
1633
|
+
input[type="radio"],
|
|
1634
|
+
input[type="range"],
|
|
1635
|
+
input[type="file"],
|
|
1636
|
+
input[type="color"],
|
|
1637
|
+
input[type="button"],
|
|
1638
|
+
input[type="submit"],
|
|
1639
|
+
input[type="reset"],
|
|
1640
|
+
input[type="image"],
|
|
1641
|
+
input[type="hidden"] {
|
|
1642
|
+
background: none;
|
|
1643
|
+
border: 0;
|
|
1644
|
+
padding: 0;
|
|
1645
|
+
border-radius: 0;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
input[type="checkbox"],
|
|
1649
|
+
input[type="radio"] {
|
|
1650
|
+
accent-color: CanvasText;
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
input[type="search"] {
|
|
1654
|
+
appearance: none;
|
|
1655
|
+
background-color: Canvas;
|
|
1656
|
+
border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
input[type="search"]::-webkit-search-decoration,
|
|
1660
|
+
input[type="search"]::-webkit-search-cancel-button,
|
|
1661
|
+
input[type="search"]::-webkit-search-results-button,
|
|
1662
|
+
input[type="search"]::-webkit-search-results-decoration {
|
|
1663
|
+
-webkit-appearance: none;
|
|
1664
|
+
display: none;
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
input[type="number"] {
|
|
1668
|
+
appearance: textfield;
|
|
1669
|
+
background-color: Canvas;
|
|
1670
|
+
border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
input[type="number"]::-webkit-inner-spin-button,
|
|
1674
|
+
input[type="number"]::-webkit-outer-spin-button {
|
|
1675
|
+
-webkit-appearance: none;
|
|
1676
|
+
margin: 0;
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
textarea {
|
|
1680
|
+
resize: vertical;
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
::placeholder {
|
|
1684
|
+
opacity: 1;
|
|
1685
|
+
color: color-mix(in srgb, CanvasText 45%, transparent);
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
:focus-visible {
|
|
1689
|
+
outline: 2px solid CanvasText;
|
|
1690
|
+
outline-offset: 2px;
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
:focus:not(:focus-visible) {
|
|
1694
|
+
outline: none;
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
:disabled,
|
|
1698
|
+
[disabled] {
|
|
1699
|
+
cursor: default;
|
|
1700
|
+
opacity: 0.6;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
label,
|
|
1704
|
+
summary,
|
|
1705
|
+
select,
|
|
1706
|
+
[role="button"] {
|
|
1707
|
+
cursor: pointer;
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
summary {
|
|
1711
|
+
display: list-item;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
details {
|
|
1715
|
+
display: block;
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
dialog {
|
|
1719
|
+
margin: auto;
|
|
1720
|
+
padding: 1rem;
|
|
1721
|
+
border: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
|
|
1722
|
+
background: Canvas;
|
|
1723
|
+
color: CanvasText;
|
|
1724
|
+
max-width: calc(100vw - 2rem);
|
|
1725
|
+
max-height: calc(100svh - 2rem);
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
dialog:not([open]) {
|
|
1729
|
+
display: none;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
::backdrop {
|
|
1733
|
+
background: color-mix(in srgb, CanvasText 40%, transparent);
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
[hidden] {
|
|
1737
|
+
display: none !important;
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
::selection {
|
|
1741
|
+
background: Highlight;
|
|
1742
|
+
color: HighlightText;
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1746
|
+
*,
|
|
1747
|
+
*::before,
|
|
1748
|
+
*::after {
|
|
1749
|
+
animation-duration: 0.01ms !important;
|
|
1750
|
+
animation-iteration-count: 1 !important;
|
|
1751
|
+
transition-duration: 0.01ms !important;
|
|
1752
|
+
scroll-behavior: auto !important;
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
@media (pointer: coarse) {
|
|
1757
|
+
input,
|
|
1758
|
+
select,
|
|
1759
|
+
textarea {
|
|
1760
|
+
font-size: max(16px, 1em);
|
|
1761
|
+
}
|
|
1762
|
+
}`;
|
|
1763
|
+
|
|
1764
|
+
// ../core/dist/admin-client.js
|
|
1765
|
+
function isRecord2(value) {
|
|
1766
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1767
|
+
}
|
|
1768
|
+
function mountAdminBrand(doc, host, service, catalog) {
|
|
1769
|
+
const clip = (value, max) => {
|
|
1770
|
+
if (typeof value !== "string")
|
|
1771
|
+
return null;
|
|
1772
|
+
const trimmed = value.trim();
|
|
1773
|
+
if (trimmed === "")
|
|
1774
|
+
return null;
|
|
1775
|
+
return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
|
|
1776
|
+
};
|
|
1777
|
+
const httpUrl = (value) => {
|
|
1778
|
+
if (typeof value !== "string" || value.length > 2e3)
|
|
1779
|
+
return null;
|
|
1780
|
+
try {
|
|
1781
|
+
const url = new URL(value);
|
|
1782
|
+
if (url.protocol !== "https:" && url.protocol !== "http:")
|
|
1783
|
+
return null;
|
|
1784
|
+
return url.href;
|
|
1785
|
+
} catch {
|
|
1786
|
+
return null;
|
|
1787
|
+
}
|
|
1788
|
+
};
|
|
1789
|
+
const hexColor = (value) => {
|
|
1790
|
+
if (typeof value !== "string")
|
|
1791
|
+
return null;
|
|
1792
|
+
const raw = value.trim();
|
|
1793
|
+
return /^#[0-9a-fA-F]{3,8}$/.test(raw) ? raw : null;
|
|
1794
|
+
};
|
|
1795
|
+
const hostname = (value) => {
|
|
1796
|
+
try {
|
|
1797
|
+
return new URL(value).hostname.replace(/^www\./, "");
|
|
1798
|
+
} catch {
|
|
1799
|
+
return value;
|
|
1800
|
+
}
|
|
1801
|
+
};
|
|
1802
|
+
host.replaceChildren();
|
|
1803
|
+
host.hidden = true;
|
|
1804
|
+
if (!isRecord2(catalog) || !Object.hasOwn(catalog, service))
|
|
1805
|
+
return;
|
|
1806
|
+
const row = catalog[service];
|
|
1807
|
+
if (!isRecord2(row))
|
|
1808
|
+
return;
|
|
1809
|
+
const vendor = clip(row.vendor, 80);
|
|
1810
|
+
const website = httpUrl(row.website);
|
|
1811
|
+
const guide = httpUrl(row.guide);
|
|
1812
|
+
const docs = httpUrl(row.docs);
|
|
1813
|
+
const logo = httpUrl(row.logo);
|
|
1814
|
+
const color = hexColor(row.color);
|
|
1815
|
+
const description = clip(row.description, 280);
|
|
1816
|
+
if (vendor === null || website === null && guide === null && docs === null)
|
|
1817
|
+
return;
|
|
1818
|
+
const add = (node) => {
|
|
1819
|
+
host.append(node);
|
|
1820
|
+
return node;
|
|
1821
|
+
};
|
|
1822
|
+
if (logo !== null) {
|
|
1823
|
+
const img = doc.createElement("img");
|
|
1824
|
+
img.alt = "";
|
|
1825
|
+
img.src = logo;
|
|
1826
|
+
img.width = 16;
|
|
1827
|
+
img.height = 16;
|
|
1828
|
+
img.decoding = "async";
|
|
1829
|
+
img.referrerPolicy = "no-referrer";
|
|
1830
|
+
if (color !== null)
|
|
1831
|
+
img.style.borderColor = color;
|
|
1832
|
+
add(img);
|
|
1833
|
+
}
|
|
1834
|
+
const name = doc.createElement("span");
|
|
1835
|
+
name.className = "vendor-name";
|
|
1836
|
+
name.textContent = vendor;
|
|
1837
|
+
if (description !== null)
|
|
1838
|
+
name.title = description;
|
|
1839
|
+
add(name);
|
|
1840
|
+
const sep = () => {
|
|
1841
|
+
const node = doc.createElement("span");
|
|
1842
|
+
node.className = "vendor-sep";
|
|
1843
|
+
node.setAttribute("aria-hidden", "true");
|
|
1844
|
+
node.textContent = "\xB7";
|
|
1845
|
+
return node;
|
|
1846
|
+
};
|
|
1847
|
+
const link = (href, label, title) => {
|
|
1848
|
+
const node = doc.createElement("a");
|
|
1849
|
+
node.className = "vendor-link";
|
|
1850
|
+
node.href = href;
|
|
1851
|
+
node.target = "_blank";
|
|
1852
|
+
node.rel = "noopener noreferrer";
|
|
1853
|
+
node.referrerPolicy = "no-referrer";
|
|
1854
|
+
node.textContent = label;
|
|
1855
|
+
node.title = title;
|
|
1856
|
+
return node;
|
|
1857
|
+
};
|
|
1858
|
+
if (website !== null) {
|
|
1859
|
+
add(sep());
|
|
1860
|
+
add(link(website, hostname(website), `${vendor} website`));
|
|
1861
|
+
}
|
|
1862
|
+
if (guide !== null) {
|
|
1863
|
+
add(sep());
|
|
1864
|
+
add(link(guide, "Docs", `Docs for ${vendor}`));
|
|
1865
|
+
}
|
|
1866
|
+
if (docs !== null) {
|
|
1867
|
+
add(sep());
|
|
1868
|
+
add(link(docs, "API", `${vendor} API reference`));
|
|
1869
|
+
}
|
|
1870
|
+
host.removeAttribute("hidden");
|
|
1871
|
+
host.hidden = false;
|
|
1872
|
+
}
|
|
1873
|
+
function startAdminBrand(env) {
|
|
1874
|
+
const requested = env.params.get("brands");
|
|
1875
|
+
let brandsUrl = env.brandsUrl;
|
|
1876
|
+
if (requested) {
|
|
1877
|
+
try {
|
|
1878
|
+
const url = new URL(requested, env.location.href);
|
|
1879
|
+
const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]" || url.origin === env.location.origin;
|
|
1880
|
+
if ((url.protocol === "https:" || url.protocol === "http:") && local)
|
|
1881
|
+
brandsUrl = url.href;
|
|
1882
|
+
} catch {
|
|
1883
|
+
brandsUrl = env.brandsUrl;
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
const vendorHost = env.$("vendor");
|
|
1887
|
+
const brandRoot = env.document.querySelector("[data-mockingbird-admin]");
|
|
1888
|
+
const brandService = brandRoot?.getAttribute("data-service") || "";
|
|
1889
|
+
if (vendorHost === null)
|
|
1890
|
+
return;
|
|
1891
|
+
env.fetch(brandsUrl, {
|
|
1892
|
+
headers: { accept: "application/json" },
|
|
1893
|
+
signal: AbortSignal.timeout(5e3)
|
|
1894
|
+
}).then(async (response) => {
|
|
1895
|
+
if (!response.ok)
|
|
1896
|
+
return null;
|
|
1897
|
+
const catalog = await response.json();
|
|
1898
|
+
return catalog;
|
|
1899
|
+
}).then((catalog) => {
|
|
1900
|
+
mountAdminBrand(env.document, vendorHost, brandService, catalog);
|
|
1901
|
+
}).catch(() => {
|
|
1902
|
+
});
|
|
1903
|
+
}
|
|
1904
|
+
function faultPresetList(value) {
|
|
1905
|
+
if (!isRecord2(value))
|
|
1906
|
+
throw new Error("Fault presets response was not an object");
|
|
1907
|
+
if (!Object.hasOwn(value, "presets") || value.presets === void 0 || value.presets === null) {
|
|
1908
|
+
return { presets: [] };
|
|
1909
|
+
}
|
|
1910
|
+
if (!Array.isArray(value.presets))
|
|
1911
|
+
throw new Error("Fault presets response was not a list");
|
|
1912
|
+
return {
|
|
1913
|
+
presets: value.presets.flatMap((item) => {
|
|
1914
|
+
if (!isRecord2(item) || typeof item.name !== "string")
|
|
1915
|
+
return [];
|
|
1916
|
+
return [{ name: item.name }];
|
|
1917
|
+
})
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1920
|
+
function bootAdmin(config) {
|
|
1921
|
+
const fieldKinds = ["string", "number", "boolean", "null", "object", "array", "unknown"];
|
|
1922
|
+
const sources = ["declared", "inferred", "mixed"];
|
|
1923
|
+
const isFieldKind = (value) => typeof value === "string" && fieldKinds.some((kind) => kind === value);
|
|
1924
|
+
const isSource = (value) => typeof value === "string" && sources.some((source) => source === value);
|
|
1925
|
+
const readJson2 = (text) => {
|
|
1926
|
+
const value = JSON.parse(text);
|
|
1927
|
+
return value;
|
|
1928
|
+
};
|
|
1929
|
+
const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => {
|
|
1930
|
+
switch (char) {
|
|
1931
|
+
case "&":
|
|
1932
|
+
return "&";
|
|
1933
|
+
case "<":
|
|
1934
|
+
return "<";
|
|
1935
|
+
case ">":
|
|
1936
|
+
return ">";
|
|
1937
|
+
case '"':
|
|
1938
|
+
return """;
|
|
1939
|
+
default:
|
|
1940
|
+
return "'";
|
|
1941
|
+
}
|
|
1942
|
+
});
|
|
1943
|
+
const el = (id, kind) => {
|
|
1944
|
+
const node = document.getElementById(id);
|
|
1945
|
+
if (!(node instanceof kind))
|
|
1946
|
+
throw new Error(`admin shell is missing #${id}`);
|
|
1947
|
+
return node;
|
|
1948
|
+
};
|
|
1949
|
+
const errorMessage = (data, fallback) => {
|
|
1950
|
+
if (!isRecord2(data))
|
|
1951
|
+
return fallback;
|
|
1952
|
+
const error = data.error;
|
|
1953
|
+
if (!isRecord2(error) || typeof error.message !== "string" || error.message === "") {
|
|
1954
|
+
return fallback;
|
|
1955
|
+
}
|
|
1956
|
+
return error.message;
|
|
1957
|
+
};
|
|
1958
|
+
const requireList = (value, label) => {
|
|
1959
|
+
if (value === void 0 || value === null)
|
|
1960
|
+
return [];
|
|
1961
|
+
if (!Array.isArray(value))
|
|
1962
|
+
throw new Error(`${label} was not a list`);
|
|
1963
|
+
return value;
|
|
1964
|
+
};
|
|
1965
|
+
const state = {
|
|
1966
|
+
namespace: "default",
|
|
1967
|
+
key: sessionStorage.getItem("mockingbird-admin-key") || "",
|
|
1968
|
+
view: "overview",
|
|
1969
|
+
collection: "",
|
|
1970
|
+
shape: null,
|
|
1971
|
+
route: null,
|
|
1972
|
+
editing: null
|
|
1973
|
+
};
|
|
1974
|
+
const standard = new Set(config.standardRoutes);
|
|
1975
|
+
const banner = el("banner", HTMLElement);
|
|
1976
|
+
const keyInput = el("admin-key", HTMLInputElement);
|
|
1977
|
+
keyInput.value = state.key;
|
|
1978
|
+
const params = new URLSearchParams(location.search);
|
|
1979
|
+
const queryKey = params.get("key");
|
|
1980
|
+
if (queryKey) {
|
|
1981
|
+
state.key = queryKey;
|
|
1982
|
+
keyInput.value = state.key;
|
|
1983
|
+
sessionStorage.setItem("mockingbird-admin-key", state.key);
|
|
1984
|
+
params.delete("key");
|
|
1985
|
+
const next = params.toString();
|
|
1986
|
+
history.replaceState(null, "", location.pathname + (next ? `?${next}` : ""));
|
|
1987
|
+
}
|
|
1988
|
+
const queryNamespace = params.get("namespace");
|
|
1989
|
+
if (queryNamespace)
|
|
1990
|
+
state.namespace = queryNamespace;
|
|
1991
|
+
const showError = (error) => {
|
|
1992
|
+
banner.textContent = error instanceof Error ? error.message : String(error);
|
|
1993
|
+
banner.classList.add("show");
|
|
1994
|
+
};
|
|
1995
|
+
const clearError = () => {
|
|
1996
|
+
banner.classList.remove("show");
|
|
1997
|
+
};
|
|
1998
|
+
const api = async (method, path, body) => {
|
|
1999
|
+
const headers = { accept: "application/json" };
|
|
2000
|
+
if (state.key !== "")
|
|
2001
|
+
headers[config.adminKeyHeader] = state.key;
|
|
2002
|
+
if (state.namespace !== "")
|
|
2003
|
+
headers["x-mockingbird-namespace"] = state.namespace;
|
|
2004
|
+
if (body !== void 0)
|
|
2005
|
+
headers["content-type"] = "application/json";
|
|
2006
|
+
const init = { method, headers };
|
|
2007
|
+
if (body !== void 0)
|
|
2008
|
+
init.body = JSON.stringify(body);
|
|
2009
|
+
const response = await fetch(config.adminPrefix + path, init);
|
|
2010
|
+
const text = await response.text();
|
|
2011
|
+
let data = null;
|
|
2012
|
+
if (text !== "") {
|
|
2013
|
+
try {
|
|
2014
|
+
data = readJson2(text);
|
|
2015
|
+
} catch {
|
|
2016
|
+
data = { raw: text };
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
if (response.status === 401) {
|
|
2020
|
+
keyInput.focus();
|
|
2021
|
+
throw new Error(errorMessage(data, "Admin key required"));
|
|
2022
|
+
}
|
|
2023
|
+
if (!response.ok) {
|
|
2024
|
+
throw new Error(errorMessage(data, `${response.status} ${method} ${path}`));
|
|
2025
|
+
}
|
|
2026
|
+
return data;
|
|
2027
|
+
};
|
|
2028
|
+
const originGet = async (path) => {
|
|
2029
|
+
const response = await fetch(path, { headers: { accept: "application/json" } });
|
|
2030
|
+
if (!response.ok)
|
|
2031
|
+
throw new Error(`${response.status} ${path}`);
|
|
2032
|
+
const value = await response.json();
|
|
2033
|
+
return value;
|
|
2034
|
+
};
|
|
2035
|
+
const panelApi = {
|
|
2036
|
+
get: (path) => api("GET", path),
|
|
2037
|
+
send: (method, path, body) => api(method, path, body),
|
|
2038
|
+
namespace: () => state.namespace
|
|
2039
|
+
};
|
|
2040
|
+
const isPanelScript = (value) => typeof value === "function";
|
|
2041
|
+
const setTheme = (theme) => {
|
|
2042
|
+
if (theme)
|
|
2043
|
+
document.documentElement.dataset.theme = theme;
|
|
2044
|
+
else
|
|
2045
|
+
delete document.documentElement.dataset.theme;
|
|
2046
|
+
};
|
|
2047
|
+
const storedTheme = localStorage.getItem("mockingbird-admin-theme");
|
|
2048
|
+
if (storedTheme)
|
|
2049
|
+
setTheme(storedTheme);
|
|
2050
|
+
el("theme", HTMLButtonElement).addEventListener("click", () => {
|
|
2051
|
+
const current = document.documentElement.dataset.theme;
|
|
2052
|
+
const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
|
|
2053
|
+
const next = dark ? "light" : "dark";
|
|
2054
|
+
localStorage.setItem("mockingbird-admin-theme", next);
|
|
2055
|
+
setTheme(next);
|
|
2056
|
+
});
|
|
2057
|
+
const show = (view) => {
|
|
2058
|
+
state.view = view;
|
|
2059
|
+
for (const button of document.querySelectorAll("#nav button")) {
|
|
2060
|
+
if (!(button instanceof HTMLButtonElement))
|
|
2061
|
+
continue;
|
|
2062
|
+
button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false");
|
|
2063
|
+
}
|
|
2064
|
+
for (const section of document.querySelectorAll("main .view")) {
|
|
2065
|
+
if (!(section instanceof HTMLElement))
|
|
2066
|
+
continue;
|
|
2067
|
+
section.hidden = section.id !== `view-${view}`;
|
|
2068
|
+
}
|
|
2069
|
+
};
|
|
2070
|
+
el("nav", HTMLElement).addEventListener("click", (event) => {
|
|
2071
|
+
const target = event.target;
|
|
2072
|
+
if (!(target instanceof Element))
|
|
2073
|
+
return;
|
|
2074
|
+
const button = target.closest("button[data-view]");
|
|
2075
|
+
if (!(button instanceof HTMLButtonElement))
|
|
2076
|
+
return;
|
|
2077
|
+
const view = button.dataset.view;
|
|
2078
|
+
if (view === void 0)
|
|
2079
|
+
return;
|
|
2080
|
+
show(view);
|
|
2081
|
+
refresh().catch(showError);
|
|
2082
|
+
});
|
|
2083
|
+
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>`;
|
|
2084
|
+
const table = (columns, rows) => {
|
|
2085
|
+
if (rows.length === 0)
|
|
2086
|
+
return '<p class="empty">Nothing here yet.</p>';
|
|
2087
|
+
const head = `<tr>${columns.map((column) => `<th>${column.label}</th>`).join("")}</tr>`;
|
|
2088
|
+
const body = rows.map((row) => {
|
|
2089
|
+
const cells = columns.map((column) => `<td data-label="${column.label}">${column.cell(row)}</td>`).join("");
|
|
2090
|
+
const attrs = "attrs" in row && typeof row.attrs === "string" ? row.attrs : "";
|
|
2091
|
+
return `<tr${attrs}>${cells}</tr>`;
|
|
2092
|
+
}).join("");
|
|
2093
|
+
return `<table><thead>${head}</thead><tbody>${body}</tbody></table>`;
|
|
2094
|
+
};
|
|
2095
|
+
const preview = (value) => {
|
|
2096
|
+
const text = JSON.stringify(value) ?? "null";
|
|
2097
|
+
return esc(text.length > 180 ? `${text.slice(0, 180)}\u2026` : text);
|
|
2098
|
+
};
|
|
2099
|
+
const when = (epoch) => {
|
|
2100
|
+
const date = new Date(epoch);
|
|
2101
|
+
return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString();
|
|
2102
|
+
};
|
|
2103
|
+
const clockCard = (epoch) => {
|
|
2104
|
+
const date = new Date(epoch);
|
|
2105
|
+
if (Number.isNaN(date.getTime()))
|
|
2106
|
+
return esc(String(epoch));
|
|
2107
|
+
const iso = date.toISOString();
|
|
2108
|
+
return `${esc(iso.slice(0, 10))}<span class="clock-time">${esc(iso.slice(11, 19))} UTC</span>`;
|
|
2109
|
+
};
|
|
2110
|
+
const decodeClock = (value) => {
|
|
2111
|
+
if (!isRecord2(value) || typeof value.now !== "number" || typeof value.offsetMs !== "number" || typeof value.frozen !== "boolean") {
|
|
2112
|
+
throw new Error("Clock response was not a clock");
|
|
2113
|
+
}
|
|
2114
|
+
return { now: value.now, offsetMs: value.offsetMs, frozen: value.frozen };
|
|
2115
|
+
};
|
|
2116
|
+
const decodeField = (value) => {
|
|
2117
|
+
if (!isRecord2(value) || typeof value.name !== "string" || !isFieldKind(value.kind)) {
|
|
2118
|
+
return void 0;
|
|
2119
|
+
}
|
|
2120
|
+
const field = {
|
|
2121
|
+
name: value.name,
|
|
2122
|
+
kind: value.kind,
|
|
2123
|
+
optional: value.optional === true
|
|
2124
|
+
};
|
|
2125
|
+
if (typeof value.description === "string")
|
|
2126
|
+
field.description = value.description;
|
|
2127
|
+
if (Array.isArray(value.fields)) {
|
|
2128
|
+
field.fields = value.fields.flatMap((item) => {
|
|
2129
|
+
const child = decodeField(item);
|
|
2130
|
+
return child === void 0 ? [] : [child];
|
|
2131
|
+
});
|
|
2132
|
+
}
|
|
2133
|
+
return field;
|
|
2134
|
+
};
|
|
2135
|
+
const decodeCollection = (value) => {
|
|
2136
|
+
if (!isRecord2(value) || typeof value.name !== "string")
|
|
2137
|
+
return void 0;
|
|
2138
|
+
const fields = Array.isArray(value.fields) ? value.fields.flatMap((item) => {
|
|
2139
|
+
const field = decodeField(item);
|
|
2140
|
+
return field === void 0 ? [] : [field];
|
|
2141
|
+
}) : [];
|
|
2142
|
+
const collection = {
|
|
2143
|
+
name: value.name,
|
|
2144
|
+
label: typeof value.label === "string" ? value.label : value.name,
|
|
2145
|
+
count: typeof value.count === "number" ? value.count : 0,
|
|
2146
|
+
source: isSource(value.source) ? value.source : "inferred",
|
|
2147
|
+
fields
|
|
2148
|
+
};
|
|
2149
|
+
if (typeof value.description === "string")
|
|
2150
|
+
collection.description = value.description;
|
|
2151
|
+
return collection;
|
|
2152
|
+
};
|
|
2153
|
+
const decodeState = (value) => {
|
|
2154
|
+
if (!isRecord2(value) || !Array.isArray(value.collections)) {
|
|
2155
|
+
throw new Error("State response has no collection list");
|
|
2156
|
+
}
|
|
2157
|
+
return {
|
|
2158
|
+
namespace: typeof value.namespace === "string" ? value.namespace : state.namespace,
|
|
2159
|
+
storageNamespace: typeof value.storageNamespace === "string" ? value.storageNamespace : "",
|
|
2160
|
+
collections: value.collections.flatMap((item) => {
|
|
2161
|
+
const collection = decodeCollection(item);
|
|
2162
|
+
return collection === void 0 ? [] : [collection];
|
|
2163
|
+
})
|
|
2164
|
+
};
|
|
2165
|
+
};
|
|
2166
|
+
const decodeRecords = (value) => requireList(isRecord2(value) ? value.records : void 0, "Records").flatMap((item) => {
|
|
2167
|
+
if (!isRecord2(item) || typeof item.id !== "string")
|
|
2168
|
+
return [];
|
|
2169
|
+
return [
|
|
2170
|
+
{
|
|
2171
|
+
id: item.id,
|
|
2172
|
+
seq: typeof item.seq === "number" ? item.seq : 0,
|
|
2173
|
+
value: item.value,
|
|
2174
|
+
attrs: ` class="clickable" data-id="${esc(item.id)}"`
|
|
2175
|
+
}
|
|
2176
|
+
];
|
|
2177
|
+
});
|
|
2178
|
+
const textOf = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : "";
|
|
2179
|
+
const decodeJournal = (value) => requireList(isRecord2(value) ? value.requests : void 0, "Journal").flatMap((item) => {
|
|
2180
|
+
if (!isRecord2(item))
|
|
2181
|
+
return [];
|
|
2182
|
+
return [
|
|
2183
|
+
{
|
|
2184
|
+
at: textOf(item.at),
|
|
2185
|
+
method: textOf(item.method),
|
|
2186
|
+
path: textOf(item.path),
|
|
2187
|
+
status: textOf(item.status),
|
|
2188
|
+
durationMs: textOf(item.durationMs),
|
|
2189
|
+
operationId: textOf(item.operationId)
|
|
2190
|
+
}
|
|
2191
|
+
];
|
|
2192
|
+
});
|
|
2193
|
+
const decodeFaults = (value) => requireList(isRecord2(value) ? value.faults : void 0, "Faults").flatMap((item) => {
|
|
2194
|
+
if (!isRecord2(item))
|
|
2195
|
+
return [];
|
|
2196
|
+
return [
|
|
2197
|
+
{
|
|
2198
|
+
id: textOf(item.id),
|
|
2199
|
+
method: typeof item.method === "string" ? item.method : void 0,
|
|
2200
|
+
pathPrefix: typeof item.pathPrefix === "string" ? item.pathPrefix : void 0,
|
|
2201
|
+
operationId: typeof item.operationId === "string" ? item.operationId : void 0,
|
|
2202
|
+
status: typeof item.status === "number" ? item.status : void 0,
|
|
2203
|
+
effect: typeof item.effect === "string" ? item.effect : void 0,
|
|
2204
|
+
drop: typeof item.drop === "boolean" ? item.drop : void 0,
|
|
2205
|
+
latencyMs: typeof item.latencyMs === "number" ? item.latencyMs : void 0
|
|
2206
|
+
}
|
|
2207
|
+
];
|
|
2208
|
+
});
|
|
2209
|
+
const loadNamespaces = async () => {
|
|
2210
|
+
const data = await api("GET", "/namespaces");
|
|
2211
|
+
const select = el("namespace", HTMLSelectElement);
|
|
2212
|
+
const listed = requireList(isRecord2(data) ? data.namespaces : void 0, "Namespaces");
|
|
2213
|
+
const fallback = isRecord2(data) && typeof data.default === "string" && data.default !== "" ? data.default : "default";
|
|
2214
|
+
const names = (listed.length > 0 ? listed.map((name) => String(name)) : [fallback]).slice();
|
|
2215
|
+
if (!names.includes(state.namespace))
|
|
2216
|
+
names.unshift(state.namespace);
|
|
2217
|
+
select.innerHTML = names.map((name) => `<option value="${esc(name)}">${esc(name)}</option>`).join("");
|
|
2218
|
+
select.value = state.namespace;
|
|
2219
|
+
};
|
|
2220
|
+
const renderOverview = async () => {
|
|
2221
|
+
const [health, shape, clock, journal] = await Promise.all([
|
|
2222
|
+
originGet("/health"),
|
|
2223
|
+
api("GET", "/state"),
|
|
2224
|
+
api("GET", "/clock"),
|
|
2225
|
+
api("GET", "/requests?limit=8")
|
|
2226
|
+
]);
|
|
2227
|
+
if (!isRecord2(health))
|
|
2228
|
+
throw new Error("Health response was not an object");
|
|
2229
|
+
const decoded = decodeState(shape);
|
|
2230
|
+
state.shape = decoded;
|
|
2231
|
+
const time = decodeClock(clock);
|
|
2232
|
+
el("overview-cards", HTMLElement).innerHTML = cards([
|
|
2233
|
+
{
|
|
2234
|
+
k: "Service",
|
|
2235
|
+
v: esc(typeof health.service === "string" && health.service !== "" ? health.service : config.service)
|
|
2236
|
+
},
|
|
2237
|
+
{
|
|
2238
|
+
k: "Status",
|
|
2239
|
+
v: esc(typeof health.status === "string" && health.status !== "" ? health.status : "ok")
|
|
2240
|
+
},
|
|
2241
|
+
{ k: "Namespace", v: esc(state.namespace) },
|
|
2242
|
+
{ k: "Collections", v: String(decoded.collections.length) },
|
|
2243
|
+
{
|
|
2244
|
+
k: "Records",
|
|
2245
|
+
v: String(decoded.collections.reduce((sum, item) => sum + item.count, 0))
|
|
2246
|
+
},
|
|
2247
|
+
{ k: "Clock", v: clockCard(time.now) }
|
|
2248
|
+
]);
|
|
2249
|
+
el("overview-requests", HTMLElement).innerHTML = table([
|
|
2250
|
+
{ label: "Method", cell: (row) => esc(row.method) },
|
|
2251
|
+
{ label: "Path", cell: (row) => esc(row.path) },
|
|
2252
|
+
{
|
|
2253
|
+
label: "Status",
|
|
2254
|
+
cell: (row) => `<span class="status-${row.status.charAt(0)}">${esc(row.status)}</span>`
|
|
2255
|
+
},
|
|
2256
|
+
{ label: "Operation", cell: (row) => esc(row.operationId) }
|
|
2257
|
+
], decodeJournal(journal));
|
|
2258
|
+
};
|
|
2259
|
+
const schemaChips = (collection) => {
|
|
2260
|
+
if (collection.fields.length === 0) {
|
|
2261
|
+
return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>';
|
|
2262
|
+
}
|
|
2263
|
+
return `<div class="chips">${collection.fields.map((field) => `<span class="chip">${esc(field.name)} <i>${esc(field.kind)}${field.optional ? "?" : ""}</i></span>`).join("")}</div>`;
|
|
2264
|
+
};
|
|
2265
|
+
const renderState = async () => {
|
|
2266
|
+
const shape = decodeState(await api("GET", "/state"));
|
|
2267
|
+
state.shape = shape;
|
|
2268
|
+
const host = el("collections", HTMLElement);
|
|
2269
|
+
if (shape.collections.length === 0) {
|
|
2270
|
+
host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>';
|
|
2271
|
+
el("schema", HTMLElement).innerHTML = "";
|
|
2272
|
+
el("records", HTMLElement).innerHTML = "";
|
|
2273
|
+
return;
|
|
2274
|
+
}
|
|
2275
|
+
if (!shape.collections.some((item) => item.name === state.collection)) {
|
|
2276
|
+
const first = shape.collections[0];
|
|
2277
|
+
if (first === void 0)
|
|
2278
|
+
return;
|
|
2279
|
+
state.collection = first.name;
|
|
2280
|
+
}
|
|
2281
|
+
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("");
|
|
2282
|
+
const selected = shape.collections.find((item) => item.name === state.collection);
|
|
2283
|
+
if (selected === void 0)
|
|
2284
|
+
return;
|
|
2285
|
+
const description = selected.description ? `<p class="lede">${esc(selected.description)}</p>` : "";
|
|
2286
|
+
el("schema", HTMLElement).innerHTML = `<h3>${esc(selected.label)}</h3>${description}${schemaChips(selected)}`;
|
|
2287
|
+
const page = await api("GET", `/state/${encodeURIComponent(state.collection)}?limit=50`);
|
|
2288
|
+
el("records", HTMLElement).innerHTML = table([
|
|
2289
|
+
{ label: "Id", cell: (row) => `<code>${esc(row.id)}</code>` },
|
|
2290
|
+
{ label: "Seq", cell: (row) => esc(row.seq) },
|
|
2291
|
+
{ label: "Value", cell: (row) => preview(row.value) }
|
|
2292
|
+
], decodeRecords(page));
|
|
2293
|
+
};
|
|
2294
|
+
const renderClock = async () => {
|
|
2295
|
+
const clock = decodeClock(await api("GET", "/clock"));
|
|
2296
|
+
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>`;
|
|
2297
|
+
const freeze = el("freeze", HTMLButtonElement);
|
|
2298
|
+
freeze.textContent = clock.frozen ? "Unfreeze" : "Freeze";
|
|
2299
|
+
freeze.dataset.frozen = clock.frozen ? "1" : "0";
|
|
2300
|
+
};
|
|
2301
|
+
const renderFaults = async () => {
|
|
2302
|
+
const [faults, presets] = await Promise.all([
|
|
2303
|
+
api("GET", "/faults"),
|
|
2304
|
+
api("GET", "/faults/presets").catch(() => ({ presets: [] }))
|
|
2305
|
+
]);
|
|
2306
|
+
const select = el("fault-preset", HTMLSelectElement);
|
|
2307
|
+
const current = select.value;
|
|
2308
|
+
const list = faultPresetList(presets).presets;
|
|
2309
|
+
select.innerHTML = `<option value="">Custom rule</option>${list.map((preset) => `<option value="${esc(preset.name)}">${esc(preset.name)}</option>`).join("")}`;
|
|
2310
|
+
select.value = current;
|
|
2311
|
+
el("fault-list", HTMLElement).innerHTML = table([
|
|
2312
|
+
{ label: "Id", cell: (row) => esc(row.id) },
|
|
2313
|
+
{
|
|
2314
|
+
label: "Match",
|
|
2315
|
+
cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter((part) => Boolean(part)).join(" ") || "all")
|
|
2316
|
+
},
|
|
2317
|
+
{
|
|
2318
|
+
label: "Effect",
|
|
2319
|
+
cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || ""))
|
|
2320
|
+
}
|
|
2321
|
+
], decodeFaults(faults));
|
|
2322
|
+
};
|
|
2323
|
+
const renderJournal = async () => {
|
|
2324
|
+
const journal = await api("GET", "/requests?limit=100");
|
|
2325
|
+
el("journal", HTMLElement).innerHTML = table([
|
|
2326
|
+
{ label: "When", cell: (row) => esc(row.at) },
|
|
2327
|
+
{ label: "Method", cell: (row) => esc(row.method) },
|
|
2328
|
+
{ label: "Path", cell: (row) => esc(row.path) },
|
|
2329
|
+
{ label: "Status", cell: (row) => esc(row.status) },
|
|
2330
|
+
{ label: "ms", cell: (row) => esc(row.durationMs) }
|
|
2331
|
+
], decodeJournal(journal));
|
|
2332
|
+
};
|
|
2333
|
+
const renderRoutes = async () => {
|
|
2334
|
+
const data = await api("GET", "/");
|
|
2335
|
+
if (!isRecord2(data))
|
|
2336
|
+
throw new Error("Route list was not an object");
|
|
2337
|
+
const routes = requireList(data.routes, "Routes").map((route) => String(route));
|
|
2338
|
+
el("route-list", HTMLElement).innerHTML = routes.map((route) => {
|
|
2339
|
+
const extra = standard.has(route) ? "" : " <span class='badge'>extension</span>";
|
|
2340
|
+
const parts = route.split(" ");
|
|
2341
|
+
const method = parts[0] ?? "";
|
|
2342
|
+
const path = parts.slice(1).join(" ");
|
|
2343
|
+
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>`;
|
|
2344
|
+
}).join("");
|
|
2345
|
+
};
|
|
2346
|
+
const renderers = {
|
|
2347
|
+
overview: renderOverview,
|
|
2348
|
+
state: renderState,
|
|
2349
|
+
clock: renderClock,
|
|
2350
|
+
faults: renderFaults,
|
|
2351
|
+
journal: renderJournal,
|
|
2352
|
+
routes: renderRoutes
|
|
2353
|
+
};
|
|
2354
|
+
async function refresh() {
|
|
2355
|
+
clearError();
|
|
2356
|
+
const render = renderers[state.view];
|
|
2357
|
+
if (render)
|
|
2358
|
+
await render();
|
|
2359
|
+
}
|
|
2360
|
+
el("namespace", HTMLSelectElement).addEventListener("change", () => {
|
|
2361
|
+
state.namespace = el("namespace", HTMLSelectElement).value;
|
|
2362
|
+
refresh().catch(showError);
|
|
2363
|
+
});
|
|
2364
|
+
keyInput.addEventListener("change", () => {
|
|
2365
|
+
state.key = keyInput.value.trim();
|
|
2366
|
+
sessionStorage.setItem("mockingbird-admin-key", state.key);
|
|
2367
|
+
refresh().catch(showError);
|
|
2368
|
+
});
|
|
2369
|
+
el("refresh", HTMLButtonElement).addEventListener("click", () => {
|
|
2370
|
+
refresh().catch(showError);
|
|
2371
|
+
});
|
|
2372
|
+
el("collections", HTMLElement).addEventListener("click", (event) => {
|
|
2373
|
+
const target = event.target;
|
|
2374
|
+
if (!(target instanceof Element))
|
|
2375
|
+
return;
|
|
2376
|
+
const button = target.closest("[data-collection]");
|
|
2377
|
+
if (!(button instanceof HTMLElement))
|
|
2378
|
+
return;
|
|
2379
|
+
const collection = button.dataset.collection;
|
|
2380
|
+
if (collection === void 0)
|
|
2381
|
+
return;
|
|
2382
|
+
state.collection = collection;
|
|
2383
|
+
renderState().catch(showError);
|
|
2384
|
+
});
|
|
2385
|
+
el("records", HTMLElement).addEventListener("click", (event) => {
|
|
2386
|
+
const target = event.target;
|
|
2387
|
+
if (!(target instanceof Element))
|
|
2388
|
+
return;
|
|
2389
|
+
const row = target.closest("[data-id]");
|
|
2390
|
+
if (!(row instanceof HTMLElement))
|
|
2391
|
+
return;
|
|
2392
|
+
const id = row.dataset.id;
|
|
2393
|
+
if (id === void 0)
|
|
2394
|
+
return;
|
|
2395
|
+
openEditor(id).catch(showError);
|
|
2396
|
+
});
|
|
2397
|
+
const editor = el("editor", HTMLDialogElement);
|
|
2398
|
+
const editorId = el("editor-id", HTMLInputElement);
|
|
2399
|
+
const editorValue = el("editor-value", HTMLTextAreaElement);
|
|
2400
|
+
const editorDelete = el("editor-delete", HTMLButtonElement);
|
|
2401
|
+
async function openEditor(id) {
|
|
2402
|
+
state.editing = id;
|
|
2403
|
+
el("editor-title", HTMLElement).textContent = id ? "Edit record" : "New record";
|
|
2404
|
+
editorId.value = id ?? "";
|
|
2405
|
+
editorId.readOnly = id !== null;
|
|
2406
|
+
editorDelete.hidden = id === null;
|
|
2407
|
+
if (id) {
|
|
2408
|
+
const record = await api("GET", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`);
|
|
2409
|
+
if (!isRecord2(record))
|
|
2410
|
+
throw new Error("Record response was not an object");
|
|
2411
|
+
editorValue.value = JSON.stringify(record.value, null, 2) ?? "";
|
|
2412
|
+
} else {
|
|
2413
|
+
editorValue.value = "{\n \n}";
|
|
2414
|
+
}
|
|
2415
|
+
editor.showModal();
|
|
2416
|
+
}
|
|
2417
|
+
el("new-record", HTMLButtonElement).addEventListener("click", () => {
|
|
2418
|
+
openEditor(null).catch(showError);
|
|
2419
|
+
});
|
|
2420
|
+
el("editor-form", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2421
|
+
const submitter = event.submitter;
|
|
2422
|
+
if (!(submitter instanceof HTMLButtonElement) || submitter.value !== "save")
|
|
2423
|
+
return;
|
|
2424
|
+
event.preventDefault();
|
|
2425
|
+
let value;
|
|
2426
|
+
try {
|
|
2427
|
+
value = readJson2(editorValue.value);
|
|
2428
|
+
} catch {
|
|
2429
|
+
showError(new Error("Value is not JSON"));
|
|
2430
|
+
return;
|
|
2431
|
+
}
|
|
2432
|
+
const id = editorId.value.trim();
|
|
2433
|
+
const path = `/state/${encodeURIComponent(state.collection)}`;
|
|
2434
|
+
const request = state.editing ? api("PUT", `${path}/${encodeURIComponent(id)}`, { value }) : api("POST", path, { id: id || void 0, value });
|
|
2435
|
+
request.then(() => {
|
|
2436
|
+
editor.close();
|
|
2437
|
+
return renderState();
|
|
2438
|
+
}).catch(showError);
|
|
2439
|
+
});
|
|
2440
|
+
editorDelete.addEventListener("click", () => {
|
|
2441
|
+
const id = state.editing;
|
|
2442
|
+
if (!id)
|
|
2443
|
+
return;
|
|
2444
|
+
api("DELETE", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`).then(() => {
|
|
2445
|
+
editor.close();
|
|
2446
|
+
return renderState();
|
|
2447
|
+
}).catch(showError);
|
|
2448
|
+
});
|
|
2449
|
+
for (const button of document.querySelectorAll("[data-advance]")) {
|
|
2450
|
+
if (!(button instanceof HTMLButtonElement))
|
|
2451
|
+
continue;
|
|
2452
|
+
button.addEventListener("click", () => {
|
|
2453
|
+
const raw = button.dataset.advance;
|
|
2454
|
+
if (raw === void 0)
|
|
2455
|
+
return;
|
|
2456
|
+
api("POST", "/clock", { advance: Number(raw) }).then(() => renderClock()).catch(showError);
|
|
2457
|
+
});
|
|
2458
|
+
}
|
|
2459
|
+
el("freeze", HTMLButtonElement).addEventListener("click", () => {
|
|
2460
|
+
const frozen = el("freeze", HTMLButtonElement).dataset.frozen === "1";
|
|
2461
|
+
api("POST", "/clock", { freeze: !frozen }).then(() => renderClock()).catch(showError);
|
|
2462
|
+
});
|
|
2463
|
+
el("clock-reset", HTMLButtonElement).addEventListener("click", () => {
|
|
2464
|
+
api("POST", "/clock", { reset: true }).then(() => renderClock()).catch(showError);
|
|
2465
|
+
});
|
|
2466
|
+
el("clock-set", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2467
|
+
event.preventDefault();
|
|
2468
|
+
const raw = el("clock-instant", HTMLInputElement).value.trim();
|
|
2469
|
+
const set = /^-?\d+$/.test(raw) ? Number(raw) : raw;
|
|
2470
|
+
api("POST", "/clock", { set }).then(() => renderClock()).catch(showError);
|
|
2471
|
+
});
|
|
2472
|
+
el("fault-form", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2473
|
+
event.preventDefault();
|
|
2474
|
+
const preset = el("fault-preset", HTMLSelectElement).value;
|
|
2475
|
+
if (preset !== "") {
|
|
2476
|
+
api("POST", "/faults", { preset }).then(() => renderFaults()).catch(showError);
|
|
2477
|
+
return;
|
|
2478
|
+
}
|
|
2479
|
+
const status = Number(el("fault-status", HTMLInputElement).value);
|
|
2480
|
+
if (!Number.isFinite(status)) {
|
|
2481
|
+
showError(new Error("Status must be a number, or choose a preset"));
|
|
2482
|
+
return;
|
|
2483
|
+
}
|
|
2484
|
+
const path = el("fault-path", HTMLInputElement).value;
|
|
2485
|
+
const method = el("fault-method", HTMLInputElement).value;
|
|
2486
|
+
api("POST", "/faults", {
|
|
2487
|
+
status,
|
|
2488
|
+
...path !== "" ? { pathPrefix: path } : {},
|
|
2489
|
+
...method !== "" ? { method } : {}
|
|
2490
|
+
}).then(() => renderFaults()).catch(showError);
|
|
2491
|
+
});
|
|
2492
|
+
el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
|
|
2493
|
+
api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
|
|
2494
|
+
});
|
|
2495
|
+
el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
|
|
2496
|
+
api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
|
|
2497
|
+
});
|
|
2498
|
+
el("route-list", HTMLElement).addEventListener("click", (event) => {
|
|
2499
|
+
const target = event.target;
|
|
2500
|
+
if (!(target instanceof Element))
|
|
2501
|
+
return;
|
|
2502
|
+
const button = target.closest("[data-route]");
|
|
2503
|
+
if (!(button instanceof HTMLElement))
|
|
2504
|
+
return;
|
|
2505
|
+
const route = button.dataset.route;
|
|
2506
|
+
if (route === void 0)
|
|
2507
|
+
return;
|
|
2508
|
+
state.route = route;
|
|
2509
|
+
el("route-title", HTMLElement).textContent = route;
|
|
2510
|
+
const method = route.split(" ")[0] ?? "";
|
|
2511
|
+
el("route-body", HTMLTextAreaElement).disabled = method === "GET" || method === "DELETE";
|
|
2512
|
+
});
|
|
2513
|
+
el("route-form", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2514
|
+
event.preventDefault();
|
|
2515
|
+
const send = async () => {
|
|
2516
|
+
if (state.route === null)
|
|
2517
|
+
throw new Error("Choose a route first");
|
|
2518
|
+
const parts = state.route.split(" ");
|
|
2519
|
+
const method = parts[0];
|
|
2520
|
+
const path = parts.slice(1).join(" ");
|
|
2521
|
+
if (method === void 0)
|
|
2522
|
+
throw new Error("Choose a route first");
|
|
2523
|
+
const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_match, key) => prompt(`Value for ${key}`) || "");
|
|
2524
|
+
let body;
|
|
2525
|
+
const routeBody = el("route-body", HTMLTextAreaElement);
|
|
2526
|
+
if (method !== "GET" && method !== "DELETE" && routeBody.value.trim() !== "") {
|
|
2527
|
+
try {
|
|
2528
|
+
body = readJson2(routeBody.value);
|
|
2529
|
+
} catch {
|
|
2530
|
+
throw new Error("Body is not JSON");
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
const data = await api(method, concrete, body);
|
|
2534
|
+
el("route-result", HTMLElement).textContent = JSON.stringify(data, null, 2);
|
|
2535
|
+
};
|
|
2536
|
+
send().catch(showError);
|
|
2537
|
+
});
|
|
2538
|
+
const decodePanel = (value) => {
|
|
2539
|
+
if (!isRecord2(value) || typeof value.id !== "string" || typeof value.title !== "string") {
|
|
2540
|
+
return void 0;
|
|
2541
|
+
}
|
|
2542
|
+
const panel = {
|
|
2543
|
+
id: value.id,
|
|
2544
|
+
title: value.title,
|
|
2545
|
+
description: typeof value.description === "string" ? value.description : "",
|
|
2546
|
+
html: typeof value.html === "string" ? value.html : ""
|
|
2547
|
+
};
|
|
2548
|
+
if (typeof value.script === "string")
|
|
2549
|
+
panel.script = value.script;
|
|
2550
|
+
return panel;
|
|
2551
|
+
};
|
|
2552
|
+
const mountPanels = (panels) => {
|
|
2553
|
+
const nav = el("nav", HTMLElement);
|
|
2554
|
+
const views = el("views", HTMLElement);
|
|
2555
|
+
for (const panel of panels) {
|
|
2556
|
+
const button = document.createElement("button");
|
|
2557
|
+
button.type = "button";
|
|
2558
|
+
button.dataset.view = panel.id;
|
|
2559
|
+
button.textContent = panel.title;
|
|
2560
|
+
nav.append(button);
|
|
2561
|
+
const section = document.createElement("section");
|
|
2562
|
+
section.className = "view";
|
|
2563
|
+
section.id = `view-${panel.id}`;
|
|
2564
|
+
section.hidden = true;
|
|
2565
|
+
const title = document.createElement("h2");
|
|
2566
|
+
title.textContent = panel.title;
|
|
2567
|
+
const lede = document.createElement("p");
|
|
2568
|
+
lede.className = "lede";
|
|
2569
|
+
lede.textContent = panel.description;
|
|
2570
|
+
const body = document.createElement("div");
|
|
2571
|
+
body.className = "panel-body";
|
|
2572
|
+
body.id = `panel-${panel.id}`;
|
|
2573
|
+
body.innerHTML = panel.html;
|
|
2574
|
+
section.append(title, lede, body);
|
|
2575
|
+
views.append(section);
|
|
2576
|
+
if (panel.script === void 0)
|
|
2577
|
+
continue;
|
|
2578
|
+
try {
|
|
2579
|
+
const compiled = new Function("root", "api", panel.script);
|
|
2580
|
+
if (!isPanelScript(compiled))
|
|
2581
|
+
throw new Error("panel script did not compile");
|
|
2582
|
+
compiled(body, panelApi);
|
|
2583
|
+
} catch (error) {
|
|
2584
|
+
const note = document.createElement("p");
|
|
2585
|
+
note.className = "banner show";
|
|
2586
|
+
note.textContent = error instanceof Error ? error.message : String(error);
|
|
2587
|
+
body.append(note);
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
};
|
|
2591
|
+
startAdminBrand({
|
|
2592
|
+
document,
|
|
2593
|
+
$: (id) => document.getElementById(id),
|
|
2594
|
+
params,
|
|
2595
|
+
location: { href: location.href, origin: location.origin },
|
|
2596
|
+
fetch,
|
|
2597
|
+
brandsUrl: config.brandsUrl
|
|
2598
|
+
});
|
|
2599
|
+
loadNamespaces().then(() => api("GET", "/ui/manifest")).then((manifest) => {
|
|
2600
|
+
const panels = requireList(isRecord2(manifest) ? manifest.panels : void 0, "Panels").flatMap((panel) => {
|
|
2601
|
+
const decoded = decodePanel(panel);
|
|
2602
|
+
return decoded === void 0 ? [] : [decoded];
|
|
2603
|
+
});
|
|
2604
|
+
mountPanels(panels);
|
|
2605
|
+
return refresh();
|
|
2606
|
+
}).catch(showError);
|
|
2607
|
+
}
|
|
2608
|
+
var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
|
|
2609
|
+
var adminClientSource = (config) => {
|
|
2610
|
+
const payload = JSON.stringify(config).replace(/</g, "\\u003c");
|
|
2611
|
+
return `<script>
|
|
2612
|
+
${clientPrelude}
|
|
2613
|
+
bootAdmin(${payload});
|
|
2614
|
+
</script>`;
|
|
2615
|
+
};
|
|
2616
|
+
|
|
1391
2617
|
// ../core/dist/surface.js
|
|
1392
2618
|
var defineMock = (runtime) => runtime;
|
|
1393
2619
|
var STANDARD_ADMIN_ROUTES = [
|
|
@@ -1456,94 +2682,106 @@ var escapeHtml = (value) => value.replace(/[&<>"']/g, (char) => {
|
|
|
1456
2682
|
return "'";
|
|
1457
2683
|
}
|
|
1458
2684
|
});
|
|
2685
|
+
var ADMIN_BRANDS_URL = "https://mockingbird.chrisvouga.dev/brands.json";
|
|
1459
2686
|
var renderAdminDocument = (service) => {
|
|
1460
2687
|
const name = escapeHtml(service);
|
|
1461
|
-
const
|
|
2688
|
+
const script = adminClientSource({
|
|
2689
|
+
standardRoutes: STANDARD_ADMIN_ROUTES,
|
|
2690
|
+
adminPrefix: ADMIN_PREFIX,
|
|
2691
|
+
adminKeyHeader: ADMIN_KEY_HEADER,
|
|
2692
|
+
brandsUrl: ADMIN_BRANDS_URL,
|
|
2693
|
+
service
|
|
2694
|
+
});
|
|
1462
2695
|
return `<!DOCTYPE html>
|
|
1463
2696
|
<html lang="en">
|
|
1464
2697
|
<head>
|
|
1465
2698
|
<meta charset="utf-8">
|
|
1466
2699
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1467
2700
|
<meta name="color-scheme" content="light dark">
|
|
1468
|
-
<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='
|
|
2701
|
+
<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">
|
|
1469
2702
|
<title>${name} admin</title>
|
|
1470
2703
|
<style>
|
|
2704
|
+
${CSS_RESET}
|
|
1471
2705
|
:root {
|
|
1472
2706
|
color-scheme: light;
|
|
1473
|
-
--bg: #
|
|
2707
|
+
--bg: #fafafa;
|
|
1474
2708
|
--bg-elev: #ffffff;
|
|
1475
|
-
--bg-muted: #
|
|
1476
|
-
--bg-inset: #
|
|
1477
|
-
--line: #
|
|
1478
|
-
--line-strong: #
|
|
1479
|
-
--ink: #
|
|
1480
|
-
--muted: #
|
|
1481
|
-
--faint: #
|
|
1482
|
-
--accent: #
|
|
1483
|
-
--accent-ink: #
|
|
1484
|
-
--accent-soft: #
|
|
1485
|
-
--ok: #
|
|
1486
|
-
--ok-soft: #
|
|
1487
|
-
--warn: #
|
|
1488
|
-
--warn-soft: #
|
|
1489
|
-
--err: #
|
|
1490
|
-
--err-soft: #
|
|
1491
|
-
--shadow:
|
|
1492
|
-
--sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
2709
|
+
--bg-muted: #f4f4f5;
|
|
2710
|
+
--bg-inset: #f4f4f5;
|
|
2711
|
+
--line: #dedee3;
|
|
2712
|
+
--line-strong: #c4c4cc;
|
|
2713
|
+
--ink: #18181b;
|
|
2714
|
+
--muted: #62626b;
|
|
2715
|
+
--faint: #71717a;
|
|
2716
|
+
--accent: #27272a;
|
|
2717
|
+
--accent-ink: #ffffff;
|
|
2718
|
+
--accent-soft: #f4f4f5;
|
|
2719
|
+
--ok: #166534;
|
|
2720
|
+
--ok-soft: #f0fdf4;
|
|
2721
|
+
--warn: #a16207;
|
|
2722
|
+
--warn-soft: #fefce8;
|
|
2723
|
+
--err: #a82d32;
|
|
2724
|
+
--err-soft: #fef2f2;
|
|
2725
|
+
--shadow: none;
|
|
2726
|
+
--sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
1493
2727
|
--mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
|
|
1494
|
-
--radius:
|
|
1495
|
-
--header:
|
|
1496
|
-
--nav:
|
|
2728
|
+
--radius: 10px;
|
|
2729
|
+
--header: 56px;
|
|
2730
|
+
--nav: 200px;
|
|
1497
2731
|
}
|
|
1498
2732
|
:root[data-theme="dark"] {
|
|
1499
2733
|
color-scheme: dark;
|
|
1500
|
-
--bg: #
|
|
1501
|
-
--bg-elev: #
|
|
1502
|
-
--bg-muted: #
|
|
1503
|
-
--bg-inset: #
|
|
1504
|
-
--line: #
|
|
1505
|
-
--line-strong: #
|
|
1506
|
-
--ink: #
|
|
1507
|
-
--muted: #
|
|
1508
|
-
--faint: #
|
|
1509
|
-
--accent: #
|
|
1510
|
-
--accent-ink: #
|
|
1511
|
-
--accent-soft: #
|
|
1512
|
-
--ok: #
|
|
1513
|
-
--ok-soft: #
|
|
1514
|
-
--warn: #
|
|
1515
|
-
--warn-soft: #
|
|
1516
|
-
--err: #
|
|
1517
|
-
--err-soft: #
|
|
1518
|
-
--shadow:
|
|
2734
|
+
--bg: #111113;
|
|
2735
|
+
--bg-elev: #19191c;
|
|
2736
|
+
--bg-muted: #242428;
|
|
2737
|
+
--bg-inset: #111113;
|
|
2738
|
+
--line: #36363c;
|
|
2739
|
+
--line-strong: #52525b;
|
|
2740
|
+
--ink: #f4f4f5;
|
|
2741
|
+
--muted: #a9a9b2;
|
|
2742
|
+
--faint: #71717a;
|
|
2743
|
+
--accent: #e4e4e7;
|
|
2744
|
+
--accent-ink: #18181b;
|
|
2745
|
+
--accent-soft: #242428;
|
|
2746
|
+
--ok: #86efac;
|
|
2747
|
+
--ok-soft: #14241b;
|
|
2748
|
+
--warn: #fde68a;
|
|
2749
|
+
--warn-soft: #2a2410;
|
|
2750
|
+
--err: #ffaaaa;
|
|
2751
|
+
--err-soft: #2c1516;
|
|
2752
|
+
--shadow: none;
|
|
1519
2753
|
}
|
|
1520
2754
|
@media (prefers-color-scheme: dark) {
|
|
1521
2755
|
:root:not([data-theme="light"]) {
|
|
1522
2756
|
color-scheme: dark;
|
|
1523
|
-
--bg: #
|
|
1524
|
-
--bg-elev: #
|
|
1525
|
-
--bg-muted: #
|
|
1526
|
-
--bg-inset: #
|
|
1527
|
-
--line: #
|
|
1528
|
-
--line-strong: #
|
|
1529
|
-
--ink: #
|
|
1530
|
-
--muted: #
|
|
1531
|
-
--faint: #
|
|
1532
|
-
--accent: #
|
|
1533
|
-
--accent-ink: #
|
|
1534
|
-
--accent-soft: #
|
|
1535
|
-
--ok: #
|
|
1536
|
-
--ok-soft: #
|
|
1537
|
-
--warn: #
|
|
1538
|
-
--warn-soft: #
|
|
1539
|
-
--err: #
|
|
1540
|
-
--err-soft: #
|
|
1541
|
-
--shadow:
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
|
-
html
|
|
2757
|
+
--bg: #111113;
|
|
2758
|
+
--bg-elev: #19191c;
|
|
2759
|
+
--bg-muted: #242428;
|
|
2760
|
+
--bg-inset: #111113;
|
|
2761
|
+
--line: #36363c;
|
|
2762
|
+
--line-strong: #52525b;
|
|
2763
|
+
--ink: #f4f4f5;
|
|
2764
|
+
--muted: #a9a9b2;
|
|
2765
|
+
--faint: #71717a;
|
|
2766
|
+
--accent: #e4e4e7;
|
|
2767
|
+
--accent-ink: #18181b;
|
|
2768
|
+
--accent-soft: #242428;
|
|
2769
|
+
--ok: #86efac;
|
|
2770
|
+
--ok-soft: #14241b;
|
|
2771
|
+
--warn: #fde68a;
|
|
2772
|
+
--warn-soft: #2a2410;
|
|
2773
|
+
--err: #ffaaaa;
|
|
2774
|
+
--err-soft: #2c1516;
|
|
2775
|
+
--shadow: none;
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
/* Fill the viewport, then grow with the content so the background covers the scroll. */
|
|
2779
|
+
html { height: 100%; }
|
|
1546
2780
|
body {
|
|
2781
|
+
margin: 0;
|
|
2782
|
+
min-height: 100%;
|
|
2783
|
+
display: flex;
|
|
2784
|
+
flex-direction: column;
|
|
1547
2785
|
font-family: var(--sans);
|
|
1548
2786
|
background: var(--bg);
|
|
1549
2787
|
color: var(--ink);
|
|
@@ -1552,24 +2790,37 @@ var renderAdminDocument = (service) => {
|
|
|
1552
2790
|
padding-right: env(safe-area-inset-right);
|
|
1553
2791
|
-webkit-text-size-adjust: 100%;
|
|
1554
2792
|
}
|
|
1555
|
-
button, input, select, textarea { font: inherit; color: inherit; }
|
|
1556
|
-
button { cursor: pointer; }
|
|
1557
2793
|
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
1558
|
-
|
|
1559
|
-
* { scroll-behavior: auto !important; transition: none !important; }
|
|
1560
|
-
}
|
|
1561
|
-
.app { min-height: 100%; display: flex; flex-direction: column; }
|
|
2794
|
+
.app { flex: 1 0 auto; display: flex; flex-direction: column; min-width: 0; width: 100%; }
|
|
1562
2795
|
header.top {
|
|
1563
2796
|
position: sticky; top: 0; z-index: 5;
|
|
1564
2797
|
display: flex; align-items: center; gap: 12px;
|
|
1565
2798
|
min-height: var(--header); padding: 10px 16px;
|
|
1566
|
-
background:
|
|
2799
|
+
background: var(--bg-elev);
|
|
1567
2800
|
border-bottom: 1px solid var(--line);
|
|
1568
|
-
|
|
1569
|
-
}
|
|
1570
|
-
.brand {
|
|
1571
|
-
|
|
1572
|
-
|
|
2801
|
+
min-width: 0; max-width: 100%;
|
|
2802
|
+
}
|
|
2803
|
+
.brand {
|
|
2804
|
+
display: flex; flex-flow: row wrap; align-items: center; gap: 14px;
|
|
2805
|
+
min-width: 0; max-width: 100%; margin-right: auto;
|
|
2806
|
+
}
|
|
2807
|
+
.brand-id { display: flex; flex-direction: column; min-width: 0; flex: none; }
|
|
2808
|
+
.brand-id strong { font-size: 15px; letter-spacing: -0.01em; }
|
|
2809
|
+
.brand-id span { color: var(--faint); font-size: 12px; }
|
|
2810
|
+
.vendor {
|
|
2811
|
+
display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
|
|
2812
|
+
min-width: 0; max-width: 100%; font-size: 12px; color: var(--muted);
|
|
2813
|
+
}
|
|
2814
|
+
.vendor[hidden] { display: none; }
|
|
2815
|
+
/* Logos are drawn for a light tile, same as the docs site, so the plate stays white. */
|
|
2816
|
+
.vendor img {
|
|
2817
|
+
width: 16px; height: 16px; padding: 2px; box-sizing: content-box;
|
|
2818
|
+
border-radius: 4px; border: 1px solid var(--line); background: #fff; object-fit: contain;
|
|
2819
|
+
}
|
|
2820
|
+
.vendor-name { color: var(--ink); font-weight: 600; }
|
|
2821
|
+
.vendor-sep { color: var(--faint); }
|
|
2822
|
+
.vendor-link { color: inherit; text-decoration: none; }
|
|
2823
|
+
.vendor-link:hover { color: var(--ink); text-decoration: underline; }
|
|
1573
2824
|
.controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
|
1574
2825
|
label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
|
|
1575
2826
|
label.field input, label.field select, .controls input[type="password"] {
|
|
@@ -1587,7 +2838,7 @@ var renderAdminDocument = (service) => {
|
|
|
1587
2838
|
record or the full nav row would widen the page past the viewport. */
|
|
1588
2839
|
.layout {
|
|
1589
2840
|
display: grid; grid-template-columns: var(--nav) minmax(0, 1fr);
|
|
1590
|
-
flex: 1
|
|
2841
|
+
flex: 1 0 auto; min-width: 0;
|
|
1591
2842
|
}
|
|
1592
2843
|
nav.side {
|
|
1593
2844
|
border-right: 1px solid var(--line); padding: 12px;
|
|
@@ -1641,8 +2892,8 @@ var renderAdminDocument = (service) => {
|
|
|
1641
2892
|
.banner.show { display: block; }
|
|
1642
2893
|
.empty { color: var(--muted); padding: 24px 8px; }
|
|
1643
2894
|
dialog {
|
|
1644
|
-
border: 1px solid var(--line); border-radius:
|
|
1645
|
-
color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow:
|
|
2895
|
+
border: 1px solid var(--line); border-radius: var(--radius); padding: 0; background: var(--bg-elev);
|
|
2896
|
+
color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow: none;
|
|
1646
2897
|
}
|
|
1647
2898
|
dialog::backdrop { background: rgb(20 19 17 / 0.45); }
|
|
1648
2899
|
dialog form, .dialog-body { padding: 16px; display: flex; flex-direction: column; gap: 10px; }
|
|
@@ -1669,7 +2920,7 @@ var renderAdminDocument = (service) => {
|
|
|
1669
2920
|
padding-top: max(10px, env(safe-area-inset-top));
|
|
1670
2921
|
}
|
|
1671
2922
|
input, select, textarea { font-size: 16px; }
|
|
1672
|
-
.controls { width: 100%; }
|
|
2923
|
+
.brand, .vendor, .controls { width: 100%; }
|
|
1673
2924
|
label.field, label.field select, label.field input { flex: 1; min-width: 0; }
|
|
1674
2925
|
.layout { grid-template-columns: minmax(0, 1fr); }
|
|
1675
2926
|
nav.side {
|
|
@@ -1700,8 +2951,11 @@ var renderAdminDocument = (service) => {
|
|
|
1700
2951
|
<div class="app" data-service="${name}" data-mockingbird-admin>
|
|
1701
2952
|
<header class="top">
|
|
1702
2953
|
<div class="brand">
|
|
1703
|
-
<
|
|
1704
|
-
|
|
2954
|
+
<div class="brand-id">
|
|
2955
|
+
<strong>${name}</strong>
|
|
2956
|
+
<span>Admin</span>
|
|
2957
|
+
</div>
|
|
2958
|
+
<div class="vendor" id="vendor" hidden></div>
|
|
1705
2959
|
</div>
|
|
1706
2960
|
<div class="controls">
|
|
1707
2961
|
<label class="field">Namespace
|
|
@@ -1831,460 +3085,7 @@ var renderAdminDocument = (service) => {
|
|
|
1831
3085
|
</div>
|
|
1832
3086
|
</form>
|
|
1833
3087
|
</dialog>
|
|
1834
|
-
|
|
1835
|
-
(() => {
|
|
1836
|
-
const STANDARD = new Set(${standard})
|
|
1837
|
-
const state = {
|
|
1838
|
-
namespace: "default",
|
|
1839
|
-
key: sessionStorage.getItem("mockingbird-admin-key") || "",
|
|
1840
|
-
view: "overview",
|
|
1841
|
-
collection: "",
|
|
1842
|
-
shape: null,
|
|
1843
|
-
route: null,
|
|
1844
|
-
editing: null
|
|
1845
|
-
}
|
|
1846
|
-
const $ = (id) => document.getElementById(id)
|
|
1847
|
-
const banner = $("banner")
|
|
1848
|
-
const keyInput = $("admin-key")
|
|
1849
|
-
keyInput.value = state.key
|
|
1850
|
-
const params = new URLSearchParams(location.search)
|
|
1851
|
-
if (params.get("key")) {
|
|
1852
|
-
state.key = params.get("key")
|
|
1853
|
-
keyInput.value = state.key
|
|
1854
|
-
sessionStorage.setItem("mockingbird-admin-key", state.key)
|
|
1855
|
-
params.delete("key")
|
|
1856
|
-
const next = params.toString()
|
|
1857
|
-
history.replaceState(null, "", location.pathname + (next ? "?" + next : ""))
|
|
1858
|
-
}
|
|
1859
|
-
if (params.get("namespace")) state.namespace = params.get("namespace")
|
|
1860
|
-
|
|
1861
|
-
const showError = (error) => {
|
|
1862
|
-
banner.textContent = error instanceof Error ? error.message : String(error)
|
|
1863
|
-
banner.classList.add("show")
|
|
1864
|
-
}
|
|
1865
|
-
const clearError = () => banner.classList.remove("show")
|
|
1866
|
-
|
|
1867
|
-
async function originGet(path) {
|
|
1868
|
-
const response = await fetch(path, { headers: { accept: "application/json" } })
|
|
1869
|
-
if (!response.ok) throw new Error(response.status + " " + path)
|
|
1870
|
-
return response.json()
|
|
1871
|
-
}
|
|
1872
|
-
async function api(method, path, body) {
|
|
1873
|
-
const headers = { accept: "application/json" }
|
|
1874
|
-
if (state.key) headers["${ADMIN_KEY_HEADER}"] = state.key
|
|
1875
|
-
if (state.namespace) headers["x-mockingbird-namespace"] = state.namespace
|
|
1876
|
-
if (body !== undefined) headers["content-type"] = "application/json"
|
|
1877
|
-
const response = await fetch("${ADMIN_PREFIX}" + path, {
|
|
1878
|
-
method,
|
|
1879
|
-
headers,
|
|
1880
|
-
body: body === undefined ? undefined : JSON.stringify(body)
|
|
1881
|
-
})
|
|
1882
|
-
const text = await response.text()
|
|
1883
|
-
let data = null
|
|
1884
|
-
if (text) {
|
|
1885
|
-
try { data = JSON.parse(text) } catch { data = { raw: text } }
|
|
1886
|
-
}
|
|
1887
|
-
if (response.status === 401) {
|
|
1888
|
-
keyInput.focus()
|
|
1889
|
-
throw new Error((data && data.error && data.error.message) || "Admin key required")
|
|
1890
|
-
}
|
|
1891
|
-
if (!response.ok) {
|
|
1892
|
-
throw new Error((data && data.error && data.error.message) || (response.status + " " + method + " " + path))
|
|
1893
|
-
}
|
|
1894
|
-
return data
|
|
1895
|
-
}
|
|
1896
|
-
const panelApi = {
|
|
1897
|
-
get: (path) => api("GET", path),
|
|
1898
|
-
send: (method, path, body) => api(method, path, body),
|
|
1899
|
-
namespace: () => state.namespace
|
|
1900
|
-
}
|
|
1901
|
-
|
|
1902
|
-
function setTheme(theme) {
|
|
1903
|
-
if (theme) document.documentElement.dataset.theme = theme
|
|
1904
|
-
else delete document.documentElement.dataset.theme
|
|
1905
|
-
}
|
|
1906
|
-
const storedTheme = localStorage.getItem("mockingbird-admin-theme")
|
|
1907
|
-
if (storedTheme) setTheme(storedTheme)
|
|
1908
|
-
$("theme").addEventListener("click", () => {
|
|
1909
|
-
const current = document.documentElement.dataset.theme
|
|
1910
|
-
const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches
|
|
1911
|
-
const next = dark ? "light" : "dark"
|
|
1912
|
-
localStorage.setItem("mockingbird-admin-theme", next)
|
|
1913
|
-
setTheme(next)
|
|
1914
|
-
})
|
|
1915
|
-
|
|
1916
|
-
function show(view) {
|
|
1917
|
-
state.view = view
|
|
1918
|
-
for (const button of document.querySelectorAll("#nav button")) {
|
|
1919
|
-
button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false")
|
|
1920
|
-
}
|
|
1921
|
-
for (const section of document.querySelectorAll("main .view")) {
|
|
1922
|
-
section.hidden = section.id !== "view-" + view
|
|
1923
|
-
}
|
|
1924
|
-
}
|
|
1925
|
-
document.getElementById("nav").addEventListener("click", (event) => {
|
|
1926
|
-
const button = event.target.closest("button[data-view]")
|
|
1927
|
-
if (!button) return
|
|
1928
|
-
show(button.dataset.view)
|
|
1929
|
-
refresh().catch(showError)
|
|
1930
|
-
})
|
|
1931
|
-
|
|
1932
|
-
function cards(items) {
|
|
1933
|
-
return '<div class="grid">' + items.map((item) =>
|
|
1934
|
-
'<article class="card"><div class="k">' + item.k + '</div><div class="v">' + item.v + '</div></article>'
|
|
1935
|
-
).join("") + "</div>"
|
|
1936
|
-
}
|
|
1937
|
-
function table(columns, rows) {
|
|
1938
|
-
if (rows.length === 0) return '<p class="empty">Nothing here yet.</p>'
|
|
1939
|
-
const head = "<tr>" + columns.map((column) => "<th>" + column.label + "</th>").join("") + "</tr>"
|
|
1940
|
-
const body = rows.map((row) => "<tr" + (row.attrs || "") + ">" + columns.map((column) =>
|
|
1941
|
-
'<td data-label="' + column.label + '">' + column.cell(row) + "</td>"
|
|
1942
|
-
).join("") + "</tr>").join("")
|
|
1943
|
-
return "<table><thead>" + head + "</thead><tbody>" + body + "</tbody></table>"
|
|
1944
|
-
}
|
|
1945
|
-
const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({
|
|
1946
|
-
"&": "&", "<": "<", ">": ">", '"': """, "'": "'"
|
|
1947
|
-
}[char]))
|
|
1948
|
-
const preview = (value) => {
|
|
1949
|
-
const text = JSON.stringify(value)
|
|
1950
|
-
return esc(text.length > 180 ? text.slice(0, 180) + "\u2026" : text)
|
|
1951
|
-
}
|
|
1952
|
-
const when = (epoch) => {
|
|
1953
|
-
const date = new Date(epoch)
|
|
1954
|
-
return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString()
|
|
1955
|
-
}
|
|
1956
|
-
const clockCard = (epoch) => {
|
|
1957
|
-
const date = new Date(epoch)
|
|
1958
|
-
if (Number.isNaN(date.getTime())) return esc(String(epoch))
|
|
1959
|
-
const iso = date.toISOString()
|
|
1960
|
-
return esc(iso.slice(0, 10)) + '<span class="clock-time">' + esc(iso.slice(11, 19)) + " UTC</span>"
|
|
1961
|
-
}
|
|
1962
|
-
|
|
1963
|
-
async function loadNamespaces() {
|
|
1964
|
-
const data = await api("GET", "/namespaces")
|
|
1965
|
-
const select = $("namespace")
|
|
1966
|
-
const names = data.namespaces && data.namespaces.length ? data.namespaces : [data.default || "default"]
|
|
1967
|
-
if (!names.includes(state.namespace)) names.unshift(state.namespace)
|
|
1968
|
-
select.innerHTML = names.map((name) =>
|
|
1969
|
-
'<option value="' + esc(name) + '">' + esc(name) + "</option>"
|
|
1970
|
-
).join("")
|
|
1971
|
-
select.value = state.namespace
|
|
1972
|
-
}
|
|
1973
|
-
|
|
1974
|
-
async function renderOverview() {
|
|
1975
|
-
const [health, shape, clock, journal] = await Promise.all([
|
|
1976
|
-
originGet("/health"),
|
|
1977
|
-
api("GET", "/state"),
|
|
1978
|
-
api("GET", "/clock"),
|
|
1979
|
-
api("GET", "/requests?limit=8")
|
|
1980
|
-
])
|
|
1981
|
-
state.shape = shape
|
|
1982
|
-
$("overview-cards").innerHTML = cards([
|
|
1983
|
-
{ k: "Service", v: esc(health.service || "${name}") },
|
|
1984
|
-
{ k: "Status", v: esc(health.status || "ok") },
|
|
1985
|
-
{ k: "Namespace", v: esc(state.namespace) },
|
|
1986
|
-
{ k: "Collections", v: String(shape.collections.length) },
|
|
1987
|
-
{ k: "Records", v: String(shape.collections.reduce((sum, item) => sum + item.count, 0)) },
|
|
1988
|
-
{ k: "Clock", v: clockCard(clock.now) }
|
|
1989
|
-
])
|
|
1990
|
-
const requests = (journal && journal.requests) || []
|
|
1991
|
-
$("overview-requests").innerHTML = table(
|
|
1992
|
-
[
|
|
1993
|
-
{ label: "Method", cell: (row) => esc(row.method) },
|
|
1994
|
-
{ label: "Path", cell: (row) => esc(row.path) },
|
|
1995
|
-
{ label: "Status", cell: (row) => '<span class="status-' + String(row.status).charAt(0) + '">' + esc(row.status) + "</span>" },
|
|
1996
|
-
{ label: "Operation", cell: (row) => esc(row.operationId || "") }
|
|
1997
|
-
],
|
|
1998
|
-
requests
|
|
1999
|
-
)
|
|
2000
|
-
}
|
|
2001
|
-
|
|
2002
|
-
function schemaChips(collection) {
|
|
2003
|
-
if (!collection || collection.fields.length === 0) {
|
|
2004
|
-
return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>'
|
|
2005
|
-
}
|
|
2006
|
-
return '<div class="chips">' + collection.fields.map((field) =>
|
|
2007
|
-
'<span class="chip">' + esc(field.name) + ' <i>' + esc(field.kind) + (field.optional ? "?" : "") + "</i></span>"
|
|
2008
|
-
).join("") + "</div>"
|
|
2009
|
-
}
|
|
2010
|
-
|
|
2011
|
-
async function renderState() {
|
|
2012
|
-
const shape = await api("GET", "/state")
|
|
2013
|
-
state.shape = shape
|
|
2014
|
-
const host = $("collections")
|
|
2015
|
-
if (shape.collections.length === 0) {
|
|
2016
|
-
host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>'
|
|
2017
|
-
$("schema").innerHTML = ""
|
|
2018
|
-
$("records").innerHTML = ""
|
|
2019
|
-
return
|
|
2020
|
-
}
|
|
2021
|
-
if (!shape.collections.some((item) => item.name === state.collection)) {
|
|
2022
|
-
state.collection = shape.collections[0].name
|
|
2023
|
-
}
|
|
2024
|
-
host.innerHTML = shape.collections.map((item) =>
|
|
2025
|
-
'<button type="button" class="collection" data-collection="' + esc(item.name) + '" aria-current="' +
|
|
2026
|
-
(item.name === state.collection ? "true" : "false") + '"><strong>' + esc(item.label) +
|
|
2027
|
-
'</strong><small>' + esc(item.count) + " \xB7 " + esc(item.source) + "</small></button>"
|
|
2028
|
-
).join("")
|
|
2029
|
-
const selected = shape.collections.find((item) => item.name === state.collection)
|
|
2030
|
-
$("schema").innerHTML = "<h3>" + esc(selected.label) + "</h3>" +
|
|
2031
|
-
(selected.description ? "<p class=" + '"lede">' + esc(selected.description) + "</p>" : "") +
|
|
2032
|
-
schemaChips(selected)
|
|
2033
|
-
const page = await api("GET", "/state/" + encodeURIComponent(state.collection) + "?limit=50")
|
|
2034
|
-
const rows = page.records.map((record) => ({
|
|
2035
|
-
...record,
|
|
2036
|
-
attrs: ' class="clickable" data-id="' + esc(record.id) + '"'
|
|
2037
|
-
}))
|
|
2038
|
-
$("records").innerHTML = table(
|
|
2039
|
-
[
|
|
2040
|
-
{ label: "Id", cell: (row) => "<code>" + esc(row.id) + "</code>" },
|
|
2041
|
-
{ label: "Seq", cell: (row) => esc(row.seq) },
|
|
2042
|
-
{ label: "Value", cell: (row) => preview(row.value) }
|
|
2043
|
-
],
|
|
2044
|
-
rows
|
|
2045
|
-
)
|
|
2046
|
-
}
|
|
2047
|
-
|
|
2048
|
-
async function renderClock() {
|
|
2049
|
-
const clock = await api("GET", "/clock")
|
|
2050
|
-
$("clock-readout").innerHTML = "<div class='k'>Now</div><div class='v'>" + esc(when(clock.now)) +
|
|
2051
|
-
"</div><p>" + esc(clock.now) + " ms \xB7 offset " + esc(clock.offsetMs) + " ms " +
|
|
2052
|
-
(clock.frozen ? "<span class='badge warn'>frozen</span>" : "<span class='badge ok'>live</span>") + "</p>"
|
|
2053
|
-
$("freeze").textContent = clock.frozen ? "Unfreeze" : "Freeze"
|
|
2054
|
-
$("freeze").dataset.frozen = clock.frozen ? "1" : "0"
|
|
2055
|
-
}
|
|
2056
|
-
|
|
2057
|
-
async function renderFaults() {
|
|
2058
|
-
const [faults, presets] = await Promise.all([
|
|
2059
|
-
api("GET", "/faults"),
|
|
2060
|
-
api("GET", "/faults/presets").catch(() => ({ presets: [] }))
|
|
2061
|
-
])
|
|
2062
|
-
const select = $("fault-preset")
|
|
2063
|
-
const current = select.value
|
|
2064
|
-
const list = presets.presets || []
|
|
2065
|
-
select.innerHTML = '<option value="">Custom rule</option>' + list.map((preset) =>
|
|
2066
|
-
'<option value="' + esc(preset.name) + '">' + esc(preset.name) + "</option>"
|
|
2067
|
-
).join("")
|
|
2068
|
-
select.value = current
|
|
2069
|
-
const rules = faults.faults || []
|
|
2070
|
-
$("fault-list").innerHTML = table(
|
|
2071
|
-
[
|
|
2072
|
-
{ label: "Id", cell: (row) => esc(row.id) },
|
|
2073
|
-
{ label: "Match", cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter(Boolean).join(" ") || "all") },
|
|
2074
|
-
{ label: "Effect", cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || "")) }
|
|
2075
|
-
],
|
|
2076
|
-
rules
|
|
2077
|
-
)
|
|
2078
|
-
}
|
|
2079
|
-
|
|
2080
|
-
async function renderJournal() {
|
|
2081
|
-
const journal = await api("GET", "/requests?limit=100")
|
|
2082
|
-
$("journal").innerHTML = table(
|
|
2083
|
-
[
|
|
2084
|
-
{ label: "When", cell: (row) => esc(row.at || "") },
|
|
2085
|
-
{ label: "Method", cell: (row) => esc(row.method) },
|
|
2086
|
-
{ label: "Path", cell: (row) => esc(row.path) },
|
|
2087
|
-
{ label: "Status", cell: (row) => esc(row.status) },
|
|
2088
|
-
{ label: "ms", cell: (row) => esc(row.durationMs) }
|
|
2089
|
-
],
|
|
2090
|
-
journal.requests || []
|
|
2091
|
-
)
|
|
2092
|
-
}
|
|
2093
|
-
|
|
2094
|
-
async function renderRoutes() {
|
|
2095
|
-
const data = await api("GET", "/")
|
|
2096
|
-
const routes = data.routes || []
|
|
2097
|
-
$("route-list").innerHTML = routes.map((route) => {
|
|
2098
|
-
const extra = STANDARD.has(route) ? "" : " <span class='badge'>extension</span>"
|
|
2099
|
-
return '<div class="route"><span class="method">' + esc(route.split(" ")[0]) +
|
|
2100
|
-
'</span><span>' + esc(route.split(" ").slice(1).join(" ")) + extra +
|
|
2101
|
-
'</span><button class="btn" type="button" data-route="' + esc(route) + '">Use</button></div>'
|
|
2102
|
-
}).join("")
|
|
2103
|
-
}
|
|
2104
|
-
|
|
2105
|
-
const renderers = {
|
|
2106
|
-
overview: renderOverview,
|
|
2107
|
-
state: renderState,
|
|
2108
|
-
clock: renderClock,
|
|
2109
|
-
faults: renderFaults,
|
|
2110
|
-
journal: renderJournal,
|
|
2111
|
-
routes: renderRoutes
|
|
2112
|
-
}
|
|
2113
|
-
|
|
2114
|
-
async function refresh() {
|
|
2115
|
-
clearError()
|
|
2116
|
-
if (state.view in renderers) await renderers[state.view]()
|
|
2117
|
-
}
|
|
2118
|
-
|
|
2119
|
-
$("namespace").addEventListener("change", () => {
|
|
2120
|
-
state.namespace = $("namespace").value
|
|
2121
|
-
refresh().catch(showError)
|
|
2122
|
-
})
|
|
2123
|
-
keyInput.addEventListener("change", () => {
|
|
2124
|
-
state.key = keyInput.value.trim()
|
|
2125
|
-
sessionStorage.setItem("mockingbird-admin-key", state.key)
|
|
2126
|
-
refresh().catch(showError)
|
|
2127
|
-
})
|
|
2128
|
-
$("refresh").addEventListener("click", () => refresh().catch(showError))
|
|
2129
|
-
|
|
2130
|
-
$("collections").addEventListener("click", (event) => {
|
|
2131
|
-
const button = event.target.closest("[data-collection]")
|
|
2132
|
-
if (!button) return
|
|
2133
|
-
state.collection = button.dataset.collection
|
|
2134
|
-
renderState().catch(showError)
|
|
2135
|
-
})
|
|
2136
|
-
$("records").addEventListener("click", (event) => {
|
|
2137
|
-
const row = event.target.closest("[data-id]")
|
|
2138
|
-
if (!row) return
|
|
2139
|
-
openEditor(row.dataset.id).catch(showError)
|
|
2140
|
-
})
|
|
2141
|
-
|
|
2142
|
-
const editor = $("editor")
|
|
2143
|
-
async function openEditor(id) {
|
|
2144
|
-
state.editing = id || null
|
|
2145
|
-
$("editor-title").textContent = id ? "Edit record" : "New record"
|
|
2146
|
-
$("editor-id").value = id || ""
|
|
2147
|
-
$("editor-id").readOnly = Boolean(id)
|
|
2148
|
-
$("editor-delete").hidden = !id
|
|
2149
|
-
if (id) {
|
|
2150
|
-
const record = await api("GET", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
|
|
2151
|
-
$("editor-value").value = JSON.stringify(record.value, null, 2)
|
|
2152
|
-
} else {
|
|
2153
|
-
$("editor-value").value = "{\\n \\n}"
|
|
2154
|
-
}
|
|
2155
|
-
editor.showModal()
|
|
2156
|
-
}
|
|
2157
|
-
$("new-record").addEventListener("click", () => openEditor(null).catch(showError))
|
|
2158
|
-
$("editor-form").addEventListener("submit", (event) => {
|
|
2159
|
-
const submitter = event.submitter
|
|
2160
|
-
if (!submitter || submitter.value !== "save") return
|
|
2161
|
-
event.preventDefault()
|
|
2162
|
-
let value
|
|
2163
|
-
try { value = JSON.parse($("editor-value").value) }
|
|
2164
|
-
catch { showError(new Error("Value is not JSON")); return }
|
|
2165
|
-
const id = $("editor-id").value.trim()
|
|
2166
|
-
const path = "/state/" + encodeURIComponent(state.collection)
|
|
2167
|
-
const request = state.editing
|
|
2168
|
-
? api("PUT", path + "/" + encodeURIComponent(id), { value })
|
|
2169
|
-
: api("POST", path, { id: id || undefined, value })
|
|
2170
|
-
request.then(() => { editor.close(); return renderState() }).catch(showError)
|
|
2171
|
-
})
|
|
2172
|
-
$("editor-delete").addEventListener("click", () => {
|
|
2173
|
-
const id = state.editing
|
|
2174
|
-
if (!id) return
|
|
2175
|
-
api("DELETE", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
|
|
2176
|
-
.then(() => { editor.close(); return renderState() })
|
|
2177
|
-
.catch(showError)
|
|
2178
|
-
})
|
|
2179
|
-
|
|
2180
|
-
document.querySelectorAll("[data-advance]").forEach((button) => {
|
|
2181
|
-
button.addEventListener("click", () => {
|
|
2182
|
-
api("POST", "/clock", { advance: Number(button.dataset.advance) }).then(renderClock).catch(showError)
|
|
2183
|
-
})
|
|
2184
|
-
})
|
|
2185
|
-
$("freeze").addEventListener("click", () => {
|
|
2186
|
-
const frozen = $("freeze").dataset.frozen === "1"
|
|
2187
|
-
api("POST", "/clock", { freeze: !frozen }).then(renderClock).catch(showError)
|
|
2188
|
-
})
|
|
2189
|
-
$("clock-reset").addEventListener("click", () => {
|
|
2190
|
-
api("POST", "/clock", { reset: true }).then(renderClock).catch(showError)
|
|
2191
|
-
})
|
|
2192
|
-
$("clock-set").addEventListener("submit", (event) => {
|
|
2193
|
-
event.preventDefault()
|
|
2194
|
-
const raw = $("clock-instant").value.trim()
|
|
2195
|
-
const set = /^-?\\d+$/.test(raw) ? Number(raw) : raw
|
|
2196
|
-
api("POST", "/clock", { set }).then(renderClock).catch(showError)
|
|
2197
|
-
})
|
|
2198
|
-
|
|
2199
|
-
$("fault-form").addEventListener("submit", (event) => {
|
|
2200
|
-
event.preventDefault()
|
|
2201
|
-
const preset = $("fault-preset").value
|
|
2202
|
-
const body = preset
|
|
2203
|
-
? { preset }
|
|
2204
|
-
: {
|
|
2205
|
-
status: Number($("fault-status").value),
|
|
2206
|
-
...($("fault-path").value ? { pathPrefix: $("fault-path").value } : {}),
|
|
2207
|
-
...($("fault-method").value ? { method: $("fault-method").value } : {})
|
|
2208
|
-
}
|
|
2209
|
-
if (!preset && !Number.isFinite(body.status)) {
|
|
2210
|
-
showError(new Error("Status must be a number, or choose a preset"))
|
|
2211
|
-
return
|
|
2212
|
-
}
|
|
2213
|
-
api("POST", "/faults", body).then(renderFaults).catch(showError)
|
|
2214
|
-
})
|
|
2215
|
-
$("fault-clear").addEventListener("click", () => api("DELETE", "/faults").then(renderFaults).catch(showError))
|
|
2216
|
-
$("journal-clear").addEventListener("click", () => api("DELETE", "/requests").then(renderJournal).catch(showError))
|
|
2217
|
-
|
|
2218
|
-
$("route-list").addEventListener("click", (event) => {
|
|
2219
|
-
const button = event.target.closest("[data-route]")
|
|
2220
|
-
if (!button) return
|
|
2221
|
-
state.route = button.dataset.route
|
|
2222
|
-
$("route-title").textContent = state.route
|
|
2223
|
-
const method = state.route.split(" ")[0]
|
|
2224
|
-
$("route-body").disabled = method === "GET" || method === "DELETE"
|
|
2225
|
-
})
|
|
2226
|
-
$("route-form").addEventListener("submit", async (event) => {
|
|
2227
|
-
event.preventDefault()
|
|
2228
|
-
if (!state.route) return showError(new Error("Choose a route first"))
|
|
2229
|
-
const [method, path] = [state.route.split(" ")[0], state.route.split(" ").slice(1).join(" ")]
|
|
2230
|
-
const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_, key) => prompt("Value for " + key) || "")
|
|
2231
|
-
let body
|
|
2232
|
-
if (method !== "GET" && method !== "DELETE" && $("route-body").value.trim()) {
|
|
2233
|
-
try { body = JSON.parse($("route-body").value) }
|
|
2234
|
-
catch { return showError(new Error("Body is not JSON")) }
|
|
2235
|
-
}
|
|
2236
|
-
try {
|
|
2237
|
-
const data = await api(method, concrete, body)
|
|
2238
|
-
$("route-result").textContent = JSON.stringify(data, null, 2)
|
|
2239
|
-
} catch (error) {
|
|
2240
|
-
showError(error)
|
|
2241
|
-
}
|
|
2242
|
-
})
|
|
2243
|
-
|
|
2244
|
-
function mountPanels(panels) {
|
|
2245
|
-
const nav = $("nav")
|
|
2246
|
-
const views = $("views")
|
|
2247
|
-
for (const panel of panels) {
|
|
2248
|
-
const button = document.createElement("button")
|
|
2249
|
-
button.type = "button"
|
|
2250
|
-
button.dataset.view = panel.id
|
|
2251
|
-
button.textContent = panel.title
|
|
2252
|
-
nav.append(button)
|
|
2253
|
-
const section = document.createElement("section")
|
|
2254
|
-
section.className = "view"
|
|
2255
|
-
section.id = "view-" + panel.id
|
|
2256
|
-
section.hidden = true
|
|
2257
|
-
const title = document.createElement("h2")
|
|
2258
|
-
title.textContent = panel.title
|
|
2259
|
-
const lede = document.createElement("p")
|
|
2260
|
-
lede.className = "lede"
|
|
2261
|
-
lede.textContent = panel.description || ""
|
|
2262
|
-
const body = document.createElement("div")
|
|
2263
|
-
body.className = "panel-body"
|
|
2264
|
-
body.id = "panel-" + panel.id
|
|
2265
|
-
body.innerHTML = panel.html || ""
|
|
2266
|
-
section.append(title, lede, body)
|
|
2267
|
-
views.append(section)
|
|
2268
|
-
if (panel.script) {
|
|
2269
|
-
try {
|
|
2270
|
-
const run = new Function("root", "api", panel.script)
|
|
2271
|
-
run(body, panelApi)
|
|
2272
|
-
} catch (error) {
|
|
2273
|
-
const note = document.createElement("p")
|
|
2274
|
-
note.className = "banner show"
|
|
2275
|
-
note.textContent = error instanceof Error ? error.message : String(error)
|
|
2276
|
-
body.append(note)
|
|
2277
|
-
}
|
|
2278
|
-
}
|
|
2279
|
-
}
|
|
2280
|
-
}
|
|
2281
|
-
|
|
2282
|
-
loadNamespaces()
|
|
2283
|
-
.then(() => api("GET", "/ui/manifest"))
|
|
2284
|
-
.then((manifest) => { mountPanels((manifest && manifest.panels) || []); return refresh() })
|
|
2285
|
-
.catch(showError)
|
|
2286
|
-
})()
|
|
2287
|
-
</script>
|
|
3088
|
+
${script}
|
|
2288
3089
|
</body>
|
|
2289
3090
|
</html>`;
|
|
2290
3091
|
};
|
|
@@ -2301,10 +3102,10 @@ var json2 = (body) => new Response(JSON.stringify(body), {
|
|
|
2301
3102
|
});
|
|
2302
3103
|
var adminUiRoutes = (service, ui) => {
|
|
2303
3104
|
const shell = () => renderAdminDocument(service);
|
|
2304
|
-
const
|
|
3105
|
+
const document3 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
|
|
2305
3106
|
return {
|
|
2306
|
-
"GET /ui": () => htmlResponse(
|
|
2307
|
-
"GET /ui/": () => htmlResponse(
|
|
3107
|
+
"GET /ui": () => htmlResponse(document3()),
|
|
3108
|
+
"GET /ui/": () => htmlResponse(document3()),
|
|
2308
3109
|
"GET /ui/manifest": () => json2({
|
|
2309
3110
|
service,
|
|
2310
3111
|
panels: (ui?.panels ?? []).map((panel) => ({
|
|
@@ -2330,11 +3131,11 @@ var EXTENSION_KEYS = {
|
|
|
2330
3131
|
};
|
|
2331
3132
|
|
|
2332
3133
|
// ../../openapi/metadata/dist/read.js
|
|
2333
|
-
var
|
|
3134
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2334
3135
|
var extensionOf = (holder, key) => holder[key];
|
|
2335
3136
|
var operationMetadata = (operation) => {
|
|
2336
3137
|
const raw = extensionOf(operation, EXTENSION_KEYS.operation);
|
|
2337
|
-
const ext =
|
|
3138
|
+
const ext = isRecord3(raw) ? raw : {};
|
|
2338
3139
|
const supported = ext.supported ?? true;
|
|
2339
3140
|
const parity = ext.parity ?? {};
|
|
2340
3141
|
return {
|
|
@@ -2444,9 +3245,9 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
2444
3245
|
this.name = "OperationRegistryError";
|
|
2445
3246
|
}
|
|
2446
3247
|
};
|
|
2447
|
-
var verifyOperations = (
|
|
3248
|
+
var verifyOperations = (document3, handlers) => {
|
|
2448
3249
|
const problems = [];
|
|
2449
|
-
const operations = listOperations(
|
|
3250
|
+
const operations = listOperations(document3);
|
|
2450
3251
|
const seen = /* @__PURE__ */ new Set();
|
|
2451
3252
|
for (const operation of operations) {
|
|
2452
3253
|
if (seen.has(operation.operationId))
|
|
@@ -2562,7 +3363,7 @@ var SAMPLE = 25;
|
|
|
2562
3363
|
var WALK_LIMIT = 1e4;
|
|
2563
3364
|
var SKIP_CTORS = /* @__PURE__ */ new Set(["Hono", "HonoRequest", "Router"]);
|
|
2564
3365
|
var fail2 = (status, message) => ({ ok: false, status, message });
|
|
2565
|
-
var
|
|
3366
|
+
var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2566
3367
|
var kindOf = (value) => {
|
|
2567
3368
|
if (value === null)
|
|
2568
3369
|
return "null";
|
|
@@ -2661,7 +3462,7 @@ var absorb = (accum, value) => {
|
|
|
2661
3462
|
const kind = kindOf(value);
|
|
2662
3463
|
accum.kinds.add(kind);
|
|
2663
3464
|
accum.present++;
|
|
2664
|
-
if (!
|
|
3465
|
+
if (!isRecord4(value))
|
|
2665
3466
|
return;
|
|
2666
3467
|
for (const [key, child] of Object.entries(value)) {
|
|
2667
3468
|
const nested = accum.nested.get(key) ?? blankAccum(key);
|
|
@@ -2686,7 +3487,7 @@ var inferFields = (records) => {
|
|
|
2686
3487
|
if (records.length === 0)
|
|
2687
3488
|
return [];
|
|
2688
3489
|
const sample = records.slice(0, SAMPLE);
|
|
2689
|
-
const objects = sample.filter((record) =>
|
|
3490
|
+
const objects = sample.filter((record) => isRecord4(record.value));
|
|
2690
3491
|
if (objects.length === 0) {
|
|
2691
3492
|
const kinds = new Set(sample.map((record) => kindOf(record.value)));
|
|
2692
3493
|
const list = [...kinds];
|
|
@@ -2873,7 +3674,7 @@ var patchStateRecord = (scope, name, id, value) => {
|
|
|
2873
3674
|
const parsed = jsonValue(value);
|
|
2874
3675
|
if (!parsed.ok)
|
|
2875
3676
|
return parsed;
|
|
2876
|
-
const next =
|
|
3677
|
+
const next = isRecord4(current.value.value) && isRecord4(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
|
|
2877
3678
|
return replaceStateRecord(scope, name, id, next);
|
|
2878
3679
|
};
|
|
2879
3680
|
var deleteStateRecord = (scope, name, id) => {
|
|
@@ -2941,7 +3742,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
2941
3742
|
const scope = open(namespace);
|
|
2942
3743
|
if (!scope.ok)
|
|
2943
3744
|
return fromResult(scope, 201);
|
|
2944
|
-
if (!
|
|
3745
|
+
if (!isRecord4(body) || !("value" in body)) {
|
|
2945
3746
|
return adminError2(400, 'expected {"id?": "<id>", "value": <json>}');
|
|
2946
3747
|
}
|
|
2947
3748
|
const id = body.id;
|
|
@@ -2959,7 +3760,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
2959
3760
|
const scope = open(namespace);
|
|
2960
3761
|
if (!scope.ok)
|
|
2961
3762
|
return fromResult(scope, 200);
|
|
2962
|
-
if (!
|
|
3763
|
+
if (!isRecord4(body) || !("value" in body))
|
|
2963
3764
|
return adminError2(400, 'expected {"value": <json>}');
|
|
2964
3765
|
const replaced = replaceStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
|
|
2965
3766
|
if (replaced.ok)
|
|
@@ -2970,7 +3771,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
2970
3771
|
const scope = open(namespace);
|
|
2971
3772
|
if (!scope.ok)
|
|
2972
3773
|
return fromResult(scope, 200);
|
|
2973
|
-
if (!
|
|
3774
|
+
if (!isRecord4(body) || !("value" in body))
|
|
2974
3775
|
return adminError2(400, 'expected {"value": <json>}');
|
|
2975
3776
|
const patched = patchStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
|
|
2976
3777
|
if (patched.ok)
|
|
@@ -2990,14 +3791,14 @@ var stateAdminRoutes = (routes) => {
|
|
|
2990
3791
|
};
|
|
2991
3792
|
|
|
2992
3793
|
// ../core/dist/version.js
|
|
2993
|
-
var PACKAGE_VERSION = true ? "1.
|
|
3794
|
+
var PACKAGE_VERSION = true ? "1.5.0" : UNRELEASED_VERSION;
|
|
2994
3795
|
|
|
2995
3796
|
// ../core/dist/webhooks.js
|
|
2996
3797
|
var json4 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
2997
3798
|
var adminError3 = (status, message) => json4(status, { error: { type: "mockingbird_admin", message } });
|
|
2998
|
-
var
|
|
3799
|
+
var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2999
3800
|
var parseEndpoint = (value) => {
|
|
3000
|
-
if (!
|
|
3801
|
+
if (!isRecord5(value) || typeof value.url !== "string")
|
|
3001
3802
|
return "each endpoint needs a url";
|
|
3002
3803
|
try {
|
|
3003
3804
|
new URL(value.url);
|
|
@@ -3014,12 +3815,12 @@ var parseEndpoint = (value) => {
|
|
|
3014
3815
|
const events = value.events ?? value.enabledEvents;
|
|
3015
3816
|
if (Array.isArray(events))
|
|
3016
3817
|
endpoint.events = events.map(String);
|
|
3017
|
-
if (
|
|
3818
|
+
if (isRecord5(value.tags)) {
|
|
3018
3819
|
endpoint.tags = Object.fromEntries(Object.entries(value.tags).map(([k, v]) => [k, String(v)]));
|
|
3019
3820
|
}
|
|
3020
3821
|
if (typeof value.account === "string")
|
|
3021
3822
|
endpoint.tags = { ...endpoint.tags, account: value.account };
|
|
3022
|
-
if (
|
|
3823
|
+
if (isRecord5(value.headers)) {
|
|
3023
3824
|
endpoint.headers = Object.fromEntries(Object.entries(value.headers).map(([k, v]) => [k, String(v)]));
|
|
3024
3825
|
}
|
|
3025
3826
|
return endpoint;
|
|
@@ -3046,7 +3847,7 @@ var webhookAdminRoutes = (hub) => ({
|
|
|
3046
3847
|
return json4(200, { status: "ok" });
|
|
3047
3848
|
},
|
|
3048
3849
|
"POST /webhooks/faults": ({ body, namespace }) => {
|
|
3049
|
-
if (!
|
|
3850
|
+
if (!isRecord5(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
|
|
3050
3851
|
return adminError3(400, "mode must be duplicate, reorder or drop");
|
|
3051
3852
|
}
|
|
3052
3853
|
const fault = { mode: body.mode };
|
|
@@ -3062,7 +3863,7 @@ var webhookAdminRoutes = (hub) => ({
|
|
|
3062
3863
|
}))
|
|
3063
3864
|
}),
|
|
3064
3865
|
"PUT /webhook-endpoints": ({ body, namespace }) => {
|
|
3065
|
-
const list = Array.isArray(body) ? body :
|
|
3866
|
+
const list = Array.isArray(body) ? body : isRecord5(body) ? body.endpoints : void 0;
|
|
3066
3867
|
if (!Array.isArray(list))
|
|
3067
3868
|
return adminError3(400, "expected [{url, secret?, events?}]");
|
|
3068
3869
|
const parsed = [];
|
|
@@ -3130,8 +3931,8 @@ var DroppedConnectionError = class extends TypeError {
|
|
|
3130
3931
|
this.name = "TypeError";
|
|
3131
3932
|
}
|
|
3132
3933
|
};
|
|
3133
|
-
var operationMatcher = (
|
|
3134
|
-
const matchers = listOperations(
|
|
3934
|
+
var operationMatcher = (document3) => {
|
|
3935
|
+
const matchers = listOperations(document3).map((operation) => ({
|
|
3135
3936
|
operationId: operation.operationId,
|
|
3136
3937
|
method: operation.method.toUpperCase(),
|
|
3137
3938
|
pattern: new RegExp(`^${operation.path.split("/").map((segment) => segment.startsWith("{") ? "[^/]+" : segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("/")}/?$`),
|
|
@@ -3226,10 +4027,10 @@ var createRuntime = (options) => {
|
|
|
3226
4027
|
const ensureBranch = (namespace, branch2, at) => {
|
|
3227
4028
|
if (!BRANCH_PATTERN2.test(branch2))
|
|
3228
4029
|
throw new RangeError(`branch must match ${BRANCH_PATTERN2}`);
|
|
3229
|
-
const
|
|
4030
|
+
const history2 = timeline(namespace);
|
|
3230
4031
|
if (branch2 === "main") {
|
|
3231
4032
|
if (at !== void 0) {
|
|
3232
|
-
const point =
|
|
4033
|
+
const point = history2.checkout("main", at);
|
|
3233
4034
|
restoreNamespace(sqlite, storageNamespace(namespace), point.value.snapshot);
|
|
3234
4035
|
captured.set(namespace, point.value.snapshot);
|
|
3235
4036
|
rng.setState(point.value.rngState);
|
|
@@ -3242,10 +4043,10 @@ var createRuntime = (options) => {
|
|
|
3242
4043
|
return namespace;
|
|
3243
4044
|
}
|
|
3244
4045
|
const storage = physicalBranch(namespace, branch2);
|
|
3245
|
-
if (!
|
|
4046
|
+
if (!history2.hasBranch(branch2)) {
|
|
3246
4047
|
if (at === void 0)
|
|
3247
|
-
|
|
3248
|
-
const point =
|
|
4048
|
+
history2.commit(capture(namespace));
|
|
4049
|
+
const point = history2.fork(branch2, at === void 0 ? {} : { from: at });
|
|
3249
4050
|
const branchRng = createRng(options.seed ?? 0);
|
|
3250
4051
|
if (point)
|
|
3251
4052
|
branchRng.setState(point.value.rngState);
|
|
@@ -3254,8 +4055,8 @@ var createRuntime = (options) => {
|
|
|
3254
4055
|
restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
|
|
3255
4056
|
if (point)
|
|
3256
4057
|
captured.set(storage, point.value.snapshot);
|
|
3257
|
-
} else if (at !== void 0 &&
|
|
3258
|
-
const point =
|
|
4058
|
+
} else if (at !== void 0 && history2.head(branch2)?.id !== at) {
|
|
4059
|
+
const point = history2.checkout(branch2, at);
|
|
3259
4060
|
if (!instances.has(storage)) {
|
|
3260
4061
|
const branchRng = createRng(options.seed ?? 0);
|
|
3261
4062
|
branchRng.setState(point.value.rngState);
|
|
@@ -3266,7 +4067,7 @@ var createRuntime = (options) => {
|
|
|
3266
4067
|
captured.set(storage, point.value.snapshot);
|
|
3267
4068
|
} else {
|
|
3268
4069
|
if (!instances.has(storage)) {
|
|
3269
|
-
const point =
|
|
4070
|
+
const point = history2.head(branch2);
|
|
3270
4071
|
const branchRng = createRng(options.seed ?? 0);
|
|
3271
4072
|
if (point)
|
|
3272
4073
|
branchRng.setState(point.value.rngState);
|
|
@@ -3290,8 +4091,8 @@ var createRuntime = (options) => {
|
|
|
3290
4091
|
const checkout = (checkpointId, checkoutOptions = {}) => {
|
|
3291
4092
|
const namespace = checkoutOptions.namespace ?? DEFAULT_NAMESPACE;
|
|
3292
4093
|
const branchName = checkoutOptions.branch ?? "main";
|
|
3293
|
-
const
|
|
3294
|
-
const point =
|
|
4094
|
+
const history2 = timeline(namespace);
|
|
4095
|
+
const point = history2.checkout(branchName, checkpointId);
|
|
3295
4096
|
const storage = ensureBranch(namespace, branchName);
|
|
3296
4097
|
restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
|
|
3297
4098
|
captured.set(storage, point.value.snapshot);
|
|
@@ -3341,9 +4142,9 @@ var createRuntime = (options) => {
|
|
|
3341
4142
|
instance(name);
|
|
3342
4143
|
restoreNamespace(sqlite, storageNamespace(name), from);
|
|
3343
4144
|
captured.set(name, from);
|
|
3344
|
-
const
|
|
3345
|
-
if (
|
|
3346
|
-
|
|
4145
|
+
const history2 = timelines.get(name);
|
|
4146
|
+
if (history2)
|
|
4147
|
+
history2.commit(capture(name), { branch: "main" });
|
|
3347
4148
|
else
|
|
3348
4149
|
timeline(name);
|
|
3349
4150
|
};
|
|
@@ -3572,10 +4373,10 @@ var createRuntime = (options) => {
|
|
|
3572
4373
|
},
|
|
3573
4374
|
release: (name, checkpointId) => timeline(name).release(checkpointId),
|
|
3574
4375
|
inspect: (name) => {
|
|
3575
|
-
const
|
|
4376
|
+
const history2 = timeline(name);
|
|
3576
4377
|
return {
|
|
3577
|
-
branches:
|
|
3578
|
-
checkpoints:
|
|
4378
|
+
branches: history2.branches(),
|
|
4379
|
+
checkpoints: history2.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
|
|
3579
4380
|
id,
|
|
3580
4381
|
branch: branchName,
|
|
3581
4382
|
parent,
|
|
@@ -3590,11 +4391,11 @@ var createRuntime = (options) => {
|
|
|
3590
4391
|
} : {},
|
|
3591
4392
|
routes: {
|
|
3592
4393
|
...credentialRoutes(credentials),
|
|
3593
|
-
...options.presets ? presetRoutes(options.presets, runtime) : {},
|
|
3594
4394
|
...options.webhooks ? webhookAdminRoutes(options.webhooks) : {},
|
|
3595
4395
|
...options.admin?.(runtime) ?? {},
|
|
3596
|
-
//
|
|
3597
|
-
//
|
|
4396
|
+
// Shell routes win a colliding key. Fault presets are one of them: the admin page
|
|
4397
|
+
// maps `presets`, and a service route must not replace that list with a record.
|
|
4398
|
+
...options.presets ? presetRoutes(options.presets, runtime) : {},
|
|
3598
4399
|
...stateAdminRoutes({
|
|
3599
4400
|
open: stateScope,
|
|
3600
4401
|
afterWrite: (namespace) => {
|
|
@@ -3669,9 +4470,12 @@ var credentialRoutes = (registry) => ({
|
|
|
3669
4470
|
}
|
|
3670
4471
|
});
|
|
3671
4472
|
var presetRoutes = (presets, runtime) => ({
|
|
3672
|
-
"GET /faults/presets": () =>
|
|
3673
|
-
|
|
3674
|
-
|
|
4473
|
+
"GET /faults/presets": () => {
|
|
4474
|
+
const body = {
|
|
4475
|
+
presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
|
|
4476
|
+
};
|
|
4477
|
+
return adminJson(200, body);
|
|
4478
|
+
},
|
|
3675
4479
|
"POST /faults/presets/:name": ({ params, body, namespace }) => {
|
|
3676
4480
|
const name = params.name;
|
|
3677
4481
|
if (!presets[name])
|
|
@@ -3682,7 +4486,7 @@ var presetRoutes = (presets, runtime) => ({
|
|
|
3682
4486
|
});
|
|
3683
4487
|
|
|
3684
4488
|
// src/generated/openapi.ts
|
|
3685
|
-
var
|
|
4489
|
+
var document2 = JSON.parse(`{"openapi":"3.1.0","info":{"title":"EasyPost Trackers API (Mockingbird subset)","description":"Stateful mock subset of the EasyPost v2 API: create (or re-use) a tracker for a tracking\\ncode, retrieve it, and list trackers. Trimmed from EasyPost's published reference to what\\nour genomics tracking lookup calls, plus the two reads that make parity walks meaningful.\\n","version":"2","x-mockingbird-upstream":{"note":"Shapes from https://docs.easypost.com/docs/trackers; request/response fields our consumer reads come from packages/lib/src/shipment-tracking-status/easypost-client.ts."}},"servers":[{"url":"https://api.easypost.com"}],"security":[{"basicAuth":[]}],"paths":{"/v2/trackers":{"post":{"operationId":"CreateTracker","description":"Creates a tracker, or answers the existing one when the same tracking code (and carrier) was already tracked. EasyPost's test codes (EZ1000000001 \u2026 EZ7000000007) answer their documented fixed statuses.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/CreateTrackerBody"}},"application/json":{"schema":{"$ref":"#/components/schemas/CreateTrackerBody"}}}},"responses":{"201":{"description":"The tracker (new or re-used)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tracker"}}}},"401":{"description":"Missing or unknown API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Missing tracking code or invalid carrier","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"ListTrackers","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"tracking_code","in":"query","required":false,"schema":{"type":"string","maxLength":40}},{"name":"carrier","in":"query","required":false,"schema":{"type":"string","enum":["USPS","UPS","FedEx","DHLExpress"]}},{"name":"page_size","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"Newest trackers first","content":{"application/json":{"schema":{"type":"object","required":["trackers","has_more"],"properties":{"trackers":{"type":"array","items":{"$ref":"#/components/schemas/Tracker"}},"has_more":{"type":"boolean"}}}}}},"401":{"description":"Missing or unknown API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Invalid query parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v2/trackers/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"tracker","missing":"trk_00000000000000000000000000000000"}}}],"get":{"operationId":"RetrieveTracker","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The tracker","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tracker"}}}},"401":{"description":"Missing or unknown API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Unknown tracker","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"basicAuth":{"type":"http","scheme":"basic","description":"The API key as the username, an empty password (\`Basic base64(key:)\`)."}},"schemas":{"CreateTrackerBody":{"type":"object","required":["tracker"],"properties":{"tracker":{"type":"object","required":["tracking_code"],"properties":{"tracking_code":{"type":"string","minLength":1,"maxLength":40,"examples":["EZ1000000001","EZ4000000004","1Z999AA10123456784"]},"carrier":{"type":"string","enum":["USPS","UPS","FedEx","DHL","DHLExpress"]}}}}},"Tracker":{"type":"object","required":["id","object","mode","tracking_code","status","status_detail","carrier","tracking_details","public_url","created_at","updated_at"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"tracker","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"object":{"type":"string","enum":["Tracker"]},"mode":{"type":"string","enum":["test","production"]},"tracking_code":{"type":"string"},"status":{"type":"string","enum":["unknown","pre_transit","in_transit","out_for_delivery","delivered","available_for_pickup","return_to_sender","failure","cancelled","error"]},"status_detail":{"type":"string"},"carrier":{"type":"string"},"signed_by":{"type":["string","null"]},"weight":{"type":["number","null"]},"est_delivery_date":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"timestamp"}},"shipment_id":{"type":["string","null"]},"tracking_details":{"type":"array","items":{"$ref":"#/components/schemas/TrackingDetail"}},"carrier_detail":{"type":["object","null"]},"public_url":{"type":"string","x-mockingbird-volatile":{"kind":"url"}},"fees":{"type":"array","items":{"type":"object"}},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"TrackingDetail":{"type":"object","required":["object","message","status","status_detail","datetime","source"],"properties":{"object":{"type":"string","enum":["TrackingDetail"]},"message":{"type":"string"},"status":{"type":"string"},"status_detail":{"type":"string"},"datetime":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"source":{"type":"string"},"tracking_location":{"type":"object"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"},"errors":{"type":"array","items":{"type":"object"}}}}}}}}}`);
|
|
3686
4490
|
var operationIds = ["ListTrackers", "CreateTracker", "RetrieveTracker"];
|
|
3687
4491
|
var supportedOperationIds = ["ListTrackers", "CreateTracker", "RetrieveTracker"];
|
|
3688
4492
|
|
|
@@ -3805,11 +4609,11 @@ var EASYPOST_PRESETS = {
|
|
|
3805
4609
|
};
|
|
3806
4610
|
var json5 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
3807
4611
|
var adminError4 = (status, message) => json5(status, { error: { type: "mockingbird_admin", message } });
|
|
3808
|
-
var
|
|
4612
|
+
var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3809
4613
|
var adminRoutes = (runtime) => ({
|
|
3810
4614
|
"GET /trackers": ({ namespace }) => json5(200, { trackers: runtime.instance(namespace).trackers() }),
|
|
3811
4615
|
"POST /trackers/:code/transition": ({ params, body, namespace }) => {
|
|
3812
|
-
if (!
|
|
4616
|
+
if (!isRecord6(body) || !isTrackerStatus(body.status)) {
|
|
3813
4617
|
return adminError4(
|
|
3814
4618
|
400,
|
|
3815
4619
|
'expected {"status": "<unknown|pre_transit|in_transit|out_for_delivery|delivered|available_for_pickup|return_to_sender|failure|cancelled|error>", "status_detail"?, "message"?, "carrier"?}'
|
|
@@ -3825,7 +4629,7 @@ var adminRoutes = (runtime) => ({
|
|
|
3825
4629
|
},
|
|
3826
4630
|
"GET /settings": ({ namespace }) => json5(200, runtime.instance(namespace).state.current()),
|
|
3827
4631
|
"PUT /settings": ({ body, namespace }) => {
|
|
3828
|
-
if (!
|
|
4632
|
+
if (!isRecord6(body)) return adminError4(400, "expected a JSON object");
|
|
3829
4633
|
const patch = {};
|
|
3830
4634
|
if (body.apiKeys !== void 0) {
|
|
3831
4635
|
if (!Array.isArray(body.apiKeys)) return adminError4(400, "apiKeys: string[]");
|
|
@@ -3836,7 +4640,7 @@ var adminRoutes = (runtime) => ({
|
|
|
3836
4640
|
});
|
|
3837
4641
|
var createRuntime2 = (options = {}) => createRuntime({
|
|
3838
4642
|
name: EASYPOST_NAMESPACE,
|
|
3839
|
-
document,
|
|
4643
|
+
document: document2,
|
|
3840
4644
|
...options.sqlite ? { sqlite: options.sqlite } : {},
|
|
3841
4645
|
...options.clock ? { clock: options.clock } : {},
|
|
3842
4646
|
...options.seed !== void 0 ? { seed: options.seed } : {},
|
|
@@ -3920,7 +4724,7 @@ var EasyPostAPI = class {
|
|
|
3920
4724
|
}
|
|
3921
4725
|
});
|
|
3922
4726
|
this.service = createService({
|
|
3923
|
-
document,
|
|
4727
|
+
document: document2,
|
|
3924
4728
|
handlers,
|
|
3925
4729
|
sqlite,
|
|
3926
4730
|
namespace,
|
|
@@ -4078,7 +4882,7 @@ var EasyPostAPI = class {
|
|
|
4078
4882
|
};
|
|
4079
4883
|
|
|
4080
4884
|
export {
|
|
4081
|
-
document,
|
|
4885
|
+
document2 as document,
|
|
4082
4886
|
operationIds,
|
|
4083
4887
|
supportedOperationIds,
|
|
4084
4888
|
TRACKER_STATUSES,
|
|
@@ -4092,4 +4896,4 @@ export {
|
|
|
4092
4896
|
isTrackerStatus,
|
|
4093
4897
|
EasyPostAPI
|
|
4094
4898
|
};
|
|
4095
|
-
//# sourceMappingURL=chunk-
|
|
4899
|
+
//# sourceMappingURL=chunk-DLVQBVAS.js.map
|