@pluv/client 2.0.2 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +141 -0
- package/dist/index.d.mts +74 -79
- package/dist/index.d.ts +74 -79
- package/dist/index.js +267 -68
- package/dist/index.mjs +269 -70
- package/package.json +5 -5
- package/src/CrdtManager.ts +9 -13
- package/src/CrdtNotifier.ts +2 -1
- package/src/ListenerManager.ts +21 -0
- package/src/MockedRoom.ts +118 -48
- package/src/PluvClient.ts +8 -8
- package/src/PluvProcedure.ts +27 -25
- package/src/PluvRoom.ts +235 -82
- package/src/PluvRouter.ts +31 -22
- package/src/StateNotifier.ts +13 -19
- package/src/UsersManager.ts +18 -4
- package/src/UsersNotifier.ts +66 -0
- package/src/constants.ts +1 -0
- package/src/createClient.ts +2 -2
- package/src/index.ts +2 -1
- package/src/types.ts +10 -31
- package/src/OtherNotifier.ts +0 -50
package/src/PluvRouter.ts
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AbstractCrdtDocFactory } from "@pluv/crdt";
|
|
2
2
|
import type { IOLike, IORouterLike, JsonObject } from "@pluv/types";
|
|
3
3
|
import type { PluvProcedure } from "./PluvProcedure";
|
|
4
4
|
|
|
5
5
|
export type PluvRouterEventConfig<
|
|
6
|
-
TIO extends IOLike
|
|
7
|
-
TPresence extends
|
|
8
|
-
|
|
9
|
-
> = { [P: string]: Pick<PluvProcedure<TIO, any, any, TPresence,
|
|
6
|
+
TIO extends IOLike,
|
|
7
|
+
TPresence extends Record<string, any>,
|
|
8
|
+
TCrdt extends AbstractCrdtDocFactory<any, any>,
|
|
9
|
+
> = { [P: string]: Pick<PluvProcedure<TIO, any, any, TPresence, TCrdt, "">, "config"> };
|
|
10
10
|
|
|
11
11
|
export type MergedRouter<
|
|
12
|
-
TRouters extends PluvRouter<TIO, TPresence,
|
|
13
|
-
TIO extends IOLike
|
|
14
|
-
TPresence extends
|
|
15
|
-
|
|
16
|
-
TRoot extends TRouters[0]["_defs"]["events"]
|
|
12
|
+
TRouters extends PluvRouter<TIO, TPresence, TCrdt, any>[],
|
|
13
|
+
TIO extends IOLike,
|
|
14
|
+
TPresence extends Record<string, any>,
|
|
15
|
+
TCrdt extends AbstractCrdtDocFactory<any, any>,
|
|
16
|
+
TRoot extends TRouters[0]["_defs"]["events"],
|
|
17
17
|
> = TRouters extends [
|
|
18
|
-
infer IHead extends PluvRouter<TIO, TPresence,
|
|
19
|
-
...infer ITail extends PluvRouter<TIO, TPresence,
|
|
18
|
+
infer IHead extends PluvRouter<TIO, TPresence, TCrdt, any>,
|
|
19
|
+
...infer ITail extends PluvRouter<TIO, TPresence, TCrdt, any>[],
|
|
20
20
|
]
|
|
21
|
-
? MergedRouter<ITail, TIO, TPresence,
|
|
22
|
-
: PluvRouter<TIO, TPresence,
|
|
21
|
+
? MergedRouter<ITail, TIO, TPresence, TCrdt, TRoot & IHead["_defs"]["events"]>
|
|
22
|
+
: PluvRouter<TIO, TPresence, TCrdt, TRoot>;
|
|
23
23
|
|
|
24
24
|
export class PluvRouter<
|
|
25
|
-
TIO extends IOLike
|
|
26
|
-
TPresence extends
|
|
27
|
-
|
|
28
|
-
TEvents extends PluvRouterEventConfig<TIO, TPresence,
|
|
25
|
+
TIO extends IOLike,
|
|
26
|
+
TPresence extends Record<string, any>,
|
|
27
|
+
TCrdt extends AbstractCrdtDocFactory<any, any>,
|
|
28
|
+
TEvents extends PluvRouterEventConfig<TIO, TPresence, TCrdt> = {},
|
|
29
29
|
> implements IORouterLike<TEvents>
|
|
30
30
|
{
|
|
31
31
|
readonly _defs: { events: TEvents } = { events: {} as TEvents };
|
|
@@ -42,9 +42,12 @@ export class PluvRouter<
|
|
|
42
42
|
this._defs = { events };
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
public static merge<
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
public static merge<
|
|
46
|
+
TRouters extends PluvRouter<TIO, TPresence, TCrdt, any>[],
|
|
47
|
+
TIO extends IOLike,
|
|
48
|
+
TPresence extends Record<string, any>,
|
|
49
|
+
TCrdt extends AbstractCrdtDocFactory<any, any>,
|
|
50
|
+
>(...routers: TRouters): MergedRouter<TRouters, TIO, TPresence, TCrdt, {}> {
|
|
48
51
|
const events = Object.assign(
|
|
49
52
|
Object.create(null),
|
|
50
53
|
...routers.map((router) => router._defs.events),
|
|
@@ -54,7 +57,13 @@ export class PluvRouter<
|
|
|
54
57
|
throw new Error('Procedures may not start with "$"');
|
|
55
58
|
}
|
|
56
59
|
|
|
57
|
-
return new PluvRouter<any, any, any, any>(events) as MergedRouter<
|
|
60
|
+
return new PluvRouter<any, any, any, any>(events) as MergedRouter<
|
|
61
|
+
TRouters,
|
|
62
|
+
TIO,
|
|
63
|
+
TPresence,
|
|
64
|
+
TCrdt,
|
|
65
|
+
{}
|
|
66
|
+
>;
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
private _isValidEventName(name: string): boolean {
|
package/src/StateNotifier.ts
CHANGED
|
@@ -1,36 +1,30 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
Id,
|
|
3
|
+
IOLike,
|
|
4
|
+
JsonObject,
|
|
5
|
+
StateNotifierSubjects,
|
|
6
|
+
SubscriptionCallback,
|
|
7
|
+
UserInfo,
|
|
8
|
+
WebSocketState,
|
|
9
|
+
} from "@pluv/types";
|
|
2
10
|
import type { Subject } from "wonka";
|
|
3
11
|
import { makeSubject, subscribe } from "wonka";
|
|
4
12
|
|
|
5
|
-
export interface StateNotifierSubjects<TIO extends IOLike, TPresence extends JsonObject> {
|
|
6
|
-
connection: Subject<Id<WebSocketState<TIO>>>;
|
|
7
|
-
"my-presence": Subject<TPresence | null>;
|
|
8
|
-
myself: Subject<Readonly<Id<UserInfo<TIO>>> | null>;
|
|
9
|
-
others: Subject<readonly Id<UserInfo<TIO>>[]>;
|
|
10
|
-
"storage-loaded": Subject<boolean>;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
13
|
type InferSubjectValue<
|
|
14
14
|
TIO extends IOLike,
|
|
15
|
-
TPresence extends
|
|
15
|
+
TPresence extends Record<string, any>,
|
|
16
16
|
TSubject extends keyof StateNotifierSubjects<TIO, TPresence>,
|
|
17
17
|
> =
|
|
18
18
|
StateNotifierSubjects<TIO, TPresence>[TSubject] extends Subject<infer IValue>
|
|
19
19
|
? Id<IValue>
|
|
20
20
|
: never;
|
|
21
21
|
|
|
22
|
-
export
|
|
23
|
-
TIO extends IOLike,
|
|
24
|
-
TPresence extends JsonObject,
|
|
25
|
-
TSubject extends keyof StateNotifierSubjects<TIO, TPresence>,
|
|
26
|
-
> = (value: InferSubjectValue<TIO, TPresence, TSubject>) => void;
|
|
27
|
-
|
|
28
|
-
export class StateNotifier<TIO extends IOLike, TPresence extends JsonObject = {}> {
|
|
22
|
+
export class StateNotifier<TIO extends IOLike, TPresence extends Record<string, any> = {}> {
|
|
29
23
|
public subjects: StateNotifierSubjects<TIO, TPresence> = {
|
|
30
24
|
connection: makeSubject<Id<WebSocketState<TIO>>>(),
|
|
31
25
|
"my-presence": makeSubject<TPresence>(),
|
|
32
|
-
myself: makeSubject<Readonly<Id<UserInfo<TIO>>> | null>(),
|
|
33
|
-
others: makeSubject<readonly Id<UserInfo<TIO>>[]>(),
|
|
26
|
+
myself: makeSubject<Readonly<Id<UserInfo<TIO, TPresence>>> | null>(),
|
|
27
|
+
others: makeSubject<readonly Id<UserInfo<TIO, TPresence>>[]>(),
|
|
34
28
|
"storage-loaded": makeSubject<true>(),
|
|
35
29
|
};
|
|
36
30
|
|
package/src/UsersManager.ts
CHANGED
|
@@ -1,29 +1,33 @@
|
|
|
1
1
|
import type { InputZodLike, IOLike, JsonObject, OptionalProps, UserInfo } from "@pluv/types";
|
|
2
|
+
import { PLUV_PRESENCE_META_KEY } from "./constants";
|
|
2
3
|
import type { PluvClientLimits } from "./types";
|
|
3
4
|
import { pickBy } from "./utils";
|
|
4
5
|
|
|
5
6
|
export type Presence = Record<string, unknown>;
|
|
6
7
|
|
|
7
|
-
export type UsersManagerConfig<TPresence extends
|
|
8
|
+
export type UsersManagerConfig<TPresence extends Record<string, any> = {}> = {
|
|
8
9
|
initialPresence?: TPresence;
|
|
9
10
|
limits: PluvClientLimits;
|
|
10
11
|
presence?: InputZodLike<TPresence>;
|
|
11
12
|
};
|
|
12
13
|
|
|
13
|
-
export type AddConnectionResult<TIO extends IOLike, TPresence extends
|
|
14
|
+
export type AddConnectionResult<TIO extends IOLike, TPresence extends Record<string, any> = {}> = {
|
|
14
15
|
clientId: string;
|
|
15
16
|
data: UserInfo<TIO, TPresence>;
|
|
16
17
|
isMyself: boolean;
|
|
17
18
|
remaining: number;
|
|
18
19
|
};
|
|
19
20
|
|
|
20
|
-
export type DeleteConnectionResult<
|
|
21
|
+
export type DeleteConnectionResult<
|
|
22
|
+
TIO extends IOLike,
|
|
23
|
+
TPresence extends Record<string, any> = {},
|
|
24
|
+
> = {
|
|
21
25
|
clientId: string;
|
|
22
26
|
data: UserInfo<TIO, TPresence>;
|
|
23
27
|
remaining: number;
|
|
24
28
|
};
|
|
25
29
|
|
|
26
|
-
export class UsersManager<TIO extends IOLike, TPresence extends
|
|
30
|
+
export class UsersManager<TIO extends IOLike, TPresence extends Record<string, any> = {}> {
|
|
27
31
|
public readonly initialPresence: TPresence;
|
|
28
32
|
|
|
29
33
|
private readonly _idMap = {
|
|
@@ -210,6 +214,16 @@ export class UsersManager<TIO extends IOLike, TPresence extends JsonObject = {}>
|
|
|
210
214
|
} as TPresence;
|
|
211
215
|
const validated = this._presence ? this._presence.parse(presence) : presence;
|
|
212
216
|
|
|
217
|
+
/**
|
|
218
|
+
* !HACK
|
|
219
|
+
* @description We're patching internal metadata back into the presence field so it doesn't
|
|
220
|
+
* get stripped from validation
|
|
221
|
+
* @date May 13, 2025
|
|
222
|
+
*/
|
|
223
|
+
if (!!presence[PLUV_PRESENCE_META_KEY]) {
|
|
224
|
+
(validated as any)[PLUV_PRESENCE_META_KEY] = presence[PLUV_PRESENCE_META_KEY];
|
|
225
|
+
}
|
|
226
|
+
|
|
213
227
|
this._others.set(clientId, { ...other, presence: validated });
|
|
214
228
|
|
|
215
229
|
return validated;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Id,
|
|
3
|
+
IOLike,
|
|
4
|
+
JsonObject,
|
|
5
|
+
OtherSubscriptionCallback,
|
|
6
|
+
OthersSubscriptionCallback,
|
|
7
|
+
OthersSubscriptionEvent,
|
|
8
|
+
UserInfo,
|
|
9
|
+
} from "@pluv/types";
|
|
10
|
+
import type { Subject } from "wonka";
|
|
11
|
+
import { makeSubject, subscribe, TypeOfSource } from "wonka";
|
|
12
|
+
|
|
13
|
+
export class UsersNotifier<TIO extends IOLike, TPresence extends Record<string, any> = {}> {
|
|
14
|
+
public readonly others = makeSubject<{
|
|
15
|
+
others: readonly Id<UserInfo<TIO, TPresence>>[];
|
|
16
|
+
event: OthersSubscriptionEvent<TIO, TPresence>;
|
|
17
|
+
}>();
|
|
18
|
+
|
|
19
|
+
private _otherSubjects = new Map<
|
|
20
|
+
[clientId: string][0],
|
|
21
|
+
Subject<Id<UserInfo<TIO, TPresence>> | null>
|
|
22
|
+
>();
|
|
23
|
+
|
|
24
|
+
public clear(): void {
|
|
25
|
+
this._otherSubjects.forEach((subject) => {
|
|
26
|
+
subject.next(null);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
this._otherSubjects.clear();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public delete(clientId: string): void {
|
|
33
|
+
this.other(clientId).next(null);
|
|
34
|
+
this._otherSubjects.delete(clientId);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public other(clientId: string): Subject<Id<UserInfo<TIO, TPresence>> | null> {
|
|
38
|
+
const subject = this._otherSubjects.get(clientId);
|
|
39
|
+
|
|
40
|
+
if (subject) return subject;
|
|
41
|
+
|
|
42
|
+
const newSubject = makeSubject<Id<UserInfo<TIO, TPresence>>>();
|
|
43
|
+
|
|
44
|
+
this._otherSubjects.set(clientId, newSubject);
|
|
45
|
+
|
|
46
|
+
return newSubject;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public subscribeOther(
|
|
50
|
+
clientId: string,
|
|
51
|
+
callback: OtherSubscriptionCallback<TIO, TPresence>,
|
|
52
|
+
): () => void {
|
|
53
|
+
const source = this.other(clientId).source;
|
|
54
|
+
const subscription = subscribe(callback)(source);
|
|
55
|
+
|
|
56
|
+
return subscription.unsubscribe;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public subscribeOthers(callback: OthersSubscriptionCallback<TIO, TPresence>): () => void {
|
|
60
|
+
const subscription = subscribe<TypeOfSource<typeof this.others.source>>(
|
|
61
|
+
({ others, event }) => callback(others, event),
|
|
62
|
+
)(this.others.source);
|
|
63
|
+
|
|
64
|
+
return subscription.unsubscribe;
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/constants.ts
CHANGED
package/src/createClient.ts
CHANGED
|
@@ -5,8 +5,8 @@ import { PluvClient } from "./PluvClient";
|
|
|
5
5
|
|
|
6
6
|
export const createClient = <
|
|
7
7
|
TIO extends IOLike<any, any>,
|
|
8
|
-
TPresence extends
|
|
9
|
-
TCrdt extends AbstractCrdtDocFactory<any> = NoopCrdtDocFactory,
|
|
8
|
+
TPresence extends Record<string, any> = {},
|
|
9
|
+
TCrdt extends AbstractCrdtDocFactory<any, any> = NoopCrdtDocFactory,
|
|
10
10
|
TMetadata extends JsonObject = {},
|
|
11
11
|
>(
|
|
12
12
|
options: PluvClientOptions<TIO, TPresence, TCrdt, TMetadata>,
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export type {
|
|
|
9
9
|
MergeEvents,
|
|
10
10
|
RoomLike,
|
|
11
11
|
UserInfo,
|
|
12
|
+
WebSocketConnection,
|
|
12
13
|
WebSocketState,
|
|
13
14
|
} from "@pluv/types";
|
|
14
15
|
export { AbstractStorageStore } from "./AbstractStorageStore";
|
|
@@ -32,4 +33,4 @@ export type {
|
|
|
32
33
|
export type { PluvRouter, PluvRouterEventConfig } from "./PluvRouter";
|
|
33
34
|
export { register } from "./register";
|
|
34
35
|
export type { RegisterParams } from "./register";
|
|
35
|
-
export type { InferMetadata, PublicKey, PublicKeyParams
|
|
36
|
+
export type { InferMetadata, PublicKey, PublicKeyParams } from "./types";
|
package/src/types.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import type { CrdtType } from "@pluv/crdt";
|
|
2
1
|
import type {
|
|
3
|
-
ConnectionState,
|
|
4
2
|
CrdtDocLike,
|
|
5
3
|
EventRecord,
|
|
6
4
|
Id,
|
|
@@ -19,22 +17,22 @@ export interface AuthorizationState<TIO extends IOLike> {
|
|
|
19
17
|
}
|
|
20
18
|
|
|
21
19
|
export type EventResolver<
|
|
22
|
-
TIO extends IOLike
|
|
23
|
-
TInput extends JsonObject
|
|
24
|
-
TOutput extends EventRecord<string, any
|
|
25
|
-
TPresence extends
|
|
26
|
-
|
|
20
|
+
TIO extends IOLike,
|
|
21
|
+
TInput extends JsonObject,
|
|
22
|
+
TOutput extends EventRecord<string, any>,
|
|
23
|
+
TPresence extends Record<string, any>,
|
|
24
|
+
TDocLike extends CrdtDocLike<any, any>,
|
|
27
25
|
> = (
|
|
28
26
|
data: TInput,
|
|
29
|
-
context: EventResolverContext<TIO, TPresence,
|
|
27
|
+
context: EventResolverContext<TIO, TPresence, TDocLike>,
|
|
30
28
|
) => MaybePromise<TOutput | void>;
|
|
31
29
|
|
|
32
30
|
export interface EventResolverContext<
|
|
33
|
-
TIO extends IOLike
|
|
34
|
-
TPresence extends
|
|
35
|
-
|
|
31
|
+
TIO extends IOLike,
|
|
32
|
+
TPresence extends Record<string, any>,
|
|
33
|
+
TDocLike extends CrdtDocLike<any, any>,
|
|
36
34
|
> {
|
|
37
|
-
doc:
|
|
35
|
+
doc: TDocLike;
|
|
38
36
|
others: readonly UserInfo<TIO, TPresence>[];
|
|
39
37
|
room: string;
|
|
40
38
|
user: UserInfo<TIO, TPresence>;
|
|
@@ -76,25 +74,6 @@ export type PublicKey<TMetadata extends JsonObject> =
|
|
|
76
74
|
| string
|
|
77
75
|
| ((params: PublicKeyParams<TMetadata>) => string);
|
|
78
76
|
|
|
79
|
-
export interface WebSocketConnection {
|
|
80
|
-
/**
|
|
81
|
-
* @description How many times a connection attempt was made. This will increment upon each
|
|
82
|
-
* unsuccessful attempt, and will reset upon a successful connection.
|
|
83
|
-
* @date April 13, 2025
|
|
84
|
-
*/
|
|
85
|
-
attempts: number;
|
|
86
|
-
/**
|
|
87
|
-
* @description How many times the user has connected to the room. This can be more than 1 in
|
|
88
|
-
* the case of reconnects. This is tracked because on the very first connection (i.e. when
|
|
89
|
-
* count is 0) we don't want to send the storage state as an update to the room and overwrite
|
|
90
|
-
* the upstream state.
|
|
91
|
-
* @date April 13, 2025
|
|
92
|
-
*/
|
|
93
|
-
count: number;
|
|
94
|
-
id: string | null;
|
|
95
|
-
state: ConnectionState;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
77
|
export type WithMetadata<TMetadata extends JsonObject = {}> = keyof TMetadata extends never
|
|
99
78
|
? { metadata?: undefined }
|
|
100
79
|
: { metadata: TMetadata };
|
package/src/OtherNotifier.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
Id,
|
|
3
|
-
IOLike,
|
|
4
|
-
JsonObject,
|
|
5
|
-
OtherNotifierSubscriptionCallback,
|
|
6
|
-
UserInfo,
|
|
7
|
-
} from "@pluv/types";
|
|
8
|
-
import type { Subject } from "wonka";
|
|
9
|
-
import { makeSubject, subscribe } from "wonka";
|
|
10
|
-
|
|
11
|
-
export class OtherNotifier<TIO extends IOLike, TPresence extends JsonObject = {}> {
|
|
12
|
-
private _subjects = new Map<
|
|
13
|
-
[clientId: string][0],
|
|
14
|
-
Subject<Id<UserInfo<TIO, TPresence>> | null>
|
|
15
|
-
>();
|
|
16
|
-
|
|
17
|
-
public clear(): void {
|
|
18
|
-
this._subjects.forEach((subject) => {
|
|
19
|
-
subject.next(null);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
this._subjects.clear();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
public delete(clientId: string): void {
|
|
26
|
-
this.subject(clientId).next(null);
|
|
27
|
-
this._subjects.delete(clientId);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
public subject(clientId: string): Subject<Id<UserInfo<TIO, TPresence>> | null> {
|
|
31
|
-
const subject = this._subjects.get(clientId);
|
|
32
|
-
|
|
33
|
-
if (subject) return subject;
|
|
34
|
-
|
|
35
|
-
const newSubject = makeSubject<Id<UserInfo<TIO, TPresence>>>();
|
|
36
|
-
|
|
37
|
-
this._subjects.set(clientId, newSubject);
|
|
38
|
-
|
|
39
|
-
return newSubject;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
public subscribe(
|
|
43
|
-
clientId: string,
|
|
44
|
-
callback: OtherNotifierSubscriptionCallback<TIO, TPresence>,
|
|
45
|
-
): () => void {
|
|
46
|
-
const source = this.subject(clientId).source;
|
|
47
|
-
|
|
48
|
-
return subscribe(callback)(source).unsubscribe;
|
|
49
|
-
}
|
|
50
|
-
}
|