@pluv/client 0.26.0 → 0.28.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/dist/index.d.ts CHANGED
@@ -1,16 +1,12 @@
1
1
  import { CrdtType, AbstractCrdtDoc, InferCrdtJson, AbstractCrdtDocFactory } from '@pluv/crdt';
2
2
  export { CrdtType, InferCrdtJson } from '@pluv/crdt';
3
3
  import * as _pluv_types from '@pluv/types';
4
- import { IOLike, InferIOOutput, Id, IOEventMessage, JsonObject, InferIOAuthorizeUser, InferIOAuthorize, InferIOInput, InputZodLike, InferIOAuthorizeRequired } from '@pluv/types';
4
+ import { IOLike, InferIOOutput, Id, IOEventMessage, JsonObject, InferIOAuthorizeUser, InferIOAuthorize, EventRecord, MaybePromise, InputZodLike, ProcedureLike, IORouterLike, InferIOInput, InferIOAuthorizeRequired } from '@pluv/types';
5
5
  export { BaseUser, EventMessage, EventRecord, IOEventMessage } from '@pluv/types';
6
6
  import { Subject } from 'wonka';
7
7
 
8
8
  type EventNotifierSubscriptionCallback<TIO extends IOLike, TEvent extends keyof InferIOOutput<TIO>> = (value: Id<IOEventMessage<TIO, TEvent>>) => void;
9
9
 
10
- interface AuthorizationState<TIO extends IOLike> {
11
- token: string | null;
12
- user: InferIOAuthorizeUser<InferIOAuthorize<TIO>> | null;
13
- }
14
10
  declare enum ConnectionState {
15
11
  Closed = "Closed",
16
12
  Connecting = "Connecting",
@@ -18,6 +14,24 @@ declare enum ConnectionState {
18
14
  Unavailable = "Unavailable",
19
15
  Untouched = "Untouched"
20
16
  }
17
+
18
+ interface AuthorizationState<TIO extends IOLike> {
19
+ token: string | null;
20
+ user: InferIOAuthorizeUser<InferIOAuthorize<TIO>> | null;
21
+ }
22
+ type EventResolver<TIO extends IOLike = IOLike, TInput extends JsonObject = {}, TOutput extends EventRecord<string, any> = {}, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}> = (data: TInput, context: EventResolverContext<TIO, TPresence, TStorage>) => MaybePromise<TOutput | void>;
23
+ interface EventResolverContext<TIO extends IOLike = IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}> {
24
+ doc: AbstractCrdtDoc<TStorage>;
25
+ others: readonly UserInfo<TIO, TPresence>[];
26
+ room: string;
27
+ user: UserInfo<TIO, TPresence>;
28
+ }
29
+ type MergeEventRecords<TEventRecords extends EventRecord<string, any>[], TRoot extends EventRecord<string, any> = {}> = TEventRecords extends [
30
+ infer IHead extends EventRecord<string, any>,
31
+ ...infer ITail extends EventRecord<string, any>[]
32
+ ] ? MergeEventRecords<ITail, Omit<TRoot, keyof IHead> & {
33
+ [P in keyof IHead]: TRoot extends Record<P, any> ? TRoot[P] | IHead[P] : IHead[P];
34
+ }> : Id<TRoot>;
21
35
  type UpdateMyPresenceAction<TPresence extends JsonObject> = Partial<TPresence> | ((oldPresence: TPresence | null) => Partial<TPresence>);
