@kokimoki/app 2.1.1 → 3.0.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.
Files changed (37) hide show
  1. package/README.md +72 -0
  2. package/dist/core/kokimoki-client.d.ts +75 -15
  3. package/dist/core/kokimoki-client.js +137 -22
  4. package/dist/index.d.ts +6 -1
  5. package/dist/index.js +4 -0
  6. package/dist/kokimoki.min.d.ts +320 -2
  7. package/dist/kokimoki.min.js +1916 -115
  8. package/dist/kokimoki.min.js.map +1 -1
  9. package/dist/protocol/ws-message/reader.d.ts +1 -1
  10. package/dist/services/index.d.ts +1 -0
  11. package/dist/services/index.js +1 -0
  12. package/dist/services/kokimoki-ai.d.ts +185 -124
  13. package/dist/services/kokimoki-ai.js +201 -111
  14. package/dist/services/kokimoki-i18n.d.ts +259 -0
  15. package/dist/services/kokimoki-i18n.js +325 -0
  16. package/dist/stores/kokimoki-local-store.d.ts +1 -1
  17. package/dist/types/common.d.ts +9 -0
  18. package/dist/types/env.d.ts +36 -0
  19. package/dist/types/env.js +1 -0
  20. package/dist/types/index.d.ts +1 -0
  21. package/dist/types/index.js +1 -0
  22. package/dist/utils/kokimoki-client.d.ts +31 -0
  23. package/dist/utils/kokimoki-client.js +38 -0
  24. package/dist/utils/kokimoki-dev.d.ts +30 -0
  25. package/dist/utils/kokimoki-dev.js +75 -0
  26. package/dist/utils/kokimoki-env.d.ts +20 -0
  27. package/dist/utils/kokimoki-env.js +30 -0
  28. package/dist/version.d.ts +1 -1
  29. package/dist/version.js +1 -1
  30. package/docs/kokimoki-ai.instructions.md +316 -0
  31. package/docs/kokimoki-dynamic-stores.instructions.md +439 -0
  32. package/docs/kokimoki-i18n.instructions.md +285 -0
  33. package/docs/kokimoki-leaderboard.instructions.md +189 -0
  34. package/docs/kokimoki-sdk.instructions.md +221 -0
  35. package/docs/kokimoki-storage.instructions.md +162 -0
  36. package/llms.txt +43 -0
  37. package/package.json +9 -13
package/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # @kokimoki/app
2
+
3
+ The core SDK for building real-time multiplayer games with Kokimoki.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @kokimoki/app
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { getKmClient } from "@kokimoki/app";
15
+
16
+ const kmClient = getKmClient();
17
+
18
+ // Create a synchronized store
19
+ const gameStore = kmClient.store("game", {
20
+ players: {},
21
+ status: "waiting",
22
+ });
23
+
24
+ // Update state with transactions
25
+ await kmClient.transact([gameStore], ([state]) => {
26
+ state.players[kmClient.id] = { name: "Player 1", score: 0 };
27
+ });
28
+ ```
29
+
30
+ ## Features
31
+
32
+ - **Real-time State Sync** - Synchronized stores across all connected clients
33
+ - **Dynamic Stores** - Room-based state isolation for teams, chat, breakout rooms
34
+ - **AI Services** - Text and image generation
35
+ - **Storage** - File uploads to CDN
36
+ - **i18n** - Internationalization with AI-powered translation
37
+ - **Leaderboards** - Player rankings and scores
38
+
39
+ ## Documentation
40
+
41
+ See the [docs](./docs/) folder for detailed instructions:
42
+
43
+ | File | Description |
44
+ | ----------------------------------------------------------------------------------------- | --------------------------------------------- |
45
+ | [kokimoki-sdk.instructions.md](./docs/kokimoki-sdk.instructions.md) | Core SDK usage (client, stores, transactions) |
46
+ | [kokimoki-dynamic-stores.instructions.md](./docs/kokimoki-dynamic-stores.instructions.md) | Room-based state isolation |
47
+ | [kokimoki-ai.instructions.md](./docs/kokimoki-ai.instructions.md) | AI text and image generation |
48
+ | [kokimoki-storage.instructions.md](./docs/kokimoki-storage.instructions.md) | File storage and CDN uploads |
49
+ | [kokimoki-i18n.instructions.md](./docs/kokimoki-i18n.instructions.md) | Internationalization |
50
+ | [kokimoki-leaderboard.instructions.md](./docs/kokimoki-leaderboard.instructions.md) | Player rankings |
51
+
52
+ ## Usage with React
53
+
54
+ ```tsx
55
+ import { useSnapshot } from "valtio";
56
+ import { getKmClient } from "@kokimoki/app";
57
+
58
+ const kmClient = getKmClient();
59
+ const gameStore = kmClient.store("game", { count: 0 });
60
+
61
+ function Counter() {
62
+ const { count } = useSnapshot(gameStore.proxy);
63
+
64
+ const increment = () => {
65
+ kmClient.transact([gameStore], ([state]) => {
66
+ state.count++;
67
+ });
68
+ };
69
+
70
+ return <button onClick={increment}>Count: {count}</button>;
71
+ }
72
+ ```
@@ -1,14 +1,12 @@
1
1
  import type TypedEmitter from "typed-emitter";
2
- import { KokimokiAiService, KokimokiLeaderboardService, KokimokiStorageService } from "../services";
2
+ import { KokimokiAiService, KokimokiI18nService, KokimokiLeaderboardService, KokimokiStorageService } from "../services";
3
3
  import { KokimokiLocalStore, KokimokiStore } from "../stores";
4
- import type { KokimokiClientEvents } from "../types";
4
+ import type { KokimokiClientEvents, KokimokiEnv } from "../types";
5
5
  type Mutable<T> = {
6
6
  -readonly [K in keyof T]: T[K] extends object ? Mutable<T[K]> : T[K];
7
7
  };
8
8
  type StoreValue<S> = S extends KokimokiStore<infer U> ? Mutable<U> : never;
9
- declare const KokimokiClient_base: {
10
- new (): TypedEmitter<KokimokiClientEvents>;
11
- };
9
+ declare const KokimokiClient_base: new () => TypedEmitter<KokimokiClientEvents>;
12
10
  /**
13
11
  * Kokimoki Client - Real-time Collaborative Game Development SDK
14
12
  *
@@ -30,12 +28,8 @@ declare const KokimokiClient_base: {
30
28
  * ```typescript
