@kokimoki/app 1.15.2 → 1.16.1

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.
@@ -1,22 +1,20 @@
1
1
  import { KokimokiStore } from "./kokimoki-store";
2
2
  import { RoomSubscriptionMode } from "./room-subscription-mode";
3
3
  import type { KokimokiClient } from "./kokimoki-client";
4
+ import type { Snapshot } from "valtio/vanilla";
4
5
  export declare class KokimokiAwareness<DataT> extends KokimokiStore<{
5
6
  [connectionId: string]: {
6
7
  clientId: string;
7
- lastPing: number;
8
8
  data: DataT;
9
9
  };
10
10
  }> {
11
11
  private _data;
12
- private _pingInterval;
13
12
  private _kmClients;
14
- constructor(roomName: string, _data: DataT, mode?: RoomSubscriptionMode, pingTimeout?: number);
13
+ constructor(roomName: string, _data: DataT, mode?: RoomSubscriptionMode);
15
14
  onJoin(client: KokimokiClient<any>): Promise<void>;
16
- onBeforeLeave(client: KokimokiClient<any>): Promise<void>;
17
15
  onLeave(client: KokimokiClient<any>): Promise<void>;
18
16
  getClients(): {
19
- [clientId: string]: DataT;
17
+ [clientId: string]: Snapshot<DataT>;
20
18
  };
21
19
  setData(data: DataT): Promise<void>;
22
20
  }
@@ -2,64 +2,29 @@ import { KokimokiStore } from "./kokimoki-store";
2
2
  import { RoomSubscriptionMode } from "./room-subscription-mode";