22
36
  interface UserInfo<TIO extends IOLike, TPresence extends JsonObject = {}> {
23
37
  connectionId: string;
@@ -37,6 +51,40 @@ interface WebSocketState<TIO extends IOLike> {
37
51
 
38
52
  type OtherNotifierSubscriptionCallback<TIO extends IOLike> = (value: Id<UserInfo<TIO>> | null) => void;
39
53
 
54
+ interface PluvProcedureConfig<TIO extends IOLike = IOLike, TInput extends JsonObject = {}, TOutput extends EventRecord<string, any> = {}, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}> {
55
+ broadcast?: EventResolver<TIO, TInput, TOutput, TPresence, TStorage> | null;
56
+ input?: InputZodLike<TInput>;
57
+ }
58
+ declare class PluvProcedure<TIO extends IOLike = IOLike, TInput extends JsonObject = {}, TOutput extends EventRecord<string, any> = {}, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TFilled extends "input" | "broadcast" | "" = ""> {
59
+ private _broadcast;
60
+ private _input;
61
+ get config(): ProcedureLike<TInput, TOutput>["config"];
62
+ constructor(config?: PluvProcedureConfig<TIO, TInput, TOutput, TPresence, TStorage>);
63
+ broadcast<TResult extends EventRecord<string, any> = {}>(resolver: EventResolver<TIO, TInput, TResult, TPresence, TStorage>): Omit<PluvProcedure<TIO, TInput, MergeEventRecords<[TOutput, TResult]>, TPresence, TStorage, TFilled | "input" | "broadcast">, TFilled | "input" | "broadcast">;
64
+ input<TData extends JsonObject>(input: InputZodLike<TData>): Omit<PluvProcedure<TIO, TData, {}, TPresence, TStorage, TFilled | "input">, TFilled | "input">;
65
+ private _resolver;
66
+ }
67
+
68
+ type PluvRouterEventConfig<TIO extends IOLike = IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}> = {
69
+ [P: string]: Pick<PluvProcedure<TIO, any, any, TPresence, TStorage>, "config">;
70
+ };
71
+ type MergedRouter<TRouters extends PluvRouter<TIO, TPresence, TStorage, any>[] = [], TIO extends IOLike = IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TRoot extends TRouters[0]["_defs"]["events"] = {}> = TRouters extends [
72
+ infer IHead extends PluvRouter<TIO, TPresence, TStorage, any>,
73
+ ...infer ITail extends PluvRouter<TIO, TPresence, TStorage, any>[]
74
+ ] ? MergedRouter<ITail, TIO, TPresence, TStorage, TRoot & IHead["_defs"]["events"]> : PluvRouter<TIO, TPresence, TStorage, TRoot>;
75
+ type MergeEvents<TClientEvents extends PluvRouterEventConfig<any, any, any>, TServerIO extends IOLike<any, any>> = TServerIO extends IOLike<infer IAuthorize, infer IServerEvents> ? IOLike<IAuthorize, {
76
+ [P in keyof TClientEvents]: TClientEvents[P] extends ProcedureLike<infer IClientInput, infer IClientOutput> ? {
77
+ [K in keyof IClientOutput]: K extends keyof IServerEvents ? IServerEvents[K] : ProcedureLike<IClientInput, Id<Pick<IClientOutput, K>>>;
78
+ }[keyof IClientOutput] : never;
79
+ } & IServerEvents> : never;
80
+ declare class PluvRouter<TIO extends IOLike = IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}> implements IORouterLike<TEvents> {
81
+ readonly _defs: {
82
+ events: TEvents;
83
+ };
84
+ constructor(events: TEvents);
85
+ static merge<TRouters extends PluvRouter<any, any, any, any>[]>(...routers: TRouters): MergedRouter<TRouters>;
86
+ }
87
+
40
88
  interface StateNotifierSubjects<TIO extends IOLike, TPresence extends JsonObject> {
41
89
  connection: Subject<Id<WebSocketState<TIO>>>;
42
90
  "my-presence": Subject<TPresence | null>;
@@ -47,15 +95,15 @@ interface StateNotifierSubjects<TIO extends IOLike, TPresence extends JsonObject
47
95
  type InferSubjectValue<TIO extends IOLike, TPresence extends JsonObject, TSubject extends keyof StateNotifierSubjects<TIO, TPresence>> = StateNotifierSubjects<TIO, TPresence>[TSubject] extends Subject<infer IValue> ? Id<IValue> : never;
48
96
  type SubscriptionCallback<TIO extends IOLike, TPresence extends JsonObject, TSubject extends keyof StateNotifierSubjects<TIO, TPresence>> = (value: InferSubjectValue<TIO, TPresence, TSubject>) => void;
49
97
 
50
- declare abstract class AbstractRoom<TIO extends IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}> {
98
+ declare abstract class AbstractRoom<TIO extends IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}> {
51
99
  readonly id: string;
52
100
  constructor(room: string);
53
101
  abstract storageLoaded: boolean;
54
- abstract broadcast<TEvent extends keyof InferIOInput<TIO>>(event: TEvent, data: Id<InferIOInput<TIO>[TEvent]>): void;
102
+ abstract broadcast<TEvent extends keyof InferIOInput<MergeEvents<TEvents, TIO>>>(event: TEvent, data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[TEvent]>): void;
55
103
  abstract canRedo(): boolean;
56
104
  abstract canUndo(): boolean;
57
105
  abstract getDoc(): AbstractCrdtDoc<TStorage>;
58
- abstract event<TEvent extends keyof InferIOOutput<TIO>>(event: TEvent, callback: EventNotifierSubscriptionCallback<TIO, TEvent>): () => void;
106
+ abstract event<TEvent extends keyof InferIOOutput<MergeEvents<TEvents, TIO>>>(event: TEvent, callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, TEvent>): () => void;
59
107
  abstract getConnection(): WebSocketConnection;
60
108
  abstract getMyPresence(): TPresence;
61
109
  abstract getMyself(): Id<UserInfo<TIO, TPresence>> | null;
@@ -132,17 +180,17 @@ type PluvRoomDebug<TIO extends IOLike> = Id<{
132
180
  output: readonly (keyof InferIOOutput<TIO>)[];
133
181
  input: readonly (keyof InferIOInput<TIO>)[];
134
182
  }>;
135
- type PluvRoomOptions<TIO extends IOLike, TMetadata extends JsonObject = {}, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}> = {
183
+ type RoomConfig<TIO extends IOLike, TMetadata extends JsonObject = {}, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}> = Id<{
136
184
  addons?: readonly PluvRoomAddon<TIO, TMetadata, TPresence, TStorage>[];
137
185
  debug?: boolean | PluvRoomDebug<TIO>;
138
186
  onAuthorizationFail?: (error: Error) => void;
139
- } & Omit<CrdtManagerOptions<TStorage>, "encodedState"> & UsersManagerConfig<TPresence> & (keyof TMetadata extends never ? {
187
+ router?: PluvRouter<TIO, TPresence, TStorage, TEvents>;
188
+ } & RoomEndpoints<TIO, TMetadata> & Pick<CrdtManagerOptions<TStorage>, "initialStorage"> & UsersManagerConfig<TPresence> & (keyof TMetadata extends never ? {
140
189
  metadata?: undefined;
141
190
  } : {
142
191
  metadata: TMetadata;
143
- });
144
- type RoomConfig<TIO extends IOLike, TMetadata extends JsonObject = {}, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}> = RoomEndpoints<TIO, TMetadata> & PluvRoomOptions<TIO, TMetadata, TPresence, TStorage>;
145
- declare class PluvRoom<TIO extends IOLike, TMetadata extends JsonObject = {}, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}> extends AbstractRoom<TIO, TPresence, TStorage> {
192
+ })>;
193
+ declare class PluvRoom<TIO extends IOLike, TMetadata extends JsonObject = {}, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}> extends AbstractRoom<TIO, TPresence, TStorage> {
146
194
  readonly _endpoints: RoomEndpoints<TIO, TMetadata>;
147
195
  private _crdtManager;
148
196
  private _crdtNotifier;
@@ -152,6 +200,7 @@ declare class PluvRoom<TIO extends IOLike, TMetadata extends JsonObject = {}, TP
152
200
  private _listeners;
153
201
  private _metadata;
154
202
  private _otherNotifier;
203
+ private _router;
155
204
  private _state;
156
205
  private _stateNotifier;
157
206
  private _storageStore;
@@ -160,15 +209,15 @@ declare class PluvRoom<TIO extends IOLike, TMetadata extends JsonObject = {}, TP
160
209
  private _windowListeners;
161
210
  private _wsListeners;
162
211
  private _usersManager;
163
- constructor(room: string, options: RoomConfig<TIO, TMetadata, TPresence, TStorage>);
212
+ constructor(room: string, options: RoomConfig<TIO, TMetadata, TPresence, TStorage, TEvents>);
164
213
  get storageLoaded(): boolean;
165
214
  get webSocket(): WebSocket | null;
166
- broadcast: (<TEvent extends keyof InferIOInput<TIO>>(event: TEvent, data: Id<InferIOInput<TIO>[TEvent]>) => void) & { [event in keyof InferIOInput<TIO>]: (data: Id<InferIOInput<TIO>[event]>) => void; };
215
+ broadcast: (<TEvent extends keyof InferIOInput<MergeEvents<TEvents, TIO>>>(event: TEvent, data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[TEvent]>) => Promise<void>) & { [event in keyof InferIOInput<TIO>]: (data: Id<InferIOInput<TIO>[event]>) => Promise<void>; };
167
216
  canRedo: () => boolean;
168
217
  canUndo: () => boolean;
169
218
  connect(): Promise<void>;
170
219
  disconnect(): Promise<void>;
171
- event: (<TEvent extends keyof InferIOOutput<TIO>>(event: TEvent, callback: EventNotifierSubscriptionCallback<TIO, TEvent>) => () => void) & { [event in keyof InferIOOutput<TIO>]: (callback: EventNotifierSubscriptionCallback<TIO, event>) => () => void; };
220
+ event: (<TEvent extends keyof InferIOOutput<MergeEvents<TEvents, TIO>>>(event: TEvent, callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, TEvent>) => () => void) & { [event in keyof InferIOOutput<MergeEvents<TEvents, TIO>>]: (callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, any>) => () => void; };
172
221
  getConnection: () => WebSocketConnection;
173
222
  getDoc: () => AbstractCrdtDoc<TStorage>;
174
223
  getMyPresence: () => TPresence;
@@ -237,47 +286,77 @@ declare class PluvRoom<TIO extends IOLike, TMetadata extends JsonObject = {}, TP
237
286
  private _updateState;
238
287
  }
