@kokimoki/app 1.17.0 → 2.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.
- package/dist/fields.d.ts +110 -0
- package/dist/fields.js +158 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -2
- package/dist/kokimoki-ai.d.ts +153 -0
- package/dist/kokimoki-ai.js +164 -0
- package/dist/kokimoki-awareness.d.ts +14 -13
- package/dist/kokimoki-awareness.js +41 -33
- package/dist/kokimoki-client-refactored.d.ts +80 -0
- package/dist/kokimoki-client-refactored.js +400 -0
- package/dist/kokimoki-client.d.ts +282 -76
- package/dist/kokimoki-client.js +295 -232
- package/dist/kokimoki-leaderboard.d.ts +175 -0
- package/dist/kokimoki-leaderboard.js +203 -0
- package/dist/kokimoki-schema.d.ts +113 -0
- package/dist/kokimoki-schema.js +162 -0
- package/dist/kokimoki-storage.d.ts +156 -0
- package/dist/kokimoki-storage.js +208 -0
- package/dist/kokimoki.min.d.ts +758 -89
- package/dist/kokimoki.min.js +664 -270
- package/dist/kokimoki.min.js.map +1 -1
- package/dist/llms.txt +649 -0
- package/dist/message-queue.d.ts +8 -0
- package/dist/message-queue.js +19 -0
- package/dist/synced-schema.d.ts +74 -0
- package/dist/synced-schema.js +83 -0
- package/dist/synced-store.d.ts +10 -0
- package/dist/synced-store.js +9 -0
- package/dist/synced-types.d.ts +47 -0
- package/dist/synced-types.js +67 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/ws-message-type copy.d.ts +6 -0
- package/dist/ws-message-type copy.js +7 -0
- package/package.json +2 -2
package/dist/kokimoki-client.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
571
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
633
|
-
*
|
|
634
|
-
* @param
|
|
635
|
-
* @param
|
|
636
|
-
* @returns
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
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
|
-
*
|
|
797
|
+
* Access AI capabilities including text generation, structured JSON output, and image modification.
|
|
716
798
|
*/
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
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
|
|
803
|
+
return this._ai;
|
|
732
804
|
}
|
|
733
805
|
/**
|
|
734
|
-
*
|
|
806
|
+
* Access file upload and management for media files, images, and user-generated content.
|
|
735
807
|
*/
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
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
|
|
812
|
+
return this._storage;
|
|
746
813
|
}
|
|
747
814
|
/**
|
|
748
|
-
*
|
|
815
|
+
* Access player ranking and score tracking with efficient queries and pagination.
|
|
749
816
|
*/
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
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
|
-
|
|
758
|
-
return { status, config };
|
|
821
|
+
return this._leaderboard;
|
|
759
822
|
}
|
|
760
823
|
}
|