@kokimoki/app 1.17.0 → 2.0.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.
Files changed (71) hide show
  1. package/dist/core/index.d.ts +3 -0
  2. package/dist/core/index.js +3 -0
  3. package/dist/core/kokimoki-client.d.ts +361 -0
  4. package/dist/core/kokimoki-client.js +819 -0
  5. package/dist/core/room-subscription-mode.d.ts +5 -0
  6. package/dist/core/room-subscription-mode.js +6 -0
  7. package/dist/core/room-subscription.d.ts +15 -0
  8. package/dist/core/room-subscription.js +53 -0
  9. package/dist/fields.d.ts +110 -0
  10. package/dist/fields.js +158 -0
  11. package/dist/index.d.ts +4 -8
  12. package/dist/index.js +4 -9
  13. package/dist/kokimoki-ai.d.ts +153 -0
  14. package/dist/kokimoki-ai.js +164 -0
  15. package/dist/kokimoki-awareness.d.ts +14 -13
  16. package/dist/kokimoki-awareness.js +41 -33
  17. package/dist/kokimoki-client-refactored.d.ts +80 -0
  18. package/dist/kokimoki-client-refactored.js +400 -0
  19. package/dist/kokimoki-client.d.ts +282 -76
  20. package/dist/kokimoki-client.js +295 -232
  21. package/dist/kokimoki-leaderboard.d.ts +175 -0
  22. package/dist/kokimoki-leaderboard.js +203 -0
  23. package/dist/kokimoki-schema.d.ts +113 -0
  24. package/dist/kokimoki-schema.js +162 -0
  25. package/dist/kokimoki-storage.d.ts +156 -0
  26. package/dist/kokimoki-storage.js +208 -0
  27. package/dist/kokimoki.min.d.ts +775 -110
  28. package/dist/kokimoki.min.js +4088 -2408
  29. package/dist/kokimoki.min.js.map +1 -1
  30. package/dist/llms.txt +657 -0
  31. package/dist/message-queue.d.ts +8 -0
  32. package/dist/message-queue.js +19 -0
  33. package/dist/protocol/ws-message/index.d.ts +3 -0
  34. package/dist/protocol/ws-message/index.js +3 -0
  35. package/dist/protocol/ws-message/reader.d.ts +11 -0
  36. package/dist/protocol/ws-message/reader.js +36 -0
  37. package/dist/protocol/ws-message/type.d.ts +11 -0
  38. package/dist/protocol/ws-message/type.js +12 -0
  39. package/dist/protocol/ws-message/writer.d.ts +9 -0
  40. package/dist/protocol/ws-message/writer.js +45 -0
  41. package/dist/services/index.d.ts +3 -0
  42. package/dist/services/index.js +3 -0
  43. package/dist/services/kokimoki-ai.d.ts +153 -0
  44. package/dist/services/kokimoki-ai.js +164 -0
  45. package/dist/services/kokimoki-leaderboard.d.ts +175 -0
  46. package/dist/services/kokimoki-leaderboard.js +203 -0
  47. package/dist/services/kokimoki-storage.d.ts +155 -0
  48. package/dist/services/kokimoki-storage.js +208 -0
  49. package/dist/stores/index.d.ts +3 -0
  50. package/dist/stores/index.js +3 -0
  51. package/dist/stores/kokimoki-local-store.d.ts +11 -0
  52. package/dist/stores/kokimoki-local-store.js +40 -0
  53. package/dist/stores/kokimoki-store.d.ts +22 -0
  54. package/dist/stores/kokimoki-store.js +117 -0
  55. package/dist/stores/kokimoki-transaction.d.ts +18 -0
  56. package/dist/stores/kokimoki-transaction.js +143 -0
  57. package/dist/synced-schema.d.ts +74 -0
  58. package/dist/synced-schema.js +83 -0
  59. package/dist/synced-store.d.ts +10 -0
  60. package/dist/synced-store.js +9 -0
  61. package/dist/synced-types.d.ts +47 -0
  62. package/dist/synced-types.js +67 -0
  63. package/dist/types/index.d.ts +3 -0
  64. package/dist/types/index.js +3 -0
  65. package/dist/utils/valtio.d.ts +7 -0
  66. package/dist/utils/valtio.js +6 -0
  67. package/dist/version.d.ts +1 -1
  68. package/dist/version.js +2 -1
  69. package/dist/ws-message-type copy.d.ts +6 -0
  70. package/dist/ws-message-type copy.js +7 -0
  71. package/package.json +4 -3
