@syncular/server-hono 0.0.6-159 → 0.0.6-165

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.
Files changed (50) hide show
  1. package/dist/blobs.d.ts +10 -4
  2. package/dist/blobs.d.ts.map +1 -1
  3. package/dist/blobs.js +260 -26
  4. package/dist/blobs.js.map +1 -1
  5. package/dist/console/gateway.d.ts +4 -0
  6. package/dist/console/gateway.d.ts.map +1 -1
  7. package/dist/console/gateway.js +97 -60
  8. package/dist/console/gateway.js.map +1 -1
  9. package/dist/console/route-descriptor.d.ts +6 -0
  10. package/dist/console/route-descriptor.d.ts.map +1 -0
  11. package/dist/console/route-descriptor.js +16 -0
  12. package/dist/console/route-descriptor.js.map +1 -0
  13. package/dist/console/routes.d.ts.map +1 -1
  14. package/dist/console/routes.js +153 -108
  15. package/dist/console/routes.js.map +1 -1
  16. package/dist/console/schema-errors.d.ts +2 -0
  17. package/dist/console/schema-errors.d.ts.map +1 -0
  18. package/dist/console/schema-errors.js +17 -0
  19. package/dist/console/schema-errors.js.map +1 -0
  20. package/dist/console/schemas.js +1 -1
  21. package/dist/console/schemas.js.map +1 -1
  22. package/dist/console/types.d.ts +32 -0
  23. package/dist/console/types.d.ts.map +1 -1
  24. package/dist/create-server.d.ts.map +1 -1
  25. package/dist/create-server.js +13 -10
  26. package/dist/create-server.js.map +1 -1
  27. package/dist/proxy/routes.d.ts +10 -0
  28. package/dist/proxy/routes.d.ts.map +1 -1
  29. package/dist/proxy/routes.js +57 -6
  30. package/dist/proxy/routes.js.map +1 -1
  31. package/dist/routes.d.ts +21 -0
  32. package/dist/routes.d.ts.map +1 -1
  33. package/dist/routes.js +338 -352
  34. package/dist/routes.js.map +1 -1
  35. package/package.json +7 -6
  36. package/src/__tests__/blob-routes.test.ts +286 -18
  37. package/src/__tests__/console-gateway-live-routes.test.ts +61 -1
  38. package/src/__tests__/console-routes.test.ts +30 -1
  39. package/src/__tests__/create-server.test.ts +237 -1
  40. package/src/__tests__/pull-chunk-storage.test.ts +98 -0
  41. package/src/blobs.ts +360 -34
  42. package/src/console/gateway.ts +335 -288
  43. package/src/console/route-descriptor.ts +22 -0
  44. package/src/console/routes.ts +327 -248
  45. package/src/console/schema-errors.ts +23 -0
  46. package/src/console/schemas.ts +1 -1
  47. package/src/console/types.ts +32 -0
  48. package/src/create-server.ts +13 -10
  49. package/src/proxy/routes.ts +73 -9
  50. package/src/routes.ts +449 -396
@@ -1,8 +1,9 @@
1
1
  import { Hono } from 'hono';
2
2
  import { cors } from 'hono/cors';
3
- import { describeRoute, resolver, validator as zValidator } from 'hono-openapi';
3
+ import { resolver, validator as zValidator } from 'hono-openapi';
4
4
  import { z } from 'zod';
5
5
  import { closeUnauthenticatedSocket, parseBearerToken, parseWebSocketAuthToken, } from './live-auth.js';
6
+ import { describeConsoleGatewayRoute } from './route-descriptor.js';
6
7
  import { ApiKeyTypeSchema, ConsoleApiKeyBulkRevokeRequestSchema, ConsoleApiKeyBulkRevokeResponseSchema, ConsoleApiKeyCreateRequestSchema, ConsoleApiKeyCreateResponseSchema, ConsoleApiKeyRevokeResponseSchema, ConsoleApiKeySchema, ConsoleClearEventsResultSchema, ConsoleClientSchema, ConsoleCommitDetailSchema, ConsoleCommitListItemSchema, ConsoleCompactResultSchema, ConsoleEvictResultSchema, ConsoleHandlerSchema, ConsoleOperationEventSchema, ConsoleOperationsQuerySchema, ConsolePaginatedResponseSchema, ConsolePaginationQuerySchema, ConsolePartitionedPaginationQuerySchema, ConsolePartitionQuerySchema, ConsolePruneEventsResultSchema, ConsolePrunePreviewSchema, ConsolePruneResultSchema, ConsoleRequestEventSchema, ConsoleRequestPayloadSchema, ConsoleTimelineItemSchema, ConsoleTimelineQuerySchema, LatencyQuerySchema, LatencyStatsResponseSchema, SyncStatsSchema, TimeseriesQuerySchema, TimeseriesStatsResponseSchema, } from './schemas.js';
