opinionated-machine 10.5.0 → 11.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +154 -0
- package/README.md +513 -52
- package/dist/lib/DIContext.d.ts +13 -1
- package/dist/lib/DIContext.js +28 -8
- package/dist/lib/DIContext.js.map +1 -1
- package/dist/lib/api-contracts/apiRouteBuilder.d.ts +47 -5
- package/dist/lib/api-contracts/apiRouteBuilder.js +57 -8
- package/dist/lib/api-contracts/apiRouteBuilder.js.map +1 -1
- package/dist/lib/api-contracts/apiSseConnectionRegistry.d.ts +182 -0
- package/dist/lib/api-contracts/apiSseConnectionRegistry.js +332 -0
- package/dist/lib/api-contracts/apiSseConnectionRegistry.js.map +1 -0
- package/dist/lib/api-contracts/index.d.ts +1 -0
- package/dist/lib/api-contracts/index.js +1 -0
- package/dist/lib/api-contracts/index.js.map +1 -1
- package/dist/lib/gateway/gatewayMetadata.d.ts +1 -0
- package/dist/lib/gateway/gatewayMetadata.js +11 -0
- package/dist/lib/gateway/gatewayMetadata.js.map +1 -1
- package/dist/lib/gateway/index.d.ts +1 -0
- package/dist/lib/gateway/index.js +1 -0
- package/dist/lib/gateway/index.js.map +1 -1
- package/dist/lib/gateway/manifest/buildManifest.d.ts +18 -0
- package/dist/lib/gateway/manifest/buildManifest.js +31 -0
- package/dist/lib/gateway/manifest/buildManifest.js.map +1 -1
- package/dist/lib/gateway/manifest/manifestSchema.d.ts +18 -0
- package/dist/lib/gateway/manifest/manifestSchema.js +27 -0
- package/dist/lib/gateway/manifest/manifestSchema.js.map +1 -1
- package/dist/lib/gateway/routeStreaming.d.ts +79 -0
- package/dist/lib/gateway/routeStreaming.js +70 -0
- package/dist/lib/gateway/routeStreaming.js.map +1 -0
- package/dist/lib/resolverFunctions.d.ts +7 -0
- package/dist/lib/resolverFunctions.js +13 -0
- package/dist/lib/resolverFunctions.js.map +1 -1
- package/dist/lib/routes/fastifyRouteBuilder.js +9 -2
- package/dist/lib/routes/fastifyRouteBuilder.js.map +1 -1
- package/dist/lib/sse/AbstractSSEController.d.ts +10 -0
- package/dist/lib/sse/AbstractSSEController.js +9 -0
- package/dist/lib/sse/AbstractSSEController.js.map +1 -1
- package/dist/lib/sse/eventIds.d.ts +138 -0
- package/dist/lib/sse/eventIds.js +155 -0
- package/dist/lib/sse/eventIds.js.map +1 -0
- package/dist/lib/sse/index.d.ts +3 -2
- package/dist/lib/sse/index.js +6 -2
- package/dist/lib/sse/index.js.map +1 -1
- package/dist/lib/sse/rooms/SSERoomEventPublisher.d.ts +121 -0
- package/dist/lib/sse/rooms/SSERoomEventPublisher.js +119 -0
- package/dist/lib/sse/rooms/SSERoomEventPublisher.js.map +1 -0
- package/dist/lib/sse/rooms/index.d.ts +1 -0
- package/dist/lib/sse/rooms/index.js +1 -0
- package/dist/lib/sse/rooms/index.js.map +1 -1
- package/dist/lib/testing/apiSseEventValidation.d.ts +1 -1
- package/dist/lib/testing/apiSseInjectHelpers.js +9 -10
- package/dist/lib/testing/apiSseInjectHelpers.js.map +1 -1
- package/dist/lib/testing/sseHttpClient.d.ts +10 -2
- package/dist/lib/testing/sseHttpClient.js +13 -6
- package/dist/lib/testing/sseHttpClient.js.map +1 -1
- package/dist/lib/testing/sseInjectClient.d.ts +1 -1
- package/dist/lib/testing/sseInjectClient.js +1 -1
- package/dist/lib/testing/sseInjectClient.js.map +1 -1
- package/dist/lib/testing/sseTestTypes.d.ts +1 -1
- package/package.json +13 -13
- package/dist/lib/sse/sseParser.d.ts +0 -167
- package/dist/lib/sse/sseParser.js +0 -225
- package/dist/lib/sse/sseParser.js.map +0 -1
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Monotonic event-id helpers for SSE streams.
|
|
3
|
+
*
|
|
4
|
+
* The polling-fallback pattern (and `Last-Event-ID` reconnection/replay in
|
|
5
|
+
* general) needs event ids a client can ORDER, not just deduplicate: the
|
|
6
|
+
* client keeps a high-watermark and drops anything at or below it. Random
|
|
7
|
+
* UUIDs (the `broadcastToRoom` default) are unique but unordered; use a
|
|
8
|
+
* sequence from {@link createEventIdSequence} instead and pass its ids
|
|
9
|
+
* explicitly:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* const seq = createEventIdSequence()
|
|
13
|
+
* await broadcaster.broadcastToRoom(room, statusEvent, data, { id: seq.next() })
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* Keep one sequence per ordering scope (per room, per resource, per stream) —
|
|
17
|
+
* ids from different scopes are not comparable. Stamp the same underlying
|
|
18
|
+
* version into the sync (poll) response body so the client can order
|
|
19
|
+
* snapshots against events.
|
|
20
|
+
*
|
|
21
|
+
* ## Choosing an id source
|
|
22
|
+
*
|
|
23
|
+
* Prefer a **domain version** (`job.version`, a revision column, an updated-at
|
|
24
|
+
* revision counter) over a generated sequence whenever the resource has one.
|
|
25
|
+
* A domain version is per-scope and writer-independent: any pod that writes the
|
|
26
|
+
* resource reads the same version, and the same number is already what the
|
|
27
|
+
* snapshot body has to carry for the client's version gate. Use
|
|
28
|
+
* {@link createEventIdSequence} when the resource has no version of its own.
|
|
29
|
+
*
|
|
30
|
+
* ## Hazard: per-process sequences are single-writer only
|
|
31
|
+
*
|
|
32
|
+
* {@link createEventIdSequence} is in-memory and per-process. Its epoch
|
|
33
|
+
* defaults to the process start time, and the client's default extractor
|
|
34
|
+
* orders by epoch first. That is safe only when a single writer owns the
|
|
35
|
+
* ordering scope.
|
|
36
|
+
*
|
|
37
|
+
* If two pods of the same service broadcast into the same room, each with its
|
|
38
|
+
* own per-process sequence, their epochs differ. The events interleave, the
|
|
39
|
+
* client's high-watermark lands on the newer epoch, and every subsequent event
|
|
40
|
+
* from the older-epoch pod compares as stale and is **silently dropped**. The
|
|
41
|
+
* failure only appears under horizontal scale, so it reaches production.
|
|
42
|
+
*
|
|
43
|
+
* For a multi-writer scope use one of:
|
|
44
|
+
*
|
|
45
|
+
* - a domain version (see above), or
|
|
46
|
+
* - a shared counter, e.g. `createRedisEventIdSequence()` from
|
|
47
|
+
* `@opinionated-machine/sse-rooms-redis`, which backs the counter with
|
|
48
|
+
* Redis `INCR` and shares one epoch across pods, or
|
|
49
|
+
* - a fixed `epoch` plus a `start` handed out from shared storage, so every
|
|
50
|
+
* writer contributes to a single ordered run.
|
|
51
|
+
*/
|
|
52
|
+
/**
|
|
53
|
+
* Epochs are digits only, and so are counters.
|
|
54
|
+
*
|
|
55
|
+
* The client's default version extractor (`@opinionated-machine/sse-fallback`)
|
|
56
|
+
* has to tell a generated id apart from any other id shape a stream might
|
|
57
|
+
* carry, and a UUID matches `<anything>-<digits>` too — ordering events by a
|
|
58
|
+
* chunk of a UUID drops them at random. Restricting the epoch to digits is what
|
|
59
|
+
* makes `<digits>-<digits>` an unambiguous marker, so every id these helpers
|
|
60
|
+
* produce is one that extractor can order. Any other id scheme needs
|
|
61
|
+
* `version.ofEvent` declared on the client.
|
|
62
|
+
*/
|
|
63
|
+
const NUMERIC_EPOCH_PATTERN = /^\d+$/;
|
|
64
|
+
const COUNTER_PAD = 12;
|
|
65
|
+
/**
|
|
66
|
+
* Largest counter value that still fits in {@link COUNTER_PAD} digits. Beyond
|
|
67
|
+
* this the id grows a digit and stops sorting after its predecessor, so
|
|
68
|
+
* {@link createEventIdSequence} refuses to produce it.
|
|
69
|
+
*/
|
|
70
|
+
export const MAX_EVENT_ID_COUNTER = 999_999_999_999;
|
|
71
|
+
/**
|
|
72
|
+
* Create a monotonic event-id sequence.
|
|
73
|
+
*
|
|
74
|
+
* Ids look like `"1754838000000-000000000042"`. Within one epoch they order
|
|
75
|
+
* lexicographically (fixed-width zero-padded counter).
|
|
76
|
+
*
|
|
77
|
+
* Sequences are in-memory and per-process, which makes them safe only for a
|
|
78
|
+
* single-writer ordering scope. See the module docs for the multi-writer
|
|
79
|
+
* hazard and the alternatives (domain versions, or a Redis-backed sequence).
|
|
80
|
+
*
|
|
81
|
+
* @throws TypeError if `epoch` is not a string of digits, or if `start` is not
|
|
82
|
+
* an integer in `[0, MAX_EVENT_ID_COUNTER - 1]`.
|
|
83
|
+
*/
|
|
84
|
+
export function createEventIdSequence(options) {
|
|
85
|
+
const epoch = options?.epoch ?? String(Date.now());
|
|
86
|
+
if (!NUMERIC_EPOCH_PATTERN.test(epoch)) {
|
|
87
|
+
throw new TypeError(`createEventIdSequence: \`epoch\` must be a non-empty string of digits, got "${epoch}"`);
|
|
88
|
+
}
|
|
89
|
+
const start = options?.start ?? 0;
|
|
90
|
+
if (!Number.isInteger(start) || start < 0 || start >= MAX_EVENT_ID_COUNTER) {
|
|
91
|
+
throw new TypeError(`createEventIdSequence: \`start\` must be an integer in [0, ${MAX_EVENT_ID_COUNTER - 1}], got ${String(start)}`);
|
|
92
|
+
}
|
|
93
|
+
let counter = start;
|
|
94
|
+
let current;
|
|
95
|
+
return {
|
|
96
|
+
next() {
|
|
97
|
+
if (counter >= MAX_EVENT_ID_COUNTER) {
|
|
98
|
+
throw new RangeError(`createEventIdSequence: counter exhausted at ${MAX_EVENT_ID_COUNTER}; start a new epoch`);
|
|
99
|
+
}
|
|
100
|
+
counter += 1;
|
|
101
|
+
current = `${epoch}-${String(counter).padStart(COUNTER_PAD, '0')}`;
|
|
102
|
+
return current;
|
|
103
|
+
},
|
|
104
|
+
get current() {
|
|
105
|
+
return current;
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const EVENT_ID_PATTERN = /^(\d+)-(\d+)$/;
|
|
110
|
+
/**
|
|
111
|
+
* Compare two event ids produced by {@link createEventIdSequence}.
|
|
112
|
+
*
|
|
113
|
+
* Returns `-1` / `0` / `1` when both ids belong to the same epoch, and
|
|
114
|
+
* `undefined` when they don't (or when either id doesn't match the expected
|
|
115
|
+
* format). An `undefined` result means the ids are not ordered relative to
|
|
116
|
+
* each other — clients should treat that as "resynchronize via a snapshot
|
|
117
|
+
* poll" rather than guess.
|
|
118
|
+
*/
|
|
119
|
+
export function compareEventIds(a, b) {
|
|
120
|
+
const parsedA = EVENT_ID_PATTERN.exec(a);
|
|
121
|
+
const parsedB = EVENT_ID_PATTERN.exec(b);
|
|
122
|
+
if (!parsedA || !parsedB)
|
|
123
|
+
return undefined;
|
|
124
|
+
if (parsedA[1] !== parsedB[1])
|
|
125
|
+
return undefined;
|
|
126
|
+
const counterA = BigInt(parsedA[2]);
|
|
127
|
+
const counterB = BigInt(parsedB[2]);
|
|
128
|
+
if (counterA < counterB)
|
|
129
|
+
return -1;
|
|
130
|
+
if (counterA > counterB)
|
|
131
|
+
return 1;
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Format a counter value as an event id in the shape
|
|
136
|
+
* {@link createEventIdSequence} produces, so ids from a shared counter compare
|
|
137
|
+
* against ids from an in-process sequence with the same epoch.
|
|
138
|
+
*
|
|
139
|
+
* @throws TypeError if `epoch` is not a string of digits.
|
|
140
|
+
* `createEventIdSequence()` refuses the same input: an id whose epoch is
|
|
141
|
+
* empty (`"-000000000001"`) or free-form is one {@link compareEventIds}
|
|
142
|
+
* cannot parse, so nothing downstream can order it.
|
|
143
|
+
* @throws RangeError if the counter does not fit the fixed id width.
|
|
144
|
+
*/
|
|
145
|
+
export function formatEventId(epoch, counter) {
|
|
146
|
+
if (!NUMERIC_EPOCH_PATTERN.test(epoch)) {
|
|
147
|
+
throw new TypeError(`formatEventId: \`epoch\` must be a non-empty string of digits, got "${epoch}"`);
|
|
148
|
+
}
|
|
149
|
+
const asBigInt = typeof counter === 'bigint' ? counter : BigInt(counter);
|
|
150
|
+
if (asBigInt < 1n || asBigInt > BigInt(MAX_EVENT_ID_COUNTER)) {
|
|
151
|
+
throw new RangeError(`formatEventId: counter must be in [1, ${MAX_EVENT_ID_COUNTER}], got ${String(counter)}`);
|
|
152
|
+
}
|
|
153
|
+
return `${epoch}-${asBigInt.toString().padStart(COUNTER_PAD, '0')}`;
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=eventIds.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eventIds.js","sourceRoot":"","sources":["../../../lib/sse/eventIds.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/sse/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
export type { AllContractEventNames, AllContractEvents, AnySSEContractDefinition, ExtractEventSchema, SSEContractDefinition, SSEEventSchemas, SSEMethod, } from '@lokalise/api-contracts';
|
|
2
|
+
export { createSSEStreamParser, type ParsedSSEEvent, type ParseSSEBufferResult, type ParseSSEStreamOptions, parseSSEBuffer, parseSSEEvents, parseSSEResponse, parseSSEStream, type SSEResponseLike, type SSEStreamParser, type SSEStreamParserOptions, } from '@opinionated-machine/sse-parser';
|
|
2
3
|
export { type BuildFastifySSERoutesReturnType, buildFastifyRoute, buildHandler, type FastifySSEHandlerConfig, type FastifySSEPreHandler, type FastifySSERouteOptions, type InferSSERequest, type RegisterSSERoutesOptions, type SSEContext, type SSEOnlyHandlers, type SSERouteHandler, type SSESession, } from '../routes/index.js';
|
|
3
4
|
export { AbstractSSEController, type SSEControllerConfig, type SSEEventSender, type SSELogger, type SSEMessage, } from './AbstractSSEController.js';
|
|
4
5
|
export { defineEvent, type SSEEventDefinition } from './defineEvent.js';
|
|
5
|
-
export {
|
|
6
|
+
export { type AsyncEventIdSequence, type CreateEventIdSequenceOptions, compareEventIds, createEventIdSequence, type EventIdSequence, formatEventId, MAX_EVENT_ID_COUNTER, } from './eventIds.js';
|
|
7
|
+
export { defineRoom, InMemoryAdapter, type PreDeliveryFilter, type RoomBroadcastOptions, type RoomNameResolver, type SSELogContext, type SSERoomAdapter, SSERoomBroadcaster, SSERoomEventPublisher, type SSERoomEventPublisherDependencies, type SSERoomEventPublishOptions, SSERoomManager, type SSERoomManagerConfig, type SSERoomMessageHandler, type SSERoomOperations, } from './rooms/index.js';
|
|
6
8
|
export { type SpiedSSESession, type SSESessionEvent, SSESessionSpy } from './SSESessionSpy.js';
|
|
7
|
-
export { type ParsedSSEEvent, type ParseSSEBufferResult, parseSSEBuffer, parseSSEEvents, } from './sseParser.js';
|
|
8
9
|
export { SSE_DIAGNOSTICS_HEADER, type SSEDiagnosticsScope, type SSESendFailure, } from './sseSendDiagnostics.js';
|
|
9
10
|
export { defineEventMetadata, type ExtractMetadata, type FilterVerdict, type IncomingEvent, type MetadataGuard, type MetadataGuards, type PublishResult, type ResolverResult, SSESubscriptionManager, type SSESubscriptionManagerConfig, type SubscriptionContext, type SubscriptionPolicy, type SubscriptionResolver, } from './subscriptions/index.js';
|
package/dist/lib/sse/index.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
// The wire-format parser lives in its own package so that the browser client
|
|
2
|
+
// (@opinionated-machine/sse-fallback) frames the stream with the same code the
|
|
3
|
+
// server's test helpers do.
|
|
4
|
+
export { createSSEStreamParser, parseSSEBuffer, parseSSEEvents, parseSSEResponse, parseSSEStream, } from '@opinionated-machine/sse-parser';
|
|
1
5
|
// Re-export route types from routes module
|
|
2
6
|
export { buildFastifyRoute, buildHandler, } from '../routes/index.js';
|
|
3
7
|
export { AbstractSSEController, } from './AbstractSSEController.js';
|
|
4
8
|
export { defineEvent } from './defineEvent.js';
|
|
9
|
+
export { compareEventIds, createEventIdSequence, formatEventId, MAX_EVENT_ID_COUNTER, } from './eventIds.js';
|
|
5
10
|
// Re-export room types and classes
|
|
6
|
-
export { defineRoom, InMemoryAdapter, SSERoomBroadcaster, SSERoomManager, } from './rooms/index.js';
|
|
11
|
+
export { defineRoom, InMemoryAdapter, SSERoomBroadcaster, SSERoomEventPublisher, SSERoomManager, } from './rooms/index.js';
|
|
7
12
|
export { SSESessionSpy } from './SSESessionSpy.js';
|
|
8
|
-
export { parseSSEBuffer, parseSSEEvents, } from './sseParser.js';
|
|
9
13
|
export { SSE_DIAGNOSTICS_HEADER, } from './sseSendDiagnostics.js';
|
|
10
14
|
// SSE Subscriptions
|
|
11
15
|
export { defineEventMetadata, SSESubscriptionManager, } from './subscriptions/index.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/sse/index.ts"],"names":[],"mappings":"AAUA,2CAA2C;AAC3C,OAAO,EAEL,iBAAiB,EACjB,YAAY,GAUb,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,qBAAqB,GAKtB,MAAM,4BAA4B,CAAA;AACnC,OAAO,EAAE,WAAW,EAA2B,MAAM,kBAAkB,CAAA;AACvE,mCAAmC;AACnC,OAAO,EACL,UAAU,EACV,eAAe,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/sse/index.ts"],"names":[],"mappings":"AAUA,6EAA6E;AAC7E,+EAA+E;AAC/E,4BAA4B;AAC5B,OAAO,EACL,qBAAqB,EAIrB,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,cAAc,GAIf,MAAM,iCAAiC,CAAA;AACxC,2CAA2C;AAC3C,OAAO,EAEL,iBAAiB,EACjB,YAAY,GAUb,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,qBAAqB,GAKtB,MAAM,4BAA4B,CAAA;AACnC,OAAO,EAAE,WAAW,EAA2B,MAAM,kBAAkB,CAAA;AACvE,OAAO,EAGL,eAAe,EACf,qBAAqB,EAErB,aAAa,EACb,oBAAoB,GACrB,MAAM,eAAe,CAAA;AACtB,mCAAmC;AACnC,OAAO,EACL,UAAU,EACV,eAAe,EAMf,kBAAkB,EAClB,qBAAqB,EAGrB,cAAc,GAIf,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAA8C,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAC9F,OAAO,EACL,sBAAsB,GAGvB,MAAM,yBAAyB,CAAA;AAChC,oBAAoB;AACpB,OAAO,EACL,mBAAmB,EAQnB,sBAAsB,GAKvB,MAAM,0BAA0B,CAAA"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { type Either, InternalError } from '@lokalise/node-core';
|
|
2
|
+
import type { z } from 'zod';
|
|
3
|
+
import type { SSEEventDefinition } from '../defineEvent.js';
|
|
4
|
+
import type { SSELogger } from '../sseTypes.js';
|
|
5
|
+
import type { SSERoomBroadcaster } from './SSERoomBroadcaster.js';
|
|
6
|
+
import type { RoomBroadcastOptions } from './types.js';
|
|
7
|
+
export type SSERoomEventPublisherDependencies = {
|
|
8
|
+
sseRoomBroadcaster: SSERoomBroadcaster;
|
|
9
|
+
logger: SSELogger;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Whatever the caller is working on behalf of, narrowed to the one thing the publisher needs
|
|
13
|
+
* from it. `@lokalise/fastify-extras`' `RequestContext` satisfies this structurally, as does a
|
|
14
|
+
* job or consumer context of your own, so neither this package nor its callers need an adapter.
|
|
15
|
+
*/
|
|
16
|
+
export type SSELogContext = {
|
|
17
|
+
logger: SSELogger;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Everything {@link SSERoomBroadcaster.broadcastToRoom} accepts, so the publisher is not a lossy
|
|
21
|
+
* wrapper over it.
|
|
22
|
+
*/
|
|
23
|
+
export type SSERoomEventPublishOptions = RoomBroadcastOptions & {
|
|
24
|
+
/**
|
|
25
|
+
* The SSE `id:` put on the wire. Defaults to a random UUID. Set it to a value a client can
|
|
26
|
+
* order, such as the sequence from `createEventIdSequence()`, when consumers deduplicate or
|
|
27
|
+
* resume by event id.
|
|
28
|
+
*/
|
|
29
|
+
id?: string;
|
|
30
|
+
/** The SSE `retry:` hint, in milliseconds: how long a client waits before reconnecting. */
|
|
31
|
+
retry?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Per-broadcast context handed to the pre-delivery filter, and to other nodes alongside the
|
|
34
|
+
* message. Only a filter reads it (`SSESubscriptionManager` installs one to evaluate its
|
|
35
|
+
* resolver pipeline), so it is redundant unless one is installed.
|
|
36
|
+
*/
|
|
37
|
+
metadata?: Record<string, unknown>;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Fire-and-forget broadcasting for domain code: a room event goes out and the caller does not
|
|
41
|
+
* await the fan-out.
|
|
42
|
+
*
|
|
43
|
+
* The typical producer is an event listener or a message handler whose primary work has already
|
|
44
|
+
* committed. It cannot retry a dropped hint, and awaiting the fan-out would tie its latency to
|
|
45
|
+
* the number of open connections. Code that needs the delivered count, or that can act on a
|
|
46
|
+
* delivery failure, should use {@link SSERoomBroadcaster} directly.
|
|
47
|
+
*
|
|
48
|
+
* The two ways publishing fails are treated differently, because they are different kinds of
|
|
49
|
+
* problem:
|
|
50
|
+
*
|
|
51
|
+
* - **A payload that violates its own event schema is a bug in the producer.** Nobody receives
|
|
52
|
+
* the event, so swallowing it means believing you published something you did not.
|
|
53
|
+
* {@link publish} throws. {@link safePublish} returns the error instead, for a caller that
|
|
54
|
+
* cannot absorb a throw.
|
|
55
|
+
* - **A failed broadcast is the world's problem**, and no caller here can retry it, so both
|
|
56
|
+
* methods log it. It also happens after the call has returned, which is why neither method can
|
|
57
|
+
* report it in a return value.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```typescript
|
|
61
|
+
* // In your DI module
|
|
62
|
+
* sseRoomEventPublisher: asSingletonClass(SSERoomEventPublisher),
|
|
63
|
+
*
|
|
64
|
+
* // In a listener. `requestContext` is passed straight through: the publisher reads its
|
|
65
|
+
* // logger, so a dropped event carries the correlation id of whatever produced it.
|
|
66
|
+
* this.publisher.publish(
|
|
67
|
+
* ProjectSse.roomResolver(projectId),
|
|
68
|
+
* ProjectSse.events.updated,
|
|
69
|
+
* payload,
|
|
70
|
+
* requestContext,
|
|
71
|
+
* )
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export declare class SSERoomEventPublisher {
|
|
75
|
+
private readonly sseRoomBroadcaster;
|
|
76
|
+
private readonly logger;
|
|
77
|
+
constructor({ sseRoomBroadcaster, logger }: SSERoomEventPublisherDependencies);
|
|
78
|
+
/**
|
|
79
|
+
* Validate `data` against the event's own schema and broadcast it to `room`, throwing if it
|
|
80
|
+
* fails.
|
|
81
|
+
*
|
|
82
|
+
* Validating here rather than leaving it to delivery is what makes the throw possible at all.
|
|
83
|
+
* Delivery-time validation runs once per connection, so it reports a mismatch once per open
|
|
84
|
+
* connection on every node, names the event but not the code that produced it, and does not
|
|
85
|
+
* run at all when nobody has joined the room. By then the call has long returned and there is
|
|
86
|
+
* nothing left to throw to.
|
|
87
|
+
*
|
|
88
|
+
* What goes on the wire is the parsed value, so a schema default is filled in once here
|
|
89
|
+
* instead of being left to every client. Delivery-time validation discards its own result and
|
|
90
|
+
* serializes what it was handed, so calling {@link SSERoomBroadcaster.broadcastToRoom}
|
|
91
|
+
* directly sends the unparsed input.
|
|
92
|
+
*
|
|
93
|
+
* `context` is whatever the caller is acting on behalf of; only its logger is read, so a
|
|
94
|
+
* request context goes in as-is. Omit it in a caller that has none and the injected logger is
|
|
95
|
+
* used, which costs a logged failure its correlation id and nothing else.
|
|
96
|
+
*
|
|
97
|
+
* @throws {InternalError} if `data` does not satisfy the event's schema.
|
|
98
|
+
*/
|
|
99
|
+
publish<T extends z.ZodType>(room: string | string[], event: SSEEventDefinition<string, T>, data: z.input<T>, context?: SSELogContext, options?: SSERoomEventPublishOptions): void;
|
|
100
|
+
/**
|
|
101
|
+
* {@link publish}, but a payload that fails its schema comes back as an error instead of being
|
|
102
|
+
* thrown.
|
|
103
|
+
*
|
|
104
|
+
* For a producer that cannot absorb a throw: a message handler whose primary work has already
|
|
105
|
+
* committed would be retried in full and redo it, and the retry cannot succeed anyway, since a
|
|
106
|
+
* malformed payload fails the same way every time. Prefer {@link publish} everywhere else.
|
|
107
|
+
*
|
|
108
|
+
* The failure is logged as well as returned, so a caller that ignores the result still leaves
|
|
109
|
+
* a trace rather than silence.
|
|
110
|
+
*
|
|
111
|
+
* @returns `{ result: true }` once the payload has been validated and handed to the
|
|
112
|
+
* broadcaster. That is acceptance, not delivery: the fan-out has not happened yet, and its
|
|
113
|
+
* own failure is logged rather than returned.
|
|
114
|
+
*/
|
|
115
|
+
safePublish<T extends z.ZodType>(room: string | string[], event: SSEEventDefinition<string, T>, data: z.input<T>, context?: SSELogContext, options?: SSERoomEventPublishOptions): Either<InternalError, true>;
|
|
116
|
+
/**
|
|
117
|
+
* The shared tail of both methods. A failed broadcast is logged rather than surfaced: it
|
|
118
|
+
* happens after the caller has returned, and none of these callers could retry it anyway.
|
|
119
|
+
*/
|
|
120
|
+
private broadcast;
|
|
121
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { InternalError } from '@lokalise/node-core';
|
|
2
|
+
/** The same error either way, so a caller switching between the two methods sees one shape. */
|
|
3
|
+
const validationError = (room, event, error) => new InternalError({
|
|
4
|
+
message: `SSE event validation failed for event "${event}": ${error.message}`,
|
|
5
|
+
errorCode: 'SSE_EVENT_VALIDATION_FAILED',
|
|
6
|
+
details: { room: Array.isArray(room) ? room.join(',') : room, event },
|
|
7
|
+
});
|
|
8
|
+
/**
|
|
9
|
+
* Fire-and-forget broadcasting for domain code: a room event goes out and the caller does not
|
|
10
|
+
* await the fan-out.
|
|
11
|
+
*
|
|
12
|
+
* The typical producer is an event listener or a message handler whose primary work has already
|
|
13
|
+
* committed. It cannot retry a dropped hint, and awaiting the fan-out would tie its latency to
|
|
14
|
+
* the number of open connections. Code that needs the delivered count, or that can act on a
|
|
15
|
+
* delivery failure, should use {@link SSERoomBroadcaster} directly.
|
|
16
|
+
*
|
|
17
|
+
* The two ways publishing fails are treated differently, because they are different kinds of
|
|
18
|
+
* problem:
|
|
19
|
+
*
|
|
20
|
+
* - **A payload that violates its own event schema is a bug in the producer.** Nobody receives
|
|
21
|
+
* the event, so swallowing it means believing you published something you did not.
|
|
22
|
+
* {@link publish} throws. {@link safePublish} returns the error instead, for a caller that
|
|
23
|
+
* cannot absorb a throw.
|
|
24
|
+
* - **A failed broadcast is the world's problem**, and no caller here can retry it, so both
|
|
25
|
+
* methods log it. It also happens after the call has returned, which is why neither method can
|
|
26
|
+
* report it in a return value.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* // In your DI module
|
|
31
|
+
* sseRoomEventPublisher: asSingletonClass(SSERoomEventPublisher),
|
|
32
|
+
*
|
|
33
|
+
* // In a listener. `requestContext` is passed straight through: the publisher reads its
|
|
34
|
+
* // logger, so a dropped event carries the correlation id of whatever produced it.
|
|
35
|
+
* this.publisher.publish(
|
|
36
|
+
* ProjectSse.roomResolver(projectId),
|
|
37
|
+
* ProjectSse.events.updated,
|
|
38
|
+
* payload,
|
|
39
|
+
* requestContext,
|
|
40
|
+
* )
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export class SSERoomEventPublisher {
|
|
44
|
+
sseRoomBroadcaster;
|
|
45
|
+
logger;
|
|
46
|
+
constructor({ sseRoomBroadcaster, logger }) {
|
|
47
|
+
this.sseRoomBroadcaster = sseRoomBroadcaster;
|
|
48
|
+
this.logger = logger;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Validate `data` against the event's own schema and broadcast it to `room`, throwing if it
|
|
52
|
+
* fails.
|
|
53
|
+
*
|
|
54
|
+
* Validating here rather than leaving it to delivery is what makes the throw possible at all.
|
|
55
|
+
* Delivery-time validation runs once per connection, so it reports a mismatch once per open
|
|
56
|
+
* connection on every node, names the event but not the code that produced it, and does not
|
|
57
|
+
* run at all when nobody has joined the room. By then the call has long returned and there is
|
|
58
|
+
* nothing left to throw to.
|
|
59
|
+
*
|
|
60
|
+
* What goes on the wire is the parsed value, so a schema default is filled in once here
|
|
61
|
+
* instead of being left to every client. Delivery-time validation discards its own result and
|
|
62
|
+
* serializes what it was handed, so calling {@link SSERoomBroadcaster.broadcastToRoom}
|
|
63
|
+
* directly sends the unparsed input.
|
|
64
|
+
*
|
|
65
|
+
* `context` is whatever the caller is acting on behalf of; only its logger is read, so a
|
|
66
|
+
* request context goes in as-is. Omit it in a caller that has none and the injected logger is
|
|
67
|
+
* used, which costs a logged failure its correlation id and nothing else.
|
|
68
|
+
*
|
|
69
|
+
* @throws {InternalError} if `data` does not satisfy the event's schema.
|
|
70
|
+
*/
|
|
71
|
+
publish(room, event, data, context, options) {
|
|
72
|
+
const validation = event.schema.safeParse(data);
|
|
73
|
+
if (!validation.success) {
|
|
74
|
+
throw validationError(room, event.event, validation.error);
|
|
75
|
+
}
|
|
76
|
+
this.broadcast(room, event, validation.data, context, options);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* {@link publish}, but a payload that fails its schema comes back as an error instead of being
|
|
80
|
+
* thrown.
|
|
81
|
+
*
|
|
82
|
+
* For a producer that cannot absorb a throw: a message handler whose primary work has already
|
|
83
|
+
* committed would be retried in full and redo it, and the retry cannot succeed anyway, since a
|
|
84
|
+
* malformed payload fails the same way every time. Prefer {@link publish} everywhere else.
|
|
85
|
+
*
|
|
86
|
+
* The failure is logged as well as returned, so a caller that ignores the result still leaves
|
|
87
|
+
* a trace rather than silence.
|
|
88
|
+
*
|
|
89
|
+
* @returns `{ result: true }` once the payload has been validated and handed to the
|
|
90
|
+
* broadcaster. That is acceptance, not delivery: the fan-out has not happened yet, and its
|
|
91
|
+
* own failure is logged rather than returned.
|
|
92
|
+
*/
|
|
93
|
+
safePublish(room, event, data, context, options) {
|
|
94
|
+
const validation = event.schema.safeParse(data);
|
|
95
|
+
if (!validation.success) {
|
|
96
|
+
const logger = context?.logger ?? this.logger;
|
|
97
|
+
logger.error({ room, event: event.event, issues: validation.error.issues }, 'Refusing to broadcast an SSE event that fails its own schema');
|
|
98
|
+
return { error: validationError(room, event.event, validation.error) };
|
|
99
|
+
}
|
|
100
|
+
this.broadcast(room, event, validation.data, context, options);
|
|
101
|
+
return { result: true };
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The shared tail of both methods. A failed broadcast is logged rather than surfaced: it
|
|
105
|
+
* happens after the caller has returned, and none of these callers could retry it anyway.
|
|
106
|
+
*/
|
|
107
|
+
broadcast(room, event,
|
|
108
|
+
// Already schema-valid, so the re-parse at delivery accepts it. The cast below is only
|
|
109
|
+
// needed because `z.output` is not `z.input` for a schema that defaults or transforms.
|
|
110
|
+
parsed, context, options) {
|
|
111
|
+
const logger = context?.logger ?? this.logger;
|
|
112
|
+
this.sseRoomBroadcaster
|
|
113
|
+
.broadcastToRoom(room, event, parsed, options)
|
|
114
|
+
.catch((error) => {
|
|
115
|
+
logger.error({ error, room, event: event.event }, 'Failed to broadcast SSE event');
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=SSERoomEventPublisher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SSERoomEventPublisher.js","sourceRoot":"","sources":["../../../../lib/sse/rooms/SSERoomEventPublisher.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { InMemoryAdapter } from './adapters/InMemoryAdapter.js';
|
|
2
2
|
export { defineRoom } from './defineRoom.js';
|
|
3
3
|
export { SSERoomBroadcaster } from './SSERoomBroadcaster.js';
|
|
4
|
+
export { type SSELogContext, SSERoomEventPublisher, type SSERoomEventPublisherDependencies, type SSERoomEventPublishOptions, } from './SSERoomEventPublisher.js';
|
|
4
5
|
export { SSERoomManager } from './SSERoomManager.js';
|
|
5
6
|
export type { PreDeliveryFilter, RoomBroadcastOptions, RoomNameResolver, SSERoomAdapter, SSERoomManagerConfig, SSERoomMessageHandler, SSERoomOperations, } from './types.js';
|
|
@@ -3,5 +3,6 @@
|
|
|
3
3
|
export { InMemoryAdapter } from './adapters/InMemoryAdapter.js';
|
|
4
4
|
export { defineRoom } from './defineRoom.js';
|
|
5
5
|
export { SSERoomBroadcaster } from './SSERoomBroadcaster.js';
|
|
6
|
+
export { SSERoomEventPublisher, } from './SSERoomEventPublisher.js';
|
|
6
7
|
export { SSERoomManager } from './SSERoomManager.js';
|
|
7
8
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../lib/sse/rooms/index.ts"],"names":[],"mappings":"AAAA,oBAAoB;AAEpB,WAAW;AACX,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../lib/sse/rooms/index.ts"],"names":[],"mappings":"AAAA,oBAAoB;AAEpB,WAAW;AACX,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAC5D,OAAO,EAEL,qBAAqB,GAGtB,MAAM,4BAA4B,CAAA;AACnC,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA"}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { SSEEventSchemas } from '@lokalise/api-contracts';
|
|
9
9
|
import { type ApiContract } from '@lokalise/api-contracts';
|
|
10
|
-
import type { ParsedSSEEvent } from '
|
|
10
|
+
import type { ParsedSSEEvent } from '@opinionated-machine/sse-parser';
|
|
11
11
|
import type { ApiSSEEvent } from './apiSseTestTypes.ts';
|
|
12
12
|
/**
|
|
13
13
|
* The contract's SSE schemas, merged across every declared status, or a thrown error naming
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { buildRequestPath, resolveResponseEntry, } from '@lokalise/api-contracts';
|
|
2
|
-
import {
|
|
2
|
+
import { createSSEStreamParser, parseSSEEvents, } from '@opinionated-machine/sse-parser';
|
|
3
3
|
import { describeSendFailures, openSSEDiagnosticsScope, unhandledSendFailures, } from "../sse/sseSendDiagnostics.js";
|
|
4
4
|
import { assertSSEResponse, mediaTypeOf, resolveApiSseSchemas, SSE_CONTENT_TYPE, validateApiSseEvent, } from "./apiSseEventValidation.js";
|
|
5
5
|
import { truncateBody } from "./sseInjectShared.js";
|
|
@@ -156,15 +156,15 @@ class InjectedSSEStream {
|
|
|
156
156
|
throw err;
|
|
157
157
|
}
|
|
158
158
|
let body = '';
|
|
159
|
-
let buffer = '';
|
|
160
159
|
const decoder = new TextDecoder();
|
|
160
|
+
// Owns the partial frame across chunks, so a frame split by a flush boundary
|
|
161
|
+
// is not read as two.
|
|
162
|
+
const parser = createSSEStreamParser();
|
|
161
163
|
try {
|
|
162
164
|
for await (const chunk of res.stream()) {
|
|
163
165
|
const text = typeof chunk === 'string' ? chunk : decoder.decode(chunk, { stream: true });
|
|
164
166
|
body += text;
|
|
165
|
-
|
|
166
|
-
const { events, remaining } = parseSSEBuffer(buffer);
|
|
167
|
-
buffer = remaining;
|
|
167
|
+
const events = parser.push(text);
|
|
168
168
|
if (events.length > 0) {
|
|
169
169
|
this.received.push(...events);
|
|
170
170
|
this.notify();
|
|
@@ -175,11 +175,10 @@ class InjectedSSEStream {
|
|
|
175
175
|
this.fail(err);
|
|
176
176
|
throw err;
|
|
177
177
|
}
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
178
|
+
// Anything still buffered is a frame the response ended in the middle of. The spec
|
|
179
|
+
// discards pending data at the end of a stream, so it is not delivered as an event:
|
|
180
|
+
// a truncated payload reported as a real one hides the handler bug that produced it.
|
|
181
|
+
// The raw text is still on `body` for a test that wants to look.
|
|
183
182
|
this.ended = true;
|
|
184
183
|
this.notify();
|
|
185
184
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apiSseInjectHelpers.js","sourceRoot":"","sources":["../../../lib/testing/apiSseInjectHelpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,gBAAgB,EAIhB,oBAAoB,GACrB,MAAM,yBAAyB,CAAA;
|
|
1
|
+
{"version":3,"file":"apiSseInjectHelpers.js","sourceRoot":"","sources":["../../../lib/testing/apiSseInjectHelpers.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ApiContract } from '@lokalise/api-contracts';
|
|
2
|
+
import { type ParsedSSEEvent } from '@opinionated-machine/sse-parser';
|
|
2
3
|
import type { SSESession } from '../routes/fastifyRouteTypes.ts';
|
|
3
4
|
import type { SpiedSSESession, SSESessionSpy } from '../sse/SSESessionSpy.ts';
|
|
4
|
-
import { type ParsedSSEEvent } from '../sse/sseParser.ts';
|
|
5
5
|
import type { ApiSSEEvent } from './apiSseTestTypes.ts';
|
|
6
6
|
/**
|
|
7
7
|
* Interface for objects that have a sessionSpy (e.g., SSE controllers in test mode).
|
|
@@ -163,11 +163,19 @@ export declare class SSEHttpClient {
|
|
|
163
163
|
* subsequent body read with an `AbortError`.
|
|
164
164
|
*/
|
|
165
165
|
readonly response: Response;
|
|
166
|
+
/**
|
|
167
|
+
* Optional hook invoked with every decoded raw text chunk as it arrives —
|
|
168
|
+
* including comment frames (e.g. `: heartbeat`) that the SSE parser drops.
|
|
169
|
+
* Useful for asserting heartbeat delivery or tracking byte-level liveness.
|
|
170
|
+
* Only fires while the stream is being consumed via events()/collectEvents().
|
|
171
|
+
*/
|
|
172
|
+
onRawChunk?: (chunk: string) => void;
|
|
166
173
|
private readonly abortController;
|
|
167
174
|
private readonly responseBody;
|
|
168
175
|
private streamReader;
|
|
169
176
|
private readonly decoder;
|
|
170
|
-
|
|
177
|
+
/** Owns the partial-frame buffer and the Last-Event-ID cursor across chunks. */
|
|
178
|
+
private readonly parser;
|
|
171
179
|
private closed;
|
|
172
180
|
private constructor();
|
|
173
181
|
/**
|