239
288
 
240
- type PluvClientOptions<TIO extends IOLike, TMetadata extends JsonObject> = RoomEndpoints<TIO, TMetadata> & {
289
+ declare const identity: <T extends unknown>(x: T) => T;
290
+
291
+ type InferCallback<TIO extends IOLike> = (i: typeof identity) => {
292
+ io: (io: TIO) => TIO;
293
+ };
294
+ type PluvClientOptions<TIO extends IOLike, TPresence extends JsonObject, TStorage extends Record<string, CrdtType<any, any>>, TMetadata extends JsonObject> = RoomEndpoints<TIO, TMetadata> & {
241
295
  debug?: boolean;
296
+ infer: InferCallback<TIO>;
297
+ initialStorage?: AbstractCrdtDocFactory<TStorage>;
298
+ metadata?: InputZodLike<TMetadata>;
299
+ presence?: InputZodLike<TPresence>;
242
300
  };
243
- declare class PluvClient<TIO extends IOLike = IOLike, TMetadata extends JsonObject = {}> {
301
+ type CreateRoomOptions<TIO extends IOLike, TPresence extends JsonObject, TStorage extends Record<string, CrdtType<any, any>>, TMetadata extends JsonObject, TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}> = {
302
+ addons?: readonly PluvRoomAddon<TIO, TMetadata, TPresence, TStorage>[];
303
+ debug?: boolean | PluvRoomDebug<TIO>;
304
+ initialPresence?: TPresence;
305
+ initialStorage?: AbstractCrdtDocFactory<TStorage>;
306
+ onAuthorizationFail?: (error: Error) => void;
307
+ router?: PluvRouter<TIO, TPresence, TStorage, TEvents>;
308
+ } & (keyof TMetadata extends never ? {
309
+ metadata?: undefined;
310
+ } : {
311
+ metadata: TMetadata;
312
+ });
313
+ declare class PluvClient<TIO extends IOLike = IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TMetadata extends JsonObject = {}> {
244
314
  private _authEndpoint;
245
315
  private _debug;
316
+ private _initialStorage?;
317
+ private _presence?;
246
318
  private _wsEndpoint;
247
319
  private _rooms;
248
- constructor(options: PluvClientOptions<TIO, TMetadata>);
249
- createRoom: <TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}>(room: string, options: PluvRoomOptions<TIO, TMetadata, TPresence, TStorage>) => PluvRoom<TIO, TMetadata, TPresence, TStorage>;
250
- enter: (room: string | PluvRoom<TIO, any, any, any>) => Promise<PluvRoom<TIO, TMetadata, JsonObject, any>>;
251
- getRoom: <TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}>(room: string) => PluvRoom<TIO, TMetadata, TPresence, TStorage> | null;
252
- getRooms: () => readonly PluvRoom<TIO, TMetadata, JsonObject, any>[];
320
+ get _defs(): {
321
+ initialStorage: AbstractCrdtDocFactory<TStorage> | undefined;
322
+ };
323
+ get procedure(): PluvProcedure<TIO, {}, {}, TPresence, TStorage>;
324
+ constructor(options: PluvClientOptions<TIO, TPresence, TStorage, TMetadata>);
325
+ createRoom: <TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}>(room: string, options: CreateRoomOptions<TIO, TPresence, TStorage, TMetadata, TEvents>) => PluvRoom<TIO, TMetadata, TPresence, TStorage>;
326
+ enter: (room: string | PluvRoom<TIO, any, any, any>) => Promise<PluvRoom<TIO, TMetadata, TPresence, TStorage, any>>;
327
+ getRoom: (room: string) => PluvRoom<TIO, TMetadata, TPresence, TStorage, any> | null;
328
+ getRooms: () => readonly PluvRoom<TIO, TMetadata, TPresence, TStorage, any>[];
253
329
  leave: (room: string | PluvRoom<TIO, any, any, any>) => Promise<void>;
