@crvouga/mockingbird-service-google-maps 1.5.0 → 1.6.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-33MOWM5L.js → chunk-HLXAIBBZ.js} +1410 -606
- package/dist/chunk-HLXAIBBZ.js.map +7 -0
- package/dist/{chunk-IGTJ5L72.js → chunk-XCSYTELK.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-33MOWM5L.js.map +0 -7
- /package/dist/{chunk-IGTJ5L72.js.map → chunk-XCSYTELK.js.map} +0 -0
|
@@ -533,10 +533,10 @@ var OpenAPIReferenceError = class extends Error {
|
|
|
533
533
|
};
|
|
534
534
|
var unescapePointer = (segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
535
535
|
var isReference = (value) => typeof value === "object" && value !== null && typeof value.$ref === "string";
|
|
536
|
-
var resolveRef = (
|
|
536
|
+
var resolveRef = (document3, ref) => {
|
|
537
537
|
if (!ref.startsWith("#/"))
|
|
538
538
|
throw new OpenAPIReferenceError(ref);
|
|
539
|
-
let cursor =
|
|
539
|
+
let cursor = document3;
|
|
540
540
|
for (const raw of ref.slice(2).split("/")) {
|
|
541
541
|
const segment = unescapePointer(raw);
|
|
542
542
|
if (typeof cursor !== "object" || cursor === null || !(segment in cursor)) {
|
|
@@ -548,14 +548,14 @@ var resolveRef = (document2, ref) => {
|
|
|
548
548
|
throw new OpenAPIReferenceError(ref);
|
|
549
549
|
return cursor;
|
|
550
550
|
};
|
|
551
|
-
var deref = (
|
|
551
|
+
var deref = (document3, value) => {
|
|
552
552
|
let current = value;
|
|
553
553
|
const seen = /* @__PURE__ */ new Set();
|
|
554
554
|
while (isReference(current)) {
|
|
555
555
|
if (seen.has(current.$ref))
|
|
556
556
|
throw new OpenAPIReferenceError(`${current.$ref} (cycle)`);
|
|
557
557
|
seen.add(current.$ref);
|
|
558
|
-
current = resolveRef(
|
|
558
|
+
current = resolveRef(document3, current.$ref);
|
|
559
559
|
}
|
|
560
560
|
return current;
|
|
561
561
|
};
|
|
@@ -573,36 +573,36 @@ var HTTP_METHODS = [
|
|
|
573
573
|
];
|
|
574
574
|
|
|
575
575
|
// ../../openapi/core/dist/document.js
|
|
576
|
-
var mergeParameters = (
|
|
576
|
+
var mergeParameters = (document3, item, own) => {
|
|
577
577
|
const merged = /* @__PURE__ */ new Map();
|
|
578
578
|
for (const raw of item.parameters ?? []) {
|
|
579
|
-
const parameter = deref(
|
|
579
|
+
const parameter = deref(document3, raw);
|
|
580
580
|
merged.set(`${parameter.in}:${parameter.name}`, parameter);
|
|
581
581
|
}
|
|
582
582
|
for (const raw of own ?? []) {
|
|
583
|
-
const parameter = deref(
|
|
583
|
+
const parameter = deref(document3, raw);
|
|
584
584
|
merged.set(`${parameter.in}:${parameter.name}`, parameter);
|
|
585
585
|
}
|
|
586
586
|
return [...merged.values()];
|
|
587
587
|
};
|
|
588
|
-
var listOperations = (
|
|
588
|
+
var listOperations = (document3) => {
|
|
589
589
|
const operations = [];
|
|
590
|
-
for (const [path, item] of Object.entries(
|
|
590
|
+
for (const [path, item] of Object.entries(document3.paths)) {
|
|
591
591
|
for (const method of HTTP_METHODS) {
|
|
592
592
|
const operation = item[method];
|
|
593
593
|
if (operation?.operationId === void 0)
|
|
594
594
|
continue;
|
|
595
595
|
const responses = {};
|
|
596
596
|
for (const [status, response] of Object.entries(operation.responses)) {
|
|
597
|
-
responses[status] = deref(
|
|
597
|
+
responses[status] = deref(document3, response);
|
|
598
598
|
}
|
|
599
599
|
operations.push({
|
|
600
600
|
operationId: operation.operationId,
|
|
601
601
|
method,
|
|
602
602
|
path,
|
|
603
603
|
operation,
|
|
604
|
-
parameters: mergeParameters(
|
|
605
|
-
requestBody: operation.requestBody === void 0 ? void 0 : deref(
|
|
604
|
+
parameters: mergeParameters(document3, item, operation.parameters),
|
|
605
|
+
requestBody: operation.requestBody === void 0 ? void 0 : deref(document3, operation.requestBody),
|
|
606
606
|
responses
|
|
607
607
|
});
|
|
608
608
|
}
|
|
@@ -1119,6 +1119,1232 @@ var clearNamespace = (sqlite, namespace) => {
|
|
|
1119
1119
|
});
|
|
1120
1120
|
};
|
|
1121
1121
|
|
|
1122
|
+
// ../../ui/dist/reset.js
|
|
1123
|
+
var CSS_RESET = `*,
|
|
1124
|
+
*::before,
|
|
1125
|
+
*::after {
|
|
1126
|
+
box-sizing: border-box;
|
|
1127
|
+
margin: 0;
|
|
1128
|
+
padding: 0;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
* {
|
|
1132
|
+
scrollbar-width: thin;
|
|
1133
|
+
scrollbar-color: color-mix(in srgb, CanvasText 35%, Canvas) Canvas;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
*::-webkit-scrollbar {
|
|
1137
|
+
width: 12px;
|
|
1138
|
+
height: 12px;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
*::-webkit-scrollbar-track,
|
|
1142
|
+
*::-webkit-scrollbar-corner {
|
|
1143
|
+
background: Canvas;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
*::-webkit-scrollbar-thumb {
|
|
1147
|
+
background: color-mix(in srgb, CanvasText 35%, Canvas);
|
|
1148
|
+
border: 3px solid Canvas;
|
|
1149
|
+
border-radius: 999px;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
html {
|
|
1153
|
+
hanging-punctuation: first allow-end last;
|
|
1154
|
+
-webkit-text-size-adjust: none;
|
|
1155
|
+
text-size-adjust: none;
|
|
1156
|
+
tab-size: 4;
|
|
1157
|
+
font-family:
|
|
1158
|
+
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI",
|
|
1159
|
+
"Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
|
1160
|
+
font-size: 16px;
|
|
1161
|
+
line-height: 1.5;
|
|
1162
|
+
-webkit-font-smoothing: antialiased;
|
|
1163
|
+
-moz-osx-font-smoothing: grayscale;
|
|
1164
|
+
text-rendering: optimizeLegibility;
|
|
1165
|
+
-webkit-tap-highlight-color: transparent;
|
|
1166
|
+
scroll-behavior: smooth;
|
|
1167
|
+
background: Canvas;
|
|
1168
|
+
color: CanvasText;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
body {
|
|
1172
|
+
min-height: 100svh;
|
|
1173
|
+
line-height: 1.5;
|
|
1174
|
+
background: Canvas;
|
|
1175
|
+
color: CanvasText;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
h1 {
|
|
1179
|
+
font-size: 2em;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
h2 {
|
|
1183
|
+
font-size: 1.5em;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
h3 {
|
|
1187
|
+
font-size: 1.17em;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
h4 {
|
|
1191
|
+
font-size: 1em;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
h5 {
|
|
1195
|
+
font-size: 0.83em;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
h6 {
|
|
1199
|
+
font-size: 0.67em;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
h1,
|
|
1203
|
+
h2,
|
|
1204
|
+
h3,
|
|
1205
|
+
h4,
|
|
1206
|
+
h5,
|
|
1207
|
+
h6 {
|
|
1208
|
+
font-weight: 700;
|
|
1209
|
+
line-height: 1.2;
|
|
1210
|
+
text-wrap: balance;
|
|
1211
|
+
overflow-wrap: break-word;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
p,
|
|
1215
|
+
li,
|
|
1216
|
+
figcaption,
|
|
1217
|
+
dd,
|
|
1218
|
+
dt,
|
|
1219
|
+
blockquote {
|
|
1220
|
+
overflow-wrap: break-word;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
p,
|
|
1224
|
+
li,
|
|
1225
|
+
figcaption,
|
|
1226
|
+
blockquote {
|
|
1227
|
+
text-wrap: pretty;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
ul,
|
|
1231
|
+
ol,
|
|
1232
|
+
menu {
|
|
1233
|
+
list-style: none;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
a {
|
|
1237
|
+
color: inherit;
|
|
1238
|
+
text-decoration: underline;
|
|
1239
|
+
text-decoration-thickness: 1px;
|
|
1240
|
+
text-underline-offset: 0.15em;
|
|
1241
|
+
text-decoration-skip-ink: auto;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
b,
|
|
1245
|
+
strong {
|
|
1246
|
+
font-weight: 700;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
small {
|
|
1250
|
+
font-size: 0.875em;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
code,
|
|
1254
|
+
kbd,
|
|
1255
|
+
samp,
|
|
1256
|
+
pre {
|
|
1257
|
+
font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
|
|
1258
|
+
font-size: 1em;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
sub,
|
|
1262
|
+
sup {
|
|
1263
|
+
font-size: 75%;
|
|
1264
|
+
line-height: 0;
|
|
1265
|
+
position: relative;
|
|
1266
|
+
vertical-align: baseline;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
sub {
|
|
1270
|
+
bottom: -0.25em;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
sup {
|
|
1274
|
+
top: -0.5em;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
abbr[title] {
|
|
1278
|
+
text-decoration: underline dotted;
|
|
1279
|
+
text-underline-offset: 0.15em;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
mark {
|
|
1283
|
+
background: Mark;
|
|
1284
|
+
color: MarkText;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
hr {
|
|
1288
|
+
height: 0;
|
|
1289
|
+
border: 0;
|
|
1290
|
+
border-top: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
|
|
1291
|
+
color: inherit;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
img,
|
|
1295
|
+
picture,
|
|
1296
|
+
video,
|
|
1297
|
+
canvas {
|
|
1298
|
+
display: block;
|
|
1299
|
+
max-width: 100%;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
img,
|
|
1303
|
+
video {
|
|
1304
|
+
height: auto;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
svg {
|
|
1308
|
+
overflow: hidden;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
iframe {
|
|
1312
|
+
border: 0;
|
|
1313
|
+
max-width: 100%;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
table {
|
|
1317
|
+
border-collapse: collapse;
|
|
1318
|
+
border-spacing: 0;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
fieldset {
|
|
1322
|
+
border: 0;
|
|
1323
|
+
min-inline-size: 0;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
legend {
|
|
1327
|
+
padding: 0;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
button,
|
|
1331
|
+
input,
|
|
1332
|
+
select,
|
|
1333
|
+
textarea,
|
|
1334
|
+
optgroup {
|
|
1335
|
+
font: inherit;
|
|
1336
|
+
letter-spacing: inherit;
|
|
1337
|
+
color: inherit;
|
|
1338
|
+
margin: 0;
|
|
1339
|
+
vertical-align: middle;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
button,
|
|
1343
|
+
input[type="button"],
|
|
1344
|
+
input[type="submit"],
|
|
1345
|
+
input[type="reset"] {
|
|
1346
|
+
appearance: none;
|
|
1347
|
+
background: none;
|
|
1348
|
+
border: 0;
|
|
1349
|
+
padding: 0;
|
|
1350
|
+
cursor: pointer;
|
|
1351
|
+
text-align: center;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
input,
|
|
1355
|
+
textarea,
|
|
1356
|
+
select {
|
|
1357
|
+
background-color: Canvas;
|
|
1358
|
+
border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
|
|
1359
|
+
border-radius: 0;
|
|
1360
|
+
padding: 0.5em 0.65em;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
input[type="checkbox"],
|
|
1364
|
+
input[type="radio"],
|
|
1365
|
+
input[type="range"],
|
|
1366
|
+
input[type="file"],
|
|
1367
|
+
input[type="color"],
|
|
1368
|
+
input[type="button"],
|
|
1369
|
+
input[type="submit"],
|
|
1370
|
+
input[type="reset"],
|
|
1371
|
+
input[type="image"],
|
|
1372
|
+
input[type="hidden"] {
|
|
1373
|
+
background: none;
|
|
1374
|
+
border: 0;
|
|
1375
|
+
padding: 0;
|
|
1376
|
+
border-radius: 0;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
input[type="checkbox"],
|
|
1380
|
+
input[type="radio"] {
|
|
1381
|
+
accent-color: CanvasText;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
input[type="search"] {
|
|
1385
|
+
appearance: none;
|
|
1386
|
+
background-color: Canvas;
|
|
1387
|
+
border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
input[type="search"]::-webkit-search-decoration,
|
|
1391
|
+
input[type="search"]::-webkit-search-cancel-button,
|
|
1392
|
+
input[type="search"]::-webkit-search-results-button,
|
|
1393
|
+
input[type="search"]::-webkit-search-results-decoration {
|
|
1394
|
+
-webkit-appearance: none;
|
|
1395
|
+
display: none;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
input[type="number"] {
|
|
1399
|
+
appearance: textfield;
|
|
1400
|
+
background-color: Canvas;
|
|
1401
|
+
border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
input[type="number"]::-webkit-inner-spin-button,
|
|
1405
|
+
input[type="number"]::-webkit-outer-spin-button {
|
|
1406
|
+
-webkit-appearance: none;
|
|
1407
|
+
margin: 0;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
textarea {
|
|
1411
|
+
resize: vertical;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
::placeholder {
|
|
1415
|
+
opacity: 1;
|
|
1416
|
+
color: color-mix(in srgb, CanvasText 45%, transparent);
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
:focus-visible {
|
|
1420
|
+
outline: 2px solid CanvasText;
|
|
1421
|
+
outline-offset: 2px;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
:focus:not(:focus-visible) {
|
|
1425
|
+
outline: none;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
:disabled,
|
|
1429
|
+
[disabled] {
|
|
1430
|
+
cursor: default;
|
|
1431
|
+
opacity: 0.6;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
label,
|
|
1435
|
+
summary,
|
|
1436
|
+
select,
|
|
1437
|
+
[role="button"] {
|
|
1438
|
+
cursor: pointer;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
summary {
|
|
1442
|
+
display: list-item;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
details {
|
|
1446
|
+
display: block;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
dialog {
|
|
1450
|
+
margin: auto;
|
|
1451
|
+
padding: 1rem;
|
|
1452
|
+
border: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
|
|
1453
|
+
background: Canvas;
|
|
1454
|
+
color: CanvasText;
|
|
1455
|
+
max-width: calc(100vw - 2rem);
|
|
1456
|
+
max-height: calc(100svh - 2rem);
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
dialog:not([open]) {
|
|
1460
|
+
display: none;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
::backdrop {
|
|
1464
|
+
background: color-mix(in srgb, CanvasText 40%, transparent);
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
[hidden] {
|
|
1468
|
+
display: none !important;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
::selection {
|
|
1472
|
+
background: Highlight;
|
|
1473
|
+
color: HighlightText;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1477
|
+
*,
|
|
1478
|
+
*::before,
|
|
1479
|
+
*::after {
|
|
1480
|
+
animation-duration: 0.01ms !important;
|
|
1481
|
+
animation-iteration-count: 1 !important;
|
|
1482
|
+
transition-duration: 0.01ms !important;
|
|
1483
|
+
scroll-behavior: auto !important;
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
@media (pointer: coarse) {
|
|
1488
|
+
input,
|
|
1489
|
+
select,
|
|
1490
|
+
textarea {
|
|
1491
|
+
font-size: max(16px, 1em);
|
|
1492
|
+
}
|
|
1493
|
+
}`;
|
|
1494
|
+
|
|
1495
|
+
// ../core/dist/admin-client.js
|
|
1496
|
+
function isRecord2(value) {
|
|
1497
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1498
|
+
}
|
|
1499
|
+
function mountAdminBrand(doc, host, service, catalog) {
|
|
1500
|
+
const clip = (value, max) => {
|
|
1501
|
+
if (typeof value !== "string")
|
|
1502
|
+
return null;
|
|
1503
|
+
const trimmed = value.trim();
|
|
1504
|
+
if (trimmed === "")
|
|
1505
|
+
return null;
|
|
1506
|
+
return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
|
|
1507
|
+
};
|
|
1508
|
+
const httpUrl = (value) => {
|
|
1509
|
+
if (typeof value !== "string" || value.length > 2e3)
|
|
1510
|
+
return null;
|
|
1511
|
+
try {
|
|
1512
|
+
const url = new URL(value);
|
|
1513
|
+
if (url.protocol !== "https:" && url.protocol !== "http:")
|
|
1514
|
+
return null;
|
|
1515
|
+
return url.href;
|
|
1516
|
+
} catch {
|
|
1517
|
+
return null;
|
|
1518
|
+
}
|
|
1519
|
+
};
|
|
1520
|
+
const hexColor = (value) => {
|
|
1521
|
+
if (typeof value !== "string")
|
|
1522
|
+
return null;
|
|
1523
|
+
const raw = value.trim();
|
|
1524
|
+
return /^#[0-9a-fA-F]{3,8}$/.test(raw) ? raw : null;
|
|
1525
|
+
};
|
|
1526
|
+
const hostname = (value) => {
|
|
1527
|
+
try {
|
|
1528
|
+
return new URL(value).hostname.replace(/^www\./, "");
|
|
1529
|
+
} catch {
|
|
1530
|
+
return value;
|
|
1531
|
+
}
|
|
1532
|
+
};
|
|
1533
|
+
host.replaceChildren();
|
|
1534
|
+
host.hidden = true;
|
|
1535
|
+
if (!isRecord2(catalog) || !Object.hasOwn(catalog, service))
|
|
1536
|
+
return;
|
|
1537
|
+
const row2 = catalog[service];
|
|
1538
|
+
if (!isRecord2(row2))
|
|
1539
|
+
return;
|
|
1540
|
+
const vendor = clip(row2.vendor, 80);
|
|
1541
|
+
const website = httpUrl(row2.website);
|
|
1542
|
+
const guide = httpUrl(row2.guide);
|
|
1543
|
+
const docs = httpUrl(row2.docs);
|
|
1544
|
+
const logo = httpUrl(row2.logo);
|
|
1545
|
+
const color = hexColor(row2.color);
|
|
1546
|
+
const description = clip(row2.description, 280);
|
|
1547
|
+
if (vendor === null || website === null && guide === null && docs === null)
|
|
1548
|
+
return;
|
|
1549
|
+
const add = (node) => {
|
|
1550
|
+
host.append(node);
|
|
1551
|
+
return node;
|
|
1552
|
+
};
|
|
1553
|
+
if (logo !== null) {
|
|
1554
|
+
const img = doc.createElement("img");
|
|
1555
|
+
img.alt = "";
|
|
1556
|
+
img.src = logo;
|
|
1557
|
+
img.width = 16;
|
|
1558
|
+
img.height = 16;
|
|
1559
|
+
img.decoding = "async";
|
|
1560
|
+
img.referrerPolicy = "no-referrer";
|
|
1561
|
+
if (color !== null)
|
|
1562
|
+
img.style.borderColor = color;
|
|
1563
|
+
add(img);
|
|
1564
|
+
}
|
|
1565
|
+
const name = doc.createElement("span");
|
|
1566
|
+
name.className = "vendor-name";
|
|
1567
|
+
name.textContent = vendor;
|
|
1568
|
+
if (description !== null)
|
|
1569
|
+
name.title = description;
|
|
1570
|
+
add(name);
|
|
1571
|
+
const sep = () => {
|
|
1572
|
+
const node = doc.createElement("span");
|
|
1573
|
+
node.className = "vendor-sep";
|
|
1574
|
+
node.setAttribute("aria-hidden", "true");
|
|
1575
|
+
node.textContent = "\xB7";
|
|
1576
|
+
return node;
|
|
1577
|
+
};
|
|
1578
|
+
const link = (href, label, title) => {
|
|
1579
|
+
const node = doc.createElement("a");
|
|
1580
|
+
node.className = "vendor-link";
|
|
1581
|
+
node.href = href;
|
|
1582
|
+
node.target = "_blank";
|
|
1583
|
+
node.rel = "noopener noreferrer";
|
|
1584
|
+
node.referrerPolicy = "no-referrer";
|
|
1585
|
+
node.textContent = label;
|
|
1586
|
+
node.title = title;
|
|
1587
|
+
return node;
|
|
1588
|
+
};
|
|
1589
|
+
if (website !== null) {
|
|
1590
|
+
add(sep());
|
|
1591
|
+
add(link(website, hostname(website), `${vendor} website`));
|
|
1592
|
+
}
|
|
1593
|
+
if (guide !== null) {
|
|
1594
|
+
add(sep());
|
|
1595
|
+
add(link(guide, "Docs", `Docs for ${vendor}`));
|
|
1596
|
+
}
|
|
1597
|
+
if (docs !== null) {
|
|
1598
|
+
add(sep());
|
|
1599
|
+
add(link(docs, "API", `${vendor} API reference`));
|
|
1600
|
+
}
|
|
1601
|
+
host.removeAttribute("hidden");
|
|
1602
|
+
host.hidden = false;
|
|
1603
|
+
}
|
|
1604
|
+
function startAdminBrand(env) {
|
|
1605
|
+
const requested = env.params.get("brands");
|
|
1606
|
+
let brandsUrl = env.brandsUrl;
|
|
1607
|
+
if (requested) {
|
|
1608
|
+
try {
|
|
1609
|
+
const url = new URL(requested, env.location.href);
|
|
1610
|
+
const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]" || url.origin === env.location.origin;
|
|
1611
|
+
if ((url.protocol === "https:" || url.protocol === "http:") && local)
|
|
1612
|
+
brandsUrl = url.href;
|
|
1613
|
+
} catch {
|
|
1614
|
+
brandsUrl = env.brandsUrl;
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
const vendorHost = env.$("vendor");
|
|
1618
|
+
const brandRoot = env.document.querySelector("[data-mockingbird-admin]");
|
|
1619
|
+
const brandService = brandRoot?.getAttribute("data-service") || "";
|
|
1620
|
+
if (vendorHost === null)
|
|
1621
|
+
return;
|
|
1622
|
+
env.fetch(brandsUrl, {
|
|
1623
|
+
headers: { accept: "application/json" },
|
|
1624
|
+
signal: AbortSignal.timeout(5e3)
|
|
1625
|
+
}).then(async (response) => {
|
|
1626
|
+
if (!response.ok)
|
|
1627
|
+
return null;
|
|
1628
|
+
const catalog = await response.json();
|
|
1629
|
+
return catalog;
|
|
1630
|
+
}).then((catalog) => {
|
|
1631
|
+
mountAdminBrand(env.document, vendorHost, brandService, catalog);
|
|
1632
|
+
}).catch(() => {
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
1635
|
+
function faultPresetList(value) {
|
|
1636
|
+
if (!isRecord2(value))
|
|
1637
|
+
throw new Error("Fault presets response was not an object");
|
|
1638
|
+
if (!Object.hasOwn(value, "presets") || value.presets === void 0 || value.presets === null) {
|
|
1639
|
+
return { presets: [] };
|
|
1640
|
+
}
|
|
1641
|
+
if (!Array.isArray(value.presets))
|
|
1642
|
+
throw new Error("Fault presets response was not a list");
|
|
1643
|
+
return {
|
|
1644
|
+
presets: value.presets.flatMap((item) => {
|
|
1645
|
+
if (!isRecord2(item) || typeof item.name !== "string")
|
|
1646
|
+
return [];
|
|
1647
|
+
return [{ name: item.name }];
|
|
1648
|
+
})
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
function bootAdmin(config) {
|
|
1652
|
+
const fieldKinds = ["string", "number", "boolean", "null", "object", "array", "unknown"];
|
|
1653
|
+
const sources = ["declared", "inferred", "mixed"];
|
|
1654
|
+
const isFieldKind = (value) => typeof value === "string" && fieldKinds.some((kind) => kind === value);
|
|
1655
|
+
const isSource = (value) => typeof value === "string" && sources.some((source) => source === value);
|
|
1656
|
+
const readJson2 = (text2) => {
|
|
1657
|
+
const value = JSON.parse(text2);
|
|
1658
|
+
return value;
|
|
1659
|
+
};
|
|
1660
|
+
const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => {
|
|
1661
|
+
switch (char) {
|
|
1662
|
+
case "&":
|
|
1663
|
+
return "&";
|
|
1664
|
+
case "<":
|
|
1665
|
+
return "<";
|
|
1666
|
+
case ">":
|
|
1667
|
+
return ">";
|
|
1668
|
+
case '"':
|
|
1669
|
+
return """;
|
|
1670
|
+
default:
|
|
1671
|
+
return "'";
|
|
1672
|
+
}
|
|
1673
|
+
});
|
|
1674
|
+
const el = (id, kind) => {
|
|
1675
|
+
const node = document.getElementById(id);
|
|
1676
|
+
if (!(node instanceof kind))
|
|
1677
|
+
throw new Error(`admin shell is missing #${id}`);
|
|
1678
|
+
return node;
|
|
1679
|
+
};
|
|
1680
|
+
const errorMessage = (data, fallback) => {
|
|
1681
|
+
if (!isRecord2(data))
|
|
1682
|
+
return fallback;
|
|
1683
|
+
const error = data.error;
|
|
1684
|
+
if (!isRecord2(error) || typeof error.message !== "string" || error.message === "") {
|
|
1685
|
+
return fallback;
|
|
1686
|
+
}
|
|
1687
|
+
return error.message;
|
|
1688
|
+
};
|
|
1689
|
+
const requireList = (value, label) => {
|
|
1690
|
+
if (value === void 0 || value === null)
|
|
1691
|
+
return [];
|
|
1692
|
+
if (!Array.isArray(value))
|
|
1693
|
+
throw new Error(`${label} was not a list`);
|
|
1694
|
+
return value;
|
|
1695
|
+
};
|
|
1696
|
+
const state = {
|
|
1697
|
+
namespace: "default",
|
|
1698
|
+
key: sessionStorage.getItem("mockingbird-admin-key") || "",
|
|
1699
|
+
view: "overview",
|
|
1700
|
+
collection: "",
|
|
1701
|
+
shape: null,
|
|
1702
|
+
route: null,
|
|
1703
|
+
editing: null
|
|
1704
|
+
};
|
|
1705
|
+
const standard = new Set(config.standardRoutes);
|
|
1706
|
+
const banner = el("banner", HTMLElement);
|
|
1707
|
+
const keyInput = el("admin-key", HTMLInputElement);
|
|
1708
|
+
keyInput.value = state.key;
|
|
1709
|
+
const params = new URLSearchParams(location.search);
|
|
1710
|
+
const queryKey = params.get("key");
|
|
1711
|
+
if (queryKey) {
|
|
1712
|
+
state.key = queryKey;
|
|
1713
|
+
keyInput.value = state.key;
|
|
1714
|
+
sessionStorage.setItem("mockingbird-admin-key", state.key);
|
|
1715
|
+
params.delete("key");
|
|
1716
|
+
const next = params.toString();
|
|
1717
|
+
history.replaceState(null, "", location.pathname + (next ? `?${next}` : ""));
|
|
1718
|
+
}
|
|
1719
|
+
const queryNamespace = params.get("namespace");
|
|
1720
|
+
if (queryNamespace)
|
|
1721
|
+
state.namespace = queryNamespace;
|
|
1722
|
+
const showError = (error) => {
|
|
1723
|
+
banner.textContent = error instanceof Error ? error.message : String(error);
|
|
1724
|
+
banner.classList.add("show");
|
|
1725
|
+
};
|
|
1726
|
+
const clearError = () => {
|
|
1727
|
+
banner.classList.remove("show");
|
|
1728
|
+
};
|
|
1729
|
+
const api = async (method, path, body) => {
|
|
1730
|
+
const headers = { accept: "application/json" };
|
|
1731
|
+
if (state.key !== "")
|
|
1732
|
+
headers[config.adminKeyHeader] = state.key;
|
|
1733
|
+
if (state.namespace !== "")
|
|
1734
|
+
headers["x-mockingbird-namespace"] = state.namespace;
|
|
1735
|
+
if (body !== void 0)
|
|
1736
|
+
headers["content-type"] = "application/json";
|
|
1737
|
+
const init = { method, headers };
|
|
1738
|
+
if (body !== void 0)
|
|
1739
|
+
init.body = JSON.stringify(body);
|
|
1740
|
+
const response = await fetch(config.adminPrefix + path, init);
|
|
1741
|
+
const text2 = await response.text();
|
|
1742
|
+
let data = null;
|
|
1743
|
+
if (text2 !== "") {
|
|
1744
|
+
try {
|
|
1745
|
+
data = readJson2(text2);
|
|
1746
|
+
} catch {
|
|
1747
|
+
data = { raw: text2 };
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
if (response.status === 401) {
|
|
1751
|
+
keyInput.focus();
|
|
1752
|
+
throw new Error(errorMessage(data, "Admin key required"));
|
|
1753
|
+
}
|
|
1754
|
+
if (!response.ok) {
|
|
1755
|
+
throw new Error(errorMessage(data, `${response.status} ${method} ${path}`));
|
|
1756
|
+
}
|
|
1757
|
+
return data;
|
|
1758
|
+
};
|
|
1759
|
+
const originGet = async (path) => {
|
|
1760
|
+
const response = await fetch(path, { headers: { accept: "application/json" } });
|
|
1761
|
+
if (!response.ok)
|
|
1762
|
+
throw new Error(`${response.status} ${path}`);
|
|
1763
|
+
const value = await response.json();
|
|
1764
|
+
return value;
|
|
1765
|
+
};
|
|
1766
|
+
const panelApi = {
|
|
1767
|
+
get: (path) => api("GET", path),
|
|
1768
|
+
send: (method, path, body) => api(method, path, body),
|
|
1769
|
+
namespace: () => state.namespace
|
|
1770
|
+
};
|
|
1771
|
+
const isPanelScript = (value) => typeof value === "function";
|
|
1772
|
+
const setTheme = (theme) => {
|
|
1773
|
+
if (theme)
|
|
1774
|
+
document.documentElement.dataset.theme = theme;
|
|
1775
|
+
else
|
|
1776
|
+
delete document.documentElement.dataset.theme;
|
|
1777
|
+
};
|
|
1778
|
+
const storedTheme = localStorage.getItem("mockingbird-admin-theme");
|
|
1779
|
+
if (storedTheme)
|
|
1780
|
+
setTheme(storedTheme);
|
|
1781
|
+
el("theme", HTMLButtonElement).addEventListener("click", () => {
|
|
1782
|
+
const current = document.documentElement.dataset.theme;
|
|
1783
|
+
const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
|
|
1784
|
+
const next = dark ? "light" : "dark";
|
|
1785
|
+
localStorage.setItem("mockingbird-admin-theme", next);
|
|
1786
|
+
setTheme(next);
|
|
1787
|
+
});
|
|
1788
|
+
const show = (view) => {
|
|
1789
|
+
state.view = view;
|
|
1790
|
+
for (const button of document.querySelectorAll("#nav button")) {
|
|
1791
|
+
if (!(button instanceof HTMLButtonElement))
|
|
1792
|
+
continue;
|
|
1793
|
+
button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false");
|
|
1794
|
+
}
|
|
1795
|
+
for (const section of document.querySelectorAll("main .view")) {
|
|
1796
|
+
if (!(section instanceof HTMLElement))
|
|
1797
|
+
continue;
|
|
1798
|
+
section.hidden = section.id !== `view-${view}`;
|
|
1799
|
+
}
|
|
1800
|
+
};
|
|
1801
|
+
el("nav", HTMLElement).addEventListener("click", (event) => {
|
|
1802
|
+
const target = event.target;
|
|
1803
|
+
if (!(target instanceof Element))
|
|
1804
|
+
return;
|
|
1805
|
+
const button = target.closest("button[data-view]");
|
|
1806
|
+
if (!(button instanceof HTMLButtonElement))
|
|
1807
|
+
return;
|
|
1808
|
+
const view = button.dataset.view;
|
|
1809
|
+
if (view === void 0)
|
|
1810
|
+
return;
|
|
1811
|
+
show(view);
|
|
1812
|
+
refresh().catch(showError);
|
|
1813
|
+
});
|
|
1814
|
+
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>`;
|
|
1815
|
+
const table = (columns, rows) => {
|
|
1816
|
+
if (rows.length === 0)
|
|
1817
|
+
return '<p class="empty">Nothing here yet.</p>';
|
|
1818
|
+
const head = `<tr>${columns.map((column) => `<th>${column.label}</th>`).join("")}</tr>`;
|
|
1819
|
+
const body = rows.map((row2) => {
|
|
1820
|
+
const cells = columns.map((column) => `<td data-label="${column.label}">${column.cell(row2)}</td>`).join("");
|
|
1821
|
+
const attrs = "attrs" in row2 && typeof row2.attrs === "string" ? row2.attrs : "";
|
|
1822
|
+
return `<tr${attrs}>${cells}</tr>`;
|
|
1823
|
+
}).join("");
|
|
1824
|
+
return `<table><thead>${head}</thead><tbody>${body}</tbody></table>`;
|
|
1825
|
+
};
|
|
1826
|
+
const preview = (value) => {
|
|
1827
|
+
const text2 = JSON.stringify(value) ?? "null";
|
|
1828
|
+
return esc(text2.length > 180 ? `${text2.slice(0, 180)}\u2026` : text2);
|
|
1829
|
+
};
|
|
1830
|
+
const when = (epoch) => {
|
|
1831
|
+
const date = new Date(epoch);
|
|
1832
|
+
return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString();
|
|
1833
|
+
};
|
|
1834
|
+
const clockCard = (epoch) => {
|
|
1835
|
+
const date = new Date(epoch);
|
|
1836
|
+
if (Number.isNaN(date.getTime()))
|
|
1837
|
+
return esc(String(epoch));
|
|
1838
|
+
const iso = date.toISOString();
|
|
1839
|
+
return `${esc(iso.slice(0, 10))}<span class="clock-time">${esc(iso.slice(11, 19))} UTC</span>`;
|
|
1840
|
+
};
|
|
1841
|
+
const decodeClock = (value) => {
|
|
1842
|
+
if (!isRecord2(value) || typeof value.now !== "number" || typeof value.offsetMs !== "number" || typeof value.frozen !== "boolean") {
|
|
1843
|
+
throw new Error("Clock response was not a clock");
|
|
1844
|
+
}
|
|
1845
|
+
return { now: value.now, offsetMs: value.offsetMs, frozen: value.frozen };
|
|
1846
|
+
};
|
|
1847
|
+
const decodeField = (value) => {
|
|
1848
|
+
if (!isRecord2(value) || typeof value.name !== "string" || !isFieldKind(value.kind)) {
|
|
1849
|
+
return void 0;
|
|
1850
|
+
}
|
|
1851
|
+
const field = {
|
|
1852
|
+
name: value.name,
|
|
1853
|
+
kind: value.kind,
|
|
1854
|
+
optional: value.optional === true
|
|
1855
|
+
};
|
|
1856
|
+
if (typeof value.description === "string")
|
|
1857
|
+
field.description = value.description;
|
|
1858
|
+
if (Array.isArray(value.fields)) {
|
|
1859
|
+
field.fields = value.fields.flatMap((item) => {
|
|
1860
|
+
const child = decodeField(item);
|
|
1861
|
+
return child === void 0 ? [] : [child];
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
return field;
|
|
1865
|
+
};
|
|
1866
|
+
const decodeCollection = (value) => {
|
|
1867
|
+
if (!isRecord2(value) || typeof value.name !== "string")
|
|
1868
|
+
return void 0;
|
|
1869
|
+
const fields = Array.isArray(value.fields) ? value.fields.flatMap((item) => {
|
|
1870
|
+
const field = decodeField(item);
|
|
1871
|
+
return field === void 0 ? [] : [field];
|
|
1872
|
+
}) : [];
|
|
1873
|
+
const collection = {
|
|
1874
|
+
name: value.name,
|
|
1875
|
+
label: typeof value.label === "string" ? value.label : value.name,
|
|
1876
|
+
count: typeof value.count === "number" ? value.count : 0,
|
|
1877
|
+
source: isSource(value.source) ? value.source : "inferred",
|
|
1878
|
+
fields
|
|
1879
|
+
};
|
|
1880
|
+
if (typeof value.description === "string")
|
|
1881
|
+
collection.description = value.description;
|
|
1882
|
+
return collection;
|
|
1883
|
+
};
|
|
1884
|
+
const decodeState = (value) => {
|
|
1885
|
+
if (!isRecord2(value) || !Array.isArray(value.collections)) {
|
|
1886
|
+
throw new Error("State response has no collection list");
|
|
1887
|
+
}
|
|
1888
|
+
return {
|
|
1889
|
+
namespace: typeof value.namespace === "string" ? value.namespace : state.namespace,
|
|
1890
|
+
storageNamespace: typeof value.storageNamespace === "string" ? value.storageNamespace : "",
|
|
1891
|
+
collections: value.collections.flatMap((item) => {
|
|
1892
|
+
const collection = decodeCollection(item);
|
|
1893
|
+
return collection === void 0 ? [] : [collection];
|
|
1894
|
+
})
|
|
1895
|
+
};
|
|
1896
|
+
};
|
|
1897
|
+
const decodeRecords = (value) => requireList(isRecord2(value) ? value.records : void 0, "Records").flatMap((item) => {
|
|
1898
|
+
if (!isRecord2(item) || typeof item.id !== "string")
|
|
1899
|
+
return [];
|
|
1900
|
+
return [
|
|
1901
|
+
{
|
|
1902
|
+
id: item.id,
|
|
1903
|
+
seq: typeof item.seq === "number" ? item.seq : 0,
|
|
1904
|
+
value: item.value,
|
|
1905
|
+
attrs: ` class="clickable" data-id="${esc(item.id)}"`
|
|
1906
|
+
}
|
|
1907
|
+
];
|
|
1908
|
+
});
|
|
1909
|
+
const textOf = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : "";
|
|
1910
|
+
const decodeJournal = (value) => requireList(isRecord2(value) ? value.requests : void 0, "Journal").flatMap((item) => {
|
|
1911
|
+
if (!isRecord2(item))
|
|
1912
|
+
return [];
|
|
1913
|
+
return [
|
|
1914
|
+
{
|
|
1915
|
+
at: textOf(item.at),
|
|
1916
|
+
method: textOf(item.method),
|
|
1917
|
+
path: textOf(item.path),
|
|
1918
|
+
status: textOf(item.status),
|
|
1919
|
+
durationMs: textOf(item.durationMs),
|
|
1920
|
+
operationId: textOf(item.operationId)
|
|
1921
|
+
}
|
|
1922
|
+
];
|
|
1923
|
+
});
|
|
1924
|
+
const decodeFaults = (value) => requireList(isRecord2(value) ? value.faults : void 0, "Faults").flatMap((item) => {
|
|
1925
|
+
if (!isRecord2(item))
|
|
1926
|
+
return [];
|
|
1927
|
+
return [
|
|
1928
|
+
{
|
|
1929
|
+
id: textOf(item.id),
|
|
1930
|
+
method: typeof item.method === "string" ? item.method : void 0,
|
|
1931
|
+
pathPrefix: typeof item.pathPrefix === "string" ? item.pathPrefix : void 0,
|
|
1932
|
+
operationId: typeof item.operationId === "string" ? item.operationId : void 0,
|
|
1933
|
+
status: typeof item.status === "number" ? item.status : void 0,
|
|
1934
|
+
effect: typeof item.effect === "string" ? item.effect : void 0,
|
|
1935
|
+
drop: typeof item.drop === "boolean" ? item.drop : void 0,
|
|
1936
|
+
latencyMs: typeof item.latencyMs === "number" ? item.latencyMs : void 0
|
|
1937
|
+
}
|
|
1938
|
+
];
|
|
1939
|
+
});
|
|
1940
|
+
const loadNamespaces = async () => {
|
|
1941
|
+
const data = await api("GET", "/namespaces");
|
|
1942
|
+
const select = el("namespace", HTMLSelectElement);
|
|
1943
|
+
const listed = requireList(isRecord2(data) ? data.namespaces : void 0, "Namespaces");
|
|
1944
|
+
const fallback = isRecord2(data) && typeof data.default === "string" && data.default !== "" ? data.default : "default";
|
|
1945
|
+
const names = (listed.length > 0 ? listed.map((name) => String(name)) : [fallback]).slice();
|
|
1946
|
+
if (!names.includes(state.namespace))
|
|
1947
|
+
names.unshift(state.namespace);
|
|
1948
|
+
select.innerHTML = names.map((name) => `<option value="${esc(name)}">${esc(name)}</option>`).join("");
|
|
1949
|
+
select.value = state.namespace;
|
|
1950
|
+
};
|
|
1951
|
+
const renderOverview = async () => {
|
|
1952
|
+
const [health, shape, clock, journal] = await Promise.all([
|
|
1953
|
+
originGet("/health"),
|
|
1954
|
+
api("GET", "/state"),
|
|
1955
|
+
api("GET", "/clock"),
|
|
1956
|
+
api("GET", "/requests?limit=8")
|
|
1957
|
+
]);
|
|
1958
|
+
if (!isRecord2(health))
|
|
1959
|
+
throw new Error("Health response was not an object");
|
|
1960
|
+
const decoded = decodeState(shape);
|
|
1961
|
+
state.shape = decoded;
|
|
1962
|
+
const time = decodeClock(clock);
|
|
1963
|
+
el("overview-cards", HTMLElement).innerHTML = cards([
|
|
1964
|
+
{
|
|
1965
|
+
k: "Service",
|
|
1966
|
+
v: esc(typeof health.service === "string" && health.service !== "" ? health.service : config.service)
|
|
1967
|
+
},
|
|
1968
|
+
{
|
|
1969
|
+
k: "Status",
|
|
1970
|
+
v: esc(typeof health.status === "string" && health.status !== "" ? health.status : "ok")
|
|
1971
|
+
},
|
|
1972
|
+
{ k: "Namespace", v: esc(state.namespace) },
|
|
1973
|
+
{ k: "Collections", v: String(decoded.collections.length) },
|
|
1974
|
+
{
|
|
1975
|
+
k: "Records",
|
|
1976
|
+
v: String(decoded.collections.reduce((sum, item) => sum + item.count, 0))
|
|
1977
|
+
},
|
|
1978
|
+
{ k: "Clock", v: clockCard(time.now) }
|
|
1979
|
+
]);
|
|
1980
|
+
el("overview-requests", HTMLElement).innerHTML = table([
|
|
1981
|
+
{ label: "Method", cell: (row2) => esc(row2.method) },
|
|
1982
|
+
{ label: "Path", cell: (row2) => esc(row2.path) },
|
|
1983
|
+
{
|
|
1984
|
+
label: "Status",
|
|
1985
|
+
cell: (row2) => `<span class="status-${row2.status.charAt(0)}">${esc(row2.status)}</span>`
|
|
1986
|
+
},
|
|
1987
|
+
{ label: "Operation", cell: (row2) => esc(row2.operationId) }
|
|
1988
|
+
], decodeJournal(journal));
|
|
1989
|
+
};
|
|
1990
|
+
const schemaChips = (collection) => {
|
|
1991
|
+
if (collection.fields.length === 0) {
|
|
1992
|
+
return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>';
|
|
1993
|
+
}
|
|
1994
|
+
return `<div class="chips">${collection.fields.map((field) => `<span class="chip">${esc(field.name)} <i>${esc(field.kind)}${field.optional ? "?" : ""}</i></span>`).join("")}</div>`;
|
|
1995
|
+
};
|
|
1996
|
+
const renderState = async () => {
|
|
1997
|
+
const shape = decodeState(await api("GET", "/state"));
|
|
1998
|
+
state.shape = shape;
|
|
1999
|
+
const host = el("collections", HTMLElement);
|
|
2000
|
+
if (shape.collections.length === 0) {
|
|
2001
|
+
host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>';
|
|
2002
|
+
el("schema", HTMLElement).innerHTML = "";
|
|
2003
|
+
el("records", HTMLElement).innerHTML = "";
|
|
2004
|
+
return;
|
|
2005
|
+
}
|
|
2006
|
+
if (!shape.collections.some((item) => item.name === state.collection)) {
|
|
2007
|
+
const first = shape.collections[0];
|
|
2008
|
+
if (first === void 0)
|
|
2009
|
+
return;
|
|
2010
|
+
state.collection = first.name;
|
|
2011
|
+
}
|
|
2012
|
+
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("");
|
|
2013
|
+
const selected = shape.collections.find((item) => item.name === state.collection);
|
|
2014
|
+
if (selected === void 0)
|
|
2015
|
+
return;
|
|
2016
|
+
const description = selected.description ? `<p class="lede">${esc(selected.description)}</p>` : "";
|
|
2017
|
+
el("schema", HTMLElement).innerHTML = `<h3>${esc(selected.label)}</h3>${description}${schemaChips(selected)}`;
|
|
2018
|
+
const page = await api("GET", `/state/${encodeURIComponent(state.collection)}?limit=50`);
|
|
2019
|
+
el("records", HTMLElement).innerHTML = table([
|
|
2020
|
+
{ label: "Id", cell: (row2) => `<code>${esc(row2.id)}</code>` },
|
|
2021
|
+
{ label: "Seq", cell: (row2) => esc(row2.seq) },
|
|
2022
|
+
{ label: "Value", cell: (row2) => preview(row2.value) }
|
|
2023
|
+
], decodeRecords(page));
|
|
2024
|
+
};
|
|
2025
|
+
const renderClock = async () => {
|
|
2026
|
+
const clock = decodeClock(await api("GET", "/clock"));
|
|
2027
|
+
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>`;
|
|
2028
|
+
const freeze = el("freeze", HTMLButtonElement);
|
|
2029
|
+
freeze.textContent = clock.frozen ? "Unfreeze" : "Freeze";
|
|
2030
|
+
freeze.dataset.frozen = clock.frozen ? "1" : "0";
|
|
2031
|
+
};
|
|
2032
|
+
const renderFaults = async () => {
|
|
2033
|
+
const [faults, presets] = await Promise.all([
|
|
2034
|
+
api("GET", "/faults"),
|
|
2035
|
+
api("GET", "/faults/presets").catch(() => ({ presets: [] }))
|
|
2036
|
+
]);
|
|
2037
|
+
const select = el("fault-preset", HTMLSelectElement);
|
|
2038
|
+
const current = select.value;
|
|
2039
|
+
const list = faultPresetList(presets).presets;
|
|
2040
|
+
select.innerHTML = `<option value="">Custom rule</option>${list.map((preset) => `<option value="${esc(preset.name)}">${esc(preset.name)}</option>`).join("")}`;
|
|
2041
|
+
select.value = current;
|
|
2042
|
+
el("fault-list", HTMLElement).innerHTML = table([
|
|
2043
|
+
{ label: "Id", cell: (row2) => esc(row2.id) },
|
|
2044
|
+
{
|
|
2045
|
+
label: "Match",
|
|
2046
|
+
cell: (row2) => esc([row2.method, row2.pathPrefix, row2.operationId].filter((part) => Boolean(part)).join(" ") || "all")
|
|
2047
|
+
},
|
|
2048
|
+
{
|
|
2049
|
+
label: "Effect",
|
|
2050
|
+
cell: (row2) => esc(row2.status ?? row2.effect ?? (row2.drop ? "drop" : row2.latencyMs || ""))
|
|
2051
|
+
}
|
|
2052
|
+
], decodeFaults(faults));
|
|
2053
|
+
};
|
|
2054
|
+
const renderJournal = async () => {
|
|
2055
|
+
const journal = await api("GET", "/requests?limit=100");
|
|
2056
|
+
el("journal", HTMLElement).innerHTML = table([
|
|
2057
|
+
{ label: "When", cell: (row2) => esc(row2.at) },
|
|
2058
|
+
{ label: "Method", cell: (row2) => esc(row2.method) },
|
|
2059
|
+
{ label: "Path", cell: (row2) => esc(row2.path) },
|
|
2060
|
+
{ label: "Status", cell: (row2) => esc(row2.status) },
|
|
2061
|
+
{ label: "ms", cell: (row2) => esc(row2.durationMs) }
|
|
2062
|
+
], decodeJournal(journal));
|
|
2063
|
+
};
|
|
2064
|
+
const renderRoutes = async () => {
|
|
2065
|
+
const data = await api("GET", "/");
|
|
2066
|
+
if (!isRecord2(data))
|
|
2067
|
+
throw new Error("Route list was not an object");
|
|
2068
|
+
const routes = requireList(data.routes, "Routes").map((route) => String(route));
|
|
2069
|
+
el("route-list", HTMLElement).innerHTML = routes.map((route) => {
|
|
2070
|
+
const extra = standard.has(route) ? "" : " <span class='badge'>extension</span>";
|
|
2071
|
+
const parts = route.split(" ");
|
|
2072
|
+
const method = parts[0] ?? "";
|
|
2073
|
+
const path = parts.slice(1).join(" ");
|
|
2074
|
+
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>`;
|
|
2075
|
+
}).join("");
|
|
2076
|
+
};
|
|
2077
|
+
const renderers = {
|
|
2078
|
+
overview: renderOverview,
|
|
2079
|
+
state: renderState,
|
|
2080
|
+
clock: renderClock,
|
|
2081
|
+
faults: renderFaults,
|
|
2082
|
+
journal: renderJournal,
|
|
2083
|
+
routes: renderRoutes
|
|
2084
|
+
};
|
|
2085
|
+
async function refresh() {
|
|
2086
|
+
clearError();
|
|
2087
|
+
const render = renderers[state.view];
|
|
2088
|
+
if (render)
|
|
2089
|
+
await render();
|
|
2090
|
+
}
|
|
2091
|
+
el("namespace", HTMLSelectElement).addEventListener("change", () => {
|
|
2092
|
+
state.namespace = el("namespace", HTMLSelectElement).value;
|
|
2093
|
+
refresh().catch(showError);
|
|
2094
|
+
});
|
|
2095
|
+
keyInput.addEventListener("change", () => {
|
|
2096
|
+
state.key = keyInput.value.trim();
|
|
2097
|
+
sessionStorage.setItem("mockingbird-admin-key", state.key);
|
|
2098
|
+
refresh().catch(showError);
|
|
2099
|
+
});
|
|
2100
|
+
el("refresh", HTMLButtonElement).addEventListener("click", () => {
|
|
2101
|
+
refresh().catch(showError);
|
|
2102
|
+
});
|
|
2103
|
+
el("collections", HTMLElement).addEventListener("click", (event) => {
|
|
2104
|
+
const target = event.target;
|
|
2105
|
+
if (!(target instanceof Element))
|
|
2106
|
+
return;
|
|
2107
|
+
const button = target.closest("[data-collection]");
|
|
2108
|
+
if (!(button instanceof HTMLElement))
|
|
2109
|
+
return;
|
|
2110
|
+
const collection = button.dataset.collection;
|
|
2111
|
+
if (collection === void 0)
|
|
2112
|
+
return;
|
|
2113
|
+
state.collection = collection;
|
|
2114
|
+
renderState().catch(showError);
|
|
2115
|
+
});
|
|
2116
|
+
el("records", HTMLElement).addEventListener("click", (event) => {
|
|
2117
|
+
const target = event.target;
|
|
2118
|
+
if (!(target instanceof Element))
|
|
2119
|
+
return;
|
|
2120
|
+
const row2 = target.closest("[data-id]");
|
|
2121
|
+
if (!(row2 instanceof HTMLElement))
|
|
2122
|
+
return;
|
|
2123
|
+
const id = row2.dataset.id;
|
|
2124
|
+
if (id === void 0)
|
|
2125
|
+
return;
|
|
2126
|
+
openEditor(id).catch(showError);
|
|
2127
|
+
});
|
|
2128
|
+
const editor = el("editor", HTMLDialogElement);
|
|
2129
|
+
const editorId = el("editor-id", HTMLInputElement);
|
|
2130
|
+
const editorValue = el("editor-value", HTMLTextAreaElement);
|
|
2131
|
+
const editorDelete = el("editor-delete", HTMLButtonElement);
|
|
2132
|
+
async function openEditor(id) {
|
|
2133
|
+
state.editing = id;
|
|
2134
|
+
el("editor-title", HTMLElement).textContent = id ? "Edit record" : "New record";
|
|
2135
|
+
editorId.value = id ?? "";
|
|
2136
|
+
editorId.readOnly = id !== null;
|
|
2137
|
+
editorDelete.hidden = id === null;
|
|
2138
|
+
if (id) {
|
|
2139
|
+
const record = await api("GET", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`);
|
|
2140
|
+
if (!isRecord2(record))
|
|
2141
|
+
throw new Error("Record response was not an object");
|
|
2142
|
+
editorValue.value = JSON.stringify(record.value, null, 2) ?? "";
|
|
2143
|
+
} else {
|
|
2144
|
+
editorValue.value = "{\n \n}";
|
|
2145
|
+
}
|
|
2146
|
+
editor.showModal();
|
|
2147
|
+
}
|
|
2148
|
+
el("new-record", HTMLButtonElement).addEventListener("click", () => {
|
|
2149
|
+
openEditor(null).catch(showError);
|
|
2150
|
+
});
|
|
2151
|
+
el("editor-form", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2152
|
+
const submitter = event.submitter;
|
|
2153
|
+
if (!(submitter instanceof HTMLButtonElement) || submitter.value !== "save")
|
|
2154
|
+
return;
|
|
2155
|
+
event.preventDefault();
|
|
2156
|
+
let value;
|
|
2157
|
+
try {
|
|
2158
|
+
value = readJson2(editorValue.value);
|
|
2159
|
+
} catch {
|
|
2160
|
+
showError(new Error("Value is not JSON"));
|
|
2161
|
+
return;
|
|
2162
|
+
}
|
|
2163
|
+
const id = editorId.value.trim();
|
|
2164
|
+
const path = `/state/${encodeURIComponent(state.collection)}`;
|
|
2165
|
+
const request = state.editing ? api("PUT", `${path}/${encodeURIComponent(id)}`, { value }) : api("POST", path, { id: id || void 0, value });
|
|
2166
|
+
request.then(() => {
|
|
2167
|
+
editor.close();
|
|
2168
|
+
return renderState();
|
|
2169
|
+
}).catch(showError);
|
|
2170
|
+
});
|
|
2171
|
+
editorDelete.addEventListener("click", () => {
|
|
2172
|
+
const id = state.editing;
|
|
2173
|
+
if (!id)
|
|
2174
|
+
return;
|
|
2175
|
+
api("DELETE", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`).then(() => {
|
|
2176
|
+
editor.close();
|
|
2177
|
+
return renderState();
|
|
2178
|
+
}).catch(showError);
|
|
2179
|
+
});
|
|
2180
|
+
for (const button of document.querySelectorAll("[data-advance]")) {
|
|
2181
|
+
if (!(button instanceof HTMLButtonElement))
|
|
2182
|
+
continue;
|
|
2183
|
+
button.addEventListener("click", () => {
|
|
2184
|
+
const raw = button.dataset.advance;
|
|
2185
|
+
if (raw === void 0)
|
|
2186
|
+
return;
|
|
2187
|
+
api("POST", "/clock", { advance: Number(raw) }).then(() => renderClock()).catch(showError);
|
|
2188
|
+
});
|
|
2189
|
+
}
|
|
2190
|
+
el("freeze", HTMLButtonElement).addEventListener("click", () => {
|
|
2191
|
+
const frozen = el("freeze", HTMLButtonElement).dataset.frozen === "1";
|
|
2192
|
+
api("POST", "/clock", { freeze: !frozen }).then(() => renderClock()).catch(showError);
|
|
2193
|
+
});
|
|
2194
|
+
el("clock-reset", HTMLButtonElement).addEventListener("click", () => {
|
|
2195
|
+
api("POST", "/clock", { reset: true }).then(() => renderClock()).catch(showError);
|
|
2196
|
+
});
|
|
2197
|
+
el("clock-set", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2198
|
+
event.preventDefault();
|
|
2199
|
+
const raw = el("clock-instant", HTMLInputElement).value.trim();
|
|
2200
|
+
const set = /^-?\d+$/.test(raw) ? Number(raw) : raw;
|
|
2201
|
+
api("POST", "/clock", { set }).then(() => renderClock()).catch(showError);
|
|
2202
|
+
});
|
|
2203
|
+
el("fault-form", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2204
|
+
event.preventDefault();
|
|
2205
|
+
const preset = el("fault-preset", HTMLSelectElement).value;
|
|
2206
|
+
if (preset !== "") {
|
|
2207
|
+
api("POST", "/faults", { preset }).then(() => renderFaults()).catch(showError);
|
|
2208
|
+
return;
|
|
2209
|
+
}
|
|
2210
|
+
const status = Number(el("fault-status", HTMLInputElement).value);
|
|
2211
|
+
if (!Number.isFinite(status)) {
|
|
2212
|
+
showError(new Error("Status must be a number, or choose a preset"));
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2215
|
+
const path = el("fault-path", HTMLInputElement).value;
|
|
2216
|
+
const method = el("fault-method", HTMLInputElement).value;
|
|
2217
|
+
api("POST", "/faults", {
|
|
2218
|
+
status,
|
|
2219
|
+
...path !== "" ? { pathPrefix: path } : {},
|
|
2220
|
+
...method !== "" ? { method } : {}
|
|
2221
|
+
}).then(() => renderFaults()).catch(showError);
|
|
2222
|
+
});
|
|
2223
|
+
el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
|
|
2224
|
+
api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
|
|
2225
|
+
});
|
|
2226
|
+
el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
|
|
2227
|
+
api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
|
|
2228
|
+
});
|
|
2229
|
+
el("route-list", HTMLElement).addEventListener("click", (event) => {
|
|
2230
|
+
const target = event.target;
|
|
2231
|
+
if (!(target instanceof Element))
|
|
2232
|
+
return;
|
|
2233
|
+
const button = target.closest("[data-route]");
|
|
2234
|
+
if (!(button instanceof HTMLElement))
|
|
2235
|
+
return;
|
|
2236
|
+
const route = button.dataset.route;
|
|
2237
|
+
if (route === void 0)
|
|
2238
|
+
return;
|
|
2239
|
+
state.route = route;
|
|
2240
|
+
el("route-title", HTMLElement).textContent = route;
|
|
2241
|
+
const method = route.split(" ")[0] ?? "";
|
|
2242
|
+
el("route-body", HTMLTextAreaElement).disabled = method === "GET" || method === "DELETE";
|
|
2243
|
+
});
|
|
2244
|
+
el("route-form", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2245
|
+
event.preventDefault();
|
|
2246
|
+
const send = async () => {
|
|
2247
|
+
if (state.route === null)
|
|
2248
|
+
throw new Error("Choose a route first");
|
|
2249
|
+
const parts = state.route.split(" ");
|
|
2250
|
+
const method = parts[0];
|
|
2251
|
+
const path = parts.slice(1).join(" ");
|
|
2252
|
+
if (method === void 0)
|
|
2253
|
+
throw new Error("Choose a route first");
|
|
2254
|
+
const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_match, key) => prompt(`Value for ${key}`) || "");
|
|
2255
|
+
let body;
|
|
2256
|
+
const routeBody = el("route-body", HTMLTextAreaElement);
|
|
2257
|
+
if (method !== "GET" && method !== "DELETE" && routeBody.value.trim() !== "") {
|
|
2258
|
+
try {
|
|
2259
|
+
body = readJson2(routeBody.value);
|
|
2260
|
+
} catch {
|
|
2261
|
+
throw new Error("Body is not JSON");
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
const data = await api(method, concrete, body);
|
|
2265
|
+
el("route-result", HTMLElement).textContent = JSON.stringify(data, null, 2);
|
|
2266
|
+
};
|
|
2267
|
+
send().catch(showError);
|
|
2268
|
+
});
|
|
2269
|
+
const decodePanel = (value) => {
|
|
2270
|
+
if (!isRecord2(value) || typeof value.id !== "string" || typeof value.title !== "string") {
|
|
2271
|
+
return void 0;
|
|
2272
|
+
}
|
|
2273
|
+
const panel = {
|
|
2274
|
+
id: value.id,
|
|
2275
|
+
title: value.title,
|
|
2276
|
+
description: typeof value.description === "string" ? value.description : "",
|
|
2277
|
+
html: typeof value.html === "string" ? value.html : ""
|
|
2278
|
+
};
|
|
2279
|
+
if (typeof value.script === "string")
|
|
2280
|
+
panel.script = value.script;
|
|
2281
|
+
return panel;
|
|
2282
|
+
};
|
|
2283
|
+
const mountPanels = (panels) => {
|
|
2284
|
+
const nav = el("nav", HTMLElement);
|
|
2285
|
+
const views = el("views", HTMLElement);
|
|
2286
|
+
for (const panel of panels) {
|
|
2287
|
+
const button = document.createElement("button");
|
|
2288
|
+
button.type = "button";
|
|
2289
|
+
button.dataset.view = panel.id;
|
|
2290
|
+
button.textContent = panel.title;
|
|
2291
|
+
nav.append(button);
|
|
2292
|
+
const section = document.createElement("section");
|
|
2293
|
+
section.className = "view";
|
|
2294
|
+
section.id = `view-${panel.id}`;
|
|
2295
|
+
section.hidden = true;
|
|
2296
|
+
const title = document.createElement("h2");
|
|
2297
|
+
title.textContent = panel.title;
|
|
2298
|
+
const lede = document.createElement("p");
|
|
2299
|
+
lede.className = "lede";
|
|
2300
|
+
lede.textContent = panel.description;
|
|
2301
|
+
const body = document.createElement("div");
|
|
2302
|
+
body.className = "panel-body";
|
|
2303
|
+
body.id = `panel-${panel.id}`;
|
|
2304
|
+
body.innerHTML = panel.html;
|
|
2305
|
+
section.append(title, lede, body);
|
|
2306
|
+
views.append(section);
|
|
2307
|
+
if (panel.script === void 0)
|
|
2308
|
+
continue;
|
|
2309
|
+
try {
|
|
2310
|
+
const compiled = new Function("root", "api", panel.script);
|
|
2311
|
+
if (!isPanelScript(compiled))
|
|
2312
|
+
throw new Error("panel script did not compile");
|
|
2313
|
+
compiled(body, panelApi);
|
|
2314
|
+
} catch (error) {
|
|
2315
|
+
const note = document.createElement("p");
|
|
2316
|
+
note.className = "banner show";
|
|
2317
|
+
note.textContent = error instanceof Error ? error.message : String(error);
|
|
2318
|
+
body.append(note);
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
};
|
|
2322
|
+
startAdminBrand({
|
|
2323
|
+
document,
|
|
2324
|
+
$: (id) => document.getElementById(id),
|
|
2325
|
+
params,
|
|
2326
|
+
location: { href: location.href, origin: location.origin },
|
|
2327
|
+
fetch,
|
|
2328
|
+
brandsUrl: config.brandsUrl
|
|
2329
|
+
});
|
|
2330
|
+
loadNamespaces().then(() => api("GET", "/ui/manifest")).then((manifest) => {
|
|
2331
|
+
const panels = requireList(isRecord2(manifest) ? manifest.panels : void 0, "Panels").flatMap((panel) => {
|
|
2332
|
+
const decoded = decodePanel(panel);
|
|
2333
|
+
return decoded === void 0 ? [] : [decoded];
|
|
2334
|
+
});
|
|
2335
|
+
mountPanels(panels);
|
|
2336
|
+
return refresh();
|
|
2337
|
+
}).catch(showError);
|
|
2338
|
+
}
|
|
2339
|
+
var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
|
|
2340
|
+
var adminClientSource = (config) => {
|
|
2341
|
+
const payload = JSON.stringify(config).replace(/</g, "\\u003c");
|
|
2342
|
+
return `<script>
|
|
2343
|
+
${clientPrelude}
|
|
2344
|
+
bootAdmin(${payload});
|
|
2345
|
+
</script>`;
|
|
2346
|
+
};
|
|
2347
|
+
|
|
1122
2348
|
// ../core/dist/surface.js
|
|
1123
2349
|
var defineMock = (runtime) => runtime;
|
|
1124
2350
|
var STANDARD_ADMIN_ROUTES = [
|
|
@@ -1187,94 +2413,106 @@ var escapeHtml = (value) => value.replace(/[&<>"']/g, (char) => {
|
|
|
1187
2413
|
return "'";
|
|
1188
2414
|
}
|
|
1189
2415
|
});
|
|
2416
|
+
var ADMIN_BRANDS_URL = "https://mockingbird.chrisvouga.dev/brands.json";
|
|
1190
2417
|
var renderAdminDocument = (service) => {
|
|
1191
2418
|
const name = escapeHtml(service);
|
|
1192
|
-
const
|
|
2419
|
+
const script = adminClientSource({
|
|
2420
|
+
standardRoutes: STANDARD_ADMIN_ROUTES,
|
|
2421
|
+
adminPrefix: ADMIN_PREFIX,
|
|
2422
|
+
adminKeyHeader: ADMIN_KEY_HEADER,
|
|
2423
|
+
brandsUrl: ADMIN_BRANDS_URL,
|
|
2424
|
+
service
|
|
2425
|
+
});
|
|
1193
2426
|
return `<!DOCTYPE html>
|
|
1194
2427
|
<html lang="en">
|
|
1195
2428
|
<head>
|
|
1196
2429
|
<meta charset="utf-8">
|
|
1197
2430
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1198
2431
|
<meta name="color-scheme" content="light dark">
|
|
1199
|
-
<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='
|
|
2432
|
+
<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">
|
|
1200
2433
|
<title>${name} admin</title>
|
|
1201
2434
|
<style>
|
|
2435
|
+
${CSS_RESET}
|
|
1202
2436
|
:root {
|
|
1203
2437
|
color-scheme: light;
|
|
1204
|
-
--bg: #
|
|
2438
|
+
--bg: #fafafa;
|
|
1205
2439
|
--bg-elev: #ffffff;
|
|
1206
|
-
--bg-muted: #
|
|
1207
|
-
--bg-inset: #
|
|
1208
|
-
--line: #
|
|
1209
|
-
--line-strong: #
|
|
1210
|
-
--ink: #
|
|
1211
|
-
--muted: #
|
|
1212
|
-
--faint: #
|
|
1213
|
-
--accent: #
|
|
1214
|
-
--accent-ink: #
|
|
1215
|
-
--accent-soft: #
|
|
1216
|
-
--ok: #
|
|
1217
|
-
--ok-soft: #
|
|
1218
|
-
--warn: #
|
|
1219
|
-
--warn-soft: #
|
|
1220
|
-
--err: #
|
|
1221
|
-
--err-soft: #
|
|
1222
|
-
--shadow:
|
|
1223
|
-
--sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
2440
|
+
--bg-muted: #f4f4f5;
|
|
2441
|
+
--bg-inset: #f4f4f5;
|
|
2442
|
+
--line: #dedee3;
|
|
2443
|
+
--line-strong: #c4c4cc;
|
|
2444
|
+
--ink: #18181b;
|
|
2445
|
+
--muted: #62626b;
|
|
2446
|
+
--faint: #71717a;
|
|
2447
|
+
--accent: #27272a;
|
|
2448
|
+
--accent-ink: #ffffff;
|
|
2449
|
+
--accent-soft: #f4f4f5;
|
|
2450
|
+
--ok: #166534;
|
|
2451
|
+
--ok-soft: #f0fdf4;
|
|
2452
|
+
--warn: #a16207;
|
|
2453
|
+
--warn-soft: #fefce8;
|
|
2454
|
+
--err: #a82d32;
|
|
2455
|
+
--err-soft: #fef2f2;
|
|
2456
|
+
--shadow: none;
|
|
2457
|
+
--sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
1224
2458
|
--mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
|
|
1225
|
-
--radius:
|
|
1226
|
-
--header:
|
|
1227
|
-
--nav:
|
|
2459
|
+
--radius: 10px;
|
|
2460
|
+
--header: 56px;
|
|
2461
|
+
--nav: 200px;
|
|
1228
2462
|
}
|
|
1229
2463
|
:root[data-theme="dark"] {
|
|
1230
2464
|
color-scheme: dark;
|
|
1231
|
-
--bg: #
|
|
1232
|
-
--bg-elev: #
|
|
1233
|
-
--bg-muted: #
|
|
1234
|
-
--bg-inset: #
|
|
1235
|
-
--line: #
|
|
1236
|
-
--line-strong: #
|
|
1237
|
-
--ink: #
|
|
1238
|
-
--muted: #
|
|
1239
|
-
--faint: #
|
|
1240
|
-
--accent: #
|
|
1241
|
-
--accent-ink: #
|
|
1242
|
-
--accent-soft: #
|
|
1243
|
-
--ok: #
|
|
1244
|
-
--ok-soft: #
|
|
1245
|
-
--warn: #
|
|
1246
|
-
--warn-soft: #
|
|
1247
|
-
--err: #
|
|
1248
|
-
--err-soft: #
|
|
1249
|
-
--shadow:
|
|
2465
|
+
--bg: #111113;
|
|
2466
|
+
--bg-elev: #19191c;
|
|
2467
|
+
--bg-muted: #242428;
|
|
2468
|
+
--bg-inset: #111113;
|
|
2469
|
+
--line: #36363c;
|
|
2470
|
+
--line-strong: #52525b;
|
|
2471
|
+
--ink: #f4f4f5;
|
|
2472
|
+
--muted: #a9a9b2;
|
|
2473
|
+
--faint: #71717a;
|
|
2474
|
+
--accent: #e4e4e7;
|
|
2475
|
+
--accent-ink: #18181b;
|
|
2476
|
+
--accent-soft: #242428;
|
|
2477
|
+
--ok: #86efac;
|
|
2478
|
+
--ok-soft: #14241b;
|
|
2479
|
+
--warn: #fde68a;
|
|
2480
|
+
--warn-soft: #2a2410;
|
|
2481
|
+
--err: #ffaaaa;
|
|
2482
|
+
--err-soft: #2c1516;
|
|
2483
|
+
--shadow: none;
|
|
1250
2484
|
}
|
|
1251
2485
|
@media (prefers-color-scheme: dark) {
|
|
1252
2486
|
:root:not([data-theme="light"]) {
|
|
1253
2487
|
color-scheme: dark;
|
|
1254
|
-
--bg: #
|
|
1255
|
-
--bg-elev: #
|
|
1256
|
-
--bg-muted: #
|
|
1257
|
-
--bg-inset: #
|
|
1258
|
-
--line: #
|
|
1259
|
-
--line-strong: #
|
|
1260
|
-
--ink: #
|
|
1261
|
-
--muted: #
|
|
1262
|
-
--faint: #
|
|
1263
|
-
--accent: #
|
|
1264
|
-
--accent-ink: #
|
|
1265
|
-
--accent-soft: #
|
|
1266
|
-
--ok: #
|
|
1267
|
-
--ok-soft: #
|
|
1268
|
-
--warn: #
|
|
1269
|
-
--warn-soft: #
|
|
1270
|
-
--err: #
|
|
1271
|
-
--err-soft: #
|
|
1272
|
-
--shadow:
|
|
2488
|
+
--bg: #111113;
|
|
2489
|
+
--bg-elev: #19191c;
|
|
2490
|
+
--bg-muted: #242428;
|
|
2491
|
+
--bg-inset: #111113;
|
|
2492
|
+
--line: #36363c;
|
|
2493
|
+
--line-strong: #52525b;
|
|
2494
|
+
--ink: #f4f4f5;
|
|
2495
|
+
--muted: #a9a9b2;
|
|
2496
|
+
--faint: #71717a;
|
|
2497
|
+
--accent: #e4e4e7;
|
|
2498
|
+
--accent-ink: #18181b;
|
|
2499
|
+
--accent-soft: #242428;
|
|
2500
|
+
--ok: #86efac;
|
|
2501
|
+
--ok-soft: #14241b;
|
|
2502
|
+
--warn: #fde68a;
|
|
2503
|
+
--warn-soft: #2a2410;
|
|
2504
|
+
--err: #ffaaaa;
|
|
2505
|
+
--err-soft: #2c1516;
|
|
2506
|
+
--shadow: none;
|
|
1273
2507
|
}
|
|
1274
2508
|
}
|
|
1275
|
-
|
|
1276
|
-
html
|
|
2509
|
+
/* Fill the viewport, then grow with the content so the background covers the scroll. */
|
|
2510
|
+
html { height: 100%; }
|
|
1277
2511
|
body {
|
|
2512
|
+
margin: 0;
|
|
2513
|
+
min-height: 100%;
|
|
2514
|
+
display: flex;
|
|
2515
|
+
flex-direction: column;
|
|
1278
2516
|
font-family: var(--sans);
|
|
1279
2517
|
background: var(--bg);
|
|
1280
2518
|
color: var(--ink);
|
|
@@ -1283,24 +2521,37 @@ var renderAdminDocument = (service) => {
|
|
|
1283
2521
|
padding-right: env(safe-area-inset-right);
|
|
1284
2522
|
-webkit-text-size-adjust: 100%;
|
|
1285
2523
|
}
|
|
1286
|
-
button, input, select, textarea { font: inherit; color: inherit; }
|
|
1287
|
-
button { cursor: pointer; }
|
|
1288
2524
|
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
1289
|
-
|
|
1290
|
-
* { scroll-behavior: auto !important; transition: none !important; }
|
|
1291
|
-
}
|
|
1292
|
-
.app { min-height: 100%; display: flex; flex-direction: column; }
|
|
2525
|
+
.app { flex: 1 0 auto; display: flex; flex-direction: column; min-width: 0; width: 100%; }
|
|
1293
2526
|
header.top {
|
|
1294
2527
|
position: sticky; top: 0; z-index: 5;
|
|
1295
2528
|
display: flex; align-items: center; gap: 12px;
|
|
1296
2529
|
min-height: var(--header); padding: 10px 16px;
|
|
1297
|
-
background:
|
|
2530
|
+
background: var(--bg-elev);
|
|
1298
2531
|
border-bottom: 1px solid var(--line);
|
|
1299
|
-
|
|
1300
|
-
}
|
|
1301
|
-
.brand {
|
|
1302
|
-
|
|
1303
|
-
|
|
2532
|
+
min-width: 0; max-width: 100%;
|
|
2533
|
+
}
|
|
2534
|
+
.brand {
|
|
2535
|
+
display: flex; flex-flow: row wrap; align-items: center; gap: 14px;
|
|
2536
|
+
min-width: 0; max-width: 100%; margin-right: auto;
|
|
2537
|
+
}
|
|
2538
|
+
.brand-id { display: flex; flex-direction: column; min-width: 0; flex: none; }
|
|
2539
|
+
.brand-id strong { font-size: 15px; letter-spacing: -0.01em; }
|
|
2540
|
+
.brand-id span { color: var(--faint); font-size: 12px; }
|
|
2541
|
+
.vendor {
|
|
2542
|
+
display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
|
|
2543
|
+
min-width: 0; max-width: 100%; font-size: 12px; color: var(--muted);
|
|
2544
|
+
}
|
|
2545
|
+
.vendor[hidden] { display: none; }
|
|
2546
|
+
/* Logos are drawn for a light tile, same as the docs site, so the plate stays white. */
|
|
2547
|
+
.vendor img {
|
|
2548
|
+
width: 16px; height: 16px; padding: 2px; box-sizing: content-box;
|
|
2549
|
+
border-radius: 4px; border: 1px solid var(--line); background: #fff; object-fit: contain;
|
|
2550
|
+
}
|
|
2551
|
+
.vendor-name { color: var(--ink); font-weight: 600; }
|
|
2552
|
+
.vendor-sep { color: var(--faint); }
|
|
2553
|
+
.vendor-link { color: inherit; text-decoration: none; }
|
|
2554
|
+
.vendor-link:hover { color: var(--ink); text-decoration: underline; }
|
|
1304
2555
|
.controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
|
1305
2556
|
label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
|
|
1306
2557
|
label.field input, label.field select, .controls input[type="password"] {
|
|
@@ -1318,7 +2569,7 @@ var renderAdminDocument = (service) => {
|
|
|
1318
2569
|
record or the full nav row would widen the page past the viewport. */
|
|
1319
2570
|
.layout {
|
|
1320
2571
|
display: grid; grid-template-columns: var(--nav) minmax(0, 1fr);
|
|
1321
|
-
flex: 1
|
|
2572
|
+
flex: 1 0 auto; min-width: 0;
|
|
1322
2573
|
}
|
|
1323
2574
|
nav.side {
|
|
1324
2575
|
border-right: 1px solid var(--line); padding: 12px;
|
|
@@ -1372,8 +2623,8 @@ var renderAdminDocument = (service) => {
|
|
|
1372
2623
|
.banner.show { display: block; }
|
|
1373
2624
|
.empty { color: var(--muted); padding: 24px 8px; }
|
|
1374
2625
|
dialog {
|
|
1375
|
-
border: 1px solid var(--line); border-radius:
|
|
1376
|
-
color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow:
|
|
2626
|
+
border: 1px solid var(--line); border-radius: var(--radius); padding: 0; background: var(--bg-elev);
|
|
2627
|
+
color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow: none;
|
|
1377
2628
|
}
|
|
1378
2629
|
dialog::backdrop { background: rgb(20 19 17 / 0.45); }
|
|
1379
2630
|
dialog form, .dialog-body { padding: 16px; display: flex; flex-direction: column; gap: 10px; }
|
|
@@ -1400,7 +2651,7 @@ var renderAdminDocument = (service) => {
|
|
|
1400
2651
|
padding-top: max(10px, env(safe-area-inset-top));
|
|
1401
2652
|
}
|
|
1402
2653
|
input, select, textarea { font-size: 16px; }
|
|
1403
|
-
.controls { width: 100%; }
|
|
2654
|
+
.brand, .vendor, .controls { width: 100%; }
|
|
1404
2655
|
label.field, label.field select, label.field input { flex: 1; min-width: 0; }
|
|
1405
2656
|
.layout { grid-template-columns: minmax(0, 1fr); }
|
|
1406
2657
|
nav.side {
|
|
@@ -1431,8 +2682,11 @@ var renderAdminDocument = (service) => {
|
|
|
1431
2682
|
<div class="app" data-service="${name}" data-mockingbird-admin>
|
|
1432
2683
|
<header class="top">
|
|
1433
2684
|
<div class="brand">
|
|
1434
|
-
<
|
|
1435
|
-
|
|
2685
|
+
<div class="brand-id">
|
|
2686
|
+
<strong>${name}</strong>
|
|
2687
|
+
<span>Admin</span>
|
|
2688
|
+
</div>
|
|
2689
|
+
<div class="vendor" id="vendor" hidden></div>
|
|
1436
2690
|
</div>
|
|
1437
2691
|
<div class="controls">
|
|
1438
2692
|
<label class="field">Namespace
|
|
@@ -1562,460 +2816,7 @@ var renderAdminDocument = (service) => {
|
|
|
1562
2816
|
</div>
|
|
1563
2817
|
</form>
|
|
1564
2818
|
</dialog>
|
|
1565
|
-
|
|
1566
|
-
(() => {
|
|
1567
|
-
const STANDARD = new Set(${standard})
|
|
1568
|
-
const state = {
|
|
1569
|
-
namespace: "default",
|
|
1570
|
-
key: sessionStorage.getItem("mockingbird-admin-key") || "",
|
|
1571
|
-
view: "overview",
|
|
1572
|
-
collection: "",
|
|
1573
|
-
shape: null,
|
|
1574
|
-
route: null,
|
|
1575
|
-
editing: null
|
|
1576
|
-
}
|
|
1577
|
-
const $ = (id) => document.getElementById(id)
|
|
1578
|
-
const banner = $("banner")
|
|
1579
|
-
const keyInput = $("admin-key")
|
|
1580
|
-
keyInput.value = state.key
|
|
1581
|
-
const params = new URLSearchParams(location.search)
|
|
1582
|
-
if (params.get("key")) {
|
|
1583
|
-
state.key = params.get("key")
|
|
1584
|
-
keyInput.value = state.key
|
|
1585
|
-
sessionStorage.setItem("mockingbird-admin-key", state.key)
|
|
1586
|
-
params.delete("key")
|
|
1587
|
-
const next = params.toString()
|
|
1588
|
-
history.replaceState(null, "", location.pathname + (next ? "?" + next : ""))
|
|
1589
|
-
}
|
|
1590
|
-
if (params.get("namespace")) state.namespace = params.get("namespace")
|
|
1591
|
-
|
|
1592
|
-
const showError = (error) => {
|
|
1593
|
-
banner.textContent = error instanceof Error ? error.message : String(error)
|
|
1594
|
-
banner.classList.add("show")
|
|
1595
|
-
}
|
|
1596
|
-
const clearError = () => banner.classList.remove("show")
|
|
1597
|
-
|
|
1598
|
-
async function originGet(path) {
|
|
1599
|
-
const response = await fetch(path, { headers: { accept: "application/json" } })
|
|
1600
|
-
if (!response.ok) throw new Error(response.status + " " + path)
|
|
1601
|
-
return response.json()
|
|
1602
|
-
}
|
|
1603
|
-
async function api(method, path, body) {
|
|
1604
|
-
const headers = { accept: "application/json" }
|
|
1605
|
-
if (state.key) headers["${ADMIN_KEY_HEADER}"] = state.key
|
|
1606
|
-
if (state.namespace) headers["x-mockingbird-namespace"] = state.namespace
|
|
1607
|
-
if (body !== undefined) headers["content-type"] = "application/json"
|
|
1608
|
-
const response = await fetch("${ADMIN_PREFIX}" + path, {
|
|
1609
|
-
method,
|
|
1610
|
-
headers,
|
|
1611
|
-
body: body === undefined ? undefined : JSON.stringify(body)
|
|
1612
|
-
})
|
|
1613
|
-
const text = await response.text()
|
|
1614
|
-
let data = null
|
|
1615
|
-
if (text) {
|
|
1616
|
-
try { data = JSON.parse(text) } catch { data = { raw: text } }
|
|
1617
|
-
}
|
|
1618
|
-
if (response.status === 401) {
|
|
1619
|
-
keyInput.focus()
|
|
1620
|
-
throw new Error((data && data.error && data.error.message) || "Admin key required")
|
|
1621
|
-
}
|
|
1622
|
-
if (!response.ok) {
|
|
1623
|
-
throw new Error((data && data.error && data.error.message) || (response.status + " " + method + " " + path))
|
|
1624
|
-
}
|
|
1625
|
-
return data
|
|
1626
|
-
}
|
|
1627
|
-
const panelApi = {
|
|
1628
|
-
get: (path) => api("GET", path),
|
|
1629
|
-
send: (method, path, body) => api(method, path, body),
|
|
1630
|
-
namespace: () => state.namespace
|
|
1631
|
-
}
|
|
1632
|
-
|
|
1633
|
-
function setTheme(theme) {
|
|
1634
|
-
if (theme) document.documentElement.dataset.theme = theme
|
|
1635
|
-
else delete document.documentElement.dataset.theme
|
|
1636
|
-
}
|
|
1637
|
-
const storedTheme = localStorage.getItem("mockingbird-admin-theme")
|
|
1638
|
-
if (storedTheme) setTheme(storedTheme)
|
|
1639
|
-
$("theme").addEventListener("click", () => {
|
|
1640
|
-
const current = document.documentElement.dataset.theme
|
|
1641
|
-
const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches
|
|
1642
|
-
const next = dark ? "light" : "dark"
|
|
1643
|
-
localStorage.setItem("mockingbird-admin-theme", next)
|
|
1644
|
-
setTheme(next)
|
|
1645
|
-
})
|
|
1646
|
-
|
|
1647
|
-
function show(view) {
|
|
1648
|
-
state.view = view
|
|
1649
|
-
for (const button of document.querySelectorAll("#nav button")) {
|
|
1650
|
-
button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false")
|
|
1651
|
-
}
|
|
1652
|
-
for (const section of document.querySelectorAll("main .view")) {
|
|
1653
|
-
section.hidden = section.id !== "view-" + view
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1656
|
-
document.getElementById("nav").addEventListener("click", (event) => {
|
|
1657
|
-
const button = event.target.closest("button[data-view]")
|
|
1658
|
-
if (!button) return
|
|
1659
|
-
show(button.dataset.view)
|
|
1660
|
-
refresh().catch(showError)
|
|
1661
|
-
})
|
|
1662
|
-
|
|
1663
|
-
function cards(items) {
|
|
1664
|
-
return '<div class="grid">' + items.map((item) =>
|
|
1665
|
-
'<article class="card"><div class="k">' + item.k + '</div><div class="v">' + item.v + '</div></article>'
|
|
1666
|
-
).join("") + "</div>"
|
|
1667
|
-
}
|
|
1668
|
-
function table(columns, rows) {
|
|
1669
|
-
if (rows.length === 0) return '<p class="empty">Nothing here yet.</p>'
|
|
1670
|
-
const head = "<tr>" + columns.map((column) => "<th>" + column.label + "</th>").join("") + "</tr>"
|
|
1671
|
-
const body = rows.map((row) => "<tr" + (row.attrs || "") + ">" + columns.map((column) =>
|
|
1672
|
-
'<td data-label="' + column.label + '">' + column.cell(row) + "</td>"
|
|
1673
|
-
).join("") + "</tr>").join("")
|
|
1674
|
-
return "<table><thead>" + head + "</thead><tbody>" + body + "</tbody></table>"
|
|
1675
|
-
}
|
|
1676
|
-
const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({
|
|
1677
|
-
"&": "&", "<": "<", ">": ">", '"': """, "'": "'"
|
|
1678
|
-
}[char]))
|
|
1679
|
-
const preview = (value) => {
|
|
1680
|
-
const text = JSON.stringify(value)
|
|
1681
|
-
return esc(text.length > 180 ? text.slice(0, 180) + "\u2026" : text)
|
|
1682
|
-
}
|
|
1683
|
-
const when = (epoch) => {
|
|
1684
|
-
const date = new Date(epoch)
|
|
1685
|
-
return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString()
|
|
1686
|
-
}
|
|
1687
|
-
const clockCard = (epoch) => {
|
|
1688
|
-
const date = new Date(epoch)
|
|
1689
|
-
if (Number.isNaN(date.getTime())) return esc(String(epoch))
|
|
1690
|
-
const iso = date.toISOString()
|
|
1691
|
-
return esc(iso.slice(0, 10)) + '<span class="clock-time">' + esc(iso.slice(11, 19)) + " UTC</span>"
|
|
1692
|
-
}
|
|
1693
|
-
|
|
1694
|
-
async function loadNamespaces() {
|
|
1695
|
-
const data = await api("GET", "/namespaces")
|
|
1696
|
-
const select = $("namespace")
|
|
1697
|
-
const names = data.namespaces && data.namespaces.length ? data.namespaces : [data.default || "default"]
|
|
1698
|
-
if (!names.includes(state.namespace)) names.unshift(state.namespace)
|
|
1699
|
-
select.innerHTML = names.map((name) =>
|
|
1700
|
-
'<option value="' + esc(name) + '">' + esc(name) + "</option>"
|
|
1701
|
-
).join("")
|
|
1702
|
-
select.value = state.namespace
|
|
1703
|
-
}
|
|
1704
|
-
|
|
1705
|
-
async function renderOverview() {
|
|
1706
|
-
const [health, shape, clock, journal] = await Promise.all([
|
|
1707
|
-
originGet("/health"),
|
|
1708
|
-
api("GET", "/state"),
|
|
1709
|
-
api("GET", "/clock"),
|
|
1710
|
-
api("GET", "/requests?limit=8")
|
|
1711
|
-
])
|
|
1712
|
-
state.shape = shape
|
|
1713
|
-
$("overview-cards").innerHTML = cards([
|
|
1714
|
-
{ k: "Service", v: esc(health.service || "${name}") },
|
|
1715
|
-
{ k: "Status", v: esc(health.status || "ok") },
|
|
1716
|
-
{ k: "Namespace", v: esc(state.namespace) },
|
|
1717
|
-
{ k: "Collections", v: String(shape.collections.length) },
|
|
1718
|
-
{ k: "Records", v: String(shape.collections.reduce((sum, item) => sum + item.count, 0)) },
|
|
1719
|
-
{ k: "Clock", v: clockCard(clock.now) }
|
|
1720
|
-
])
|
|
1721
|
-
const requests = (journal && journal.requests) || []
|
|
1722
|
-
$("overview-requests").innerHTML = table(
|
|
1723
|
-
[
|
|
1724
|
-
{ label: "Method", cell: (row) => esc(row.method) },
|
|
1725
|
-
{ label: "Path", cell: (row) => esc(row.path) },
|
|
1726
|
-
{ label: "Status", cell: (row) => '<span class="status-' + String(row.status).charAt(0) + '">' + esc(row.status) + "</span>" },
|
|
1727
|
-
{ label: "Operation", cell: (row) => esc(row.operationId || "") }
|
|
1728
|
-
],
|
|
1729
|
-
requests
|
|
1730
|
-
)
|
|
1731
|
-
}
|
|
1732
|
-
|
|
1733
|
-
function schemaChips(collection) {
|
|
1734
|
-
if (!collection || collection.fields.length === 0) {
|
|
1735
|
-
return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>'
|
|
1736
|
-
}
|
|
1737
|
-
return '<div class="chips">' + collection.fields.map((field) =>
|
|
1738
|
-
'<span class="chip">' + esc(field.name) + ' <i>' + esc(field.kind) + (field.optional ? "?" : "") + "</i></span>"
|
|
1739
|
-
).join("") + "</div>"
|
|
1740
|
-
}
|
|
1741
|
-
|
|
1742
|
-
async function renderState() {
|
|
1743
|
-
const shape = await api("GET", "/state")
|
|
1744
|
-
state.shape = shape
|
|
1745
|
-
const host = $("collections")
|
|
1746
|
-
if (shape.collections.length === 0) {
|
|
1747
|
-
host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>'
|
|
1748
|
-
$("schema").innerHTML = ""
|
|
1749
|
-
$("records").innerHTML = ""
|
|
1750
|
-
return
|
|
1751
|
-
}
|
|
1752
|
-
if (!shape.collections.some((item) => item.name === state.collection)) {
|
|
1753
|
-
state.collection = shape.collections[0].name
|
|
1754
|
-
}
|
|
1755
|
-
host.innerHTML = shape.collections.map((item) =>
|
|
1756
|
-
'<button type="button" class="collection" data-collection="' + esc(item.name) + '" aria-current="' +
|
|
1757
|
-
(item.name === state.collection ? "true" : "false") + '"><strong>' + esc(item.label) +
|
|
1758
|
-
'</strong><small>' + esc(item.count) + " \xB7 " + esc(item.source) + "</small></button>"
|
|
1759
|
-
).join("")
|
|
1760
|
-
const selected = shape.collections.find((item) => item.name === state.collection)
|
|
1761
|
-
$("schema").innerHTML = "<h3>" + esc(selected.label) + "</h3>" +
|
|
1762
|
-
(selected.description ? "<p class=" + '"lede">' + esc(selected.description) + "</p>" : "") +
|
|
1763
|
-
schemaChips(selected)
|
|
1764
|
-
const page = await api("GET", "/state/" + encodeURIComponent(state.collection) + "?limit=50")
|
|
1765
|
-
const rows = page.records.map((record) => ({
|
|
1766
|
-
...record,
|
|
1767
|
-
attrs: ' class="clickable" data-id="' + esc(record.id) + '"'
|
|
1768
|
-
}))
|
|
1769
|
-
$("records").innerHTML = table(
|
|
1770
|
-
[
|
|
1771
|
-
{ label: "Id", cell: (row) => "<code>" + esc(row.id) + "</code>" },
|
|
1772
|
-
{ label: "Seq", cell: (row) => esc(row.seq) },
|
|
1773
|
-
{ label: "Value", cell: (row) => preview(row.value) }
|
|
1774
|
-
],
|
|
1775
|
-
rows
|
|
1776
|
-
)
|
|
1777
|
-
}
|
|
1778
|
-
|
|
1779
|
-
async function renderClock() {
|
|
1780
|
-
const clock = await api("GET", "/clock")
|
|
1781
|
-
$("clock-readout").innerHTML = "<div class='k'>Now</div><div class='v'>" + esc(when(clock.now)) +
|
|
1782
|
-
"</div><p>" + esc(clock.now) + " ms \xB7 offset " + esc(clock.offsetMs) + " ms " +
|
|
1783
|
-
(clock.frozen ? "<span class='badge warn'>frozen</span>" : "<span class='badge ok'>live</span>") + "</p>"
|
|
1784
|
-
$("freeze").textContent = clock.frozen ? "Unfreeze" : "Freeze"
|
|
1785
|
-
$("freeze").dataset.frozen = clock.frozen ? "1" : "0"
|
|
1786
|
-
}
|
|
1787
|
-
|
|
1788
|
-
async function renderFaults() {
|
|
1789
|
-
const [faults, presets] = await Promise.all([
|
|
1790
|
-
api("GET", "/faults"),
|
|
1791
|
-
api("GET", "/faults/presets").catch(() => ({ presets: [] }))
|
|
1792
|
-
])
|
|
1793
|
-
const select = $("fault-preset")
|
|
1794
|
-
const current = select.value
|
|
1795
|
-
const list = presets.presets || []
|
|
1796
|
-
select.innerHTML = '<option value="">Custom rule</option>' + list.map((preset) =>
|
|
1797
|
-
'<option value="' + esc(preset.name) + '">' + esc(preset.name) + "</option>"
|
|
1798
|
-
).join("")
|
|
1799
|
-
select.value = current
|
|
1800
|
-
const rules = faults.faults || []
|
|
1801
|
-
$("fault-list").innerHTML = table(
|
|
1802
|
-
[
|
|
1803
|
-
{ label: "Id", cell: (row) => esc(row.id) },
|
|
1804
|
-
{ label: "Match", cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter(Boolean).join(" ") || "all") },
|
|
1805
|
-
{ label: "Effect", cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || "")) }
|
|
1806
|
-
],
|
|
1807
|
-
rules
|
|
1808
|
-
)
|
|
1809
|
-
}
|
|
1810
|
-
|
|
1811
|
-
async function renderJournal() {
|
|
1812
|
-
const journal = await api("GET", "/requests?limit=100")
|
|
1813
|
-
$("journal").innerHTML = table(
|
|
1814
|
-
[
|
|
1815
|
-
{ label: "When", cell: (row) => esc(row.at || "") },
|
|
1816
|
-
{ label: "Method", cell: (row) => esc(row.method) },
|
|
1817
|
-
{ label: "Path", cell: (row) => esc(row.path) },
|
|
1818
|
-
{ label: "Status", cell: (row) => esc(row.status) },
|
|
1819
|
-
{ label: "ms", cell: (row) => esc(row.durationMs) }
|
|
1820
|
-
],
|
|
1821
|
-
journal.requests || []
|
|
1822
|
-
)
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
|
-
async function renderRoutes() {
|
|
1826
|
-
const data = await api("GET", "/")
|
|
1827
|
-
const routes = data.routes || []
|
|
1828
|
-
$("route-list").innerHTML = routes.map((route) => {
|
|
1829
|
-
const extra = STANDARD.has(route) ? "" : " <span class='badge'>extension</span>"
|
|
1830
|
-
return '<div class="route"><span class="method">' + esc(route.split(" ")[0]) +
|
|
1831
|
-
'</span><span>' + esc(route.split(" ").slice(1).join(" ")) + extra +
|
|
1832
|
-
'</span><button class="btn" type="button" data-route="' + esc(route) + '">Use</button></div>'
|
|
1833
|
-
}).join("")
|
|
1834
|
-
}
|
|
1835
|
-
|
|
1836
|
-
const renderers = {
|
|
1837
|
-
overview: renderOverview,
|
|
1838
|
-
state: renderState,
|
|
1839
|
-
clock: renderClock,
|
|
1840
|
-
faults: renderFaults,
|
|
1841
|
-
journal: renderJournal,
|
|
1842
|
-
routes: renderRoutes
|
|
1843
|
-
}
|
|
1844
|
-
|
|
1845
|
-
async function refresh() {
|
|
1846
|
-
clearError()
|
|
1847
|
-
if (state.view in renderers) await renderers[state.view]()
|
|
1848
|
-
}
|
|
1849
|
-
|
|
1850
|
-
$("namespace").addEventListener("change", () => {
|
|
1851
|
-
state.namespace = $("namespace").value
|
|
1852
|
-
refresh().catch(showError)
|
|
1853
|
-
})
|
|
1854
|
-
keyInput.addEventListener("change", () => {
|
|
1855
|
-
state.key = keyInput.value.trim()
|
|
1856
|
-
sessionStorage.setItem("mockingbird-admin-key", state.key)
|
|
1857
|
-
refresh().catch(showError)
|
|
1858
|
-
})
|
|
1859
|
-
$("refresh").addEventListener("click", () => refresh().catch(showError))
|
|
1860
|
-
|
|
1861
|
-
$("collections").addEventListener("click", (event) => {
|
|
1862
|
-
const button = event.target.closest("[data-collection]")
|
|
1863
|
-
if (!button) return
|
|
1864
|
-
state.collection = button.dataset.collection
|
|
1865
|
-
renderState().catch(showError)
|
|
1866
|
-
})
|
|
1867
|
-
$("records").addEventListener("click", (event) => {
|
|
1868
|
-
const row = event.target.closest("[data-id]")
|
|
1869
|
-
if (!row) return
|
|
1870
|
-
openEditor(row.dataset.id).catch(showError)
|
|
1871
|
-
})
|
|
1872
|
-
|
|
1873
|
-
const editor = $("editor")
|
|
1874
|
-
async function openEditor(id) {
|
|
1875
|
-
state.editing = id || null
|
|
1876
|
-
$("editor-title").textContent = id ? "Edit record" : "New record"
|
|
1877
|
-
$("editor-id").value = id || ""
|
|
1878
|
-
$("editor-id").readOnly = Boolean(id)
|
|
1879
|
-
$("editor-delete").hidden = !id
|
|
1880
|
-
if (id) {
|
|
1881
|
-
const record = await api("GET", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
|
|
1882
|
-
$("editor-value").value = JSON.stringify(record.value, null, 2)
|
|
1883
|
-
} else {
|
|
1884
|
-
$("editor-value").value = "{\\n \\n}"
|
|
1885
|
-
}
|
|
1886
|
-
editor.showModal()
|
|
1887
|
-
}
|
|
1888
|
-
$("new-record").addEventListener("click", () => openEditor(null).catch(showError))
|
|
1889
|
-
$("editor-form").addEventListener("submit", (event) => {
|
|
1890
|
-
const submitter = event.submitter
|
|
1891
|
-
if (!submitter || submitter.value !== "save") return
|
|
1892
|
-
event.preventDefault()
|
|
1893
|
-
let value
|
|
1894
|
-
try { value = JSON.parse($("editor-value").value) }
|
|
1895
|
-
catch { showError(new Error("Value is not JSON")); return }
|
|
1896
|
-
const id = $("editor-id").value.trim()
|
|
1897
|
-
const path = "/state/" + encodeURIComponent(state.collection)
|
|
1898
|
-
const request = state.editing
|
|
1899
|
-
? api("PUT", path + "/" + encodeURIComponent(id), { value })
|
|
1900
|
-
: api("POST", path, { id: id || undefined, value })
|
|
1901
|
-
request.then(() => { editor.close(); return renderState() }).catch(showError)
|
|
1902
|
-
})
|
|
1903
|
-
$("editor-delete").addEventListener("click", () => {
|
|
1904
|
-
const id = state.editing
|
|
1905
|
-
if (!id) return
|
|
1906
|
-
api("DELETE", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
|
|
1907
|
-
.then(() => { editor.close(); return renderState() })
|
|
1908
|
-
.catch(showError)
|
|
1909
|
-
})
|
|
1910
|
-
|
|
1911
|
-
document.querySelectorAll("[data-advance]").forEach((button) => {
|
|
1912
|
-
button.addEventListener("click", () => {
|
|
1913
|
-
api("POST", "/clock", { advance: Number(button.dataset.advance) }).then(renderClock).catch(showError)
|
|
1914
|
-
})
|
|
1915
|
-
})
|
|
1916
|
-
$("freeze").addEventListener("click", () => {
|
|
1917
|
-
const frozen = $("freeze").dataset.frozen === "1"
|
|
1918
|
-
api("POST", "/clock", { freeze: !frozen }).then(renderClock).catch(showError)
|
|
1919
|
-
})
|
|
1920
|
-
$("clock-reset").addEventListener("click", () => {
|
|
1921
|
-
api("POST", "/clock", { reset: true }).then(renderClock).catch(showError)
|
|
1922
|
-
})
|
|
1923
|
-
$("clock-set").addEventListener("submit", (event) => {
|
|
1924
|
-
event.preventDefault()
|
|
1925
|
-
const raw = $("clock-instant").value.trim()
|
|
1926
|
-
const set = /^-?\\d+$/.test(raw) ? Number(raw) : raw
|
|
1927
|
-
api("POST", "/clock", { set }).then(renderClock).catch(showError)
|
|
1928
|
-
})
|
|
1929
|
-
|
|
1930
|
-
$("fault-form").addEventListener("submit", (event) => {
|
|
1931
|
-
event.preventDefault()
|
|
1932
|
-
const preset = $("fault-preset").value
|
|
1933
|
-
const body = preset
|
|
1934
|
-
? { preset }
|
|
1935
|
-
: {
|
|
1936
|
-
status: Number($("fault-status").value),
|
|
1937
|
-
...($("fault-path").value ? { pathPrefix: $("fault-path").value } : {}),
|
|
1938
|
-
...($("fault-method").value ? { method: $("fault-method").value } : {})
|
|
1939
|
-
}
|
|
1940
|
-
if (!preset && !Number.isFinite(body.status)) {
|
|
1941
|
-
showError(new Error("Status must be a number, or choose a preset"))
|
|
1942
|
-
return
|
|
1943
|
-
}
|
|
1944
|
-
api("POST", "/faults", body).then(renderFaults).catch(showError)
|
|
1945
|
-
})
|
|
1946
|
-
$("fault-clear").addEventListener("click", () => api("DELETE", "/faults").then(renderFaults).catch(showError))
|
|
1947
|
-
$("journal-clear").addEventListener("click", () => api("DELETE", "/requests").then(renderJournal).catch(showError))
|
|
1948
|
-
|
|
1949
|
-
$("route-list").addEventListener("click", (event) => {
|
|
1950
|
-
const button = event.target.closest("[data-route]")
|
|
1951
|
-
if (!button) return
|
|
1952
|
-
state.route = button.dataset.route
|
|
1953
|
-
$("route-title").textContent = state.route
|
|
1954
|
-
const method = state.route.split(" ")[0]
|
|
1955
|
-
$("route-body").disabled = method === "GET" || method === "DELETE"
|
|
1956
|
-
})
|
|
1957
|
-
$("route-form").addEventListener("submit", async (event) => {
|
|
1958
|
-
event.preventDefault()
|
|
1959
|
-
if (!state.route) return showError(new Error("Choose a route first"))
|
|
1960
|
-
const [method, path] = [state.route.split(" ")[0], state.route.split(" ").slice(1).join(" ")]
|
|
1961
|
-
const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_, key) => prompt("Value for " + key) || "")
|
|
1962
|
-
let body
|
|
1963
|
-
if (method !== "GET" && method !== "DELETE" && $("route-body").value.trim()) {
|
|
1964
|
-
try { body = JSON.parse($("route-body").value) }
|
|
1965
|
-
catch { return showError(new Error("Body is not JSON")) }
|
|
1966
|
-
}
|
|
1967
|
-
try {
|
|
1968
|
-
const data = await api(method, concrete, body)
|
|
1969
|
-
$("route-result").textContent = JSON.stringify(data, null, 2)
|
|
1970
|
-
} catch (error) {
|
|
1971
|
-
showError(error)
|
|
1972
|
-
}
|
|
1973
|
-
})
|
|
1974
|
-
|
|
1975
|
-
function mountPanels(panels) {
|
|
1976
|
-
const nav = $("nav")
|
|
1977
|
-
const views = $("views")
|
|
1978
|
-
for (const panel of panels) {
|
|
1979
|
-
const button = document.createElement("button")
|
|
1980
|
-
button.type = "button"
|
|
1981
|
-
button.dataset.view = panel.id
|
|
1982
|
-
button.textContent = panel.title
|
|
1983
|
-
nav.append(button)
|
|
1984
|
-
const section = document.createElement("section")
|
|
1985
|
-
section.className = "view"
|
|
1986
|
-
section.id = "view-" + panel.id
|
|
1987
|
-
section.hidden = true
|
|
1988
|
-
const title = document.createElement("h2")
|
|
1989
|
-
title.textContent = panel.title
|
|
1990
|
-
const lede = document.createElement("p")
|
|
1991
|
-
lede.className = "lede"
|
|
1992
|
-
lede.textContent = panel.description || ""
|
|
1993
|
-
const body = document.createElement("div")
|
|
1994
|
-
body.className = "panel-body"
|
|
1995
|
-
body.id = "panel-" + panel.id
|
|
1996
|
-
body.innerHTML = panel.html || ""
|
|
1997
|
-
section.append(title, lede, body)
|
|
1998
|
-
views.append(section)
|
|
1999
|
-
if (panel.script) {
|
|
2000
|
-
try {
|
|
2001
|
-
const run = new Function("root", "api", panel.script)
|
|
2002
|
-
run(body, panelApi)
|
|
2003
|
-
} catch (error) {
|
|
2004
|
-
const note = document.createElement("p")
|
|
2005
|
-
note.className = "banner show"
|
|
2006
|
-
note.textContent = error instanceof Error ? error.message : String(error)
|
|
2007
|
-
body.append(note)
|
|
2008
|
-
}
|
|
2009
|
-
}
|
|
2010
|
-
}
|
|
2011
|
-
}
|
|
2012
|
-
|
|
2013
|
-
loadNamespaces()
|
|
2014
|
-
.then(() => api("GET", "/ui/manifest"))
|
|
2015
|
-
.then((manifest) => { mountPanels((manifest && manifest.panels) || []); return refresh() })
|
|
2016
|
-
.catch(showError)
|
|
2017
|
-
})()
|
|
2018
|
-
</script>
|
|
2819
|
+
${script}
|
|
2019
2820
|
</body>
|
|
2020
2821
|
</html>`;
|
|
2021
2822
|
};
|
|
@@ -2032,10 +2833,10 @@ var json2 = (body) => new Response(JSON.stringify(body), {
|
|
|
2032
2833
|
});
|
|
2033
2834
|
var adminUiRoutes = (service, ui) => {
|
|
2034
2835
|
const shell = () => renderAdminDocument(service);
|
|
2035
|
-
const
|
|
2836
|
+
const document3 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
|
|
2036
2837
|
return {
|
|
2037
|
-
"GET /ui": () => htmlResponse(
|
|
2038
|
-
"GET /ui/": () => htmlResponse(
|
|
2838
|
+
"GET /ui": () => htmlResponse(document3()),
|
|
2839
|
+
"GET /ui/": () => htmlResponse(document3()),
|
|
2039
2840
|
"GET /ui/manifest": () => json2({
|
|
2040
2841
|
service,
|
|
2041
2842
|
panels: (ui?.panels ?? []).map((panel) => ({
|
|
@@ -2061,11 +2862,11 @@ var EXTENSION_KEYS = {
|
|
|
2061
2862
|
};
|
|
2062
2863
|
|
|
2063
2864
|
// ../../openapi/metadata/dist/read.js
|
|
2064
|
-
var
|
|
2865
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2065
2866
|
var extensionOf = (holder, key) => holder[key];
|
|
2066
2867
|
var operationMetadata = (operation) => {
|
|
2067
2868
|
const raw = extensionOf(operation, EXTENSION_KEYS.operation);
|
|
2068
|
-
const ext =
|
|
2869
|
+
const ext = isRecord3(raw) ? raw : {};
|
|
2069
2870
|
const supported = ext.supported ?? true;
|
|
2070
2871
|
const parity = ext.parity ?? {};
|
|
2071
2872
|
return {
|
|
@@ -2101,9 +2902,9 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
2101
2902
|
this.name = "OperationRegistryError";
|
|
2102
2903
|
}
|
|
2103
2904
|
};
|
|
2104
|
-
var verifyOperations = (
|
|
2905
|
+
var verifyOperations = (document3, handlers) => {
|
|
2105
2906
|
const problems = [];
|
|
2106
|
-
const operations = listOperations(
|
|
2907
|
+
const operations = listOperations(document3);
|
|
2107
2908
|
const seen = /* @__PURE__ */ new Set();
|
|
2108
2909
|
for (const operation of operations) {
|
|
2109
2910
|
if (seen.has(operation.operationId))
|
|
@@ -2219,7 +3020,7 @@ var SAMPLE = 25;
|
|
|
2219
3020
|
var WALK_LIMIT = 1e4;
|
|
2220
3021
|
var SKIP_CTORS = /* @__PURE__ */ new Set(["Hono", "HonoRequest", "Router"]);
|
|
2221
3022
|
var fail = (status, message) => ({ ok: false, status, message });
|
|
2222
|
-
var
|
|
3023
|
+
var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2223
3024
|
var kindOf = (value) => {
|
|
2224
3025
|
if (value === null)
|
|
2225
3026
|
return "null";
|
|
@@ -2318,7 +3119,7 @@ var absorb = (accum, value) => {
|
|
|
2318
3119
|
const kind = kindOf(value);
|
|
2319
3120
|
accum.kinds.add(kind);
|
|
2320
3121
|
accum.present++;
|
|
2321
|
-
if (!
|
|
3122
|
+
if (!isRecord4(value))
|
|
2322
3123
|
return;
|
|
2323
3124
|
for (const [key, child] of Object.entries(value)) {
|
|
2324
3125
|
const nested = accum.nested.get(key) ?? blankAccum(key);
|
|
@@ -2343,7 +3144,7 @@ var inferFields = (records) => {
|
|
|
2343
3144
|
if (records.length === 0)
|
|
2344
3145
|
return [];
|
|
2345
3146
|
const sample = records.slice(0, SAMPLE);
|
|
2346
|
-
const objects = sample.filter((record) =>
|
|
3147
|
+
const objects = sample.filter((record) => isRecord4(record.value));
|
|
2347
3148
|
if (objects.length === 0) {
|
|
2348
3149
|
const kinds = new Set(sample.map((record) => kindOf(record.value)));
|
|
2349
3150
|
const list = [...kinds];
|
|
@@ -2530,7 +3331,7 @@ var patchStateRecord = (scope, name, id, value) => {
|
|
|
2530
3331
|
const parsed = jsonValue(value);
|
|
2531
3332
|
if (!parsed.ok)
|
|
2532
3333
|
return parsed;
|
|
2533
|
-
const next =
|
|
3334
|
+
const next = isRecord4(current.value.value) && isRecord4(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
|
|
2534
3335
|
return replaceStateRecord(scope, name, id, next);
|
|
2535
3336
|
};
|
|
2536
3337
|
var deleteStateRecord = (scope, name, id) => {
|
|
@@ -2598,7 +3399,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
2598
3399
|
const scope = open(namespace);
|
|
2599
3400
|
if (!scope.ok)
|
|
2600
3401
|
return fromResult(scope, 201);
|
|
2601
|
-
if (!
|
|
3402
|
+
if (!isRecord4(body) || !("value" in body)) {
|
|
2602
3403
|
return adminError2(400, 'expected {"id?": "<id>", "value": <json>}');
|
|
2603
3404
|
}
|
|
2604
3405
|
const id = body.id;
|
|
@@ -2616,7 +3417,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
2616
3417
|
const scope = open(namespace);
|
|
2617
3418
|
if (!scope.ok)
|
|
2618
3419
|
return fromResult(scope, 200);
|
|
2619
|
-
if (!
|
|
3420
|
+
if (!isRecord4(body) || !("value" in body))
|
|
2620
3421
|
return adminError2(400, 'expected {"value": <json>}');
|
|
2621
3422
|
const replaced = replaceStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
|
|
2622
3423
|
if (replaced.ok)
|
|
@@ -2627,7 +3428,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
2627
3428
|
const scope = open(namespace);
|
|
2628
3429
|
if (!scope.ok)
|
|
2629
3430
|
return fromResult(scope, 200);
|
|
2630
|
-
if (!
|
|
3431
|
+
if (!isRecord4(body) || !("value" in body))
|
|
2631
3432
|
return adminError2(400, 'expected {"value": <json>}');
|
|
2632
3433
|
const patched = patchStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
|
|
2633
3434
|
if (patched.ok)
|
|
@@ -2647,7 +3448,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
2647
3448
|
};
|
|
2648
3449
|
|
|
2649
3450
|
// ../core/dist/version.js
|
|
2650
|
-
var PACKAGE_VERSION = true ? "1.
|
|
3451
|
+
var PACKAGE_VERSION = true ? "1.6.0" : UNRELEASED_VERSION;
|
|
2651
3452
|
|
|
2652
3453
|
// ../core/dist/signing.js
|
|
2653
3454
|
var encoder = new TextEncoder();
|
|
@@ -2663,9 +3464,9 @@ var fromBase64 = (value) => Uint8Array.from(atob(value), (char) => char.charCode
|
|
|
2663
3464
|
// ../core/dist/webhooks.js
|
|
2664
3465
|
var json4 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
2665
3466
|
var adminError3 = (status, message) => json4(status, { error: { type: "mockingbird_admin", message } });
|
|
2666
|
-
var
|
|
3467
|
+
var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2667
3468
|
var parseEndpoint = (value) => {
|
|
2668
|
-
if (!
|
|
3469
|
+
if (!isRecord5(value) || typeof value.url !== "string")
|
|
2669
3470
|
return "each endpoint needs a url";
|
|
2670
3471
|
try {
|
|
2671
3472
|
new URL(value.url);
|
|
@@ -2682,12 +3483,12 @@ var parseEndpoint = (value) => {
|
|
|
2682
3483
|
const events = value.events ?? value.enabledEvents;
|
|
2683
3484
|
if (Array.isArray(events))
|
|
2684
3485
|
endpoint.events = events.map(String);
|
|
2685
|
-
if (
|
|
3486
|
+
if (isRecord5(value.tags)) {
|
|
2686
3487
|
endpoint.tags = Object.fromEntries(Object.entries(value.tags).map(([k, v]) => [k, String(v)]));
|
|
2687
3488
|
}
|
|
2688
3489
|
if (typeof value.account === "string")
|
|
2689
3490
|
endpoint.tags = { ...endpoint.tags, account: value.account };
|
|
2690
|
-
if (
|
|
3491
|
+
if (isRecord5(value.headers)) {
|
|
2691
3492
|
endpoint.headers = Object.fromEntries(Object.entries(value.headers).map(([k, v]) => [k, String(v)]));
|
|
2692
3493
|
}
|
|
2693
3494
|
return endpoint;
|
|
@@ -2714,7 +3515,7 @@ var webhookAdminRoutes = (hub) => ({
|
|
|
2714
3515
|
return json4(200, { status: "ok" });
|
|
2715
3516
|
},
|
|
2716
3517
|
"POST /webhooks/faults": ({ body, namespace }) => {
|
|
2717
|
-
if (!
|
|
3518
|
+
if (!isRecord5(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
|
|
2718
3519
|
return adminError3(400, "mode must be duplicate, reorder or drop");
|
|
2719
3520
|
}
|
|
2720
3521
|
const fault = { mode: body.mode };
|
|
@@ -2730,7 +3531,7 @@ var webhookAdminRoutes = (hub) => ({
|
|
|
2730
3531
|
}))
|
|
2731
3532
|
}),
|
|
2732
3533
|
"PUT /webhook-endpoints": ({ body, namespace }) => {
|
|
2733
|
-
const list = Array.isArray(body) ? body :
|
|
3534
|
+
const list = Array.isArray(body) ? body : isRecord5(body) ? body.endpoints : void 0;
|
|
2734
3535
|
if (!Array.isArray(list))
|
|
2735
3536
|
return adminError3(400, "expected [{url, secret?, events?}]");
|
|
2736
3537
|
const parsed = [];
|
|
@@ -2800,8 +3601,8 @@ var DroppedConnectionError = class extends TypeError {
|
|
|
2800
3601
|
this.name = "TypeError";
|
|
2801
3602
|
}
|
|
2802
3603
|
};
|
|
2803
|
-
var operationMatcher = (
|
|
2804
|
-
const matchers = listOperations(
|
|
3604
|
+
var operationMatcher = (document3) => {
|
|
3605
|
+
const matchers = listOperations(document3).map((operation) => ({
|
|
2805
3606
|
operationId: operation.operationId,
|
|
2806
3607
|
method: operation.method.toUpperCase(),
|
|
2807
3608
|
pattern: new RegExp(`^${operation.path.split("/").map((segment) => segment.startsWith("{") ? "[^/]+" : segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("/")}/?$`),
|
|
@@ -2896,10 +3697,10 @@ var createRuntime = (options) => {
|
|
|
2896
3697
|
const ensureBranch = (namespace, branch2, at) => {
|
|
2897
3698
|
if (!BRANCH_PATTERN2.test(branch2))
|
|
2898
3699
|
throw new RangeError(`branch must match ${BRANCH_PATTERN2}`);
|
|
2899
|
-
const
|
|
3700
|
+
const history2 = timeline(namespace);
|
|
2900
3701
|
if (branch2 === "main") {
|
|
2901
3702
|
if (at !== void 0) {
|
|
2902
|
-
const point =
|
|
3703
|
+
const point = history2.checkout("main", at);
|
|
2903
3704
|
restoreNamespace(sqlite, storageNamespace(namespace), point.value.snapshot);
|
|
2904
3705
|
captured.set(namespace, point.value.snapshot);
|
|
2905
3706
|
rng.setState(point.value.rngState);
|
|
@@ -2912,10 +3713,10 @@ var createRuntime = (options) => {
|
|
|
2912
3713
|
return namespace;
|
|
2913
3714
|
}
|
|
2914
3715
|
const storage = physicalBranch(namespace, branch2);
|
|
2915
|
-
if (!
|
|
3716
|
+
if (!history2.hasBranch(branch2)) {
|
|
2916
3717
|
if (at === void 0)
|
|
2917
|
-
|
|
2918
|
-
const point =
|
|
3718
|
+
history2.commit(capture(namespace));
|
|
3719
|
+
const point = history2.fork(branch2, at === void 0 ? {} : { from: at });
|
|
2919
3720
|
const branchRng = createRng(options.seed ?? 0);
|
|
2920
3721
|
if (point)
|
|
2921
3722
|
branchRng.setState(point.value.rngState);
|
|
@@ -2924,8 +3725,8 @@ var createRuntime = (options) => {
|
|
|
2924
3725
|
restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
|
|
2925
3726
|
if (point)
|
|
2926
3727
|
captured.set(storage, point.value.snapshot);
|
|
2927
|
-
} else if (at !== void 0 &&
|
|
2928
|
-
const point =
|
|
3728
|
+
} else if (at !== void 0 && history2.head(branch2)?.id !== at) {
|
|
3729
|
+
const point = history2.checkout(branch2, at);
|
|
2929
3730
|
if (!instances.has(storage)) {
|
|
2930
3731
|
const branchRng = createRng(options.seed ?? 0);
|
|
2931
3732
|
branchRng.setState(point.value.rngState);
|
|
@@ -2936,7 +3737,7 @@ var createRuntime = (options) => {
|
|
|
2936
3737
|
captured.set(storage, point.value.snapshot);
|
|
2937
3738
|
} else {
|
|
2938
3739
|
if (!instances.has(storage)) {
|
|
2939
|
-
const point =
|
|
3740
|
+
const point = history2.head(branch2);
|
|
2940
3741
|
const branchRng = createRng(options.seed ?? 0);
|
|
2941
3742
|
if (point)
|
|
2942
3743
|
branchRng.setState(point.value.rngState);
|
|
@@ -2960,8 +3761,8 @@ var createRuntime = (options) => {
|
|
|
2960
3761
|
const checkout = (checkpointId, checkoutOptions = {}) => {
|
|
2961
3762
|
const namespace = checkoutOptions.namespace ?? DEFAULT_NAMESPACE;
|
|
2962
3763
|
const branchName = checkoutOptions.branch ?? "main";
|
|
2963
|
-
const
|
|
2964
|
-
const point =
|
|
3764
|
+
const history2 = timeline(namespace);
|
|
3765
|
+
const point = history2.checkout(branchName, checkpointId);
|
|
2965
3766
|
const storage = ensureBranch(namespace, branchName);
|
|
2966
3767
|
restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
|
|
2967
3768
|
captured.set(storage, point.value.snapshot);
|
|
@@ -3011,9 +3812,9 @@ var createRuntime = (options) => {
|
|
|
3011
3812
|
instance(name);
|
|
3012
3813
|
restoreNamespace(sqlite, storageNamespace(name), from);
|
|
3013
3814
|
captured.set(name, from);
|
|
3014
|
-
const
|
|
3015
|
-
if (
|
|
3016
|
-
|
|
3815
|
+
const history2 = timelines.get(name);
|
|
3816
|
+
if (history2)
|
|
3817
|
+
history2.commit(capture(name), { branch: "main" });
|
|
3017
3818
|
else
|
|
3018
3819
|
timeline(name);
|
|
3019
3820
|
};
|
|
@@ -3242,10 +4043,10 @@ var createRuntime = (options) => {
|
|
|
3242
4043
|
},
|
|
3243
4044
|
release: (name, checkpointId) => timeline(name).release(checkpointId),
|
|
3244
4045
|
inspect: (name) => {
|
|
3245
|
-
const
|
|
4046
|
+
const history2 = timeline(name);
|
|
3246
4047
|
return {
|
|
3247
|
-
branches:
|
|
3248
|
-
checkpoints:
|
|
4048
|
+
branches: history2.branches(),
|
|
4049
|
+
checkpoints: history2.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
|
|
3249
4050
|
id,
|
|
3250
4051
|
branch: branchName,
|
|
3251
4052
|
parent,
|
|
@@ -3260,11 +4061,11 @@ var createRuntime = (options) => {
|
|
|
3260
4061
|
} : {},
|
|
3261
4062
|
routes: {
|
|
3262
4063
|
...credentialRoutes(credentials),
|
|
3263
|
-
...options.presets ? presetRoutes(options.presets, runtime) : {},
|
|
3264
4064
|
...options.webhooks ? webhookAdminRoutes(options.webhooks) : {},
|
|
3265
4065
|
...options.admin?.(runtime) ?? {},
|
|
3266
|
-
//
|
|
3267
|
-
//
|
|
4066
|
+
// Shell routes win a colliding key. Fault presets are one of them: the admin page
|
|
4067
|
+
// maps `presets`, and a service route must not replace that list with a record.
|
|
4068
|
+
...options.presets ? presetRoutes(options.presets, runtime) : {},
|
|
3268
4069
|
...stateAdminRoutes({
|
|
3269
4070
|
open: stateScope,
|
|
3270
4071
|
afterWrite: (namespace) => {
|
|
@@ -3339,9 +4140,12 @@ var credentialRoutes = (registry) => ({
|
|
|
3339
4140
|
}
|
|
3340
4141
|
});
|
|
3341
4142
|
var presetRoutes = (presets, runtime) => ({
|
|
3342
|
-
"GET /faults/presets": () =>
|
|
3343
|
-
|
|
3344
|
-
|
|
4143
|
+
"GET /faults/presets": () => {
|
|
4144
|
+
const body = {
|
|
4145
|
+
presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
|
|
4146
|
+
};
|
|
4147
|
+
return adminJson(200, body);
|
|
4148
|
+
},
|
|
3345
4149
|
"POST /faults/presets/:name": ({ params, body, namespace }) => {
|
|
3346
4150
|
const name = params.name;
|
|
3347
4151
|
if (!presets[name])
|
|
@@ -3352,7 +4156,7 @@ var presetRoutes = (presets, runtime) => ({
|
|
|
3352
4156
|
});
|
|
3353
4157
|
|
|
3354
4158
|
// src/generated/openapi.ts
|
|
3355
|
-
var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Google Places, Geocoding and Maps JavaScript API (Mockingbird subset)","description":"The Google Maps Platform surface our member app calls: Places Autocomplete, Place Details\\nand Find Place From Text (the legacy JSON web services), the Geocoding API, and the Maps\\nJavaScript API loader (\`/maps/api/js?libraries=places\`). Every web-service answer is HTTP 200\\nwith Google's \`status\` field; the mock resolves addresses from a QA corpus.\\n","version":"1","x-mockingbird-upstream":{"note":"Hand-authored from Google's documented response shapes and the fields our consumer reads (address-autocomplete-native-rest.tsx, address-autocomplete-web.tsx, use-geocoded-address.ts). Google publishes no OpenAPI document for these endpoints."}},"servers":[{"url":"https://maps.googleapis.com"}],"paths":{"/maps/api/place/autocomplete/json":{"get":{"operationId":"PlaceAutocomplete","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Key"},{"name":"input","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":60,"examples":["1625 N Central","Phoenix","85004","4821 Maple Street Denver","200 N Spring St, Los Angeles","136 Murray"]}},{"name":"types","in":"query","schema":{"type":"string","enum":["address","geocode"]}},{"name":"components","in":"query","schema":{"type":"string","enum":["country:us","country:ca","country:us|country:ca"]}},{"$ref":"#/components/parameters/SessionToken"}],"responses":{"200":{"description":"Predictions, or a non-OK status (HTTP 200 either way)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutocompleteResponse"}}}},"500":{"description":"Google front-end failure (server_error preset)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/maps/api/place/details/json":{"get":{"operationId":"PlaceDetails","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Key"},{"name":"place_id","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":200,"examples":["ChIJ5BKAwJuCB5951g03ZzrkHx9","ChIJE1ellR5XyWiy2lHIlJ5cls3","ChIJaWYsCINV1XBQQ97jO3pznpg","EieyJsIjoiNDgyMSBNYXBsZSBTdHJlZXQiLCJjIjoiRGVudmVyIiwicyI6IkNPIiwieiI6IjgwMjAyIiwibiI6IkRlbnZlciBDb3VudHkiLCJhIjozOS43NDc4OCwibyI6LTEwNC45OTQyNX0","ChIJdoesNotExist000000000"]}},{"name":"fields","in":"query","schema":{"type":"string","enum":["address_component","address_components","geometry","formatted_address,geometry","place_id,name,types","address_component,geometry,formatted_address"]}},{"$ref":"#/components/parameters/SessionToken"}],"responses":{"200":{"description":"The place, or a non-OK status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailsResponse"}}}},"500":{"description":"Google front-end failure (server_error preset)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/maps/api/geocode/json":{"get":{"operationId":"Geocode","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Key"},{"name":"address","in":"query","schema":{"type":"string","minLength":1,"maxLength":80,"examples":["1625 N Central Ave, Phoenix, AZ 85004","88 Greenwich St, New York, NY 10006","4821 Maple Street, Denver, CO 80202","85004","Phoenix, AZ","1625 N Central Ave"]}},{"name":"place_id","in":"query","schema":{"type":"string","examples":["ChIJ5BKAwJuCB5951g03ZzrkHx9"]}},{"name":"components","in":"query","schema":{"type":"string","enum":["postal_code:85004","postal_code:10006","postal_code:00000"]}}],"responses":{"200":{"description":"Geocoding results, or a non-OK status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeocodeResponse"}}}},"500":{"description":"Google front-end failure (server_error preset)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/maps/api/place/findplacefromtext/json":{"get":{"operationId":"FindPlaceFromText","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Key"},{"name":"input","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":80,"examples":["1625 N Central Ave, Phoenix, AZ 85004","1625 N Central","Gilbert"]}},{"name":"inputtype","in":"query","required":true,"schema":{"type":"string","enum":["textquery","phonenumber"]}},{"name":"fields","in":"query","schema":{"type":"string","enum":["geometry","formatted_address,geometry","place_id,name","place_id"]}}],"responses":{"200":{"description":"Candidates, or a non-OK status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindPlaceResponse"}}}},"500":{"description":"Google front-end failure (server_error preset)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/maps/api/js":{"get":{"operationId":"MapsJavaScriptApi","description":"The Maps JavaScript API loader. The mock serves a shim defining google.maps.places (AutocompleteService, PlacesService, AutocompleteSessionToken, PlacesServiceStatus) and google.maps.Geocoder, backed by the REST operations above.","x-mockingbird":{"supported":true,"parity":{"enabled":false,"reason":"A JavaScript program, not data: it is exercised by evaluating it in a fake window and driving our web client through it (google-maps.acceptance.test.ts)."}},"parameters":[{"name":"key","in":"query","schema":{"type":"string"}},{"name":"libraries","in":"query","schema":{"type":"string"}},{"name":"callback","in":"query","schema":{"type":"string"}},{"name":"v","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The Maps JavaScript shim","content":{"text/javascript":{"schema":{"type":"string"}}}},"503":{"description":"Script unavailable (script_unavailable preset)","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/v1:validateAddress":{"servers":[{"url":"https://addressvalidation.googleapis.com"}],"post":{"operationId":"ValidateAddress","description":"Address Validation API v1 validateAddress, with USPS CASS data (\`enableUspsCass\`). A Google Cloud API: errors are HTTP 4xx/5xx with a \`google.rpc.Status\` envelope, and the key may also be sent as the \`X-Goog-Api-Key\` header. Responses are proto3 JSON (false booleans and empty lists omitted). Reference: https://developers.google.com/maps/documentation/address-validation/reference/rest/v1/TopLevel/validateAddress","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Key"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateAddressRequest"}}}},"responses":{"200":{"description":"The validation result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateAddressResponse"}}}},"400":{"description":"INVALID_ARGUMENT (malformed request, unsupported region, invalid API key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleApiError"}}}},"403":{"description":"PERMISSION_DENIED (missing key; address_validation_denied preset)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleApiError"}}}},"503":{"description":"UNAVAILABLE (address_validation_unavailable preset)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleApiError"}}}}}}}},"components":{"parameters":{"Key":{"name":"key","in":"query","required":true,"description":"The Google Maps Platform API key (\`PLACES_KEY\`); also the namespace credential.","schema":{"type":"string","minLength":1,"maxLength":40,"examples":["parity-key"]}},"SessionToken":{"name":"sessiontoken","in":"query","description":"Autocomplete session token (billing grouping); accepted and journaled as metadata.","schema":{"type":"string","format":"uuid"}}},"schemas":{"Status":{"type":"string","enum":["OK","ZERO_RESULTS","INVALID_REQUEST","OVER_QUERY_LIMIT","REQUEST_DENIED","UNKNOWN_ERROR","NOT_FOUND"]},"LatLng":{"type":"object","required":["lat","lng"],"properties":{"lat":{"type":"number"},"lng":{"type":"number"}}},"Viewport":{"type":"object","required":["northeast","southwest"],"properties":{"northeast":{"$ref":"#/components/schemas/LatLng"},"southwest":{"$ref":"#/components/schemas/LatLng"}}},"Geometry":{"type":"object","required":["location","viewport"],"properties":{"location":{"$ref":"#/components/schemas/LatLng"},"location_type":{"type":"string","enum":["ROOFTOP","RANGE_INTERPOLATED","GEOMETRIC_CENTER","APPROXIMATE"]},"viewport":{"$ref":"#/components/schemas/Viewport"}}},"AddressComponent":{"type":"object","required":["long_name","short_name","types"],"properties":{"long_name":{"type":"string"},"short_name":{"type":"string"},"types":{"type":"array","items":{"type":"string"}}}},"MatchedSubstring":{"type":"object","required":["length","offset"],"properties":{"length":{"type":"integer"},"offset":{"type":"integer"}}},"Prediction":{"type":"object","required":["description","place_id","reference","structured_formatting","terms","types","matched_substrings"],"properties":{"description":{"type":"string"},"matched_substrings":{"type":"array","items":{"$ref":"#/components/schemas/MatchedSubstring"}},"place_id":{"type":"string","x-mockingbird-resource":{"type":"place","identity":true}},"reference":{"type":"string"},"structured_formatting":{"type":"object","required":["main_text","secondary_text"],"properties":{"main_text":{"type":"string"},"main_text_matched_substrings":{"type":"array","items":{"$ref":"#/components/schemas/MatchedSubstring"}},"secondary_text":{"type":"string"}}},"terms":{"type":"array","items":{"type":"object","required":["offset","value"],"properties":{"offset":{"type":"integer"},"value":{"type":"string"}}}},"types":{"type":"array","items":{"type":"string"}}}},"PlaceResult":{"type":"object","description":"Only the fields requested with \`fields=\` are present.","properties":{"address_components":{"type":"array","items":{"$ref":"#/components/schemas/AddressComponent"}},"adr_address":{"type":"string"},"formatted_address":{"type":"string"},"geometry":{"$ref":"#/components/schemas/Geometry"},"name":{"type":"string"},"place_id":{"type":"string"},"reference":{"type":"string"},"types":{"type":"array","items":{"type":"string"}},"url":{"type":"string"},"utc_offset":{"type":"integer"},"vicinity":{"type":"string"}}},"AutocompleteResponse":{"type":"object","required":["predictions","status"],"properties":{"predictions":{"type":"array","items":{"$ref":"#/components/schemas/Prediction"}},"status":{"$ref":"#/components/schemas/Status"},"error_message":{"type":"string"}}},"DetailsResponse":{"type":"object","required":["html_attributions","status"],"properties":{"html_attributions":{"type":"array","items":{"type":"string"}},"result":{"$ref":"#/components/schemas/PlaceResult"},"status":{"$ref":"#/components/schemas/Status"},"error_message":{"type":"string"}}},"GeocodeResult":{"type":"object","required":["address_components","formatted_address","geometry","place_id","types"],"properties":{"address_components":{"type":"array","items":{"$ref":"#/components/schemas/AddressComponent"}},"formatted_address":{"type":"string"},"geometry":{"$ref":"#/components/schemas/Geometry"},"place_id":{"type":"string"},"types":{"type":"array","items":{"type":"string"}}}},"GeocodeResponse":{"type":"object","required":["results","status"],"properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/GeocodeResult"}},"status":{"$ref":"#/components/schemas/Status"},"error_message":{"type":"string"}}},"FindPlaceResponse":{"type":"object","required":["candidates","status"],"properties":{"candidates":{"type":"array","items":{"$ref":"#/components/schemas/PlaceResult"}},"status":{"$ref":"#/components/schemas/Status"},"error_message":{"type":"string"}}},"GoogleApiError":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","status"],"properties":{"code":{"type":"integer"},"message":{"type":"string"},"status":{"type":"string"},"details":{"type":"array","items":{"type":"object"}}}}}},"PostalAddress":{"type":"object","description":"google.type.PostalAddress","properties":{"revision":{"type":"integer"},"regionCode":{"type":"string","examples":["US"]},"languageCode":{"type":"string"},"postalCode":{"type":"string","examples":["85004","10006","80202","90012","00000"]},"sortingCode":{"type":"string"},"administrativeArea":{"type":"string","examples":["AZ","NY","CO","CA"]},"locality":{"type":"string","examples":["Phoenix","New York","Denver","Los Angeles","Springfield"]},"sublocality":{"type":"string"},"addressLines":{"type":"array","maxItems":3,"items":{"type":"string","maxLength":80,"examples":["1625 N Central Ave","1625 North Central Avenue Building A","4821 Maple Street","N Central Ave","Apt 2"]}},"recipients":{"type":"array","items":{"type":"string"}},"organization":{"type":"string"}}},"ValidateAddressRequest":{"type":"object","required":["address"],"additionalProperties":false,"properties":{"address":{"$ref":"#/components/schemas/PostalAddress","examples":[{"regionCode":"US","addressLines":["1625 N Central Ave"],"locality":"Phoenix","administrativeArea":"AZ","postalCode":"85004"},{"regionCode":"US","addressLines":["4821 Maple Street"],"locality":"Denver","administrativeArea":"CO","postalCode":"80202"},{"regionCode":"US","addressLines":["N Central Ave"],"locality":"Phoenix","administrativeArea":"AZ","postalCode":"85004"},{"regionCode":"US","addressLines":["1625 N Central Ave, Phoenix, AZ 85004"]},{"regionCode":"US","addressLines":["12 Nowhere Rd"],"locality":"Springfield","administrativeArea":"ZZ","postalCode":"00000"}]},"previousResponseId":{"type":"string"},"enableUspsCass":{"type":"boolean"},"languageOptions":{"type":"object"},"sessionToken":{"type":"string"}}},"ValidationAddressComponent":{"type":"object","required":["componentName","componentType","confirmationLevel"],"properties":{"componentName":{"type":"object","required":["text"],"properties":{"text":{"type":"string"},"languageCode":{"type":"string"}}},"componentType":{"type":"string"},"confirmationLevel":{"type":"string","enum":["CONFIRMED","UNCONFIRMED_BUT_PLAUSIBLE","UNCONFIRMED_AND_SUSPICIOUS"]},"inferred":{"type":"boolean"},"spellCorrected":{"type":"boolean"},"replaced":{"type":"boolean"},"unexpected":{"type":"boolean"}}},"Granularity":{"type":"string","enum":["GRANULARITY_UNSPECIFIED","SUB_PREMISE","PREMISE","PREMISE_PROXIMITY","BLOCK","ROUTE","OTHER"]},"ValidationResult":{"type":"object","properties":{"verdict":{"type":"object","properties":{"inputGranularity":{"$ref":"#/components/schemas/Granularity"},"validationGranularity":{"$ref":"#/components/schemas/Granularity"},"geocodeGranularity":{"$ref":"#/components/schemas/Granularity"},"addressComplete":{"type":"boolean"},"hasUnconfirmedComponents":{"type":"boolean"},"hasInferredComponents":{"type":"boolean"},"hasReplacedComponents":{"type":"boolean"},"hasSpellCorrectedComponents":{"type":"boolean"},"possibleNextAction":{"type":"string","enum":["POSSIBLE_NEXT_ACTION_UNSPECIFIED","FIX","CONFIRM_ADD_SUBPREMISES","CONFIRM","ACCEPT"]}}},"address":{"type":"object","properties":{"formattedAddress":{"type":"string"},"postalAddress":{"$ref":"#/components/schemas/PostalAddress"},"addressComponents":{"type":"array","items":{"$ref":"#/components/schemas/ValidationAddressComponent"}},"missingComponentTypes":{"type":"array","items":{"type":"string"}},"unconfirmedComponentTypes":{"type":"array","items":{"type":"string"}},"unresolvedTokens":{"type":"array","items":{"type":"string"}}}},"geocode":{"type":"object","properties":{"location":{"type":"object","properties":{"latitude":{"type":"number"},"longitude":{"type":"number"}}},"placeId":{"type":"string"},"placeTypes":{"type":"array","items":{"type":"string"}}}},"uspsData":{"type":"object","properties":{"standardizedAddress":{"type":"object","properties":{"firstAddressLine":{"type":"string"},"secondAddressLine":{"type":"string"},"cityStateZipAddressLine":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"zipCode":{"type":"string"},"zipCodeExtension":{"type":"string"}}},"dpvConfirmation":{"type":"string","enum":["Y","N","S","D"]},"dpvFootnote":{"type":"string"},"postOfficeCity":{"type":"string"},"postOfficeState":{"type":"string"},"errorMessage":{"type":"string"},"cassProcessed":{"type":"boolean"}}}}},"ValidateAddressResponse":{"type":"object","required":["result","responseId"],"properties":{"result":{"$ref":"#/components/schemas/ValidationResult"},"responseId":{"type":"string"}}}}}}`);
|
|
4159
|
+
var document2 = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Google Places, Geocoding and Maps JavaScript API (Mockingbird subset)","description":"The Google Maps Platform surface our member app calls: Places Autocomplete, Place Details\\nand Find Place From Text (the legacy JSON web services), the Geocoding API, and the Maps\\nJavaScript API loader (\`/maps/api/js?libraries=places\`). Every web-service answer is HTTP 200\\nwith Google's \`status\` field; the mock resolves addresses from a QA corpus.\\n","version":"1","x-mockingbird-upstream":{"note":"Hand-authored from Google's documented response shapes and the fields our consumer reads (address-autocomplete-native-rest.tsx, address-autocomplete-web.tsx, use-geocoded-address.ts). Google publishes no OpenAPI document for these endpoints."}},"servers":[{"url":"https://maps.googleapis.com"}],"paths":{"/maps/api/place/autocomplete/json":{"get":{"operationId":"PlaceAutocomplete","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Key"},{"name":"input","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":60,"examples":["1625 N Central","Phoenix","85004","4821 Maple Street Denver","200 N Spring St, Los Angeles","136 Murray"]}},{"name":"types","in":"query","schema":{"type":"string","enum":["address","geocode"]}},{"name":"components","in":"query","schema":{"type":"string","enum":["country:us","country:ca","country:us|country:ca"]}},{"$ref":"#/components/parameters/SessionToken"}],"responses":{"200":{"description":"Predictions, or a non-OK status (HTTP 200 either way)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutocompleteResponse"}}}},"500":{"description":"Google front-end failure (server_error preset)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/maps/api/place/details/json":{"get":{"operationId":"PlaceDetails","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Key"},{"name":"place_id","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":200,"examples":["ChIJ5BKAwJuCB5951g03ZzrkHx9","ChIJE1ellR5XyWiy2lHIlJ5cls3","ChIJaWYsCINV1XBQQ97jO3pznpg","EieyJsIjoiNDgyMSBNYXBsZSBTdHJlZXQiLCJjIjoiRGVudmVyIiwicyI6IkNPIiwieiI6IjgwMjAyIiwibiI6IkRlbnZlciBDb3VudHkiLCJhIjozOS43NDc4OCwibyI6LTEwNC45OTQyNX0","ChIJdoesNotExist000000000"]}},{"name":"fields","in":"query","schema":{"type":"string","enum":["address_component","address_components","geometry","formatted_address,geometry","place_id,name,types","address_component,geometry,formatted_address"]}},{"$ref":"#/components/parameters/SessionToken"}],"responses":{"200":{"description":"The place, or a non-OK status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DetailsResponse"}}}},"500":{"description":"Google front-end failure (server_error preset)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/maps/api/geocode/json":{"get":{"operationId":"Geocode","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Key"},{"name":"address","in":"query","schema":{"type":"string","minLength":1,"maxLength":80,"examples":["1625 N Central Ave, Phoenix, AZ 85004","88 Greenwich St, New York, NY 10006","4821 Maple Street, Denver, CO 80202","85004","Phoenix, AZ","1625 N Central Ave"]}},{"name":"place_id","in":"query","schema":{"type":"string","examples":["ChIJ5BKAwJuCB5951g03ZzrkHx9"]}},{"name":"components","in":"query","schema":{"type":"string","enum":["postal_code:85004","postal_code:10006","postal_code:00000"]}}],"responses":{"200":{"description":"Geocoding results, or a non-OK status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeocodeResponse"}}}},"500":{"description":"Google front-end failure (server_error preset)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/maps/api/place/findplacefromtext/json":{"get":{"operationId":"FindPlaceFromText","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Key"},{"name":"input","in":"query","required":true,"schema":{"type":"string","minLength":1,"maxLength":80,"examples":["1625 N Central Ave, Phoenix, AZ 85004","1625 N Central","Gilbert"]}},{"name":"inputtype","in":"query","required":true,"schema":{"type":"string","enum":["textquery","phonenumber"]}},{"name":"fields","in":"query","schema":{"type":"string","enum":["geometry","formatted_address,geometry","place_id,name","place_id"]}}],"responses":{"200":{"description":"Candidates, or a non-OK status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindPlaceResponse"}}}},"500":{"description":"Google front-end failure (server_error preset)","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/maps/api/js":{"get":{"operationId":"MapsJavaScriptApi","description":"The Maps JavaScript API loader. The mock serves a shim defining google.maps.places (AutocompleteService, PlacesService, AutocompleteSessionToken, PlacesServiceStatus) and google.maps.Geocoder, backed by the REST operations above.","x-mockingbird":{"supported":true,"parity":{"enabled":false,"reason":"A JavaScript program, not data: it is exercised by evaluating it in a fake window and driving our web client through it (google-maps.acceptance.test.ts)."}},"parameters":[{"name":"key","in":"query","schema":{"type":"string"}},{"name":"libraries","in":"query","schema":{"type":"string"}},{"name":"callback","in":"query","schema":{"type":"string"}},{"name":"v","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"The Maps JavaScript shim","content":{"text/javascript":{"schema":{"type":"string"}}}},"503":{"description":"Script unavailable (script_unavailable preset)","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/v1:validateAddress":{"servers":[{"url":"https://addressvalidation.googleapis.com"}],"post":{"operationId":"ValidateAddress","description":"Address Validation API v1 validateAddress, with USPS CASS data (\`enableUspsCass\`). A Google Cloud API: errors are HTTP 4xx/5xx with a \`google.rpc.Status\` envelope, and the key may also be sent as the \`X-Goog-Api-Key\` header. Responses are proto3 JSON (false booleans and empty lists omitted). Reference: https://developers.google.com/maps/documentation/address-validation/reference/rest/v1/TopLevel/validateAddress","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"$ref":"#/components/parameters/Key"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateAddressRequest"}}}},"responses":{"200":{"description":"The validation result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateAddressResponse"}}}},"400":{"description":"INVALID_ARGUMENT (malformed request, unsupported region, invalid API key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleApiError"}}}},"403":{"description":"PERMISSION_DENIED (missing key; address_validation_denied preset)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleApiError"}}}},"503":{"description":"UNAVAILABLE (address_validation_unavailable preset)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoogleApiError"}}}}}}}},"components":{"parameters":{"Key":{"name":"key","in":"query","required":true,"description":"The Google Maps Platform API key (\`PLACES_KEY\`); also the namespace credential.","schema":{"type":"string","minLength":1,"maxLength":40,"examples":["parity-key"]}},"SessionToken":{"name":"sessiontoken","in":"query","description":"Autocomplete session token (billing grouping); accepted and journaled as metadata.","schema":{"type":"string","format":"uuid"}}},"schemas":{"Status":{"type":"string","enum":["OK","ZERO_RESULTS","INVALID_REQUEST","OVER_QUERY_LIMIT","REQUEST_DENIED","UNKNOWN_ERROR","NOT_FOUND"]},"LatLng":{"type":"object","required":["lat","lng"],"properties":{"lat":{"type":"number"},"lng":{"type":"number"}}},"Viewport":{"type":"object","required":["northeast","southwest"],"properties":{"northeast":{"$ref":"#/components/schemas/LatLng"},"southwest":{"$ref":"#/components/schemas/LatLng"}}},"Geometry":{"type":"object","required":["location","viewport"],"properties":{"location":{"$ref":"#/components/schemas/LatLng"},"location_type":{"type":"string","enum":["ROOFTOP","RANGE_INTERPOLATED","GEOMETRIC_CENTER","APPROXIMATE"]},"viewport":{"$ref":"#/components/schemas/Viewport"}}},"AddressComponent":{"type":"object","required":["long_name","short_name","types"],"properties":{"long_name":{"type":"string"},"short_name":{"type":"string"},"types":{"type":"array","items":{"type":"string"}}}},"MatchedSubstring":{"type":"object","required":["length","offset"],"properties":{"length":{"type":"integer"},"offset":{"type":"integer"}}},"Prediction":{"type":"object","required":["description","place_id","reference","structured_formatting","terms","types","matched_substrings"],"properties":{"description":{"type":"string"},"matched_substrings":{"type":"array","items":{"$ref":"#/components/schemas/MatchedSubstring"}},"place_id":{"type":"string","x-mockingbird-resource":{"type":"place","identity":true}},"reference":{"type":"string"},"structured_formatting":{"type":"object","required":["main_text","secondary_text"],"properties":{"main_text":{"type":"string"},"main_text_matched_substrings":{"type":"array","items":{"$ref":"#/components/schemas/MatchedSubstring"}},"secondary_text":{"type":"string"}}},"terms":{"type":"array","items":{"type":"object","required":["offset","value"],"properties":{"offset":{"type":"integer"},"value":{"type":"string"}}}},"types":{"type":"array","items":{"type":"string"}}}},"PlaceResult":{"type":"object","description":"Only the fields requested with \`fields=\` are present.","properties":{"address_components":{"type":"array","items":{"$ref":"#/components/schemas/AddressComponent"}},"adr_address":{"type":"string"},"formatted_address":{"type":"string"},"geometry":{"$ref":"#/components/schemas/Geometry"},"name":{"type":"string"},"place_id":{"type":"string"},"reference":{"type":"string"},"types":{"type":"array","items":{"type":"string"}},"url":{"type":"string"},"utc_offset":{"type":"integer"},"vicinity":{"type":"string"}}},"AutocompleteResponse":{"type":"object","required":["predictions","status"],"properties":{"predictions":{"type":"array","items":{"$ref":"#/components/schemas/Prediction"}},"status":{"$ref":"#/components/schemas/Status"},"error_message":{"type":"string"}}},"DetailsResponse":{"type":"object","required":["html_attributions","status"],"properties":{"html_attributions":{"type":"array","items":{"type":"string"}},"result":{"$ref":"#/components/schemas/PlaceResult"},"status":{"$ref":"#/components/schemas/Status"},"error_message":{"type":"string"}}},"GeocodeResult":{"type":"object","required":["address_components","formatted_address","geometry","place_id","types"],"properties":{"address_components":{"type":"array","items":{"$ref":"#/components/schemas/AddressComponent"}},"formatted_address":{"type":"string"},"geometry":{"$ref":"#/components/schemas/Geometry"},"place_id":{"type":"string"},"types":{"type":"array","items":{"type":"string"}}}},"GeocodeResponse":{"type":"object","required":["results","status"],"properties":{"results":{"type":"array","items":{"$ref":"#/components/schemas/GeocodeResult"}},"status":{"$ref":"#/components/schemas/Status"},"error_message":{"type":"string"}}},"FindPlaceResponse":{"type":"object","required":["candidates","status"],"properties":{"candidates":{"type":"array","items":{"$ref":"#/components/schemas/PlaceResult"}},"status":{"$ref":"#/components/schemas/Status"},"error_message":{"type":"string"}}},"GoogleApiError":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message","status"],"properties":{"code":{"type":"integer"},"message":{"type":"string"},"status":{"type":"string"},"details":{"type":"array","items":{"type":"object"}}}}}},"PostalAddress":{"type":"object","description":"google.type.PostalAddress","properties":{"revision":{"type":"integer"},"regionCode":{"type":"string","examples":["US"]},"languageCode":{"type":"string"},"postalCode":{"type":"string","examples":["85004","10006","80202","90012","00000"]},"sortingCode":{"type":"string"},"administrativeArea":{"type":"string","examples":["AZ","NY","CO","CA"]},"locality":{"type":"string","examples":["Phoenix","New York","Denver","Los Angeles","Springfield"]},"sublocality":{"type":"string"},"addressLines":{"type":"array","maxItems":3,"items":{"type":"string","maxLength":80,"examples":["1625 N Central Ave","1625 North Central Avenue Building A","4821 Maple Street","N Central Ave","Apt 2"]}},"recipients":{"type":"array","items":{"type":"string"}},"organization":{"type":"string"}}},"ValidateAddressRequest":{"type":"object","required":["address"],"additionalProperties":false,"properties":{"address":{"$ref":"#/components/schemas/PostalAddress","examples":[{"regionCode":"US","addressLines":["1625 N Central Ave"],"locality":"Phoenix","administrativeArea":"AZ","postalCode":"85004"},{"regionCode":"US","addressLines":["4821 Maple Street"],"locality":"Denver","administrativeArea":"CO","postalCode":"80202"},{"regionCode":"US","addressLines":["N Central Ave"],"locality":"Phoenix","administrativeArea":"AZ","postalCode":"85004"},{"regionCode":"US","addressLines":["1625 N Central Ave, Phoenix, AZ 85004"]},{"regionCode":"US","addressLines":["12 Nowhere Rd"],"locality":"Springfield","administrativeArea":"ZZ","postalCode":"00000"}]},"previousResponseId":{"type":"string"},"enableUspsCass":{"type":"boolean"},"languageOptions":{"type":"object"},"sessionToken":{"type":"string"}}},"ValidationAddressComponent":{"type":"object","required":["componentName","componentType","confirmationLevel"],"properties":{"componentName":{"type":"object","required":["text"],"properties":{"text":{"type":"string"},"languageCode":{"type":"string"}}},"componentType":{"type":"string"},"confirmationLevel":{"type":"string","enum":["CONFIRMED","UNCONFIRMED_BUT_PLAUSIBLE","UNCONFIRMED_AND_SUSPICIOUS"]},"inferred":{"type":"boolean"},"spellCorrected":{"type":"boolean"},"replaced":{"type":"boolean"},"unexpected":{"type":"boolean"}}},"Granularity":{"type":"string","enum":["GRANULARITY_UNSPECIFIED","SUB_PREMISE","PREMISE","PREMISE_PROXIMITY","BLOCK","ROUTE","OTHER"]},"ValidationResult":{"type":"object","properties":{"verdict":{"type":"object","properties":{"inputGranularity":{"$ref":"#/components/schemas/Granularity"},"validationGranularity":{"$ref":"#/components/schemas/Granularity"},"geocodeGranularity":{"$ref":"#/components/schemas/Granularity"},"addressComplete":{"type":"boolean"},"hasUnconfirmedComponents":{"type":"boolean"},"hasInferredComponents":{"type":"boolean"},"hasReplacedComponents":{"type":"boolean"},"hasSpellCorrectedComponents":{"type":"boolean"},"possibleNextAction":{"type":"string","enum":["POSSIBLE_NEXT_ACTION_UNSPECIFIED","FIX","CONFIRM_ADD_SUBPREMISES","CONFIRM","ACCEPT"]}}},"address":{"type":"object","properties":{"formattedAddress":{"type":"string"},"postalAddress":{"$ref":"#/components/schemas/PostalAddress"},"addressComponents":{"type":"array","items":{"$ref":"#/components/schemas/ValidationAddressComponent"}},"missingComponentTypes":{"type":"array","items":{"type":"string"}},"unconfirmedComponentTypes":{"type":"array","items":{"type":"string"}},"unresolvedTokens":{"type":"array","items":{"type":"string"}}}},"geocode":{"type":"object","properties":{"location":{"type":"object","properties":{"latitude":{"type":"number"},"longitude":{"type":"number"}}},"placeId":{"type":"string"},"placeTypes":{"type":"array","items":{"type":"string"}}}},"uspsData":{"type":"object","properties":{"standardizedAddress":{"type":"object","properties":{"firstAddressLine":{"type":"string"},"secondAddressLine":{"type":"string"},"cityStateZipAddressLine":{"type":"string"},"city":{"type":"string"},"state":{"type":"string"},"zipCode":{"type":"string"},"zipCodeExtension":{"type":"string"}}},"dpvConfirmation":{"type":"string","enum":["Y","N","S","D"]},"dpvFootnote":{"type":"string"},"postOfficeCity":{"type":"string"},"postOfficeState":{"type":"string"},"errorMessage":{"type":"string"},"cassProcessed":{"type":"boolean"}}}}},"ValidateAddressResponse":{"type":"object","required":["result","responseId"],"properties":{"result":{"$ref":"#/components/schemas/ValidationResult"},"responseId":{"type":"string"}}}}}}`);
|
|
3356
4160
|
var operationIds = ["PlaceAutocomplete", "PlaceDetails", "Geocode", "FindPlaceFromText", "MapsJavaScriptApi", "ValidateAddress"];
|
|
3357
4161
|
var supportedOperationIds = ["PlaceAutocomplete", "PlaceDetails", "Geocode", "FindPlaceFromText", "MapsJavaScriptApi", "ValidateAddress"];
|
|
3358
4162
|
|
|
@@ -4462,7 +5266,7 @@ var POSTAL_ADDRESS = /* @__PURE__ */ new Set([
|
|
|
4462
5266
|
var MAX_INPUT_LENGTH = 280;
|
|
4463
5267
|
var MAX_ADDRESS_LINES = 3;
|
|
4464
5268
|
var MAX_ADDRESS_LINE_LENGTH = 80;
|
|
4465
|
-
var
|
|
5269
|
+
var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4466
5270
|
var optionalString = (value, field) => {
|
|
4467
5271
|
if (value === void 0 || value === null) return void 0;
|
|
4468
5272
|
if (typeof value !== "string") {
|
|
@@ -4474,7 +5278,7 @@ var optionalString = (value, field) => {
|
|
|
4474
5278
|
return trimmed ? trimmed : void 0;
|
|
4475
5279
|
};
|
|
4476
5280
|
var parseValidateRequest = (body) => {
|
|
4477
|
-
if (!
|
|
5281
|
+
if (!isRecord6(body)) throw new ValidationRequestError("Invalid JSON payload received.");
|
|
4478
5282
|
for (const key of Object.keys(body)) {
|
|
4479
5283
|
if (!TOP_LEVEL.has(key)) {
|
|
4480
5284
|
throw new ValidationRequestError(
|
|
@@ -4486,7 +5290,7 @@ var parseValidateRequest = (body) => {
|
|
|
4486
5290
|
if (address === void 0 || address === null) {
|
|
4487
5291
|
throw new ValidationRequestError("Address is missing from the request.");
|
|
4488
5292
|
}
|
|
4489
|
-
if (!
|
|
5293
|
+
if (!isRecord6(address)) {
|
|
4490
5294
|
throw new ValidationRequestError(
|
|
4491
5295
|
`Invalid value at 'address' (type.googleapis.com/google.type.PostalAddress), ${JSON.stringify(address)}`
|
|
4492
5296
|
);
|
|
@@ -5057,12 +5861,12 @@ var GOOGLE_MAPS_PRESETS = {
|
|
|
5057
5861
|
};
|
|
5058
5862
|
var json5 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
5059
5863
|
var adminError4 = (status, message) => json5(status, { error: { type: "mockingbird_admin", message } });
|
|
5060
|
-
var
|
|
5864
|
+
var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5061
5865
|
var stringList = (value) => Array.isArray(value) && value.every((v) => typeof v === "string") ? value : void 0;
|
|
5062
5866
|
var parseValidation = (value, index) => {
|
|
5063
5867
|
if (value === void 0 || value === null) return void 0;
|
|
5064
5868
|
const at = `addresses[${index}].validation`;
|
|
5065
|
-
if (!
|
|
5869
|
+
if (!isRecord7(value)) return `${at} must be an object`;
|
|
5066
5870
|
const out = {};
|
|
5067
5871
|
const enums = [
|
|
5068
5872
|
["granularity", GRANULARITIES],
|
|
@@ -5096,7 +5900,7 @@ var parseValidation = (value, index) => {
|
|
|
5096
5900
|
return out;
|
|
5097
5901
|
};
|
|
5098
5902
|
var parseAddress = (value, index) => {
|
|
5099
|
-
if (!
|
|
5903
|
+
if (!isRecord7(value)) return `addresses[${index}] must be an object`;
|
|
5100
5904
|
for (const key of ["line1", "city", "state", "zip"]) {
|
|
5101
5905
|
if (typeof value[key] !== "string" || !value[key].trim()) {
|
|
5102
5906
|
return `addresses[${index}].${key} must be a non-empty string`;
|
|
@@ -5132,7 +5936,7 @@ var adminRoutes = (runtime) => ({
|
|
|
5132
5936
|
});
|
|
5133
5937
|
},
|
|
5134
5938
|
"PUT /corpus": ({ body, namespace }) => {
|
|
5135
|
-
const list = Array.isArray(body) ? body :
|
|
5939
|
+
const list = Array.isArray(body) ? body : isRecord7(body) ? body.addresses : void 0;
|
|
5136
5940
|
if (!Array.isArray(list)) {
|
|
5137
5941
|
return adminError4(
|
|
5138
5942
|
400,
|
|
@@ -5155,7 +5959,7 @@ var adminRoutes = (runtime) => ({
|
|
|
5155
5959
|
},
|
|
5156
5960
|
"GET /settings": ({ namespace }) => json5(200, runtime.instance(namespace).state.current()),
|
|
5157
5961
|
"PUT /settings": ({ body, namespace }) => {
|
|
5158
|
-
if (!
|
|
5962
|
+
if (!isRecord7(body)) return adminError4(400, "expected a JSON object");
|
|
5159
5963
|
const patch = {};
|
|
5160
5964
|
if (body.keys !== void 0) {
|
|
5161
5965
|
if (!Array.isArray(body.keys)) return adminError4(400, "keys: string[]");
|
|
@@ -5172,7 +5976,7 @@ var adminRoutes = (runtime) => ({
|
|
|
5172
5976
|
});
|
|
5173
5977
|
var createRuntime2 = (options = {}) => createRuntime({
|
|
5174
5978
|
name: GOOGLE_MAPS_NAMESPACE,
|
|
5175
|
-
document,
|
|
5979
|
+
document: document2,
|
|
5176
5980
|
...options.sqlite ? { sqlite: options.sqlite } : {},
|
|
5177
5981
|
...options.clock ? { clock: options.clock } : {},
|
|
5178
5982
|
...options.seed !== void 0 ? { seed: options.seed } : {},
|
|
@@ -5252,7 +6056,7 @@ var GoogleMapsAPI = class {
|
|
|
5252
6056
|
ValidateAddress: (context) => this.validate(context)
|
|
5253
6057
|
});
|
|
5254
6058
|
this.service = createService({
|
|
5255
|
-
document,
|
|
6059
|
+
document: document2,
|
|
5256
6060
|
handlers,
|
|
5257
6061
|
sqlite,
|
|
5258
6062
|
namespace,
|
|
@@ -5471,7 +6275,7 @@ var GoogleMapsAPI = class {
|
|
|
5471
6275
|
};
|
|
5472
6276
|
|
|
5473
6277
|
export {
|
|
5474
|
-
document,
|
|
6278
|
+
document2 as document,
|
|
5475
6279
|
operationIds,
|
|
5476
6280
|
supportedOperationIds,
|
|
5477
6281
|
DEFAULT_CORPUS,
|
|
@@ -5490,4 +6294,4 @@ export {
|
|
|
5490
6294
|
keyCredential,
|
|
5491
6295
|
GoogleMapsAPI
|
|
5492
6296
|
};
|
|
5493
|
-
//# sourceMappingURL=chunk-
|
|
6297
|
+
//# sourceMappingURL=chunk-HLXAIBBZ.js.map
|