@@ -7,10 +7,162 @@ import { WsMessageType } from "./ws-message-type";
7
7
  import { WsMessageWriter } from "./ws-message-writer";
8
8
  import { WsMessageReader } from "./ws-message-reader";
9
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";
13
10
  import { KokimokiLocalStore } from "./kokimoki-local-store";
11
+ import { KokimokiAi } from "./kokimoki-ai";
12
+ import { KokimokiLeaderboard } from "./kokimoki-leaderboard";
13
+ /**
14
+ * Kokimoki Client - Real-time Collaborative Game Development SDK
15
+ *
16
+ * The main entry point for building multiplayer games and collaborative applications.
17
+ * Provides real-time state synchronization, AI integration, cloud storage, leaderboards,
18
+ * and more - all without complex backend setup.
19
+ *
20
+ * **Core Capabilities:**
21
+ * - **Real-time Stores**: Synchronized state with automatic conflict resolution (powered by Valtio + Y.js)
22
+ * - **Atomic Transactions**: Update multiple stores consistently with automatic batching
23
+ * - **AI Integration**: Built-in text generation, structured JSON output, and image modification
24
+ * - **Cloud Storage**: File uploads with CDN delivery and tag-based organization
25
+ * - **Leaderboards**: Efficient player ranking with database indexes and pagination
26
+ * - **Presence Tracking**: Real-time connection status for all players
27
+ * - **Time Sync**: Server-synchronized timestamps across all clients
28
+ * - **Webhooks**: Send data to external services for backend processing
29
+ *
30
+ * **Quick Start:**
31
+ * ```typescript
32
+ * import { KokimokiClient } from '@kokimoki/app';
33
+ *
34
+ * // Initialize the client
35
+ * const kmClient = new KokimokiClient(
36
+ * 'your-host.kokimoki.com',
37
+ * 'your-app-id',
38
+ * 'optional-access-code'
39
+ * );
40
+ *
41
+ * // Connect to the server
42
+ * await kmClient.connect();
43
+ *
44
+ * // Create a synchronized store
45
+ * interface GameState {
46
+ * players: Record<string, { name: string; score: number }>;
47
+ * round: number;
48
+ * }
49
+ *
50
+ * const gameStore = kmClient.store<GameState>('game', {
51
+ * players: {},
52
+ * round: 1
53
+ * });
54
+ *
55
+ * // Update state atomically
56
+ * await kmClient.transact([gameStore], ([game]) => {
57
+ * game.players[kmClient.id] = { name: 'Player 1', score: 0 };
58
+ * game.round = 2;
59
+ * });
60
+ *
61
+ * // Use in React components with Valtio
62
+ * import { useSnapshot } from 'valtio';
63
+ *
64
+ * function GameComponent() {
65
+ * const game = useSnapshot(gameStore.proxy);
66
+ * return <div>Round: {game.round}</div>;
67
+ * }
68
+ * ```
69
+ *
70
+ * **Key Features:**
71
+ *
72
+ * **1. Real-time State Management**
73
+ * - Create global stores shared across all players: `kmClient.store()`
74
+ * - Create local stores for client-side data: `kmClient.localStore()`
75
+ * - Automatic synchronization and conflict resolution
76
+ * - Use `useSnapshot()` from Valtio for reactive React components
77
+ *
78
+ * **2. Atomic Transactions**
79
+ * ```typescript
80
+ * // Update multiple stores atomically
81
+ * await kmClient.transact([playerStore, gameStore], ([player, game]) => {
82
+ * player.score += 10;
83
+ * game.lastUpdate = kmClient.serverTimestamp();
84
+ * });
85
+ * ```
86
+ *
87
+ * **3. AI Integration (No API keys required)**
88
+ * ```typescript
89
+ * // Generate text
90
+ * const story = await kmClient.ai.chat({
91
+ * model: 'gpt-4o',
92
+ * userPrompt: 'Write a quest description',
93
+ * temperature: 0.8
94
+ * });
95
+ *
96
+ * // Generate structured data
97
+ * interface Quest { title: string; reward: number; }
98
+ * const quest = await kmClient.ai.generateJson<Quest>({
99
+ * userPrompt: 'Create a level 5 quest'
100
+ * });
101
+ *
102
+ * // Modify images
103
+ * const modified = await kmClient.ai.modifyImage(url, 'Make it pixel art');
104
+ * ```
105
+ *
106
+ * **4. Cloud Storage**
107
+ * ```typescript
108
+ * // Upload files with tags
109
+ * const upload = await kmClient.storage.upload('avatar.jpg', blob, ['profile']);
110
+ *
111
+ * // Query uploads
112
+ * const images = await kmClient.storage.listUploads({
113
+ * clientId: kmClient.id,
114
+ * mimeTypes: ['image/jpeg', 'image/png']
115
+ * });
116
+ * ```
117
+ *
118
+ * **5. Leaderboards**
119
+ * ```typescript
120
+ * // Submit score (replaces previous entry)
121
+ * await kmClient.leaderboard.upsertEntry(
122
+ * 'high-scores',
123
+ * 'desc',
124
+ * 1500,
125
+ * { playerName: 'Alice' },
126
+ * {}
127
+ * );
128
+ *
129
+ * // Get top 10
130
+ * const top10 = await kmClient.leaderboard.listEntries('high-scores', 'desc', 0, 10);
131
+ * ```
132
+ *
133
+ * **6. Presence Tracking**
134
+ * ```typescript
135
+ * // Track online players
136
+ * const onlineClientIds = useSnapshot(gameStore.connections).clientIds;
137
+ * const isPlayerOnline = onlineClientIds.has(playerId);
138
+ * ```
139
+ *
140
+ * **Best Practices:**
141
+ * - Always use `kmClient.serverTimestamp()` for time-sensitive operations
142
+ * - Prefer Records over Arrays: `Record<string, T>` with timestamp keys
143
+ * - Use `kmClient.transact()` for all state updates to ensure atomicity
144
+ * - Tag uploads for easy filtering and organization
145
+ * - Use local stores for client-side settings and preferences
146
+ * - Leverage TypeScript generics for type-safe stores
147
+ *
148
+ * **Events:**
149
+ * - `connected`: Fired when client connects/reconnects to server
150
+ * - `disconnected`: Fired when connection is lost
151
+ *
152
+ * @template ClientContextT The type of client context data (custom user data from your backend)
153
+ *
154
+ * @example
155
+ * ```typescript
156
+ * // Listen for connection events
157
+ * kmClient.on('connected', () => {
158
+ * console.log('Connected to Kokimoki!');
159
+ * });
160
+ *
161
+ * kmClient.on('disconnected', () => {
162
+ * console.log('Connection lost, will auto-reconnect...');
163
+ * });
164
+ * ```
165
+ */
14
166
  export class KokimokiClient extends EventEmitter {
15
167
  host;
16
168
  appId;
@@ -37,6 +189,9 @@ export class KokimokiClient extends EventEmitter {
37
189
  _pingInterval;
38
190
  _clientTokenKey = "KM_TOKEN";
39
191
  _editorContext;
192
+ _ai;
193
+ _storage;
194
+ _leaderboard;
40
195
  constructor(host, appId, code = "") {
41
196
  super();
42
197
  this.host = host;
@@ -46,6 +201,9 @@ export class KokimokiClient extends EventEmitter {
46
201
  const secure = this.host.indexOf(":") === -1;
47
202
  this._wsUrl = `ws${secure ? "s" : ""}://${this.host}`;
48
203
  this._apiUrl = `http${secure ? "s" : ""}://${this.host}`;
204
+ // Initialize modules
205
+ this._ai = new KokimokiAi(this);
206
+ this._leaderboard = new KokimokiLeaderboard(this);
49
207
  // Set up ping interval
50
208
  const pingMsg = new WsMessageWriter();
51
209
  pingMsg.writeInt32(WsMessageType.Ping);
@@ -106,6 +264,9 @@ export class KokimokiClient extends EventEmitter {
106
264
  }
107
265
  return this._clientContext;
108
266
  }
267
+ /**
268
+ * Indicates whether the client is currently connected to the server.
269
+ */
109
270
  get connected() {
110
271
  return this._connected;
111
272
  }
@@ -115,9 +276,21 @@ export class KokimokiClient extends EventEmitter {
115
276
  }
116
277
  return this._ws;
117
278
  }
279
+ /**
280
+ * Indicates whether the client is running in editor/development mode.
281
+ */
118
282
  get isEditor() {
119
283
  return !!this._editorContext;
120
284
  }
285
+ /**
286
+ * Establishes a connection to the Kokimoki server.
287
+ *
288
+ * Handles authentication, WebSocket setup, and automatic reconnection.
289
+ * If already connecting, returns the existing connection promise.
290
+ *
291
+ * @returns A promise that resolves when the connection is established.
292
+ * @throws Error if the connection fails.
293
+ */
121
294
  async connect() {
122
295
  if (this._connectPromise) {
123
296
  return await this._connectPromise;
@@ -322,10 +495,21 @@ export class KokimokiClient extends EventEmitter {
322
495
  }
323
496
  }
324
497
  }
498
+ /**
499
+ * Gets the current server timestamp, accounting for client-server time offset.
500
+ *
501
+ * @returns The current server timestamp in milliseconds.
502
+ */
325
503
  serverTimestamp() {
326
504
  return Date.now() - this._serverTimeOffset;
327
505
  }
328
- // Send Y update to room
506
+ /**
507
+ * Sends a Y.js update to a specific room.
508
+ *
509
+ * @param room - The name of the room to update.
510
+ * @param update - The Y.js update as a Uint8Array.
511
+ * @returns A promise that resolves with the server response.
512
+ */
329
513
  async patchRoomState(room, update) {
330
514
  const res = await fetch(`${this._apiUrl}/rooms/${room}`, {
331
515
  method: "PATCH",
@@ -334,81 +518,6 @@ export class KokimokiClient extends EventEmitter {
334
518
  });
335
519
  return await res.json();
336
520
  }
337
- // Storage
338
- async createUpload(name, blob, tags) {
339
- const res = await fetch(`${this._apiUrl}/uploads`, {
340
- method: "POST",
341
- headers: this.apiHeaders,
342
- body: JSON.stringify({
343
- name,
344
- size: blob.size,
345
- mimeType: blob.type,
346
- tags,
347
- }),
348
- });
349
- return await res.json();
350
- }
351
- async uploadChunks(blob, chunkSize, signedUrls) {
352
- return await Promise.all(signedUrls.map(async (url, index) => {
353
- const start = index * chunkSize;
354
- const end = Math.min(start + chunkSize, blob.size);
355
- const chunk = blob.slice(start, end);
356
- const res = await fetch(url, { method: "PUT", body: chunk });
357
- return JSON.parse(res.headers.get("ETag") || '""');
358
- }));
359
- }
360
- async completeUpload(id, etags) {
361
- const res = await fetch(`${this._apiUrl}/uploads/${id}`, {
362
- method: "PUT",
363
- headers: this.apiHeaders,
364
- body: JSON.stringify({ etags }),
365
- });
366
- return await res.json();
367
- }
368
- async upload(name, blob, tags = []) {
369
- const { id, chunkSize, urls } = await this.createUpload(name, blob, tags);
370
- const etags = await this.uploadChunks(blob, chunkSize, urls);
371
- return await this.completeUpload(id, etags);
372
- }
373
- async updateUpload(id, update) {
374
- const res = await fetch(`${this._apiUrl}/uploads/${id}`, {
375
- method: "PUT",
376
- headers: this.apiHeaders,
377
- body: JSON.stringify(update),
378
- });
379
- return await res.json();
380
- }
381
- async listUploads(filter = {}, skip = 0, limit = 100) {
382
- const url = new URL("/uploads", this._apiUrl);
383
- url.searchParams.set("skip", skip.toString());
384
- url.searchParams.set("limit", limit.toString());
385
- if (filter.clientId) {
386
- url.searchParams.set("clientId", filter.clientId);
387
- }
388
- if (filter.mimeTypes) {
389
- url.searchParams.set("mimeTypes", filter.mimeTypes.join());
390
- }
391
- if (filter.tags) {
392
- url.searchParams.set("tags", filter.tags.join());
393
- }
394
- const res = await fetch(url.href, {
395
- headers: this.apiHeaders,
396
- });
397
- return await res.json();
398
- }
399
- async deleteUpload(id) {
400
- const res = await fetch(`${this._apiUrl}/uploads/${id}`, {
401
- method: "DELETE",
402
- headers: this.apiHeaders,
403
- });
404
- return await res.json();
405
- }
406
- async exposeScriptingContext(context) {
407
- // @ts-ignore
408
- window.KM_SCRIPTING_CONTEXT = context;
409
- // @ts-ignore
410
- window.dispatchEvent(new CustomEvent("km:scriptingContextExposed"));
411
- }
412
521
  async sendSubscribeReq(roomName, mode) {
413
522
  // Set up sync resolver
414
523
  const reqId = ++this._messageId;
@@ -443,6 +552,15 @@ export class KokimokiClient extends EventEmitter {
443
552
  this.ws.send(msg.getBuffer());
444
553
  });
445
554
  }
555
+ /**
556
+ * Joins a store by subscribing to its corresponding room.
557
+ *
558
+ * If already joined, this method does nothing. For local stores, initializes
559
+ * the store locally. For remote stores, sends a subscription request to the server.
560
+ *
561
+ * @param store - The KokimokiStore to join.
562
+ * @template T - The type of the store's state object.
563
+ */
446
564
  async join(store) {
447
565
  let subscription = this._subscriptionsByName.get(store.roomName);
448
566
  if (!subscription) {
@@ -464,6 +582,14 @@ export class KokimokiClient extends EventEmitter {
464
582
  await store.onJoin(this);
465
583
  }
466
584
  }
585
+ /**
586
+ * Leaves a store by unsubscribing from its corresponding room.
587
+ *
588
+ * Triggers the store's `onBeforeLeave` and `onLeave` lifecycle hooks.
589
+ *
590
+ * @param store - The KokimokiStore to leave.
591
+ * @template T - The type of the store's state object.
592
+ */
467
593
  async leave(store) {
468
594
  const subscription = this._subscriptionsByName.get(store.roomName);
469
595
  if (subscription) {
@@ -476,6 +602,27 @@ export class KokimokiClient extends EventEmitter {
476
602
  store.onLeave(this);
477
603
  }
478
604
  }
605
+ /**
606
+ * Executes a transaction across one or more stores.
607
+ *
608
+ * Provides proxies to the stores that track changes. All changes are batched
609
+ * and sent to the server atomically. The transaction ensures consistency across
610
+ * multiple stores.
611
+ *
612
+ * @param stores - Array of stores to include in the transaction.
613
+ * @param handler - Function that receives store proxies and performs modifications.
614
+ * @returns A promise that resolves with the return value of the handler.
615
+ * @template TStores - Tuple type of the stores array.
616
+ * @template ReturnT - The return type of the handler function.
617
+ *
618
+ * @example
619
+ * ```ts
620
+ * await client.transact([playerStore, gameStore], ([player, game]) => {
621
+ * player.score += 10;
622
+ * game.lastUpdate = Date.now();
623
+ * });
624
+ * ```
625
+ */
479
626
  async transact(stores, handler) {
480
627
  // if (!this._connected) {
481
628
  // throw new Error("Client not connected");
@@ -550,6 +697,11 @@ export class KokimokiClient extends EventEmitter {
550
697
  }
551
698
  return returnValue;
552
699
  }
700
+ /**
701
+ * Closes the client connection and cleans up resources.
702
+ *
703
+ * Disables automatic reconnection, closes the WebSocket, and clears all intervals.
704
+ */
553
705
  async close() {
554
706
  this._autoReconnect = false;
555
707
  if (this._ws) {
@@ -559,7 +711,14 @@ export class KokimokiClient extends EventEmitter {
559
711
  clearInterval(this._pingInterval);
560
712
  }
561
713
  }
562
- // Get room hash value for store
714
+ /**
715
+ * Gets the internal room hash identifier for a store.
716
+ *
717
+ * @param store - The store to get the room hash for.
718
+ * @returns The room hash as a number.
719
+ * @throws Error if the store hasn't been joined.
720
+ * @template T - The type of the store's state object.
721
+ */
563
722
  getRoomHash(store) {
564
723
  const subscription = this._subscriptionsByName.get(store.roomName);
565
724
  if (!subscription) {
@@ -567,8 +726,20 @@ export class KokimokiClient extends EventEmitter {
567
726
  }
568
727
  return subscription.roomHash;
569
728
  }
570
- /** Initializers */
571
- // store
729
+ /**
730
+ * Creates a new remote store synchronized with the server.
731
+ *
732
+ * @param name - The name of the room/store.
733
+ * @param defaultState - The initial state of the store.
734
+ * @param autoJoin - Whether to automatically join the store (default: true).
735
+ * @returns A new KokimokiStore instance.
736
+ * @template T - The type of the store's state object.
737
+ *
738
+ * @example
739
+ * ```ts
740
+ * const gameStore = client.store('game', { players: [], score: 0 });
741
+ * ```
742
+ */
572
743
  store(name, defaultState, autoJoin = true) {
573
744
  const store = new KokimokiStore(name, defaultState);
574
745
  if (autoJoin) {
@@ -578,7 +749,22 @@ export class KokimokiClient extends EventEmitter {
578
749
  }
579
750
  return store;
580
751
  }
581
- // local store
752
+ /**
753
+ * Creates a new local store that persists only in the client's browser.
754
+ *
755
+ * Local stores are automatically joined and are not synchronized with the server.
756
+ * Data is stored locally per client and app.
757
+ *
758
+ * @param name - The name of the local store.
759
+ * @param defaultState - The initial state of the store.
760
+ * @returns A new KokimokiLocalStore instance.
761
+ * @template T - The type of the store's state object.
762
+ *
763
+ * @example
764
+ * ```ts
765
+ * const settingsStore = client.localStore('settings', { volume: 0.5, theme: 'dark' });
766
+ * ```
767
+ */
582
768
  localStore(name, defaultState) {
583
769
  const store = new KokimokiLocalStore(name, defaultState);
584
770
  this.join(store)
@@ -586,122 +772,18 @@ export class KokimokiClient extends EventEmitter {
586
772
  .catch(() => { });
587
773
  return store;
588
774
  }
589
- // // queue
590
- // queue<T extends S.Generic<unknown>>(
591
- // name: string,
592
- // schema: T,
593
- // mode: RoomSubscriptionMode,
594
- // autoJoin = true
595
- // ) {
596
- // const queue = new KokimokiQueue<T>(name, schema, mode);
597
- // if (autoJoin) {
598
- // this.join(queue)
599
- // .then(() => {})
600
- // .catch(() => {});
601
- // }
602
- // return queue;
603
- // }
604
- // awareness
605
- awareness(name, initialData, autoJoin = true) {
606
- const awareness = new KokimokiAwareness(name, initialData);
607
- if (autoJoin) {
608
- this.join(awareness)
609
- .then(() => { })
610
- .catch(() => { });
611
- }
612
- return awareness;
613
- }
614
- // // req-res
615
- // reqRes<Req extends S.Generic<unknown>, Res extends S.Generic<unknown>>(
616
- // serviceName: string,
617
- // reqSchema: Req,
618
- // resSchema: Res,
619
- // handleRequest: (
620
- // payload: Req["defaultValue"]
621
- // ) => Promise<Res["defaultValue"]>
622
- // ) {
623
- // return new KokimokiReqRes(
624
- // this,
625
- // serviceName,
626
- // reqSchema,
627
- // resSchema,
628
- // handleRequest
629
- // );
630
- // }
631
775
  /**
632
- * Add a new entry to a leaderboard
633
- * @param leaderboardName
634
- * @param score
635
- * @param metadata
636
- * @returns
637
- */
638
- async insertLeaderboardEntry(leaderboardName, sortDir, score, metadata, privateMetadata) {
639
- const res = await fetch(`${this._apiUrl}/leaderboard-entries`, {
640
- method: "POST",
641
- headers: this.apiHeaders,
642
- body: JSON.stringify({
643
- leaderboardName,
644
- sortDir,
645
- score,
646
- metadata,
647
- privateMetadata,
648
- upsert: false,
649
- }),
650
- });
651
- return await res.json();
652
- }
653
- /**
654
- * Add or update latest entry to a leaderboard
655
- * @param leaderboardName
656
- * @param score
657
- * @param metadata
658
- * @param privateMetadata Can only be read using the leaderboard API
659
- * @returns
660
- */
661
- async upsertLeaderboardEntry(leaderboardName, sortDir, score, metadata, privateMetadata) {
662
- const res = await fetch(`${this._apiUrl}/leaderboard-entries`, {
663
- method: "POST",
664
- headers: this.apiHeaders,
665
- body: JSON.stringify({
666
- leaderboardName,
667
- sortDir,
668
- score,
669
- metadata,
670
- privateMetadata,
671
- upsert: true,
672
- }),
673
- });
674
- return await res.json();
675
- }
676
- /**
677
- * List entries in a leaderboard
678
- * @param leaderboardName
679
- * @param skip
680
- * @param limit
681
- * @returns
682
- */
683
- async listLeaderboardEntries(leaderboardName, sortDir, skip = 0, limit = 100) {
684
- const encodedLeaderboardName = encodeURIComponent(leaderboardName);
685
- const res = await fetch(`${this._apiUrl}/leaderboard-entries?leaderboardName=${encodedLeaderboardName}&sortDir=${sortDir}&skip=${skip}&limit=${limit}`, {
686
- headers: this.apiHeaders,
687
- });
688
- return await res.json();
689
- }
690
- /**
691
- * Get best entry in leaderboard for a client, defaults to current client
692
- * @param leaderboardName
693
- * @param sortDir
694
- * @param clientId
695
- */
696
- async getBestLeaderboardEntry(leaderboardName, sortDir, clientId) {
697
- const encodedLeaderboardName = encodeURIComponent(leaderboardName);
698
- const res = await fetch(`${this._apiUrl}/leaderboard-entries/best?leaderboardName=${encodedLeaderboardName}&sortDir=${sortDir}&clientId=${clientId || this.id}`, {
699
- headers: this._apiHeaders,
700
- });
701
- return await res.json();
702
- }
703
- /**
704
- * Send app data via webhook
776
+ * Sends app data to the server via webhook for external processing.
777
+ *
778
+ * @param event - The name of the webhook event.
779
+ * @param data - The data to send with the webhook.
780
+ * @returns A promise that resolves with the job ID.
781
+ * @template T - The type of the data being sent.
782
+ *
783
+ * @example
784
+ * ```ts
785
+ * await client.sendWebhook('game-ended', { winner: 'player1', score: 100 });
786
+ * ```
705
787
  */
706
788
  async sendWebhook(event, data) {
707
789
  const res = await fetch(`${this._apiUrl}/webhooks`, {
@@ -712,49 +794,30 @@ export class KokimokiClient extends EventEmitter {
712
794
  return await res.json();
713
795
  }
714
796
  /**
715
- * Generic AI chat endpoint: send a system prompt (and optional user prompt) to get a response.
797
+ * Access AI capabilities including text generation, structured JSON output, and image modification.
716
798
  */
717
- async chat(systemPrompt, userPrompt, temperature, maxTokens) {
718
- const res = await fetch(`${this._apiUrl}/ai/chat`, {
719
- method: "POST",
720
- headers: this.apiHeaders,
721
- body: JSON.stringify({
722
- systemPrompt,
723
- userPrompt,
724
- temperature,
725
- maxTokens,
726
- }),
727
- });
728
- if (!res.ok) {
729
- throw await res.json();
799
+ get ai() {
800
+ if (!this._ai) {
801
+ throw new Error("AI client not initialized");
730
802
  }
731
- return await res.json();
803
+ return this._ai;
732
804
  }
733
805
  /**
734
- * Use AI to apply prompt to an image
806
+ * Access file upload and management for media files, images, and user-generated content.
735
807
  */
736
- async transformImage(baseImageUrl, prompt, tags = []) {
737
- const res = await fetch(`${this._apiUrl}/ai/images`, {
738
- method: "POST",
739
- headers: this.apiHeaders,
740
- body: JSON.stringify({ baseImageUrl, prompt, tags }),
741
- });
742
- if (!res.ok) {
743
- throw await res.json();
808
+ get storage() {
809
+ if (!this._storage) {
810
+ throw new Error("Storage client not initialized");
744
811
  }
745
- return await res.json();
812
+ return this._storage;
746
813
  }
747
814
  /**
748
- * Load app config - optionally translated to any language
815
+ * Access player ranking and score tracking with efficient queries and pagination.
749
816
  */
750
- async getConfig(language = "") {
751
- const res = await fetch(`${this._apiUrl}/app/config?language=${encodeURIComponent(language)}`, {
752
- headers: this.apiHeaders,
753
- });
754
- if (!res.ok) {
755
- throw await res.json();
817
+ get leaderboard() {
818
+ if (!this._leaderboard) {
819
+ throw new Error("Leaderboard client not initialized");
756
820
  }
757
- const { status, config } = await res.json();
758
- return { status, config };
821
+ return this._leaderboard;
759
822
  }
760
823
  }