330
+ router<TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}>(events: TEvents): PluvRouter<TIO, TPresence, TStorage, TEvents>;
254
331
  private _logDebug;
255
332
  }
256
333
 
257
- declare const createClient: <TIO extends IOLike = IOLike<_pluv_types.IOAuthorize<any, any, any>, {}>, TMetadata extends JsonObject = {}>(options: PluvClientOptions<TIO, TMetadata>) => PluvClient<TIO, TMetadata>;
334
+ declare const createClient: <TIO extends IOLike = IOLike<_pluv_types.IOAuthorize<any, any, any>, {}>, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TMetadata extends JsonObject = {}>(options: PluvClientOptions<TIO, TPresence, TStorage, TMetadata>) => PluvClient<TIO, TPresence, TStorage, TMetadata>;
258
335
 
259
336
  type MockedRoomEvents<TIO extends IOLike> = Partial<{
260
337
  [P in keyof InferIOInput<TIO>]: (data: Id<InferIOInput<TIO>[P]>) => Partial<InferIOOutput<TIO>>;
261
338
  }>;
262
- type MockedRoomConfig<TIO extends IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}> = {
263
- events?: MockedRoomEvents<TIO>;
264
- } & Omit<CrdtManagerOptions<TStorage>, "encodedState"> & UsersManagerConfig<TPresence>;
265
- declare class MockedRoom<TIO extends IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}> extends AbstractRoom<TIO, TPresence, TStorage> {
339
+ type MockedRoomConfig<TIO extends IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}> = {
340
+ events?: MockedRoomEvents<MergeEvents<TEvents, TIO>>;
341
+ router?: PluvRouter<TIO, TPresence, TStorage, TEvents>;
342
+ } & Pick<CrdtManagerOptions<TStorage>, "initialStorage"> & UsersManagerConfig<TPresence>;
343
+ declare class MockedRoom<TIO extends IOLike, TPresence extends JsonObject = {}, TStorage extends Record<string, CrdtType<any, any>> = {}, TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {}> extends AbstractRoom<TIO, TPresence, TStorage> {
266
344
  private _crdtManager;
267
345
  private _crdtNotifier;
268
346
  private _eventNotifier;
269
347
  private _events?;
270
348
  private _otherNotifier;
349
+ private _router;
271
350
  private _state;
272
351
  private _stateNotifier;
273
352
  private _subscriptions;
274
353
  private _usersManager;
275
- constructor(room: string, options: MockedRoomConfig<TIO, TPresence, TStorage>);
354
+ constructor(room: string, options: MockedRoomConfig<TIO, TPresence, TStorage, TEvents>);
276
355
  get storageLoaded(): boolean;
277
- broadcast<TEvent extends keyof InferIOInput<TIO>>(event: TEvent, data: Id<InferIOInput<TIO>[TEvent]>): void;
356
+ broadcast: (<TEvent extends keyof InferIOInput<MergeEvents<TEvents, TIO>>>(event: TEvent, data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[TEvent]>) => Promise<void>) & { [event in keyof InferIOInput<TIO>]: (data: Id<InferIOInput<TIO>[event]>) => Promise<void>; };
278
357
  canRedo: () => boolean;
279
358
  canUndo: () => boolean;
280
- event: <TEvent extends keyof InferIOOutput<TIO>>(event: TEvent, callback: EventNotifierSubscriptionCallback<TIO, TEvent>) => (() => void);
359
+ event: (<TEvent extends keyof InferIOOutput<MergeEvents<TEvents, TIO>>>(event: TEvent, callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, TEvent>) => () => void) & { [event in keyof InferIOOutput<MergeEvents<TEvents, TIO>>]: (callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, any>) => () => void; };
281
360
  getConnection: () => WebSocketConnection;
282
361
  getDoc(): AbstractCrdtDoc<TStorage>;
283
362
  getMyPresence: () => TPresence;
@@ -296,6 +375,7 @@ declare class MockedRoom<TIO extends IOLike, TPresence extends JsonObject = {},
296
375
  undo: () => void;
297
376
  updateMyPresence: (presence: UpdateMyPresenceAction<TPresence>) => void;
298
377
  private _observeCrdt;
378
+ private _simulateEvent;
299
379
  }