31
29
  * import { KokimokiClient } from '@kokimoki/app';
32
30
  *
33
- * // Initialize the client
34
- * const kmClient = new KokimokiClient(
35
- * 'your-host.kokimoki.com',
36
- * 'your-app-id',
37
- * 'optional-access-code'
38
- * );
31
+ * // Initialize the client (uses environment config from @kokimoki/kit)
32
+ * const kmClient = new KokimokiClient();
39
33
  *
40
34
  * // Connect to the server
41
35
  * await kmClient.connect();
@@ -163,9 +157,6 @@ declare const KokimokiClient_base: {
163
157
  * ```
164
158
  */
165
159
  export declare class KokimokiClient<ClientContextT = any> extends KokimokiClient_base {
166
- readonly host: string;
167
- readonly appId: string;
168
- readonly code: string;
169
160
  private _wsUrl;
170
161
  private _apiUrl;
171
162
  private _id?;
@@ -189,9 +180,30 @@ export declare class KokimokiClient<ClientContextT = any> extends KokimokiClient
189
180
  private _clientTokenKey;
190
181
  private _editorContext;
191
182
  private _ai?;
183
+ private _i18n?;
192
184
  private _storage?;
193
185
  private _leaderboard?;
194
- constructor(host: string, appId: string, code?: string);
186
+ /**
187
+ * Dev mode config - set when running in dev frame with ?key= param
188
+ */
189
+ private _devModeConfig?;
190
+ /**
191
+ * Environment configuration
192
+ */
193
+ private _env;
194
+ /**
195
+ * The WebSocket server host.
196
+ */
197
+ readonly host: string;
198
+ /**
199
+ * The application identifier.
200
+ */
201
+ readonly appId: string;
202
+ /**
203
+ * The deploy code slug.
204
+ */
205
+ readonly code: string;
206
+ constructor(env?: KokimokiEnv);
195
207
  get id(): string;
196
208
  get connectionId(): string;
197
209
  get token(): string;
@@ -207,6 +219,32 @@ export declare class KokimokiClient<ClientContextT = any> extends KokimokiClient
207
219
  * Indicates whether the client is running in editor/development mode.
208
220
  */
209
221
  get isEditor(): boolean;
222
+ /**
223
+ * Generates a link for joining the app via URL or QR code.
224
+ *
225
+ * In dev mode, the link includes the code and context as URL parameters
226
+ * for local testing. In production, it generates a clean path-based URL.
227
+ *
228
+ * @param code - The join code (e.g., playerCode or presenterCode from clientContext)
229
+ * @param context - The client context to embed in the link (used in dev mode only)
230
+ * @returns The generated URL string
231
+ *
232
+ * @example
233
+ * ```typescript
234
+ * // Generate a player join link
235
+ * const playerLink = kmClient.generateLink(
236
+ * kmClient.clientContext.playerCode,
237
+ * { mode: 'player' }
238
+ * );
239
+ *
240
+ * // Generate a presenter link with player code
241
+ * const presenterLink = kmClient.generateLink(
242
+ * kmClient.clientContext.presenterCode,
243
+ * { mode: 'presenter', playerCode: kmClient.clientContext.playerCode }
244
+ * );
245
+ * ```
246
+ */
247
+ generateLink(code: string, context: object): string;
210
248
  /**
211
249
  * Establishes a connection to the Kokimoki server.
212
250
  *
@@ -288,6 +326,24 @@ export declare class KokimokiClient<ClientContextT = any> extends KokimokiClient
288
326
  * Disables automatic reconnection, closes the WebSocket, and clears all intervals.
289
327
  */
290
328
  close(): Promise<void>;
329
+ /**
330
+ * Waits for all subscriptions to be fully joined and notifies the loading screen.
331
+ *
332
+ * This should be called before rendering the app. It will:
333
+ * 1. Wait for all stores to complete initial synchronization
334
+ * 2. Post a 'km:ready' message to trigger loading screen fade
335
+ * 3. Post a 'km:ready' message to the parent window (for dev frame coordination)
336
+ *
337
+ * @param timeout - Maximum time to wait for subscriptions in milliseconds (default: 5000ms).
338
+ * @returns A promise that resolves when ready to render.
339
+ *
340
+ * @example
341
+ * ```ts
342
+ * await kmClient.waitForReady();
343
+ * renderApp(<App />);
344
+ * ```
345
+ */
346
+ waitForReady(timeout?: number): Promise<void>;
291
347
  /**
292
348
  * Gets the internal room hash identifier for a store.
293
349
  *
@@ -349,6 +405,10 @@ export declare class KokimokiClient<ClientContextT = any> extends KokimokiClient
349
405
  * Access AI capabilities including text generation, structured JSON output, and image modification.
350
406
  */
351
407
  get ai(): KokimokiAiService;
408
+ /**
409
+ * Access i18n URL resolution and translation loading utilities.
410
+ */
411
+ get i18n(): KokimokiI18nService;
352
412
  /**
353
413
  * Access file upload and management for media files, images, and user-generated content.
354
414
  */
@@ -1,8 +1,10 @@
1
1
  import EventEmitter from "events";
2
2
  import * as Y from "yjs";
3
3
  import { WsMessageReader, WsMessageType, WsMessageWriter, } from "../protocol/ws-message";
4
- import { KokimokiAiService, KokimokiLeaderboardService, KokimokiStorageService, } from "../services";
4
+ import { KokimokiAiService, KokimokiI18nService, KokimokiLeaderboardService, KokimokiStorageService, } from "../services";
5
5
  import { KokimokiLocalStore, KokimokiStore, KokimokiTransaction, } from "../stores";
6
+ import { initDevMode } from "../utils/kokimoki-dev";
7
+ import { getKmEnv } from "../utils/kokimoki-env";
6
8
  import { KOKIMOKI_APP_VERSION } from "../version";
7
9
  import { RoomSubscription } from "./room-subscription";
8
10
  /**
@@ -26,12 +28,8 @@ import { RoomSubscription } from "./room-subscription";
26
28
  * ```typescript
27
29
  * import { KokimokiClient } from '@kokimoki/app';
28
30
  *
29
- * // Initialize the client
30
- * const kmClient = new KokimokiClient(
31
- * 'your-host.kokimoki.com',
32
- * 'your-app-id',
33
- * 'optional-access-code'
34
- * );
31
+ * // Initialize the client (uses environment config from @kokimoki/kit)
32
+ * const kmClient = new KokimokiClient();
35
33
  *
36
34
  * // Connect to the server
37
35
  * await kmClient.connect();
@@ -159,9 +157,6 @@ import { RoomSubscription } from "./room-subscription";
159
157
  * ```
160
158
  */
161
159
  export class KokimokiClient extends EventEmitter {
162
- host;
163
- appId;
164
- code;
165
160
  _wsUrl;
166
161
  _apiUrl;
167
162
  _id;
@@ -185,19 +180,49 @@ export class KokimokiClient extends EventEmitter {
185
180
  _clientTokenKey = "KM_TOKEN";
186
181
  _editorContext;
187
182
  _ai;
183
+ _i18n;
188
184
  _storage;
189
185
  _leaderboard;
190
- constructor(host, appId, code = "") {
186
+ /**
187
+ * Dev mode config - set when running in dev frame with ?key= param
188
+ */
189
+ _devModeConfig;
190
+ /**
191
+ * Environment configuration
192
+ */
193
+ _env;
194
+ /**
195
+ * The WebSocket server host.
196
+ */
197
+ host;
198
+ /**
199
+ * The application identifier.
200
+ */
201
+ appId;
202
+ /**
203
+ * The deploy code slug.
204
+ */
205
+ code;
206
+ constructor(env = getKmEnv()) {
191
207
  super();
192
- this.host = host;
193
- this.appId = appId;
194
- this.code = code;
208
+ this._env = env;
209
+ this.host = env.host;
210
+ this.appId = env.appId;
211
+ this.code = env.code ?? "";
212
+ // Initialize dev mode from URL params if running in dev environment
213
+ if (env.dev) {
214
+ this._devModeConfig = initDevMode();
215
+ if (this._devModeConfig) {
216
+ this._clientTokenKey = this._devModeConfig.tokenKey;
217
+ }
218
+ }
195
219
  // Set up the URLs
196
220
  const secure = this.host.indexOf(":") === -1;
197
221
  this._wsUrl = `ws${secure ? "s" : ""}://${this.host}`;
198
222
  this._apiUrl = `http${secure ? "s" : ""}://${this.host}`;
199
223
  // Initialize modules
200
224
  this._ai = new KokimokiAiService(this);
225
+ this._i18n = new KokimokiI18nService(this);
201
226
  this._storage = new KokimokiStorageService(this);
202
227
  this._leaderboard = new KokimokiLeaderboardService(this);
203
228
  // Set up ping interval
@@ -278,6 +303,37 @@ export class KokimokiClient extends EventEmitter {
278
303
  get isEditor() {
279
304
  return !!this._editorContext;
280
305
  }
306
+ /**
307
+ * Generates a link for joining the app via URL or QR code.
308
+ *
309
+ * In dev mode, the link includes the code and context as URL parameters
310
+ * for local testing. In production, it generates a clean path-based URL.
311
+ *
312
+ * @param code - The join code (e.g., playerCode or presenterCode from clientContext)
313
+ * @param context - The client context to embed in the link (used in dev mode only)
314
+ * @returns The generated URL string
315
+ *
316
+ * @example
317
+ * ```typescript
318
+ * // Generate a player join link
319
+ * const playerLink = kmClient.generateLink(
320
+ * kmClient.clientContext.playerCode,
321
+ * { mode: 'player' }
322
+ * );
323
+ *
324
+ * // Generate a presenter link with player code
325
+ * const presenterLink = kmClient.generateLink(
326
+ * kmClient.clientContext.presenterCode,
327
+ * { mode: 'presenter', playerCode: kmClient.clientContext.playerCode }
328
+ * );
329
+ * ```
330
+ */
331
+ generateLink(code, context) {
332
+ if (this._env.dev) {
333
+ return `${window.location.origin}?key=${code}&context=${btoa(JSON.stringify(context))}`;
334
+ }
335
+ return `${window.location.origin}/${code}`;
336
+ }
281
337
  /**
282
338
  * Establishes a connection to the Kokimoki server.
283
339
  *
@@ -291,14 +347,11 @@ export class KokimokiClient extends EventEmitter {
291
347
  if (this._connectPromise) {
292
348
  return await this._connectPromise;
293
349
  }
294
- // Detect devtools
295
- if (window.parent && window.self !== window.parent) {
350
+ // Detect devtools (editor)
351
+ if (!this._devModeConfig &&
352
+ window.parent &&
353
+ window.self !== window.parent) {
296
354
  await new Promise((resolve) => {
297
- /* // Wait up to 500ms for parent to respond
298
- const timeout = setTimeout(() => {
299
- window.removeEventListener("message", onMessage);
300
- resolve();
301
- }, 500); */
302
355
  // Listen for parent response
303
356
  const onMessage = (e) => {
304
357
  // clearTimeout(timeout);
@@ -399,7 +452,20 @@ export class KokimokiClient extends EventEmitter {
399
452
  this._id = message.clientId;
400
453
  this._connectionId = message.id;
401
454
  this._token = message.appToken;
402
- this._clientContext = message.clientContext;
455
+ // Context priority: dev mode URL params > test mode env > server context
456
+ if (this._devModeConfig?.context !== undefined) {
457
+ this._clientContext = this._devModeConfig.context;
458
+ }
459
+ else if (this._env.test && this._env.clientContext) {
460
+ this._clientContext = JSON.parse(this._env.clientContext);
461
+ }
462
+ else {
463
+ this._clientContext = message.clientContext;
464
+ }
465
+ // Notify parent window in test mode (editor)
466
+ if (this._env.test) {
467
+ setTimeout(() => window.postMessage({ clientKey: "test" }, "*"));
468
+ }
403
469
  // Set up the auth headers
404
470
  this._apiHeaders = new Headers({
405
471
  Authorization: `Bearer ${this.token}`,
@@ -707,6 +773,46 @@ export class KokimokiClient extends EventEmitter {
707
773
  clearInterval(this._pingInterval);
708
774
  }
709
775
  }
776
+ /**
777
+ * Waits for all subscriptions to be fully joined and notifies the loading screen.
778
+ *
779
+ * This should be called before rendering the app. It will:
780
+ * 1. Wait for all stores to complete initial synchronization
781
+ * 2. Post a 'km:ready' message to trigger loading screen fade
782
+ * 3. Post a 'km:ready' message to the parent window (for dev frame coordination)
783
+ *
784
+ * @param timeout - Maximum time to wait for subscriptions in milliseconds (default: 5000ms).
785
+ * @returns A promise that resolves when ready to render.
786
+ *
787
+ * @example
788
+ * ```ts
789
+ * await kmClient.waitForReady();
790
+ * renderApp(<App />);
791
+ * ```
792
+ */
793
+ async waitForReady(timeout = 5000) {
794
+ const checkInterval = 10;
795
+ const maxChecks = Math.ceil(timeout / checkInterval);
796
+ await new Promise((resolve) => {
797
+ let checks = 0;
798
+ const intervalId = setInterval(() => {
799
+ let allJoined = true;
800
+ for (const subscription of this._subscriptionsByName.values()) {
801
+ if (!subscription.joined) {
802
+ allJoined = false;
803
+ break;
804
+ }
805
+ }
806
+ if (allJoined || ++checks >= maxChecks) {
807
+ clearInterval(intervalId);
808
+ resolve();
809
+ }
810
+ }, checkInterval);
811
+ });
812
+ // Post to both parent (for dev frame) and self (for loading screen)
813
+ window.parent.postMessage("km:ready", "*");
814
+ window.postMessage("km:ready", "*");
815
+ }
710
816
  /**
711
817
  * Gets the internal room hash identifier for a store.
712
818
  *
@@ -798,6 +904,15 @@ export class KokimokiClient extends EventEmitter {
798
904
  }
799
905
  return this._ai;
800
906
  }
907
+ /**
908
+ * Access i18n URL resolution and translation loading utilities.
909
+ */
910
+ get i18n() {
911
+ if (!this._i18n) {
912
+ throw new Error("I18n client not initialized");
913
+ }
914
+ return this._i18n;
915
+ }
801
916
  /**
802
917
  * Access file upload and management for media files, images, and user-generated content.
803
918
  */
package/dist/index.d.ts CHANGED
@@ -1,4 +1,9 @@
1
1
  export { KokimokiClient } from "./core";
2
2
  export { KokimokiStore } from "./stores";
3
- export type { KokimokiClientEvents, Paginated, Upload } from "./types";
3
+ export type { KokimokiClientEvents, KokimokiEnv, Paginated, PollOptions, Upload, } from "./types";
4
+ export type { AllLanguagesStatus, I18nOptions, LanguageStatus, NamespaceStatus, RequestTranslationResult, TranslationStatus, } from "./services/kokimoki-i18n";
5
+ export type { AiJob, AiJobStatus, AiJobType } from "./services/kokimoki-ai";
6
+ export { getKmClient } from "./utils/kokimoki-client";
7
+ export { getKmEnv } from "./utils/kokimoki-env";
4
8
  export * from "./utils/valtio";
9
+ export { z, type ZodType } from "zod/v4";
package/dist/index.js CHANGED
@@ -1,4 +1,8 @@
1
1
  export { KokimokiClient } from "./core";
2
2
  export { KokimokiStore } from "./stores";
3
+ export { getKmClient } from "./utils/kokimoki-client";
4
+ export { getKmEnv } from "./utils/kokimoki-env";
3
5
  // Re-export Valtio utilities
4
6
  export * from "./utils/valtio";
7
+ // Re-export Zod for schema definitions
8
+ export { z } from "zod/v4";