3
3
  export class KokimokiAwareness extends KokimokiStore {
4
4
  _data;
5
- _pingInterval = null;
6
5
  _kmClients = new Set();
7
- constructor(roomName, _data, mode = RoomSubscriptionMode.ReadWrite, pingTimeout = 3000) {
6
+ constructor(roomName, _data, mode = RoomSubscriptionMode.ReadWrite) {
8
7
  super(`/a/${roomName}`, {}, mode);
9
8
  this._data = _data;
10
- this._pingInterval = setInterval(async () => {
11
- const kmClients = Array.from(this._kmClients);
12
- await Promise.all(kmClients.map(async (client) => {
13
- try {
14
- await client.transact([this], ([state]) => {
15
- const timestamp = client.serverTimestamp();
16
- // Update self
17
- if (state[client.connectionId]) {
18
- state[client.connectionId].lastPing = timestamp;
19
- }
20
- else {
21
- state[client.connectionId] = {
22
- clientId: client.id,
23
- lastPing: timestamp,
24
- data: this._data,
25
- };
26
- }
27
- // Delete clients that haven't pinged in a while
28
- for (const connectionId in state) {
29
- const { lastPing } = state[connectionId];
30
- if (!lastPing || timestamp - lastPing > pingTimeout * 2) {
31
- delete state[connectionId];
32
- }
33
- }
34
- });
35
- }
36
- catch (e) { }
37
- }));
38
- }, pingTimeout);
39
9
  }
40
10
  async onJoin(client) {
41
11
  this._kmClients.add(client);
42
12
  await client.transact([this], ([state]) => {
43
13
  state[client.connectionId] = {
44
14
  clientId: client.id,
45
- lastPing: client.serverTimestamp(),
46
15
  data: this._data,
47
16
  };
48
17
  });
49
18
  }
50
- async onBeforeLeave(client) {
51
- await client.transact([this], ([state]) => {
52
- delete state[client.connectionId];
53
- });
54
- }
55
19
  async onLeave(client) {
56
20
  this._kmClients.delete(client);
57
21
  }
58
22
  getClients() {
59
23
  const clients = {};
60
- for (const connectionId in this.proxy) {
61
- clients[this.proxy[connectionId].clientId] =
62
- this.proxy[connectionId].data;
24
+ const connections = this.get();
25
+ for (const connectionId in connections) {
26
+ clients[connections[connectionId].clientId] =
27
+ connections[connectionId].data;
63
28
  }
64
29
  return clients;
65
30
  }
@@ -5,7 +5,10 @@ import type { Paginated } from "./types/common";
5
5
  import { KokimokiStore } from "./kokimoki-store";
6
6
  import { KokimokiAwareness } from "./kokimoki-awareness";
7
7
  import { KokimokiLocalStore } from "./kokimoki-local-store";
8
- type StoreValue<S> = S extends KokimokiStore<infer U> ? U : never;
8
+ type Mutable<T> = {
9
+ -readonly [K in keyof T]: T[K] extends object ? Mutable<T[K]> : T[K];
10
+ };
11
+ type StoreValue<S> = S extends KokimokiStore<infer U> ? Mutable<U> : never;
9
12
  declare const KokimokiClient_base: new () => TypedEmitter<KokimokiClientEvents>;
10
13
  export declare class KokimokiClient<ClientContextT = any> extends KokimokiClient_base {
11
14
  readonly host: string;
@@ -71,16 +74,16 @@ export declare class KokimokiClient<ClientContextT = any> extends KokimokiClient
71
74
  exposeScriptingContext(context: any): Promise<void>;
72
75
  private sendSubscribeReq;
73
76
  private sendUnsubscribeReq;
74
- join<T>(store: KokimokiStore<T>): Promise<void>;
75
- leave<T>(store: KokimokiStore<T>): Promise<void>;
77
+ join<T extends object>(store: KokimokiStore<T>): Promise<void>;
78
+ leave<T extends object>(store: KokimokiStore<T>): Promise<void>;
76
79
  transact<TStores extends readonly KokimokiStore<any>[], ReturnT = void>(stores: [...TStores], handler: (proxies: {
77
80
  [K in keyof TStores]: StoreValue<TStores[K]>;
78
81
  }) => ReturnT | Promise<ReturnT>): Promise<ReturnT>;
79
82
  close(): Promise<void>;
80
- getRoomHash<T>(store: KokimokiStore<T>): number;
83
+ getRoomHash<T extends object>(store: KokimokiStore<T>): number;
81
84
  /** Initializers */
82
- store<T>(name: string, defaultState: T, autoJoin?: boolean): KokimokiStore<T>;
83
- localStore<T>(name: string, defaultState: T): KokimokiLocalStore<T>;
85
+ store<T extends object>(name: string, defaultState: T, autoJoin?: boolean): KokimokiStore<T>;
86
+ localStore<T extends object>(name: string, defaultState: T): KokimokiLocalStore<T>;
84
87
  awareness<T>(name: string, initialData: T, autoJoin?: boolean): KokimokiAwareness<T>;
85
88
  /**
86
89
  * Add a new entry to a leaderboard
@@ -461,7 +461,7 @@ export class KokimokiClient extends EventEmitter {
461
461
  this._subscriptionsByHash.set(res.roomHash, subscription);
462
462
  await subscription.applyInitialResponse(res.roomHash, res.initialUpdate);
463
463
  // Trigger onJoin event
464
- store.onJoin(this);
464
+ await store.onJoin(this);
465
465
  }
466
466
  }
467
467
  async leave(store) {
@@ -718,7 +718,12 @@ export class KokimokiClient extends EventEmitter {
718
718
  const res = await fetch(`${this._apiUrl}/ai/chat`, {
719
719
  method: "POST",
720
720
  headers: this.apiHeaders,
721
- body: JSON.stringify({ systemPrompt, userPrompt, temperature, maxTokens }),
721
+ body: JSON.stringify({
722
+ systemPrompt,
723
+ userPrompt,
724
+ temperature,
725
+ maxTokens,
726
+ }),
722
727
  });
723
728
  if (!res.ok) {
724
729
  throw await res.json();
@@ -1,5 +1,5 @@
1
1
  import { KokimokiStore } from "./kokimoki-store";
2
- export declare class KokimokiLocalStore<T> extends KokimokiStore<T> {
2
+ export declare class KokimokiLocalStore<T extends object> extends KokimokiStore<T> {
3
3
  private readonly localRoomName;
4
4
  private _stateKey?;
5
5
  private get stateKey();
@@ -1,17 +1,21 @@
1
1
  import * as Y from "yjs";
2
+ import { Snapshot } from "valtio/vanilla";
2
3
  import { RoomSubscriptionMode } from "./room-subscription-mode";
3
4
  import type { KokimokiClient } from "./kokimoki-client";
4
- export declare class KokimokiStore<T> {
5
+ export declare class KokimokiStore<T extends object> {
5
6
  readonly roomName: string;
6
7
  readonly defaultValue: T;
7
8
  readonly mode: RoomSubscriptionMode;
8
9
  readonly doc: Y.Doc;
9
10
  readonly proxy: T;
10
11
  readonly docRoot: Y.Map<unknown>;
12
+ readonly connections: {
13
+ clientIds: Set<string>;
14
+ };
11
15
  constructor(roomName: string, defaultValue: T, mode?: RoomSubscriptionMode);
12
- get(): T;
13
- subscribe(set: (value: T) => void): () => void;
14
- onJoin(client: KokimokiClient): Promise<void>;
16
+ get(): Snapshot<T>;
17
+ subscribe(set: (value: Snapshot<T>) => void): () => void;
18
+ onJoin(client: KokimokiClient<any>): Promise<void>;
15
19
  onBeforeLeave(client: KokimokiClient): Promise<void>;
16
20
  onLeave(client: KokimokiClient): Promise<void>;
17
21
  }
@@ -1,5 +1,5 @@
1
1
  import * as Y from "yjs";
2
- import { proxy as yjsProxy } from "valtio/vanilla";
2
+ import { snapshot, proxy, subscribe } from "valtio/vanilla";
3
3
  import { bind as yjsBind } from "valtio-yjs";
4
4
  import { RoomSubscriptionMode } from "./room-subscription-mode";
5
5
  export class KokimokiStore {
@@ -9,49 +9,72 @@ export class KokimokiStore {
9
9
  doc;
10
10
  proxy;
11
11
  docRoot;
12
+ connections;
12
13
  constructor(roomName, defaultValue, mode = RoomSubscriptionMode.ReadWrite) {
13
14
  this.roomName = roomName;
14
15
  this.defaultValue = defaultValue;
15
16
  this.mode = mode;
17
+ // "_connections" is a reserved key for tracking connections
18
+ if ("_connections" in defaultValue) {
19
+ throw new Error(`"_connections" is a reserved key in KokimokiStore`);
20
+ }
16
21
  // Construct Y doc
17
22
  this.doc = new Y.Doc();
18
23
  this.docRoot = this.doc.getMap("root");
19
24
  // Construct proxy object
20
- this.proxy = yjsProxy(); //T["defaultValue"];
25
+ this.proxy = proxy();
21
26
  // @ts-ignore
22
27
  yjsBind(this.proxy, this.docRoot);
23
- // // Create root proxy
24
- // const store = this;
25
- // // @ts-ignore
26
- // this.root = new DeepProxy(this.proxy, {
27
- // get() {
28
- // return this.nest(function () {});
29
- // },
30
- // apply() {
31
- // let obj: any = store.proxy;
32
- // for (let i = 0; i < this.path.length - 1; i++) {
33
- // obj = obj[this.path[i]];
34
- // }
35
- // return {
36
- // roomName: store.roomName,
37
- // doc: store.doc,
38
- // path: this.path,
39
- // obj,
40
- // key: this.path[this.path.length - 1],
41
- // };
42
- // },
43
- // });
28
+ // Construct connections proxy
29
+ this.connections = proxy({ clientIds: new Set() });
30
+ // Update connections.clientIds whenever _connections changes
31
+ let prevConnections = new Set();
32
+ subscribe(this.proxy, () => {
33
+ // @ts-ignore
34
+ const newConnections = new Set(
35
+ // @ts-ignore
36
+ Object.values(this.proxy._connections || {}));
37
+ // Update only if there are changes
38
+ let changed = false;
39
+ if (newConnections.size !== prevConnections.size) {
40
+ changed = true;
41
+ }
42
+ if (!changed) {
43
+ for (const id of newConnections) {
44
+ if (!prevConnections.has(id)) {
45
+ changed = true;
46
+ break;
47
+ }
48
+ }
49
+ }
50
+ if (!changed) {
51
+ for (const id of prevConnections) {
52
+ if (!newConnections.has(id)) {
53
+ changed = true;
54
+ break;
55
+ }
56
+ }
57
+ }
58
+ if (changed) {
59
+ this.connections.clientIds = newConnections;
60
+ prevConnections = new Set(newConnections);
61
+ }
62
+ });
44
63
  }
45
64
  get() {
46
- return this.proxy;
65
+ return snapshot(this.proxy);
47
66
  }
48
67
  subscribe(set) {
49
- const handler = () => set(this.proxy);
68
+ const handler = () => set(this.get());
50
69
  this.doc.on("update", handler);
51
- set(this.proxy);
70
+ set(this.get());
52
71
  return () => this.doc.off("update", handler);
53
72
  }
54
- async onJoin(client) { }
73
+ async onJoin(client) {
74
+ await client.transact([this], ([state]) => {
75
+ state._connections[client.connectionId] = client.id;
76
+ });
77
+ }
55
78
  async onBeforeLeave(client) { }
56
79
  async onLeave(client) { }
57
80
  }
@@ -1,5 +1,5 @@
1
1
  import { KokimokiStore } from "./kokimoki-store";
2
- export declare class KokimokiTransaction<T extends unknown[]> {
2
+ export declare class KokimokiTransaction<T extends object[]> {
3
3
  private _updates;
4
4
  private _consumeMessagesInRooms;
5
5
  private _proxies;
@@ -1,5 +1,6 @@
1
1
  import TypedEmitter from 'typed-emitter';
2
2
  import * as Y from 'yjs';
3
+ import { Snapshot } from 'valtio/vanilla';
3
4
 
4
5
  interface Paginated<T> {
5
6
  items: T[];
@@ -30,17 +31,20 @@ declare enum RoomSubscriptionMode {
30
31
  ReadWrite = "b"
31
32
  }
32
33
 
33
- declare class KokimokiStore<T> {
34
+ declare class KokimokiStore<T extends object> {
34
35
  readonly roomName: string;
35
36
  readonly defaultValue: T;
36
37
  readonly mode: RoomSubscriptionMode;
37
38
  readonly doc: Y.Doc;
38
39
  readonly proxy: T;
39
40
  readonly docRoot: Y.Map<unknown>;
41
+ readonly connections: {
42
+ clientIds: Set<string>;
43
+ };
40
44
  constructor(roomName: string, defaultValue: T, mode?: RoomSubscriptionMode);
41
- get(): T;
42
- subscribe(set: (value: T) => void): () => void;
43
- onJoin(client: KokimokiClient): Promise<void>;
45
+ get(): Snapshot<T>;
46
+ subscribe(set: (value: Snapshot<T>) => void): () => void;
47
+ onJoin(client: KokimokiClient<any>): Promise<void>;
44
48
  onBeforeLeave(client: KokimokiClient): Promise<void>;
45
49
  onLeave(client: KokimokiClient): Promise<void>;
46
50
  }
@@ -48,24 +52,21 @@ declare class KokimokiStore<T> {
48
52
  declare class KokimokiAwareness<DataT> extends KokimokiStore<{
49
53
  [connectionId: string]: {
50
54
  clientId: string;
51
- lastPing: number;
52
55
  data: DataT;
53
56
  };
54
57
  }> {
55
58
  private _data;
56
- private _pingInterval;
57
59
  private _kmClients;
58
- constructor(roomName: string, _data: DataT, mode?: RoomSubscriptionMode, pingTimeout?: number);
60
+ constructor(roomName: string, _data: DataT, mode?: RoomSubscriptionMode);
59
61
  onJoin(client: KokimokiClient<any>): Promise<void>;
60
- onBeforeLeave(client: KokimokiClient<any>): Promise<void>;
61
62
  onLeave(client: KokimokiClient<any>): Promise<void>;
62
63
  getClients(): {
63
- [clientId: string]: DataT;
64
+ [clientId: string]: Snapshot<DataT>;
64
65
  };
65
66
  setData(data: DataT): Promise<void>;
66
67
  }
67
68
 
68
- declare class KokimokiLocalStore<T> extends KokimokiStore<T> {
69
+ declare class KokimokiLocalStore<T extends object> extends KokimokiStore<T> {
69
70
  private readonly localRoomName;
70
71
  private _stateKey?;
71
72
  private get stateKey();
@@ -76,7 +77,10 @@ declare class KokimokiLocalStore<T> extends KokimokiStore<T> {
76
77
  };
77
78
  }
78
79
 
79
- type StoreValue<S> = S extends KokimokiStore<infer U> ? U : never;
80
+ type Mutable<T> = {
81
+ -readonly [K in keyof T]: T[K] extends object ? Mutable<T[K]> : T[K];
82
+ };
83
+ type StoreValue<S> = S extends KokimokiStore<infer U> ? Mutable<U> : never;
80
84
  declare const KokimokiClient_base: new () => TypedEmitter<KokimokiClientEvents>;
81
85
  declare class KokimokiClient<ClientContextT = any> extends KokimokiClient_base {
82
86
  readonly host: string;
@@ -142,16 +146,16 @@ declare class KokimokiClient<ClientContextT = any> extends KokimokiClient_base {
142
146
  exposeScriptingContext(context: any): Promise<void>;
143
147
  private sendSubscribeReq;
144
148
  private sendUnsubscribeReq;
145
- join<T>(store: KokimokiStore<T>): Promise<void>;
146
- leave<T>(store: KokimokiStore<T>): Promise<void>;
149
+ join<T extends object>(store: KokimokiStore<T>): Promise<void>;
150
+ leave<T extends object>(store: KokimokiStore<T>): Promise<void>;
147
151
  transact<TStores extends readonly KokimokiStore<any>[], ReturnT = void>(stores: [...TStores], handler: (proxies: {
148
152
  [K in keyof TStores]: StoreValue<TStores[K]>;
149
153
  }) => ReturnT | Promise<ReturnT>): Promise<ReturnT>;
150
154
  close(): Promise<void>;
151
- getRoomHash<T>(store: KokimokiStore<T>): number;
155
+ getRoomHash<T extends object>(store: KokimokiStore<T>): number;
152
156
  /** Initializers */
153
- store<T>(name: string, defaultState: T, autoJoin?: boolean): KokimokiStore<T>;
154
- localStore<T>(name: string, defaultState: T): KokimokiLocalStore<T>;
157
+ store<T extends object>(name: string, defaultState: T, autoJoin?: boolean): KokimokiStore<T>;
158
+ localStore<T extends object>(name: string, defaultState: T): KokimokiLocalStore<T>;
155
159
  awareness<T>(name: string, initialData: T, autoJoin?: boolean): KokimokiAwareness<T>;
156
160
  /**
157
161
  * Add a new entry to a leaderboard