300
380
 
301
- export { AbstractRoom, AbstractStorageStore, ConnectionState, MockedRoom, type MockedRoomConfig, type MockedRoomEvents, PluvClient, type PluvClientOptions, PluvRoom, type PluvRoomAddon, type PluvRoomAddonInput, type PluvRoomAddonResult, type PluvRoomDebug, type PluvRoomOptions, type UserInfo, type WebSocketConnection, type WebSocketState, createClient };
381
+ export { AbstractRoom, AbstractStorageStore, ConnectionState, type CreateRoomOptions, type MergeEvents, MockedRoom, type MockedRoomConfig, type MockedRoomEvents, PluvClient, type PluvClientOptions, type PluvProcedureConfig, PluvRoom, type PluvRoomAddon, type PluvRoomAddonInput, type PluvRoomAddonResult, type PluvRoomDebug, PluvRouter, type PluvRouterEventConfig, type UserInfo, type WebSocketConnection, type WebSocketState, createClient };
package/dist/index.js CHANGED
@@ -81,6 +81,41 @@ var AbstractStorageStore = class {
81
81
  }
82
82
  };
83
83
 
84
+ // src/PluvProcedure.ts
85
+ var PluvProcedure = class _PluvProcedure {
86
+ constructor(config = {}) {
87
+ this._broadcast = null;
88
+ this._input = null;
89
+ const { broadcast, input } = config;
90
+ this._broadcast = broadcast != null ? broadcast : null;
91
+ this._input = input != null ? input : null;
92
+ }
93
+ get config() {
94
+ var _a, _b, _c;
95
+ return {
96
+ broadcast: (_b = (_a = this._broadcast) == null ? void 0 : _a.bind(this)) != null ? _b : null,
97
+ input: (_c = this._input) != null ? _c : null,
98
+ resolver: this._resolver()
99
+ };
100
+ }
101
+ broadcast(resolver) {
102
+ const { broadcast } = this.config;
103
+ if (!!broadcast) throw new Error("Broadcast was already defined for this procedure");
104
+ return new _PluvProcedure(__spreadProps(__spreadValues({}, this.config), {
105
+ broadcast: resolver
106
+ }));
107
+ }
108
+ input(input) {
109
+ return new _PluvProcedure({ input });
110
+ }
111
+ _resolver() {
112
+ return (data, context) => {
113
+ var _a;
114
+ return (_a = this._broadcast) == null ? void 0 : _a.call(this, data, context);
115
+ };
116
+ }
117
+ };
118
+
84
119
  // src/CrdtManager.ts
