room-kit 1.0.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/.github/workflows/publish.yml +16 -0
- package/LICENSE +21 -0
- package/README.md +262 -0
- package/changelog.md +13 -0
- package/dist/client.d.ts +16 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +317 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/room.d.ts +24 -0
- package/dist/room.d.ts.map +1 -0
- package/dist/room.js +26 -0
- package/dist/room.js.map +1 -0
- package/dist/server.d.ts +27 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +770 -0
- package/dist/server.js.map +1 -0
- package/dist/types.d.ts +413 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +21 -0
- package/dist/types.js.map +1 -0
- package/example/README.md +43 -0
- package/example/common.ts +41 -0
- package/example/package.json +17 -0
- package/example/public/app.ts +298 -0
- package/example/public/index.html +86 -0
- package/example/public/styles.css +317 -0
- package/example/server.ts +200 -0
- package/jsr.json +13 -0
- package/package.json +42 -0
- package/src/client.ts +433 -0
- package/src/index.ts +34 -0
- package/src/room.ts +32 -0
- package/src/server.ts +1064 -0
- package/src/types.ts +503 -0
- package/test/room.spec.ts +2401 -0
- package/tsconfig.json +20 -0
- package/tsconfig.test.json +9 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Controls which presence operations are available for a room type.
|
|
3
|
+
*
|
|
4
|
+
* - `"none"`: no presence APIs
|
|
5
|
+
* - `"count"`: count only
|
|
6
|
+
* - `"list"`: count + paginated member listings
|
|
7
|
+
*/
|
|
8
|
+
export type PresencePolicy = "none" | "count" | "list";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Transport-level connection state exposed by `RoomClient.connection`.
|
|
12
|
+
*/
|
|
13
|
+
export type ClientConnectionState = "connecting" | "connected" | "reconnecting" | "disconnected";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Marks an error message as safe to send to clients.
|
|
17
|
+
*
|
|
18
|
+
* Any non-`ClientSafeError` thrown by handlers is sanitized to a generic
|
|
19
|
+
* internal error message by the server runtime.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { ClientSafeError } from "room-kit";
|
|
24
|
+
*
|
|
25
|
+
* throw new ClientSafeError("Invalid room key");
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export class ClientSafeError extends Error {
|
|
29
|
+
constructor(message: string) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = "ClientSafeError";
|
|
32
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Pagination options for presence list queries.
|
|
38
|
+
*/
|
|
39
|
+
export type PresenceListQuery = {
|
|
40
|
+
offset?: number;
|
|
41
|
+
limit?: number;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Type-only schema used by `defineRoomType`.
|
|
46
|
+
*
|
|
47
|
+
* `joinRequest.roomId` and `roomProfile.roomId` are required for room
|
|
48
|
+
* separation and runtime consistency checks.
|
|
49
|
+
*/
|
|
50
|
+
export type RoomSchema = {
|
|
51
|
+
joinRequest: {
|
|
52
|
+
roomId: string;
|
|
53
|
+
[key: string]: unknown;
|
|
54
|
+
};
|
|
55
|
+
memberProfile?: object;
|
|
56
|
+
roomProfile: {
|
|
57
|
+
roomId: string;
|
|
58
|
+
[key: string]: unknown;
|
|
59
|
+
};
|
|
60
|
+
serverState?: object;
|
|
61
|
+
events?: Record<string, unknown>;
|
|
62
|
+
rpc?: Record<string, (...args: any[]) => any>;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Runtime room type definition returned by `defineRoomType`.
|
|
67
|
+
*/
|
|
68
|
+
export type RoomDefinition<TSchema extends RoomSchema, TPresence extends PresencePolicy = PresencePolicy> = {
|
|
69
|
+
readonly kind: "room";
|
|
70
|
+
readonly name: string;
|
|
71
|
+
readonly presence: TPresence;
|
|
72
|
+
readonly __schema?: TSchema;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Extracts the `joinRequest` payload type for a room definition.
|
|
77
|
+
*/
|
|
78
|
+
export type JoinRequest<TRoom extends RoomDefinition<any>> =
|
|
79
|
+
TRoom extends RoomDefinition<infer TSchema>
|
|
80
|
+
? TSchema extends { joinRequest: infer TJoin }
|
|
81
|
+
? TJoin
|
|
82
|
+
: Record<string, never>
|
|
83
|
+
: never;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Extracts the `memberProfile` type for a room definition.
|
|
87
|
+
*/
|
|
88
|
+
export type MemberProfileFor<TRoom extends RoomDefinition<any>> =
|
|
89
|
+
TRoom extends RoomDefinition<infer TSchema>
|
|
90
|
+
? TSchema extends { memberProfile: infer TMember }
|
|
91
|
+
? TMember
|
|
92
|
+
: Record<string, never>
|
|
93
|
+
: never;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Extracts the `roomProfile` type for a room definition.
|
|
97
|
+
*/
|
|
98
|
+
export type RoomProfileFor<TRoom extends RoomDefinition<any>> =
|
|
99
|
+
TRoom extends RoomDefinition<infer TSchema>
|
|
100
|
+
? TSchema extends { roomProfile: infer TProps }
|
|
101
|
+
? TProps
|
|
102
|
+
: Record<string, never>
|
|
103
|
+
: never;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Extracts the room event payload map.
|
|
107
|
+
*/
|
|
108
|
+
export type RoomEvents<TRoom extends RoomDefinition<any>> =
|
|
109
|
+
TRoom extends RoomDefinition<infer TSchema>
|
|
110
|
+
? TSchema extends { events: infer TEvents }
|
|
111
|
+
? TEvents extends Record<string, unknown>
|
|
112
|
+
? TEvents
|
|
113
|
+
: Record<string, never>
|
|
114
|
+
: Record<string, never>
|
|
115
|
+
: never;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Extracts the room private server-state type.
|
|
119
|
+
*/
|
|
120
|
+
export type ServerStateFor<TRoom extends RoomDefinition<any>> =
|
|
121
|
+
TRoom extends RoomDefinition<infer TSchema>
|
|
122
|
+
? TSchema extends { serverState: infer TState }
|
|
123
|
+
? TState
|
|
124
|
+
: Record<string, never>
|
|
125
|
+
: never;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Extracts the room RPC handler contract.
|
|
129
|
+
*/
|
|
130
|
+
export type RoomRpc<TRoom extends RoomDefinition<any>> =
|
|
131
|
+
TRoom extends RoomDefinition<infer TSchema>
|
|
132
|
+
? TSchema extends { rpc: infer TRpc }
|
|
133
|
+
? TRpc extends Record<string, (...args: any[]) => any>
|
|
134
|
+
? TRpc
|
|
135
|
+
: Record<string, never>
|
|
136
|
+
: Record<string, never>
|
|
137
|
+
: never;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Presence shape available to a joined client or server context.
|
|
141
|
+
*
|
|
142
|
+
* Derived from the room definition's runtime `presence` mode.
|
|
143
|
+
*/
|
|
144
|
+
export type PresenceFor<TRoom extends RoomDefinition<any>> =
|
|
145
|
+
TRoom extends RoomDefinition<any, infer TPresence>
|
|
146
|
+
? TPresence extends "list"
|
|
147
|
+
? { count: number; members: Array<VisibleMemberFor<TRoom>> }
|
|
148
|
+
: TPresence extends "count"
|
|
149
|
+
? { count: number }
|
|
150
|
+
: never
|
|
151
|
+
: never;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Presence value including the `"none"` case (`undefined`).
|
|
155
|
+
*/
|
|
156
|
+
export type PresenceValueFor<TRoom extends RoomDefinition<any>> =
|
|
157
|
+
[PresenceFor<TRoom>] extends [never]
|
|
158
|
+
? undefined
|
|
159
|
+
: PresenceFor<TRoom>;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Paginated presence page for `"list"` presence mode.
|
|
163
|
+
*/
|
|
164
|
+
export type PresencePageFor<TRoom extends RoomDefinition<any>> =
|
|
165
|
+
PresenceFor<TRoom> extends { members: Array<VisibleMemberFor<TRoom>> }
|
|
166
|
+
? {
|
|
167
|
+
count: number;
|
|
168
|
+
offset: number;
|
|
169
|
+
limit: number;
|
|
170
|
+
members: Array<VisibleMemberFor<TRoom>>;
|
|
171
|
+
}
|
|
172
|
+
: never;
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Public member entry used in list presence responses.
|
|
176
|
+
*/
|
|
177
|
+
export type VisibleMemberFor<TRoom extends RoomDefinition<any>> = {
|
|
178
|
+
memberId: string;
|
|
179
|
+
memberProfile: MemberProfileFor<TRoom>;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Event metadata supplied to event listeners.
|
|
184
|
+
*
|
|
185
|
+
* Includes source information so consumers can distinguish server-originated
|
|
186
|
+
* events from member-originated events.
|
|
187
|
+
*/
|
|
188
|
+
export type EventMetaFor<TRoom extends RoomDefinition<any>> =
|
|
189
|
+
| {
|
|
190
|
+
roomId: string;
|
|
191
|
+
sentAt: Date;
|
|
192
|
+
source: {
|
|
193
|
+
kind: "server";
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
| {
|
|
197
|
+
roomId: string;
|
|
198
|
+
sentAt: Date;
|
|
199
|
+
source:
|
|
200
|
+
PresenceFor<TRoom> extends { members: any[] }
|
|
201
|
+
? {
|
|
202
|
+
kind: "member";
|
|
203
|
+
memberId: string;
|
|
204
|
+
memberProfile: MemberProfileFor<TRoom>;
|
|
205
|
+
}
|
|
206
|
+
: {
|
|
207
|
+
kind: "member";
|
|
208
|
+
memberId: string;
|
|
209
|
+
member?: never;
|
|
210
|
+
};
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Listener signature for room events.
|
|
215
|
+
*/
|
|
216
|
+
export type EventListener<TRoom extends RoomDefinition<any>, TName extends keyof RoomEvents<TRoom>> = (
|
|
217
|
+
payload: RoomEvents<TRoom>[TName],
|
|
218
|
+
meta: EventMetaFor<TRoom>,
|
|
219
|
+
) => void;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Server-side snapshot entry for a connected socket membership.
|
|
223
|
+
*/
|
|
224
|
+
export type RoomMemberSnapshot<TRoom extends RoomDefinition<any>> = {
|
|
225
|
+
readonly socketId: string;
|
|
226
|
+
readonly memberId: string;
|
|
227
|
+
readonly memberProfile: MemberProfileFor<TRoom>;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Introspection snapshot for a room instance.
|
|
232
|
+
*/
|
|
233
|
+
export type RoomSnapshot<TRoom extends RoomDefinition<any>> = {
|
|
234
|
+
readonly roomId: string;
|
|
235
|
+
readonly roomProfile: RoomProfileFor<TRoom>;
|
|
236
|
+
readonly serverState: ServerStateFor<TRoom>;
|
|
237
|
+
readonly presence: PresenceValueFor<TRoom>;
|
|
238
|
+
readonly memberCount: number;
|
|
239
|
+
readonly members: Array<RoomMemberSnapshot<TRoom>>;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Server broadcast API for emitting events beyond the current room scope.
|
|
244
|
+
*/
|
|
245
|
+
export type RoomServerBroadcastApi<TRoom extends RoomDefinition<any>> = {
|
|
246
|
+
readonly emit: EventEmitApi<TRoom>;
|
|
247
|
+
toRoom(roomId: string): {
|
|
248
|
+
readonly emit: EventEmitApi<TRoom>;
|
|
249
|
+
};
|
|
250
|
+
toMembers(memberIds: readonly string[]): {
|
|
251
|
+
readonly emit: EventEmitApi<TRoom>;
|
|
252
|
+
};
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Joined room instance returned by `RoomClient.join`.
|
|
257
|
+
*
|
|
258
|
+
* @example
|
|
259
|
+
* ```ts
|
|
260
|
+
* const joined = await client.join({ roomId: "a", roomKey: "k", userName: "Ada" });
|
|
261
|
+
* await joined.rpc.sendMessage({ text: "hello" });
|
|
262
|
+
* joined.on.message((payload) => console.log(payload.text));
|
|
263
|
+
* await joined.leave();
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
export type JoinedRoom<TRoom extends RoomDefinition<any>> = {
|
|
267
|
+
readonly name: string;
|
|
268
|
+
readonly roomId: string;
|
|
269
|
+
readonly memberId: string;
|
|
270
|
+
readonly roomProfile: RoomProfileFor<TRoom>;
|
|
271
|
+
readonly rpc: RpcClientApi<TRoom>;
|
|
272
|
+
readonly emit: EventEmitApi<TRoom>;
|
|
273
|
+
readonly on: EventListenApi<TRoom>;
|
|
274
|
+
leave(): Promise<void>;
|
|
275
|
+
} & PresenceClientApi<TRoom>;
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Handle returned by `serveRoomType`.
|
|
279
|
+
*
|
|
280
|
+
* The handle is callable and unregisters listeners for the bound socket.
|
|
281
|
+
* It also exposes read-only introspection helpers for tests and diagnostics.
|
|
282
|
+
*/
|
|
283
|
+
export type RoomServerHandle<TRoom extends RoomDefinition<any>> = (() => void) & {
|
|
284
|
+
rooms(): Array<RoomSnapshot<TRoom>>;
|
|
285
|
+
room(roomId: string): RoomSnapshot<TRoom> | undefined;
|
|
286
|
+
members(roomId: string, query?: PresenceListQuery): PresencePageFor<TRoom> | undefined;
|
|
287
|
+
count(roomId: string): number;
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Client adapter bound to a specific room type.
|
|
292
|
+
*
|
|
293
|
+
* @example
|
|
294
|
+
* ```ts
|
|
295
|
+
* const client = createRoomClient(socket, chatRoomType);
|
|
296
|
+
* const joined = await client.join({ roomId: "a", roomKey: "k", userName: "Ada" });
|
|
297
|
+
* ```
|
|
298
|
+
*/
|
|
299
|
+
export type RoomClient<TRoom extends RoomDefinition<any>> = {
|
|
300
|
+
readonly name: string;
|
|
301
|
+
/**
|
|
302
|
+
* Transport-state monitor for the underlying socket.
|
|
303
|
+
*/
|
|
304
|
+
readonly connection: {
|
|
305
|
+
/**
|
|
306
|
+
* Latest known transport connection state.
|
|
307
|
+
*/
|
|
308
|
+
readonly current: ClientConnectionState;
|
|
309
|
+
/**
|
|
310
|
+
* Subscribes to transport-state changes.
|
|
311
|
+
*/
|
|
312
|
+
onChange(handler: (state: ClientConnectionState) => void): () => void;
|
|
313
|
+
};
|
|
314
|
+
join(payload: JoinRequest<TRoom>): Promise<JoinedRoom<TRoom>>;
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Conditionally adds presence APIs to a joined room.
|
|
319
|
+
*/
|
|
320
|
+
export type PresenceClientApi<TRoom extends RoomDefinition<any>> =
|
|
321
|
+
PresenceFor<TRoom> extends never
|
|
322
|
+
? Record<string, never>
|
|
323
|
+
: {
|
|
324
|
+
readonly presence: {
|
|
325
|
+
readonly current: PresenceFor<TRoom>;
|
|
326
|
+
onChange(handler: (presence: PresenceFor<TRoom>) => void): () => void;
|
|
327
|
+
count(): Promise<number>;
|
|
328
|
+
} & (PresenceFor<TRoom> extends { members: Array<VisibleMemberFor<TRoom>> }
|
|
329
|
+
? {
|
|
330
|
+
list(query?: PresenceListQuery): Promise<PresencePageFor<TRoom>>;
|
|
331
|
+
}
|
|
332
|
+
: Record<string, never>);
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* RPC call surface generated from `RoomRpc`.
|
|
337
|
+
*/
|
|
338
|
+
export type RpcClientApi<TRoom extends RoomDefinition<any>> = {
|
|
339
|
+
[K in keyof RoomRpc<TRoom>]:
|
|
340
|
+
RoomRpc<TRoom>[K] extends (...args: infer TArgs) => infer TResult
|
|
341
|
+
? (...args: TArgs) => Promise<Awaited<TResult>>
|
|
342
|
+
: never;
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Event emit surface generated from `RoomEvents`.
|
|
347
|
+
*/
|
|
348
|
+
export type EventEmitApi<TRoom extends RoomDefinition<any>> = {
|
|
349
|
+
[K in keyof RoomEvents<TRoom>]:
|
|
350
|
+
(payload: RoomEvents<TRoom>[K]) => Promise<void>;
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Event subscription surface generated from `RoomEvents`.
|
|
355
|
+
*/
|
|
356
|
+
export type EventListenApi<TRoom extends RoomDefinition<any>> = {
|
|
357
|
+
[K in keyof RoomEvents<TRoom>]:
|
|
358
|
+
(handler: EventListener<TRoom, K>) => () => void;
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Successful admission payload returned by `handlers.admit`.
|
|
363
|
+
*/
|
|
364
|
+
export type ServerAdmission<TRoom extends RoomDefinition<any>> = {
|
|
365
|
+
roomId: string;
|
|
366
|
+
memberId: string;
|
|
367
|
+
memberProfile: MemberProfileFor<TRoom>;
|
|
368
|
+
roomProfile: RoomProfileFor<TRoom>;
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Optional decision result returned by `revalidateAuth`.
|
|
373
|
+
*
|
|
374
|
+
* - `{ kind: "ok" }`: keep existing auth
|
|
375
|
+
* - `{ kind: "ok", auth }`: replace cached auth with `auth`
|
|
376
|
+
* - `{ kind: "reject" }`: reject request with a safe error
|
|
377
|
+
*/
|
|
378
|
+
export type AuthRevalidationDecision<TAuth> =
|
|
379
|
+
| {
|
|
380
|
+
kind: "ok";
|
|
381
|
+
auth?: TAuth;
|
|
382
|
+
}
|
|
383
|
+
| {
|
|
384
|
+
kind: "reject";
|
|
385
|
+
message?: string;
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
type IsUnknown<T> = unknown extends T
|
|
389
|
+
? ([T] extends [unknown] ? true : false)
|
|
390
|
+
: false;
|
|
391
|
+
|
|
392
|
+
type RoomServerHandlersCommon<TRoom extends RoomDefinition<any>, TAuth> = {
|
|
393
|
+
initState?(join: JoinRequest<TRoom>): Promise<ServerStateFor<TRoom>> | ServerStateFor<TRoom>;
|
|
394
|
+
/**
|
|
395
|
+
* Called once when server listeners are attached for a socket.
|
|
396
|
+
*
|
|
397
|
+
* Useful for transport-level telemetry and side effects that should happen
|
|
398
|
+
* before any room join.
|
|
399
|
+
*/
|
|
400
|
+
onConnect?(socket: ServerSocketLike, auth: TAuth): Promise<void> | void;
|
|
401
|
+
/**
|
|
402
|
+
* Called before each authenticated room operation (`admit`, `events`, `rpc`,
|
|
403
|
+
* presence queries, and disconnect cleanup).
|
|
404
|
+
*
|
|
405
|
+
* Return `{ kind: "ok", auth }` to rotate auth context, or
|
|
406
|
+
* `{ kind: "reject", message }` to reject the operation.
|
|
407
|
+
*/
|
|
408
|
+
revalidateAuth?(socket: ServerSocketLike, auth: TAuth): Promise<AuthRevalidationDecision<TAuth> | void> | AuthRevalidationDecision<TAuth> | void;
|
|
409
|
+
admit(join: JoinRequest<TRoom>, ctx: RoomServerContext<TRoom, TAuth>): Promise<ServerAdmission<TRoom>> | ServerAdmission<TRoom>;
|
|
410
|
+
/**
|
|
411
|
+
* Called once when a server socket disconnects.
|
|
412
|
+
*
|
|
413
|
+
* Runs before per-room `onLeave` hooks are emitted for disconnect cleanup.
|
|
414
|
+
*/
|
|
415
|
+
onDisconnect?(socket: ServerSocketLike, auth: TAuth): Promise<void> | void;
|
|
416
|
+
onJoin?(member: MemberProfileFor<TRoom>, ctx: RoomServerContext<TRoom, TAuth>): Promise<void> | void;
|
|
417
|
+
onLeave?(member: MemberProfileFor<TRoom>, ctx: RoomServerContext<TRoom, TAuth>): Promise<void> | void;
|
|
418
|
+
/**
|
|
419
|
+
* Optional per-request presence override for presence queries.
|
|
420
|
+
*
|
|
421
|
+
* The returned policy is clamped by the room type's configured presence mode,
|
|
422
|
+
* so it cannot escalate visibility above the room default.
|
|
423
|
+
*/
|
|
424
|
+
presencePolicy?(ctx: RoomServerContext<TRoom, TAuth>): Promise<PresencePolicy> | PresencePolicy;
|
|
425
|
+
events?: Partial<{
|
|
426
|
+
[K in keyof RoomEvents<TRoom>]:
|
|
427
|
+
(payload: RoomEvents<TRoom>[K], ctx: RoomServerContext<TRoom, TAuth>) => Promise<void> | void;
|
|
428
|
+
}>;
|
|
429
|
+
rpc?: Partial<{
|
|
430
|
+
[K in keyof RoomRpc<TRoom>]:
|
|
431
|
+
RoomRpc<TRoom>[K] extends (...args: infer TArgs) => infer TResult
|
|
432
|
+
? (...args: [...TArgs, RoomServerContext<TRoom, TAuth>]) => Promise<Awaited<TResult>> | Awaited<TResult>
|
|
433
|
+
: never;
|
|
434
|
+
}>;
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Server handler contract for a room type.
|
|
439
|
+
*
|
|
440
|
+
* `onAuth` is required when `TAuth` is explicitly typed to a non-`unknown`
|
|
441
|
+
* shape, and optional otherwise.
|
|
442
|
+
*/
|
|
443
|
+
export type RoomServerHandlers<TRoom extends RoomDefinition<any>, TAuth = unknown> =
|
|
444
|
+
IsUnknown<TAuth> extends true
|
|
445
|
+
? RoomServerHandlersCommon<TRoom, TAuth> & {
|
|
446
|
+
onAuth?(socket: ServerSocketLike): Promise<TAuth> | TAuth;
|
|
447
|
+
}
|
|
448
|
+
: RoomServerHandlersCommon<TRoom, TAuth> & {
|
|
449
|
+
onAuth(socket: ServerSocketLike): Promise<TAuth> | TAuth;
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Context provided to server handlers (`admit`, `rpc`, `events`, join/leave).
|
|
454
|
+
*/
|
|
455
|
+
export type RoomServerContext<TRoom extends RoomDefinition<any>, TAuth = unknown> = {
|
|
456
|
+
readonly name: string;
|
|
457
|
+
readonly roomId: string;
|
|
458
|
+
readonly auth: TAuth;
|
|
459
|
+
readonly memberId: string;
|
|
460
|
+
readonly memberProfile: MemberProfileFor<TRoom>;
|
|
461
|
+
readonly roomProfile: RoomProfileFor<TRoom>;
|
|
462
|
+
readonly serverState: ServerStateFor<TRoom>;
|
|
463
|
+
readonly emit: EventEmitApi<TRoom>;
|
|
464
|
+
readonly broadcast: RoomServerBroadcastApi<TRoom>;
|
|
465
|
+
getPresence(): PresenceValueFor<TRoom>;
|
|
466
|
+
getPresenceCount(): number;
|
|
467
|
+
listPresenceMembers(query?: PresenceListQuery): PresencePageFor<TRoom>;
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Minimal client socket contract required by the runtime.
|
|
472
|
+
*/
|
|
473
|
+
export type ClientSocketLike = {
|
|
474
|
+
emit(eventName: string, ...args: any[]): void;
|
|
475
|
+
on(eventName: string, handler: (...args: any[]) => void): void;
|
|
476
|
+
off(eventName: string, handler: (...args: any[]) => void): void;
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Minimal namespace contract required for targeted emits.
|
|
481
|
+
*/
|
|
482
|
+
export type ServerNamespaceLike = {
|
|
483
|
+
to(roomOrSocketId: string): {
|
|
484
|
+
emit(eventName: string, payload: unknown): void;
|
|
485
|
+
};
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Optional adapter for custom socket-id fanout behavior.
|
|
490
|
+
*/
|
|
491
|
+
export type RoomServerAdapter = {
|
|
492
|
+
emitToSocketIds(socketIds: readonly string[], eventName: string, payload: unknown): void;
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Minimal server socket contract required by `serveRoomType`.
|
|
497
|
+
*/
|
|
498
|
+
export type ServerSocketLike = ClientSocketLike & {
|
|
499
|
+
readonly id: string;
|
|
500
|
+
readonly nsp: ServerNamespaceLike;
|
|
501
|
+
join(room: string): Promise<void> | void;
|
|
502
|
+
leave(room: string): Promise<void> | void;
|
|
503
|
+
};
|