@kokimoki/app 1.3.2 → 1.4.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.
@@ -3,8 +3,12 @@ import type { KokimokiClientEvents } from "./types/events";
3
3
  import type { Upload } from "./types/upload";
4
4
  import type { Paginated } from "./types/common";
5
5
  import { KokimokiTransaction } from "./kokimoki-transaction";
6
- import type { KokimokiStore } from "./kokimoki-store";
6
+ import { KokimokiStore } from "./kokimoki-store";
7
7
  import type { KokimokiSchema as S } from "./kokimoki-schema";
8
+ import type { RoomSubscriptionMode } from "./room-subscription-mode";
9
+ import { KokimokiQueue } from "./kokimoki-queue";
10
+ import { KokimokiAwareness } from "./kokimoki-awareness";
11
+ import { KokimokiReqRes } from "./kokimoki-req-res";
8
12
  declare const KokimokiClient_base: new () => TypedEmitter<KokimokiClientEvents>;
9
13
  export declare class KokimokiClient<ClientContextT = any> extends KokimokiClient_base {
10
14
  readonly host: string;
@@ -69,7 +73,13 @@ export declare class KokimokiClient<ClientContextT = any> extends KokimokiClient
69
73
  private sendUnsubscribeReq;
70
74
  join<T extends S.Generic<unknown>>(store: KokimokiStore<T>): Promise<void>;
71
75
  leave<T extends S.Generic<unknown>>(store: KokimokiStore<T>): Promise<void>;
72
- transact(handler: (t: KokimokiTransaction) => void): Promise<void>;
76
+ transact(handler: (t: KokimokiTransaction) => void | Promise<void>): Promise<void>;
73
77
  close(): Promise<void>;
78
+ getRoomHash<T extends S.Generic<unknown>>(store: KokimokiStore<T>): number;
79
+ /** Initializers */
80
+ store<T extends S.Generic<unknown>>(name: string, schema: T, autoJoin?: boolean): KokimokiStore<T, T["defaultValue"]>;
81
+ queue<T extends S.Generic<unknown>>(name: string, schema: T, mode: RoomSubscriptionMode, autoJoin?: boolean): KokimokiQueue<T>;
82
+ awareness<T extends S.Generic<unknown>>(name: string, dataSchema: T, initialData?: T["defaultValue"], autoJoin?: boolean): KokimokiAwareness<T>;
83
+ reqRes<Req extends S.Generic<unknown>, Res extends S.Generic<unknown>>(serviceName: string, reqSchema: Req, resSchema: Res, handleRequest: (payload: Req["defaultValue"]) => Promise<Res["defaultValue"]>): KokimokiReqRes<Req, Res>;
74
84
  }
75
85
  export {};
@@ -2,10 +2,14 @@ import EventEmitter from "events";
2
2
  import { KOKIMOKI_APP_VERSION } from "./version";
3
3
  import * as Y from "yjs";
4
4
  import { KokimokiTransaction } from "./kokimoki-transaction";
5
+ import { KokimokiStore } from "./kokimoki-store";
5
6
  import { WsMessageType } from "./ws-message-type";
6
7
  import { WsMessageWriter } from "./ws-message-writer";
7
8
  import { WsMessageReader } from "./ws-message-reader";
8
9
  import { RoomSubscription } from "./room-subscription";
10
+ import { KokimokiQueue } from "./kokimoki-queue";
11
+ import { KokimokiAwareness } from "./kokimoki-awareness";
12
+ import { KokimokiReqRes } from "./kokimoki-req-res";
9
13
  export class KokimokiClient extends EventEmitter {
10
14
  host;
11
15
  appId;
@@ -419,6 +423,7 @@ export class KokimokiClient extends EventEmitter {
419
423
  await this.sendUnsubscribeReq(subscription.roomHash);
420
424
  this._subscriptionsByName.delete(store.roomName);
421
425
  this._subscriptionsByHash.delete(subscription.roomHash);
426
+ subscription.close();
422
427
  // Trigger onLeave event
423
428
  store.onLeave(this);
424
429
  }
@@ -428,7 +433,7 @@ export class KokimokiClient extends EventEmitter {
428
433
  throw new Error("Client not connected");
429
434
  }
430
435
  const transaction = new KokimokiTransaction(this);
431
- handler(transaction);
436
+ await handler(transaction);
432
437
  const { updates, consumedMessages } = await transaction.getUpdates();
433
438
  if (!updates.length) {
434
439
  return;
@@ -473,15 +478,6 @@ export class KokimokiClient extends EventEmitter {
473
478
  throw e;
474
479
  }
475
480
  });