85
120
  var import_crdt = require("@pluv/crdt");
86
121
  var CrdtManager = class {
@@ -208,6 +243,21 @@ var OtherNotifier = class {
208
243
  }
209
244
  };
210
245
 
246
+ // src/PluvRouter.ts
247
+ var PluvRouter = class _PluvRouter {
248
+ constructor(events) {
249
+ this._defs = { events: {} };
250
+ this._defs = { events };
251
+ }
252
+ static merge(...routers) {
253
+ const events = Object.assign(/* @__PURE__ */ Object.create(null), ...routers.map((router) => router._defs.events));
254
+ if (Object.keys(events).some((key) => key.startsWith("$"))) {
255
+ throw new Error('Procedures may not start with "$"');
256
+ }
257
+ return new _PluvRouter(events);
258
+ }
259
+ };
260
+
211
261
  // src/StateNotifier.ts
212
262
  var import_wonka4 = require("wonka");
213
263
  var StateNotifier = class {
@@ -346,7 +396,7 @@ var UsersManager = class {
346
396
  }
347
397
  };
348
398
 
349
- // src/types.ts
399
+ // src/enums.ts
350
400
  var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
351
401
  ConnectionState2["Closed"] = "Closed";
352
402
  ConnectionState2["Connecting"] = "Connecting";
@@ -396,6 +446,7 @@ var PluvRoom = class extends AbstractRoom {
396
446
  metadata,
397
447
  onAuthorizationFail,
398
448
  presence,
449
+ router,
399
450
  wsEndpoint
400
451
  } = options;
401
452
  super(room);
