@syncular/server-hono 0.0.6-125 → 0.0.6-135
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/dist/console/gateway.d.ts.map +1 -1
- package/dist/console/gateway.js +799 -1200
- package/dist/console/gateway.js.map +1 -1
- package/dist/console/live-auth.d.ts +7 -0
- package/dist/console/live-auth.d.ts.map +1 -0
- package/dist/console/live-auth.js +35 -0
- package/dist/console/live-auth.js.map +1 -0
- package/dist/console/routes.d.ts +5 -1
- package/dist/console/routes.d.ts.map +1 -1
- package/dist/console/routes.js +39 -191
- package/dist/console/routes.js.map +1 -1
- package/dist/routes.d.ts +1 -1
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +86 -4
- package/dist/routes.js.map +1 -1
- package/dist/ws.d.ts +5 -12
- package/dist/ws.d.ts.map +1 -1
- package/dist/ws.js +30 -202
- package/dist/ws.js.map +1 -1
- package/package.json +6 -6
- package/src/__tests__/create-server.test.ts +26 -0
- package/src/__tests__/sync-maintenance.test.ts +257 -0
- package/src/console/gateway.ts +985 -1591
- package/src/console/live-auth.ts +39 -0
- package/src/console/routes.ts +59 -211
- package/src/routes.ts +102 -4
- package/src/ws.ts +44 -213
package/src/console/gateway.ts
CHANGED
|
@@ -4,20 +4,19 @@ import { cors } from 'hono/cors';
|
|
|
4
4
|
import type { UpgradeWebSocket } from 'hono/ws';
|
|
5
5
|
import { describeRoute, resolver, validator as zValidator } from 'hono-openapi';
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
+
import {
|
|
8
|
+
closeUnauthenticatedSocket,
|
|
9
|
+
parseBearerToken,
|
|
10
|
+
parseWebSocketAuthToken,
|
|
11
|
+
} from './live-auth';
|
|
7
12
|
import type {
|
|
8
13
|
ConsoleApiKey,
|
|
9
14
|
ConsoleApiKeyBulkRevokeResponse,
|
|
10
15
|
ConsoleApiKeyCreateResponse,
|
|
11
|
-
ConsoleClearEventsResult,
|
|
12
16
|
ConsoleClient,
|
|
13
17
|
ConsoleCommitListItem,
|
|
14
|
-
ConsoleCompactResult,
|
|
15
|
-
ConsoleEvictResult,
|
|
16
18
|
ConsoleOperationEvent,
|
|
17
19
|
ConsolePaginatedResponse,
|
|
18
|
-
ConsolePruneEventsResult,
|
|
19
|
-
ConsolePrunePreview,
|
|
20
|
-
ConsolePruneResult,
|
|
21
20
|
ConsoleRequestEvent,
|
|
22
21
|
ConsoleTimelineItem,
|
|
23
22
|
LatencyPercentiles,
|
|
@@ -155,6 +154,11 @@ const GatewaySingleInstanceQuerySchema = GatewayInstanceFilterSchema;
|
|
|
155
154
|
const GatewaySingleInstancePartitionQuerySchema =
|
|
156
155
|
ConsolePartitionQuerySchema.extend(GatewayInstanceFilterSchema.shape);
|
|
157
156
|
|
|
157
|
+
type GatewayInstanceFilterQuery = {
|
|
158
|
+
instanceId?: string;
|
|
159
|
+
instanceIds?: string;
|
|
160
|
+
};
|
|
161
|
+
|
|
158
162
|
const GatewayApiKeyStatusSchema = z.enum(['active', 'revoked', 'expiring']);
|
|
159
163
|
|
|
160
164
|
const GatewayApiKeysQuerySchema = ConsolePaginationQuerySchema.extend({
|
|
@@ -416,84 +420,58 @@ function parseLocalNumericId(value: string): number | null {
|
|
|
416
420
|
return parsed;
|
|
417
421
|
}
|
|
418
422
|
|
|
419
|
-
function
|
|
420
|
-
|
|
423
|
+
function noInstancesSelectedResponse(): {
|
|
424
|
+
ok: false;
|
|
425
|
+
status: 400;
|
|
426
|
+
error: 'NO_INSTANCES_SELECTED';
|
|
427
|
+
message: string;
|
|
428
|
+
} {
|
|
429
|
+
return {
|
|
430
|
+
ok: false,
|
|
431
|
+
status: 400,
|
|
432
|
+
error: 'NO_INSTANCES_SELECTED',
|
|
433
|
+
message: 'No enabled instances matched the provided instance filter.',
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function resolveSingleSelectedInstance(args: {
|
|
421
438
|
instances: ConsoleGatewayInstance[];
|
|
422
|
-
query:
|
|
439
|
+
query: GatewayInstanceFilterQuery;
|
|
440
|
+
onMultiple: { error: string; message: string };
|
|
423
441
|
}):
|
|
424
|
-
| { ok: true; instance: ConsoleGatewayInstance
|
|
425
|
-
| { ok: false; status: 400
|
|
426
|
-
const
|
|
427
|
-
if (federated) {
|
|
428
|
-
const instance = findInstanceById({
|
|
429
|
-
instances: args.instances,
|
|
430
|
-
instanceId: federated.instanceId,
|
|
431
|
-
});
|
|
432
|
-
if (!instance) {
|
|
433
|
-
return {
|
|
434
|
-
ok: false,
|
|
435
|
-
status: 404,
|
|
436
|
-
error: 'NOT_FOUND',
|
|
437
|
-
message: 'Instance not found',
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
return { ok: true, instance, localEventId: federated.localId };
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
const localEventId = parseLocalNumericId(args.id);
|
|
444
|
-
if (localEventId === null) {
|
|
445
|
-
return {
|
|
446
|
-
ok: false,
|
|
447
|
-
status: 400,
|
|
448
|
-
error: 'INVALID_FEDERATED_ID',
|
|
449
|
-
message:
|
|
450
|
-
'Expected either "<instanceId>:<eventId>" or "<eventId>" with an explicit instance filter.',
|
|
451
|
-
};
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
const selectedInstances = selectInstances({
|
|
455
|
-
instances: args.instances,
|
|
456
|
-
query: args.query,
|
|
457
|
-
});
|
|
442
|
+
| { ok: true; instance: ConsoleGatewayInstance }
|
|
443
|
+
| { ok: false; status: 400; error: string; message: string } {
|
|
444
|
+
const selectedInstances = selectInstances(args);
|
|
458
445
|
if (selectedInstances.length === 0) {
|
|
459
|
-
return
|
|
460
|
-
ok: false,
|
|
461
|
-
status: 400,
|
|
462
|
-
error: 'NO_INSTANCES_SELECTED',
|
|
463
|
-
message: 'No enabled instances matched the provided instance filter.',
|
|
464
|
-
};
|
|
446
|
+
return noInstancesSelectedResponse();
|
|
465
447
|
}
|
|
466
448
|
if (selectedInstances.length > 1) {
|
|
467
449
|
return {
|
|
468
450
|
ok: false,
|
|
469
451
|
status: 400,
|
|
470
|
-
error:
|
|
471
|
-
message:
|
|
472
|
-
'Local event IDs are ambiguous across multiple instances. Use "<instanceId>:<eventId>" or select one instance.',
|
|
452
|
+
error: args.onMultiple.error,
|
|
453
|
+
message: args.onMultiple.message,
|
|
473
454
|
};
|
|
474
455
|
}
|
|
475
456
|
|
|
476
457
|
const instance = selectedInstances[0];
|
|
477
458
|
if (!instance) {
|
|
478
|
-
return
|
|
479
|
-
ok: false,
|
|
480
|
-
status: 400,
|
|
481
|
-
error: 'NO_INSTANCES_SELECTED',
|
|
482
|
-
message: 'No enabled instances matched the provided instance filter.',
|
|
483
|
-
};
|
|
459
|
+
return noInstancesSelectedResponse();
|
|
484
460
|
}
|
|
485
|
-
|
|
486
|
-
return { ok: true, instance, localEventId };
|
|
461
|
+
return { ok: true, instance };
|
|
487
462
|
}
|
|
488
463
|
|
|
489
|
-
function
|
|
490
|
-
|
|
464
|
+
function resolveFederatedOrLocalNumericTarget(args: {
|
|
465
|
+
id: string;
|
|
491
466
|
instances: ConsoleGatewayInstance[];
|
|
492
|
-
query:
|
|
467
|
+
query: GatewayInstanceFilterQuery;
|
|
468
|
+
invalidMessage: string;
|
|
469
|
+
ambiguousError: string;
|
|
470
|
+
ambiguousMessage: string;
|
|
493
471
|
}):
|
|
494
|
-
| { ok: true; instance: ConsoleGatewayInstance;
|
|
472
|
+
| { ok: true; instance: ConsoleGatewayInstance; localId: number }
|
|
495
473
|
| { ok: false; status: 400 | 404; error: string; message?: string } {
|
|
496
|
-
const federated = parseFederatedNumericId(args.
|
|
474
|
+
const federated = parseFederatedNumericId(args.id);
|
|
497
475
|
if (federated) {
|
|
498
476
|
const instance = findInstanceById({
|
|
499
477
|
instances: args.instances,
|
|
@@ -507,92 +485,96 @@ function resolveCommitTarget(args: {
|
|
|
507
485
|
message: 'Instance not found',
|
|
508
486
|
};
|
|
509
487
|
}
|
|
510
|
-
return { ok: true, instance,
|
|
488
|
+
return { ok: true, instance, localId: federated.localId };
|
|
511
489
|
}
|
|
512
490
|
|
|
513
|
-
const
|
|
514
|
-
if (
|
|
491
|
+
const localId = parseLocalNumericId(args.id);
|
|
492
|
+
if (localId === null) {
|
|
515
493
|
return {
|
|
516
494
|
ok: false,
|
|
517
495
|
status: 400,
|
|
518
496
|
error: 'INVALID_FEDERATED_ID',
|
|
519
|
-
message:
|
|
520
|
-
'Expected either "<instanceId>:<commitSeq>" or "<commitSeq>" with an explicit instance filter.',
|
|
497
|
+
message: args.invalidMessage,
|
|
521
498
|
};
|
|
522
499
|
}
|
|
523
500
|
|
|
524
|
-
const
|
|
501
|
+
const selection = resolveSingleSelectedInstance({
|
|
525
502
|
instances: args.instances,
|
|
526
503
|
query: args.query,
|
|
504
|
+
onMultiple: {
|
|
505
|
+
error: args.ambiguousError,
|
|
506
|
+
message: args.ambiguousMessage,
|
|
507
|
+
},
|
|
527
508
|
});
|
|
528
|
-
if (
|
|
529
|
-
return {
|
|
530
|
-
ok: false,
|
|
531
|
-
status: 400,
|
|
532
|
-
error: 'NO_INSTANCES_SELECTED',
|
|
533
|
-
message: 'No enabled instances matched the provided instance filter.',
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
|
-
if (selectedInstances.length > 1) {
|
|
537
|
-
return {
|
|
538
|
-
ok: false,
|
|
539
|
-
status: 400,
|
|
540
|
-
error: 'AMBIGUOUS_COMMIT_ID',
|
|
541
|
-
message:
|
|
542
|
-
'Local commit IDs are ambiguous across multiple instances. Use "<instanceId>:<commitSeq>" or select one instance.',
|
|
543
|
-
};
|
|
544
|
-
}
|
|
509
|
+
if (!selection.ok) return selection;
|
|
545
510
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
}
|
|
511
|
+
return { ok: true, instance: selection.instance, localId };
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function resolveEventTarget(args: {
|
|
515
|
+
id: string;
|
|
516
|
+
instances: ConsoleGatewayInstance[];
|
|
517
|
+
query: GatewayInstanceFilterQuery;
|
|
518
|
+
}):
|
|
519
|
+
| { ok: true; instance: ConsoleGatewayInstance; localEventId: number }
|
|
520
|
+
| { ok: false; status: 400 | 404; error: string; message?: string } {
|
|
521
|
+
const resolved = resolveFederatedOrLocalNumericTarget({
|
|
522
|
+
id: args.id,
|
|
523
|
+
instances: args.instances,
|
|
524
|
+
query: args.query,
|
|
525
|
+
invalidMessage:
|
|
526
|
+
'Expected either "<instanceId>:<eventId>" or "<eventId>" with an explicit instance filter.',
|
|
527
|
+
ambiguousError: 'AMBIGUOUS_EVENT_ID',
|
|
528
|
+
ambiguousMessage:
|
|
529
|
+
'Local event IDs are ambiguous across multiple instances. Use "<instanceId>:<eventId>" or select one instance.',
|
|
530
|
+
});
|
|
531
|
+
if (!resolved.ok) return resolved;
|
|
532
|
+
return {
|
|
533
|
+
ok: true,
|
|
534
|
+
instance: resolved.instance,
|
|
535
|
+
localEventId: resolved.localId,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
555
538
|
|
|
556
|
-
|
|
539
|
+
function resolveCommitTarget(args: {
|
|
540
|
+
seq: string;
|
|
541
|
+
instances: ConsoleGatewayInstance[];
|
|
542
|
+
query: GatewayInstanceFilterQuery;
|
|
543
|
+
}):
|
|
544
|
+
| { ok: true; instance: ConsoleGatewayInstance; localCommitSeq: number }
|
|
545
|
+
| { ok: false; status: 400 | 404; error: string; message?: string } {
|
|
546
|
+
const resolved = resolveFederatedOrLocalNumericTarget({
|
|
547
|
+
id: args.seq,
|
|
548
|
+
instances: args.instances,
|
|
549
|
+
query: args.query,
|
|
550
|
+
invalidMessage:
|
|
551
|
+
'Expected either "<instanceId>:<commitSeq>" or "<commitSeq>" with an explicit instance filter.',
|
|
552
|
+
ambiguousError: 'AMBIGUOUS_COMMIT_ID',
|
|
553
|
+
ambiguousMessage:
|
|
554
|
+
'Local commit IDs are ambiguous across multiple instances. Use "<instanceId>:<commitSeq>" or select one instance.',
|
|
555
|
+
});
|
|
556
|
+
if (!resolved.ok) return resolved;
|
|
557
|
+
return {
|
|
558
|
+
ok: true,
|
|
559
|
+
instance: resolved.instance,
|
|
560
|
+
localCommitSeq: resolved.localId,
|
|
561
|
+
};
|
|
557
562
|
}
|
|
558
563
|
|
|
559
564
|
function resolveSingleInstanceTarget(args: {
|
|
560
565
|
instances: ConsoleGatewayInstance[];
|
|
561
|
-
query:
|
|
566
|
+
query: GatewayInstanceFilterQuery;
|
|
562
567
|
}):
|
|
563
568
|
| { ok: true; instance: ConsoleGatewayInstance }
|
|
564
569
|
| { ok: false; status: 400; error: string; message: string } {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
ok: false,
|
|
569
|
-
status: 400,
|
|
570
|
-
error: 'NO_INSTANCES_SELECTED',
|
|
571
|
-
message: 'No enabled instances matched the provided instance filter.',
|
|
572
|
-
};
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
if (selectedInstances.length > 1) {
|
|
576
|
-
return {
|
|
577
|
-
ok: false,
|
|
578
|
-
status: 400,
|
|
570
|
+
return resolveSingleSelectedInstance({
|
|
571
|
+
...args,
|
|
572
|
+
onMultiple: {
|
|
579
573
|
error: 'INSTANCE_REQUIRED',
|
|
580
574
|
message:
|
|
581
575
|
'This endpoint requires exactly one target instance. Provide `instanceId` or a single-value `instanceIds` filter.',
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
const instance = selectedInstances[0];
|
|
586
|
-
if (!instance) {
|
|
587
|
-
return {
|
|
588
|
-
ok: false,
|
|
589
|
-
status: 400,
|
|
590
|
-
error: 'NO_INSTANCES_SELECTED',
|
|
591
|
-
message: 'No enabled instances matched the provided instance filter.',
|
|
592
|
-
};
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
return { ok: true, instance };
|
|
576
|
+
},
|
|
577
|
+
});
|
|
596
578
|
}
|
|
597
579
|
|
|
598
580
|
function minNullable(values: Array<number | null>): number | null {
|
|
@@ -728,17 +710,6 @@ function resolveForwardAuthorization(args: {
|
|
|
728
710
|
return null;
|
|
729
711
|
}
|
|
730
712
|
|
|
731
|
-
function parseBearerToken(
|
|
732
|
-
authHeader: string | null | undefined
|
|
733
|
-
): string | null {
|
|
734
|
-
const value = authHeader?.trim();
|
|
735
|
-
if (!value?.startsWith('Bearer ')) {
|
|
736
|
-
return null;
|
|
737
|
-
}
|
|
738
|
-
const token = value.slice(7).trim();
|
|
739
|
-
return token.length > 0 ? token : null;
|
|
740
|
-
}
|
|
741
|
-
|
|
742
713
|
async function fetchDownstreamJson<T>(args: {
|
|
743
714
|
c: Context;
|
|
744
715
|
instance: ConsoleGatewayInstance;
|
|
@@ -1079,6 +1050,222 @@ export function createConsoleGatewayRoutes(
|
|
|
1079
1050
|
})
|
|
1080
1051
|
);
|
|
1081
1052
|
|
|
1053
|
+
const withGatewayAuth = async (
|
|
1054
|
+
c: Context,
|
|
1055
|
+
callback: () => Promise<Response>
|
|
1056
|
+
): Promise<Response> => {
|
|
1057
|
+
const auth = await options.authenticate(c);
|
|
1058
|
+
if (!auth) {
|
|
1059
|
+
return unauthorizedResponse(c);
|
|
1060
|
+
}
|
|
1061
|
+
return callback();
|
|
1062
|
+
};
|
|
1063
|
+
|
|
1064
|
+
const proxySingleInstanceJsonRequest = async <T>(args: {
|
|
1065
|
+
c: Context;
|
|
1066
|
+
query: { instanceId?: string; instanceIds?: string };
|
|
1067
|
+
method: 'GET' | 'POST' | 'DELETE';
|
|
1068
|
+
path: string;
|
|
1069
|
+
responseSchema: z.ZodType<T>;
|
|
1070
|
+
body?: unknown;
|
|
1071
|
+
}): Promise<Response> => {
|
|
1072
|
+
const target = resolveSingleInstanceTarget({
|
|
1073
|
+
instances,
|
|
1074
|
+
query: args.query,
|
|
1075
|
+
});
|
|
1076
|
+
if (!target.ok) {
|
|
1077
|
+
return args.c.json(
|
|
1078
|
+
{
|
|
1079
|
+
error: target.error,
|
|
1080
|
+
message: target.message,
|
|
1081
|
+
},
|
|
1082
|
+
target.status
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
const forwardQuery = sanitizeForwardQueryParams(
|
|
1087
|
+
new URL(args.c.req.url).searchParams
|
|
1088
|
+
);
|
|
1089
|
+
const result = await forwardDownstreamJsonRequest<T>({
|
|
1090
|
+
c: args.c,
|
|
1091
|
+
instance: target.instance,
|
|
1092
|
+
method: args.method,
|
|
1093
|
+
path: args.path,
|
|
1094
|
+
query: forwardQuery,
|
|
1095
|
+
...(args.body === undefined ? {} : { body: args.body }),
|
|
1096
|
+
responseSchema: args.responseSchema,
|
|
1097
|
+
fetchImpl,
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
if (!result.ok) {
|
|
1101
|
+
return jsonResponse(result.body, result.status);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
return jsonResponse(result.data, result.status);
|
|
1105
|
+
};
|
|
1106
|
+
|
|
1107
|
+
const selectTargetInstances = (
|
|
1108
|
+
c: Context,
|
|
1109
|
+
query: GatewayInstanceFilterQuery
|
|
1110
|
+
):
|
|
1111
|
+
| { ok: true; selectedInstances: ConsoleGatewayInstance[] }
|
|
1112
|
+
| { ok: false; response: Response } => {
|
|
1113
|
+
const selectedInstances = selectInstances({ instances, query });
|
|
1114
|
+
if (selectedInstances.length > 0) {
|
|
1115
|
+
return { ok: true, selectedInstances };
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
const noInstanceError = noInstancesSelectedResponse();
|
|
1119
|
+
return {
|
|
1120
|
+
ok: false,
|
|
1121
|
+
response: c.json(
|
|
1122
|
+
{
|
|
1123
|
+
error: noInstanceError.error,
|
|
1124
|
+
message: noInstanceError.message,
|
|
1125
|
+
},
|
|
1126
|
+
noInstanceError.status
|
|
1127
|
+
),
|
|
1128
|
+
};
|
|
1129
|
+
};
|
|
1130
|
+
|
|
1131
|
+
const fetchFromSelectedInstances = async <T>(args: {
|
|
1132
|
+
c: Context;
|
|
1133
|
+
selectedInstances: ConsoleGatewayInstance[];
|
|
1134
|
+
path: string;
|
|
1135
|
+
query: URLSearchParams;
|
|
1136
|
+
schema: z.ZodType<T>;
|
|
1137
|
+
}): Promise<
|
|
1138
|
+
| {
|
|
1139
|
+
ok: true;
|
|
1140
|
+
successfulResults: Array<{
|
|
1141
|
+
instance: ConsoleGatewayInstance;
|
|
1142
|
+
data: T;
|
|
1143
|
+
}>;
|
|
1144
|
+
failedInstances: GatewayFailure[];
|
|
1145
|
+
}
|
|
1146
|
+
| { ok: false; response: Response }
|
|
1147
|
+
> => {
|
|
1148
|
+
const results = await Promise.all(
|
|
1149
|
+
args.selectedInstances.map((instance) =>
|
|
1150
|
+
fetchDownstreamJson({
|
|
1151
|
+
c: args.c,
|
|
1152
|
+
instance,
|
|
1153
|
+
path: args.path,
|
|
1154
|
+
query: args.query,
|
|
1155
|
+
schema: args.schema,
|
|
1156
|
+
fetchImpl,
|
|
1157
|
+
})
|
|
1158
|
+
)
|
|
1159
|
+
);
|
|
1160
|
+
|
|
1161
|
+
const failedInstances = results
|
|
1162
|
+
.filter(
|
|
1163
|
+
(result): result is { ok: false; failure: GatewayFailure } => !result.ok
|
|
1164
|
+
)
|
|
1165
|
+
.map((result) => result.failure);
|
|
1166
|
+
const successfulResults = results
|
|
1167
|
+
.map((result, index) => ({
|
|
1168
|
+
result,
|
|
1169
|
+
instance: args.selectedInstances[index],
|
|
1170
|
+
}))
|
|
1171
|
+
.filter(
|
|
1172
|
+
(
|
|
1173
|
+
entry
|
|
1174
|
+
): entry is {
|
|
1175
|
+
result: { ok: true; data: T };
|
|
1176
|
+
instance: ConsoleGatewayInstance;
|
|
1177
|
+
} => Boolean(entry.instance) && entry.result.ok
|
|
1178
|
+
)
|
|
1179
|
+
.map((entry) => ({
|
|
1180
|
+
instance: entry.instance,
|
|
1181
|
+
data: entry.result.data,
|
|
1182
|
+
}));
|
|
1183
|
+
|
|
1184
|
+
if (successfulResults.length === 0) {
|
|
1185
|
+
return {
|
|
1186
|
+
ok: false,
|
|
1187
|
+
response: allInstancesFailedResponse(args.c, failedInstances),
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
return {
|
|
1192
|
+
ok: true,
|
|
1193
|
+
successfulResults,
|
|
1194
|
+
failedInstances,
|
|
1195
|
+
};
|
|
1196
|
+
};
|
|
1197
|
+
|
|
1198
|
+
const fetchPagedFromSelectedInstances = async <T>(args: {
|
|
1199
|
+
c: Context;
|
|
1200
|
+
selectedInstances: ConsoleGatewayInstance[];
|
|
1201
|
+
path: string;
|
|
1202
|
+
query: URLSearchParams;
|
|
1203
|
+
targetCount: number;
|
|
1204
|
+
schema: z.ZodType<ConsolePaginatedResponse<T>>;
|
|
1205
|
+
}): Promise<
|
|
1206
|
+
| {
|
|
1207
|
+
ok: true;
|
|
1208
|
+
successfulResults: Array<{
|
|
1209
|
+
instance: ConsoleGatewayInstance;
|
|
1210
|
+
items: T[];
|
|
1211
|
+
total: number;
|
|
1212
|
+
}>;
|
|
1213
|
+
failedInstances: GatewayFailure[];
|
|
1214
|
+
}
|
|
1215
|
+
| { ok: false; response: Response }
|
|
1216
|
+
> => {
|
|
1217
|
+
const results = await Promise.all(
|
|
1218
|
+
args.selectedInstances.map((instance) =>
|
|
1219
|
+
fetchDownstreamPaged({
|
|
1220
|
+
c: args.c,
|
|
1221
|
+
instance,
|
|
1222
|
+
path: args.path,
|
|
1223
|
+
query: args.query,
|
|
1224
|
+
targetCount: args.targetCount,
|
|
1225
|
+
schema: args.schema,
|
|
1226
|
+
fetchImpl,
|
|
1227
|
+
})
|
|
1228
|
+
)
|
|
1229
|
+
);
|
|
1230
|
+
|
|
1231
|
+
const failedInstances = results
|
|
1232
|
+
.filter(
|
|
1233
|
+
(result): result is { ok: false; failure: GatewayFailure } => !result.ok
|
|
1234
|
+
)
|
|
1235
|
+
.map((result) => result.failure);
|
|
1236
|
+
const successfulResults = results
|
|
1237
|
+
.map((result, index) => ({
|
|
1238
|
+
result,
|
|
1239
|
+
instance: args.selectedInstances[index],
|
|
1240
|
+
}))
|
|
1241
|
+
.filter(
|
|
1242
|
+
(
|
|
1243
|
+
entry
|
|
1244
|
+
): entry is {
|
|
1245
|
+
result: { ok: true; items: T[]; total: number };
|
|
1246
|
+
instance: ConsoleGatewayInstance;
|
|
1247
|
+
} => Boolean(entry.instance) && entry.result.ok
|
|
1248
|
+
)
|
|
1249
|
+
.map((entry) => ({
|
|
1250
|
+
instance: entry.instance,
|
|
1251
|
+
items: entry.result.items,
|
|
1252
|
+
total: entry.result.total,
|
|
1253
|
+
}));
|
|
1254
|
+
|
|
1255
|
+
if (successfulResults.length === 0) {
|
|
1256
|
+
return {
|
|
1257
|
+
ok: false,
|
|
1258
|
+
response: allInstancesFailedResponse(args.c, failedInstances),
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
return {
|
|
1263
|
+
ok: true,
|
|
1264
|
+
successfulResults,
|
|
1265
|
+
failedInstances,
|
|
1266
|
+
};
|
|
1267
|
+
};
|
|
1268
|
+
|
|
1082
1269
|
routes.get(
|
|
1083
1270
|
'/instances',
|
|
1084
1271
|
describeRoute({
|
|
@@ -1104,18 +1291,15 @@ export function createConsoleGatewayRoutes(
|
|
|
1104
1291
|
},
|
|
1105
1292
|
}),
|
|
1106
1293
|
async (c) => {
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
baseUrl: instance.baseUrl,
|
|
1117
|
-
enabled: instance.enabled ?? true,
|
|
1118
|
-
})),
|
|
1294
|
+
return withGatewayAuth(c, async () => {
|
|
1295
|
+
return c.json({
|
|
1296
|
+
items: instances.map((instance) => ({
|
|
1297
|
+
instanceId: instance.instanceId,
|
|
1298
|
+
label: instance.label ?? instance.instanceId,
|
|
1299
|
+
baseUrl: instance.baseUrl,
|
|
1300
|
+
enabled: instance.enabled ?? true,
|
|
1301
|
+
})),
|
|
1302
|
+
});
|
|
1119
1303
|
});
|
|
1120
1304
|
}
|
|
1121
1305
|
);
|
|
@@ -1146,46 +1330,36 @@ export function createConsoleGatewayRoutes(
|
|
|
1146
1330
|
}),
|
|
1147
1331
|
zValidator('query', GatewayInstanceFilterSchema),
|
|
1148
1332
|
async (c) => {
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1333
|
+
return withGatewayAuth(c, async () => {
|
|
1334
|
+
const query = c.req.valid('query');
|
|
1335
|
+
const selection = selectTargetInstances(c, query);
|
|
1336
|
+
if (!selection.ok) {
|
|
1337
|
+
return selection.response;
|
|
1338
|
+
}
|
|
1153
1339
|
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
},
|
|
1163
|
-
400
|
|
1340
|
+
const items = await Promise.all(
|
|
1341
|
+
selection.selectedInstances.map((instance) =>
|
|
1342
|
+
checkDownstreamInstanceHealth({
|
|
1343
|
+
c,
|
|
1344
|
+
instance,
|
|
1345
|
+
fetchImpl,
|
|
1346
|
+
})
|
|
1347
|
+
)
|
|
1164
1348
|
);
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
const items = await Promise.all(
|
|
1168
|
-
selectedInstances.map((instance) =>
|
|
1169
|
-
checkDownstreamInstanceHealth({
|
|
1170
|
-
c,
|
|
1171
|
-
instance,
|
|
1172
|
-
fetchImpl,
|
|
1173
|
-
})
|
|
1174
|
-
)
|
|
1175
|
-
);
|
|
1176
1349
|
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1350
|
+
const failedInstances = items
|
|
1351
|
+
.filter((item) => !item.healthy)
|
|
1352
|
+
.map((item) => ({
|
|
1353
|
+
instanceId: item.instanceId,
|
|
1354
|
+
reason: item.reason ?? 'Health probe failed',
|
|
1355
|
+
...(item.status !== undefined ? { status: item.status } : {}),
|
|
1356
|
+
}));
|
|
1357
|
+
|
|
1358
|
+
return c.json({
|
|
1359
|
+
items,
|
|
1360
|
+
partial: failedInstances.length > 0,
|
|
1361
|
+
failedInstances,
|
|
1362
|
+
});
|
|
1189
1363
|
});
|
|
1190
1364
|
}
|
|
1191
1365
|
);
|
|
@@ -1209,41 +1383,16 @@ export function createConsoleGatewayRoutes(
|
|
|
1209
1383
|
}),
|
|
1210
1384
|
zValidator('query', GatewaySingleInstanceQuerySchema),
|
|
1211
1385
|
async (c) => {
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
return
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
{
|
|
1222
|
-
error: target.error,
|
|
1223
|
-
message: target.message,
|
|
1224
|
-
},
|
|
1225
|
-
target.status
|
|
1226
|
-
);
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
const forwardQuery = sanitizeForwardQueryParams(
|
|
1230
|
-
new URL(c.req.url).searchParams
|
|
1231
|
-
);
|
|
1232
|
-
const result = await forwardDownstreamJsonRequest({
|
|
1233
|
-
c,
|
|
1234
|
-
instance: target.instance,
|
|
1235
|
-
method: 'GET',
|
|
1236
|
-
path: '/handlers',
|
|
1237
|
-
query: forwardQuery,
|
|
1238
|
-
responseSchema: GatewayHandlersResponseSchema,
|
|
1239
|
-
fetchImpl,
|
|
1386
|
+
return withGatewayAuth(c, async () => {
|
|
1387
|
+
const query = c.req.valid('query');
|
|
1388
|
+
return proxySingleInstanceJsonRequest({
|
|
1389
|
+
c,
|
|
1390
|
+
query,
|
|
1391
|
+
method: 'GET',
|
|
1392
|
+
path: '/handlers',
|
|
1393
|
+
responseSchema: GatewayHandlersResponseSchema,
|
|
1394
|
+
});
|
|
1240
1395
|
});
|
|
1241
|
-
|
|
1242
|
-
if (!result.ok) {
|
|
1243
|
-
return jsonResponse(result.body, result.status);
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
return jsonResponse(result.data, result.status);
|
|
1247
1396
|
}
|
|
1248
1397
|
);
|
|
1249
1398
|
|
|
@@ -1266,41 +1415,16 @@ export function createConsoleGatewayRoutes(
|
|
|
1266
1415
|
}),
|
|
1267
1416
|
zValidator('query', GatewaySingleInstanceQuerySchema),
|
|
1268
1417
|
async (c) => {
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
return
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
{
|
|
1279
|
-
error: target.error,
|
|
1280
|
-
message: target.message,
|
|
1281
|
-
},
|
|
1282
|
-
target.status
|
|
1283
|
-
);
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
const forwardQuery = sanitizeForwardQueryParams(
|
|
1287
|
-
new URL(c.req.url).searchParams
|
|
1288
|
-
);
|
|
1289
|
-
const result = await forwardDownstreamJsonRequest<ConsolePrunePreview>({
|
|
1290
|
-
c,
|
|
1291
|
-
instance: target.instance,
|
|
1292
|
-
method: 'POST',
|
|
1293
|
-
path: '/prune/preview',
|
|
1294
|
-
query: forwardQuery,
|
|
1295
|
-
responseSchema: ConsolePrunePreviewSchema,
|
|
1296
|
-
fetchImpl,
|
|
1418
|
+
return withGatewayAuth(c, async () => {
|
|
1419
|
+
const query = c.req.valid('query');
|
|
1420
|
+
return proxySingleInstanceJsonRequest({
|
|
1421
|
+
c,
|
|
1422
|
+
query,
|
|
1423
|
+
method: 'POST',
|
|
1424
|
+
path: '/prune/preview',
|
|
1425
|
+
responseSchema: ConsolePrunePreviewSchema,
|
|
1426
|
+
});
|
|
1297
1427
|
});
|
|
1298
|
-
|
|
1299
|
-
if (!result.ok) {
|
|
1300
|
-
return jsonResponse(result.body, result.status);
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
return jsonResponse(result.data, result.status);
|
|
1304
1428
|
}
|
|
1305
1429
|
);
|
|
1306
1430
|
|
|
@@ -1323,41 +1447,16 @@ export function createConsoleGatewayRoutes(
|
|
|
1323
1447
|
}),
|
|
1324
1448
|
zValidator('query', GatewaySingleInstanceQuerySchema),
|
|
1325
1449
|
async (c) => {
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
return
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
{
|
|
1336
|
-
error: target.error,
|
|
1337
|
-
message: target.message,
|
|
1338
|
-
},
|
|
1339
|
-
target.status
|
|
1340
|
-
);
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
|
-
const forwardQuery = sanitizeForwardQueryParams(
|
|
1344
|
-
new URL(c.req.url).searchParams
|
|
1345
|
-
);
|
|
1346
|
-
const result = await forwardDownstreamJsonRequest<ConsolePruneResult>({
|
|
1347
|
-
c,
|
|
1348
|
-
instance: target.instance,
|
|
1349
|
-
method: 'POST',
|
|
1350
|
-
path: '/prune',
|
|
1351
|
-
query: forwardQuery,
|
|
1352
|
-
responseSchema: ConsolePruneResultSchema,
|
|
1353
|
-
fetchImpl,
|
|
1450
|
+
return withGatewayAuth(c, async () => {
|
|
1451
|
+
const query = c.req.valid('query');
|
|
1452
|
+
return proxySingleInstanceJsonRequest({
|
|
1453
|
+
c,
|
|
1454
|
+
query,
|
|
1455
|
+
method: 'POST',
|
|
1456
|
+
path: '/prune',
|
|
1457
|
+
responseSchema: ConsolePruneResultSchema,
|
|
1458
|
+
});
|
|
1354
1459
|
});
|
|
1355
|
-
|
|
1356
|
-
if (!result.ok) {
|
|
1357
|
-
return jsonResponse(result.body, result.status);
|
|
1358
|
-
}
|
|
1359
|
-
|
|
1360
|
-
return jsonResponse(result.data, result.status);
|
|
1361
1460
|
}
|
|
1362
1461
|
);
|
|
1363
1462
|
|
|
@@ -1380,41 +1479,16 @@ export function createConsoleGatewayRoutes(
|
|
|
1380
1479
|
}),
|
|
1381
1480
|
zValidator('query', GatewaySingleInstanceQuerySchema),
|
|
1382
1481
|
async (c) => {
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
return
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
{
|
|
1393
|
-
error: target.error,
|
|
1394
|
-
message: target.message,
|
|
1395
|
-
},
|
|
1396
|
-
target.status
|
|
1397
|
-
);
|
|
1398
|
-
}
|
|
1399
|
-
|
|
1400
|
-
const forwardQuery = sanitizeForwardQueryParams(
|
|
1401
|
-
new URL(c.req.url).searchParams
|
|
1402
|
-
);
|
|
1403
|
-
const result = await forwardDownstreamJsonRequest<ConsoleCompactResult>({
|
|
1404
|
-
c,
|
|
1405
|
-
instance: target.instance,
|
|
1406
|
-
method: 'POST',
|
|
1407
|
-
path: '/compact',
|
|
1408
|
-
query: forwardQuery,
|
|
1409
|
-
responseSchema: ConsoleCompactResultSchema,
|
|
1410
|
-
fetchImpl,
|
|
1482
|
+
return withGatewayAuth(c, async () => {
|
|
1483
|
+
const query = c.req.valid('query');
|
|
1484
|
+
return proxySingleInstanceJsonRequest({
|
|
1485
|
+
c,
|
|
1486
|
+
query,
|
|
1487
|
+
method: 'POST',
|
|
1488
|
+
path: '/compact',
|
|
1489
|
+
responseSchema: ConsoleCompactResultSchema,
|
|
1490
|
+
});
|
|
1411
1491
|
});
|
|
1412
|
-
|
|
1413
|
-
if (!result.ok) {
|
|
1414
|
-
return jsonResponse(result.body, result.status);
|
|
1415
|
-
}
|
|
1416
|
-
|
|
1417
|
-
return jsonResponse(result.data, result.status);
|
|
1418
1492
|
}
|
|
1419
1493
|
);
|
|
1420
1494
|
|
|
@@ -1438,43 +1512,18 @@ export function createConsoleGatewayRoutes(
|
|
|
1438
1512
|
zValidator('query', GatewaySingleInstanceQuerySchema),
|
|
1439
1513
|
zValidator('json', GatewayNotifyDataChangeRequestSchema),
|
|
1440
1514
|
async (c) => {
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
error: target.error,
|
|
1453
|
-
message: target.message,
|
|
1454
|
-
},
|
|
1455
|
-
target.status
|
|
1456
|
-
);
|
|
1457
|
-
}
|
|
1458
|
-
|
|
1459
|
-
const forwardQuery = sanitizeForwardQueryParams(
|
|
1460
|
-
new URL(c.req.url).searchParams
|
|
1461
|
-
);
|
|
1462
|
-
const result = await forwardDownstreamJsonRequest({
|
|
1463
|
-
c,
|
|
1464
|
-
instance: target.instance,
|
|
1465
|
-
method: 'POST',
|
|
1466
|
-
path: '/notify-data-change',
|
|
1467
|
-
query: forwardQuery,
|
|
1468
|
-
body,
|
|
1469
|
-
responseSchema: GatewayNotifyDataChangeResponseSchema,
|
|
1470
|
-
fetchImpl,
|
|
1515
|
+
return withGatewayAuth(c, async () => {
|
|
1516
|
+
const query = c.req.valid('query');
|
|
1517
|
+
const body = c.req.valid('json');
|
|
1518
|
+
return proxySingleInstanceJsonRequest({
|
|
1519
|
+
c,
|
|
1520
|
+
query,
|
|
1521
|
+
method: 'POST',
|
|
1522
|
+
path: '/notify-data-change',
|
|
1523
|
+
body,
|
|
1524
|
+
responseSchema: GatewayNotifyDataChangeResponseSchema,
|
|
1525
|
+
});
|
|
1471
1526
|
});
|
|
1472
|
-
|
|
1473
|
-
if (!result.ok) {
|
|
1474
|
-
return jsonResponse(result.body, result.status);
|
|
1475
|
-
}
|
|
1476
|
-
|
|
1477
|
-
return jsonResponse(result.data, result.status);
|
|
1478
1527
|
}
|
|
1479
1528
|
);
|
|
1480
1529
|
|
|
@@ -1498,42 +1547,17 @@ export function createConsoleGatewayRoutes(
|
|
|
1498
1547
|
zValidator('param', GatewayClientPathParamSchema),
|
|
1499
1548
|
zValidator('query', GatewaySingleInstancePartitionQuerySchema),
|
|
1500
1549
|
async (c) => {
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
{
|
|
1512
|
-
error: target.error,
|
|
1513
|
-
message: target.message,
|
|
1514
|
-
},
|
|
1515
|
-
target.status
|
|
1516
|
-
);
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
const forwardQuery = sanitizeForwardQueryParams(
|
|
1520
|
-
new URL(c.req.url).searchParams
|
|
1521
|
-
);
|
|
1522
|
-
const result = await forwardDownstreamJsonRequest<ConsoleEvictResult>({
|
|
1523
|
-
c,
|
|
1524
|
-
instance: target.instance,
|
|
1525
|
-
method: 'DELETE',
|
|
1526
|
-
path: `/clients/${encodeURIComponent(id)}`,
|
|
1527
|
-
query: forwardQuery,
|
|
1528
|
-
responseSchema: ConsoleEvictResultSchema,
|
|
1529
|
-
fetchImpl,
|
|
1550
|
+
return withGatewayAuth(c, async () => {
|
|
1551
|
+
const { id } = c.req.valid('param');
|
|
1552
|
+
const query = c.req.valid('query');
|
|
1553
|
+
return proxySingleInstanceJsonRequest({
|
|
1554
|
+
c,
|
|
1555
|
+
query,
|
|
1556
|
+
method: 'DELETE',
|
|
1557
|
+
path: `/clients/${encodeURIComponent(id)}`,
|
|
1558
|
+
responseSchema: ConsoleEvictResultSchema,
|
|
1559
|
+
});
|
|
1530
1560
|
});
|
|
1531
|
-
|
|
1532
|
-
if (!result.ok) {
|
|
1533
|
-
return jsonResponse(result.body, result.status);
|
|
1534
|
-
}
|
|
1535
|
-
|
|
1536
|
-
return jsonResponse(result.data, result.status);
|
|
1537
1561
|
}
|
|
1538
1562
|
);
|
|
1539
1563
|
|
|
@@ -1556,42 +1580,16 @@ export function createConsoleGatewayRoutes(
|
|
|
1556
1580
|
}),
|
|
1557
1581
|
zValidator('query', GatewaySingleInstanceQuerySchema),
|
|
1558
1582
|
async (c) => {
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
return
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
const query = c.req.valid('query');
|
|
1565
|
-
const target = resolveSingleInstanceTarget({ instances, query });
|
|
1566
|
-
if (!target.ok) {
|
|
1567
|
-
return c.json(
|
|
1568
|
-
{
|
|
1569
|
-
error: target.error,
|
|
1570
|
-
message: target.message,
|
|
1571
|
-
},
|
|
1572
|
-
target.status
|
|
1573
|
-
);
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
|
-
const forwardQuery = sanitizeForwardQueryParams(
|
|
1577
|
-
new URL(c.req.url).searchParams
|
|
1578
|
-
);
|
|
1579
|
-
const result =
|
|
1580
|
-
await forwardDownstreamJsonRequest<ConsoleClearEventsResult>({
|
|
1583
|
+
return withGatewayAuth(c, async () => {
|
|
1584
|
+
const query = c.req.valid('query');
|
|
1585
|
+
return proxySingleInstanceJsonRequest({
|
|
1581
1586
|
c,
|
|
1582
|
-
|
|
1587
|
+
query,
|
|
1583
1588
|
method: 'DELETE',
|
|
1584
1589
|
path: '/events',
|
|
1585
|
-
query: forwardQuery,
|
|
1586
1590
|
responseSchema: ConsoleClearEventsResultSchema,
|
|
1587
|
-
fetchImpl,
|
|
1588
1591
|
});
|
|
1589
|
-
|
|
1590
|
-
if (!result.ok) {
|
|
1591
|
-
return jsonResponse(result.body, result.status);
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
|
-
return jsonResponse(result.data, result.status);
|
|
1592
|
+
});
|
|
1595
1593
|
}
|
|
1596
1594
|
);
|
|
1597
1595
|
|
|
@@ -1614,42 +1612,16 @@ export function createConsoleGatewayRoutes(
|
|
|
1614
1612
|
}),
|
|
1615
1613
|
zValidator('query', GatewaySingleInstanceQuerySchema),
|
|
1616
1614
|
async (c) => {
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
return
|
|
1620
|
-
}
|
|
1621
|
-
|
|
1622
|
-
const query = c.req.valid('query');
|
|
1623
|
-
const target = resolveSingleInstanceTarget({ instances, query });
|
|
1624
|
-
if (!target.ok) {
|
|
1625
|
-
return c.json(
|
|
1626
|
-
{
|
|
1627
|
-
error: target.error,
|
|
1628
|
-
message: target.message,
|
|
1629
|
-
},
|
|
1630
|
-
target.status
|
|
1631
|
-
);
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
const forwardQuery = sanitizeForwardQueryParams(
|
|
1635
|
-
new URL(c.req.url).searchParams
|
|
1636
|
-
);
|
|
1637
|
-
const result =
|
|
1638
|
-
await forwardDownstreamJsonRequest<ConsolePruneEventsResult>({
|
|
1615
|
+
return withGatewayAuth(c, async () => {
|
|
1616
|
+
const query = c.req.valid('query');
|
|
1617
|
+
return proxySingleInstanceJsonRequest({
|
|
1639
1618
|
c,
|
|
1640
|
-
|
|
1619
|
+
query,
|
|
1641
1620
|
method: 'POST',
|
|
1642
1621
|
path: '/events/prune',
|
|
1643
|
-
query: forwardQuery,
|
|
1644
1622
|
responseSchema: ConsolePruneEventsResultSchema,
|
|
1645
|
-
fetchImpl,
|
|
1646
1623
|
});
|
|
1647
|
-
|
|
1648
|
-
if (!result.ok) {
|
|
1649
|
-
return jsonResponse(result.body, result.status);
|
|
1650
|
-
}
|
|
1651
|
-
|
|
1652
|
-
return jsonResponse(result.data, result.status);
|
|
1624
|
+
});
|
|
1653
1625
|
}
|
|
1654
1626
|
);
|
|
1655
1627
|
|
|
@@ -1674,43 +1646,18 @@ export function createConsoleGatewayRoutes(
|
|
|
1674
1646
|
}),
|
|
1675
1647
|
zValidator('query', GatewayApiKeysQuerySchema),
|
|
1676
1648
|
async (c) => {
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
return
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
message: target.message,
|
|
1689
|
-
},
|
|
1690
|
-
target.status
|
|
1691
|
-
);
|
|
1692
|
-
}
|
|
1693
|
-
|
|
1694
|
-
const forwardQuery = sanitizeForwardQueryParams(
|
|
1695
|
-
new URL(c.req.url).searchParams
|
|
1696
|
-
);
|
|
1697
|
-
const result = await forwardDownstreamJsonRequest<
|
|
1698
|
-
ConsolePaginatedResponse<ConsoleApiKey>
|
|
1699
|
-
>({
|
|
1700
|
-
c,
|
|
1701
|
-
instance: target.instance,
|
|
1702
|
-
method: 'GET',
|
|
1703
|
-
path: '/api-keys',
|
|
1704
|
-
query: forwardQuery,
|
|
1705
|
-
responseSchema: ConsolePaginatedResponseSchema(ConsoleApiKeySchema),
|
|
1706
|
-
fetchImpl,
|
|
1649
|
+
return withGatewayAuth(c, async () => {
|
|
1650
|
+
const query = c.req.valid('query');
|
|
1651
|
+
return proxySingleInstanceJsonRequest<
|
|
1652
|
+
ConsolePaginatedResponse<ConsoleApiKey>
|
|
1653
|
+
>({
|
|
1654
|
+
c,
|
|
1655
|
+
query,
|
|
1656
|
+
method: 'GET',
|
|
1657
|
+
path: '/api-keys',
|
|
1658
|
+
responseSchema: ConsolePaginatedResponseSchema(ConsoleApiKeySchema),
|
|
1659
|
+
});
|
|
1707
1660
|
});
|
|
1708
|
-
|
|
1709
|
-
if (!result.ok) {
|
|
1710
|
-
return jsonResponse(result.body, result.status);
|
|
1711
|
-
}
|
|
1712
|
-
|
|
1713
|
-
return jsonResponse(result.data, result.status);
|
|
1714
1661
|
}
|
|
1715
1662
|
);
|
|
1716
1663
|
|
|
@@ -1734,44 +1681,18 @@ export function createConsoleGatewayRoutes(
|
|
|
1734
1681
|
zValidator('query', GatewaySingleInstanceQuerySchema),
|
|
1735
1682
|
zValidator('json', ConsoleApiKeyCreateRequestSchema),
|
|
1736
1683
|
async (c) => {
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
const query = c.req.valid('query');
|
|
1743
|
-
const body = c.req.valid('json');
|
|
1744
|
-
const target = resolveSingleInstanceTarget({ instances, query });
|
|
1745
|
-
if (!target.ok) {
|
|
1746
|
-
return c.json(
|
|
1747
|
-
{
|
|
1748
|
-
error: target.error,
|
|
1749
|
-
message: target.message,
|
|
1750
|
-
},
|
|
1751
|
-
target.status
|
|
1752
|
-
);
|
|
1753
|
-
}
|
|
1754
|
-
|
|
1755
|
-
const forwardQuery = sanitizeForwardQueryParams(
|
|
1756
|
-
new URL(c.req.url).searchParams
|
|
1757
|
-
);
|
|
1758
|
-
const result =
|
|
1759
|
-
await forwardDownstreamJsonRequest<ConsoleApiKeyCreateResponse>({
|
|
1684
|
+
return withGatewayAuth(c, async () => {
|
|
1685
|
+
const query = c.req.valid('query');
|
|
1686
|
+
const body = c.req.valid('json');
|
|
1687
|
+
return proxySingleInstanceJsonRequest<ConsoleApiKeyCreateResponse>({
|
|
1760
1688
|
c,
|
|
1761
|
-
|
|
1689
|
+
query,
|
|
1762
1690
|
method: 'POST',
|
|
1763
1691
|
path: '/api-keys',
|
|
1764
|
-
query: forwardQuery,
|
|
1765
1692
|
body,
|
|
1766
1693
|
responseSchema: ConsoleApiKeyCreateResponseSchema,
|
|
1767
|
-
fetchImpl,
|
|
1768
1694
|
});
|
|
1769
|
-
|
|
1770
|
-
if (!result.ok) {
|
|
1771
|
-
return jsonResponse(result.body, result.status);
|
|
1772
|
-
}
|
|
1773
|
-
|
|
1774
|
-
return jsonResponse(result.data, result.status);
|
|
1695
|
+
});
|
|
1775
1696
|
}
|
|
1776
1697
|
);
|
|
1777
1698
|
|
|
@@ -1795,42 +1716,17 @@ export function createConsoleGatewayRoutes(
|
|
|
1795
1716
|
zValidator('param', GatewayApiKeyPathParamSchema),
|
|
1796
1717
|
zValidator('query', GatewaySingleInstanceQuerySchema),
|
|
1797
1718
|
async (c) => {
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
{
|
|
1809
|
-
error: target.error,
|
|
1810
|
-
message: target.message,
|
|
1811
|
-
},
|
|
1812
|
-
target.status
|
|
1813
|
-
);
|
|
1814
|
-
}
|
|
1815
|
-
|
|
1816
|
-
const forwardQuery = sanitizeForwardQueryParams(
|
|
1817
|
-
new URL(c.req.url).searchParams
|
|
1818
|
-
);
|
|
1819
|
-
const result = await forwardDownstreamJsonRequest<ConsoleApiKey>({
|
|
1820
|
-
c,
|
|
1821
|
-
instance: target.instance,
|
|
1822
|
-
method: 'GET',
|
|
1823
|
-
path: `/api-keys/${encodeURIComponent(id)}`,
|
|
1824
|
-
query: forwardQuery,
|
|
1825
|
-
responseSchema: ConsoleApiKeySchema,
|
|
1826
|
-
fetchImpl,
|
|
1719
|
+
return withGatewayAuth(c, async () => {
|
|
1720
|
+
const { id } = c.req.valid('param');
|
|
1721
|
+
const query = c.req.valid('query');
|
|
1722
|
+
return proxySingleInstanceJsonRequest<ConsoleApiKey>({
|
|
1723
|
+
c,
|
|
1724
|
+
query,
|
|
1725
|
+
method: 'GET',
|
|
1726
|
+
path: `/api-keys/${encodeURIComponent(id)}`,
|
|
1727
|
+
responseSchema: ConsoleApiKeySchema,
|
|
1728
|
+
});
|
|
1827
1729
|
});
|
|
1828
|
-
|
|
1829
|
-
if (!result.ok) {
|
|
1830
|
-
return jsonResponse(result.body, result.status);
|
|
1831
|
-
}
|
|
1832
|
-
|
|
1833
|
-
return jsonResponse(result.data, result.status);
|
|
1834
1730
|
}
|
|
1835
1731
|
);
|
|
1836
1732
|
|
|
@@ -1854,42 +1750,17 @@ export function createConsoleGatewayRoutes(
|
|
|
1854
1750
|
zValidator('param', GatewayApiKeyPathParamSchema),
|
|
1855
1751
|
zValidator('query', GatewaySingleInstanceQuerySchema),
|
|
1856
1752
|
async (c) => {
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
{
|
|
1868
|
-
error: target.error,
|
|
1869
|
-
message: target.message,
|
|
1870
|
-
},
|
|
1871
|
-
target.status
|
|
1872
|
-
);
|
|
1873
|
-
}
|
|
1874
|
-
|
|
1875
|
-
const forwardQuery = sanitizeForwardQueryParams(
|
|
1876
|
-
new URL(c.req.url).searchParams
|
|
1877
|
-
);
|
|
1878
|
-
const result = await forwardDownstreamJsonRequest<{ revoked: boolean }>({
|
|
1879
|
-
c,
|
|
1880
|
-
instance: target.instance,
|
|
1881
|
-
method: 'DELETE',
|
|
1882
|
-
path: `/api-keys/${encodeURIComponent(id)}`,
|
|
1883
|
-
query: forwardQuery,
|
|
1884
|
-
responseSchema: ConsoleApiKeyRevokeResponseSchema,
|
|
1885
|
-
fetchImpl,
|
|
1753
|
+
return withGatewayAuth(c, async () => {
|
|
1754
|
+
const { id } = c.req.valid('param');
|
|
1755
|
+
const query = c.req.valid('query');
|
|
1756
|
+
return proxySingleInstanceJsonRequest<{ revoked: boolean }>({
|
|
1757
|
+
c,
|
|
1758
|
+
query,
|
|
1759
|
+
method: 'DELETE',
|
|
1760
|
+
path: `/api-keys/${encodeURIComponent(id)}`,
|
|
1761
|
+
responseSchema: ConsoleApiKeyRevokeResponseSchema,
|
|
1762
|
+
});
|
|
1886
1763
|
});
|
|
1887
|
-
|
|
1888
|
-
if (!result.ok) {
|
|
1889
|
-
return jsonResponse(result.body, result.status);
|
|
1890
|
-
}
|
|
1891
|
-
|
|
1892
|
-
return jsonResponse(result.data, result.status);
|
|
1893
1764
|
}
|
|
1894
1765
|
);
|
|
1895
1766
|
|
|
@@ -1913,44 +1784,18 @@ export function createConsoleGatewayRoutes(
|
|
|
1913
1784
|
zValidator('query', GatewaySingleInstanceQuerySchema),
|
|
1914
1785
|
zValidator('json', ConsoleApiKeyBulkRevokeRequestSchema),
|
|
1915
1786
|
async (c) => {
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
const query = c.req.valid('query');
|
|
1922
|
-
const body = c.req.valid('json');
|
|
1923
|
-
const target = resolveSingleInstanceTarget({ instances, query });
|
|
1924
|
-
if (!target.ok) {
|
|
1925
|
-
return c.json(
|
|
1926
|
-
{
|
|
1927
|
-
error: target.error,
|
|
1928
|
-
message: target.message,
|
|
1929
|
-
},
|
|
1930
|
-
target.status
|
|
1931
|
-
);
|
|
1932
|
-
}
|
|
1933
|
-
|
|
1934
|
-
const forwardQuery = sanitizeForwardQueryParams(
|
|
1935
|
-
new URL(c.req.url).searchParams
|
|
1936
|
-
);
|
|
1937
|
-
const result =
|
|
1938
|
-
await forwardDownstreamJsonRequest<ConsoleApiKeyBulkRevokeResponse>({
|
|
1787
|
+
return withGatewayAuth(c, async () => {
|
|
1788
|
+
const query = c.req.valid('query');
|
|
1789
|
+
const body = c.req.valid('json');
|
|
1790
|
+
return proxySingleInstanceJsonRequest<ConsoleApiKeyBulkRevokeResponse>({
|
|
1939
1791
|
c,
|
|
1940
|
-
|
|
1792
|
+
query,
|
|
1941
1793
|
method: 'POST',
|
|
1942
1794
|
path: '/api-keys/bulk-revoke',
|
|
1943
|
-
query: forwardQuery,
|
|
1944
1795
|
body,
|
|
1945
1796
|
responseSchema: ConsoleApiKeyBulkRevokeResponseSchema,
|
|
1946
|
-
fetchImpl,
|
|
1947
1797
|
});
|
|
1948
|
-
|
|
1949
|
-
if (!result.ok) {
|
|
1950
|
-
return jsonResponse(result.body, result.status);
|
|
1951
|
-
}
|
|
1952
|
-
|
|
1953
|
-
return jsonResponse(result.data, result.status);
|
|
1798
|
+
});
|
|
1954
1799
|
}
|
|
1955
1800
|
);
|
|
1956
1801
|
|
|
@@ -1974,43 +1819,17 @@ export function createConsoleGatewayRoutes(
|
|
|
1974
1819
|
zValidator('param', GatewayApiKeyPathParamSchema),
|
|
1975
1820
|
zValidator('query', GatewaySingleInstanceQuerySchema),
|
|
1976
1821
|
async (c) => {
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
const { id } = c.req.valid('param');
|
|
1983
|
-
const query = c.req.valid('query');
|
|
1984
|
-
const target = resolveSingleInstanceTarget({ instances, query });
|
|
1985
|
-
if (!target.ok) {
|
|
1986
|
-
return c.json(
|
|
1987
|
-
{
|
|
1988
|
-
error: target.error,
|
|
1989
|
-
message: target.message,
|
|
1990
|
-
},
|
|
1991
|
-
target.status
|
|
1992
|
-
);
|
|
1993
|
-
}
|
|
1994
|
-
|
|
1995
|
-
const forwardQuery = sanitizeForwardQueryParams(
|
|
1996
|
-
new URL(c.req.url).searchParams
|
|
1997
|
-
);
|
|
1998
|
-
const result =
|
|
1999
|
-
await forwardDownstreamJsonRequest<ConsoleApiKeyCreateResponse>({
|
|
1822
|
+
return withGatewayAuth(c, async () => {
|
|
1823
|
+
const { id } = c.req.valid('param');
|
|
1824
|
+
const query = c.req.valid('query');
|
|
1825
|
+
return proxySingleInstanceJsonRequest<ConsoleApiKeyCreateResponse>({
|
|
2000
1826
|
c,
|
|
2001
|
-
|
|
1827
|
+
query,
|
|
2002
1828
|
method: 'POST',
|
|
2003
1829
|
path: `/api-keys/${encodeURIComponent(id)}/rotate/stage`,
|
|
2004
|
-
query: forwardQuery,
|
|
2005
1830
|
responseSchema: ConsoleApiKeyCreateResponseSchema,
|
|
2006
|
-
fetchImpl,
|
|
2007
1831
|
});
|
|
2008
|
-
|
|
2009
|
-
if (!result.ok) {
|
|
2010
|
-
return jsonResponse(result.body, result.status);
|
|
2011
|
-
}
|
|
2012
|
-
|
|
2013
|
-
return jsonResponse(result.data, result.status);
|
|
1832
|
+
});
|
|
2014
1833
|
}
|
|
2015
1834
|
);
|
|
2016
1835
|
|
|
@@ -2034,43 +1853,17 @@ export function createConsoleGatewayRoutes(
|
|
|
2034
1853
|
zValidator('param', GatewayApiKeyPathParamSchema),
|
|
2035
1854
|
zValidator('query', GatewaySingleInstanceQuerySchema),
|
|
2036
1855
|
async (c) => {
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
const { id } = c.req.valid('param');
|
|
2043
|
-
const query = c.req.valid('query');
|
|
2044
|
-
const target = resolveSingleInstanceTarget({ instances, query });
|
|
2045
|
-
if (!target.ok) {
|
|
2046
|
-
return c.json(
|
|
2047
|
-
{
|
|
2048
|
-
error: target.error,
|
|
2049
|
-
message: target.message,
|
|
2050
|
-
},
|
|
2051
|
-
target.status
|
|
2052
|
-
);
|
|
2053
|
-
}
|
|
2054
|
-
|
|
2055
|
-
const forwardQuery = sanitizeForwardQueryParams(
|
|
2056
|
-
new URL(c.req.url).searchParams
|
|
2057
|
-
);
|
|
2058
|
-
const result =
|
|
2059
|
-
await forwardDownstreamJsonRequest<ConsoleApiKeyCreateResponse>({
|
|
1856
|
+
return withGatewayAuth(c, async () => {
|
|
1857
|
+
const { id } = c.req.valid('param');
|
|
1858
|
+
const query = c.req.valid('query');
|
|
1859
|
+
return proxySingleInstanceJsonRequest<ConsoleApiKeyCreateResponse>({
|
|
2060
1860
|
c,
|
|
2061
|
-
|
|
1861
|
+
query,
|
|
2062
1862
|
method: 'POST',
|
|
2063
1863
|
path: `/api-keys/${encodeURIComponent(id)}/rotate`,
|
|
2064
|
-
query: forwardQuery,
|
|
2065
1864
|
responseSchema: ConsoleApiKeyCreateResponseSchema,
|
|
2066
|
-
fetchImpl,
|
|
2067
1865
|
});
|
|
2068
|
-
|
|
2069
|
-
if (!result.ok) {
|
|
2070
|
-
return jsonResponse(result.body, result.status);
|
|
2071
|
-
}
|
|
2072
|
-
|
|
2073
|
-
return jsonResponse(result.data, result.status);
|
|
1866
|
+
});
|
|
2074
1867
|
}
|
|
2075
1868
|
);
|
|
2076
1869
|
|
|
@@ -2092,96 +1885,65 @@ export function createConsoleGatewayRoutes(
|
|
|
2092
1885
|
}),
|
|
2093
1886
|
zValidator('query', GatewayStatsQuerySchema),
|
|
2094
1887
|
async (c) => {
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
1888
|
+
return withGatewayAuth(c, async () => {
|
|
1889
|
+
const query = c.req.valid('query');
|
|
1890
|
+
const selection = selectTargetInstances(c, query);
|
|
1891
|
+
if (!selection.ok) {
|
|
1892
|
+
return selection.response;
|
|
1893
|
+
}
|
|
2099
1894
|
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
if (selectedInstances.length === 0) {
|
|
2103
|
-
return c.json(
|
|
2104
|
-
{
|
|
2105
|
-
error: 'NO_INSTANCES_SELECTED',
|
|
2106
|
-
message:
|
|
2107
|
-
'No enabled instances matched the provided instance filter.',
|
|
2108
|
-
},
|
|
2109
|
-
400
|
|
1895
|
+
const forwardQuery = sanitizeForwardQueryParams(
|
|
1896
|
+
new URL(c.req.url).searchParams
|
|
2110
1897
|
);
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
instance,
|
|
2122
|
-
path: '/stats',
|
|
2123
|
-
query: forwardQuery,
|
|
2124
|
-
schema: SyncStatsSchema,
|
|
2125
|
-
fetchImpl,
|
|
2126
|
-
})
|
|
2127
|
-
)
|
|
2128
|
-
);
|
|
2129
|
-
|
|
2130
|
-
const failedInstances = results
|
|
2131
|
-
.filter(
|
|
2132
|
-
(result): result is { ok: false; failure: GatewayFailure } =>
|
|
2133
|
-
!result.ok
|
|
2134
|
-
)
|
|
2135
|
-
.map((result) => result.failure);
|
|
2136
|
-
const successfulResults = results.filter(
|
|
2137
|
-
(result): result is { ok: true; data: SyncStats } => result.ok
|
|
2138
|
-
);
|
|
2139
|
-
|
|
2140
|
-
if (successfulResults.length === 0) {
|
|
2141
|
-
return allInstancesFailedResponse(c, failedInstances);
|
|
2142
|
-
}
|
|
1898
|
+
const fetched = await fetchFromSelectedInstances<SyncStats>({
|
|
1899
|
+
c,
|
|
1900
|
+
selectedInstances: selection.selectedInstances,
|
|
1901
|
+
path: '/stats',
|
|
1902
|
+
query: forwardQuery,
|
|
1903
|
+
schema: SyncStatsSchema,
|
|
1904
|
+
});
|
|
1905
|
+
if (!fetched.ok) {
|
|
1906
|
+
return fetched.response;
|
|
1907
|
+
}
|
|
2143
1908
|
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
const instance = selectedInstances[i];
|
|
2149
|
-
if (!instance) continue;
|
|
2150
|
-
statsByInstance.set(instance.instanceId, result.data);
|
|
2151
|
-
}
|
|
1909
|
+
const statsByInstance = new Map<string, SyncStats>();
|
|
1910
|
+
for (const result of fetched.successfulResults) {
|
|
1911
|
+
statsByInstance.set(result.instance.instanceId, result.data);
|
|
1912
|
+
}
|
|
2152
1913
|
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
1914
|
+
const statsValues = Array.from(statsByInstance.values());
|
|
1915
|
+
const sum = (selector: (stats: SyncStats) => number): number =>
|
|
1916
|
+
statsValues.reduce((acc, stats) => acc + selector(stats), 0);
|
|
2156
1917
|
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
1918
|
+
const minCommitSeqByInstance: Record<string, number> = {};
|
|
1919
|
+
const maxCommitSeqByInstance: Record<string, number> = {};
|
|
1920
|
+
for (const [instanceId, stats] of statsByInstance.entries()) {
|
|
1921
|
+
minCommitSeqByInstance[instanceId] = stats.minCommitSeq;
|
|
1922
|
+
maxCommitSeqByInstance[instanceId] = stats.maxCommitSeq;
|
|
1923
|
+
}
|
|
2163
1924
|
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
1925
|
+
return c.json({
|
|
1926
|
+
commitCount: sum((stats) => stats.commitCount),
|
|
1927
|
+
changeCount: sum((stats) => stats.changeCount),
|
|
1928
|
+
minCommitSeq: Math.min(
|
|
1929
|
+
...statsValues.map((stats) => stats.minCommitSeq)
|
|
1930
|
+
),
|
|
1931
|
+
maxCommitSeq: Math.max(
|
|
1932
|
+
...statsValues.map((stats) => stats.maxCommitSeq)
|
|
1933
|
+
),
|
|
1934
|
+
clientCount: sum((stats) => stats.clientCount),
|
|
1935
|
+
activeClientCount: sum((stats) => stats.activeClientCount),
|
|
1936
|
+
minActiveClientCursor: minNullable(
|
|
1937
|
+
statsValues.map((stats) => stats.minActiveClientCursor)
|
|
1938
|
+
),
|
|
1939
|
+
maxActiveClientCursor: maxNullable(
|
|
1940
|
+
statsValues.map((stats) => stats.maxActiveClientCursor)
|
|
1941
|
+
),
|
|
1942
|
+
minCommitSeqByInstance,
|
|
1943
|
+
maxCommitSeqByInstance,
|
|
1944
|
+
partial: fetched.failedInstances.length > 0,
|
|
1945
|
+
failedInstances: fetched.failedInstances,
|
|
1946
|
+
});
|
|
2185
1947
|
});
|
|
2186
1948
|
}
|
|
2187
1949
|
);
|
|
@@ -2204,64 +1966,37 @@ export function createConsoleGatewayRoutes(
|
|
|
2204
1966
|
}),
|
|
2205
1967
|
zValidator('query', GatewayTimeseriesQuerySchema),
|
|
2206
1968
|
async (c) => {
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
1969
|
+
return withGatewayAuth(c, async () => {
|
|
1970
|
+
const query = c.req.valid('query');
|
|
1971
|
+
const selection = selectTargetInstances(c, query);
|
|
1972
|
+
if (!selection.ok) {
|
|
1973
|
+
return selection.response;
|
|
1974
|
+
}
|
|
2211
1975
|
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
if (selectedInstances.length === 0) {
|
|
2215
|
-
return c.json(
|
|
2216
|
-
{
|
|
2217
|
-
error: 'NO_INSTANCES_SELECTED',
|
|
2218
|
-
message:
|
|
2219
|
-
'No enabled instances matched the provided instance filter.',
|
|
2220
|
-
},
|
|
2221
|
-
400
|
|
1976
|
+
const forwardQuery = sanitizeForwardQueryParams(
|
|
1977
|
+
new URL(c.req.url).searchParams
|
|
2222
1978
|
);
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
const forwardQuery = sanitizeForwardQueryParams(
|
|
2226
|
-
new URL(c.req.url).searchParams
|
|
2227
|
-
);
|
|
2228
|
-
|
|
2229
|
-
const results = await Promise.all(
|
|
2230
|
-
selectedInstances.map((instance) =>
|
|
2231
|
-
fetchDownstreamJson({
|
|
1979
|
+
const fetched =
|
|
1980
|
+
await fetchFromSelectedInstances<TimeseriesStatsResponse>({
|
|
2232
1981
|
c,
|
|
2233
|
-
|
|
1982
|
+
selectedInstances: selection.selectedInstances,
|
|
2234
1983
|
path: '/stats/timeseries',
|
|
2235
1984
|
query: forwardQuery,
|
|
2236
1985
|
schema: TimeseriesStatsResponseSchema,
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
const failedInstances = results
|
|
2243
|
-
.filter(
|
|
2244
|
-
(result): result is { ok: false; failure: GatewayFailure } =>
|
|
2245
|
-
!result.ok
|
|
2246
|
-
)
|
|
2247
|
-
.map((result) => result.failure);
|
|
2248
|
-
const successfulResults = results.filter(
|
|
2249
|
-
(result): result is { ok: true; data: TimeseriesStatsResponse } =>
|
|
2250
|
-
result.ok
|
|
2251
|
-
);
|
|
2252
|
-
|
|
2253
|
-
if (successfulResults.length === 0) {
|
|
2254
|
-
return allInstancesFailedResponse(c, failedInstances);
|
|
2255
|
-
}
|
|
1986
|
+
});
|
|
1987
|
+
if (!fetched.ok) {
|
|
1988
|
+
return fetched.response;
|
|
1989
|
+
}
|
|
2256
1990
|
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
1991
|
+
return c.json({
|
|
1992
|
+
buckets: mergeTimeseriesBuckets(
|
|
1993
|
+
fetched.successfulResults.map((result) => result.data)
|
|
1994
|
+
),
|
|
1995
|
+
interval: query.interval,
|
|
1996
|
+
range: query.range,
|
|
1997
|
+
partial: fetched.failedInstances.length > 0,
|
|
1998
|
+
failedInstances: fetched.failedInstances,
|
|
1999
|
+
});
|
|
2265
2000
|
});
|
|
2266
2001
|
}
|
|
2267
2002
|
);
|
|
@@ -2284,66 +2019,38 @@ export function createConsoleGatewayRoutes(
|
|
|
2284
2019
|
}),
|
|
2285
2020
|
zValidator('query', GatewayLatencyQuerySchema),
|
|
2286
2021
|
async (c) => {
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2022
|
+
return withGatewayAuth(c, async () => {
|
|
2023
|
+
const query = c.req.valid('query');
|
|
2024
|
+
const selection = selectTargetInstances(c, query);
|
|
2025
|
+
if (!selection.ok) {
|
|
2026
|
+
return selection.response;
|
|
2027
|
+
}
|
|
2291
2028
|
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
if (selectedInstances.length === 0) {
|
|
2295
|
-
return c.json(
|
|
2296
|
-
{
|
|
2297
|
-
error: 'NO_INSTANCES_SELECTED',
|
|
2298
|
-
message:
|
|
2299
|
-
'No enabled instances matched the provided instance filter.',
|
|
2300
|
-
},
|
|
2301
|
-
400
|
|
2029
|
+
const forwardQuery = sanitizeForwardQueryParams(
|
|
2030
|
+
new URL(c.req.url).searchParams
|
|
2302
2031
|
);
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
instance,
|
|
2314
|
-
path: '/stats/latency',
|
|
2315
|
-
query: forwardQuery,
|
|
2316
|
-
schema: LatencyStatsResponseSchema,
|
|
2317
|
-
fetchImpl,
|
|
2318
|
-
})
|
|
2319
|
-
)
|
|
2320
|
-
);
|
|
2321
|
-
|
|
2322
|
-
const failedInstances = results
|
|
2323
|
-
.filter(
|
|
2324
|
-
(result): result is { ok: false; failure: GatewayFailure } =>
|
|
2325
|
-
!result.ok
|
|
2326
|
-
)
|
|
2327
|
-
.map((result) => result.failure);
|
|
2328
|
-
const successfulResults = results.filter(
|
|
2329
|
-
(result): result is { ok: true; data: LatencyStatsResponse } =>
|
|
2330
|
-
result.ok
|
|
2331
|
-
);
|
|
2332
|
-
|
|
2333
|
-
if (successfulResults.length === 0) {
|
|
2334
|
-
return allInstancesFailedResponse(c, failedInstances);
|
|
2335
|
-
}
|
|
2032
|
+
const fetched = await fetchFromSelectedInstances<LatencyStatsResponse>({
|
|
2033
|
+
c,
|
|
2034
|
+
selectedInstances: selection.selectedInstances,
|
|
2035
|
+
path: '/stats/latency',
|
|
2036
|
+
query: forwardQuery,
|
|
2037
|
+
schema: LatencyStatsResponseSchema,
|
|
2038
|
+
});
|
|
2039
|
+
if (!fetched.ok) {
|
|
2040
|
+
return fetched.response;
|
|
2041
|
+
}
|
|
2336
2042
|
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2043
|
+
return c.json({
|
|
2044
|
+
push: averagePercentiles(
|
|
2045
|
+
fetched.successfulResults.map((result) => result.data.push)
|
|
2046
|
+
),
|
|
2047
|
+
pull: averagePercentiles(
|
|
2048
|
+
fetched.successfulResults.map((result) => result.data.pull)
|
|
2049
|
+
),
|
|
2050
|
+
range: query.range,
|
|
2051
|
+
partial: fetched.failedInstances.length > 0,
|
|
2052
|
+
failedInstances: fetched.failedInstances,
|
|
2053
|
+
});
|
|
2347
2054
|
});
|
|
2348
2055
|
}
|
|
2349
2056
|
);
|
|
@@ -2368,95 +2075,62 @@ export function createConsoleGatewayRoutes(
|
|
|
2368
2075
|
}),
|
|
2369
2076
|
zValidator('query', GatewayPaginatedQuerySchema),
|
|
2370
2077
|
async (c) => {
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2078
|
+
return withGatewayAuth(c, async () => {
|
|
2079
|
+
const query = c.req.valid('query');
|
|
2080
|
+
const selection = selectTargetInstances(c, query);
|
|
2081
|
+
if (!selection.ok) {
|
|
2082
|
+
return selection.response;
|
|
2083
|
+
}
|
|
2375
2084
|
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
return c.json(
|
|
2380
|
-
{
|
|
2381
|
-
error: 'NO_INSTANCES_SELECTED',
|
|
2382
|
-
message:
|
|
2383
|
-
'No enabled instances matched the provided instance filter.',
|
|
2384
|
-
},
|
|
2385
|
-
400
|
|
2085
|
+
const targetCount = query.offset + query.limit;
|
|
2086
|
+
const forwardQuery = sanitizeForwardQueryParams(
|
|
2087
|
+
new URL(c.req.url).searchParams
|
|
2386
2088
|
);
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
forwardQuery.delete('offset');
|
|
2395
|
-
const pageSchema = ConsolePaginatedResponseSchema(
|
|
2396
|
-
ConsoleCommitListItemSchema
|
|
2397
|
-
);
|
|
2398
|
-
|
|
2399
|
-
const results = await Promise.all(
|
|
2400
|
-
selectedInstances.map((instance) =>
|
|
2401
|
-
fetchDownstreamPaged({
|
|
2089
|
+
forwardQuery.delete('limit');
|
|
2090
|
+
forwardQuery.delete('offset');
|
|
2091
|
+
const pageSchema = ConsolePaginatedResponseSchema(
|
|
2092
|
+
ConsoleCommitListItemSchema
|
|
2093
|
+
);
|
|
2094
|
+
const fetched =
|
|
2095
|
+
await fetchPagedFromSelectedInstances<ConsoleCommitListItem>({
|
|
2402
2096
|
c,
|
|
2403
|
-
|
|
2097
|
+
selectedInstances: selection.selectedInstances,
|
|
2404
2098
|
path: '/commits',
|
|
2405
2099
|
query: forwardQuery,
|
|
2406
2100
|
targetCount,
|
|
2407
2101
|
schema: pageSchema,
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
const failedInstances = results
|
|
2414
|
-
.filter(
|
|
2415
|
-
(result): result is { ok: false; failure: GatewayFailure } =>
|
|
2416
|
-
!result.ok
|
|
2417
|
-
)
|
|
2418
|
-
.map((result) => result.failure);
|
|
2419
|
-
const successful = results
|
|
2420
|
-
.map((result, index) => ({
|
|
2421
|
-
result,
|
|
2422
|
-
instance: selectedInstances[index],
|
|
2423
|
-
}))
|
|
2424
|
-
.filter(
|
|
2425
|
-
(
|
|
2426
|
-
entry
|
|
2427
|
-
): entry is {
|
|
2428
|
-
result: { ok: true; items: ConsoleCommitListItem[]; total: number };
|
|
2429
|
-
instance: ConsoleGatewayInstance;
|
|
2430
|
-
} => Boolean(entry.instance) && entry.result.ok
|
|
2431
|
-
);
|
|
2432
|
-
|
|
2433
|
-
if (successful.length === 0) {
|
|
2434
|
-
return allInstancesFailedResponse(c, failedInstances);
|
|
2435
|
-
}
|
|
2102
|
+
});
|
|
2103
|
+
if (!fetched.ok) {
|
|
2104
|
+
return fetched.response;
|
|
2105
|
+
}
|
|
2436
2106
|
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2107
|
+
const merged = fetched.successfulResults
|
|
2108
|
+
.flatMap(({ items, instance }) =>
|
|
2109
|
+
items.map((commit) => ({
|
|
2110
|
+
...commit,
|
|
2111
|
+
instanceId: instance.instanceId,
|
|
2112
|
+
federatedCommitId: `${instance.instanceId}:${commit.commitSeq}`,
|
|
2113
|
+
}))
|
|
2114
|
+
)
|
|
2115
|
+
.sort((a, b) => {
|
|
2116
|
+
const byTime = compareIsoDesc(a.createdAt, b.createdAt);
|
|
2117
|
+
if (byTime !== 0) return byTime;
|
|
2118
|
+
const byInstance = a.instanceId.localeCompare(b.instanceId);
|
|
2119
|
+
if (byInstance !== 0) return byInstance;
|
|
2120
|
+
return b.commitSeq - a.commitSeq;
|
|
2121
|
+
});
|
|
2122
|
+
|
|
2123
|
+
return c.json({
|
|
2124
|
+
items: merged.slice(query.offset, query.offset + query.limit),
|
|
2125
|
+
total: fetched.successfulResults.reduce(
|
|
2126
|
+
(acc, entry) => acc + entry.total,
|
|
2127
|
+
0
|
|
2128
|
+
),
|
|
2129
|
+
offset: query.offset,
|
|
2130
|
+
limit: query.limit,
|
|
2131
|
+
partial: fetched.failedInstances.length > 0,
|
|
2132
|
+
failedInstances: fetched.failedInstances,
|
|
2451
2133
|
});
|
|
2452
|
-
|
|
2453
|
-
return c.json({
|
|
2454
|
-
items: merged.slice(query.offset, query.offset + query.limit),
|
|
2455
|
-
total: successful.reduce((acc, entry) => acc + entry.result.total, 0),
|
|
2456
|
-
offset: query.offset,
|
|
2457
|
-
limit: query.limit,
|
|
2458
|
-
partial: failedInstances.length > 0,
|
|
2459
|
-
failedInstances,
|
|
2460
2134
|
});
|
|
2461
2135
|
}
|
|
2462
2136
|
);
|
|
@@ -2483,54 +2157,51 @@ export function createConsoleGatewayRoutes(
|
|
|
2483
2157
|
ConsolePartitionQuerySchema.extend(GatewayInstanceFilterSchema.shape)
|
|
2484
2158
|
),
|
|
2485
2159
|
async (c) => {
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2160
|
+
return withGatewayAuth(c, async () => {
|
|
2161
|
+
const { seq } = c.req.valid('param');
|
|
2162
|
+
const query = c.req.valid('query');
|
|
2163
|
+
const target = resolveCommitTarget({ seq, instances, query });
|
|
2164
|
+
if (!target.ok) {
|
|
2165
|
+
return c.json(
|
|
2166
|
+
{
|
|
2167
|
+
error: target.error,
|
|
2168
|
+
...(target.message ? { message: target.message } : {}),
|
|
2169
|
+
},
|
|
2170
|
+
target.status
|
|
2171
|
+
);
|
|
2172
|
+
}
|
|
2490
2173
|
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
const target = resolveCommitTarget({ seq, instances, query });
|
|
2494
|
-
if (!target.ok) {
|
|
2495
|
-
return c.json(
|
|
2496
|
-
{
|
|
2497
|
-
error: target.error,
|
|
2498
|
-
...(target.message ? { message: target.message } : {}),
|
|
2499
|
-
},
|
|
2500
|
-
target.status
|
|
2174
|
+
const forwardQuery = sanitizeForwardQueryParams(
|
|
2175
|
+
new URL(c.req.url).searchParams
|
|
2501
2176
|
);
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
path: `/commits/${target.localCommitSeq}`,
|
|
2511
|
-
query: forwardQuery,
|
|
2512
|
-
schema: ConsoleCommitDetailSchema,
|
|
2513
|
-
fetchImpl,
|
|
2514
|
-
});
|
|
2177
|
+
const result = await fetchDownstreamJson({
|
|
2178
|
+
c,
|
|
2179
|
+
instance: target.instance,
|
|
2180
|
+
path: `/commits/${target.localCommitSeq}`,
|
|
2181
|
+
query: forwardQuery,
|
|
2182
|
+
schema: ConsoleCommitDetailSchema,
|
|
2183
|
+
fetchImpl,
|
|
2184
|
+
});
|
|
2515
2185
|
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2186
|
+
if (!result.ok) {
|
|
2187
|
+
if (result.failure.status === 404) {
|
|
2188
|
+
return c.json({ error: 'NOT_FOUND' }, 404);
|
|
2189
|
+
}
|
|
2190
|
+
return c.json(
|
|
2191
|
+
{
|
|
2192
|
+
error: 'DOWNSTREAM_UNAVAILABLE',
|
|
2193
|
+
failedInstances: [result.failure],
|
|
2194
|
+
},
|
|
2195
|
+
502
|
|
2196
|
+
);
|
|
2519
2197
|
}
|
|
2520
|
-
return c.json(
|
|
2521
|
-
{
|
|
2522
|
-
error: 'DOWNSTREAM_UNAVAILABLE',
|
|
2523
|
-
failedInstances: [result.failure],
|
|
2524
|
-
},
|
|
2525
|
-
502
|
|
2526
|
-
);
|
|
2527
|
-
}
|
|
2528
2198
|
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2199
|
+
return c.json({
|
|
2200
|
+
...result.data,
|
|
2201
|
+
instanceId: target.instance.instanceId,
|
|
2202
|
+
federatedCommitId: `${target.instance.instanceId}:${result.data.commitSeq}`,
|
|
2203
|
+
localCommitSeq: result.data.commitSeq,
|
|
2204
|
+
});
|
|
2534
2205
|
});
|
|
2535
2206
|
}
|
|
2536
2207
|
);
|
|
@@ -2555,93 +2226,59 @@ export function createConsoleGatewayRoutes(
|
|
|
2555
2226
|
}),
|
|
2556
2227
|
zValidator('query', GatewayPaginatedQuerySchema),
|
|
2557
2228
|
async (c) => {
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
const selectedInstances = selectInstances({ instances, query });
|
|
2565
|
-
if (selectedInstances.length === 0) {
|
|
2566
|
-
return c.json(
|
|
2567
|
-
{
|
|
2568
|
-
error: 'NO_INSTANCES_SELECTED',
|
|
2569
|
-
message:
|
|
2570
|
-
'No enabled instances matched the provided instance filter.',
|
|
2571
|
-
},
|
|
2572
|
-
400
|
|
2573
|
-
);
|
|
2574
|
-
}
|
|
2575
|
-
|
|
2576
|
-
const targetCount = query.offset + query.limit;
|
|
2577
|
-
const forwardQuery = sanitizeForwardQueryParams(
|
|
2578
|
-
new URL(c.req.url).searchParams
|
|
2579
|
-
);
|
|
2580
|
-
forwardQuery.delete('limit');
|
|
2581
|
-
forwardQuery.delete('offset');
|
|
2582
|
-
const pageSchema = ConsolePaginatedResponseSchema(ConsoleClientSchema);
|
|
2583
|
-
|
|
2584
|
-
const results = await Promise.all(
|
|
2585
|
-
selectedInstances.map((instance) =>
|
|
2586
|
-
fetchDownstreamPaged({
|
|
2587
|
-
c,
|
|
2588
|
-
instance,
|
|
2589
|
-
path: '/clients',
|
|
2590
|
-
query: forwardQuery,
|
|
2591
|
-
targetCount,
|
|
2592
|
-
schema: pageSchema,
|
|
2593
|
-
fetchImpl,
|
|
2594
|
-
})
|
|
2595
|
-
)
|
|
2596
|
-
);
|
|
2229
|
+
return withGatewayAuth(c, async () => {
|
|
2230
|
+
const query = c.req.valid('query');
|
|
2231
|
+
const selection = selectTargetInstances(c, query);
|
|
2232
|
+
if (!selection.ok) {
|
|
2233
|
+
return selection.response;
|
|
2234
|
+
}
|
|
2597
2235
|
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
(
|
|
2601
|
-
!result.ok
|
|
2602
|
-
)
|
|
2603
|
-
.map((result) => result.failure);
|
|
2604
|
-
const successful = results
|
|
2605
|
-
.map((result, index) => ({
|
|
2606
|
-
result,
|
|
2607
|
-
instance: selectedInstances[index],
|
|
2608
|
-
}))
|
|
2609
|
-
.filter(
|
|
2610
|
-
(
|
|
2611
|
-
entry
|
|
2612
|
-
): entry is {
|
|
2613
|
-
result: { ok: true; items: ConsoleClient[]; total: number };
|
|
2614
|
-
instance: ConsoleGatewayInstance;
|
|
2615
|
-
} => Boolean(entry.instance) && entry.result.ok
|
|
2236
|
+
const targetCount = query.offset + query.limit;
|
|
2237
|
+
const forwardQuery = sanitizeForwardQueryParams(
|
|
2238
|
+
new URL(c.req.url).searchParams
|
|
2616
2239
|
);
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
federatedClientId: `${instance.instanceId}:${client.clientId}`,
|
|
2628
|
-
}))
|
|
2629
|
-
)
|
|
2630
|
-
.sort((a, b) => {
|
|
2631
|
-
const byTime = compareIsoDesc(a.updatedAt, b.updatedAt);
|
|
2632
|
-
if (byTime !== 0) return byTime;
|
|
2633
|
-
const byInstance = a.instanceId.localeCompare(b.instanceId);
|
|
2634
|
-
if (byInstance !== 0) return byInstance;
|
|
2635
|
-
return a.clientId.localeCompare(b.clientId);
|
|
2240
|
+
forwardQuery.delete('limit');
|
|
2241
|
+
forwardQuery.delete('offset');
|
|
2242
|
+
const pageSchema = ConsolePaginatedResponseSchema(ConsoleClientSchema);
|
|
2243
|
+
const fetched = await fetchPagedFromSelectedInstances<ConsoleClient>({
|
|
2244
|
+
c,
|
|
2245
|
+
selectedInstances: selection.selectedInstances,
|
|
2246
|
+
path: '/clients',
|
|
2247
|
+
query: forwardQuery,
|
|
2248
|
+
targetCount,
|
|
2249
|
+
schema: pageSchema,
|
|
2636
2250
|
});
|
|
2251
|
+
if (!fetched.ok) {
|
|
2252
|
+
return fetched.response;
|
|
2253
|
+
}
|
|
2637
2254
|
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2255
|
+
const merged = fetched.successfulResults
|
|
2256
|
+
.flatMap(({ items, instance }) =>
|
|
2257
|
+
items.map((client) => ({
|
|
2258
|
+
...client,
|
|
2259
|
+
instanceId: instance.instanceId,
|
|
2260
|
+
federatedClientId: `${instance.instanceId}:${client.clientId}`,
|
|
2261
|
+
}))
|
|
2262
|
+
)
|
|
2263
|
+
.sort((a, b) => {
|
|
2264
|
+
const byTime = compareIsoDesc(a.updatedAt, b.updatedAt);
|
|
2265
|
+
if (byTime !== 0) return byTime;
|
|
2266
|
+
const byInstance = a.instanceId.localeCompare(b.instanceId);
|
|
2267
|
+
if (byInstance !== 0) return byInstance;
|
|
2268
|
+
return a.clientId.localeCompare(b.clientId);
|
|
2269
|
+
});
|
|
2270
|
+
|
|
2271
|
+
return c.json({
|
|
2272
|
+
items: merged.slice(query.offset, query.offset + query.limit),
|
|
2273
|
+
total: fetched.successfulResults.reduce(
|
|
2274
|
+
(acc, entry) => acc + entry.total,
|
|
2275
|
+
0
|
|
2276
|
+
),
|
|
2277
|
+
offset: query.offset,
|
|
2278
|
+
limit: query.limit,
|
|
2279
|
+
partial: fetched.failedInstances.length > 0,
|
|
2280
|
+
failedInstances: fetched.failedInstances,
|
|
2281
|
+
});
|
|
2645
2282
|
});
|
|
2646
2283
|
}
|
|
2647
2284
|
);
|
|
@@ -2666,110 +2303,79 @@ export function createConsoleGatewayRoutes(
|
|
|
2666
2303
|
}),
|
|
2667
2304
|
zValidator('query', GatewayTimelineQuerySchema),
|
|
2668
2305
|
async (c) => {
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2306
|
+
return withGatewayAuth(c, async () => {
|
|
2307
|
+
const query = c.req.valid('query');
|
|
2308
|
+
const selection = selectTargetInstances(c, query);
|
|
2309
|
+
if (!selection.ok) {
|
|
2310
|
+
return selection.response;
|
|
2311
|
+
}
|
|
2673
2312
|
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
return c.json(
|
|
2678
|
-
{
|
|
2679
|
-
error: 'NO_INSTANCES_SELECTED',
|
|
2680
|
-
message:
|
|
2681
|
-
'No enabled instances matched the provided instance filter.',
|
|
2682
|
-
},
|
|
2683
|
-
400
|
|
2313
|
+
const targetCount = query.offset + query.limit;
|
|
2314
|
+
const forwardQuery = sanitizeForwardQueryParams(
|
|
2315
|
+
new URL(c.req.url).searchParams
|
|
2684
2316
|
);
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
forwardQuery.delete('offset');
|
|
2693
|
-
const pageSchema = ConsolePaginatedResponseSchema(
|
|
2694
|
-
ConsoleTimelineItemSchema
|
|
2695
|
-
);
|
|
2696
|
-
|
|
2697
|
-
const results = await Promise.all(
|
|
2698
|
-
selectedInstances.map((instance) =>
|
|
2699
|
-
fetchDownstreamPaged({
|
|
2317
|
+
forwardQuery.delete('limit');
|
|
2318
|
+
forwardQuery.delete('offset');
|
|
2319
|
+
const pageSchema = ConsolePaginatedResponseSchema(
|
|
2320
|
+
ConsoleTimelineItemSchema
|
|
2321
|
+
);
|
|
2322
|
+
const fetched =
|
|
2323
|
+
await fetchPagedFromSelectedInstances<ConsoleTimelineItem>({
|
|
2700
2324
|
c,
|
|
2701
|
-
|
|
2325
|
+
selectedInstances: selection.selectedInstances,
|
|
2702
2326
|
path: '/timeline',
|
|
2703
2327
|
query: forwardQuery,
|
|
2704
2328
|
targetCount,
|
|
2705
2329
|
schema: pageSchema,
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
const failedInstances = results
|
|
2712
|
-
.filter(
|
|
2713
|
-
(result): result is { ok: false; failure: GatewayFailure } =>
|
|
2714
|
-
!result.ok
|
|
2715
|
-
)
|
|
2716
|
-
.map((result) => result.failure);
|
|
2717
|
-
const successful = results
|
|
2718
|
-
.map((result, index) => ({
|
|
2719
|
-
result,
|
|
2720
|
-
instance: selectedInstances[index],
|
|
2721
|
-
}))
|
|
2722
|
-
.filter(
|
|
2723
|
-
(
|
|
2724
|
-
entry
|
|
2725
|
-
): entry is {
|
|
2726
|
-
result: { ok: true; items: ConsoleTimelineItem[]; total: number };
|
|
2727
|
-
instance: ConsoleGatewayInstance;
|
|
2728
|
-
} => Boolean(entry.instance) && entry.result.ok
|
|
2729
|
-
);
|
|
2730
|
-
|
|
2731
|
-
if (successful.length === 0) {
|
|
2732
|
-
return allInstancesFailedResponse(c, failedInstances);
|
|
2733
|
-
}
|
|
2330
|
+
});
|
|
2331
|
+
if (!fetched.ok) {
|
|
2332
|
+
return fetched.response;
|
|
2333
|
+
}
|
|
2734
2334
|
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2335
|
+
const merged = fetched.successfulResults
|
|
2336
|
+
.flatMap(({ items, instance }) =>
|
|
2337
|
+
items.map((item) => {
|
|
2338
|
+
const localCommitSeq =
|
|
2339
|
+
item.type === 'commit'
|
|
2340
|
+
? (item.commit?.commitSeq ?? null)
|
|
2341
|
+
: null;
|
|
2342
|
+
const localEventId =
|
|
2343
|
+
item.type === 'event' ? (item.event?.eventId ?? null) : null;
|
|
2344
|
+
const localIdSegment =
|
|
2345
|
+
item.type === 'commit'
|
|
2346
|
+
? String(localCommitSeq ?? 'unknown')
|
|
2347
|
+
: String(localEventId ?? 'unknown');
|
|
2348
|
+
|
|
2349
|
+
return {
|
|
2350
|
+
...item,
|
|
2351
|
+
instanceId: instance.instanceId,
|
|
2352
|
+
federatedTimelineId: `${instance.instanceId}:${item.type}:${localIdSegment}`,
|
|
2353
|
+
localCommitSeq,
|
|
2354
|
+
localEventId,
|
|
2355
|
+
};
|
|
2356
|
+
})
|
|
2357
|
+
)
|
|
2358
|
+
.sort((a, b) => {
|
|
2359
|
+
const byTime = compareIsoDesc(a.timestamp, b.timestamp);
|
|
2360
|
+
if (byTime !== 0) return byTime;
|
|
2361
|
+
const byInstance = a.instanceId.localeCompare(b.instanceId);
|
|
2362
|
+
if (byInstance !== 0) return byInstance;
|
|
2363
|
+
const aLocalId = a.localCommitSeq ?? a.localEventId ?? 0;
|
|
2364
|
+
const bLocalId = b.localCommitSeq ?? b.localEventId ?? 0;
|
|
2365
|
+
return bLocalId - aLocalId;
|
|
2366
|
+
});
|
|
2367
|
+
|
|
2368
|
+
return c.json({
|
|
2369
|
+
items: merged.slice(query.offset, query.offset + query.limit),
|
|
2370
|
+
total: fetched.successfulResults.reduce(
|
|
2371
|
+
(acc, entry) => acc + entry.total,
|
|
2372
|
+
0
|
|
2373
|
+
),
|
|
2374
|
+
offset: query.offset,
|
|
2375
|
+
limit: query.limit,
|
|
2376
|
+
partial: fetched.failedInstances.length > 0,
|
|
2377
|
+
failedInstances: fetched.failedInstances,
|
|
2764
2378
|
});
|
|
2765
|
-
|
|
2766
|
-
return c.json({
|
|
2767
|
-
items: merged.slice(query.offset, query.offset + query.limit),
|
|
2768
|
-
total: successful.reduce((acc, entry) => acc + entry.result.total, 0),
|
|
2769
|
-
offset: query.offset,
|
|
2770
|
-
limit: query.limit,
|
|
2771
|
-
partial: failedInstances.length > 0,
|
|
2772
|
-
failedInstances,
|
|
2773
2379
|
});
|
|
2774
2380
|
}
|
|
2775
2381
|
);
|
|
@@ -2794,96 +2400,63 @@ export function createConsoleGatewayRoutes(
|
|
|
2794
2400
|
}),
|
|
2795
2401
|
zValidator('query', GatewayOperationsQuerySchema),
|
|
2796
2402
|
async (c) => {
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2403
|
+
return withGatewayAuth(c, async () => {
|
|
2404
|
+
const query = c.req.valid('query');
|
|
2405
|
+
const selection = selectTargetInstances(c, query);
|
|
2406
|
+
if (!selection.ok) {
|
|
2407
|
+
return selection.response;
|
|
2408
|
+
}
|
|
2801
2409
|
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
return c.json(
|
|
2806
|
-
{
|
|
2807
|
-
error: 'NO_INSTANCES_SELECTED',
|
|
2808
|
-
message:
|
|
2809
|
-
'No enabled instances matched the provided instance filter.',
|
|
2810
|
-
},
|
|
2811
|
-
400
|
|
2410
|
+
const targetCount = query.offset + query.limit;
|
|
2411
|
+
const forwardQuery = sanitizeForwardQueryParams(
|
|
2412
|
+
new URL(c.req.url).searchParams
|
|
2812
2413
|
);
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
forwardQuery.delete('offset');
|
|
2821
|
-
const pageSchema = ConsolePaginatedResponseSchema(
|
|
2822
|
-
ConsoleOperationEventSchema
|
|
2823
|
-
);
|
|
2824
|
-
|
|
2825
|
-
const results = await Promise.all(
|
|
2826
|
-
selectedInstances.map((instance) =>
|
|
2827
|
-
fetchDownstreamPaged({
|
|
2414
|
+
forwardQuery.delete('limit');
|
|
2415
|
+
forwardQuery.delete('offset');
|
|
2416
|
+
const pageSchema = ConsolePaginatedResponseSchema(
|
|
2417
|
+
ConsoleOperationEventSchema
|
|
2418
|
+
);
|
|
2419
|
+
const fetched =
|
|
2420
|
+
await fetchPagedFromSelectedInstances<ConsoleOperationEvent>({
|
|
2828
2421
|
c,
|
|
2829
|
-
|
|
2422
|
+
selectedInstances: selection.selectedInstances,
|
|
2830
2423
|
path: '/operations',
|
|
2831
2424
|
query: forwardQuery,
|
|
2832
2425
|
targetCount,
|
|
2833
2426
|
schema: pageSchema,
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
const failedInstances = results
|
|
2840
|
-
.filter(
|
|
2841
|
-
(result): result is { ok: false; failure: GatewayFailure } =>
|
|
2842
|
-
!result.ok
|
|
2843
|
-
)
|
|
2844
|
-
.map((result) => result.failure);
|
|
2845
|
-
const successful = results
|
|
2846
|
-
.map((result, index) => ({
|
|
2847
|
-
result,
|
|
2848
|
-
instance: selectedInstances[index],
|
|
2849
|
-
}))
|
|
2850
|
-
.filter(
|
|
2851
|
-
(
|
|
2852
|
-
entry
|
|
2853
|
-
): entry is {
|
|
2854
|
-
result: { ok: true; items: ConsoleOperationEvent[]; total: number };
|
|
2855
|
-
instance: ConsoleGatewayInstance;
|
|
2856
|
-
} => Boolean(entry.instance) && entry.result.ok
|
|
2857
|
-
);
|
|
2858
|
-
|
|
2859
|
-
if (successful.length === 0) {
|
|
2860
|
-
return allInstancesFailedResponse(c, failedInstances);
|
|
2861
|
-
}
|
|
2427
|
+
});
|
|
2428
|
+
if (!fetched.ok) {
|
|
2429
|
+
return fetched.response;
|
|
2430
|
+
}
|
|
2862
2431
|
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2432
|
+
const merged = fetched.successfulResults
|
|
2433
|
+
.flatMap(({ items, instance }) =>
|
|
2434
|
+
items.map((operation) => ({
|
|
2435
|
+
...operation,
|
|
2436
|
+
instanceId: instance.instanceId,
|
|
2437
|
+
federatedOperationId: `${instance.instanceId}:${operation.operationId}`,
|
|
2438
|
+
localOperationId: operation.operationId,
|
|
2439
|
+
}))
|
|
2440
|
+
)
|
|
2441
|
+
.sort((a, b) => {
|
|
2442
|
+
const byTime = compareIsoDesc(a.createdAt, b.createdAt);
|
|
2443
|
+
if (byTime !== 0) return byTime;
|
|
2444
|
+
const byInstance = a.instanceId.localeCompare(b.instanceId);
|
|
2445
|
+
if (byInstance !== 0) return byInstance;
|
|
2446
|
+
return b.localOperationId - a.localOperationId;
|
|
2447
|
+
});
|
|
2448
|
+
|
|
2449
|
+
return c.json({
|
|
2450
|
+
items: merged.slice(query.offset, query.offset + query.limit),
|
|
2451
|
+
total: fetched.successfulResults.reduce(
|
|
2452
|
+
(acc, entry) => acc + entry.total,
|
|
2453
|
+
0
|
|
2454
|
+
),
|
|
2455
|
+
offset: query.offset,
|
|
2456
|
+
limit: query.limit,
|
|
2457
|
+
partial: fetched.failedInstances.length > 0,
|
|
2458
|
+
failedInstances: fetched.failedInstances,
|
|
2878
2459
|
});
|
|
2879
|
-
|
|
2880
|
-
return c.json({
|
|
2881
|
-
items: merged.slice(query.offset, query.offset + query.limit),
|
|
2882
|
-
total: successful.reduce((acc, entry) => acc + entry.result.total, 0),
|
|
2883
|
-
offset: query.offset,
|
|
2884
|
-
limit: query.limit,
|
|
2885
|
-
partial: failedInstances.length > 0,
|
|
2886
|
-
failedInstances,
|
|
2887
2460
|
});
|
|
2888
2461
|
}
|
|
2889
2462
|
);
|
|
@@ -2908,96 +2481,63 @@ export function createConsoleGatewayRoutes(
|
|
|
2908
2481
|
}),
|
|
2909
2482
|
zValidator('query', GatewayEventsQuerySchema),
|
|
2910
2483
|
async (c) => {
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2484
|
+
return withGatewayAuth(c, async () => {
|
|
2485
|
+
const query = c.req.valid('query');
|
|
2486
|
+
const selection = selectTargetInstances(c, query);
|
|
2487
|
+
if (!selection.ok) {
|
|
2488
|
+
return selection.response;
|
|
2489
|
+
}
|
|
2915
2490
|
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
return c.json(
|
|
2920
|
-
{
|
|
2921
|
-
error: 'NO_INSTANCES_SELECTED',
|
|
2922
|
-
message:
|
|
2923
|
-
'No enabled instances matched the provided instance filter.',
|
|
2924
|
-
},
|
|
2925
|
-
400
|
|
2491
|
+
const targetCount = query.offset + query.limit;
|
|
2492
|
+
const forwardQuery = sanitizeForwardQueryParams(
|
|
2493
|
+
new URL(c.req.url).searchParams
|
|
2926
2494
|
);
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
forwardQuery.delete('offset');
|
|
2935
|
-
const pageSchema = ConsolePaginatedResponseSchema(
|
|
2936
|
-
ConsoleRequestEventSchema
|
|
2937
|
-
);
|
|
2938
|
-
|
|
2939
|
-
const results = await Promise.all(
|
|
2940
|
-
selectedInstances.map((instance) =>
|
|
2941
|
-
fetchDownstreamPaged({
|
|
2495
|
+
forwardQuery.delete('limit');
|
|
2496
|
+
forwardQuery.delete('offset');
|
|
2497
|
+
const pageSchema = ConsolePaginatedResponseSchema(
|
|
2498
|
+
ConsoleRequestEventSchema
|
|
2499
|
+
);
|
|
2500
|
+
const fetched =
|
|
2501
|
+
await fetchPagedFromSelectedInstances<ConsoleRequestEvent>({
|
|
2942
2502
|
c,
|
|
2943
|
-
|
|
2503
|
+
selectedInstances: selection.selectedInstances,
|
|
2944
2504
|
path: '/events',
|
|
2945
2505
|
query: forwardQuery,
|
|
2946
2506
|
targetCount,
|
|
2947
2507
|
schema: pageSchema,
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
const failedInstances = results
|
|
2954
|
-
.filter(
|
|
2955
|
-
(result): result is { ok: false; failure: GatewayFailure } =>
|
|
2956
|
-
!result.ok
|
|
2957
|
-
)
|
|
2958
|
-
.map((result) => result.failure);
|
|
2959
|
-
const successful = results
|
|
2960
|
-
.map((result, index) => ({
|
|
2961
|
-
result,
|
|
2962
|
-
instance: selectedInstances[index],
|
|
2963
|
-
}))
|
|
2964
|
-
.filter(
|
|
2965
|
-
(
|
|
2966
|
-
entry
|
|
2967
|
-
): entry is {
|
|
2968
|
-
result: { ok: true; items: ConsoleRequestEvent[]; total: number };
|
|
2969
|
-
instance: ConsoleGatewayInstance;
|
|
2970
|
-
} => Boolean(entry.instance) && entry.result.ok
|
|
2971
|
-
);
|
|
2972
|
-
|
|
2973
|
-
if (successful.length === 0) {
|
|
2974
|
-
return allInstancesFailedResponse(c, failedInstances);
|
|
2975
|
-
}
|
|
2508
|
+
});
|
|
2509
|
+
if (!fetched.ok) {
|
|
2510
|
+
return fetched.response;
|
|
2511
|
+
}
|
|
2976
2512
|
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2513
|
+
const merged = fetched.successfulResults
|
|
2514
|
+
.flatMap(({ items, instance }) =>
|
|
2515
|
+
items.map((event) => ({
|
|
2516
|
+
...event,
|
|
2517
|
+
instanceId: instance.instanceId,
|
|
2518
|
+
federatedEventId: `${instance.instanceId}:${event.eventId}`,
|
|
2519
|
+
localEventId: event.eventId,
|
|
2520
|
+
}))
|
|
2521
|
+
)
|
|
2522
|
+
.sort((a, b) => {
|
|
2523
|
+
const byTime = compareIsoDesc(a.createdAt, b.createdAt);
|
|
2524
|
+
if (byTime !== 0) return byTime;
|
|
2525
|
+
const byInstance = a.instanceId.localeCompare(b.instanceId);
|
|
2526
|
+
if (byInstance !== 0) return byInstance;
|
|
2527
|
+
return b.localEventId - a.localEventId;
|
|
2528
|
+
});
|
|
2529
|
+
|
|
2530
|
+
return c.json({
|
|
2531
|
+
items: merged.slice(query.offset, query.offset + query.limit),
|
|
2532
|
+
total: fetched.successfulResults.reduce(
|
|
2533
|
+
(acc, entry) => acc + entry.total,
|
|
2534
|
+
0
|
|
2535
|
+
),
|
|
2536
|
+
offset: query.offset,
|
|
2537
|
+
limit: query.limit,
|
|
2538
|
+
partial: fetched.failedInstances.length > 0,
|
|
2539
|
+
failedInstances: fetched.failedInstances,
|
|
2992
2540
|
});
|
|
2993
|
-
|
|
2994
|
-
return c.json({
|
|
2995
|
-
items: merged.slice(query.offset, query.offset + query.limit),
|
|
2996
|
-
total: successful.reduce((acc, entry) => acc + entry.result.total, 0),
|
|
2997
|
-
offset: query.offset,
|
|
2998
|
-
limit: query.limit,
|
|
2999
|
-
partial: failedInstances.length > 0,
|
|
3000
|
-
failedInstances,
|
|
3001
2541
|
});
|
|
3002
2542
|
}
|
|
3003
2543
|
);
|
|
@@ -3024,6 +2564,7 @@ export function createConsoleGatewayRoutes(
|
|
|
3024
2564
|
heartbeatInterval: ReturnType<typeof setInterval> | null;
|
|
3025
2565
|
authTimeout: ReturnType<typeof setTimeout> | null;
|
|
3026
2566
|
isAuthenticated: boolean;
|
|
2567
|
+
startAuthenticatedSession: ((token: string | null) => void) | null;
|
|
3027
2568
|
}
|
|
3028
2569
|
>();
|
|
3029
2570
|
|
|
@@ -3066,17 +2607,6 @@ export function createConsoleGatewayRoutes(
|
|
|
3066
2607
|
return options.authenticate(authContext);
|
|
3067
2608
|
};
|
|
3068
2609
|
|
|
3069
|
-
const closeUnauthenticated = (ws: WebSocketLike) => {
|
|
3070
|
-
try {
|
|
3071
|
-
ws.send(
|
|
3072
|
-
JSON.stringify({ type: 'error', message: 'UNAUTHENTICATED' })
|
|
3073
|
-
);
|
|
3074
|
-
} catch {
|
|
3075
|
-
// no-op
|
|
3076
|
-
}
|
|
3077
|
-
ws.close(4001, 'Unauthenticated');
|
|
3078
|
-
};
|
|
3079
|
-
|
|
3080
2610
|
const cleanup = (ws: WebSocketLike) => {
|
|
3081
2611
|
const state = liveState.get(ws);
|
|
3082
2612
|
if (!state) return;
|
|
@@ -3115,11 +2645,15 @@ export function createConsoleGatewayRoutes(
|
|
|
3115
2645
|
heartbeatInterval: ReturnType<typeof setInterval> | null;
|
|
3116
2646
|
authTimeout: ReturnType<typeof setTimeout> | null;
|
|
3117
2647
|
isAuthenticated: boolean;
|
|
2648
|
+
startAuthenticatedSession:
|
|
2649
|
+
| ((token: string | null) => void)
|
|
2650
|
+
| null;
|
|
3118
2651
|
} = {
|
|
3119
2652
|
downstreamSockets: [],
|
|
3120
2653
|
heartbeatInterval: null,
|
|
3121
2654
|
authTimeout: null,
|
|
3122
2655
|
isAuthenticated: false,
|
|
2656
|
+
startAuthenticatedSession: null,
|
|
3123
2657
|
};
|
|
3124
2658
|
liveState.set(ws, state);
|
|
3125
2659
|
|
|
@@ -3248,6 +2782,7 @@ export function createConsoleGatewayRoutes(
|
|
|
3248
2782
|
}, heartbeatIntervalMs);
|
|
3249
2783
|
state.heartbeatInterval = heartbeatInterval;
|
|
3250
2784
|
};
|
|
2785
|
+
state.startAuthenticatedSession = startAuthenticatedSession;
|
|
3251
2786
|
|
|
3252
2787
|
if (initialAuth) {
|
|
3253
2788
|
startAuthenticatedSession(
|
|
@@ -3261,7 +2796,7 @@ export function createConsoleGatewayRoutes(
|
|
|
3261
2796
|
if (!current || current.isAuthenticated) {
|
|
3262
2797
|
return;
|
|
3263
2798
|
}
|
|
3264
|
-
|
|
2799
|
+
closeUnauthenticatedSocket(ws);
|
|
3265
2800
|
cleanup(ws);
|
|
3266
2801
|
}, 5_000);
|
|
3267
2802
|
},
|
|
@@ -3272,30 +2807,15 @@ export function createConsoleGatewayRoutes(
|
|
|
3272
2807
|
}
|
|
3273
2808
|
|
|
3274
2809
|
if (typeof event.data !== 'string') {
|
|
3275
|
-
|
|
2810
|
+
closeUnauthenticatedSocket(ws);
|
|
3276
2811
|
cleanup(ws);
|
|
3277
2812
|
return;
|
|
3278
2813
|
}
|
|
3279
2814
|
|
|
3280
|
-
|
|
3281
|
-
try {
|
|
3282
|
-
const parsed = JSON.parse(event.data) as {
|
|
3283
|
-
type?: unknown;
|
|
3284
|
-
token?: unknown;
|
|
3285
|
-
};
|
|
3286
|
-
if (
|
|
3287
|
-
parsed.type === 'auth' &&
|
|
3288
|
-
typeof parsed.token === 'string' &&
|
|
3289
|
-
parsed.token.trim().length > 0
|
|
3290
|
-
) {
|
|
3291
|
-
token = parsed.token;
|
|
3292
|
-
}
|
|
3293
|
-
} catch {
|
|
3294
|
-
// Invalid auth message will be handled below.
|
|
3295
|
-
}
|
|
2815
|
+
const token = parseWebSocketAuthToken(event.data);
|
|
3296
2816
|
|
|
3297
2817
|
if (!token) {
|
|
3298
|
-
|
|
2818
|
+
closeUnauthenticatedSocket(ws);
|
|
3299
2819
|
cleanup(ws);
|
|
3300
2820
|
return;
|
|
3301
2821
|
}
|
|
@@ -3306,131 +2826,11 @@ export function createConsoleGatewayRoutes(
|
|
|
3306
2826
|
return;
|
|
3307
2827
|
}
|
|
3308
2828
|
if (!auth) {
|
|
3309
|
-
|
|
2829
|
+
closeUnauthenticatedSocket(ws);
|
|
3310
2830
|
cleanup(ws);
|
|
3311
2831
|
return;
|
|
3312
2832
|
}
|
|
3313
|
-
|
|
3314
|
-
current.isAuthenticated = true;
|
|
3315
|
-
if (current.authTimeout) {
|
|
3316
|
-
clearTimeout(current.authTimeout);
|
|
3317
|
-
current.authTimeout = null;
|
|
3318
|
-
}
|
|
3319
|
-
|
|
3320
|
-
for (const instance of selectedInstances) {
|
|
3321
|
-
const downstreamQuery = new URLSearchParams();
|
|
3322
|
-
if (partitionId) {
|
|
3323
|
-
downstreamQuery.set('partitionId', partitionId);
|
|
3324
|
-
}
|
|
3325
|
-
if (replaySince) {
|
|
3326
|
-
downstreamQuery.set('since', replaySince);
|
|
3327
|
-
}
|
|
3328
|
-
downstreamQuery.set('replayLimit', String(replayLimit));
|
|
3329
|
-
|
|
3330
|
-
const downstreamUrl = buildConsoleEndpointUrl({
|
|
3331
|
-
instance,
|
|
3332
|
-
requestUrl: c.req.url,
|
|
3333
|
-
path: '/events/live',
|
|
3334
|
-
query: downstreamQuery,
|
|
3335
|
-
});
|
|
3336
|
-
|
|
3337
|
-
const downstreamSocket = createDownstreamSocket(downstreamUrl);
|
|
3338
|
-
const upstreamToken = token.trim();
|
|
3339
|
-
const downstreamToken =
|
|
3340
|
-
instance.token?.trim() ||
|
|
3341
|
-
(upstreamToken.length > 0 ? upstreamToken : null);
|
|
3342
|
-
if (downstreamToken && downstreamSocket.send) {
|
|
3343
|
-
downstreamSocket.onopen = () => {
|
|
3344
|
-
try {
|
|
3345
|
-
downstreamSocket.send?.(
|
|
3346
|
-
JSON.stringify({
|
|
3347
|
-
type: 'auth',
|
|
3348
|
-
token: downstreamToken,
|
|
3349
|
-
})
|
|
3350
|
-
);
|
|
3351
|
-
} catch {
|
|
3352
|
-
// no-op
|
|
3353
|
-
}
|
|
3354
|
-
};
|
|
3355
|
-
}
|
|
3356
|
-
|
|
3357
|
-
downstreamSocket.onmessage = (message: MessageEvent) => {
|
|
3358
|
-
if (typeof message.data !== 'string') {
|
|
3359
|
-
return;
|
|
3360
|
-
}
|
|
3361
|
-
try {
|
|
3362
|
-
const payload = JSON.parse(message.data) as Record<
|
|
3363
|
-
string,
|
|
3364
|
-
unknown
|
|
3365
|
-
>;
|
|
3366
|
-
if (
|
|
3367
|
-
typeof payload.type === 'string' &&
|
|
3368
|
-
(payload.type === 'connected' ||
|
|
3369
|
-
payload.type === 'heartbeat')
|
|
3370
|
-
) {
|
|
3371
|
-
return;
|
|
3372
|
-
}
|
|
3373
|
-
|
|
3374
|
-
const payloadData =
|
|
3375
|
-
payload.data &&
|
|
3376
|
-
typeof payload.data === 'object' &&
|
|
3377
|
-
!Array.isArray(payload.data)
|
|
3378
|
-
? { ...payload.data, instanceId: instance.instanceId }
|
|
3379
|
-
: { instanceId: instance.instanceId };
|
|
3380
|
-
|
|
3381
|
-
const liveEvent = {
|
|
3382
|
-
...payload,
|
|
3383
|
-
data: payloadData,
|
|
3384
|
-
instanceId: instance.instanceId,
|
|
3385
|
-
timestamp:
|
|
3386
|
-
typeof payload.timestamp === 'string'
|
|
3387
|
-
? payload.timestamp
|
|
3388
|
-
: new Date().toISOString(),
|
|
3389
|
-
};
|
|
3390
|
-
ws.send(JSON.stringify(liveEvent));
|
|
3391
|
-
} catch {
|
|
3392
|
-
// Ignore malformed downstream events
|
|
3393
|
-
}
|
|
3394
|
-
};
|
|
3395
|
-
|
|
3396
|
-
downstreamSocket.onerror = () => {
|
|
3397
|
-
try {
|
|
3398
|
-
ws.send(
|
|
3399
|
-
JSON.stringify({
|
|
3400
|
-
type: 'instance_error',
|
|
3401
|
-
instanceId: instance.instanceId,
|
|
3402
|
-
timestamp: new Date().toISOString(),
|
|
3403
|
-
})
|
|
3404
|
-
);
|
|
3405
|
-
} catch {
|
|
3406
|
-
// ignore send errors
|
|
3407
|
-
}
|
|
3408
|
-
};
|
|
3409
|
-
|
|
3410
|
-
current.downstreamSockets.push(downstreamSocket);
|
|
3411
|
-
}
|
|
3412
|
-
|
|
3413
|
-
ws.send(
|
|
3414
|
-
JSON.stringify({
|
|
3415
|
-
type: 'connected',
|
|
3416
|
-
timestamp: new Date().toISOString(),
|
|
3417
|
-
instanceCount: selectedInstances.length,
|
|
3418
|
-
})
|
|
3419
|
-
);
|
|
3420
|
-
|
|
3421
|
-
const heartbeatInterval = setInterval(() => {
|
|
3422
|
-
try {
|
|
3423
|
-
ws.send(
|
|
3424
|
-
JSON.stringify({
|
|
3425
|
-
type: 'heartbeat',
|
|
3426
|
-
timestamp: new Date().toISOString(),
|
|
3427
|
-
})
|
|
3428
|
-
);
|
|
3429
|
-
} catch {
|
|
3430
|
-
clearInterval(heartbeatInterval);
|
|
3431
|
-
}
|
|
3432
|
-
}, heartbeatIntervalMs);
|
|
3433
|
-
current.heartbeatInterval = heartbeatInterval;
|
|
2833
|
+
current.startAuthenticatedSession?.(token);
|
|
3434
2834
|
},
|
|
3435
2835
|
onClose(_event, ws) {
|
|
3436
2836
|
cleanup(ws);
|
|
@@ -3465,58 +2865,55 @@ export function createConsoleGatewayRoutes(
|
|
|
3465
2865
|
ConsolePartitionQuerySchema.extend(GatewayInstanceFilterSchema.shape)
|
|
3466
2866
|
),
|
|
3467
2867
|
async (c) => {
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
2868
|
+
return withGatewayAuth(c, async () => {
|
|
2869
|
+
const { id } = c.req.valid('param');
|
|
2870
|
+
const query = c.req.valid('query');
|
|
2871
|
+
const target = resolveEventTarget({
|
|
2872
|
+
id,
|
|
2873
|
+
instances,
|
|
2874
|
+
query,
|
|
2875
|
+
});
|
|
2876
|
+
if (!target.ok) {
|
|
2877
|
+
return c.json(
|
|
2878
|
+
{
|
|
2879
|
+
error: target.error,
|
|
2880
|
+
...(target.message ? { message: target.message } : {}),
|
|
2881
|
+
},
|
|
2882
|
+
target.status
|
|
2883
|
+
);
|
|
2884
|
+
}
|
|
3472
2885
|
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
const target = resolveEventTarget({
|
|
3476
|
-
id,
|
|
3477
|
-
instances,
|
|
3478
|
-
query,
|
|
3479
|
-
});
|
|
3480
|
-
if (!target.ok) {
|
|
3481
|
-
return c.json(
|
|
3482
|
-
{
|
|
3483
|
-
error: target.error,
|
|
3484
|
-
...(target.message ? { message: target.message } : {}),
|
|
3485
|
-
},
|
|
3486
|
-
target.status
|
|
2886
|
+
const forwardQuery = sanitizeForwardQueryParams(
|
|
2887
|
+
new URL(c.req.url).searchParams
|
|
3487
2888
|
);
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
path: `/events/${target.localEventId}`,
|
|
3497
|
-
query: forwardQuery,
|
|
3498
|
-
schema: ConsoleRequestEventSchema,
|
|
3499
|
-
fetchImpl,
|
|
3500
|
-
});
|
|
2889
|
+
const result = await fetchDownstreamJson({
|
|
2890
|
+
c,
|
|
2891
|
+
instance: target.instance,
|
|
2892
|
+
path: `/events/${target.localEventId}`,
|
|
2893
|
+
query: forwardQuery,
|
|
2894
|
+
schema: ConsoleRequestEventSchema,
|
|
2895
|
+
fetchImpl,
|
|
2896
|
+
});
|
|
3501
2897
|
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
2898
|
+
if (!result.ok) {
|
|
2899
|
+
if (result.failure.status === 404) {
|
|
2900
|
+
return c.json({ error: 'NOT_FOUND' }, 404);
|
|
2901
|
+
}
|
|
2902
|
+
return c.json(
|
|
2903
|
+
{
|
|
2904
|
+
error: 'DOWNSTREAM_UNAVAILABLE',
|
|
2905
|
+
failedInstances: [result.failure],
|
|
2906
|
+
},
|
|
2907
|
+
502
|
|
2908
|
+
);
|
|
3505
2909
|
}
|
|
3506
|
-
return c.json(
|
|
3507
|
-
{
|
|
3508
|
-
error: 'DOWNSTREAM_UNAVAILABLE',
|
|
3509
|
-
failedInstances: [result.failure],
|
|
3510
|
-
},
|
|
3511
|
-
502
|
|
3512
|
-
);
|
|
3513
|
-
}
|
|
3514
2910
|
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
2911
|
+
return c.json({
|
|
2912
|
+
...result.data,
|
|
2913
|
+
instanceId: target.instance.instanceId,
|
|
2914
|
+
federatedEventId: `${target.instance.instanceId}:${result.data.eventId}`,
|
|
2915
|
+
localEventId: result.data.eventId,
|
|
2916
|
+
});
|
|
3520
2917
|
});
|
|
3521
2918
|
}
|
|
3522
2919
|
);
|
|
@@ -3543,58 +2940,55 @@ export function createConsoleGatewayRoutes(
|
|
|
3543
2940
|
ConsolePartitionQuerySchema.extend(GatewayInstanceFilterSchema.shape)
|
|
3544
2941
|
),
|
|
3545
2942
|
async (c) => {
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
2943
|
+
return withGatewayAuth(c, async () => {
|
|
2944
|
+
const { id } = c.req.valid('param');
|
|
2945
|
+
const query = c.req.valid('query');
|
|
2946
|
+
const target = resolveEventTarget({
|
|
2947
|
+
id,
|
|
2948
|
+
instances,
|
|
2949
|
+
query,
|
|
2950
|
+
});
|
|
2951
|
+
if (!target.ok) {
|
|
2952
|
+
return c.json(
|
|
2953
|
+
{
|
|
2954
|
+
error: target.error,
|
|
2955
|
+
...(target.message ? { message: target.message } : {}),
|
|
2956
|
+
},
|
|
2957
|
+
target.status
|
|
2958
|
+
);
|
|
2959
|
+
}
|
|
3550
2960
|
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
const target = resolveEventTarget({
|
|
3554
|
-
id,
|
|
3555
|
-
instances,
|
|
3556
|
-
query,
|
|
3557
|
-
});
|
|
3558
|
-
if (!target.ok) {
|
|
3559
|
-
return c.json(
|
|
3560
|
-
{
|
|
3561
|
-
error: target.error,
|
|
3562
|
-
...(target.message ? { message: target.message } : {}),
|
|
3563
|
-
},
|
|
3564
|
-
target.status
|
|
2961
|
+
const forwardQuery = sanitizeForwardQueryParams(
|
|
2962
|
+
new URL(c.req.url).searchParams
|
|
3565
2963
|
);
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
path: `/events/${target.localEventId}/payload`,
|
|
3575
|
-
query: forwardQuery,
|
|
3576
|
-
schema: ConsoleRequestPayloadSchema,
|
|
3577
|
-
fetchImpl,
|
|
3578
|
-
});
|
|
2964
|
+
const result = await fetchDownstreamJson({
|
|
2965
|
+
c,
|
|
2966
|
+
instance: target.instance,
|
|
2967
|
+
path: `/events/${target.localEventId}/payload`,
|
|
2968
|
+
query: forwardQuery,
|
|
2969
|
+
schema: ConsoleRequestPayloadSchema,
|
|
2970
|
+
fetchImpl,
|
|
2971
|
+
});
|
|
3579
2972
|
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
2973
|
+
if (!result.ok) {
|
|
2974
|
+
if (result.failure.status === 404) {
|
|
2975
|
+
return c.json({ error: 'NOT_FOUND' }, 404);
|
|
2976
|
+
}
|
|
2977
|
+
return c.json(
|
|
2978
|
+
{
|
|
2979
|
+
error: 'DOWNSTREAM_UNAVAILABLE',
|
|
2980
|
+
failedInstances: [result.failure],
|
|
2981
|
+
},
|
|
2982
|
+
502
|
|
2983
|
+
);
|
|
3583
2984
|
}
|
|
3584
|
-
return c.json(
|
|
3585
|
-
{
|
|
3586
|
-
error: 'DOWNSTREAM_UNAVAILABLE',
|
|
3587
|
-
failedInstances: [result.failure],
|
|
3588
|
-
},
|
|
3589
|
-
502
|
|
3590
|
-
);
|
|
3591
|
-
}
|
|
3592
2985
|
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
2986
|
+
return c.json({
|
|
2987
|
+
...result.data,
|
|
2988
|
+
instanceId: target.instance.instanceId,
|
|
2989
|
+
federatedEventId: `${target.instance.instanceId}:${target.localEventId}`,
|
|
2990
|
+
localEventId: target.localEventId,
|
|
2991
|
+
});
|
|
3598
2992
|
});
|
|
3599
2993
|
}
|
|
3600
2994
|
);
|