@chidchanun/bcp 0.2.12 → 0.2.14
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/README.md +223 -222
- package/docs/README.md +50 -54
- package/docs/api-manifest.json +32 -71
- package/docs/api-reference.md +165 -58
- package/docs/docs-web-manifest.json +9 -5
- package/docs/platform-manifest.json +33 -4
- package/docs/realtime-platform.md +447 -0
- package/docs/releases/0.2.13.md +122 -0
- package/docs/releases/0.2.14.md +241 -0
- package/docs/testing-platform.md +602 -0
- package/package.json +11 -1
- package/packages/bundler/src/client-boundary.ts +2 -0
- package/packages/client/src/realtime.mjs +973 -0
- package/packages/client/src/realtime.ts +31 -0
- package/packages/client/src/testing.mjs +2357 -0
- package/packages/client/src/testing.ts +52 -0
- package/packages/server/src/realtime.ts +1518 -0
- package/packages/server/src/testing-page.ts +274 -0
- package/packages/server/src/testing.ts +1884 -0
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
# Realtime Platform
|
|
2
|
+
|
|
3
|
+
BCP Framework `0.2.13` adds the server-only `bcp/realtime` entrypoint for application realtime delivery without forcing one WebSocket or pub/sub provider.
|
|
4
|
+
|
|
5
|
+
The platform separates four concerns:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
connection/session
|
|
9
|
+
|
|
|
10
|
+
+--> channel membership
|
|
11
|
+
+--> authentication / authorization
|
|
12
|
+
+--> heartbeat
|
|
13
|
+
|
|
|
14
|
+
v
|
|
15
|
+
RealtimeHub
|
|
16
|
+
|
|
|
17
|
+
+--> RealtimeBroker
|
|
18
|
+
| -> cross-hub delivery
|
|
19
|
+
|
|
|
20
|
+
+--> RealtimePresenceStore
|
|
21
|
+
| -> room/channel presence
|
|
22
|
+
|
|
|
23
|
+
+--> RealtimeSocket adapter
|
|
24
|
+
| -> WebSocket provider integration
|
|
25
|
+
|
|
|
26
|
+
+--> SSE Response
|
|
27
|
+
-> built-in HTTP streaming
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Create a hub
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import {
|
|
34
|
+
createRealtime,
|
|
35
|
+
} from "bcp/realtime";
|
|
36
|
+
|
|
37
|
+
export const realtime =
|
|
38
|
+
createRealtime();
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The default broker and presence store are process-local and intended for development, tests and single-process deployments.
|
|
42
|
+
|
|
43
|
+
## Channels and rooms
|
|
44
|
+
|
|
45
|
+
BCP treats rooms as named channels.
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
const connection =
|
|
49
|
+
await realtime.connect();
|
|
50
|
+
|
|
51
|
+
await connection.join(
|
|
52
|
+
"orders:42"
|
|
53
|
+
);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Broadcast to everyone currently joined:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
await realtime.broadcast(
|
|
60
|
+
"orders:42",
|
|
61
|
+
"order.updated",
|
|
62
|
+
{
|
|
63
|
+
status: "paid",
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
You can exclude one connection when echo suppression is needed:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
await realtime.broadcast(
|
|
72
|
+
"chat:general",
|
|
73
|
+
"chat.message",
|
|
74
|
+
message,
|
|
75
|
+
{
|
|
76
|
+
excludeConnectionId:
|
|
77
|
+
sender.id,
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Presence
|
|
83
|
+
|
|
84
|
+
Presence is stored separately from message delivery.
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
await connection.join(
|
|
88
|
+
"project:7",
|
|
89
|
+
{
|
|
90
|
+
presence: {
|
|
91
|
+
status: "online",
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const members =
|
|
97
|
+
await realtime.members(
|
|
98
|
+
"project:7"
|
|
99
|
+
);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The default `createMemoryRealtimePresenceStore()` is local-only. Multi-instance deployments should use a shared store whose `join`, `leave`, `leaveConnection`, `touch` and `list` operations are visible to every instance.
|
|
103
|
+
|
|
104
|
+
## Authentication
|
|
105
|
+
|
|
106
|
+
A realtime connection may be authenticated when it is created:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
const realtime =
|
|
110
|
+
createRealtime({
|
|
111
|
+
authenticate:
|
|
112
|
+
async ({ request }) => {
|
|
113
|
+
if (!request) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return loadRealtimeUser(
|
|
118
|
+
request
|
|
119
|
+
);
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
getUserId:
|
|
123
|
+
user => user.id,
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The authenticated value is available as:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
connection.user
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
BCP does not automatically turn a failed authentication callback into a rejected connection. Applications that require authentication should enforce that policy in their authentication callback/adapter or channel authorization policy.
|
|
134
|
+
|
|
135
|
+
## Channel authorization
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
const realtime =
|
|
139
|
+
createRealtime({
|
|
140
|
+
authorizeChannel:
|
|
141
|
+
async ({
|
|
142
|
+
connection,
|
|
143
|
+
channel,
|
|
144
|
+
}) => {
|
|
145
|
+
if (
|
|
146
|
+
channel.startsWith(
|
|
147
|
+
"private:"
|
|
148
|
+
)
|
|
149
|
+
) {
|
|
150
|
+
return Boolean(
|
|
151
|
+
connection.user
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return true;
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Authorization is evaluated before a connection joins a channel.
|
|
161
|
+
|
|
162
|
+
## Client-originated events
|
|
163
|
+
|
|
164
|
+
Register a server event handler:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
realtime.on(
|
|
168
|
+
"chat.message",
|
|
169
|
+
async ({
|
|
170
|
+
connection,
|
|
171
|
+
channel,
|
|
172
|
+
payload,
|
|
173
|
+
}) => {
|
|
174
|
+
await saveMessage(
|
|
175
|
+
payload
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
await realtime.broadcast(
|
|
179
|
+
channel,
|
|
180
|
+
"chat.message",
|
|
181
|
+
payload,
|
|
182
|
+
{
|
|
183
|
+
excludeConnectionId:
|
|
184
|
+
connection.id,
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
);
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Calling `connection.emit()` runs registered server handlers. It does not automatically broadcast the message. This keeps application authorization and mutation logic explicit.
|
|
192
|
+
|
|
193
|
+
## WebSocket adapter contract
|
|
194
|
+
|
|
195
|
+
BCP does not install `ws`, Socket.IO, uWebSockets.js or another WebSocket server package.
|
|
196
|
+
|
|
197
|
+
Adapt your selected provider to this shape:
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
interface RealtimeSocket {
|
|
201
|
+
send(
|
|
202
|
+
data: string
|
|
203
|
+
): void | Promise<void>;
|
|
204
|
+
|
|
205
|
+
close?(
|
|
206
|
+
code?: number,
|
|
207
|
+
reason?: string
|
|
208
|
+
): void | Promise<void>;
|
|
209
|
+
|
|
210
|
+
onMessage(
|
|
211
|
+
listener: (
|
|
212
|
+
data: string
|
|
213
|
+
) => void | Promise<void>
|
|
214
|
+
): () => void;
|
|
215
|
+
|
|
216
|
+
onClose(
|
|
217
|
+
listener: () =>
|
|
218
|
+
void | Promise<void>
|
|
219
|
+
): () => void;
|
|
220
|
+
|
|
221
|
+
onError?(
|
|
222
|
+
listener: (
|
|
223
|
+
error: unknown
|
|
224
|
+
) => void | Promise<void>
|
|
225
|
+
): () => void;
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Attach it:
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
const connection =
|
|
233
|
+
await realtime.attachSocket(
|
|
234
|
+
socketAdapter,
|
|
235
|
+
{
|
|
236
|
+
request,
|
|
237
|
+
}
|
|
238
|
+
);
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Socket message protocol
|
|
242
|
+
|
|
243
|
+
Inbound JSON messages use these shapes:
|
|
244
|
+
|
|
245
|
+
```json
|
|
246
|
+
{
|
|
247
|
+
"type": "join",
|
|
248
|
+
"channel": "chat:general",
|
|
249
|
+
"presence": {
|
|
250
|
+
"status": "online"
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
```json
|
|
256
|
+
{
|
|
257
|
+
"type": "leave",
|
|
258
|
+
"channel": "chat:general"
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
```json
|
|
263
|
+
{
|
|
264
|
+
"type": "event",
|
|
265
|
+
"channel": "chat:general",
|
|
266
|
+
"event": "chat.message",
|
|
267
|
+
"payload": {
|
|
268
|
+
"text": "hello"
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Heartbeat input:
|
|
274
|
+
|
|
275
|
+
```json
|
|
276
|
+
{
|
|
277
|
+
"type": "ping"
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
The server answers with a `realtime.pong` event.
|
|
282
|
+
|
|
283
|
+
## Server-Sent Events
|
|
284
|
+
|
|
285
|
+
SSE does not require a WebSocket provider.
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
export function GET(
|
|
289
|
+
request: Request
|
|
290
|
+
) {
|
|
291
|
+
return realtime.sse(
|
|
292
|
+
"jobs:42",
|
|
293
|
+
{
|
|
294
|
+
signal:
|
|
295
|
+
request.signal,
|
|
296
|
+
}
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Equivalent standalone helper:
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
import {
|
|
305
|
+
createRealtimeSseResponse,
|
|
306
|
+
} from "bcp/realtime";
|
|
307
|
+
|
|
308
|
+
return createRealtimeSseResponse(
|
|
309
|
+
realtime,
|
|
310
|
+
"jobs:42",
|
|
311
|
+
{
|
|
312
|
+
signal:
|
|
313
|
+
request.signal,
|
|
314
|
+
}
|
|
315
|
+
);
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
The response includes:
|
|
319
|
+
|
|
320
|
+
```text
|
|
321
|
+
Content-Type: text/event-stream
|
|
322
|
+
Cache-Control: no-cache, no-transform
|
|
323
|
+
Connection: keep-alive
|
|
324
|
+
X-Accel-Buffering: no
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Optional settings include `retryMs`, `keepAliveMs`, an event-name filter and additional response headers.
|
|
328
|
+
|
|
329
|
+
## Heartbeat and stale connections
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
const realtime =
|
|
333
|
+
createRealtime({
|
|
334
|
+
heartbeatTimeoutMs:
|
|
335
|
+
60_000,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const heartbeat =
|
|
339
|
+
realtime.startHeartbeat({
|
|
340
|
+
intervalMs: 20_000,
|
|
341
|
+
});
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
The heartbeat runner sends `realtime.ping` and calls `sweepStale()` periodically.
|
|
345
|
+
|
|
346
|
+
A client `ping` message or any valid socket message updates the connection's `lastSeenAt` timestamp.
|
|
347
|
+
|
|
348
|
+
Shutdown:
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
await heartbeat.stop();
|
|
352
|
+
await realtime.close();
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
## Cross-instance broker
|
|
356
|
+
|
|
357
|
+
`RealtimeBroker` is the pub/sub boundary:
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
interface RealtimeBroker {
|
|
361
|
+
publish(
|
|
362
|
+
message: RealtimeEnvelope
|
|
363
|
+
): Promise<void>;
|
|
364
|
+
|
|
365
|
+
subscribe(
|
|
366
|
+
listener: (
|
|
367
|
+
message: RealtimeEnvelope
|
|
368
|
+
) => void | Promise<void>
|
|
369
|
+
): () => void;
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
The built-in memory broker can be shared by multiple hubs in one process and is useful for tests.
|
|
374
|
+
|
|
375
|
+
For multiple Node processes or containers, implement the broker using shared infrastructure such as Redis Pub/Sub, NATS or another application-selected service.
|
|
376
|
+
|
|
377
|
+
A production topology may look like:
|
|
378
|
+
|
|
379
|
+
```text
|
|
380
|
+
Browser A -> App A ----\
|
|
381
|
+
Shared Broker
|
|
382
|
+
Browser B -> App B ----/
|
|
383
|
+
| |
|
|
384
|
+
+---- Shared Presence Store
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
BCP intentionally does not install or own a Redis/NATS connection.
|
|
388
|
+
|
|
389
|
+
## Jobs, workflows and events
|
|
390
|
+
|
|
391
|
+
Realtime is designed to be the delivery edge for existing backend systems.
|
|
392
|
+
|
|
393
|
+
Job progress:
|
|
394
|
+
|
|
395
|
+
```ts
|
|
396
|
+
await realtime.broadcast(
|
|
397
|
+
`jobs:${job.id}`,
|
|
398
|
+
"job.progress",
|
|
399
|
+
{
|
|
400
|
+
progress: 60,
|
|
401
|
+
}
|
|
402
|
+
);
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
Workflow progress:
|
|
406
|
+
|
|
407
|
+
```ts
|
|
408
|
+
await realtime.broadcast(
|
|
409
|
+
`workflow:${run.id}`,
|
|
410
|
+
"workflow.updated",
|
|
411
|
+
run
|
|
412
|
+
);
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
Outbox/event delivery can broadcast after durable publication/consumption according to application semantics.
|
|
416
|
+
|
|
417
|
+
Do not treat a transient realtime broadcast as a durable event store. Important business events should continue to use the transactional outbox/durable queue path.
|
|
418
|
+
|
|
419
|
+
## Delivery semantics
|
|
420
|
+
|
|
421
|
+
Realtime delivery is transient. A disconnected client can miss a broadcast.
|
|
422
|
+
|
|
423
|
+
Use:
|
|
424
|
+
|
|
425
|
+
- database/outbox for durable business facts,
|
|
426
|
+
- jobs/workflows for durable background execution,
|
|
427
|
+
- realtime for live client delivery.
|
|
428
|
+
|
|
429
|
+
For reconnect catch-up, expose application state or an event history endpoint and let the client refetch after reconnecting.
|
|
430
|
+
|
|
431
|
+
## Security
|
|
432
|
+
|
|
433
|
+
`bcp/realtime` is server-only.
|
|
434
|
+
|
|
435
|
+
Applications should:
|
|
436
|
+
|
|
437
|
+
- authenticate connections where required,
|
|
438
|
+
- authorize every private channel join,
|
|
439
|
+
- validate client event payloads,
|
|
440
|
+
- rate-limit untrusted client messages when appropriate,
|
|
441
|
+
- avoid placing secrets in broadcast payloads,
|
|
442
|
+
- use TLS (`wss:` / HTTPS) in production,
|
|
443
|
+
- apply origin checks at the WebSocket upgrade boundary when browser-origin restrictions are required.
|
|
444
|
+
|
|
445
|
+
## 0.2.13 scope
|
|
446
|
+
|
|
447
|
+
`0.2.13` provides the provider-neutral realtime runtime contract. It does not add a built-in Redis broker, WebSocket server dependency, durable message replay or browser client SDK.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# BCP Framework 0.2.13
|
|
2
|
+
|
|
3
|
+
Status: unreleased development target.
|
|
4
|
+
|
|
5
|
+
## Realtime Platform
|
|
6
|
+
|
|
7
|
+
`0.2.13` introduces `bcp/realtime`, a server-only provider-neutral realtime layer for channels/rooms, presence, cross-hub broker delivery, WebSocket adapter integration, SSE responses and heartbeat lifecycle.
|
|
8
|
+
|
|
9
|
+
## Public API
|
|
10
|
+
|
|
11
|
+
New entrypoint:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import {
|
|
15
|
+
createMemoryRealtimeBroker,
|
|
16
|
+
createMemoryRealtimePresenceStore,
|
|
17
|
+
createRealtime,
|
|
18
|
+
createRealtimeSseResponse,
|
|
19
|
+
} from "bcp/realtime";
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Key contracts:
|
|
23
|
+
|
|
24
|
+
- `RealtimeHub`
|
|
25
|
+
- `RealtimeConnection`
|
|
26
|
+
- `RealtimeBroker`
|
|
27
|
+
- `RealtimePresenceStore`
|
|
28
|
+
- `RealtimeSocket`
|
|
29
|
+
- `RealtimeEnvelope`
|
|
30
|
+
- `RealtimeEventHandler`
|
|
31
|
+
- `RealtimeAuthenticate`
|
|
32
|
+
- `RealtimeAuthorizeChannel`
|
|
33
|
+
|
|
34
|
+
## Channels and presence
|
|
35
|
+
|
|
36
|
+
Connections can join/leave named channels and attach typed presence metadata. Hub broadcasts deliver only to local connections that joined the channel, while `RealtimeBroker` distributes envelopes across hubs.
|
|
37
|
+
|
|
38
|
+
`createMemoryRealtimeBroker()` and `createMemoryRealtimePresenceStore()` provide deterministic local/test implementations.
|
|
39
|
+
|
|
40
|
+
## Authentication and authorization
|
|
41
|
+
|
|
42
|
+
`createRealtime()` accepts:
|
|
43
|
+
|
|
44
|
+
- `authenticate` for request/data-based connection identity,
|
|
45
|
+
- `getUserId` for presence identity,
|
|
46
|
+
- `authorizeChannel` for private-channel join checks.
|
|
47
|
+
|
|
48
|
+
Authentication policy remains application-owned so public and authenticated realtime endpoints can share the same runtime primitives.
|
|
49
|
+
|
|
50
|
+
## WebSocket integration
|
|
51
|
+
|
|
52
|
+
BCP does not add a mandatory WebSocket dependency. `RealtimeSocket` defines the minimal transport surface required by `attachSocket()`.
|
|
53
|
+
|
|
54
|
+
The built-in JSON protocol supports:
|
|
55
|
+
|
|
56
|
+
- `join`
|
|
57
|
+
- `leave`
|
|
58
|
+
- `event`
|
|
59
|
+
- `ping`
|
|
60
|
+
|
|
61
|
+
Valid client messages refresh connection liveness. Ping receives `realtime.pong`.
|
|
62
|
+
|
|
63
|
+
## Server-Sent Events
|
|
64
|
+
|
|
65
|
+
`RealtimeHub.sse()` and `createRealtimeSseResponse()` produce Web-standard streaming `Response` objects with event-stream headers, optional retry hints, event filtering and keep-alive comments.
|
|
66
|
+
|
|
67
|
+
## Heartbeat lifecycle
|
|
68
|
+
|
|
69
|
+
`startHeartbeat()` periodically sends `realtime.ping` and sweeps stale connections. `sweepStale()` can also be invoked manually for deterministic infrastructure loops and tests.
|
|
70
|
+
|
|
71
|
+
## Multi-instance contract
|
|
72
|
+
|
|
73
|
+
The memory broker and presence store are process-local.
|
|
74
|
+
|
|
75
|
+
Production multi-instance applications can implement shared `RealtimeBroker` and `RealtimePresenceStore` adapters using infrastructure such as Redis, NATS or another service without coupling BCP to one provider/client library.
|
|
76
|
+
|
|
77
|
+
## Integration model
|
|
78
|
+
|
|
79
|
+
Realtime complements, but does not replace, durable platform primitives:
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
Database / Transactional Outbox
|
|
83
|
+
|
|
|
84
|
+
v
|
|
85
|
+
Durable Jobs / Workflow
|
|
86
|
+
|
|
|
87
|
+
v
|
|
88
|
+
Realtime Hub
|
|
89
|
+
|
|
|
90
|
+
WebSocket / SSE
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Realtime broadcasts are transient. Applications that require reconnect catch-up should refetch durable state/history after reconnect.
|
|
94
|
+
|
|
95
|
+
## Package/runtime
|
|
96
|
+
|
|
97
|
+
- `bcp/realtime` is server-only.
|
|
98
|
+
- Client page/island graphs reject direct realtime imports.
|
|
99
|
+
- publish preparation compiles `packages/client/src/realtime.ts` to `realtime.mjs`.
|
|
100
|
+
- no new runtime dependency is required.
|
|
101
|
+
|
|
102
|
+
## Validation
|
|
103
|
+
|
|
104
|
+
The release adds unit/package coverage for:
|
|
105
|
+
|
|
106
|
+
- cross-hub broker broadcasts,
|
|
107
|
+
- channel membership,
|
|
108
|
+
- presence metadata,
|
|
109
|
+
- authentication identity,
|
|
110
|
+
- channel authorization,
|
|
111
|
+
- socket join/event/ping protocol,
|
|
112
|
+
- stale heartbeat cleanup,
|
|
113
|
+
- SSE streaming,
|
|
114
|
+
- server-only boundary enforcement,
|
|
115
|
+
- compiled package runtime execution,
|
|
116
|
+
- public-entrypoint manifest parity.
|
|
117
|
+
|
|
118
|
+
## Compatibility
|
|
119
|
+
|
|
120
|
+
Previous baseline: `0.2.12`.
|
|
121
|
+
|
|
122
|
+
There are no intentional breaking changes from `0.2.12`.
|