@@ -429,17 +480,37 @@ var PluvRoom = class extends AbstractRoom {
429
480
  this._windowListeners = null;
430
481
  this._wsListeners = null;
431
482
  this.broadcast = new Proxy(
432
- (event, data) => {
483
+ (event, data) => __async(this, null, function* () {
433
484
  if (!this._state.webSocket) return;
434
485
  if (this._state.connection.state !== "Open" /* Open */) return;
435
486
  const type = event.toString();
436
- this._sendMessage({ data, type });
437
- },
487
+ const procedure = this._router._defs.events[type];
488
+ if (!(procedure == null ? void 0 : procedure.config.broadcast)) {
489
+ this._sendMessage({ data, type });
490
+ return;
491
+ }
492
+ const myself = this._usersManager.myself;
493
+ if (!myself) return;
494
+ const parsed = procedure.config.input ? procedure.config.input.parse(data) : data;
495
+ const context = {
496
+ doc: this._crdtManager.doc,
497
+ others: this._usersManager.getOthers(),
498
+ room: this.id,
499
+ user: myself
500
+ };
501
+ const output = yield procedure.config.broadcast(
502
+ parsed,
503
+ context
504
+ );
505
+ Object.entries(output).forEach(([_type, _data]) => {
506
+ this._sendMessage({ data: _data, type: _type });
507
+ });
508
+ }),
438
509
  {
439
510
  get(fn, prop) {
440
- return (data) => {
441
- return fn(prop, data);
442
- };
511
+ return (data) => __async(this, null, function* () {
512
+ return yield fn(prop, data);
513
+ });
443
514
  }
444
515
  }
445
516
  );
@@ -514,7 +585,10 @@ var PluvRoom = class extends AbstractRoom {
514
585
  const myself = (_a = this._usersManager.myself) != null ? _a : null;
515
586
  this._stateNotifier.subjects["my-presence"].next(myPresence);
516
587
  !!myself && this._stateNotifier.subjects["myself"].next(myself);
517
- this.broadcast("$UPDATE_PRESENCE", { presence: newPresence });
588
+ this.broadcast(
589
+ "$UPDATE_PRESENCE",
590
+ { presence: newPresence }
591
+ );
518
592
  };
519
593
  this._flattenStorageStore = debounce(
520
594
  () => __async(this, null, function* () {
@@ -546,6 +620,7 @@ var PluvRoom = class extends AbstractRoom {
546
620
  onAuthorizationFail == null ? void 0 : onAuthorizationFail(error);
547
621
  }
548
622
  };
623
+ this._router = router != null ? router : new PluvRouter({});
549
624
  this._usersManager = new UsersManager({ initialPresence, presence });
550
625
  this._crdtManager = new CrdtManager({ initialStorage });
551
626
  }
@@ -1114,9 +1189,11 @@ var PluvClient = class {
1114
1189
  authEndpoint: this._authEndpoint,
1115
1190
  debug: options.debug,
1116
1191
  initialPresence: options.initialPresence,
1117
- initialStorage: options.initialStorage,
1192
+ initialStorage: this._initialStorage,
1118
1193
  metadata: options.metadata,
1119
1194
  onAuthorizationFail: options.onAuthorizationFail,
1195
+ presence: this._presence,
1196
+ router: options.router,
1120
1197
  wsEndpoint: this._wsEndpoint
1121
1198
  });
1122
1199
  this._rooms.set(room, newRoom);
@@ -1145,11 +1222,24 @@ var PluvClient = class {
1145
1222
  this._rooms.delete(toLeave.id);
1146
1223
  this._logDebug(`Left and deleted room: ${room}`);
1147
1224
  });
1148
- const { authEndpoint, debug = false, wsEndpoint } = options;
1225
+ const { authEndpoint, debug = false, initialStorage, presence, wsEndpoint } = options;
1149
1226
  this._authEndpoint = authEndpoint;
1150
1227
  this._debug = debug;
1228
+ this._initialStorage = initialStorage;
1229
+ this._presence = presence;
1151
1230
  this._wsEndpoint = wsEndpoint;
1152
1231
  }
