@pluv/client 0.25.4 → 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/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;
@@ -46,10 +51,11 @@ const ORIGIN_STORAGE_UPDATED = "$STORAGE_UPDATED";
46
51
 
47
52
  export const DEFAULT_PLUV_CLIENT_ADDON = <
48
53
  TIO extends IOLike = IOLike,
54
+ TMetadata extends JsonObject = {},
49
55
  TPresence extends JsonObject = {},
50
56
  TStorage extends Record<string, CrdtType<any, any>> = {},
51
57
  >(
52
- input: PluvRoomAddonInput<TIO, TPresence, TStorage>,
58
+ input: PluvRoomAddonInput<TIO, TMetadata, TPresence, TStorage>,
53
59
  ): PluvRoomAddonResult => ({
54
60
  storage: new StorageStore(input.room.id),
55
61
  });
@@ -83,15 +89,22 @@ interface TimeoutIds {
83
89
  reconnect: number | null;
84
90
  }
85
91
 
86
- export type AuthEndpoint = string | ((room: string) => string | FetchOptions) | true;
87
- export type WsEndpoint = string | ((room: string) => string);
92
+ export interface EndpointParams<TMetadata extends JsonObject> {
93
+ metadata: TMetadata;
94
+ room: string;
95
+ }
96
+ export type AuthEndpoint<TMetadata extends JsonObject> =
97
+ | string
98
+ | ((params: EndpointParams<TMetadata>) => string | FetchOptions)
99
+ | true;
100
+ export type WsEndpoint<TMetadata extends JsonObject> = string | ((params: EndpointParams<TMetadata>) => string);
88
101
 
89
102
  type FetchOptions = { url: string; options?: RequestInit };
90
103
 
