@pluv/client 0.26.0 → 0.27.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 +125 -0
- package/dist/index.d.mts +109 -32
- package/dist/index.d.ts +109 -32
- package/dist/index.js +153 -26
- package/dist/index.mjs +153 -26
- package/package.json +7 -7
- package/src/AbstractRoom.ts +6 -4
- package/src/MockedRoom.ts +115 -27
- package/src/PluvClient.ts +71 -20
- package/src/PluvProcedure.ts +81 -0
- package/src/PluvRoom.ts +82 -30
- package/src/PluvRouter.ts +70 -0
- package/src/createClient.ts +10 -4
- package/src/enums.ts +7 -0
- package/src/index.ts +5 -9
- package/src/types.ts +43 -7
- package/src/utils/identity.ts +1 -0
- package/src/utils/index.ts +1 -0
package/src/PluvClient.ts
CHANGED
|
@@ -1,45 +1,92 @@
|
|
|
1
|
-
import type { CrdtType } from "@pluv/crdt";
|
|
2
|
-
import type { IOLike, JsonObject } from "@pluv/types";
|
|
3
|
-
import
|
|
1
|
+
import type { AbstractCrdtDocFactory, CrdtType } from "@pluv/crdt";
|
|
2
|
+
import type { InputZodLike, IOLike, JsonObject } from "@pluv/types";
|
|
3
|
+
import { PluvProcedure } from "./PluvProcedure";
|
|
4
|
+
import type { AuthEndpoint, PluvRoomAddon, PluvRoomDebug, RoomConfig, RoomEndpoints, WsEndpoint } from "./PluvRoom";
|
|
4
5
|
import { PluvRoom } from "./PluvRoom";
|
|
6
|
+
import type { PluvRouterEventConfig } from "./PluvRouter";
|
|
7
|
+
import { PluvRouter } from "./PluvRouter";
|
|
8
|
+
import type { identity } from "./utils";
|
|
5
9
|
|
|
6
|
-
export type
|
|
10
|
+
export type InferCallback<TIO extends IOLike> = (i: typeof identity) => {
|
|
11
|
+
io: (io: TIO) => TIO;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type PluvClientOptions<
|
|
15
|
+
TIO extends IOLike,
|
|
16
|
+
TPresence extends JsonObject,
|
|
17
|
+
TStorage extends Record<string, CrdtType<any, any>>,
|
|
18
|
+
TMetadata extends JsonObject,
|
|
19
|
+
> = RoomEndpoints<TIO, TMetadata> & {
|
|
7
20
|
debug?: boolean;
|
|
21
|
+
infer: InferCallback<TIO>;
|
|
22
|
+
initialStorage?: AbstractCrdtDocFactory<TStorage>;
|
|
23
|
+
metadata?: InputZodLike<TMetadata>;
|
|
24
|
+
presence?: InputZodLike<TPresence>;
|
|
8
25
|
};
|
|
9
26
|
|
|
10
|
-
export
|
|
27
|
+
export type CreateRoomOptions<
|
|
28
|
+
TIO extends IOLike,
|
|
29
|
+
TPresence extends JsonObject,
|
|
30
|
+
TStorage extends Record<string, CrdtType<any, any>>,
|
|
31
|
+
TMetadata extends JsonObject,
|
|
32
|
+
TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {},
|
|
33
|
+
> = {
|
|
34
|
+
addons?: readonly PluvRoomAddon<TIO, TMetadata, TPresence, TStorage>[];
|
|
35
|
+
debug?: boolean | PluvRoomDebug<TIO>;
|
|
36
|
+
initialPresence?: TPresence;
|
|
37
|
+
initialStorage?: AbstractCrdtDocFactory<TStorage>;
|
|
38
|
+
onAuthorizationFail?: (error: Error) => void;
|
|
39
|
+
router?: PluvRouter<TIO, TPresence, TStorage, TEvents>;
|
|
40
|
+
} & (keyof TMetadata extends never ? { metadata?: undefined } : { metadata: TMetadata });
|
|
41
|
+
|
|
42
|
+
export class PluvClient<
|
|
43
|
+
TIO extends IOLike = IOLike,
|
|
44
|
+
TPresence extends JsonObject = {},
|
|
45
|
+
TStorage extends Record<string, CrdtType<any, any>> = {},
|
|
46
|
+
TMetadata extends JsonObject = {},
|
|
47
|
+
> {
|
|
11
48
|
private _authEndpoint: AuthEndpoint<TMetadata> | undefined;
|
|
12
49
|
private _debug: boolean;
|
|
50
|
+
private _initialStorage?: AbstractCrdtDocFactory<TStorage>;
|
|
51
|
+
private _presence?: InputZodLike<TPresence>;
|
|
13
52
|
private _wsEndpoint: WsEndpoint<TMetadata> | undefined;
|
|
14
53
|
|
|
15
|
-
private _rooms = new Map<string, PluvRoom<TIO, TMetadata,
|
|
54
|
+
private _rooms = new Map<string, PluvRoom<TIO, TMetadata, TPresence, TStorage, any>>();
|
|
55
|
+
|
|
56
|
+
public get procedure(): PluvProcedure<TIO, {}, {}, TPresence, TStorage> {
|
|
57
|
+
return new PluvProcedure<TIO, {}, {}, TPresence, TStorage>();
|
|
58
|
+
}
|
|
16
59
|
|
|
17
|
-
constructor(options: PluvClientOptions<TIO, TMetadata>) {
|
|
18
|
-
const { authEndpoint, debug = false, wsEndpoint } = options;
|
|
60
|
+
constructor(options: PluvClientOptions<TIO, TPresence, TStorage, TMetadata>) {
|
|
61
|
+
const { authEndpoint, debug = false, initialStorage, presence, wsEndpoint } = options;
|
|
19
62
|
|
|
20
63
|
this._authEndpoint = authEndpoint as AuthEndpoint<TMetadata>;
|
|
21
64
|
this._debug = debug;
|
|
65
|
+
this._initialStorage = initialStorage;
|
|
66
|
+
this._presence = presence;
|
|
22
67
|
this._wsEndpoint = wsEndpoint;
|
|
23
68
|
}
|
|
24
69
|
|
|
25
|
-
public createRoom = <
|
|
70
|
+
public createRoom = <TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}>(
|
|
26
71
|
room: string,
|
|
27
|
-
options:
|
|
72
|
+
options: CreateRoomOptions<TIO, TPresence, TStorage, TMetadata, TEvents>,
|
|
28
73
|
): PluvRoom<TIO, TMetadata, TPresence, TStorage> => {
|
|
29
|
-
const oldRoom = this.getRoom
|
|
74
|
+
const oldRoom = this.getRoom(room);
|
|
30
75
|
|
|
31
76
|
if (oldRoom) return oldRoom;
|
|
32
77
|
|
|
33
|
-
const newRoom = new PluvRoom<TIO, TMetadata, TPresence, TStorage>(room, {
|
|
78
|
+
const newRoom = new PluvRoom<TIO, TMetadata, TPresence, TStorage, TEvents>(room, {
|
|
34
79
|
addons: options.addons,
|
|
35
80
|
authEndpoint: this._authEndpoint,
|
|
36
81
|
debug: options.debug,
|
|
37
82
|
initialPresence: options.initialPresence,
|
|
38
|
-
initialStorage:
|
|
83
|
+
initialStorage: this._initialStorage,
|
|
39
84
|
metadata: options.metadata,
|
|
40
85
|
onAuthorizationFail: options.onAuthorizationFail,
|
|
86
|
+
presence: this._presence,
|
|
87
|
+
router: options.router,
|
|
41
88
|
wsEndpoint: this._wsEndpoint,
|
|
42
|
-
} as RoomConfig<TIO, TMetadata, TPresence, TStorage>);
|
|
89
|
+
} as RoomConfig<TIO, TMetadata, TPresence, TStorage, TEvents>);
|
|
43
90
|
|
|
44
91
|
this._rooms.set(room, newRoom);
|
|
45
92
|
|
|
@@ -50,7 +97,7 @@ export class PluvClient<TIO extends IOLike = IOLike, TMetadata extends JsonObjec
|
|
|
50
97
|
|
|
51
98
|
public enter = async (
|
|
52
99
|
room: string | PluvRoom<TIO, any, any, any>,
|
|
53
|
-
): Promise<PluvRoom<TIO, TMetadata,
|
|
100
|
+
): Promise<PluvRoom<TIO, TMetadata, TPresence, TStorage, any>> => {
|
|
54
101
|
const toEnter = typeof room === "string" ? this.getRoom(room) : room;
|
|
55
102
|
|
|
56
103
|
if (!toEnter) throw new Error(`Could not find room: ${room}.`);
|
|
@@ -64,15 +111,13 @@ export class PluvClient<TIO extends IOLike = IOLike, TMetadata extends JsonObjec
|
|
|
64
111
|
return toEnter;
|
|
65
112
|
};
|
|
66
113
|
|
|
67
|
-
public getRoom =
|
|
68
|
-
room
|
|
69
|
-
): PluvRoom<TIO, TMetadata, TPresence, TStorage> | null => {
|
|
70
|
-
const found = this._rooms.get(room) as PluvRoom<TIO, TMetadata, TPresence, TStorage> | undefined;
|
|
114
|
+
public getRoom = (room: string): PluvRoom<TIO, TMetadata, TPresence, TStorage, any> | null => {
|
|
115
|
+
const found = this._rooms.get(room) as PluvRoom<TIO, TMetadata, TPresence, TStorage, any> | undefined;
|
|
71
116
|
|
|
72
117
|
return found ?? null;
|
|
73
118
|
};
|
|
74
119
|
|
|
75
|
-
public getRooms = (): readonly PluvRoom<TIO, TMetadata,
|
|
120
|
+
public getRooms = (): readonly PluvRoom<TIO, TMetadata, TPresence, TStorage, any>[] => {
|
|
76
121
|
return Array.from(this._rooms.values());
|
|
77
122
|
};
|
|
78
123
|
|
|
@@ -88,6 +133,12 @@ export class PluvClient<TIO extends IOLike = IOLike, TMetadata extends JsonObjec
|
|
|
88
133
|
this._logDebug(`Left and deleted room: ${room}`);
|
|
89
134
|
};
|
|
90
135
|
|
|
136
|
+
public router<TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}>(
|
|
137
|
+
events: TEvents,
|
|
138
|
+
): PluvRouter<TIO, TPresence, TStorage, TEvents> {
|
|
139
|
+
return new PluvRouter<TIO, TPresence, TStorage, TEvents>(events);
|
|
140
|
+
}
|
|
141
|
+
|
|
91
142
|
private _logDebug(...data: any[]): void {
|
|
92
143
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
93
144
|
if (process?.env?.NODE_ENV === "production") return;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { CrdtType } from "@pluv/crdt";
|
|
2
|
+
import type { EventRecord, IOLike, InputZodLike, JsonObject, ProcedureLike } from "@pluv/types";
|
|
3
|
+
import type { EventResolver, MergeEventRecords } from "./types";
|
|
4
|
+
|
|
5
|
+
export interface PluvProcedureConfig<
|
|
6
|
+
TIO extends IOLike = IOLike,
|
|
7
|
+
TInput extends JsonObject = {},
|
|
8
|
+
TOutput extends EventRecord<string, any> = {},
|
|
9
|
+
TPresence extends JsonObject = {},
|
|
10
|
+
TStorage extends Record<string, CrdtType<any, any>> = {},
|
|
11
|
+
> {
|
|
12
|
+
broadcast?: EventResolver<TIO, TInput, TOutput, TPresence, TStorage> | null;
|
|
13
|
+
input?: InputZodLike<TInput>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class PluvProcedure<
|
|
17
|
+
TIO extends IOLike = IOLike,
|
|
18
|
+
TInput extends JsonObject = {},
|
|
19
|
+
TOutput extends EventRecord<string, any> = {},
|
|
20
|
+
TPresence extends JsonObject = {},
|
|
21
|
+
TStorage extends Record<string, CrdtType<any, any>> = {},
|
|
22
|
+
TFilled extends "input" | "broadcast" | "" = "",
|
|
23
|
+
> {
|
|
24
|
+
private _broadcast: EventResolver<TIO, TInput, Partial<TOutput>, TPresence, TStorage> | null = null;
|
|
25
|
+
private _input: InputZodLike<TInput> | null = null;
|
|
26
|
+
|
|
27
|
+
public get config(): ProcedureLike<TInput, TOutput>["config"] {
|
|
28
|
+
return {
|
|
29
|
+
broadcast: this._broadcast?.bind(this) ?? null,
|
|
30
|
+
input: this._input ?? null,
|
|
31
|
+
resolver: this._resolver(),
|
|
32
|
+
} as ProcedureLike<TInput, TOutput>["config"];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
constructor(config: PluvProcedureConfig<TIO, TInput, TOutput, TPresence, TStorage> = {}) {
|
|
36
|
+
const { broadcast, input } = config;
|
|
37
|
+
|
|
38
|
+
this._broadcast = broadcast ?? null;
|
|
39
|
+
this._input = input ?? null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public broadcast<TResult extends EventRecord<string, any> = {}>(
|
|
43
|
+
resolver: EventResolver<TIO, TInput, TResult, TPresence, TStorage>,
|
|
44
|
+
): Omit<
|
|
45
|
+
PluvProcedure<
|
|
46
|
+
TIO,
|
|
47
|
+
TInput,
|
|
48
|
+
MergeEventRecords<[TOutput, TResult]>,
|
|
49
|
+
TPresence,
|
|
50
|
+
TStorage,
|
|
51
|
+
TFilled | "input" | "broadcast"
|
|
52
|
+
>,
|
|
53
|
+
TFilled | "input" | "broadcast"
|
|
54
|
+
> {
|
|
55
|
+
const { broadcast } = this.config;
|
|
56
|
+
|
|
57
|
+
if (!!broadcast) throw new Error("Broadcast was already defined for this procedure");
|
|
58
|
+
|
|
59
|
+
return new PluvProcedure<
|
|
60
|
+
TIO,
|
|
61
|
+
TInput,
|
|
62
|
+
MergeEventRecords<[TOutput, TResult]>,
|
|
63
|
+
TPresence,
|
|
64
|
+
TStorage,
|
|
65
|
+
TFilled | "input" | "broadcast"
|
|
66
|
+
>({
|
|
67
|
+
...(this.config as any),
|
|
68
|
+
broadcast: resolver as any,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public input<TData extends JsonObject>(
|
|
73
|
+
input: InputZodLike<TData>,
|
|
74
|
+
): Omit<PluvProcedure<TIO, TData, {}, TPresence, TStorage, TFilled | "input">, TFilled | "input"> {
|
|
75
|
+
return new PluvProcedure<TIO, TData, {}, TPresence, TStorage, TFilled | "input">({ input });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private _resolver(): EventResolver<TIO, TInput, TOutput, TPresence, TStorage> {
|
|
79
|
+
return (data, context) => this._broadcast?.(data, context) as TOutput;
|
|
80
|
+
}
|
|
81
|
+
}
|
package/src/PluvRoom.ts
CHANGED
|
@@ -21,20 +21,25 @@ import type { EventNotifierSubscriptionCallback } from "./EventNotifier";
|
|
|
21
21
|
import { EventNotifier } from "./EventNotifier";
|
|
22
22
|
import type { OtherNotifierSubscriptionCallback } from "./OtherNotifier";
|
|
23
23
|
import { OtherNotifier } from "./OtherNotifier";
|
|
24
|
+
import { PluvProcedure } from "./PluvProcedure";
|
|
25
|
+
import type { MergeEvents, PluvRouterEventConfig } from "./PluvRouter";
|
|
26
|
+
import { PluvRouter } from "./PluvRouter";
|
|
24
27
|
import type { StateNotifierSubjects, SubscriptionCallback } from "./StateNotifier";
|
|
25
28
|
import { StateNotifier } from "./StateNotifier";
|
|
26
29
|
import { StorageStore } from "./StorageStore";
|
|
27
30
|
import type { UsersManagerConfig } from "./UsersManager";
|
|
28
31
|
import { UsersManager } from "./UsersManager";
|
|
32
|
+
import { ConnectionState } from "./enums";
|
|
29
33
|
import type {
|
|
30
34
|
AuthorizationState,
|
|
35
|
+
EventResolver,
|
|
36
|
+
EventResolverContext,
|
|
31
37
|
InternalSubscriptions,
|
|
32
38
|
UpdateMyPresenceAction,
|
|
33
39
|
UserInfo,
|
|
34
40
|
WebSocketConnection,
|
|
35
41
|
WebSocketState,
|
|
36
42
|
} from "./types";
|
|
37
|
-
import { ConnectionState } from "./types";
|
|
38
43
|
import { debounce } from "./utils";
|
|
39
44
|
|
|
40
45
|
const ADD_TO_STORAGE_STATE_DEBOUNCE_MS = 1_000;
|
|
@@ -131,44 +136,44 @@ export type PluvRoomDebug<TIO extends IOLike> = Id<{
|
|
|
131
136
|
input: readonly (keyof InferIOInput<TIO>)[];
|
|
132
137
|
}>;
|
|
133
138
|
|
|
134
|
-
export type PluvRoomOptions<
|
|
135
|
-
TIO extends IOLike,
|
|
136
|
-
TMetadata extends JsonObject = {},
|
|
137
|
-
TPresence extends JsonObject = {},
|
|
138
|
-
TStorage extends Record<string, CrdtType<any, any>> = {},
|
|
139
|
-
> = {
|
|
140
|
-
addons?: readonly PluvRoomAddon<TIO, TMetadata, TPresence, TStorage>[];
|
|
141
|
-
debug?: boolean | PluvRoomDebug<TIO>;
|
|
142
|
-
onAuthorizationFail?: (error: Error) => void;
|
|
143
|
-
} & Omit<CrdtManagerOptions<TStorage>, "encodedState"> &
|
|
144
|
-
UsersManagerConfig<TPresence> &
|
|
145
|
-
(keyof TMetadata extends never ? { metadata?: undefined } : { metadata: TMetadata });
|
|
146
|
-
|
|
147
139
|
export type RoomConfig<
|
|
148
140
|
TIO extends IOLike,
|
|
149
141
|
TMetadata extends JsonObject = {},
|
|
150
142
|
TPresence extends JsonObject = {},
|
|
151
143
|
TStorage extends Record<string, CrdtType<any, any>> = {},
|
|
152
|
-
|
|
144
|
+
TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {},
|
|
145
|
+
> = Id<
|
|
146
|
+
{
|
|
147
|
+
addons?: readonly PluvRoomAddon<TIO, TMetadata, TPresence, TStorage>[];
|
|
148
|
+
debug?: boolean | PluvRoomDebug<TIO>;
|
|
149
|
+
onAuthorizationFail?: (error: Error) => void;
|
|
150
|
+
router?: PluvRouter<TIO, TPresence, TStorage, TEvents>;
|
|
151
|
+
} & RoomEndpoints<TIO, TMetadata> &
|
|
152
|
+
Pick<CrdtManagerOptions<TStorage>, "initialStorage"> &
|
|
153
|
+
UsersManagerConfig<TPresence> &
|
|
154
|
+
(keyof TMetadata extends never ? { metadata?: undefined } : { metadata: TMetadata })
|
|
155
|
+
>;
|
|
153
156
|
|
|
154
157
|
export class PluvRoom<
|
|
155
158
|
TIO extends IOLike,
|
|
156
159
|
TMetadata extends JsonObject = {},
|
|
157
160
|
TPresence extends JsonObject = {},
|
|
158
161
|
TStorage extends Record<string, CrdtType<any, any>> = {},
|
|
162
|
+
TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {},
|
|
159
163
|
> extends AbstractRoom<TIO, TPresence, TStorage> {
|
|
160
164
|
readonly _endpoints: RoomEndpoints<TIO, TMetadata>;
|
|
161
165
|
|
|
162
166
|
private _crdtManager: CrdtManager<TStorage>;
|
|
163
167
|
private _crdtNotifier = new CrdtNotifier<TStorage>();
|
|
164
168
|
private _debug: boolean | PluvRoomDebug<TIO>;
|
|
165
|
-
private _eventNotifier = new EventNotifier<TIO
|
|
169
|
+
private _eventNotifier = new EventNotifier<MergeEvents<TEvents, TIO>>();
|
|
166
170
|
private _intervals: IntervalIds = {
|
|
167
171
|
heartbeat: null,
|
|
168
172
|
};
|
|
169
173
|
private _listeners: InternalListeners;
|
|
170
174
|
private _metadata: TMetadata = {} as TMetadata;
|
|
171
175
|
private _otherNotifier = new OtherNotifier<TIO>();
|
|
176
|
+
private _router: PluvRouter<TIO, TPresence, TStorage, TEvents>;
|
|
172
177
|
private _state: WebSocketState<TIO> = {
|
|
173
178
|
authorization: {
|
|
174
179
|
token: null,
|
|
@@ -194,7 +199,7 @@ export class PluvRoom<
|
|
|
194
199
|
private _wsListeners: WebSocketListeners | null = null;
|
|
195
200
|
private _usersManager: UsersManager<TIO, TPresence>;
|
|
196
201
|
|
|
197
|
-
constructor(room: string, options: RoomConfig<TIO, TMetadata, TPresence, TStorage>) {
|
|
202
|
+
constructor(room: string, options: RoomConfig<TIO, TMetadata, TPresence, TStorage, TEvents>) {
|
|
198
203
|
const {
|
|
199
204
|
addons = [],
|
|
200
205
|
authEndpoint,
|
|
@@ -204,6 +209,7 @@ export class PluvRoom<
|
|
|
204
209
|
metadata,
|
|
205
210
|
onAuthorizationFail,
|
|
206
211
|
presence,
|
|
212
|
+
router,
|
|
207
213
|
wsEndpoint,
|
|
208
214
|
} = options;
|
|
209
215
|
|
|
@@ -231,6 +237,7 @@ export class PluvRoom<
|
|
|
231
237
|
},
|
|
232
238
|
};
|
|
233
239
|
|
|
240
|
+
this._router = router ?? (new PluvRouter({}) as PluvRouter<TIO, TPresence, TStorage, TEvents>);
|
|
234
241
|
this._usersManager = new UsersManager<TIO, TPresence>({ initialPresence, presence });
|
|
235
242
|
this._crdtManager = new CrdtManager<TStorage>({ initialStorage });
|
|
236
243
|
}
|
|
@@ -244,23 +251,62 @@ export class PluvRoom<
|
|
|
244
251
|
}
|
|
245
252
|
|
|
246
253
|
public broadcast = new Proxy(
|
|
247
|
-
<TEvent extends keyof InferIOInput<
|
|
254
|
+
async <TEvent extends keyof InferIOInput<MergeEvents<TEvents, TIO>>>(
|
|
255
|
+
event: TEvent,
|
|
256
|
+
data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[TEvent]>,
|
|
257
|
+
): Promise<void> => {
|
|
248
258
|
if (!this._state.webSocket) return;
|
|
249
259
|
if (this._state.connection.state !== ConnectionState.Open) return;
|
|
250
260
|
|
|
251
261
|
const type = event.toString();
|
|
252
262
|
|
|
253
|
-
this.
|
|
263
|
+
const procedure = this._router._defs.events[type] as PluvProcedure<
|
|
264
|
+
TIO,
|
|
265
|
+
any,
|
|
266
|
+
any,
|
|
267
|
+
TPresence,
|
|
268
|
+
TStorage
|
|
269
|
+
> | null;
|
|
270
|
+
|
|
271
|
+
if (!procedure?.config.broadcast) {
|
|
272
|
+
this._sendMessage({ data, type });
|
|
273
|
+
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const myself = this._usersManager.myself;
|
|
278
|
+
|
|
279
|
+
if (!myself) return;
|
|
280
|
+
|
|
281
|
+
const parsed = procedure.config.input ? procedure.config.input.parse(data) : data;
|
|
282
|
+
const context: EventResolverContext<TIO, TPresence, TStorage> = {
|
|
283
|
+
doc: this._crdtManager.doc,
|
|
284
|
+
others: this._usersManager.getOthers(),
|
|
285
|
+
room: this.id,
|
|
286
|
+
user: myself,
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const output = await (procedure.config.broadcast as EventResolver<TIO, any, any, TPresence, TStorage>)(
|
|
290
|
+
parsed,
|
|
291
|
+
context,
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
Object.entries(output).forEach(([_type, _data]) => {
|
|
295
|
+
this._sendMessage({ data: _data, type: _type });
|
|
296
|
+
});
|
|
254
297
|
},
|
|
255
298
|
{
|
|
256
299
|
get(fn, prop) {
|
|
257
|
-
return (data: Id<InferIOInput<TIO
|
|
258
|
-
return fn(prop, data);
|
|
300
|
+
return async (data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[any]>): Promise<void> => {
|
|
301
|
+
return await fn(prop, data);
|
|
259
302
|
};
|
|
260
303
|
},
|
|
261
304
|
},
|
|
262
|
-
) as (<TEvent extends keyof InferIOInput<
|
|
263
|
-
|
|
305
|
+
) as (<TEvent extends keyof InferIOInput<MergeEvents<TEvents, TIO>>>(
|
|
306
|
+
event: TEvent,
|
|
307
|
+
data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[TEvent]>,
|
|
308
|
+
) => Promise<void>) & {
|
|
309
|
+
[event in keyof InferIOInput<TIO>]: (data: Id<InferIOInput<TIO>[event]>) => Promise<void>;
|
|
264
310
|
};
|
|
265
311
|
|
|
266
312
|
public canRedo = (): boolean => {
|
|
@@ -348,20 +394,23 @@ export class PluvRoom<
|
|
|
348
394
|
}
|
|
349
395
|
|
|
350
396
|
public event = new Proxy(
|
|
351
|
-
<TEvent extends keyof InferIOOutput<TIO
|
|
397
|
+
<TEvent extends keyof InferIOOutput<MergeEvents<TEvents, TIO>>>(
|
|
352
398
|
event: TEvent,
|
|
353
|
-
callback: EventNotifierSubscriptionCallback<
|
|
399
|
+
callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, any>,
|
|
354
400
|
): (() => void) => this._eventNotifier.subscribe(event, callback),
|
|
355
401
|
{
|
|
356
402
|
get(fn, prop) {
|
|
357
|
-
return (callback: EventNotifierSubscriptionCallback<
|
|
403
|
+
return (callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, any>): (() => void) =>
|
|
404
|
+
fn(prop as any, callback);
|
|
358
405
|
},
|
|
359
406
|
},
|
|
360
|
-
) as (<TEvent extends keyof InferIOOutput<TIO
|
|
407
|
+
) as (<TEvent extends keyof InferIOOutput<MergeEvents<TEvents, TIO>>>(
|
|
361
408
|
event: TEvent,
|
|
362
|
-
callback: EventNotifierSubscriptionCallback<
|
|
409
|
+
callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, TEvent>,
|
|
363
410
|
) => () => void) & {
|
|
364
|
-
[event in keyof InferIOOutput<TIO
|
|
411
|
+
[event in keyof InferIOOutput<MergeEvents<TEvents, TIO>>]: (
|
|
412
|
+
callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, any>,
|
|
413
|
+
) => () => void;
|
|
365
414
|
};
|
|
366
415
|
|
|
367
416
|
public getConnection = (): WebSocketConnection => {
|
|
@@ -475,7 +524,10 @@ export class PluvRoom<
|
|
|
475
524
|
this._stateNotifier.subjects["my-presence"].next(myPresence);
|
|
476
525
|
!!myself && this._stateNotifier.subjects["myself"].next(myself);
|
|
477
526
|
|
|
478
|
-
this.broadcast(
|
|
527
|
+
this.broadcast(
|
|
528
|
+
"$UPDATE_PRESENCE" as keyof InferIOInput<MergeEvents<TEvents, TIO>>,
|
|
529
|
+
{ presence: newPresence } as any,
|
|
530
|
+
);
|
|
479
531
|
};
|
|
480
532
|
|
|
481
533
|
private async _addToStorageStore(update: string): Promise<void> {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { CrdtType } from "@pluv/crdt";
|
|
2
|
+
import type { Id, IOLike, IORouterLike, JsonObject, ProcedureLike } from "@pluv/types";
|
|
3
|
+
import type { PluvProcedure } from "./PluvProcedure";
|
|
4
|
+
|
|
5
|
+
export type PluvRouterEventConfig<
|
|
6
|
+
TIO extends IOLike = IOLike,
|
|
7
|
+
TPresence extends JsonObject = {},
|
|
8
|
+
TStorage extends Record<string, CrdtType<any, any>> = {},
|
|
9
|
+
> = { [P: string]: Pick<PluvProcedure<TIO, any, any, TPresence, TStorage>, "config"> };
|
|
10
|
+
|
|
11
|
+
export type MergedRouter<
|
|
12
|
+
TRouters extends PluvRouter<TIO, TPresence, TStorage, any>[] = [],
|
|
13
|
+
TIO extends IOLike = IOLike,
|
|
14
|
+
TPresence extends JsonObject = {},
|
|
15
|
+
TStorage extends Record<string, CrdtType<any, any>> = {},
|
|
16
|
+
TRoot extends TRouters[0]["_defs"]["events"] = {},
|
|
17
|
+
> = TRouters extends [
|
|
18
|
+
infer IHead extends PluvRouter<TIO, TPresence, TStorage, any>,
|
|
19
|
+
...infer ITail extends PluvRouter<TIO, TPresence, TStorage, any>[],
|
|
20
|
+
]
|
|
21
|
+
? MergedRouter<ITail, TIO, TPresence, TStorage, TRoot & IHead["_defs"]["events"]>
|
|
22
|
+
: PluvRouter<TIO, TPresence, TStorage, TRoot>;
|
|
23
|
+
|
|
24
|
+
export type MergeEvents<
|
|
25
|
+
TClientEvents extends PluvRouterEventConfig<any, any, any>,
|
|
26
|
+
TServerIO extends IOLike<any, any>,
|
|
27
|
+
> =
|
|
28
|
+
TServerIO extends IOLike<infer IAuthorize, infer IServerEvents>
|
|
29
|
+
? IOLike<
|
|
30
|
+
IAuthorize,
|
|
31
|
+
{
|
|
32
|
+
[P in keyof TClientEvents]: TClientEvents[P] extends ProcedureLike<
|
|
33
|
+
infer IClientInput,
|
|
34
|
+
infer IClientOutput
|
|
35
|
+
>
|
|
36
|
+
? {
|
|
37
|
+
[K in keyof IClientOutput]: K extends keyof IServerEvents
|
|
38
|
+
? IServerEvents[K]
|
|
39
|
+
: ProcedureLike<IClientInput, Id<Pick<IClientOutput, K>>>;
|
|
40
|
+
}[keyof IClientOutput]
|
|
41
|
+
: never;
|
|
42
|
+
} & IServerEvents
|
|
43
|
+
>
|
|
44
|
+
: never;
|
|
45
|
+
|
|
46
|
+
export class PluvRouter<
|
|
47
|
+
TIO extends IOLike = IOLike,
|
|
48
|
+
TPresence extends JsonObject = {},
|
|
49
|
+
TStorage extends Record<string, CrdtType<any, any>> = {},
|
|
50
|
+
TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {},
|
|
51
|
+
> implements IORouterLike<TEvents>
|
|
52
|
+
{
|
|
53
|
+
readonly _defs: { events: TEvents } = { events: {} as TEvents };
|
|
54
|
+
|
|
55
|
+
constructor(events: TEvents) {
|
|
56
|
+
this._defs = { events };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public static merge<TRouters extends PluvRouter<any, any, any, any>[]>(
|
|
60
|
+
...routers: TRouters
|
|
61
|
+
): MergedRouter<TRouters> {
|
|
62
|
+
const events = Object.assign(Object.create(null), ...routers.map((router) => router._defs.events));
|
|
63
|
+
|
|
64
|
+
if (Object.keys(events).some((key) => key.startsWith("$"))) {
|
|
65
|
+
throw new Error('Procedures may not start with "$"');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return new PluvRouter<any, any, any, any>(events) as MergedRouter<TRouters>;
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/createClient.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
import type { CrdtType } from "@pluv/crdt";
|
|
1
2
|
import type { IOLike, JsonObject } from "@pluv/types";
|
|
2
3
|
import type { PluvClientOptions } from "./PluvClient";
|
|
3
4
|
import { PluvClient } from "./PluvClient";
|
|
4
5
|
|
|
5
|
-
export const createClient = <
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
export const createClient = <
|
|
7
|
+
TIO extends IOLike = IOLike,
|
|
8
|
+
TPresence extends JsonObject = {},
|
|
9
|
+
TStorage extends Record<string, CrdtType<any, any>> = {},
|
|
10
|
+
TMetadata extends JsonObject = {},
|
|
11
|
+
>(
|
|
12
|
+
options: PluvClientOptions<TIO, TPresence, TStorage, TMetadata>,
|
|
13
|
+
): PluvClient<TIO, TPresence, TStorage, TMetadata> => {
|
|
14
|
+
return new PluvClient<TIO, TPresence, TStorage, TMetadata>(options);
|
|
9
15
|
};
|
package/src/enums.ts
ADDED
package/src/index.ts
CHANGED
|
@@ -3,17 +3,13 @@ export type { BaseUser, EventMessage, EventRecord, IOEventMessage } from "@pluv/
|
|
|
3
3
|
export { AbstractRoom } from "./AbstractRoom";
|
|
4
4
|
export { AbstractStorageStore } from "./AbstractStorageStore";
|
|
5
5
|
export { createClient } from "./createClient";
|
|
6
|
+
export { ConnectionState } from "./enums";
|
|
6
7
|
export { MockedRoom } from "./MockedRoom";
|
|
7
8
|
export type { MockedRoomConfig, MockedRoomEvents } from "./MockedRoom";
|
|
8
9
|
export { PluvClient } from "./PluvClient";
|
|
9
|
-
export type { PluvClientOptions } from "./PluvClient";
|
|
10
|
+
export type { CreateRoomOptions, PluvClientOptions } from "./PluvClient";
|
|
11
|
+
export type { PluvProcedureConfig } from "./PluvProcedure";
|
|
10
12
|
export { PluvRoom } from "./PluvRoom";
|
|
11
|
-
export type {
|
|
12
|
-
|
|
13
|
-
PluvRoomAddonInput,
|
|
14
|
-
PluvRoomAddonResult,
|
|
15
|
-
PluvRoomDebug,
|
|
16
|
-
PluvRoomOptions,
|
|
17
|
-
} from "./PluvRoom";
|
|
18
|
-
export { ConnectionState } from "./types";
|
|
13
|
+
export type { PluvRoomAddon, PluvRoomAddonInput, PluvRoomAddonResult, PluvRoomDebug } from "./PluvRoom";
|
|
14
|
+
export type { MergeEvents, PluvRouter, PluvRouterEventConfig } from "./PluvRouter";
|
|
19
15
|
export type { UserInfo, WebSocketConnection, WebSocketState } from "./types";
|
package/src/types.ts
CHANGED
|
@@ -1,22 +1,58 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AbstractCrdtDoc, CrdtType } from "@pluv/crdt";
|
|
2
|
+
import type {
|
|
3
|
+
EventRecord,
|
|
4
|
+
Id,
|
|
5
|
+
InferIOAuthorize,
|
|
6
|
+
InferIOAuthorizeUser,
|
|
7
|
+
IOLike,
|
|
8
|
+
JsonObject,
|
|
9
|
+
MaybePromise,
|
|
10
|
+
} from "@pluv/types";
|
|
11
|
+
import type { ConnectionState } from "./enums";
|
|
2
12
|
|
|
3
13
|
export interface AuthorizationState<TIO extends IOLike> {
|
|
4
14
|
token: string | null;
|
|
5
15
|
user: InferIOAuthorizeUser<InferIOAuthorize<TIO>> | null;
|
|
6
16
|
}
|
|
7
17
|
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
export type EventResolver<
|
|
19
|
+
TIO extends IOLike = IOLike,
|
|
20
|
+
TInput extends JsonObject = {},
|
|
21
|
+
TOutput extends EventRecord<string, any> = {},
|
|
22
|
+
TPresence extends JsonObject = {},
|
|
23
|
+
TStorage extends Record<string, CrdtType<any, any>> = {},
|
|
24
|
+
> = (data: TInput, context: EventResolverContext<TIO, TPresence, TStorage>) => MaybePromise<TOutput | void>;
|
|
25
|
+
|
|
26
|
+
export interface EventResolverContext<
|
|
27
|
+
TIO extends IOLike = IOLike,
|
|
28
|
+
TPresence extends JsonObject = {},
|
|
29
|
+
TStorage extends Record<string, CrdtType<any, any>> = {},
|
|
30
|
+
> {
|
|
31
|
+
doc: AbstractCrdtDoc<TStorage>;
|
|
32
|
+
others: readonly UserInfo<TIO, TPresence>[];
|
|
33
|
+
room: string;
|
|
34
|
+
user: UserInfo<TIO, TPresence>;
|
|
14
35
|
}
|
|
15
36
|
|
|
16
37
|
export interface InternalSubscriptions {
|
|
17
38
|
observeCrdt: (() => void) | null;
|
|
18
39
|
}
|
|
19
40
|
|
|
41
|
+
export type MergeEventRecords<
|
|
42
|
+
TEventRecords extends EventRecord<string, any>[],
|
|
43
|
+
TRoot extends EventRecord<string, any> = {},
|
|
44
|
+
> = TEventRecords extends [
|
|
45
|
+
infer IHead extends EventRecord<string, any>,
|
|
46
|
+
...infer ITail extends EventRecord<string, any>[],
|
|
47
|
+
]
|
|
48
|
+
? MergeEventRecords<
|
|
49
|
+
ITail,
|
|
50
|
+
Omit<TRoot, keyof IHead> & {
|
|
51
|
+
[P in keyof IHead]: TRoot extends Record<P, any> ? TRoot[P] | IHead[P] : IHead[P];
|
|
52
|
+
}
|
|
53
|
+
>
|
|
54
|
+
: Id<TRoot>;
|
|
55
|
+
|
|
20
56
|
export type UpdateMyPresenceAction<TPresence extends JsonObject> =
|
|
21
57
|
| Partial<TPresence>
|
|
22
58
|
| ((oldPresence: TPresence | null) => Partial<TPresence>);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const identity = <T extends unknown>(x: T): T => x;
|
package/src/utils/index.ts
CHANGED