1232
+ get _defs() {
1233
+ return {
1234
+ initialStorage: this._initialStorage
1235
+ };
1236
+ }
1237
+ get procedure() {
1238
+ return new PluvProcedure();
1239
+ }
1240
+ router(events) {
1241
+ return new PluvRouter(events);
1242
+ }
1153
1243
  _logDebug(...data) {
1154
1244
  var _a;
1155
1245
  if (((_a = process == null ? void 0 : process.env) == null ? void 0 : _a.NODE_ENV) === "production") return;
@@ -1165,7 +1255,7 @@ var createClient = (options) => {
1165
1255
  // src/MockedRoom.ts
1166
1256
  var MockedRoom = class extends AbstractRoom {
1167
1257
  constructor(room, options) {
1168
- const { events, initialPresence, initialStorage, presence } = options;
1258
+ const { events, initialPresence, initialStorage, presence, router } = options;
1169
1259
  super(room);
1170
1260
  this._crdtNotifier = new CrdtNotifier();
1171
1261
  this._eventNotifier = new EventNotifier();
@@ -1186,15 +1276,55 @@ var MockedRoom = class extends AbstractRoom {
1186
1276
  this._subscriptions = {
1187
1277
  observeCrdt: null
1188
1278
  };
1279
+ this.broadcast = new Proxy(
1280
+ (event, data) => __async(this, null, function* () {
1281
+ if (!this._state.webSocket) return;
1282
+ if (this._state.connection.state !== "Open" /* Open */) return;
1283
+ const type = event.toString();
1284
+ const procedure = this._router._defs.events[type];
1285
+ if (!(procedure == null ? void 0 : procedure.config.broadcast)) {
1286
+ this._simulateEvent(type, data);
1287
+ return;
1288
+ }
1289
+ const myself = this._usersManager.myself;
1290
+ if (!myself) return;
1291
+ const parsed = procedure.config.input ? procedure.config.input.parse(data) : data;
1292
+ const context = {
1293
+ doc: this._crdtManager.doc,
1294
+ others: this._usersManager.getOthers(),
1295
+ room: this.id,
1296
+ user: myself
1297
+ };
1298
+ const output = yield procedure.config.broadcast(
1299
+ parsed,
1300
+ context
1301
+ );
1302
+ Object.entries(output).forEach(([_type, _data]) => {
1303
+ this._simulateEvent(_type, _data);
1304
+ });
1305
+ }),
1306
+ {
1307
+ get(fn, prop) {
1308
+ return (data) => __async(this, null, function* () {
1309
+ return yield fn(prop, data);
1310
+ });
1311
+ }
1312
+ }
1313
+ );
1189
1314
  this.canRedo = () => {
1190
1315
  return !!this._crdtManager.doc.canRedo();
1191
1316
  };
1192
1317
  this.canUndo = () => {
1193
1318
  return !!this._crdtManager.doc.canUndo();
1194
1319
  };
1195
- this.event = (event, callback) => {
1196
- return this._eventNotifier.subscribe(event, callback);
1197
- };
1320
+ this.event = new Proxy(
1321
+ (event, callback) => this._eventNotifier.subscribe(event, callback),
1322
+ {
1323
+ get(fn, prop) {
1324
+ return (callback) => fn(prop, callback);
1325
+ }
1326
+ }
1327
+ );
1198
1328
  this.getConnection = () => {
1199
1329
  return Object.freeze(JSON.parse(JSON.stringify(this._state.connection)));
1200
1330
  };
@@ -1253,6 +1383,7 @@ var MockedRoom = class extends AbstractRoom {
1253
1383
  !!myself && this._stateNotifier.subjects["myself"].next(myself);
1254
1384
  };
1255
1385
  this._events = events;
1386
+ this._router = router != null ? router : new PluvRouter({});
1256
1387
  this._usersManager = new UsersManager({
1257
1388
  initialPresence,
1258
1389
  presence
@@ -1265,18 +1396,6 @@ var MockedRoom = class extends AbstractRoom {
1265
1396
  get storageLoaded() {
1266
1397
  return true;
1267
1398
  }
1268
- broadcast(event, data) {
1269
- if (!this._events) return;
1270
- const resolver = this._events[event];
1271
- if (!resolver) return;
1272
- const result = resolver(data);
1273
- Object.keys(result).forEach((type) => {
1274
- const _type = type.toString();
1275
- const data2 = result[_type];
1276
- if (!data2) return;
1277
- this._eventNotifier.subject(_type).next(data2);
1278
- });
1279
- }
1280
1399
  getDoc() {
1281
1400
  return this._crdtManager.doc;
1282
1401
  }
@@ -1308,6 +1427,19 @@ var MockedRoom = class extends AbstractRoom {
1308
1427
  });
1309
1428
  this._subscriptions.observeCrdt = unsubscribe;
1310
1429
  }
1430
+ _simulateEvent(event, data) {
1431
+ if (!this._events) return;
1432
+ const type = event.toString();
1433
+ const resolver = this._events[type];
1434
+ if (!resolver) return;
1435
+ const result = resolver(data);
1436
+ Object.keys(result).forEach((type2) => {
1437
+ const _type = type2.toString();
1438
+ const data2 = result[_type];
1439
+ if (!data2) return;
1440
+ this._eventNotifier.subject(_type).next(data2);
1441
+ });
1442
+ }
1311
1443
  };
1312
1444
  // Annotate the CommonJS export names for ESM import in node:
1313
1445
  0 && (module.exports = {