476
- /* // Reset doc in Write-only mode
477
- for (const { roomName, update } of updates) {
478
- const mode = this._subscriptions.get(roomName);
479
-
480
- if (mode === RoomSubscriptionMode.Write) {
481
- // @ts-ignore
482
- // this._stores.get(this._roomHashes.get(roomName)!)!.doc = new Y.Doc();
483
- }
484
- } */
485
481
  }
486
482
  async close() {
487
483
  this._autoReconnect = false;
@@ -492,4 +488,47 @@ export class KokimokiClient extends EventEmitter {
492
488
  clearInterval(this._pingInterval);
493
489
  }
494
490
  }
491
+ // Get room hash value for store
492
+ getRoomHash(store) {
493
+ const subscription = this._subscriptionsByName.get(store.roomName);
494
+ if (!subscription) {
495
+ throw new Error(`Store "${store.roomName}" not joined`);
496
+ }
497
+ return subscription.roomHash;
498
+ }
499
+ /** Initializers */
500
+ // store
501
+ store(name, schema, autoJoin = true) {
502
+ const store = new KokimokiStore(name, schema);
503
+ if (autoJoin) {
504
+ this.join(store)
505
+ .then(() => { })
506
+ .catch(() => { });
507
+ }
508
+ return store;
509
+ }
510
+ // queue
511
+ queue(name, schema, mode, autoJoin = true) {
512
+ const queue = new KokimokiQueue(name, schema, mode);
513
+ if (autoJoin) {
514
+ this.join(queue)
515
+ .then(() => { })
516
+ .catch(() => { });
517
+ }
518
+ return queue;
519
+ }
520
+ // awareness
521
+ awareness(name, dataSchema, initialData = dataSchema.defaultValue, autoJoin = true) {
522
+ const awareness = new KokimokiAwareness(name, dataSchema, initialData);
523
+ if (autoJoin) {
524
+ this.join(awareness)
525
+ .then(() => { })
526
+ .catch(() => { });
527
+ }
528
+ return awareness;
529
+ }
530
+ // req-res
531
+ reqRes(serviceName, reqSchema, resSchema, handleRequest) {
532
+ return new KokimokiReqRes(this, serviceName, reqSchema, resSchema, handleRequest);
533
+ }
495
534
  }
@@ -2,10 +2,7 @@ import { KokimokiStore } from "./kokimoki-store";
2
2
  import { KokimokiSchema as S } from "./kokimoki-schema";
3
3
  import type TypedEventEmitter from "typed-emitter";
4
4
  import type { RoomSubscriptionMode } from "./room-subscription-mode";
