@chaos-overlords/server 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +14 -0
- package/dist/app.js.map +1 -1
- package/dist/container.d.ts +15 -1
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +2 -1
- package/dist/container.js.map +1 -1
- package/dist/http/errorHandler.d.ts.map +1 -1
- package/dist/http/errorHandler.js +14 -3
- package/dist/http/errorHandler.js.map +1 -1
- package/dist/http/responseValidation.d.ts +15 -0
- package/dist/http/responseValidation.d.ts.map +1 -0
- package/dist/http/responseValidation.js +37 -0
- package/dist/http/responseValidation.js.map +1 -0
- package/dist/http/types.d.ts +3 -0
- package/dist/http/types.d.ts.map +1 -1
- package/dist/routes/events.js +7 -3
- package/dist/routes/events.js.map +1 -1
- package/dist/sse/LocalEventHub.d.ts +4 -2
- package/dist/sse/LocalEventHub.d.ts.map +1 -1
- package/dist/sse/LocalEventHub.js +10 -8
- package/dist/sse/LocalEventHub.js.map +1 -1
- package/dist/sse/createSseResponse.d.ts +4 -2
- package/dist/sse/createSseResponse.d.ts.map +1 -1
- package/dist/sse/createSseResponse.js +25 -6
- package/dist/sse/createSseResponse.js.map +1 -1
- package/package.json +5 -5
package/dist/app.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAG3B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAUlD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAQ1C,eAAO,MAAM,UAAU,YAAY,CAAA;AAInC,2FAA2F;AAC3F,wBAAgB,SAAS,CAAC,SAAS,EAAE,eAAe,GAAG,IAAI,CAAC,MAAM,CAAC,CAuBlE"}
|
package/dist/app.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { BUG_REPORT_LIMITS, LIMITS } from '@chaos-overlords/contracts';
|
|
2
2
|
import { Hono } from 'hono';
|
|
3
3
|
import { bodyLimit } from 'hono/body-limit';
|
|
4
|
+
import { cors } from 'hono/cors';
|
|
4
5
|
import { handleError } from './http/errorHandler';
|
|
5
6
|
import { bearerAuth, bugReportRateLimited, memberRateLimited, rateLimited, requestId, } from './http/middleware';
|
|
7
|
+
import { validateContractResponse } from './http/responseValidation';
|
|
6
8
|
import { registerBugReportRoutes } from './routes/bugReports';
|
|
7
9
|
import { registerEventRoutes } from './routes/events';
|
|
8
10
|
import { registerMemberLobbyRoutes, registerPublicLobbyRoutes } from './routes/lobby';
|
|
@@ -20,6 +22,14 @@ export function createApp(container) {
|
|
|
20
22
|
await next();
|
|
21
23
|
});
|
|
22
24
|
app.use('*', requestId);
|
|
25
|
+
if (container.config.corsOrigins.length > 0) {
|
|
26
|
+
app.use('*', cors({
|
|
27
|
+
origin: [...container.config.corsOrigins],
|
|
28
|
+
allowHeaders: ['Authorization', 'Content-Type', 'Last-Event-ID'],
|
|
29
|
+
exposeHeaders: ['X-Request-Id'],
|
|
30
|
+
maxAge: 600,
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
23
33
|
app.get('/health', (c) => c.json({ ok: true }));
|
|
24
34
|
app.route(API_PREFIX, apiRoutes());
|
|
25
35
|
return app;
|
|
@@ -36,6 +46,10 @@ export function createApp(container) {
|
|
|
36
46
|
*/
|
|
37
47
|
function apiRoutes() {
|
|
38
48
|
const api = new Hono();
|
|
49
|
+
// Run outside every contract route, then validate the completed successful response after its
|
|
50
|
+
// handler has produced it. This closes the runtime gap left by TypeScript-only
|
|
51
|
+
// handler checking: storage values and JSON columns are not trustworthy merely because typed.
|
|
52
|
+
api.use('*', validateContractResponse);
|
|
39
53
|
// The unauthenticated handshake parses a body before any player identity exists, so it needs the
|
|
40
54
|
// same address budget and small-body cap as the lobby doors it protects.
|
|
41
55
|
api.use('/handshake', rateLimited, bodyLimit({ maxSize: SMALL_BODY }));
|
package/dist/app.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAA;AACtE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
|
package/dist/container.d.ts
CHANGED
|
@@ -3,10 +3,24 @@ import type { EventStreamOpener, Kernel, RateLimiter } from '@chaos-overlords/ke
|
|
|
3
3
|
import type { Context } from 'hono';
|
|
4
4
|
import type { AppEnv } from './http/types';
|
|
5
5
|
export interface ServerConfig {
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Serve `GET /api/v1/matches` (public lobby browsing). On by default.
|
|
8
|
+
*
|
|
9
|
+
* It lists the matches whose host chose `visibility: "public"` and nothing else, so a server
|
|
10
|
+
* that serves it still keeps every code-only lobby out of sight. Off, the route answers 404 and
|
|
11
|
+
* the game's Browse screen has nothing to show on any server — which is worth choosing
|
|
12
|
+
* deliberately, for a server that exists for one group of friends, and is the wrong thing to
|
|
13
|
+
* land on by having configured nothing.
|
|
14
|
+
*/
|
|
7
15
|
publicListing: boolean;
|
|
8
16
|
/** Interval between SSE keepalive comments. */
|
|
9
17
|
sseHeartbeatMs: number;
|
|
18
|
+
/**
|
|
19
|
+
* Origins a browser client may call from, or none. The game is not a browser and sends no
|
|
20
|
+
* `Origin`, so nothing is opened by default; a web front end lists its origins here, which
|
|
21
|
+
* allows the preflighted `Authorization` and `Last-Event-ID` headers the TypeScript client sends.
|
|
22
|
+
*/
|
|
23
|
+
corsOrigins: readonly string[];
|
|
10
24
|
}
|
|
11
25
|
/**
|
|
12
26
|
* The three doors that need throttling, each with its own budget and key.
|
package/dist/container.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AACpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AACrF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAE1C,MAAM,WAAW,YAAY;IAC3B
|
|
1
|
+
{"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AACpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AACrF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAE1C,MAAM,WAAW,YAAY;IAC3B;;;;;;;;OAQG;IACH,aAAa,EAAE,OAAO,CAAA;IACtB,+CAA+C;IAC/C,cAAc,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;CAC/B;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,WAAW,CAAA;IACtB,MAAM,EAAE,WAAW,CAAA;IACnB,MAAM,EAAE,WAAW,CAAA;IACnB;;;;;;;OAOG;IACH,SAAS,EAAE,WAAW,CAAA;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,UAAU,CAAC,EAAE,gBAAgB,CAAA;IAC7B,WAAW,EAAE,iBAAiB,CAAA;IAC9B,YAAY,EAAE,YAAY,CAAA;IAC1B,MAAM,EAAE,YAAY,CAAA;IACpB,wFAAwF;IACxF,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,MAAM,CAAA;CAC/C;AAED,eAAO,MAAM,qBAAqB,EAAE,YAInC,CAAA;AAED,sFAAsF;AACtF,eAAO,MAAM,mBAAmB;;;;IAI9B;;;;OAIG;;CAEK,CAAA"}
|
package/dist/container.js
CHANGED
package/dist/container.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"container.js","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"container.js","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"AAiEA,MAAM,CAAC,MAAM,qBAAqB,GAAiB;IACjD,aAAa,EAAE,IAAI;IACnB,cAAc,EAAE,MAAM;IACtB,WAAW,EAAE,EAAE;CAChB,CAAA;AAED,sFAAsF;AACtF,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,kBAAkB,EAAE,EAAE;IACtB,eAAe,EAAE,GAAG;IACpB,eAAe,EAAE,EAAE;IACnB;;;;OAIG;IACH,kBAAkB,EAAE,CAAC;CACb,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errorHandler.d.ts","sourceRoot":"","sources":["../../src/http/errorHandler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"errorHandler.d.ts","sourceRoot":"","sources":["../../src/http/errorHandler.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAEnC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAErC,kGAAkG;AAClG,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,QAAQ,CA0BtE"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { STATUS_BY_CODE } from '@chaos-overlords/contracts';
|
|
1
|
+
import { errorEnvelopeSchema, STATUS_BY_CODE, validateSync, } from '@chaos-overlords/contracts';
|
|
2
2
|
import { isDomainError } from '@chaos-overlords/kernel';
|
|
3
3
|
import { SchemaValidationError } from '@toad-contracts/core';
|
|
4
4
|
import { HTTPException } from 'hono/http-exception';
|
|
@@ -12,7 +12,7 @@ export function handleError(error, c) {
|
|
|
12
12
|
// The contract validator refused a path param, a query value or the body. The issue paths and
|
|
13
13
|
// messages come straight from valibot rather than being restated per endpoint, so a client is
|
|
14
14
|
// told which field it got wrong.
|
|
15
|
-
return respond(c, 'validation_failed', 'Request failed contract validation', { reason: 'invalid_request', issues: error.issues }, requestId);
|
|
15
|
+
return respond(c, 'validation_failed', 'Request failed contract validation', { reason: 'invalid_request', issues: error.issues.map(describeIssue) }, requestId);
|
|
16
16
|
}
|
|
17
17
|
if (error instanceof HTTPException) {
|
|
18
18
|
const code = codeForStatus(error.status);
|
|
@@ -24,6 +24,17 @@ export function handleError(error, c) {
|
|
|
24
24
|
});
|
|
25
25
|
return respond(c, 'internal', 'Internal server error', { reason: 'internal' }, requestId);
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* What a client is told about a refused field: where and why, never what it sent.
|
|
29
|
+
*
|
|
30
|
+
* A validator's issue carries the offending input beside the message (valibot's `input` and
|
|
31
|
+
* `received`, and the value at every step of the path). Echoing it would put a mistyped password
|
|
32
|
+
* or a whole order document into the response and into any proxy log on the way.
|
|
33
|
+
*/
|
|
34
|
+
function describeIssue(issue) {
|
|
35
|
+
const path = (issue.path ?? []).map((segment) => String(typeof segment === 'object' && segment !== null && 'key' in segment ? segment.key : segment));
|
|
36
|
+
return { message: issue.message, path };
|
|
37
|
+
}
|
|
27
38
|
/**
|
|
28
39
|
* The error code a framework `HTTPException` maps to. Written out rather than derived by scanning
|
|
29
40
|
* `STATUS_BY_CODE` for a matching value: that scan silently picks whichever code was declared first
|
|
@@ -44,6 +55,6 @@ function codeForStatus(status) {
|
|
|
44
55
|
}
|
|
45
56
|
function respond(c, code, message, details, requestId) {
|
|
46
57
|
const body = { error: { code, message, details, requestId } };
|
|
47
|
-
return c.json(body, STATUS_BY_CODE[code]);
|
|
58
|
+
return c.json(validateSync(errorEnvelopeSchema, body), STATUS_BY_CODE[code]);
|
|
48
59
|
}
|
|
49
60
|
//# sourceMappingURL=errorHandler.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errorHandler.js","sourceRoot":"","sources":["../../src/http/errorHandler.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"errorHandler.js","sourceRoot":"","sources":["../../src/http/errorHandler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from 'hono';
|
|
2
|
+
import type { AppEnv } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Refuses a response that does not match the same contract which mounted its route.
|
|
5
|
+
*
|
|
6
|
+
* The Hono adapter constrains handlers at compile time, but a database, a JSON column, or an
|
|
7
|
+
* unsafe cast can still put a different runtime value behind a correct TypeScript type. This is
|
|
8
|
+
* the last server-side boundary before those values become wire JSON, so it turns producer drift
|
|
9
|
+
* into a local 500. Error envelopes are validated directly by their single producer.
|
|
10
|
+
*/
|
|
11
|
+
export declare const validateContractResponse: MiddlewareHandler<AppEnv>;
|
|
12
|
+
export declare class ContractResponseError extends Error {
|
|
13
|
+
constructor(message: string, cause?: unknown);
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=responseValidation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"responseValidation.d.ts","sourceRoot":"","sources":["../../src/http/responseValidation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAC7C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAErC;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBAAiB,CAAC,MAAM,CA6B9D,CAAA;AAED,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAI7C"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { resolveResponseEntry, validate } from '@toad-contracts/core';
|
|
2
|
+
/**
|
|
3
|
+
* Refuses a response that does not match the same contract which mounted its route.
|
|
4
|
+
*
|
|
5
|
+
* The Hono adapter constrains handlers at compile time, but a database, a JSON column, or an
|
|
6
|
+
* unsafe cast can still put a different runtime value behind a correct TypeScript type. This is
|
|
7
|
+
* the last server-side boundary before those values become wire JSON, so it turns producer drift
|
|
8
|
+
* into a local 500. Error envelopes are validated directly by their single producer.
|
|
9
|
+
*/
|
|
10
|
+
export const validateContractResponse = async (c, next) => {
|
|
11
|
+
await next();
|
|
12
|
+
const contract = c.get('apiContract');
|
|
13
|
+
if (!contract)
|
|
14
|
+
return;
|
|
15
|
+
const contentType = c.res.headers.get('content-type') ?? undefined;
|
|
16
|
+
const responseKind = resolveResponseEntry(contract.responsesByStatusCode, c.res.status, contentType, true);
|
|
17
|
+
if (!responseKind) {
|
|
18
|
+
throw new ContractResponseError(`status ${c.res.status} with content type ${contentType ?? '(none)'} is not in the contract`);
|
|
19
|
+
}
|
|
20
|
+
if (responseKind.kind !== 'json')
|
|
21
|
+
return;
|
|
22
|
+
let body;
|
|
23
|
+
try {
|
|
24
|
+
body = await c.res.clone().json();
|
|
25
|
+
await validate(responseKind.schema, body);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
throw new ContractResponseError(`status ${c.res.status} body does not match the contract`, error);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
export class ContractResponseError extends Error {
|
|
32
|
+
constructor(message, cause) {
|
|
33
|
+
super(message, { cause });
|
|
34
|
+
this.name = 'ContractResponseError';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=responseValidation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"responseValidation.js","sourceRoot":"","sources":["../../src/http/responseValidation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAIrE;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAA8B,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;IACnF,MAAM,IAAI,EAAE,CAAA;IACZ,MAAM,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;IACrC,IAAI,CAAC,QAAQ;QAAE,OAAM;IAErB,MAAM,WAAW,GAAG,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,SAAS,CAAA;IAClE,MAAM,YAAY,GAAG,oBAAoB,CACvC,QAAQ,CAAC,qBAAqB,EAC9B,CAAC,CAAC,GAAG,CAAC,MAAM,EACZ,WAAW,EACX,IAAI,CACL,CAAA;IACD,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,qBAAqB,CAC7B,UAAU,CAAC,CAAC,GAAG,CAAC,MAAM,sBAAsB,WAAW,IAAI,QAAQ,yBAAyB,CAC7F,CAAA;IACH,CAAC;IACD,IAAI,YAAY,CAAC,IAAI,KAAK,MAAM;QAAE,OAAM;IAExC,IAAI,IAAa,CAAA;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAA;QACjC,MAAM,QAAQ,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;IAC3C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,qBAAqB,CAC7B,UAAU,CAAC,CAAC,GAAG,CAAC,MAAM,mCAAmC,EACzD,KAAK,CACN,CAAA;IACH,CAAC;AACH,CAAC,CAAA;AAED,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAC9C,YAAY,OAAe,EAAE,KAAe;QAC1C,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;QACzB,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAA;IACrC,CAAC;CACF"}
|
package/dist/http/types.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { Principal } from '@chaos-overlords/kernel';
|
|
2
|
+
import type { ApiContract } from '@toad-contracts/core';
|
|
2
3
|
import type { ServerContainer } from '../container';
|
|
3
4
|
export interface AppEnv {
|
|
4
5
|
Variables: {
|
|
5
6
|
container: ServerContainer;
|
|
6
7
|
requestId: string;
|
|
7
8
|
principal: Principal;
|
|
9
|
+
/** Set by the contract route before the handler runs; absent on non-contract routes. */
|
|
10
|
+
apiContract?: ApiContract;
|
|
8
11
|
};
|
|
9
12
|
}
|
|
10
13
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/http/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/http/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAEnD,MAAM,WAAW,MAAM;IACrB,SAAS,EAAE;QACT,SAAS,EAAE,eAAe,CAAA;QAC1B,SAAS,EAAE,MAAM,CAAA;QACjB,SAAS,EAAE,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/http/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAEnD,MAAM,WAAW,MAAM;IACrB,SAAS,EAAE;QACT,SAAS,EAAE,eAAe,CAAA;QAC1B,SAAS,EAAE,MAAM,CAAA;QACjB,SAAS,EAAE,SAAS,CAAA;QACpB,wFAAwF;QACxF,WAAW,CAAC,EAAE,WAAW,CAAA;KAC1B,CAAA;CACF"}
|
package/dist/routes/events.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { listEventsContract, streamEventsContract } from '@chaos-overlords/contracts';
|
|
1
|
+
import { INT32_MAX, listEventsContract, streamEventsContract } from '@chaos-overlords/contracts';
|
|
2
2
|
import { requireMember } from '../http/guards';
|
|
3
3
|
import { buildHonoRoute } from '../http/routes';
|
|
4
4
|
/** The event log: a paged REST read (the fallback) and the SSE stream over the same log. */
|
|
@@ -27,13 +27,17 @@ export function registerEventRoutes(api) {
|
|
|
27
27
|
*
|
|
28
28
|
* The stream reads these itself rather than through a query schema because a browser's own
|
|
29
29
|
* `EventSource` sets the header and nothing else, and a resume point that failed validation should
|
|
30
|
-
* restart the stream from the beginning rather than refuse the connection.
|
|
30
|
+
* restart the stream from the beginning rather than refuse the connection. The bound is the one
|
|
31
|
+
* the REST read applies: a value past it is not a sequence number, and on Postgres it would fail
|
|
32
|
+
* inside the drain after the 200 went out, which a client reads as a retryable stream error and
|
|
33
|
+
* repeats with the same value forever.
|
|
31
34
|
*/
|
|
32
35
|
function resumePoint(lastEventId, after) {
|
|
33
36
|
for (const candidate of [lastEventId, after]) {
|
|
34
37
|
const parsed = Number(candidate);
|
|
35
|
-
if (candidate !== undefined && Number.isInteger(parsed) && parsed >= 0)
|
|
38
|
+
if (candidate !== undefined && Number.isInteger(parsed) && parsed >= 0 && parsed <= INT32_MAX) {
|
|
36
39
|
return parsed;
|
|
40
|
+
}
|
|
37
41
|
}
|
|
38
42
|
return 0;
|
|
39
43
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/routes/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/routes/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AAEhG,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAG/C,4FAA4F;AAC5F,MAAM,UAAU,mBAAmB,CAAC,GAAiB;IACnD,cAAc,CAAC,GAAG,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAClD,MAAM,SAAS,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAA;QACjF,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QAC7C,MAAM,MAAM,GAAG,MAAM,CAAC;aACnB,GAAG,CAAC,WAAW,CAAC;aAChB,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,CAAA;QACzE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,CAAC,CAAA;IAChC,CAAC,CAAC,CAAA;IAEF,cAAc,CAAC,GAAG,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACpD,MAAM,SAAS,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAA;QACjF,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAA;QACjF,OAAO,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC;YACzC,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC,EAAE;YAC3B,QAAQ,EAAE,SAAS,CAAC,MAAM,CAAC,EAAE;YAC7B,QAAQ;YACR,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM;SACzB,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,WAAW,CAAC,WAA+B,EAAE,KAAyB;IAC7E,KAAK,MAAM,SAAS,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC;QAC7C,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,CAAA;QAChC,IAAI,SAAS,KAAK,SAAS,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,IAAI,MAAM,IAAI,SAAS,EAAE,CAAC;YAC9F,OAAO,MAAM,CAAA;QACf,CAAC;IACH,CAAC;IACD,OAAO,CAAC,CAAA;AACV,CAAC"}
|
|
@@ -3,7 +3,7 @@ import { type EventNotifier, type EventRepository, type EventStreamOpener, type
|
|
|
3
3
|
* How many streams may be open at once, and against what.
|
|
4
4
|
*
|
|
5
5
|
* A stream is not a request: it lives until the client closes it, it holds a heartbeat timer and a
|
|
6
|
-
* wake listener, and every event
|
|
6
|
+
* wake listener, and every event or heartbeat costs it one `listAfter` query. The
|
|
7
7
|
* per-player rate limit counts the opening call and nothing after it, so without these one
|
|
8
8
|
* authenticated member could hold thousands of streams and turn each published event into thousands
|
|
9
9
|
* of database reads and thousands of response writes — the whole match stops sealing for everybody.
|
|
@@ -60,7 +60,9 @@ export declare class LocalEventHub implements EventNotifier, EventStreamOpener,
|
|
|
60
60
|
*
|
|
61
61
|
* The process cap is checked first: it is the one that protects everyone else's matches, and a
|
|
62
62
|
* refusal is cheaper than closing somebody's live stream to make room for a caller this cap is
|
|
63
|
-
* about to turn away anyway.
|
|
63
|
+
* about to turn away anyway. The caller's own stale streams go next, BEFORE the match cap is
|
|
64
|
+
* read: a full match is exactly the state a reconnecting player finds when every seat holds its
|
|
65
|
+
* quota, and refusing them there would turn the one legitimate reconnect into a 429.
|
|
64
66
|
*/
|
|
65
67
|
private makeRoom;
|
|
66
68
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalEventHub.d.ts","sourceRoot":"","sources":["../../src/sse/LocalEventHub.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EAEnB,KAAK,YAAY,EAClB,MAAM,yBAAyB,CAAA;AAKhC;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,eAAO,MAAM,wBAAwB,EAAE,cAItC,CAAA;AAQD;;;GAGG;AACH,qBAAa,aAAc,YAAW,aAAa,EAAE,iBAAiB,EAAE,YAAY;IAMhF,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAPzB,yFAAyF;IACzF,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAuC;IACjE,OAAO,CAAC,KAAK,CAAI;gBAGE,MAAM,EAAE,eAAe,EACvB,WAAW,EAAE,MAAM,EACnB,MAAM,GAAE,cAAyC;IAG9D,MAAM,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD,+FAA+F;IAC/F,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI3B;;;;;;OAMG;IACG,KAAK,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAMxE,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAIxC,uDAAuD;IACvD,IAAI,WAAW,IAAI,MAAM,CAExB;IAEK,IAAI,CAAC,KAAK,EAAE;QAChB,OAAO,EAAE,MAAM,CAAA;QACf,QAAQ,EAAE,MAAM,CAAA;QAChB,QAAQ,EAAE,MAAM,CAAA;QAChB,MAAM,EAAE,WAAW,CAAA;KACpB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAWrB,OAAO,CAAC,SAAS;IAkBjB
|
|
1
|
+
{"version":3,"file":"LocalEventHub.d.ts","sourceRoot":"","sources":["../../src/sse/LocalEventHub.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EAEnB,KAAK,YAAY,EAClB,MAAM,yBAAyB,CAAA;AAKhC;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,eAAO,MAAM,wBAAwB,EAAE,cAItC,CAAA;AAQD;;;GAGG;AACH,qBAAa,aAAc,YAAW,aAAa,EAAE,iBAAiB,EAAE,YAAY;IAMhF,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAPzB,yFAAyF;IACzF,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAuC;IACjE,OAAO,CAAC,KAAK,CAAI;gBAGE,MAAM,EAAE,eAAe,EACvB,WAAW,EAAE,MAAM,EACnB,MAAM,GAAE,cAAyC;IAG9D,MAAM,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD,+FAA+F;IAC/F,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI3B;;;;;;OAMG;IACG,KAAK,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAMxE,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAIxC,uDAAuD;IACvD,IAAI,WAAW,IAAI,MAAM,CAExB;IAEK,IAAI,CAAC,KAAK,EAAE;QAChB,OAAO,EAAE,MAAM,CAAA;QACf,QAAQ,EAAE,MAAM,CAAA;QAChB,QAAQ,EAAE,MAAM,CAAA;QAChB,MAAM,EAAE,WAAW,CAAA;KACpB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAWrB,OAAO,CAAC,SAAS;IAkBjB;;;;;;;;OAQG;IACH,OAAO,CAAC,QAAQ;CAmBjB"}
|
|
@@ -77,27 +77,29 @@ export class LocalEventHub {
|
|
|
77
77
|
*
|
|
78
78
|
* The process cap is checked first: it is the one that protects everyone else's matches, and a
|
|
79
79
|
* refusal is cheaper than closing somebody's live stream to make room for a caller this cap is
|
|
80
|
-
* about to turn away anyway.
|
|
80
|
+
* about to turn away anyway. The caller's own stale streams go next, BEFORE the match cap is
|
|
81
|
+
* read: a full match is exactly the state a reconnecting player finds when every seat holds its
|
|
82
|
+
* quota, and refusing them there would turn the one legitimate reconnect into a 429.
|
|
81
83
|
*/
|
|
82
84
|
makeRoom(matchId, playerId) {
|
|
83
|
-
const set = this.listeners.get(matchId);
|
|
84
85
|
if (this.open_ >= this.limits.perProcess) {
|
|
85
86
|
throw new RateLimitedError('This server is holding as many event streams as it can', {
|
|
86
87
|
reason: 'too_many_streams',
|
|
87
88
|
scope: 'process',
|
|
88
89
|
});
|
|
89
90
|
}
|
|
90
|
-
|
|
91
|
+
const set = this.listeners.get(matchId);
|
|
92
|
+
if (set) {
|
|
93
|
+
const mine = [...set].filter((subscription) => subscription.playerId === playerId);
|
|
94
|
+
for (const stale of mine.slice(0, mine.length - this.limits.perPlayer + 1))
|
|
95
|
+
stale.close();
|
|
96
|
+
}
|
|
97
|
+
if ((this.listeners.get(matchId)?.size ?? 0) >= this.limits.perMatch) {
|
|
91
98
|
throw new RateLimitedError('This match is holding as many event streams as it can', {
|
|
92
99
|
reason: 'too_many_streams',
|
|
93
100
|
scope: 'match',
|
|
94
101
|
});
|
|
95
102
|
}
|
|
96
|
-
if (!set)
|
|
97
|
-
return;
|
|
98
|
-
const mine = [...set].filter((subscription) => subscription.playerId === playerId);
|
|
99
|
-
for (const stale of mine.slice(0, mine.length - this.limits.perPlayer + 1))
|
|
100
|
-
stale.close();
|
|
101
103
|
}
|
|
102
104
|
}
|
|
103
105
|
//# sourceMappingURL=LocalEventHub.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalEventHub.js","sourceRoot":"","sources":["../../src/sse/LocalEventHub.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"LocalEventHub.js","sourceRoot":"","sources":["../../src/sse/LocalEventHub.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAA;AACnD,OAAO,EAKL,gBAAgB,GAEjB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAEvD,MAAM,SAAS,GAAG,GAAG,CAAA;AAsBrB,MAAM,CAAC,MAAM,wBAAwB,GAAmB;IACtD,SAAS,EAAE,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC,UAAU,GAAG,CAAC;IAC/B,UAAU,EAAE,GAAG;CAChB,CAAA;AAQD;;;GAGG;AACH,MAAM,OAAO,aAAa;IAML;IACA;IACA;IAPnB,yFAAyF;IACxE,SAAS,GAAG,IAAI,GAAG,EAA6B,CAAA;IACzD,KAAK,GAAG,CAAC,CAAA;IAEjB,YACmB,MAAuB,EACvB,WAAmB,EACnB,SAAyB,wBAAwB;QAFjD,WAAM,GAAN,MAAM,CAAiB;QACvB,gBAAW,GAAX,WAAW,CAAQ;QACnB,WAAM,GAAN,MAAM,CAA2C;IACjE,CAAC;IAEJ,KAAK,CAAC,MAAM,CAAC,KAAqB;QAChC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IAC1B,CAAC;IAED,+FAA+F;IAC/F,IAAI,CAAC,OAAe;QAClB,KAAK,MAAM,YAAY,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;YAAE,YAAY,CAAC,IAAI,EAAE,CAAA;IAC1F,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,KAAK,CAAC,KAA4C;QACtD,KAAK,MAAM,YAAY,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;YAC1E,IAAI,YAAY,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ;gBAAE,YAAY,CAAC,KAAK,EAAE,CAAA;QACpE,CAAC;IACH,CAAC;IAED,eAAe,CAAC,OAAe;QAC7B,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,IAAI,IAAI,CAAC,CAAA;IAC/C,CAAC;IAED,uDAAuD;IACvD,IAAI,WAAW;QACb,OAAO,IAAI,CAAC,KAAK,CAAA;IACnB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,KAKV;QACC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;QAC5C,OAAO,iBAAiB,CACtB;YACE,SAAS,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC;YAClF,SAAS,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC;SACvF,EACD,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAClF,CAAA;IACH,CAAC;IAEO,SAAS,CACf,OAAe,EACf,QAAgB,EAChB,IAAgB,EAChB,KAAiB;QAEjB,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,GAAG,EAAgB,CAAA;QAClE,MAAM,YAAY,GAAiB,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;QAC5D,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;QACrB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;QAChC,IAAI,CAAC,KAAK,IAAI,CAAC,CAAA;QACf,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC;gBAAE,OAAM;YACrC,IAAI,CAAC,KAAK,IAAI,CAAC,CAAA;YACf,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC;gBAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QACpD,CAAC,CAAA;IACH,CAAC;IAED;;;;;;;;OAQG;IACK,QAAQ,CAAC,OAAe,EAAE,QAAgB;QAChD,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;YACzC,MAAM,IAAI,gBAAgB,CAAC,wDAAwD,EAAE;gBACnF,MAAM,EAAE,kBAAkB;gBAC1B,KAAK,EAAE,SAAS;aACjB,CAAC,CAAA;QACJ,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QACvC,IAAI,GAAG,EAAE,CAAC;YACR,MAAM,IAAI,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAA;YAClF,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,GAAG,CAAC,CAAC;gBAAE,KAAK,CAAC,KAAK,EAAE,CAAA;QAC3F,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YACrE,MAAM,IAAI,gBAAgB,CAAC,uDAAuD,EAAE;gBAClF,MAAM,EAAE,kBAAkB;gBAC1B,KAAK,EAAE,OAAO;aACf,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;CACF"}
|
|
@@ -20,8 +20,10 @@ export interface SseOptions {
|
|
|
20
20
|
* A server-sent event stream over a persisted, sequence-numbered log.
|
|
21
21
|
*
|
|
22
22
|
* The log is the truth and the wake-up is only a hint: every wake drains the log from the last
|
|
23
|
-
* delivered sequence,
|
|
24
|
-
*
|
|
23
|
+
* delivered sequence, while every heartbeat also performs a catch-up drain. A notification lost
|
|
24
|
+
* between persist and fan-out therefore costs at most one heartbeat rather than waiting for some
|
|
25
|
+
* unrelated later event, and `Last-Event-ID` resumes exactly. One drain runs at a time per
|
|
26
|
+
* connection.
|
|
25
27
|
*
|
|
26
28
|
* The drain respects backpressure. `enqueue` on a stream nobody is reading never refuses, it just
|
|
27
29
|
* buffers, so a consumer that has stopped reading (a suspended phone, a half-open TCP connection)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createSseResponse.d.ts","sourceRoot":"","sources":["../../src/sse/createSseResponse.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;
|
|
1
|
+
{"version":3,"file":"createSseResponse.d.ts","sourceRoot":"","sources":["../../src/sse/createSseResponse.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAG7D,MAAM,WAAW,iBAAiB;IAChC,sEAAsE;IACtE,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAAA;IACtD;;;;;;OAMG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,IAAI,EAAE,KAAK,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;CAC3D;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,WAAW,CAAA;CACpB;AAaD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,UAAU,GAAG,QAAQ,CA2H1F;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAGzD"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { SSE_HEARTBEAT_COMMENT } from '@chaos-overlords/contracts';
|
|
1
|
+
import { matchEventSchema, SSE_HEARTBEAT_COMMENT } from '@chaos-overlords/contracts';
|
|
2
|
+
import { validateSync } from '@toad-contracts/core';
|
|
2
3
|
const encoder = new TextEncoder();
|
|
3
4
|
/**
|
|
4
5
|
* Frames buffered for a consumer that is not keeping up, before the drain parks.
|
|
@@ -12,8 +13,10 @@ const STREAM_HIGH_WATER_MARK = 32;
|
|
|
12
13
|
* A server-sent event stream over a persisted, sequence-numbered log.
|
|
13
14
|
*
|
|
14
15
|
* The log is the truth and the wake-up is only a hint: every wake drains the log from the last
|
|
15
|
-
* delivered sequence,
|
|
16
|
-
*
|
|
16
|
+
* delivered sequence, while every heartbeat also performs a catch-up drain. A notification lost
|
|
17
|
+
* between persist and fan-out therefore costs at most one heartbeat rather than waiting for some
|
|
18
|
+
* unrelated later event, and `Last-Event-ID` resumes exactly. One drain runs at a time per
|
|
19
|
+
* connection.
|
|
17
20
|
*
|
|
18
21
|
* The drain respects backpressure. `enqueue` on a stream nobody is reading never refuses, it just
|
|
19
22
|
* buffers, so a consumer that has stopped reading (a suspended phone, a half-open TCP connection)
|
|
@@ -71,8 +74,18 @@ export function createSseResponse(source, options) {
|
|
|
71
74
|
} while (wakeAgain && !closed);
|
|
72
75
|
})()
|
|
73
76
|
.catch((error) => {
|
|
74
|
-
|
|
77
|
+
// A failed read (a database blip) ends this stream as a whole. Erroring the
|
|
78
|
+
// controller alone left the heartbeat enqueueing into a dead stream and the
|
|
79
|
+
// subscription counted against every cap until the socket happened to close.
|
|
80
|
+
if (closed)
|
|
81
|
+
return;
|
|
82
|
+
try {
|
|
75
83
|
controller.error(error);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Already closed by the consumer.
|
|
87
|
+
}
|
|
88
|
+
shutdown();
|
|
76
89
|
})
|
|
77
90
|
.finally(() => {
|
|
78
91
|
draining = null;
|
|
@@ -90,7 +103,12 @@ export function createSseResponse(source, options) {
|
|
|
90
103
|
const unsubscribe = source.subscribe(wake, () => {
|
|
91
104
|
shutdown();
|
|
92
105
|
});
|
|
93
|
-
const heartbeat = setInterval(() =>
|
|
106
|
+
const heartbeat = setInterval(() => {
|
|
107
|
+
send(`: ${SSE_HEARTBEAT_COMMENT}\n\n`);
|
|
108
|
+
// Fan-out is deliberately best-effort. Re-read the durable log even when the socket is
|
|
109
|
+
// healthy so a failed or process-local notification cannot strand this client forever.
|
|
110
|
+
wake();
|
|
111
|
+
}, options.heartbeatMs);
|
|
94
112
|
shutdown = () => {
|
|
95
113
|
if (closed)
|
|
96
114
|
return;
|
|
@@ -132,6 +150,7 @@ export function createSseResponse(source, options) {
|
|
|
132
150
|
});
|
|
133
151
|
}
|
|
134
152
|
export function formatEvent(event) {
|
|
135
|
-
|
|
153
|
+
const validated = validateSync(matchEventSchema, event);
|
|
154
|
+
return `id: ${validated.seq}\nevent: ${validated.type}\ndata: ${JSON.stringify(validated)}\n\n`;
|
|
136
155
|
}
|
|
137
156
|
//# sourceMappingURL=createSseResponse.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createSseResponse.js","sourceRoot":"","sources":["../../src/sse/createSseResponse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAA;
|
|
1
|
+
{"version":3,"file":"createSseResponse.js","sourceRoot":"","sources":["../../src/sse/createSseResponse.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chaos-overlords/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "The Hono application both runtimes serve: REST intents, the SSE event stream, auth and the error envelope. Takes its ports through a container.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chaos-overlords",
|
|
@@ -48,15 +48,15 @@
|
|
|
48
48
|
"provenance": true
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@chaos-overlords/bug-reports": "0.
|
|
52
|
-
"@chaos-overlords/contracts": "0.
|
|
53
|
-
"@chaos-overlords/kernel": "0.
|
|
51
|
+
"@chaos-overlords/bug-reports": "0.8.0",
|
|
52
|
+
"@chaos-overlords/contracts": "0.8.0",
|
|
53
|
+
"@chaos-overlords/kernel": "0.8.0",
|
|
54
54
|
"@toad-contracts/core": "0.4.0",
|
|
55
55
|
"@toad-contracts/hono": "0.3.2",
|
|
56
56
|
"hono": "^4.13.7"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@chaos-overlords/conformance": "0.
|
|
59
|
+
"@chaos-overlords/conformance": "0.8.0",
|
|
60
60
|
"typescript": "^5.9.3",
|
|
61
61
|
"vitest": "^4.1.11"
|
|
62
62
|
},
|