7
8
  const GatewayFailureSchema = z.object({
8
9
  instanceId: z.string(),
@@ -817,8 +818,7 @@ export function createConsoleGatewayRoutes(options) {
817
818
  failedInstances,
818
819
  };
819
820
  };
820
- routes.get('/instances', describeRoute({
821
- tags: ['console-gateway'],
821
+ routes.get('/instances', describeConsoleGatewayRoute({
822
822
  summary: 'List configured downstream console instances',
823
823
  responses: {
824
824
  200: {
@@ -850,8 +850,7 @@ export function createConsoleGatewayRoutes(options) {
850
850
  });
851
851
  });
852
852
  });
853
- routes.get('/instances/health', describeRoute({
854
- tags: ['console-gateway'],
853
+ routes.get('/instances/health', describeConsoleGatewayRoute({
855
854
  summary: 'Probe downstream console health by instance',
856
855
  responses: {
857
856
  200: {
@@ -897,8 +896,7 @@ export function createConsoleGatewayRoutes(options) {
897
896
  });
898
897
  });
899
898
  });
900
- routes.get('/handlers', describeRoute({
901
- tags: ['console-gateway'],
899
+ routes.get('/handlers', describeConsoleGatewayRoute({
902
900
  summary: 'List handlers for a single target instance (requires instance selection)',
903
901
  responses: {
904
902
  200: {
@@ -922,8 +920,7 @@ export function createConsoleGatewayRoutes(options) {
922
920
  });
923
921
  });
924
922
  });
925
- routes.post('/prune/preview', describeRoute({
926
- tags: ['console-gateway'],
923
+ routes.post('/prune/preview', describeConsoleGatewayRoute({
927
924
  summary: 'Preview prune on a single target instance (requires instance selection)',
928
925
  responses: {
929
926
  200: {
@@ -947,8 +944,7 @@ export function createConsoleGatewayRoutes(options) {
947
944
  });
948
945
  });
949
946
  });
950
- routes.post('/prune', describeRoute({
951
- tags: ['console-gateway'],
947
+ routes.post('/prune', describeConsoleGatewayRoute({
952
948
  summary: 'Trigger prune on a single target instance (requires instance selection)',
953
949
  responses: {
954
950
  200: {
@@ -972,8 +968,7 @@ export function createConsoleGatewayRoutes(options) {
972
968
  });
973
969
  });
974
970
  });
975
- routes.post('/compact', describeRoute({
976
- tags: ['console-gateway'],
971
+ routes.post('/compact', describeConsoleGatewayRoute({
977
972
  summary: 'Trigger compaction on a single target instance (requires instance selection)',
978
973
  responses: {
979
974
  200: {
@@ -997,8 +992,7 @@ export function createConsoleGatewayRoutes(options) {
997
992
  });
998
993
  });
999
994
  });
1000
- routes.post('/notify-data-change', describeRoute({
1001
- tags: ['console-gateway'],
995
+ routes.post('/notify-data-change', describeConsoleGatewayRoute({
1002
996
  summary: 'Notify data change on a single target instance (requires instance selection)',
1003
997
  responses: {
1004
998
  200: {
@@ -1024,8 +1018,7 @@ export function createConsoleGatewayRoutes(options) {
1024
1018
  });
1025
1019
  });
1026
1020
  });
1027
- routes.delete('/clients/:id', describeRoute({
1028
- tags: ['console-gateway'],
1021
+ routes.delete('/clients/:id', describeConsoleGatewayRoute({
1029
1022
  summary: 'Evict client on a single target instance (requires instance selection)',
1030
1023
  responses: {
1031
1024
  200: {
@@ -1050,8 +1043,7 @@ export function createConsoleGatewayRoutes(options) {
1050
1043
  });
1051
1044
  });
1052
1045
  });
1053
- routes.delete('/events', describeRoute({
1054
- tags: ['console-gateway'],
1046
+ routes.delete('/events', describeConsoleGatewayRoute({
1055
1047
  summary: 'Clear request events on a single target instance (requires instance selection)',
1056
1048
  responses: {
1057
1049
  200: {
@@ -1075,8 +1067,7 @@ export function createConsoleGatewayRoutes(options) {
1075
1067
  });
1076
1068
  });
1077
1069
  });
1078
- routes.post('/events/prune', describeRoute({
1079
- tags: ['console-gateway'],
1070
+ routes.post('/events/prune', describeConsoleGatewayRoute({
1080
1071
  summary: 'Prune request events on a single target instance (requires instance selection)',
1081
1072
  responses: {
1082
1073
  200: {
@@ -1100,8 +1091,7 @@ export function createConsoleGatewayRoutes(options) {
1100
1091
  });
1101
1092
  });
1102
1093
  });
1103
- routes.get('/api-keys', describeRoute({
1104
- tags: ['console-gateway'],
1094
+ routes.get('/api-keys', describeConsoleGatewayRoute({
1105
1095
  summary: 'List API keys for a single target instance (requires instance selection)',
1106
1096
  responses: {
1107
1097
  200: {
@@ -1125,8 +1115,7 @@ export function createConsoleGatewayRoutes(options) {
1125
1115
  });
1126
1116
  });
1127
1117
  });
1128
- routes.post('/api-keys', describeRoute({
1129
- tags: ['console-gateway'],
1118
+ routes.post('/api-keys', describeConsoleGatewayRoute({
1130
1119
  summary: 'Create API key on a single target instance (requires instance selection)',
1131
1120
  responses: {
1132
1121
  201: {
@@ -1152,8 +1141,7 @@ export function createConsoleGatewayRoutes(options) {
1152
1141
  });
1153
1142
  });
1154
1143
  });
1155
- routes.get('/api-keys/:id', describeRoute({
1156
- tags: ['console-gateway'],
1144
+ routes.get('/api-keys/:id', describeConsoleGatewayRoute({
1157
1145
  summary: 'Get API key from a single target instance (requires instance selection)',
1158
1146
  responses: {
1159
1147
  200: {
@@ -1178,8 +1166,7 @@ export function createConsoleGatewayRoutes(options) {
1178
1166
  });
1179
1167
  });
1180
1168
  });
1181
- routes.delete('/api-keys/:id', describeRoute({
1182
- tags: ['console-gateway'],
1169
+ routes.delete('/api-keys/:id', describeConsoleGatewayRoute({
1183
1170
  summary: 'Revoke API key on a single target instance (requires instance selection)',
1184
1171
  responses: {
1185
1172
  200: {
@@ -1204,8 +1191,7 @@ export function createConsoleGatewayRoutes(options) {
1204
1191
  });
1205
1192
  });
1206
1193
  });
1207
- routes.post('/api-keys/bulk-revoke', describeRoute({
1208
- tags: ['console-gateway'],
1194
+ routes.post('/api-keys/bulk-revoke', describeConsoleGatewayRoute({
1209
1195
  summary: 'Bulk revoke API keys on a single target instance (requires instance selection)',
1210
1196
  responses: {
1211
1197
  200: {
@@ -1231,8 +1217,7 @@ export function createConsoleGatewayRoutes(options) {
1231
1217
  });
1232
1218
  });
1233
1219
  });
1234
- routes.post('/api-keys/:id/rotate/stage', describeRoute({
1235
- tags: ['console-gateway'],
1220
+ routes.post('/api-keys/:id/rotate/stage', describeConsoleGatewayRoute({
1236
1221
  summary: 'Stage-rotate API key on a single target instance (requires instance selection)',
1237
1222
  responses: {
1238
1223
  200: {
@@ -1257,8 +1242,7 @@ export function createConsoleGatewayRoutes(options) {
1257
1242
  });
1258
1243
  });
1259
1244
  });
1260
- routes.post('/api-keys/:id/rotate', describeRoute({
1261
- tags: ['console-gateway'],
1245
+ routes.post('/api-keys/:id/rotate', describeConsoleGatewayRoute({
1262
1246
  summary: 'Rotate API key on a single target instance (requires instance selection)',
1263
1247
  responses: {
1264
1248
  200: {
@@ -1283,8 +1267,7 @@ export function createConsoleGatewayRoutes(options) {
1283
1267
  });
1284
1268
  });
1285
1269
  });
1286
- routes.get('/stats', describeRoute({
1287
- tags: ['console-gateway'],
1270
+ routes.get('/stats', describeConsoleGatewayRoute({
1288
1271
  summary: 'Get merged sync stats across instances',
1289
1272
  responses: {
1290
1273
  200: {
@@ -1342,8 +1325,7 @@ export function createConsoleGatewayRoutes(options) {
1342
1325
  });
1343
1326
  });
1344
1327
  });
1345
- routes.get('/stats/timeseries', describeRoute({
1346
- tags: ['console-gateway'],
1328
+ routes.get('/stats/timeseries', describeConsoleGatewayRoute({
1347
1329
  summary: 'Get merged time-series stats across instances',
1348
1330
  responses: {
1349
1331
  200: {
@@ -1382,8 +1364,7 @@ export function createConsoleGatewayRoutes(options) {
1382
1364
  });
1383
1365
  });
1384
1366
  });
1385
- routes.get('/stats/latency', describeRoute({
1386
- tags: ['console-gateway'],
1367
+ routes.get('/stats/latency', describeConsoleGatewayRoute({
1387
1368
  summary: 'Get merged latency stats across instances',
1388
1369
  responses: {
1389
1370
  200: {
@@ -1422,8 +1403,7 @@ export function createConsoleGatewayRoutes(options) {
1422
1403
  });
1423
1404
  });
1424
1405
  });
1425
- routes.get('/commits', describeRoute({
1426
- tags: ['console-gateway'],
1406
+ routes.get('/commits', describeConsoleGatewayRoute({
1427
1407
  summary: 'List merged commits across instances',
1428
1408
  responses: {
1429
1409
  200: {
@@ -1483,8 +1463,7 @@ export function createConsoleGatewayRoutes(options) {
1483
1463
  });
1484
1464
  });
1485
1465
  });
1486
- routes.get('/commits/:seq', describeRoute({
1487
- tags: ['console-gateway'],
1466
+ routes.get('/commits/:seq', describeConsoleGatewayRoute({
1488
1467
  summary: 'Get merged commit detail by federated id',
1489
1468
  responses: {
1490
1469
  200: {
@@ -1533,8 +1512,7 @@ export function createConsoleGatewayRoutes(options) {
1533
1512
  });
1534
1513
  });
1535
1514
  });
1536
- routes.get('/clients', describeRoute({
1537
- tags: ['console-gateway'],
1515
+ routes.get('/clients', describeConsoleGatewayRoute({
1538
1516
  summary: 'List merged clients across instances',
1539
1517
  responses: {
1540
1518
  200: {
@@ -1594,8 +1572,7 @@ export function createConsoleGatewayRoutes(options) {
1594
1572
  });
1595
1573
  });
1596
1574
  });
1597
- routes.get('/timeline', describeRoute({
1598
- tags: ['console-gateway'],
1575
+ routes.get('/timeline', describeConsoleGatewayRoute({
1599
1576
  summary: 'List merged timeline items across instances',
1600
1577
  responses: {
1601
1578
  200: {
@@ -1668,8 +1645,7 @@ export function createConsoleGatewayRoutes(options) {
1668
1645
  });
1669
1646
  });
1670
1647
  });
1671
- routes.get('/operations', describeRoute({
1672
- tags: ['console-gateway'],
1648
+ routes.get('/operations', describeConsoleGatewayRoute({
1673
1649
  summary: 'List merged operation events across instances',
1674
1650
  responses: {
1675
1651
  200: {
@@ -1730,8 +1706,7 @@ export function createConsoleGatewayRoutes(options) {
1730
1706
  });
1731
1707
  });
1732
1708
  });
1733
- routes.get('/events', describeRoute({
1734
- tags: ['console-gateway'],
1709
+ routes.get('/events', describeConsoleGatewayRoute({
1735
1710
  summary: 'List merged request events across instances',
1736
1711
  responses: {
1737
1712
  200: {
@@ -1796,10 +1771,13 @@ export function createConsoleGatewayRoutes(options) {
1796
1771
  options.websocket?.upgradeWebSocket !== undefined) {
1797
1772
  const upgradeWebSocket = options.websocket.upgradeWebSocket;
1798
1773
  const heartbeatIntervalMs = options.websocket.heartbeatIntervalMs ?? 30000;
1774
+ const maxMessageBytes = options.websocket.maxMessageBytes ?? 1024 * 1024;
1775
+ const maxMessagesPerWindow = options.websocket.maxMessagesPerWindow ?? 120;
1776
+ const messageRateWindowMs = options.websocket.messageRateWindowMs ?? 10000;
1799
1777
  const createDownstreamSocket = options.websocket.createWebSocket ??
1800
1778
  ((url) => new WebSocket(url));
1801
1779
  const liveState = new WeakMap();
1802
- routes.get('/events/live', upgradeWebSocket(async (c) => {
1780
+ const liveEventsWebSocketRoute = upgradeWebSocket(async (c) => {
1803
1781
  const initialAuth = await options.authenticate(c);
1804
1782
  const partitionId = c.req.query('partitionId')?.trim() || undefined;
1805
1783
  const replaySince = c.req.query('since')?.trim() || undefined;
@@ -1866,6 +1844,8 @@ export function createConsoleGatewayRoutes(options) {
1866
1844
  authTimeout: null,
1867
1845
  isAuthenticated: false,
1868
1846
  startAuthenticatedSession: null,
1847
+ messageRateWindowStart: Date.now(),
1848
+ messageRateWindowCount: 0,
1869
1849
  };
1870
1850
  liveState.set(ws, state);
1871
1851
  const startAuthenticatedSession = (upstreamBearerToken) => {
@@ -1985,7 +1965,29 @@ export function createConsoleGatewayRoutes(options) {
1985
1965
  },
1986
1966
  async onMessage(event, ws) {
1987
1967
  const state = liveState.get(ws);
1988
- if (!state || state.isAuthenticated) {
1968
+ if (!state) {
1969
+ return;
1970
+ }
1971
+ const messageBytes = measureWebSocketMessageBytes(event.data);
1972
+ if (messageBytes > maxMessageBytes) {
1973
+ ws.close(1009, 'message too large');
1974
+ cleanup(ws);
1975
+ return;
1976
+ }
1977
+ if (maxMessagesPerWindow > 0 && messageRateWindowMs > 0) {
1978
+ const nowMs = Date.now();
1979
+ if (nowMs - state.messageRateWindowStart >= messageRateWindowMs) {
1980
+ state.messageRateWindowStart = nowMs;
1981
+ state.messageRateWindowCount = 0;
1982
+ }
1983
+ state.messageRateWindowCount += 1;
1984
+ if (state.messageRateWindowCount > maxMessagesPerWindow) {
1985
+ ws.close(1008, 'message rate exceeded');
1986
+ cleanup(ws);
1987
+ return;
1988
+ }
1989
+ }
1990
+ if (state.isAuthenticated) {
1989
1991
  return;
1990
1992
  }
1991
1993
  if (typeof event.data !== 'string') {
@@ -2018,10 +2020,15 @@ export function createConsoleGatewayRoutes(options) {
2018
2020
  cleanup(ws);
2019
2021
  },
2020
2022
  };
2021
- }));
2023
+ });
2024
+ routes.get('/events/live', async (c, next) => {
2025
+ if (!isWebSocketOriginAllowed(c, options.websocket?.allowedOrigins)) {
2026
+ return c.json({ error: 'FORBIDDEN_ORIGIN' }, 403);
2027
+ }
2028
+ return liveEventsWebSocketRoute(c, next);
2029
+ });
2022
2030
  }
2023
- routes.get('/events/:id', describeRoute({
2024
- tags: ['console-gateway'],
2031
+ routes.get('/events/:id', describeConsoleGatewayRoute({
2025
2032
  summary: 'Get merged event detail by federated id',
2026
2033
  responses: {
2027
2034
  200: {
@@ -2074,8 +2081,7 @@ export function createConsoleGatewayRoutes(options) {
2074
2081
  });
2075
2082
  });
2076
2083
  });
2077
- routes.get('/events/:id/payload', describeRoute({
2078
- tags: ['console-gateway'],
2084
+ routes.get('/events/:id/payload', describeConsoleGatewayRoute({
2079
2085
  summary: 'Get merged event payload by federated id',
2080
2086
  responses: {
2081
2087
  200: {
@@ -2130,4 +2136,35 @@ export function createConsoleGatewayRoutes(options) {
2130
2136
  });
2131
2137
  return routes;
2132
2138
  }
2139
+ function isWebSocketOriginAllowed(c, allowedOrigins) {
2140
+ if (!allowedOrigins)
2141
+ return true;
2142
+ if (allowedOrigins === '*')
2143
+ return true;
2144
+ const origin = c.req.header('origin');
2145
+ if (!origin)
2146
+ return false;
2147
+ try {
2148
+ const normalizedOrigin = new URL(origin).origin;
2149
+ return allowedOrigins.includes(normalizedOrigin);
2150
+ }
2151
+ catch {
2152
+ return false;
2153
+ }
2154
+ }
2155
+ function measureWebSocketMessageBytes(data) {
2156
+ if (typeof data === 'string') {
2157
+ return new TextEncoder().encode(data).byteLength;
2158
+ }
2159
+ if (data instanceof ArrayBuffer) {
2160
+ return data.byteLength;
2161
+ }
2162
+ if (ArrayBuffer.isView(data)) {
2163
+ return data.byteLength;
2164
+ }
2165
+ if (typeof Blob !== 'undefined' && data instanceof Blob) {
2166
+ return data.size;
2167
+ }
2168
+ return new TextEncoder().encode(String(data)).byteLength;
2169
+ }
2133
2170
  //# sourceMappingURL=gateway.js.map