5
- export declare class KokimokiQueue<Req extends S.Generic<unknown>> extends KokimokiStore<S.Dict<S.Struct<{
6
- timestamp: S.Number;
7
- payload: Req;
8
- }>>> {
5
+ export declare class KokimokiQueue<Req extends S.Generic<unknown>> extends KokimokiStore<S.Dict<Req>> {
9
6
  readonly payloadSchema: Req;
10
7
  private _emitter;
11
8
  readonly on: <E extends "messages">(event: E, listener: {
@@ -7,10 +7,7 @@ export class KokimokiQueue extends KokimokiStore {
7
7
  on = this._emitter.on.bind(this._emitter);
8
8
  off = this._emitter.off.bind(this._emitter);
9
9
  constructor(roomName, payloadSchema, mode) {
10
- super(`/q/${roomName}`, S.dict(S.struct({
11
- timestamp: S.number(),
12
- payload: payloadSchema,
13
- })), mode);
10
+ super(`/q/${roomName}`, S.dict(payloadSchema), mode);
14
11
  this.payloadSchema = payloadSchema;
15
12
  const emittedMessageIds = new Set();
16
13
  this.doc.on("update", () => {
@@ -20,7 +17,7 @@ export class KokimokiQueue extends KokimokiStore {
20
17
  continue;
21
18
  }
22
19
  emittedMessageIds.add(id);
23
- newMessages.push({ id, payload: this.proxy[id].payload });
20
+ newMessages.push({ id, payload: this.proxy[id] });
24
21
  }
25
22
  if (newMessages.length > 0) {
26
23
  this._emitter.emit("messages", newMessages);
@@ -0,0 +1,20 @@
1
+ import type { KokimokiClient } from "./kokimoki-client";
2
+ import { KokimokiSchema as S } from "./kokimoki-schema";
3
+ export declare class KokimokiReqRes<Req extends S.Generic<unknown>, Res extends S.Generic<unknown>> {
4
+ private kmClient;
5
+ readonly serviceName: string;
6
+ readonly reqSchema: Req;
7
+ readonly resSchema: Res;
8
+ private handleRequest;
9
+ private _reqQueueSchema;
10
+ private _resQueueSchema;
11
+ private _reqQueues;
12
+ private _resQueues;
13
+ private _reqPromises;
14
+ private _getReqQueue;
15
+ private _getResQueue;
16
+ constructor(kmClient: KokimokiClient, serviceName: string, reqSchema: Req, resSchema: Res, handleRequest: (payload: Req["defaultValue"]) => Promise<Res["defaultValue"]>);
17
+ private handleRequests;
18
+ private handleResponses;
19
+ send(toClientId: string, payload: Req["defaultValue"], timeout?: number): Promise<Res["defaultValue"]>;
20
+ }
@@ -0,0 +1,143 @@
1
+ import { KokimokiSchema as S } from "./kokimoki-schema";
2
+ import { RoomSubscriptionMode } from "./room-subscription-mode";
3
+ export class KokimokiReqRes {
4
+ kmClient;
5
+ serviceName;
6
+ reqSchema;
7
+ resSchema;
8
+ handleRequest;
9
+ _reqQueueSchema;
10
+ _resQueueSchema;
11
+ // Request queues are linked to client ids
12
+ _reqQueues = {};
13
+ // Response queues are linked to specific connection ids
14
+ _resQueues = {};
15
+ _reqPromises = {};
16
+ async _getReqQueue(clientId, mode = RoomSubscriptionMode.Write) {
17
+ if (!this._reqQueues.hasOwnProperty(clientId)) {
18
+ this._reqQueues[clientId] = this.kmClient.queue(`/r/${clientId}/req/${this.serviceName}`, this._reqQueueSchema, mode, false);
19
+ await this.kmClient.join(this._reqQueues[clientId]);
20
+ }
21
+ return this._reqQueues[clientId];
22
+ }
23
+ async _getResQueue(connectionId, mode = RoomSubscriptionMode.Write) {
24
+ if (!this._resQueues.hasOwnProperty(connectionId)) {
25
+ this._resQueues[connectionId] = this.kmClient.queue(`/r/${connectionId}/res/${this.serviceName}`, this._resQueueSchema, mode, false);
26
+ await this.kmClient.join(this._resQueues[connectionId]);
27
+ }
28
+ return this._resQueues[connectionId];
29
+ }
30
+ constructor(kmClient, serviceName, reqSchema, resSchema, handleRequest) {
31
+ this.kmClient = kmClient;
32
+ this.serviceName = serviceName;
33
+ this.reqSchema = reqSchema;
34
+ this.resSchema = resSchema;
35
+ this.handleRequest = handleRequest;
36
+ this._reqQueueSchema = S.struct({
37
+ connectionId: S.string(),
38
+ // roomHash: S.number(),
39
+ payload: reqSchema,
40
+ });
41
+ this._resQueueSchema = S.struct({
42
+ reqId: S.string(),
43
+ error: S.optional(S.string()),
44
+ payload: resSchema,
45
+ });
46
+ // Set up queues on connect
47
+ kmClient.on("connected", async () => {
48
+ // Handle responses to self
49
+ const resQueue = await this._getResQueue(kmClient.connectionId, RoomSubscriptionMode.ReadWrite);
50
+ let handlingResponses = false;
51
+ let rehandleResponses = false;
52
+ resQueue.subscribe(async (messages) => {
53
+ // console.log("RES", Object.keys(messages));
54
+ if (handlingResponses) {
55
+ rehandleResponses = true;
56
+ return;
57
+ }
58
+ handlingResponses = true;
59
+ await this.handleResponses(resQueue, messages);
60
+ if (rehandleResponses) {
61
+ rehandleResponses = false;
62
+ await this.handleResponses(resQueue, messages);
63
+ }
64
+ handlingResponses = false;
65
+ });
66
+ // Handle requests to self
67
+ const reqQueue = await this._getReqQueue(kmClient.id, RoomSubscriptionMode.ReadWrite);
68
+ let handlingRequests = false;
69
+ let rehandleRequests = false;
70
+ reqQueue.subscribe(async (messages) => {
71
+ // console.log("REQ", Object.keys(messages));
72
+ if (handlingRequests) {
73
+ rehandleRequests = true;
74
+ return;
75
+ }
76
+ handlingRequests = true;
77
+ await this.handleRequests(reqQueue, messages);
78
+ if (rehandleRequests) {
79
+ rehandleRequests = false;
80
+ await this.handleRequests(reqQueue, messages);
81
+ }
82
+ handlingRequests = false;
83
+ });
84
+ });
85
+ }
86
+ async handleRequests(reqQueue, messages) {
87
+ // Send responses
88
+ await this.kmClient.transact(async (t) => {
89
+ for (const reqId in messages) {
90
+ const { connectionId, payload } = messages[reqId];
91
+ const resQueue = await this._getResQueue(connectionId);
92
+ t.consumeMessage(reqQueue, reqId);
93
+ try {
94
+ t.queueMessage(resQueue, {
95
+ reqId,
96
+ error: undefined,
97
+ payload: await this.handleRequest(payload),
98
+ });
99
+ }
100
+ catch (e) {
101
+ t.queueMessage(resQueue, {
102
+ reqId,
103
+ error: e.toString(),
104
+ payload: null,
105
+ });
106
+ }
107
+ }
108
+ });
109
+ }
110
+ async handleResponses(resQueue, messages) {
111
+ // Handle responses
112
+ await this.kmClient.transact(async (t) => {
113
+ for (const resId in messages) {
114
+ const { reqId, error, payload } = messages[resId];
115
+ const promise = this._reqPromises[reqId];
116
+ t.consumeMessage(resQueue, resId);
117
+ if (promise) {
118
+ if (error) {
119
+ promise.reject(error);
120
+ }
121
+ else {
122
+ promise.resolve(payload);
123
+ }
124
+ delete this._reqPromises[reqId];
125
+ }
126
+ }
127
+ });
128
+ }
129
+ async send(toClientId, payload, timeout) {
130
+ const toReqQueue = await this._getReqQueue(toClientId);
131
+ // Create a promise for the response
132
+ return await new Promise(async (resolve, reject) => {
133
+ await this.kmClient.transact((t) => {
134
+ const reqId = t.queueMessage(toReqQueue, {
135
+ connectionId: this.kmClient.connectionId,
136
+ payload,
137
+ // roomHash,
138
+ });
139
+ this._reqPromises[reqId] = { resolve, reject };
140
+ });
141
+ });
142
+ }
143
+ }
@@ -6,6 +6,7 @@ export declare class KokimokiTransaction {
6
6
  private _clones;
7
7
  private _updates;
8
8
  private _consumeMessagesInRooms;
9
+ private _queueMessageCounter;
9
10
  constructor(kmClient: KokimokiClient<any>);
10
11
  private _parseTarget;
11
12
  private _parsePath;
@@ -14,7 +15,7 @@ export declare class KokimokiTransaction {
14
15
  set<T>(target: T, value: T): void;
15
16
  delete<T>(target: T): void;
16
17
  push<T>(target: T[], value: T): void;
17
- queueMessage<T>(queue: KokimokiQueue<S.Generic<T>>, payload: T): void;
18
+ queueMessage<T>(queue: KokimokiQueue<S.Generic<T>>, payload: T): string;
18
19
  consumeMessage<T>(queue: KokimokiQueue<S.Generic<T>>, messageId: string): void;
19
20
  getUpdates(): Promise<{
20
21
  updates: {
@@ -6,6 +6,7 @@ export class KokimokiTransaction {
6
6
  _clones = new Map();
7
7
  _updates = new Map();
8
8
  _consumeMessagesInRooms = new Set();
9
+ _queueMessageCounter = 0;
9
10
  constructor(kmClient) {
10
11
  this.kmClient = kmClient;
11
12
  }
@@ -73,11 +74,10 @@ export class KokimokiTransaction {
73
74
  obj[key].push(value);
74
75
  }
75
76
  queueMessage(queue, payload) {
76
- const messageId = Math.random().toString(36);
77
- this.set(queue.root[messageId], {
78
- timestamp: this.kmClient.serverTimestamp(),
79
- payload,
80
- });
77
+ const messageId = `${this.kmClient.serverTimestamp()}/${this
78
+ ._queueMessageCounter++}/${Math.random().toString(36).slice(2)}`;
79
+ this.set(queue.root[messageId], payload);
80
+ return messageId;
81
81
  }
82
82
  consumeMessage(queue, messageId) {
83
83
  if (!queue.proxy[messageId]) {
@@ -227,10 +227,7 @@ declare class KokimokiStore<T extends KokimokiSchema.Generic<unknown>, Subscribe
227
227
  onLeave(client: KokimokiClient): Promise<void>;
228
228
  }
229
229
 
230
- declare class KokimokiQueue<Req extends KokimokiSchema.Generic<unknown>> extends KokimokiStore<KokimokiSchema.Dict<KokimokiSchema.Struct<{
231
- timestamp: KokimokiSchema.Number;
232
- payload: Req;
233
- }>>> {
230
+ declare class KokimokiQueue<Req extends KokimokiSchema.Generic<unknown>> extends KokimokiStore<KokimokiSchema.Dict<Req>> {
234
231
  readonly payloadSchema: Req;
235
232
  private _emitter;
236
233
  readonly on: <E extends "messages">(event: E, listener: {
@@ -263,6 +260,7 @@ declare class KokimokiTransaction {
263
260
  private _clones;
264
261
  private _updates;
265
262
  private _consumeMessagesInRooms;
263
+ private _queueMessageCounter;
266
264
  constructor(kmClient: KokimokiClient<any>);
267
265
  private _parseTarget;
268
266
  private _parsePath;
@@ -271,7 +269,7 @@ declare class KokimokiTransaction {
271
269
  set<T>(target: T, value: T): void;
272
270
  delete<T>(target: T): void;
273
271
  push<T>(target: T[], value: T): void;
274
- queueMessage<T>(queue: KokimokiQueue<KokimokiSchema.Generic<T>>, payload: T): void;
272
+ queueMessage<T>(queue: KokimokiQueue<KokimokiSchema.Generic<T>>, payload: T): string;
275
273
  consumeMessage<T>(queue: KokimokiQueue<KokimokiSchema.Generic<T>>, messageId: string): void;
276
274
  getUpdates(): Promise<{
277
275
  updates: {
@@ -282,6 +280,44 @@ declare class KokimokiTransaction {
282
280
  }>;
283
281
  }
284
282
 
283
+ declare class KokimokiAwareness<Data extends KokimokiSchema.Generic<unknown>> extends KokimokiStore<KokimokiSchema.Dict<KokimokiSchema.Struct<{
284
+ clientId: KokimokiSchema.String;
285
+ lastPing: KokimokiSchema.Number;
286
+ data: Data;
287
+ }>>> {
288
+ readonly dataSchema: Data;
289
+ private _data;
290
+ private _pingInterval;
291
+ private _kmClients;
292
+ constructor(roomName: string, dataSchema: Data, _data: Data["defaultValue"], mode?: RoomSubscriptionMode, pingTimeout?: number);
293
+ onJoin(client: KokimokiClient<any>): Promise<void>;
294
+ onBeforeLeave(client: KokimokiClient<any>): Promise<void>;
295
+ onLeave(client: KokimokiClient<any>): Promise<void>;
296
+ getClients(): {
297
+ [clientId: string]: Data["defaultValue"];
298
+ };
299
+ setData(data: Data["defaultValue"]): Promise<void>;
300
+ }
301
+
302
+ declare class KokimokiReqRes<Req extends KokimokiSchema.Generic<unknown>, Res extends KokimokiSchema.Generic<unknown>> {
303
+ private kmClient;
304
+ readonly serviceName: string;
305
+ readonly reqSchema: Req;
306
+ readonly resSchema: Res;
307
+ private handleRequest;
308
+ private _reqQueueSchema;
309
+ private _resQueueSchema;
310
+ private _reqQueues;
311
+ private _resQueues;
312
+ private _reqPromises;
313
+ private _getReqQueue;
314
+ private _getResQueue;
315
+ constructor(kmClient: KokimokiClient, serviceName: string, reqSchema: Req, resSchema: Res, handleRequest: (payload: Req["defaultValue"]) => Promise<Res["defaultValue"]>);
316
+ private handleRequests;
317
+ private handleResponses;
318
+ send(toClientId: string, payload: Req["defaultValue"], timeout?: number): Promise<Res["defaultValue"]>;
319
+ }
320
+
285
321
  declare const KokimokiClient_base: new () => TypedEventEmitter<KokimokiClientEvents>;
286
322
  declare class KokimokiClient<ClientContextT = any> extends KokimokiClient_base {
287
323
  readonly host: string;
@@ -346,8 +382,14 @@ declare class KokimokiClient<ClientContextT = any> extends KokimokiClient_base {
346
382
  private sendUnsubscribeReq;
347
383
  join<T extends KokimokiSchema.Generic<unknown>>(store: KokimokiStore<T>): Promise<void>;
348
384
  leave<T extends KokimokiSchema.Generic<unknown>>(store: KokimokiStore<T>): Promise<void>;
349
- transact(handler: (t: KokimokiTransaction) => void): Promise<void>;
385
+ transact(handler: (t: KokimokiTransaction) => void | Promise<void>): Promise<void>;
350
386
  close(): Promise<void>;
387
+ getRoomHash<T extends KokimokiSchema.Generic<unknown>>(store: KokimokiStore<T>): number;
388
+ /** Initializers */
389
+ store<T extends KokimokiSchema.Generic<unknown>>(name: string, schema: T, autoJoin?: boolean): KokimokiStore<T, T["defaultValue"]>;
390
+ queue<T extends KokimokiSchema.Generic<unknown>>(name: string, schema: T, mode: RoomSubscriptionMode, autoJoin?: boolean): KokimokiQueue<T>;
391
+ awareness<T extends KokimokiSchema.Generic<unknown>>(name: string, dataSchema: T, initialData?: T["defaultValue"], autoJoin?: boolean): KokimokiAwareness<T>;
392
+ reqRes<Req extends KokimokiSchema.Generic<unknown>, Res extends KokimokiSchema.Generic<unknown>>(serviceName: string, reqSchema: Req, resSchema: Res, handleRequest: (payload: Req["defaultValue"]) => Promise<Res["defaultValue"]>): KokimokiReqRes<Req, Res>;
351
393
  }
352
394
 
353
395
  declare class RoomSubscription {
@@ -364,23 +406,4 @@ declare class RoomSubscription {
364
406
  close(): void;
365
407
  }
366
408
 
367
- declare class KokimokiAwareness<Data extends KokimokiSchema.Generic<unknown>> extends KokimokiStore<KokimokiSchema.Dict<KokimokiSchema.Struct<{
368
- clientId: KokimokiSchema.String;
369
- lastPing: KokimokiSchema.Number;
370
- data: Data;
371
- }>>> {
372
- readonly dataSchema: Data;
373
- private _data;
374
- private _pingInterval;
375
- private _kmClients;
376
- constructor(roomName: string, dataSchema: Data, _data: Data["defaultValue"], mode?: RoomSubscriptionMode, pingTimeout?: number);
377
- onJoin(client: KokimokiClient<any>): Promise<void>;
378
- onBeforeLeave(client: KokimokiClient<any>): Promise<void>;
379
- onLeave(client: KokimokiClient<any>): Promise<void>;
380
- getClients(): {
381
- [clientId: string]: Data["defaultValue"];
382
- };
383
- setData(data: Data["defaultValue"]): Promise<void>;
384
- }
385
-
386
409
  export { BooleanField, ConstField, EnumField, Field, type FieldOptions, FloatField, Form, FormArray, FormGroup, ImageField, IntegerField, KokimokiAwareness, KokimokiClient, type KokimokiClientEvents, KokimokiQueue, KokimokiSchema, KokimokiStore, type Paginated, RoomSubscription, RoomSubscriptionMode, TextField, type Upload };