@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.min.js
CHANGED
|
@@ -486,7 +486,7 @@ function eventTargetAgnosticAddListener(emitter, name, listener, flags) {
|
|
|
486
486
|
var eventsExports = events.exports;
|
|
487
487
|
var EventEmitter$1 = /*@__PURE__*/getDefaultExportFromCjs(eventsExports);
|
|
488
488
|
|
|
489
|
-
const KOKIMOKI_APP_VERSION = "
|
|
489
|
+
const KOKIMOKI_APP_VERSION = "2.0.0";
|
|
490
490
|
|
|
491
491
|
/**
|
|
492
492
|
* Utility module to work with key-value stores.
|
|
@@ -12258,45 +12258,6 @@ class RoomSubscription {
|
|
|
12258
12258
|
}
|
|
12259
12259
|
}
|
|
12260
12260
|
|
|
12261
|
-
class KokimokiAwareness extends KokimokiStore {
|
|
12262
|
-
_data;
|
|
12263
|
-
_kmClients = new Set();
|
|
12264
|
-
constructor(roomName, _data, mode = RoomSubscriptionMode.ReadWrite) {
|
|
12265
|
-
super(`/a/${roomName}`, {}, mode);
|
|
12266
|
-
this._data = _data;
|
|
12267
|
-
}
|
|
12268
|
-
async onJoin(client) {
|
|
12269
|
-
this._kmClients.add(client);
|
|
12270
|
-
await client.transact([this], ([state]) => {
|
|
12271
|
-
state[client.connectionId] = {
|
|
12272
|
-
clientId: client.id,
|
|
12273
|
-
data: this._data,
|
|
12274
|
-
};
|
|
12275
|
-
});
|
|
12276
|
-
}
|
|
12277
|
-
async onLeave(client) {
|
|
12278
|
-
this._kmClients.delete(client);
|
|
12279
|
-
}
|
|
12280
|
-
getClients() {
|
|
12281
|
-
const clients = {};
|
|
12282
|
-
const connections = this.get();
|
|
12283
|
-
for (const connectionId in connections) {
|
|
12284
|
-
clients[connections[connectionId].clientId] =
|
|
12285
|
-
connections[connectionId].data;
|
|
12286
|
-
}
|
|
12287
|
-
return clients;
|
|
12288
|
-
}
|
|
12289
|
-
async setData(data) {
|
|
12290
|
-
this._data = data;
|
|
12291
|
-
const kmClients = Array.from(this._kmClients);
|
|
12292
|
-
await Promise.all(kmClients.map(async (client) => {
|
|
12293
|
-
await client.transact([this], ([state]) => {
|
|
12294
|
-
state[client.connectionId].data = this._data;
|
|
12295
|
-
});
|
|
12296
|
-
}));
|
|
12297
|
-
}
|
|
12298
|
-
}
|
|
12299
|
-
|
|
12300
12261
|
var farmhash_modern = {exports: {}};
|
|
12301
12262
|
|
|
12302
12263
|
var util = {};
|
|
@@ -14863,6 +14824,528 @@ class KokimokiLocalStore extends KokimokiStore {
|
|
|
14863
14824
|
}
|
|
14864
14825
|
}
|
|
14865
14826
|
|
|
14827
|
+
/**
|
|
14828
|
+
* Kokimoki AI Integration Service
|
|
14829
|
+
*
|
|
14830
|
+
* Provides built-in AI capabilities for game applications without requiring API keys or setup.
|
|
14831
|
+
* Includes text generation, structured JSON output, and image modification.
|
|
14832
|
+
*
|
|
14833
|
+
* **Key Features:**
|
|
14834
|
+
* - Multiple AI models (GPT-4, GPT-5, Gemini variants)
|
|
14835
|
+
* - Text generation with configurable creativity (temperature)
|
|
14836
|
+
* - Structured JSON output for game data
|
|
14837
|
+
* - AI-powered image modifications
|
|
14838
|
+
* - No API keys or configuration required
|
|
14839
|
+
*
|
|
14840
|
+
* **Common Use Cases:**
|
|
14841
|
+
* - Generate dynamic game content (quests, dialogues, stories)
|
|
14842
|
+
* - Create AI opponents or NPCs with personalities
|
|
14843
|
+
* - Generate quiz questions or trivia
|
|
14844
|
+
* - Create game assets (character stats, item descriptions)
|
|
14845
|
+
* - Transform user-uploaded images
|
|
14846
|
+
* - Generate procedural content
|
|
14847
|
+
*
|
|
14848
|
+
* Access via `kmClient.ai`
|
|
14849
|
+
*
|
|
14850
|
+
* @example
|
|
14851
|
+
* ```typescript
|
|
14852
|
+
* // Generate story text
|
|
14853
|
+
* const story = await kmClient.ai.chat({
|
|
14854
|
+
* systemPrompt: 'You are a fantasy story writer',
|
|
14855
|
+
* userPrompt: 'Write a short quest description',
|
|
14856
|
+
* temperature: 0.8
|
|
14857
|
+
* });
|
|
14858
|
+
*
|
|
14859
|
+
* // Generate structured game data
|
|
14860
|
+
* interface Enemy {
|
|
14861
|
+
* name: string;
|
|
14862
|
+
* health: number;
|
|
14863
|
+
* attack: number;
|
|
14864
|
+
* }
|
|
14865
|
+
* const enemy = await kmClient.ai.generateJson<Enemy>({
|
|
14866
|
+
* userPrompt: 'Create a level 5 goblin warrior'
|
|
14867
|
+
* });
|
|
14868
|
+
*
|
|
14869
|
+
* // Transform image
|
|
14870
|
+
* const modified = await kmClient.ai.modifyImage(
|
|
14871
|
+
* imageUrl,
|
|
14872
|
+
* 'Make it look like pixel art'
|
|
14873
|
+
* );
|
|
14874
|
+
* ```
|
|
14875
|
+
*/
|
|
14876
|
+
class KokimokiAi {
|
|
14877
|
+
client;
|
|
14878
|
+
constructor(client) {
|
|
14879
|
+
this.client = client;
|
|
14880
|
+
}
|
|
14881
|
+
/**
|
|
14882
|
+
* Generate a chat response from the AI model.
|
|
14883
|
+
*
|
|
14884
|
+
* Sends a chat request to the AI service and returns the generated response.
|
|
14885
|
+
* Supports multiple AI models including GPT and Gemini variants with configurable
|
|
14886
|
+
* parameters for fine-tuning the response behavior.
|
|
14887
|
+
*
|
|
14888
|
+
* @param req The chat request parameters.
|
|
14889
|
+
* @param req.model Optional. The AI model to use. Defaults to server-side default if not specified.
|
|
14890
|
+
* Available models:
|
|
14891
|
+
* - `gpt-4o`: OpenAI GPT-4 Optimized
|
|
14892
|
+
* - `gpt-4o-mini`: Smaller, faster GPT-4 variant
|
|
14893
|
+
* - `gpt-5`: OpenAI GPT-5 (latest)
|
|
14894
|
+
* - `gpt-5-mini`: Smaller GPT-5 variant
|
|
14895
|
+
* - `gpt-5-nano`: Smallest GPT-5 variant for lightweight tasks
|
|
14896
|
+
* - `gemini-2.5-flash-lite`: Google Gemini lite variant
|
|
14897
|
+
* - `gemini-2.5-flash`: Google Gemini fast variant
|
|
14898
|
+
* @param req.systemPrompt Optional. The system message that sets the behavior and context for the AI.
|
|
14899
|
+
* This helps define the AI's role, personality, and constraints.
|
|
14900
|
+
* @param req.userPrompt The user's message or question to send to the AI.
|
|
14901
|
+
* @param req.temperature Optional. Controls randomness in the response (0.0 to 1.0).
|
|
14902
|
+
* Lower values make output more focused and deterministic,
|
|
14903
|
+
* higher values make it more creative and varied.
|
|
14904
|
+
* @param req.maxTokens Optional. The maximum number of tokens to generate in the response.
|
|
14905
|
+
* Controls the length of the AI's output.
|
|
14906
|
+
*
|
|
14907
|
+
* @returns A promise that resolves to an object containing the AI-generated response.
|
|
14908
|
+
* @returns {string} content The text content of the AI's response.
|
|
14909
|
+
*
|
|
14910
|
+
* @throws An error object if the API request fails.
|
|
14911
|
+
*
|
|
14912
|
+
* @example
|
|
14913
|
+
* ```typescript
|
|
14914
|
+
* const response = await client.ai.chat({
|
|
14915
|
+
* model: "gpt-4o",
|
|
14916
|
+
* systemPrompt: "You are a helpful coding assistant.",
|
|
14917
|
+
* userPrompt: "Explain what TypeScript is in one sentence.",
|
|
14918
|
+
* temperature: 0.7,
|
|
14919
|
+
* maxTokens: 100
|
|
14920
|
+
* });
|
|
14921
|
+
* console.log(response.content);
|
|
14922
|
+
* ```
|
|
14923
|
+
*/
|
|
14924
|
+
async chat(req) {
|
|
14925
|
+
const res = await fetch(`${this.client.apiUrl}/ai/chat`, {
|
|
14926
|
+
method: "POST",
|
|
14927
|
+
headers: this.client.apiHeaders,
|
|
14928
|
+
body: JSON.stringify(req),
|
|
14929
|
+
});
|
|
14930
|
+
if (!res.ok) {
|
|
14931
|
+
throw await res.json();
|
|
14932
|
+
}
|
|
14933
|
+
return await res.json();
|
|
14934
|
+
}
|
|
14935
|
+
/**
|
|
14936
|
+
* Generate structured JSON output from the AI model.
|
|
14937
|
+
*
|
|
14938
|
+
* Sends a chat request to the AI service with the expectation of receiving
|
|
14939
|
+
* a JSON-formatted response. This is useful for scenarios where the output
|
|
14940
|
+
* needs to be parsed or processed programmatically.
|
|
14941
|
+
*
|
|
14942
|
+
* @param req The chat request parameters.
|
|
14943
|
+
* @param req.model Optional. The AI model to use. Defaults to server-side default if not specified.
|
|
14944
|
+
* Available models:
|
|
14945
|
+
* - `gpt-4o`: OpenAI GPT-4 Optimized
|
|
14946
|
+
* - `gpt-4o-mini`: Smaller, faster GPT-4 variant
|
|
14947
|
+
* - `gpt-5`: OpenAI GPT-5 (latest)
|
|
14948
|
+
* - `gpt-5-mini`: Smaller GPT-5 variant
|
|
14949
|
+
* - `gpt-5-nano`: Smallest GPT-5 variant for lightweight tasks
|
|
14950
|
+
* - `gemini-2.5-flash-lite`: Google Gemini lite variant
|
|
14951
|
+
* - `gemini-2.5-flash`: Google Gemini fast variant
|
|
14952
|
+
* @param req.systemPrompt Optional. The system message that sets the behavior and context for the AI.
|
|
14953
|
+
* This helps define the AI's role, personality, and constraints.
|
|
14954
|
+
* @param req.userPrompt The user's message or question to send to the AI.
|
|
14955
|
+
* @param req.temperature Optional. Controls randomness in the response (0.0 to 1.0).
|
|
14956
|
+
* Lower values make output more focused and deterministic,
|
|
14957
|
+
* higher values make it more creative and varied.
|
|
14958
|
+
* @param req.maxTokens Optional. The maximum number of tokens to generate in the response.
|
|
14959
|
+
* Controls the length of the AI's output.
|
|
14960
|
+
*
|
|
14961
|
+
* @returns A promise that resolves to the parsed JSON object generated by the AI.
|
|
14962
|
+
*
|
|
14963
|
+
* @throws An error object if the API request fails or if the response is not valid JSON.
|
|
14964
|
+
*/
|
|
14965
|
+
async generateJson(req) {
|
|
14966
|
+
const { content } = await this.chat({
|
|
14967
|
+
...req,
|
|
14968
|
+
responseMimeType: "application/json",
|
|
14969
|
+
});
|
|
14970
|
+
return JSON.parse(content);
|
|
14971
|
+
}
|
|
14972
|
+
/**
|
|
14973
|
+
* Modify an image using the AI service.
|
|
14974
|
+
* @param baseImageUrl The URL of the base image to modify.
|
|
14975
|
+
* @param prompt The modification prompt to apply to the image.
|
|
14976
|
+
* @param tags Optional. Tags to associate with the image.
|
|
14977
|
+
* @returns A promise that resolves to the modified image upload information.
|
|
14978
|
+
*/
|
|
14979
|
+
async modifyImage(baseImageUrl, prompt, tags = []) {
|
|
14980
|
+
const res = await fetch(`${this.client.apiUrl}/ai/modify-image`, {
|
|
14981
|
+
method: "POST",
|
|
14982
|
+
headers: this.client.apiHeaders,
|
|
14983
|
+
body: JSON.stringify({ baseImageUrl, prompt, tags }),
|
|
14984
|
+
});
|
|
14985
|
+
if (!res.ok) {
|
|
14986
|
+
throw await res.json();
|
|
14987
|
+
}
|
|
14988
|
+
return await res.json();
|
|
14989
|
+
}
|
|
14990
|
+
}
|
|
14991
|
+
|
|
14992
|
+
/**
|
|
14993
|
+
* Kokimoki Leaderboard Service
|
|
14994
|
+
*
|
|
14995
|
+
* Provides efficient player ranking and score tracking with database indexes and optimized queries.
|
|
14996
|
+
* Ideal for games with large numbers of players and competitive scoring.
|
|
14997
|
+
*
|
|
14998
|
+
* **Key Features:**
|
|
14999
|
+
* - Efficient ranking with database indexes
|
|
15000
|
+
* - Support for ascending (lowest-is-best) and descending (highest-is-best) sorting
|
|
15001
|
+
* - Pagination for large leaderboards
|
|
15002
|
+
* - Public and private metadata for entries
|
|
15003
|
+
* - Insert (preserve all attempts) or upsert (keep latest only) modes
|
|
15004
|
+
*
|
|
15005
|
+
* **When to use Leaderboard API vs Global Store:**
|
|
15006
|
+
*
|
|
15007
|
+
* Use the **Leaderboard API** when:
|
|
15008
|
+
* - You have a large number of entries (hundreds to thousands of players)
|
|
15009
|
+
* - You need efficient ranking and sorting with database indexes
|
|
15010
|
+
* - You want pagination and optimized queries for top scores
|
|
15011
|
+
* - Memory and network efficiency are important
|
|
15012
|
+
*
|
|
15013
|
+
* Use a **Global Store** when:
|
|
15014
|
+
* - You have a small number of players (typically under 100)
|
|
15015
|
+
* - You need real-time updates and live leaderboard changes
|
|
15016
|
+
* - You want to combine player scores with other game state
|
|
15017
|
+
* - The leaderboard is temporary (session-based or reset frequently)
|
|
15018
|
+
*
|
|
15019
|
+
* Access via `kmClient.leaderboard`
|
|
15020
|
+
*
|
|
15021
|
+
* @example
|
|
15022
|
+
* ```typescript
|
|
15023
|
+
* // Submit a high score
|
|
15024
|
+
* const { rank } = await kmClient.leaderboard.upsertEntry(
|
|
15025
|
+
* 'high-scores',
|
|
15026
|
+
* 'desc',
|
|
15027
|
+
* 1500,
|
|
15028
|
+
* { playerName: 'Alice' },
|
|
15029
|
+
* {}
|
|
15030
|
+
* );
|
|
15031
|
+
*
|
|
15032
|
+
* // Get top 10
|
|
15033
|
+
* const { items } = await kmClient.leaderboard.listEntries('high-scores', 'desc', 0, 10);
|
|
15034
|
+
*
|
|
15035
|
+
* // Get player's best
|
|
15036
|
+
* const best = await kmClient.leaderboard.getBestEntry('high-scores', 'desc');
|
|
15037
|
+
* ```
|
|
15038
|
+
*/
|
|
15039
|
+
class KokimokiLeaderboard {
|
|
15040
|
+
client;
|
|
15041
|
+
constructor(client) {
|
|
15042
|
+
this.client = client;
|
|
15043
|
+
}
|
|
15044
|
+
/**
|
|
15045
|
+
* Add a new entry to a leaderboard.
|
|
15046
|
+
*
|
|
15047
|
+
* Creates a new entry each time it's called, preserving all attempts. Use this when you want
|
|
15048
|
+
* to track every score submission (e.g., all game attempts).
|
|
15049
|
+
*
|
|
15050
|
+
* @param leaderboardName The name of the leaderboard to add the entry to
|
|
15051
|
+
* @param sortDir Sort direction: "asc" for lowest-is-best (e.g., completion time),
|
|
15052
|
+
* "desc" for highest-is-best (e.g., points)
|
|
15053
|
+
* @param score The numeric score value
|
|
15054
|
+
* @param metadata Public metadata visible to all players (e.g., player name, level)
|
|
15055
|
+
* @param privateMetadata Private metadata only accessible via API calls (e.g., session ID)
|
|
15056
|
+
* @returns A promise resolving to an object with the entry's rank
|
|
15057
|
+
*
|
|
15058
|
+
* @example
|
|
15059
|
+
* ```typescript
|
|
15060
|
+
* const { rank } = await kmClient.leaderboard.insertEntry(
|
|
15061
|
+
* 'high-scores',
|
|
15062
|
+
* 'desc',
|
|
15063
|
+
* 1500,
|
|
15064
|
+
* { playerName: 'Alice', level: 10 },
|
|
15065
|
+
* { sessionId: 'abc123' }
|
|
15066
|
+
* );
|
|
15067
|
+
* console.log(`New rank: ${rank}`);
|
|
15068
|
+
* ```
|
|
15069
|
+
*/
|
|
15070
|
+
async insertEntry(leaderboardName, sortDir, score, metadata, privateMetadata) {
|
|
15071
|
+
const res = await fetch(`${this.client.apiUrl}/leaderboard-entries`, {
|
|
15072
|
+
method: "POST",
|
|
15073
|
+
headers: this.client.apiHeaders,
|
|
15074
|
+
body: JSON.stringify({
|
|
15075
|
+
leaderboardName,
|
|
15076
|
+
sortDir,
|
|
15077
|
+
score,
|
|
15078
|
+
metadata,
|
|
15079
|
+
privateMetadata,
|
|
15080
|
+
upsert: false,
|
|
15081
|
+
}),
|
|
15082
|
+
});
|
|
15083
|
+
return await res.json();
|
|
15084
|
+
}
|
|
15085
|
+
/**
|
|
15086
|
+
* Add or update the latest entry for the current client in a leaderboard.
|
|
15087
|
+
*
|
|
15088
|
+
* Replaces the previous entry if one exists for this client. Use this when you only want
|
|
15089
|
+
* to keep the latest or best score per player (e.g., daily high score).
|
|
15090
|
+
*
|
|
15091
|
+
* @param leaderboardName The name of the leaderboard to upsert the entry in
|
|
15092
|
+
* @param sortDir Sort direction: "asc" for lowest-is-best (e.g., completion time),
|
|
15093
|
+
* "desc" for highest-is-best (e.g., points)
|
|
15094
|
+
* @param score The numeric score value
|
|
15095
|
+
* @param metadata Public metadata visible to all players (e.g., player name, completion time)
|
|
15096
|
+
* @param privateMetadata Private metadata only accessible via API calls (e.g., device ID)
|
|
15097
|
+
* @returns A promise resolving to an object with the entry's updated rank
|
|
15098
|
+
*
|
|
15099
|
+
* @example
|
|
15100
|
+
* ```typescript
|
|
15101
|
+
* const { rank } = await kmClient.leaderboard.upsertEntry(
|
|
15102
|
+
* 'daily-scores',
|
|
15103
|
+
* 'desc',
|
|
15104
|
+
* 2000,
|
|
15105
|
+
* { playerName: 'Bob', completionTime: 120 },
|
|
15106
|
+
* { deviceId: 'xyz789' }
|
|
15107
|
+
* );
|
|
15108
|
+
* console.log(`Updated rank: ${rank}`);
|
|
15109
|
+
* ```
|
|
15110
|
+
*/
|
|
15111
|
+
async upsertEntry(leaderboardName, sortDir, score, metadata, privateMetadata) {
|
|
15112
|
+
const res = await fetch(`${this.client.apiUrl}/leaderboard-entries`, {
|
|
15113
|
+
method: "POST",
|
|
15114
|
+
headers: this.client.apiHeaders,
|
|
15115
|
+
body: JSON.stringify({
|
|
15116
|
+
leaderboardName,
|
|
15117
|
+
sortDir,
|
|
15118
|
+
score,
|
|
15119
|
+
metadata,
|
|
15120
|
+
privateMetadata,
|
|
15121
|
+
upsert: true,
|
|
15122
|
+
}),
|
|
15123
|
+
});
|
|
15124
|
+
return await res.json();
|
|
15125
|
+
}
|
|
15126
|
+
/**
|
|
15127
|
+
* List entries in a leaderboard with pagination.
|
|
15128
|
+
*
|
|
15129
|
+
* Retrieves a sorted list of leaderboard entries. Use skip and limit parameters for
|
|
15130
|
+
* pagination (e.g., showing top 10, or implementing "load more" functionality).
|
|
15131
|
+
*
|
|
15132
|
+
* @param leaderboardName The name of the leaderboard to query
|
|
15133
|
+
* @param sortDir Sort direction: "asc" for lowest-is-best (e.g., completion time),
|
|
15134
|
+
* "desc" for highest-is-best (e.g., points)
|
|
15135
|
+
* @param skip Number of entries to skip for pagination (default: 0)
|
|
15136
|
+
* @param limit Maximum number of entries to return (default: 100)
|
|
15137
|
+
* @returns A promise resolving to a paginated list of entries with rank, score, and metadata
|
|
15138
|
+
*
|
|
15139
|
+
* @example
|
|
15140
|
+
* ```typescript
|
|
15141
|
+
* // Get top 10 scores
|
|
15142
|
+
* const { items, total } = await kmClient.leaderboard.listEntries(
|
|
15143
|
+
* 'weekly-scores',
|
|
15144
|
+
* 'desc',
|
|
15145
|
+
* 0,
|
|
15146
|
+
* 10
|
|
15147
|
+
* );
|
|
15148
|
+
*
|
|
15149
|
+
* items.forEach(entry => {
|
|
15150
|
+
* console.log(`Rank ${entry.rank}: ${entry.metadata.playerName} - ${entry.score}`);
|
|
15151
|
+
* });
|
|
15152
|
+
* ```
|
|
15153
|
+
*/
|
|
15154
|
+
async listEntries(leaderboardName, sortDir, skip = 0, limit = 100) {
|
|
15155
|
+
const encodedLeaderboardName = encodeURIComponent(leaderboardName);
|
|
15156
|
+
const res = await fetch(`${this.client.apiUrl}/leaderboard-entries?leaderboardName=${encodedLeaderboardName}&sortDir=${sortDir}&skip=${skip}&limit=${limit}`, {
|
|
15157
|
+
headers: this.client.apiHeaders,
|
|
15158
|
+
});
|
|
15159
|
+
return await res.json();
|
|
15160
|
+
}
|
|
15161
|
+
/**
|
|
15162
|
+
* Get the best entry for a specific client in a leaderboard.
|
|
15163
|
+
*
|
|
15164
|
+
* Retrieves the highest-ranked entry for a client based on the sort direction.
|
|
15165
|
+
* Defaults to the current client if no clientId is provided.
|
|
15166
|
+
*
|
|
15167
|
+
* @param leaderboardName The name of the leaderboard to query
|
|
15168
|
+
* @param sortDir Sort direction: "asc" for lowest-is-best (e.g., completion time),
|
|
15169
|
+
* "desc" for highest-is-best (e.g., points)
|
|
15170
|
+
* @param clientId The client ID to get the best entry for (optional, defaults to current client)
|
|
15171
|
+
* @returns A promise resolving to the best entry with rank, score, and metadata
|
|
15172
|
+
*
|
|
15173
|
+
* @example
|
|
15174
|
+
* ```typescript
|
|
15175
|
+
* // Get current client's best entry
|
|
15176
|
+
* const myBest = await kmClient.leaderboard.getBestEntry('all-time-high', 'desc');
|
|
15177
|
+
* console.log(`My best: Rank ${myBest.rank}, Score ${myBest.score}`);
|
|
15178
|
+
*
|
|
15179
|
+
* // Get another player's best entry
|
|
15180
|
+
* const otherBest = await kmClient.leaderboard.getBestEntry(
|
|
15181
|
+
* 'all-time-high',
|
|
15182
|
+
* 'desc',
|
|
15183
|
+
* 'other-client-id'
|
|
15184
|
+
* );
|
|
15185
|
+
* ```
|
|
15186
|
+
*/
|
|
15187
|
+
async getBestEntry(leaderboardName, sortDir, clientId) {
|
|
15188
|
+
const encodedLeaderboardName = encodeURIComponent(leaderboardName);
|
|
15189
|
+
const res = await fetch(`${this.client.apiUrl}/leaderboard-entries/best?leaderboardName=${encodedLeaderboardName}&sortDir=${sortDir}&clientId=${clientId || this.client.id}`, {
|
|
15190
|
+
headers: this.client.apiHeaders,
|
|
15191
|
+
});
|
|
15192
|
+
return await res.json();
|
|
15193
|
+
}
|
|
15194
|
+
}
|
|
15195
|
+
|
|
15196
|
+
/**
|
|
15197
|
+
* Kokimoki Client - Real-time Collaborative Game Development SDK
|
|
15198
|
+
*
|
|
15199
|
+
* The main entry point for building multiplayer games and collaborative applications.
|
|
15200
|
+
* Provides real-time state synchronization, AI integration, cloud storage, leaderboards,
|
|
15201
|
+
* and more - all without complex backend setup.
|
|
15202
|
+
*
|
|
15203
|
+
* **Core Capabilities:**
|
|
15204
|
+
* - **Real-time Stores**: Synchronized state with automatic conflict resolution (powered by Valtio + Y.js)
|
|
15205
|
+
* - **Atomic Transactions**: Update multiple stores consistently with automatic batching
|
|
15206
|
+
* - **AI Integration**: Built-in text generation, structured JSON output, and image modification
|
|
15207
|
+
* - **Cloud Storage**: File uploads with CDN delivery and tag-based organization
|
|
15208
|
+
* - **Leaderboards**: Efficient player ranking with database indexes and pagination
|
|
15209
|
+
* - **Presence Tracking**: Real-time connection status for all players
|
|
15210
|
+
* - **Time Sync**: Server-synchronized timestamps across all clients
|
|
15211
|
+
* - **Webhooks**: Send data to external services for backend processing
|
|
15212
|
+
*
|
|
15213
|
+
* **Quick Start:**
|
|
15214
|
+
* ```typescript
|
|
15215
|
+
* import { KokimokiClient } from '@kokimoki/app';
|
|
15216
|
+
*
|
|
15217
|
+
* // Initialize the client
|
|
15218
|
+
* const kmClient = new KokimokiClient(
|
|
15219
|
+
* 'your-host.kokimoki.com',
|
|
15220
|
+
* 'your-app-id',
|
|
15221
|
+
* 'optional-access-code'
|
|
15222
|
+
* );
|
|
15223
|
+
*
|
|
15224
|
+
* // Connect to the server
|
|
15225
|
+
* await kmClient.connect();
|
|
15226
|
+
*
|
|
15227
|
+
* // Create a synchronized store
|
|
15228
|
+
* interface GameState {
|
|
15229
|
+
* players: Record<string, { name: string; score: number }>;
|
|
15230
|
+
* round: number;
|
|
15231
|
+
* }
|
|
15232
|
+
*
|
|
15233
|
+
* const gameStore = kmClient.store<GameState>('game', {
|
|
15234
|
+
* players: {},
|
|
15235
|
+
* round: 1
|
|
15236
|
+
* });
|
|
15237
|
+
*
|
|
15238
|
+
* // Update state atomically
|
|
15239
|
+
* await kmClient.transact([gameStore], ([game]) => {
|
|
15240
|
+
* game.players[kmClient.id] = { name: 'Player 1', score: 0 };
|
|
15241
|
+
* game.round = 2;
|
|
15242
|
+
* });
|
|
15243
|
+
*
|
|
15244
|
+
* // Use in React components with Valtio
|
|
15245
|
+
* import { useSnapshot } from 'valtio';
|
|
15246
|
+
*
|
|
15247
|
+
* function GameComponent() {
|
|
15248
|
+
* const game = useSnapshot(gameStore.proxy);
|
|
15249
|
+
* return <div>Round: {game.round}</div>;
|
|
15250
|
+
* }
|
|
15251
|
+
* ```
|
|
15252
|
+
*
|
|
15253
|
+
* **Key Features:**
|
|
15254
|
+
*
|
|
15255
|
+
* **1. Real-time State Management**
|
|
15256
|
+
* - Create global stores shared across all players: `kmClient.store()`
|
|
15257
|
+
* - Create local stores for client-side data: `kmClient.localStore()`
|
|
15258
|
+
* - Automatic synchronization and conflict resolution
|
|
15259
|
+
* - Use `useSnapshot()` from Valtio for reactive React components
|
|
15260
|
+
*
|
|
15261
|
+
* **2. Atomic Transactions**
|
|
15262
|
+
* ```typescript
|
|
15263
|
+
* // Update multiple stores atomically
|
|
15264
|
+
* await kmClient.transact([playerStore, gameStore], ([player, game]) => {
|
|
15265
|
+
* player.score += 10;
|
|
15266
|
+
* game.lastUpdate = kmClient.serverTimestamp();
|
|
15267
|
+
* });
|
|
15268
|
+
* ```
|
|
15269
|
+
*
|
|
15270
|
+
* **3. AI Integration (No API keys required)**
|
|
15271
|
+
* ```typescript
|
|
15272
|
+
* // Generate text
|
|
15273
|
+
* const story = await kmClient.ai.chat({
|
|
15274
|
+
* model: 'gpt-4o',
|
|
15275
|
+
* userPrompt: 'Write a quest description',
|
|
15276
|
+
* temperature: 0.8
|
|
15277
|
+
* });
|
|
15278
|
+
*
|
|
15279
|
+
* // Generate structured data
|
|
15280
|
+
* interface Quest { title: string; reward: number; }
|
|
15281
|
+
* const quest = await kmClient.ai.generateJson<Quest>({
|
|
15282
|
+
* userPrompt: 'Create a level 5 quest'
|
|
15283
|
+
* });
|
|
15284
|
+
*
|
|
15285
|
+
* // Modify images
|
|
15286
|
+
* const modified = await kmClient.ai.modifyImage(url, 'Make it pixel art');
|
|
15287
|
+
* ```
|
|
15288
|
+
*
|
|
15289
|
+
* **4. Cloud Storage**
|
|
15290
|
+
* ```typescript
|
|
15291
|
+
* // Upload files with tags
|
|
15292
|
+
* const upload = await kmClient.storage.upload('avatar.jpg', blob, ['profile']);
|
|
15293
|
+
*
|
|
15294
|
+
* // Query uploads
|
|
15295
|
+
* const images = await kmClient.storage.listUploads({
|
|
15296
|
+
* clientId: kmClient.id,
|
|
15297
|
+
* mimeTypes: ['image/jpeg', 'image/png']
|
|
15298
|
+
* });
|
|
15299
|
+
* ```
|
|
15300
|
+
*
|
|
15301
|
+
* **5. Leaderboards**
|
|
15302
|
+
* ```typescript
|
|
15303
|
+
* // Submit score (replaces previous entry)
|
|
15304
|
+
* await kmClient.leaderboard.upsertEntry(
|
|
15305
|
+
* 'high-scores',
|
|
15306
|
+
* 'desc',
|
|
15307
|
+
* 1500,
|
|
15308
|
+
* { playerName: 'Alice' },
|
|
15309
|
+
* {}
|
|
15310
|
+
* );
|
|
15311
|
+
*
|
|
15312
|
+
* // Get top 10
|
|
15313
|
+
* const top10 = await kmClient.leaderboard.listEntries('high-scores', 'desc', 0, 10);
|
|
15314
|
+
* ```
|
|
15315
|
+
*
|
|
15316
|
+
* **6. Presence Tracking**
|
|
15317
|
+
* ```typescript
|
|
15318
|
+
* // Track online players
|
|
15319
|
+
* const onlineClientIds = useSnapshot(gameStore.connections).clientIds;
|
|
15320
|
+
* const isPlayerOnline = onlineClientIds.has(playerId);
|
|
15321
|
+
* ```
|
|
15322
|
+
*
|
|
15323
|
+
* **Best Practices:**
|
|
15324
|
+
* - Always use `kmClient.serverTimestamp()` for time-sensitive operations
|
|
15325
|
+
* - Prefer Records over Arrays: `Record<string, T>` with timestamp keys
|
|
15326
|
+
* - Use `kmClient.transact()` for all state updates to ensure atomicity
|
|
15327
|
+
* - Tag uploads for easy filtering and organization
|
|
15328
|
+
* - Use local stores for client-side settings and preferences
|
|
15329
|
+
* - Leverage TypeScript generics for type-safe stores
|
|
15330
|
+
*
|
|
15331
|
+
* **Events:**
|
|
15332
|
+
* - `connected`: Fired when client connects/reconnects to server
|
|
15333
|
+
* - `disconnected`: Fired when connection is lost
|
|
15334
|
+
*
|
|
15335
|
+
* @template ClientContextT The type of client context data (custom user data from your backend)
|
|
15336
|
+
*
|
|
15337
|
+
* @example
|
|
15338
|
+
* ```typescript
|
|
15339
|
+
* // Listen for connection events
|
|
15340
|
+
* kmClient.on('connected', () => {
|
|
15341
|
+
* console.log('Connected to Kokimoki!');
|
|
15342
|
+
* });
|
|
15343
|
+
*
|
|
15344
|
+
* kmClient.on('disconnected', () => {
|
|
15345
|
+
* console.log('Connection lost, will auto-reconnect...');
|
|
15346
|
+
* });
|
|
15347
|
+
* ```
|
|
15348
|
+
*/
|
|
14866
15349
|
class KokimokiClient extends EventEmitter$1 {
|
|
14867
15350
|
host;
|
|
14868
15351
|
appId;
|
|
@@ -14889,6 +15372,9 @@ class KokimokiClient extends EventEmitter$1 {
|
|
|
14889
15372
|
_pingInterval;
|
|
14890
15373
|
_clientTokenKey = "KM_TOKEN";
|
|
14891
15374
|
_editorContext;
|
|
15375
|
+
_ai;
|
|
15376
|
+
_storage;
|
|
15377
|
+
_leaderboard;
|
|
14892
15378
|
constructor(host, appId, code = "") {
|
|
14893
15379
|
super();
|
|
14894
15380
|
this.host = host;
|
|
@@ -14898,6 +15384,9 @@ class KokimokiClient extends EventEmitter$1 {
|
|
|
14898
15384
|
const secure = this.host.indexOf(":") === -1;
|
|
14899
15385
|
this._wsUrl = `ws${secure ? "s" : ""}://${this.host}`;
|
|
14900
15386
|
this._apiUrl = `http${secure ? "s" : ""}://${this.host}`;
|
|
15387
|
+
// Initialize modules
|
|
15388
|
+
this._ai = new KokimokiAi(this);
|
|
15389
|
+
this._leaderboard = new KokimokiLeaderboard(this);
|
|
14901
15390
|
// Set up ping interval
|
|
14902
15391
|
const pingMsg = new WsMessageWriter();
|
|
14903
15392
|
pingMsg.writeInt32(WsMessageType.Ping);
|
|
@@ -14958,6 +15447,9 @@ class KokimokiClient extends EventEmitter$1 {
|
|
|
14958
15447
|
}
|
|
14959
15448
|
return this._clientContext;
|
|
14960
15449
|
}
|
|
15450
|
+
/**
|
|
15451
|
+
* Indicates whether the client is currently connected to the server.
|
|
15452
|
+
*/
|
|
14961
15453
|
get connected() {
|
|
14962
15454
|
return this._connected;
|
|
14963
15455
|
}
|
|
@@ -14967,9 +15459,21 @@ class KokimokiClient extends EventEmitter$1 {
|
|
|
14967
15459
|
}
|
|
14968
15460
|
return this._ws;
|
|
14969
15461
|
}
|
|
15462
|
+
/**
|
|
15463
|
+
* Indicates whether the client is running in editor/development mode.
|
|
15464
|
+
*/
|
|
14970
15465
|
get isEditor() {
|
|
14971
15466
|
return !!this._editorContext;
|
|
14972
15467
|
}
|
|
15468
|
+
/**
|
|
15469
|
+
* Establishes a connection to the Kokimoki server.
|
|
15470
|
+
*
|
|
15471
|
+
* Handles authentication, WebSocket setup, and automatic reconnection.
|
|
15472
|
+
* If already connecting, returns the existing connection promise.
|
|
15473
|
+
*
|
|
15474
|
+
* @returns A promise that resolves when the connection is established.
|
|
15475
|
+
* @throws Error if the connection fails.
|
|
15476
|
+
*/
|
|
14973
15477
|
async connect() {
|
|
14974
15478
|
if (this._connectPromise) {
|
|
14975
15479
|
return await this._connectPromise;
|
|
@@ -15174,10 +15678,21 @@ class KokimokiClient extends EventEmitter$1 {
|
|
|
15174
15678
|
}
|
|
15175
15679
|
}
|
|
15176
15680
|
}
|
|
15681
|
+
/**
|
|
15682
|
+
* Gets the current server timestamp, accounting for client-server time offset.
|
|
15683
|
+
*
|
|
15684
|
+
* @returns The current server timestamp in milliseconds.
|
|
15685
|
+
*/
|
|
15177
15686
|
serverTimestamp() {
|
|
15178
15687
|
return Date.now() - this._serverTimeOffset;
|
|
15179
15688
|
}
|
|
15180
|
-
|
|
15689
|
+
/**
|
|
15690
|
+
* Sends a Y.js update to a specific room.
|
|
15691
|
+
*
|
|
15692
|
+
* @param room - The name of the room to update.
|
|
15693
|
+
* @param update - The Y.js update as a Uint8Array.
|
|
15694
|
+
* @returns A promise that resolves with the server response.
|
|
15695
|
+
*/
|
|
15181
15696
|
async patchRoomState(room, update) {
|
|
15182
15697
|
const res = await fetch(`${this._apiUrl}/rooms/${room}`, {
|
|
15183
15698
|
method: "PATCH",
|
|
@@ -15186,81 +15701,6 @@ class KokimokiClient extends EventEmitter$1 {
|
|
|
15186
15701
|
});
|
|
15187
15702
|
return await res.json();
|
|
15188
15703
|
}
|
|
15189
|
-
// Storage
|
|
15190
|
-
async createUpload(name, blob, tags) {
|
|
15191
|
-
const res = await fetch(`${this._apiUrl}/uploads`, {
|
|
15192
|
-
method: "POST",
|
|
15193
|
-
headers: this.apiHeaders,
|
|
15194
|
-
body: JSON.stringify({
|
|
15195
|
-
name,
|
|
15196
|
-
size: blob.size,
|
|
15197
|
-
mimeType: blob.type,
|
|
15198
|
-
tags,
|
|
15199
|
-
}),
|
|
15200
|
-
});
|
|
15201
|
-
return await res.json();
|
|
15202
|
-
}
|
|
15203
|
-
async uploadChunks(blob, chunkSize, signedUrls) {
|
|
15204
|
-
return await Promise.all(signedUrls.map(async (url, index) => {
|
|
15205
|
-
const start = index * chunkSize;
|
|
15206
|
-
const end = Math.min(start + chunkSize, blob.size);
|
|
15207
|
-
const chunk = blob.slice(start, end);
|
|
15208
|
-
const res = await fetch(url, { method: "PUT", body: chunk });
|
|
15209
|
-
return JSON.parse(res.headers.get("ETag") || '""');
|
|
15210
|
-
}));
|
|
15211
|
-
}
|
|
15212
|
-
async completeUpload(id, etags) {
|
|
15213
|
-
const res = await fetch(`${this._apiUrl}/uploads/${id}`, {
|
|
15214
|
-
method: "PUT",
|
|
15215
|
-
headers: this.apiHeaders,
|
|
15216
|
-
body: JSON.stringify({ etags }),
|
|
15217
|
-
});
|
|
15218
|
-
return await res.json();
|
|
15219
|
-
}
|
|
15220
|
-
async upload(name, blob, tags = []) {
|
|
15221
|
-
const { id, chunkSize, urls } = await this.createUpload(name, blob, tags);
|
|
15222
|
-
const etags = await this.uploadChunks(blob, chunkSize, urls);
|
|
15223
|
-
return await this.completeUpload(id, etags);
|
|
15224
|
-
}
|
|
15225
|
-
async updateUpload(id, update) {
|
|
15226
|
-
const res = await fetch(`${this._apiUrl}/uploads/${id}`, {
|
|
15227
|
-
method: "PUT",
|
|
15228
|
-
headers: this.apiHeaders,
|
|
15229
|
-
body: JSON.stringify(update),
|
|
15230
|
-
});
|
|
15231
|
-
return await res.json();
|
|
15232
|
-
}
|
|
15233
|
-
async listUploads(filter = {}, skip = 0, limit = 100) {
|
|
15234
|
-
const url = new URL("/uploads", this._apiUrl);
|
|
15235
|
-
url.searchParams.set("skip", skip.toString());
|
|
15236
|
-
url.searchParams.set("limit", limit.toString());
|
|
15237
|
-
if (filter.clientId) {
|
|
15238
|
-
url.searchParams.set("clientId", filter.clientId);
|
|
15239
|
-
}
|
|
15240
|
-
if (filter.mimeTypes) {
|
|
15241
|
-
url.searchParams.set("mimeTypes", filter.mimeTypes.join());
|
|
15242
|
-
}
|
|
15243
|
-
if (filter.tags) {
|
|
15244
|
-
url.searchParams.set("tags", filter.tags.join());
|
|
15245
|
-
}
|
|
15246
|
-
const res = await fetch(url.href, {
|
|
15247
|
-
headers: this.apiHeaders,
|
|
15248
|
-
});
|
|
15249
|
-
return await res.json();
|
|
15250
|
-
}
|
|
15251
|
-
async deleteUpload(id) {
|
|
15252
|
-
const res = await fetch(`${this._apiUrl}/uploads/${id}`, {
|
|
15253
|
-
method: "DELETE",
|
|
15254
|
-
headers: this.apiHeaders,
|
|
15255
|
-
});
|
|
15256
|
-
return await res.json();
|
|
15257
|
-
}
|
|
15258
|
-
async exposeScriptingContext(context) {
|
|
15259
|
-
// @ts-ignore
|
|
15260
|
-
window.KM_SCRIPTING_CONTEXT = context;
|
|
15261
|
-
// @ts-ignore
|
|
15262
|
-
window.dispatchEvent(new CustomEvent("km:scriptingContextExposed"));
|
|
15263
|
-
}
|
|
15264
15704
|
async sendSubscribeReq(roomName, mode) {
|
|
15265
15705
|
// Set up sync resolver
|
|
15266
15706
|
const reqId = ++this._messageId;
|
|
@@ -15295,6 +15735,15 @@ class KokimokiClient extends EventEmitter$1 {
|
|
|
15295
15735
|
this.ws.send(msg.getBuffer());
|
|
15296
15736
|
});
|
|
15297
15737
|
}
|
|
15738
|
+
/**
|
|
15739
|
+
* Joins a store by subscribing to its corresponding room.
|
|
15740
|
+
*
|
|
15741
|
+
* If already joined, this method does nothing. For local stores, initializes
|
|
15742
|
+
* the store locally. For remote stores, sends a subscription request to the server.
|
|
15743
|
+
*
|
|
15744
|
+
* @param store - The KokimokiStore to join.
|
|
15745
|
+
* @template T - The type of the store's state object.
|
|
15746
|
+
*/
|
|
15298
15747
|
async join(store) {
|
|
15299
15748
|
let subscription = this._subscriptionsByName.get(store.roomName);
|
|
15300
15749
|
if (!subscription) {
|
|
@@ -15316,6 +15765,14 @@ class KokimokiClient extends EventEmitter$1 {
|
|
|
15316
15765
|
await store.onJoin(this);
|
|
15317
15766
|
}
|
|
15318
15767
|
}
|
|
15768
|
+
/**
|
|
15769
|
+
* Leaves a store by unsubscribing from its corresponding room.
|
|
15770
|
+
*
|
|
15771
|
+
* Triggers the store's `onBeforeLeave` and `onLeave` lifecycle hooks.
|
|
15772
|
+
*
|
|
15773
|
+
* @param store - The KokimokiStore to leave.
|
|
15774
|
+
* @template T - The type of the store's state object.
|
|
15775
|
+
*/
|
|
15319
15776
|
async leave(store) {
|
|
15320
15777
|
const subscription = this._subscriptionsByName.get(store.roomName);
|
|
15321
15778
|
if (subscription) {
|
|
@@ -15328,6 +15785,27 @@ class KokimokiClient extends EventEmitter$1 {
|
|
|
15328
15785
|
store.onLeave(this);
|
|
15329
15786
|
}
|
|
15330
15787
|
}
|
|
15788
|
+
/**
|
|
15789
|
+
* Executes a transaction across one or more stores.
|
|
15790
|
+
*
|
|
15791
|
+
* Provides proxies to the stores that track changes. All changes are batched
|
|
15792
|
+
* and sent to the server atomically. The transaction ensures consistency across
|
|
15793
|
+
* multiple stores.
|
|
15794
|
+
*
|
|
15795
|
+
* @param stores - Array of stores to include in the transaction.
|
|
15796
|
+
* @param handler - Function that receives store proxies and performs modifications.
|
|
15797
|
+
* @returns A promise that resolves with the return value of the handler.
|
|
15798
|
+
* @template TStores - Tuple type of the stores array.
|
|
15799
|
+
* @template ReturnT - The return type of the handler function.
|
|
15800
|
+
*
|
|
15801
|
+
* @example
|
|
15802
|
+
* ```ts
|
|
15803
|
+
* await client.transact([playerStore, gameStore], ([player, game]) => {
|
|
15804
|
+
* player.score += 10;
|
|
15805
|
+
* game.lastUpdate = Date.now();
|
|
15806
|
+
* });
|
|
15807
|
+
* ```
|
|
15808
|
+
*/
|
|
15331
15809
|
async transact(stores, handler) {
|
|
15332
15810
|
// if (!this._connected) {
|
|
15333
15811
|
// throw new Error("Client not connected");
|
|
@@ -15402,6 +15880,11 @@ class KokimokiClient extends EventEmitter$1 {
|
|
|
15402
15880
|
}
|
|
15403
15881
|
return returnValue;
|
|
15404
15882
|
}
|
|
15883
|
+
/**
|
|
15884
|
+
* Closes the client connection and cleans up resources.
|
|
15885
|
+
*
|
|
15886
|
+
* Disables automatic reconnection, closes the WebSocket, and clears all intervals.
|
|
15887
|
+
*/
|
|
15405
15888
|
async close() {
|
|
15406
15889
|
this._autoReconnect = false;
|
|
15407
15890
|
if (this._ws) {
|
|
@@ -15411,7 +15894,14 @@ class KokimokiClient extends EventEmitter$1 {
|
|
|
15411
15894
|
clearInterval(this._pingInterval);
|
|
15412
15895
|
}
|
|
15413
15896
|
}
|
|
15414
|
-
|
|
15897
|
+
/**
|
|
15898
|
+
* Gets the internal room hash identifier for a store.
|
|
15899
|
+
*
|
|
15900
|
+
* @param store - The store to get the room hash for.
|
|
15901
|
+
* @returns The room hash as a number.
|
|
15902
|
+
* @throws Error if the store hasn't been joined.
|
|
15903
|
+
* @template T - The type of the store's state object.
|
|
15904
|
+
*/
|
|
15415
15905
|
getRoomHash(store) {
|
|
15416
15906
|
const subscription = this._subscriptionsByName.get(store.roomName);
|
|
15417
15907
|
if (!subscription) {
|
|
@@ -15419,8 +15909,20 @@ class KokimokiClient extends EventEmitter$1 {
|
|
|
15419
15909
|
}
|
|
15420
15910
|
return subscription.roomHash;
|
|
15421
15911
|
}
|
|
15422
|
-
/**
|
|
15423
|
-
|
|
15912
|
+
/**
|
|
15913
|
+
* Creates a new remote store synchronized with the server.
|
|
15914
|
+
*
|
|
15915
|
+
* @param name - The name of the room/store.
|
|
15916
|
+
* @param defaultState - The initial state of the store.
|
|
15917
|
+
* @param autoJoin - Whether to automatically join the store (default: true).
|
|
15918
|
+
* @returns A new KokimokiStore instance.
|
|
15919
|
+
* @template T - The type of the store's state object.
|
|
15920
|
+
*
|
|
15921
|
+
* @example
|
|
15922
|
+
* ```ts
|
|
15923
|
+
* const gameStore = client.store('game', { players: [], score: 0 });
|
|
15924
|
+
* ```
|
|
15925
|
+
*/
|
|
15424
15926
|
store(name, defaultState, autoJoin = true) {
|
|
15425
15927
|
const store = new KokimokiStore(name, defaultState);
|
|
15426
15928
|
if (autoJoin) {
|
|
@@ -15430,7 +15932,22 @@ class KokimokiClient extends EventEmitter$1 {
|
|
|
15430
15932
|
}
|
|
15431
15933
|
return store;
|
|
15432
15934
|
}
|
|
15433
|
-
|
|
15935
|
+
/**
|
|
15936
|
+
* Creates a new local store that persists only in the client's browser.
|
|
15937
|
+
*
|
|
15938
|
+
* Local stores are automatically joined and are not synchronized with the server.
|
|
15939
|
+
* Data is stored locally per client and app.
|
|
15940
|
+
*
|
|
15941
|
+
* @param name - The name of the local store.
|
|
15942
|
+
* @param defaultState - The initial state of the store.
|
|
15943
|
+
* @returns A new KokimokiLocalStore instance.
|
|
15944
|
+
* @template T - The type of the store's state object.
|
|
15945
|
+
*
|
|
15946
|
+
* @example
|
|
15947
|
+
* ```ts
|
|
15948
|
+
* const settingsStore = client.localStore('settings', { volume: 0.5, theme: 'dark' });
|
|
15949
|
+
* ```
|
|
15950
|
+
*/
|
|
15434
15951
|
localStore(name, defaultState) {
|
|
15435
15952
|
const store = new KokimokiLocalStore(name, defaultState);
|
|
15436
15953
|
this.join(store)
|
|
@@ -15438,122 +15955,18 @@ class KokimokiClient extends EventEmitter$1 {
|
|
|
15438
15955
|
.catch(() => { });
|
|
15439
15956
|
return store;
|
|
15440
15957
|
}
|
|
15441
|
-
// // queue
|
|
15442
|
-
// queue<T extends S.Generic<unknown>>(
|
|
15443
|
-
// name: string,
|
|
15444
|
-
// schema: T,
|
|
15445
|
-
// mode: RoomSubscriptionMode,
|
|
15446
|
-
// autoJoin = true
|
|
15447
|
-
// ) {
|
|
15448
|
-
// const queue = new KokimokiQueue<T>(name, schema, mode);
|
|
15449
|
-
// if (autoJoin) {
|
|
15450
|
-
// this.join(queue)
|
|
15451
|
-
// .then(() => {})
|
|
15452
|
-
// .catch(() => {});
|
|
15453
|
-
// }
|
|
15454
|
-
// return queue;
|
|
15455
|
-
// }
|
|
15456
|
-
// awareness
|
|
15457
|
-
awareness(name, initialData, autoJoin = true) {
|
|
15458
|
-
const awareness = new KokimokiAwareness(name, initialData);
|
|
15459
|
-
if (autoJoin) {
|
|
15460
|
-
this.join(awareness)
|
|
15461
|
-
.then(() => { })
|
|
15462
|
-
.catch(() => { });
|
|
15463
|
-
}
|
|
15464
|
-
return awareness;
|
|
15465
|
-
}
|
|
15466
|
-
// // req-res
|
|
15467
|
-
// reqRes<Req extends S.Generic<unknown>, Res extends S.Generic<unknown>>(
|
|
15468
|
-
// serviceName: string,
|
|
15469
|
-
// reqSchema: Req,
|
|
15470
|
-
// resSchema: Res,
|
|
15471
|
-
// handleRequest: (
|
|
15472
|
-
// payload: Req["defaultValue"]
|
|
15473
|
-
// ) => Promise<Res["defaultValue"]>
|
|
15474
|
-
// ) {
|
|
15475
|
-
// return new KokimokiReqRes(
|
|
15476
|
-
// this,
|
|
15477
|
-
// serviceName,
|
|
15478
|
-
// reqSchema,
|
|
15479
|
-
// resSchema,
|
|
15480
|
-
// handleRequest
|
|
15481
|
-
// );
|
|
15482
|
-
// }
|
|
15483
|
-
/**
|
|
15484
|
-
* Add a new entry to a leaderboard
|
|
15485
|
-
* @param leaderboardName
|
|
15486
|
-
* @param score
|
|
15487
|
-
* @param metadata
|
|
15488
|
-
* @returns
|
|
15489
|
-
*/
|
|
15490
|
-
async insertLeaderboardEntry(leaderboardName, sortDir, score, metadata, privateMetadata) {
|
|
15491
|
-
const res = await fetch(`${this._apiUrl}/leaderboard-entries`, {
|
|
15492
|
-
method: "POST",
|
|
15493
|
-
headers: this.apiHeaders,
|
|
15494
|
-
body: JSON.stringify({
|
|
15495
|
-
leaderboardName,
|
|
15496
|
-
sortDir,
|
|
15497
|
-
score,
|
|
15498
|
-
metadata,
|
|
15499
|
-
privateMetadata,
|
|
15500
|
-
upsert: false,
|
|
15501
|
-
}),
|
|
15502
|
-
});
|
|
15503
|
-
return await res.json();
|
|
15504
|
-
}
|
|
15505
|
-
/**
|
|
15506
|
-
* Add or update latest entry to a leaderboard
|
|
15507
|
-
* @param leaderboardName
|
|
15508
|
-
* @param score
|
|
15509
|
-
* @param metadata
|
|
15510
|
-
* @param privateMetadata Can only be read using the leaderboard API
|
|
15511
|
-
* @returns
|
|
15512
|
-
*/
|
|
15513
|
-
async upsertLeaderboardEntry(leaderboardName, sortDir, score, metadata, privateMetadata) {
|
|
15514
|
-
const res = await fetch(`${this._apiUrl}/leaderboard-entries`, {
|
|
15515
|
-
method: "POST",
|
|
15516
|
-
headers: this.apiHeaders,
|
|
15517
|
-
body: JSON.stringify({
|
|
15518
|
-
leaderboardName,
|
|
15519
|
-
sortDir,
|
|
15520
|
-
score,
|
|
15521
|
-
metadata,
|
|
15522
|
-
privateMetadata,
|
|
15523
|
-
upsert: true,
|
|
15524
|
-
}),
|
|
15525
|
-
});
|
|
15526
|
-
return await res.json();
|
|
15527
|
-
}
|
|
15528
|
-
/**
|
|
15529
|
-
* List entries in a leaderboard
|
|
15530
|
-
* @param leaderboardName
|
|
15531
|
-
* @param skip
|
|
15532
|
-
* @param limit
|
|
15533
|
-
* @returns
|
|
15534
|
-
*/
|
|
15535
|
-
async listLeaderboardEntries(leaderboardName, sortDir, skip = 0, limit = 100) {
|
|
15536
|
-
const encodedLeaderboardName = encodeURIComponent(leaderboardName);
|
|
15537
|
-
const res = await fetch(`${this._apiUrl}/leaderboard-entries?leaderboardName=${encodedLeaderboardName}&sortDir=${sortDir}&skip=${skip}&limit=${limit}`, {
|
|
15538
|
-
headers: this.apiHeaders,
|
|
15539
|
-
});
|
|
15540
|
-
return await res.json();
|
|
15541
|
-
}
|
|
15542
15958
|
/**
|
|
15543
|
-
*
|
|
15544
|
-
*
|
|
15545
|
-
* @param
|
|
15546
|
-
* @param
|
|
15547
|
-
|
|
15548
|
-
|
|
15549
|
-
|
|
15550
|
-
|
|
15551
|
-
|
|
15552
|
-
|
|
15553
|
-
|
|
15554
|
-
}
|
|
15555
|
-
/**
|
|
15556
|
-
* Send app data via webhook
|
|
15959
|
+
* Sends app data to the server via webhook for external processing.
|
|
15960
|
+
*
|
|
15961
|
+
* @param event - The name of the webhook event.
|
|
15962
|
+
* @param data - The data to send with the webhook.
|
|
15963
|
+
* @returns A promise that resolves with the job ID.
|
|
15964
|
+
* @template T - The type of the data being sent.
|
|
15965
|
+
*
|
|
15966
|
+
* @example
|
|
15967
|
+
* ```ts
|
|
15968
|
+
* await client.sendWebhook('game-ended', { winner: 'player1', score: 100 });
|
|
15969
|
+
* ```
|
|
15557
15970
|
*/
|
|
15558
15971
|
async sendWebhook(event, data) {
|
|
15559
15972
|
const res = await fetch(`${this._apiUrl}/webhooks`, {
|
|
@@ -15564,52 +15977,33 @@ class KokimokiClient extends EventEmitter$1 {
|
|
|
15564
15977
|
return await res.json();
|
|
15565
15978
|
}
|
|
15566
15979
|
/**
|
|
15567
|
-
*
|
|
15980
|
+
* Access AI capabilities including text generation, structured JSON output, and image modification.
|
|
15568
15981
|
*/
|
|
15569
|
-
|
|
15570
|
-
|
|
15571
|
-
|
|
15572
|
-
headers: this.apiHeaders,
|
|
15573
|
-
body: JSON.stringify({
|
|
15574
|
-
systemPrompt,
|
|
15575
|
-
userPrompt,
|
|
15576
|
-
temperature,
|
|
15577
|
-
maxTokens,
|
|
15578
|
-
}),
|
|
15579
|
-
});
|
|
15580
|
-
if (!res.ok) {
|
|
15581
|
-
throw await res.json();
|
|
15982
|
+
get ai() {
|
|
15983
|
+
if (!this._ai) {
|
|
15984
|
+
throw new Error("AI client not initialized");
|
|
15582
15985
|
}
|
|
15583
|
-
return
|
|
15986
|
+
return this._ai;
|
|
15584
15987
|
}
|
|
15585
15988
|
/**
|
|
15586
|
-
*
|
|
15989
|
+
* Access file upload and management for media files, images, and user-generated content.
|
|
15587
15990
|
*/
|
|
15588
|
-
|
|
15589
|
-
|
|
15590
|
-
|
|
15591
|
-
headers: this.apiHeaders,
|
|
15592
|
-
body: JSON.stringify({ baseImageUrl, prompt, tags }),
|
|
15593
|
-
});
|
|
15594
|
-
if (!res.ok) {
|
|
15595
|
-
throw await res.json();
|
|
15991
|
+
get storage() {
|
|
15992
|
+
if (!this._storage) {
|
|
15993
|
+
throw new Error("Storage client not initialized");
|
|
15596
15994
|
}
|
|
15597
|
-
return
|
|
15995
|
+
return this._storage;
|
|
15598
15996
|
}
|
|
15599
15997
|
/**
|
|
15600
|
-
*
|
|
15998
|
+
* Access player ranking and score tracking with efficient queries and pagination.
|
|
15601
15999
|
*/
|
|
15602
|
-
|
|
15603
|
-
|
|
15604
|
-
|
|
15605
|
-
});
|
|
15606
|
-
if (!res.ok) {
|
|
15607
|
-
throw await res.json();
|
|
16000
|
+
get leaderboard() {
|
|
16001
|
+
if (!this._leaderboard) {
|
|
16002
|
+
throw new Error("Leaderboard client not initialized");
|
|
15608
16003
|
}
|
|
15609
|
-
|
|
15610
|
-
return { status, config };
|
|
16004
|
+
return this._leaderboard;
|
|
15611
16005
|
}
|
|
15612
16006
|
}
|
|
15613
16007
|
|
|
15614
|
-
export {
|
|
16008
|
+
export { KokimokiClient, KokimokiStore, RoomSubscription, RoomSubscriptionMode };
|
|
15615
16009
|
//# sourceMappingURL=kokimoki.min.js.map
|