@pluv/io 0.17.3 → 0.18.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/.turbo/turbo-build.log +9 -9
- package/CHANGELOG.md +129 -0
- package/dist/index.d.mts +121 -57
- package/dist/index.d.ts +121 -57
- package/dist/index.js +300 -284
- package/dist/index.mjs +297 -284
- package/package.json +6 -6
- package/src/AbstractPersistance.ts +4 -16
- package/src/AbstractPlatform.ts +4 -14
- package/src/AbstractPubSub.ts +1 -4
- package/src/AbstractWebSocket.ts +6 -21
- package/src/IORoom.ts +117 -270
- package/src/Persistance.ts +3 -11
- package/src/PluvIO.ts +156 -473
- package/src/PluvProcedure.ts +203 -0
- package/src/PluvRouter.ts +46 -0
- package/src/PluvServer.ts +182 -0
- package/src/PubSub.ts +6 -18
- package/src/authorize.ts +5 -22
- package/src/createIO.ts +10 -49
- package/src/index.ts +9 -13
- package/src/types.ts +49 -101
- package/src/version.generated.ts +1 -1
package/src/PluvIO.ts
CHANGED
|
@@ -2,314 +2,182 @@ import type { AbstractCrdtDocFactory } from "@pluv/crdt";
|
|
|
2
2
|
import { noop } from "@pluv/crdt";
|
|
3
3
|
import type {
|
|
4
4
|
BaseIOAuthorize,
|
|
5
|
-
EventRecord,
|
|
6
5
|
IOAuthorize,
|
|
7
6
|
IOLike,
|
|
8
7
|
InferIOAuthorize,
|
|
9
8
|
InferIOAuthorizeUser,
|
|
10
|
-
InferIOInput,
|
|
11
|
-
InferIOOutputBroadcast,
|
|
12
|
-
InferIOOutputSelf,
|
|
13
|
-
InferIOOutputSync,
|
|
14
9
|
JsonObject,
|
|
15
10
|
Maybe,
|
|
16
|
-
MaybePromise,
|
|
17
|
-
Spread,
|
|
18
11
|
} from "@pluv/types";
|
|
19
|
-
import
|
|
20
|
-
import
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
} from "./AbstractPlatform";
|
|
25
|
-
import type { IORoomListenerEvent } from "./IORoom";
|
|
26
|
-
import { IORoom } from "./IORoom";
|
|
12
|
+
import type { AbstractPlatform, InferPlatformRoomContextType } from "./AbstractPlatform";
|
|
13
|
+
import { PluvProcedure } from "./PluvProcedure";
|
|
14
|
+
import type { MergedRouter, PluvRouterEventConfig } from "./PluvRouter";
|
|
15
|
+
import { PluvRouter } from "./PluvRouter";
|
|
16
|
+
import { PluvServer } from "./PluvServer";
|
|
27
17
|
import type { JWTEncodeParams } from "./authorize";
|
|
28
18
|
import { authorize } from "./authorize";
|
|
29
|
-
import type {
|
|
19
|
+
import type { GetInitialStorageFn, PluvIOListeners } from "./types";
|
|
30
20
|
import { __PLUV_VERSION } from "./version";
|
|
31
21
|
|
|
32
|
-
type InferIOContext<TIO extends PluvIO<any, any, any, any, any, any, any>> =
|
|
33
|
-
TIO extends PluvIO<any, any, infer IContext, any, any, any, any>
|
|
34
|
-
? IContext
|
|
35
|
-
: never;
|
|
36
|
-
|
|
37
|
-
type InferIOPlatform<TIO extends PluvIO<any, any, any, any, any, any, any>> =
|
|
38
|
-
TIO extends PluvIO<infer IPlatform, any, any, any, any, any, any>
|
|
39
|
-
? IPlatform
|
|
40
|
-
: never;
|
|
41
|
-
|
|
42
|
-
export type InferIORoom<TIO extends PluvIO<any, any, any, any, any, any, any>> =
|
|
43
|
-
TIO extends PluvIO<
|
|
44
|
-
infer IPlatform,
|
|
45
|
-
infer IAuthorize,
|
|
46
|
-
infer IContext,
|
|
47
|
-
infer IInput,
|
|
48
|
-
infer IOutputBroadcast,
|
|
49
|
-
infer IOutputSelf,
|
|
50
|
-
infer IOutputSync
|
|
51
|
-
>
|
|
52
|
-
? IORoom<
|
|
53
|
-
IPlatform,
|
|
54
|
-
IAuthorize,
|
|
55
|
-
IContext,
|
|
56
|
-
IInput,
|
|
57
|
-
IOutputBroadcast,
|
|
58
|
-
IOutputSelf,
|
|
59
|
-
IOutputSync
|
|
60
|
-
>
|
|
61
|
-
: never;
|
|
62
|
-
|
|
63
|
-
type GetInitialStorageEvent<TPlatform extends AbstractPlatform> = {
|
|
64
|
-
room: string;
|
|
65
|
-
} & InferPlatformRoomContextType<TPlatform>;
|
|
66
|
-
|
|
67
|
-
export type GetInitialStorageFn<TPlatform extends AbstractPlatform> = (
|
|
68
|
-
event: GetInitialStorageEvent<TPlatform>,
|
|
69
|
-
) => MaybePromise<Maybe<string>>;
|
|
70
|
-
|
|
71
|
-
export interface PluvIOListeners<TPlatform extends AbstractPlatform> {
|
|
72
|
-
onRoomDeleted: (event: IORoomListenerEvent<TPlatform>) => void;
|
|
73
|
-
onStorageUpdated: (event: IORoomListenerEvent<TPlatform>) => void;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
22
|
export type PluvIOConfig<
|
|
77
|
-
TPlatform extends AbstractPlatform<any
|
|
78
|
-
TAuthorize extends IOAuthorize<
|
|
79
|
-
|
|
80
|
-
any,
|
|
81
|
-
InferPlatformRoomContextType<TPlatform>
|
|
82
|
-
> | null = null,
|
|
83
|
-
TContext extends JsonObject = {},
|
|
84
|
-
TInput extends EventRecord<string, any> = {},
|
|
85
|
-
TOutputBroadcast extends EventRecord<string, any> = {},
|
|
86
|
-
TOutputSelf extends EventRecord<string, any> = {},
|
|
87
|
-
TOutputSync extends EventRecord<string, any> = {},
|
|
23
|
+
TPlatform extends AbstractPlatform<any>,
|
|
24
|
+
TAuthorize extends IOAuthorize<any, any, InferPlatformRoomContextType<TPlatform>>,
|
|
25
|
+
TContext extends JsonObject,
|
|
88
26
|
> = Partial<PluvIOListeners<TPlatform>> & {
|
|
89
27
|
authorize?: TAuthorize;
|
|
90
28
|
context?: TContext;
|
|
91
29
|
crdt?: { doc: (value: any) => AbstractCrdtDocFactory<any> };
|
|
92
30
|
debug?: boolean;
|
|
93
|
-
events?: InferEventConfig<
|
|
94
|
-
TPlatform,
|
|
95
|
-
TContext,
|
|
96
|
-
TInput,
|
|
97
|
-
TOutputBroadcast,
|
|
98
|
-
TOutputSelf,
|
|
99
|
-
TOutputSync
|
|
100
|
-
>;
|
|
101
31
|
getInitialStorage?: GetInitialStorageFn<TPlatform>;
|
|
102
32
|
platform: TPlatform;
|
|
103
33
|
};
|
|
104
34
|
|
|
105
|
-
export
|
|
106
|
-
|
|
107
|
-
|
|
35
|
+
export interface ServerConfig<
|
|
36
|
+
TPlatform extends AbstractPlatform<any> = AbstractPlatform<any>,
|
|
37
|
+
TAuthorize extends IOAuthorize<any, any, InferPlatformRoomContextType<TPlatform>> = BaseIOAuthorize,
|
|
38
|
+
TContext extends JsonObject = {},
|
|
39
|
+
TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext> = {},
|
|
40
|
+
> {
|
|
41
|
+
router?: PluvRouter<TPlatform, TAuthorize, TContext, TEvents>;
|
|
42
|
+
}
|
|
108
43
|
|
|
109
44
|
export class PluvIO<
|
|
110
45
|
TPlatform extends AbstractPlatform<any> = AbstractPlatform<any>,
|
|
111
|
-
TAuthorize extends IOAuthorize<
|
|
112
|
-
any,
|
|
113
|
-
any,
|
|
114
|
-
InferPlatformRoomContextType<TPlatform>
|
|
115
|
-
> = BaseIOAuthorize,
|
|
46
|
+
TAuthorize extends IOAuthorize<any, any, InferPlatformRoomContextType<TPlatform>> = BaseIOAuthorize,
|
|
116
47
|
TContext extends JsonObject = {},
|
|
117
|
-
|
|
118
|
-
TOutputBroadcast extends EventRecord<string, any> = {},
|
|
119
|
-
TOutputSelf extends EventRecord<string, any> = {},
|
|
120
|
-
TOutputSync extends EventRecord<string, any> = {},
|
|
121
|
-
> implements
|
|
122
|
-
IOLike<TAuthorize, TInput, TOutputBroadcast, TOutputSelf, TOutputSync>
|
|
48
|
+
> implements IOLike<TAuthorize, {}>
|
|
123
49
|
{
|
|
124
|
-
private
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
TOutputSync
|
|
134
|
-
>
|
|
135
|
-
>();
|
|
136
|
-
|
|
137
|
-
readonly _authorize: TAuthorize | null = null;
|
|
138
|
-
readonly _context: TContext = {} as TContext;
|
|
139
|
-
readonly _crdt: { doc: (value: any) => AbstractCrdtDocFactory<any> };
|
|
140
|
-
readonly _debug: boolean;
|
|
141
|
-
readonly _events: InferEventConfig<
|
|
142
|
-
TPlatform,
|
|
143
|
-
TContext,
|
|
144
|
-
TInput,
|
|
145
|
-
TOutputBroadcast,
|
|
146
|
-
TOutputSelf,
|
|
147
|
-
TOutputSync
|
|
148
|
-
> = {
|
|
149
|
-
$GET_OTHERS: {
|
|
150
|
-
resolver: {
|
|
151
|
-
sync: (data, { room, session, sessions }) => {
|
|
152
|
-
const others = Array.from(sessions.entries())
|
|
153
|
-
.filter(([, wsSession]) => wsSession.id !== session?.id)
|
|
154
|
-
.reduce<{
|
|
155
|
-
[connectionId: string]: {
|
|
156
|
-
connectionId: string;
|
|
157
|
-
room: string | null;
|
|
158
|
-
user: InferIOAuthorizeUser<TAuthorize>;
|
|
159
|
-
};
|
|
160
|
-
}>(
|
|
161
|
-
(acc, [connectionId, { presence, user }]) => ({
|
|
162
|
-
...acc,
|
|
163
|
-
[connectionId]: {
|
|
164
|
-
connectionId,
|
|
165
|
-
presence,
|
|
166
|
-
room,
|
|
167
|
-
user: user as InferIOAuthorizeUser<TAuthorize>,
|
|
168
|
-
},
|
|
169
|
-
}),
|
|
170
|
-
{},
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
return { $OTHERS_RECEIVED: { others } };
|
|
174
|
-
},
|
|
175
|
-
},
|
|
176
|
-
},
|
|
177
|
-
$INITIALIZE_SESSION: {
|
|
178
|
-
resolver: {
|
|
179
|
-
broadcast: ({ presence }, { session }) => {
|
|
180
|
-
if (!session) return {};
|
|
181
|
-
|
|
182
|
-
session.presence = presence;
|
|
183
|
-
|
|
184
|
-
return {
|
|
185
|
-
$USER_JOINED: {
|
|
186
|
-
connectionId: session.id,
|
|
187
|
-
user: session.user,
|
|
188
|
-
presence,
|
|
189
|
-
},
|
|
50
|
+
private _router: PluvRouter<TPlatform, TAuthorize, TContext, {}> = new PluvRouter({
|
|
51
|
+
$GET_OTHERS: this.procedure.sync((data, { room, session, sessions }) => {
|
|
52
|
+
const others = Array.from(sessions.entries())
|
|
53
|
+
.filter(([, wsSession]) => wsSession.id !== session?.id)
|
|
54
|
+
.reduce<{
|
|
55
|
+
[connectionId: string]: {
|
|
56
|
+
connectionId: string;
|
|
57
|
+
room: string | null;
|
|
58
|
+
user: JsonObject;
|
|
190
59
|
};
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
oldState
|
|
198
|
-
? null
|
|
199
|
-
: await this._getInitialStorage?.({
|
|
200
|
-
...context,
|
|
201
|
-
room,
|
|
202
|
-
}),
|
|
203
|
-
update,
|
|
204
|
-
];
|
|
205
|
-
|
|
206
|
-
if (updates.some((_update) => !!_update)) {
|
|
207
|
-
const encodedState = doc
|
|
208
|
-
.batchApplyEncodedState(updates)
|
|
209
|
-
.getEncodedState();
|
|
210
|
-
|
|
211
|
-
await this._platform.persistance.setStorageState(
|
|
60
|
+
}>(
|
|
61
|
+
(acc, [connectionId, { presence, user }]) => ({
|
|
62
|
+
...acc,
|
|
63
|
+
[connectionId]: {
|
|
64
|
+
connectionId,
|
|
65
|
+
presence,
|
|
212
66
|
room,
|
|
213
|
-
|
|
214
|
-
|
|
67
|
+
user,
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
{},
|
|
71
|
+
);
|
|
215
72
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
73
|
+
return { $OTHERS_RECEIVED: { others } };
|
|
74
|
+
}),
|
|
75
|
+
$INITIALIZE_SESSION: this.procedure
|
|
76
|
+
.broadcast((data, { session }) => {
|
|
77
|
+
const presence = (data as any)?.presence;
|
|
222
78
|
|
|
223
|
-
const state =
|
|
224
|
-
(await this._platform.persistance.getStorageState(
|
|
225
|
-
room,
|
|
226
|
-
)) ?? doc.getEncodedState();
|
|
227
|
-
|
|
228
|
-
return { $STORAGE_RECEIVED: { state } };
|
|
229
|
-
},
|
|
230
|
-
},
|
|
231
|
-
},
|
|
232
|
-
$PING: {
|
|
233
|
-
resolver: {
|
|
234
|
-
self: (data, { session }) => {
|
|
235
|
-
if (!session) return {};
|
|
236
|
-
|
|
237
|
-
const currentTime = new Date().getTime();
|
|
238
|
-
|
|
239
|
-
session.timers.ping = currentTime;
|
|
240
|
-
|
|
241
|
-
return { $PONG: {} };
|
|
242
|
-
},
|
|
243
|
-
},
|
|
244
|
-
},
|
|
245
|
-
$UPDATE_PRESENCE: {
|
|
246
|
-
resolver: ({ presence }, { session }) => {
|
|
247
79
|
if (!session) return {};
|
|
248
80
|
|
|
249
|
-
session.presence =
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
81
|
+
session.presence = presence;
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
$USER_JOINED: {
|
|
85
|
+
connectionId: session.id,
|
|
86
|
+
user: session.user,
|
|
87
|
+
presence,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
})
|
|
91
|
+
.self(async (data, { context, doc, room }) => {
|
|
92
|
+
const oldState = await this._platform.persistance.getStorageState(room);
|
|
93
|
+
const update = (data as any)?.update as Maybe<string>;
|
|
94
|
+
|
|
95
|
+
const updates: readonly Maybe<string>[] = [
|
|
96
|
+
oldState
|
|
97
|
+
? null
|
|
98
|
+
: await this._getInitialStorage?.({
|
|
99
|
+
...context,
|
|
100
|
+
room,
|
|
101
|
+
}),
|
|
102
|
+
update,
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
if (updates.some((_update) => !!_update)) {
|
|
106
|
+
const encodedState = doc.batchApplyEncodedState(updates).getEncodedState();
|
|
107
|
+
|
|
108
|
+
await this._platform.persistance.setStorageState(room, encodedState);
|
|
109
|
+
|
|
110
|
+
this._listeners.onStorageUpdated({ ...context, encodedState, room });
|
|
265
111
|
}
|
|
266
112
|
|
|
267
|
-
const
|
|
268
|
-
const encodedState = updated.getEncodedState();
|
|
113
|
+
const state = (await this._platform.persistance.getStorageState(room)) ?? doc.getEncodedState();
|
|
269
114
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
115
|
+
return { $STORAGE_RECEIVED: { state } };
|
|
116
|
+
}),
|
|
117
|
+
$PING: this.procedure.self((data, { session }) => {
|
|
118
|
+
if (!session) return {};
|
|
119
|
+
|
|
120
|
+
const currentTime = new Date().getTime();
|
|
121
|
+
|
|
122
|
+
session.timers.ping = currentTime;
|
|
123
|
+
|
|
124
|
+
return { $PONG: {} };
|
|
125
|
+
}),
|
|
126
|
+
$UPDATE_PRESENCE: this.procedure.broadcast((data, { session }) => {
|
|
127
|
+
const presence = (data as any)?.presence;
|
|
128
|
+
|
|
129
|
+
if (!session) return {};
|
|
130
|
+
|
|
131
|
+
session.presence = Object.assign(Object.create(null), session.presence, presence);
|
|
132
|
+
|
|
133
|
+
return { $PRESENCE_UPDATED: { presence } };
|
|
134
|
+
}),
|
|
135
|
+
$UPDATE_STORAGE: this.procedure.broadcast((data, { context, doc, room }) => {
|
|
136
|
+
const origin = (data as any)?.origin as Maybe<string>;
|
|
137
|
+
const update: string | null = (data as any)?.update ?? null;
|
|
138
|
+
|
|
139
|
+
if (origin === "$INITIALIZED" && Object.keys(doc.toJson()).length) {
|
|
140
|
+
return {};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const updated = update === null ? doc : doc.applyEncodedState({ update });
|
|
144
|
+
const encodedState = updated.getEncodedState();
|
|
145
|
+
|
|
146
|
+
this._platform.persistance.setStorageState(room, encodedState).then(() => {
|
|
147
|
+
this._listeners.onStorageUpdated({
|
|
148
|
+
...context,
|
|
149
|
+
encodedState,
|
|
150
|
+
room,
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return { $STORAGE_UPDATED: { state: encodedState } };
|
|
155
|
+
}),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
readonly _authorize: TAuthorize | null = null;
|
|
159
|
+
readonly _context: TContext = {} as TContext;
|
|
160
|
+
readonly _crdt: { doc: (value: any) => AbstractCrdtDocFactory<any> };
|
|
161
|
+
readonly _debug: boolean;
|
|
291
162
|
readonly _getInitialStorage: GetInitialStorageFn<TPlatform> | null = null;
|
|
292
163
|
readonly _listeners: PluvIOListeners<TPlatform>;
|
|
293
164
|
readonly _platform: TPlatform;
|
|
294
165
|
readonly _version: string = __PLUV_VERSION as any;
|
|
295
166
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
>,
|
|
306
|
-
) {
|
|
167
|
+
public get _events() {
|
|
168
|
+
return this._router._events;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
public get procedure(): PluvProcedure<TPlatform, TAuthorize, TContext, {}, {}> {
|
|
172
|
+
return new PluvProcedure();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
constructor(options: PluvIOConfig<TPlatform, TAuthorize, TContext>) {
|
|
307
176
|
const {
|
|
308
177
|
authorize,
|
|
309
178
|
context,
|
|
310
179
|
crdt = noop,
|
|
311
180
|
debug = false,
|
|
312
|
-
events,
|
|
313
181
|
getInitialStorage,
|
|
314
182
|
onRoomDeleted,
|
|
315
183
|
onStorageUpdated,
|
|
@@ -322,7 +190,6 @@ export class PluvIO<
|
|
|
322
190
|
|
|
323
191
|
if (authorize) this._authorize = authorize;
|
|
324
192
|
if (context) this._context = context;
|
|
325
|
-
if (events) this._events = events;
|
|
326
193
|
if (getInitialStorage) this._getInitialStorage = getInitialStorage;
|
|
327
194
|
|
|
328
195
|
this._listeners = {
|
|
@@ -332,21 +199,14 @@ export class PluvIO<
|
|
|
332
199
|
}
|
|
333
200
|
|
|
334
201
|
public async createToken(
|
|
335
|
-
params: JWTEncodeParams<
|
|
336
|
-
InferIOAuthorizeUser<InferIOAuthorize<this>>,
|
|
337
|
-
TPlatform
|
|
338
|
-
>,
|
|
202
|
+
params: JWTEncodeParams<InferIOAuthorizeUser<InferIOAuthorize<this>>, TPlatform>,
|
|
339
203
|
): Promise<string> {
|
|
340
204
|
if (!this._authorize) {
|
|
341
|
-
throw new Error(
|
|
342
|
-
"IO does not specify authorize during initialization.",
|
|
343
|
-
);
|
|
205
|
+
throw new Error("IO does not specify authorize during initialization.");
|
|
344
206
|
}
|
|
345
207
|
|
|
346
208
|
const ioAuthorize =
|
|
347
|
-
typeof this._authorize === "function"
|
|
348
|
-
? this._authorize(params)
|
|
349
|
-
: (this._authorize as { secret: string });
|
|
209
|
+
typeof this._authorize === "function" ? this._authorize(params) : (this._authorize as { secret: string });
|
|
350
210
|
|
|
351
211
|
const secret = ioAuthorize.secret;
|
|
352
212
|
|
|
@@ -356,213 +216,36 @@ export class PluvIO<
|
|
|
356
216
|
}).encode(params);
|
|
357
217
|
}
|
|
358
218
|
|
|
359
|
-
public
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
TResultSelf extends EventRecord<string, any> = {},
|
|
364
|
-
TResultSync extends EventRecord<string, any> = {},
|
|
365
|
-
>(
|
|
366
|
-
event: TEvent,
|
|
367
|
-
config: EventConfig<
|
|
368
|
-
TPlatform,
|
|
369
|
-
TContext,
|
|
370
|
-
TData,
|
|
371
|
-
TResultBroadcast,
|
|
372
|
-
TResultSelf,
|
|
373
|
-
TResultSync
|
|
374
|
-
>,
|
|
375
|
-
): PluvIO<
|
|
376
|
-
TPlatform,
|
|
377
|
-
TAuthorize,
|
|
378
|
-
TContext,
|
|
379
|
-
Spread<[TInput, EventRecord<TEvent, TData>]>,
|
|
380
|
-
Spread<[TOutputBroadcast, TResultBroadcast]>,
|
|
381
|
-
Spread<[TOutputSelf, TResultSelf]>,
|
|
382
|
-
Spread<[TOutputSync, TResultSync]>
|
|
383
|
-
> {
|
|
384
|
-
if (event.startsWith("$")) {
|
|
385
|
-
throw new Error('Event name must not start with "$".');
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
const newEvent = {
|
|
389
|
-
[event]: config,
|
|
390
|
-
} as InferEventConfig<
|
|
391
|
-
TPlatform,
|
|
392
|
-
TContext,
|
|
393
|
-
EventRecord<TEvent, TData>,
|
|
394
|
-
TResultBroadcast extends EventRecord<string, any>
|
|
395
|
-
? TResultBroadcast
|
|
396
|
-
: {},
|
|
397
|
-
TResultSelf extends EventRecord<string, any> ? TResultSelf : {},
|
|
398
|
-
TResultSync extends EventRecord<string, any> ? TResultSync : {}
|
|
399
|
-
>;
|
|
400
|
-
|
|
401
|
-
/**
|
|
402
|
-
* !HACK
|
|
403
|
-
* @description Typing this is becoming way too unmanagable, due to resolver
|
|
404
|
-
* being able to return void and needing to handle the conditional typing.
|
|
405
|
-
* Assume the types will work out, and cast as any here.
|
|
406
|
-
*
|
|
407
|
-
* TODO
|
|
408
|
-
* @description Revisit EventResolver and related types to resolve types
|
|
409
|
-
* without casting to any.
|
|
410
|
-
*
|
|
411
|
-
* @author leedavidcs
|
|
412
|
-
* @date September 25, 2022
|
|
413
|
-
*/
|
|
414
|
-
return PluvIO.merge(
|
|
415
|
-
this as any,
|
|
416
|
-
new PluvIO({
|
|
417
|
-
crdt: this._crdt,
|
|
418
|
-
events: newEvent as any,
|
|
419
|
-
platform: this._platform,
|
|
420
|
-
}),
|
|
421
|
-
) as any;
|
|
219
|
+
public mergeRouters<TRouters extends PluvRouter<TPlatform, TAuthorize, TContext, any>[]>(
|
|
220
|
+
...routers: TRouters
|
|
221
|
+
): MergedRouter<TRouters> {
|
|
222
|
+
return PluvRouter.merge(...routers);
|
|
422
223
|
}
|
|
423
224
|
|
|
424
|
-
public
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
TAuthorize,
|
|
430
|
-
TContext,
|
|
431
|
-
TInput,
|
|
432
|
-
TOutputBroadcast,
|
|
433
|
-
TOutputSelf,
|
|
434
|
-
TOutputSync
|
|
435
|
-
> {
|
|
436
|
-
const { debug, ...platformRoomContext } = options;
|
|
437
|
-
|
|
438
|
-
this._purgeEmptyRooms();
|
|
439
|
-
|
|
440
|
-
if (!/^[a-z0-9]+[a-z0-9\-_]+[a-z0-9]+$/i.test(room)) {
|
|
441
|
-
throw new Error("Unsupported room name");
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
const platform = this._platform;
|
|
445
|
-
const oldRoom = this._rooms.get(room);
|
|
446
|
-
|
|
447
|
-
if (oldRoom) return oldRoom;
|
|
448
|
-
|
|
449
|
-
const roomContext = {
|
|
450
|
-
...this._context,
|
|
451
|
-
...platformRoomContext,
|
|
452
|
-
} as TContext & InferPlatformRoomContextType<TPlatform>;
|
|
225
|
+
public router<TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>>(
|
|
226
|
+
events: TEvents,
|
|
227
|
+
): PluvRouter<TPlatform, TAuthorize, TContext, TEvents> {
|
|
228
|
+
return new PluvRouter<TPlatform, TAuthorize, TContext, TEvents>(events);
|
|
229
|
+
}
|
|
453
230
|
|
|
454
|
-
|
|
231
|
+
public server<TEvents extends PluvRouterEventConfig<TPlatform, TAuthorize, TContext>>(
|
|
232
|
+
config: ServerConfig<TPlatform, TAuthorize, TContext, TEvents> = {},
|
|
233
|
+
): PluvServer<TPlatform, TAuthorize, TContext, TEvents> {
|
|
234
|
+
const router = (config.router ? this.mergeRouters(this._router, config.router) : this._router) as PluvRouter<
|
|
455
235
|
TPlatform,
|
|
456
236
|
TAuthorize,
|
|
457
237
|
TContext,
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
238
|
+
TEvents
|
|
239
|
+
>;
|
|
240
|
+
|
|
241
|
+
return new PluvServer<TPlatform, TAuthorize, TContext, TEvents>({
|
|
242
|
+
...this._listeners,
|
|
463
243
|
authorize: this._authorize ?? undefined,
|
|
464
|
-
context:
|
|
244
|
+
context: this._context,
|
|
465
245
|
crdt: this._crdt,
|
|
466
|
-
debug:
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
this._logDebug(
|
|
470
|
-
`${colors.blue("Deleting empty room:")} ${room}`,
|
|
471
|
-
);
|
|
472
|
-
|
|
473
|
-
const roomContext = {
|
|
474
|
-
room,
|
|
475
|
-
encodedState,
|
|
476
|
-
...platformRoomContext,
|
|
477
|
-
} as IORoomListenerEvent<TPlatform> &
|
|
478
|
-
InferPlatformRoomContextType<TPlatform>;
|
|
479
|
-
|
|
480
|
-
this._rooms.delete(room);
|
|
481
|
-
this._listeners.onRoomDeleted(roomContext);
|
|
482
|
-
|
|
483
|
-
if (this._debug) {
|
|
484
|
-
const rooms = Array.from(this._rooms.keys());
|
|
485
|
-
|
|
486
|
-
this._logDebug(`${colors.blue("Deleted room:")} ${room}`);
|
|
487
|
-
this._logDebug(
|
|
488
|
-
`${colors.blue("Rooms available:")} ${rooms.join(
|
|
489
|
-
", ",
|
|
490
|
-
)}`,
|
|
491
|
-
);
|
|
492
|
-
}
|
|
493
|
-
},
|
|
494
|
-
platform,
|
|
495
|
-
});
|
|
496
|
-
|
|
497
|
-
this._rooms.set(room, newRoom);
|
|
498
|
-
|
|
499
|
-
if (this._debug) {
|
|
500
|
-
const rooms = Array.from(this._rooms.keys());
|
|
501
|
-
|
|
502
|
-
this._logDebug(`${colors.blue("Created room:")} ${room}`);
|
|
503
|
-
this._logDebug(
|
|
504
|
-
`${colors.blue("Rooms available:")} ${rooms.join(", ")}`,
|
|
505
|
-
);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
return newRoom;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
public static merge<TIO1 extends PluvIO, TIO2 extends PluvIO>(
|
|
512
|
-
first: TIO1,
|
|
513
|
-
second: TIO2,
|
|
514
|
-
): PluvIO<
|
|
515
|
-
InferIOPlatform<TIO1>,
|
|
516
|
-
InferIOAuthorize<TIO1>,
|
|
517
|
-
InferIOContext<TIO1>,
|
|
518
|
-
Spread<[InferIOInput<TIO1>, InferIOInput<TIO2>]>,
|
|
519
|
-
Spread<[InferIOOutputBroadcast<TIO1>, InferIOOutputBroadcast<TIO2>]>,
|
|
520
|
-
Spread<[InferIOOutputSelf<TIO1>, InferIOOutputSelf<TIO2>]>,
|
|
521
|
-
Spread<[InferIOOutputSync<TIO1>, InferIOOutputSync<TIO2>]>
|
|
522
|
-
> {
|
|
523
|
-
const authorize = (first._authorize ??
|
|
524
|
-
undefined) as InferIOAuthorize<TIO1>;
|
|
525
|
-
const context = first._context as InferIOContext<TIO1>;
|
|
526
|
-
const crdt = first._crdt;
|
|
527
|
-
const debug = first._debug;
|
|
528
|
-
const platform = first._platform as InferIOPlatform<TIO1>;
|
|
529
|
-
|
|
530
|
-
const getInitialStorage =
|
|
531
|
-
first._getInitialStorage as GetInitialStorageFn<
|
|
532
|
-
InferIOPlatform<TIO1>
|
|
533
|
-
>;
|
|
534
|
-
|
|
535
|
-
const events1 = first._events;
|
|
536
|
-
const events2 = second._events;
|
|
537
|
-
|
|
538
|
-
const { onRoomDeleted, onStorageUpdated } = first._listeners;
|
|
539
|
-
|
|
540
|
-
const events = Object.assign(Object.create(null), events1, events2);
|
|
541
|
-
|
|
542
|
-
return new PluvIO({
|
|
543
|
-
authorize,
|
|
544
|
-
context,
|
|
545
|
-
crdt,
|
|
546
|
-
debug,
|
|
547
|
-
events,
|
|
548
|
-
getInitialStorage,
|
|
549
|
-
onRoomDeleted,
|
|
550
|
-
onStorageUpdated,
|
|
551
|
-
platform,
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
private _logDebug(...data: any[]): void {
|
|
556
|
-
this._debug && console.log(...data);
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
private _purgeEmptyRooms(): void {
|
|
560
|
-
const rooms = Array.from(this._rooms.entries());
|
|
561
|
-
|
|
562
|
-
rooms.forEach(([id, room]) => {
|
|
563
|
-
if (room.getSize()) return;
|
|
564
|
-
|
|
565
|
-
this._rooms.delete(id);
|
|
246
|
+
debug: this._debug,
|
|
247
|
+
platform: this._platform,
|
|
248
|
+
router,
|
|
566
249
|
});
|
|
567
250
|
}
|
|
568
251
|
}
|