91
- export type RoomEndpoints<TIO extends IOLike> = {
92
- wsEndpoint?: WsEndpoint;
104
+ export type RoomEndpoints<TIO extends IOLike, TMetadata extends JsonObject> = {
105
+ wsEndpoint?: WsEndpoint<TMetadata>;
93
106
  } & (InferIOAuthorizeRequired<InferIOAuthorize<TIO>> extends true
94
- ? { authEndpoint: AuthEndpoint }
107
+ ? { authEndpoint: AuthEndpoint<TMetadata> }
95
108
  : { authEndpoint?: undefined });
96
109
 
97
110
  interface InternalListeners {
@@ -100,16 +113,18 @@ interface InternalListeners {
100
113
 
101
114
  export type PluvRoomAddon<
102
115
  TIO extends IOLike = IOLike,
116
+ TMetadata extends JsonObject = {},
103
117
  TPresence extends JsonObject = {},
104
118
  TStorage extends Record<string, CrdtType<any, any>> = {},
105
- > = (input: PluvRoomAddonInput<TIO, TPresence, TStorage>) => Partial<PluvRoomAddonResult>;
119
+ > = (input: PluvRoomAddonInput<TIO, TMetadata, TPresence, TStorage>) => Partial<PluvRoomAddonResult>;
106
120
 
107
121
  export interface PluvRoomAddonInput<
108
122
  TIO extends IOLike = IOLike,
123
+ TMetadata extends JsonObject = {},
109
124
  TPresence extends JsonObject = {},
110
125
  TStorage extends Record<string, CrdtType<any, any>> = {},
111
126
  > {
112
- room: PluvRoom<TIO, TPresence, TStorage>;
127
+ room: PluvRoom<TIO, TMetadata, TPresence, TStorage>;
113
128
  }
114
129
 
115
130
  export interface PluvRoomAddonResult {
@@ -121,39 +136,44 @@ export type PluvRoomDebug<TIO extends IOLike> = Id<{
121
136
  input: readonly (keyof InferIOInput<TIO>)[];
122
137
  }>;
123
138
 
124
- export type PluvRoomOptions<
125
- TIO extends IOLike,
126
- TPresence extends JsonObject = {},
127
- TStorage extends Record<string, CrdtType<any, any>> = {},
128
- > = {
129
- addons?: readonly PluvRoomAddon<TIO, TPresence, TStorage>[];
130
- debug?: boolean | PluvRoomDebug<TIO>;
131
- onAuthorizationFail?: (error: Error) => void;
132
- } & Omit<CrdtManagerOptions<TStorage>, "encodedState"> &
133
- UsersManagerConfig<TPresence>;
134
-
135
139
  export type RoomConfig<
136
140
  TIO extends IOLike,
141
+ TMetadata extends JsonObject = {},
137
142
  TPresence extends JsonObject = {},
138
143
  TStorage extends Record<string, CrdtType<any, any>> = {},
139
- > = RoomEndpoints<TIO> & PluvRoomOptions<TIO, TPresence, TStorage>;
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
+ >;
140
156
 
141
157
  export class PluvRoom<
142
158
  TIO extends IOLike,
159
+ TMetadata extends JsonObject = {},
143
160
  TPresence extends JsonObject = {},
144
161
  TStorage extends Record<string, CrdtType<any, any>> = {},
162
+ TEvents extends PluvRouterEventConfig<TIO, TPresence, TStorage> = {},
145
163
  > extends AbstractRoom<TIO, TPresence, TStorage> {
146
- readonly _endpoints: RoomEndpoints<TIO>;
164
+ readonly _endpoints: RoomEndpoints<TIO, TMetadata>;
147
165
 
148
166
  private _crdtManager: CrdtManager<TStorage>;
149
167
  private _crdtNotifier = new CrdtNotifier<TStorage>();
150
168
  private _debug: boolean | PluvRoomDebug<TIO>;
151
- private _eventNotifier = new EventNotifier<TIO>();
169
+ private _eventNotifier = new EventNotifier<MergeEvents<TEvents, TIO>>();
152
170
  private _intervals: IntervalIds = {
153
171
  heartbeat: null,
154
172
  };
155
173
  private _listeners: InternalListeners;
174
+ private _metadata: TMetadata = {} as TMetadata;
156
175
  private _otherNotifier = new OtherNotifier<TIO>();
176
+ private _router: PluvRouter<TIO, TPresence, TStorage, TEvents>;
157
177
  private _state: WebSocketState<TIO> = {
158
178
  authorization: {
159
179
  token: null,
@@ -179,15 +199,17 @@ export class PluvRoom<
179
199
  private _wsListeners: WebSocketListeners | null = null;
180
200
  private _usersManager: UsersManager<TIO, TPresence>;
181
201
 
182
- constructor(room: string, options: RoomConfig<TIO, TPresence, TStorage>) {
202
+ constructor(room: string, options: RoomConfig<TIO, TMetadata, TPresence, TStorage, TEvents>) {
183
203
  const {
184
204
  addons = [],
185
205
  authEndpoint,
186
206
  debug = false,
187
207
  initialPresence,
188
208
  initialStorage,
209
+ metadata,
189
210
  onAuthorizationFail,
190
211
  presence,
212
+ router,
191
213
  wsEndpoint,
192
214
  } = options;
193
215
 
@@ -200,11 +222,11 @@ export class PluvRoom<
200
222
  ...addon({ room: this }),
201
223
  };
202
224
 
203
- this._storageStore = storage;
204
-
205
225
  this._debug = debug;
226
+ this._endpoints = { authEndpoint, wsEndpoint } as RoomEndpoints<TIO, TMetadata>;
227
+ this._storageStore = storage;
206
228
 
207
- this._endpoints = { authEndpoint, wsEndpoint } as RoomEndpoints<TIO>;
229
+ if (!!metadata) this._metadata = metadata;
208
230
 
209
231
  this._listeners = {
210
232
  onAuthorizationFail: (error) => {
@@ -215,6 +237,7 @@ export class PluvRoom<
215
237
  },
216
238
  };
217
239
 
240
+ this._router = router ?? (new PluvRouter({}) as PluvRouter<TIO, TPresence, TStorage, TEvents>);
218
241
  this._usersManager = new UsersManager<TIO, TPresence>({ initialPresence, presence });
219
242
  this._crdtManager = new CrdtManager<TStorage>({ initialStorage });
220
243
  }
@@ -228,23 +251,62 @@ export class PluvRoom<
228
251
  }
229
252
 
230
253
  public broadcast = new Proxy(
231
- <TEvent extends keyof InferIOInput<TIO>>(event: TEvent, data: Id<InferIOInput<TIO>[TEvent]>): void => {
254
+ async <TEvent extends keyof InferIOInput<MergeEvents<TEvents, TIO>>>(
255
+ event: TEvent,
256
+ data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[TEvent]>,
257
+ ): Promise<void> => {
232
258
  if (!this._state.webSocket) return;
233
259
  if (this._state.connection.state !== ConnectionState.Open) return;
234
260
 
235
261
  const type = event.toString();
236
262
 
237
- this._sendMessage({ data, type });
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
+ });
238
297
  },
239
298
  {
240
299
  get(fn, prop) {
241
- return (data: Id<InferIOInput<TIO>[any]>): void => {
242
- return fn(prop, data);
300
+ return async (data: Id<InferIOInput<MergeEvents<TEvents, TIO>>[any]>): Promise<void> => {
301
+ return await fn(prop, data);
243
302
  };
244
303
  },
245
304
  },
246
- ) as (<TEvent extends keyof InferIOInput<TIO>>(event: TEvent, data: Id<InferIOInput<TIO>[TEvent]>) => void) & {
247
- [event in keyof InferIOInput<TIO>]: (data: Id<InferIOInput<TIO>[event]>) => void;
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>;
248
310
  };
249
311
 
250
312
  public canRedo = (): boolean => {
@@ -332,20 +394,23 @@ export class PluvRoom<
332
394
  }
333
395
 
334
396
  public event = new Proxy(
335
- <TEvent extends keyof InferIOOutput<TIO>>(
397
+ <TEvent extends keyof InferIOOutput<MergeEvents<TEvents, TIO>>>(
336
398
  event: TEvent,
337
- callback: EventNotifierSubscriptionCallback<TIO, TEvent>,
399
+ callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, any>,
338
400
  ): (() => void) => this._eventNotifier.subscribe(event, callback),
339
401
  {
340
402
  get(fn, prop) {
341
- return (callback: EventNotifierSubscriptionCallback<TIO, any>): (() => void) => fn(prop, callback);
403
+ return (callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, any>): (() => void) =>
404
+ fn(prop as any, callback);
342
405
  },
343
406
  },
344
- ) as (<TEvent extends keyof InferIOOutput<TIO>>(
407
+ ) as (<TEvent extends keyof InferIOOutput<MergeEvents<TEvents, TIO>>>(
345
408
  event: TEvent,
346
- callback: EventNotifierSubscriptionCallback<TIO, TEvent>,
409
+ callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, TEvent>,
347
410
  ) => () => void) & {
348
- [event in keyof InferIOOutput<TIO>]: (callback: EventNotifierSubscriptionCallback<TIO, event>) => () => void;
411
+ [event in keyof InferIOOutput<MergeEvents<TEvents, TIO>>]: (
412
+ callback: EventNotifierSubscriptionCallback<MergeEvents<TEvents, TIO>, any>,
413
+ ) => () => void;
349
414
  };
350
415
 
351
416
  public getConnection = (): WebSocketConnection => {
@@ -459,7 +524,10 @@ export class PluvRoom<
459
524
  this._stateNotifier.subjects["my-presence"].next(myPresence);
460
525
  !!myself && this._stateNotifier.subjects["myself"].next(myself);
461
526
 
462
- this.broadcast("$UPDATE_PRESENCE" as keyof InferIOInput<TIO>, { presence: newPresence } as any);
527
+ this.broadcast(
528
+ "$UPDATE_PRESENCE" as keyof InferIOInput<MergeEvents<TEvents, TIO>>,
529
+ { presence: newPresence } as any,
530
+ );
463
531
  };
464
532
 
465
533
  private async _addToStorageStore(update: string): Promise<void> {
@@ -607,9 +675,9 @@ export class PluvRoom<
607
675
  );
608
676
 
609
677
  private _getAddon = (
610
- addons: readonly PluvRoomAddon<TIO, TPresence, TStorage>[],
611
- ): PluvRoomAddon<TIO, TPresence, TStorage> => {
612
- return addons.reduce<PluvRoomAddon<TIO, TPresence, TStorage>>(
678
+ addons: readonly PluvRoomAddon<TIO, TMetadata, TPresence, TStorage>[],
679
+ ): PluvRoomAddon<TIO, TMetadata, TPresence, TStorage> => {
680
+ return addons.reduce<PluvRoomAddon<TIO, TMetadata, TPresence, TStorage>>(
613
681
  (acc, addon) => () => ({
614
682
  ...acc({ room: this }),
615
683
  ...addon({ room: this }),
@@ -635,7 +703,7 @@ export class PluvRoom<
635
703
  };
636
704
  }
637
705
 
638
- const result = this._endpoints.authEndpoint(room);
706
+ const result = this._endpoints.authEndpoint({ metadata: this._metadata, room });
639
707
 
640
708
  return typeof result === "string" ? { url: result, options: {} } : result;
641
709
  }
@@ -667,7 +735,7 @@ export class PluvRoom<
667
735
  case "string":
668
736
  return this._endpoints.wsEndpoint;
669
737
  default:
670
- return this._endpoints.wsEndpoint(room);
738
+ return this._endpoints.wsEndpoint({ metadata: this._metadata, room });
671
739
  }
672
740
  }
673
741
 
@@ -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
+ }
@@ -1,7 +1,15 @@
1
- import type { IOLike } from "@pluv/types";
1
+ import type { CrdtType } from "@pluv/crdt";
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 = <TIO extends IOLike = IOLike>(options: PluvClientOptions<TIO>): PluvClient<TIO> => {
6
- return new PluvClient<TIO>(options);
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);
7
15
  };
package/src/enums.ts ADDED
@@ -0,0 +1,7 @@
1
+ export enum ConnectionState {
2
+ Closed = "Closed",
3
+ Connecting = "Connecting",
4
+ Open = "Open",
5
+ Unavailable = "Unavailable",
6
+ Untouched = "Untouched",
7
+ }
package/src/index.ts CHANGED
@@ -2,12 +2,14 @@ export type { CrdtType, InferCrdtJson } from "@pluv/crdt";
2
2
  export type { BaseUser, EventMessage, EventRecord, IOEventMessage } from "@pluv/types";
3
3
  export { AbstractRoom } from "./AbstractRoom";
4
4
  export { AbstractStorageStore } from "./AbstractStorageStore";
5
+ export { createClient } from "./createClient";
6
+ export { ConnectionState } from "./enums";
5
7
  export { MockedRoom } from "./MockedRoom";
6
8
  export type { MockedRoomConfig, MockedRoomEvents } from "./MockedRoom";
7
9
  export { PluvClient } from "./PluvClient";
8
- export type { PluvClientOptions } from "./PluvClient";
10
+ export type { CreateRoomOptions, PluvClientOptions } from "./PluvClient";
11
+ export type { PluvProcedureConfig } from "./PluvProcedure";
9
12
  export { PluvRoom } from "./PluvRoom";
10
13
  export type { PluvRoomAddon, PluvRoomAddonInput, PluvRoomAddonResult, PluvRoomDebug } from "./PluvRoom";
11
- export { createClient } from "./createClient";
12
- export { ConnectionState } from "./types";
14
+ export type { MergeEvents, PluvRouter, PluvRouterEventConfig } from "./PluvRouter";
13
15
  export type { UserInfo, WebSocketConnection, WebSocketState } from "./types";
package/src/types.ts CHANGED
@@ -1,22 +1,58 @@
1
- import type { Id, InferIOAuthorize, InferIOAuthorizeUser, IOLike, JsonObject } from "@pluv/types";
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 enum ConnectionState {
9
- Closed = "Closed",
10
- Connecting = "Connecting",
11
- Open = "Open",
12
- Unavailable = "Unavailable",
13
- Untouched = "Untouched",
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;
@@ -1 +1,2 @@
1
1
  export { debounce } from "./debounce";
2
+ export { identity } from "./identity";