@kokimoki/app 3.1.2 → 3.1.4
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/README.md +1 -2
- package/dist/core/kokimoki-client.d.ts +4 -4
- package/dist/core/kokimoki-client.js +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/kokimoki.min.d.ts +3 -321
- package/dist/kokimoki.min.js +763 -2014
- package/dist/kokimoki.min.js.map +1 -1
- package/dist/services/kokimoki-ai.d.ts +15 -53
- package/dist/services/kokimoki-ai.js +11 -82
- package/dist/services/kokimoki-i18n.d.ts +25 -97
- package/dist/services/kokimoki-i18n.js +30 -99
- package/dist/utils/valtio.d.ts +2 -5
- package/dist/utils/valtio.js +4 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/kokimoki-ai.instructions.md +33 -88
- package/docs/kokimoki-dynamic-stores.instructions.md +3 -3
- package/docs/kokimoki-i18n.instructions.md +8 -115
- package/docs/kokimoki-sdk.instructions.md +46 -7
- package/package.json +8 -2
- package/dist/fields.d.ts +0 -110
- package/dist/fields.js +0 -158
- package/dist/kokimoki-ai.d.ts +0 -153
- package/dist/kokimoki-ai.js +0 -164
- package/dist/kokimoki-awareness.d.ts +0 -21
- package/dist/kokimoki-awareness.js +0 -48
- package/dist/kokimoki-client-refactored.d.ts +0 -80
- package/dist/kokimoki-client-refactored.js +0 -400
- package/dist/kokimoki-client.d.ts +0 -362
- package/dist/kokimoki-client.js +0 -823
- package/dist/kokimoki-leaderboard.d.ts +0 -175
- package/dist/kokimoki-leaderboard.js +0 -203
- package/dist/kokimoki-local-store.d.ts +0 -11
- package/dist/kokimoki-local-store.js +0 -40
- package/dist/kokimoki-queue.d.ts +0 -0
- package/dist/kokimoki-queue.js +0 -38
- package/dist/kokimoki-req-res.d.ts +0 -0
- package/dist/kokimoki-req-res.js +0 -198
- package/dist/kokimoki-schema.d.ts +0 -113
- package/dist/kokimoki-schema.js +0 -162
- package/dist/kokimoki-storage.d.ts +0 -156
- package/dist/kokimoki-storage.js +0 -208
- package/dist/kokimoki-store.d.ts +0 -23
- package/dist/kokimoki-store.js +0 -117
- package/dist/kokimoki-transaction.d.ts +0 -18
- package/dist/kokimoki-transaction.js +0 -143
- package/dist/message-queue.d.ts +0 -8
- package/dist/message-queue.js +0 -19
- package/dist/room-subscription-mode.d.ts +0 -5
- package/dist/room-subscription-mode.js +0 -6
- package/dist/room-subscription.d.ts +0 -15
- package/dist/room-subscription.js +0 -52
- package/dist/synced-schema.d.ts +0 -74
- package/dist/synced-schema.js +0 -83
- package/dist/synced-store.d.ts +0 -10
- package/dist/synced-store.js +0 -9
- package/dist/synced-types.d.ts +0 -47
- package/dist/synced-types.js +0 -67
- package/dist/ws-message-reader.d.ts +0 -11
- package/dist/ws-message-reader.js +0 -36
- package/dist/ws-message-type copy.d.ts +0 -6
- package/dist/ws-message-type copy.js +0 -7
- package/dist/ws-message-type.d.ts +0 -11
- package/dist/ws-message-type.js +0 -12
- package/dist/ws-message-writer.d.ts +0 -9
- package/dist/ws-message-writer.js +0 -45
|
@@ -6,8 +6,7 @@ applyTo: "**/*.ts,**/*.tsx"
|
|
|
6
6
|
# AI Integration
|
|
7
7
|
|
|
8
8
|
Built-in methods for AI text generation and image transformation. No API keys required.
|
|
9
|
-
All generation methods are **async job-based** - they submit a job and return immediately with a `jobId`.
|
|
10
|
-
Use the corresponding poll method to wait for the result.
|
|
9
|
+
All generation methods are **async job-based** - they submit a job and return immediately with a `jobId`. Use `getJob()` to poll for the result.
|
|
11
10
|
|
|
12
11
|
For core SDK concepts, see [Kokimoki SDK](./kokimoki-sdk.instructions.md).
|
|
13
12
|
|
|
@@ -19,7 +18,7 @@ Access AI API via `kmClient.ai`.
|
|
|
19
18
|
- Text generation with configurable creativity (temperature)
|
|
20
19
|
- Structured JSON output with Zod schema validation
|
|
21
20
|
- AI-powered image generation and modification
|
|
22
|
-
- Job-based async processing
|
|
21
|
+
- Job-based async processing
|
|
23
22
|
- Resumable after page reload (persist jobId)
|
|
24
23
|
|
|
25
24
|
## Available Models
|
|
@@ -45,7 +44,7 @@ Access AI API via `kmClient.ai`.
|
|
|
45
44
|
|
|
46
45
|
### ai.generateText(req): Promise<{ jobId: string }>
|
|
47
46
|
|
|
48
|
-
Submits a text generation job. Use `
|
|
47
|
+
Submits a text generation job. Use `getJob()` to poll the result.
|
|
49
48
|
|
|
50
49
|
**Parameters:**
|
|
51
50
|
|
|
@@ -67,12 +66,13 @@ const { jobId } = await kmClient.ai.generateText({
|
|
|
67
66
|
});
|
|
68
67
|
|
|
69
68
|
// Poll for result
|
|
70
|
-
const
|
|
69
|
+
const job = await kmClient.ai.getJob<string>(jobId);
|
|
70
|
+
console.log("Generated quest:", job.result);
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
### ai.generateJson<T>(req): Promise<{ jobId: string }>
|
|
74
74
|
|
|
75
|
-
Submits a JSON generation job with Zod schema validation. Use `
|
|
75
|
+
Submits a JSON generation job with Zod schema validation. Use `getJob<T>()` to wait for the result with type inference.
|
|
76
76
|
|
|
77
77
|
**Parameters:**
|
|
78
78
|
|
|
@@ -86,7 +86,7 @@ Submits a JSON generation job with Zod schema validation. Use `pollJson()` to wa
|
|
|
86
86
|
**Example:**
|
|
87
87
|
|
|
88
88
|
```typescript
|
|
89
|
-
import { z } from "
|
|
89
|
+
import { z } from "@kokimoki/app";
|
|
90
90
|
|
|
91
91
|
// Define schema with Zod
|
|
92
92
|
const enemySchema = z.object({
|
|
@@ -107,8 +107,8 @@ const { jobId } = await kmClient.ai.generateJson({
|
|
|
107
107
|
await saveJobId(jobId);
|
|
108
108
|
|
|
109
109
|
// Poll with schema for type inference and validation
|
|
110
|
-
const enemy = await kmClient.ai.
|
|
111
|
-
console.log(enemy.name, enemy.health); // Fully typed!
|
|
110
|
+
const enemy = await kmClient.ai.getJob<Enemy>(jobId);
|
|
111
|
+
console.log(enemy.result?.name, enemy.result?.health); // Fully typed!
|
|
112
112
|
```
|
|
113
113
|
|
|
114
114
|
```typescript
|
|
@@ -127,13 +127,13 @@ const { jobId } = await kmClient.ai.generateJson({
|
|
|
127
127
|
prompt: "Create 5 history quiz questions with 4 options each",
|
|
128
128
|
});
|
|
129
129
|
|
|
130
|
-
const questions = await kmClient.ai.
|
|
131
|
-
questions.forEach((q) => console.log(q.question));
|
|
130
|
+
const questions = await kmClient.ai.getJob<typeof questionSchema>(jobId);
|
|
131
|
+
questions.result?.forEach((q) => console.log(q.question));
|
|
132
132
|
```
|
|
133
133
|
|
|
134
134
|
### ai.generateImage(req): Promise<{ jobId: string }>
|
|
135
135
|
|
|
136
|
-
Submits an image generation/modification job. Use `
|
|
136
|
+
Submits an image generation/modification job. Use `getJob()` to wait for the result.
|
|
137
137
|
The result is stored as `Upload` object (see [Storage](./kokimoki-storage.instructions.md)).
|
|
138
138
|
|
|
139
139
|
**Parameters:**
|
|
@@ -152,8 +152,8 @@ const { jobId } = await kmClient.ai.generateImage({
|
|
|
152
152
|
tags: ["background", "ai-generated"],
|
|
153
153
|
});
|
|
154
154
|
|
|
155
|
-
const
|
|
156
|
-
console.log(
|
|
155
|
+
const job = await kmClient.ai.getJob<string>(jobId);
|
|
156
|
+
console.log("Generated image:", job.result); // CDN URL to the generated image
|
|
157
157
|
```
|
|
158
158
|
|
|
159
159
|
```typescript
|
|
@@ -164,12 +164,13 @@ const { jobId } = await kmClient.ai.generateImage({
|
|
|
164
164
|
tags: ["art", "ai-generated"],
|
|
165
165
|
});
|
|
166
166
|
|
|
167
|
-
const
|
|
167
|
+
const job = await kmClient.ai.getJob<string>(jobId);
|
|
168
|
+
console.log("Generated image:", job.result); // CDN URL to the generated image
|
|
168
169
|
```
|
|
169
170
|
|
|
170
171
|
### ai.getJob(jobId): Promise<AiJob>
|
|
171
172
|
|
|
172
|
-
Get the current status of an AI job
|
|
173
|
+
Get the current status of an AI job.
|
|
173
174
|
|
|
174
175
|
**Parameters:**
|
|
175
176
|
|
|
@@ -199,73 +200,13 @@ if (job.status === "completed") {
|
|
|
199
200
|
}
|
|
200
201
|
```
|
|
201
202
|
|
|
202
|
-
### ai.pollText(jobId, options?): Promise<string>
|
|
203
|
-
|
|
204
|
-
Poll a text generation job until completion.
|
|
205
|
-
|
|
206
|
-
**Parameters:**
|
|
207
|
-
|
|
208
|
-
- **jobId**: `string` The job ID to poll
|
|
209
|
-
- **options.pollInterval**: `number` Polling interval in ms (default: 2000, min: 1000)
|
|
210
|
-
- **options.timeout**: `number` Timeout in ms (default: 120000)
|
|
211
|
-
- **options.onProgress**: `(status: AiJobStatus) => void` Progress callback
|
|
212
|
-
|
|
213
|
-
**Example:**
|
|
214
|
-
|
|
215
|
-
```typescript
|
|
216
|
-
const text = await kmClient.ai.pollText(jobId, {
|
|
217
|
-
timeout: 60000,
|
|
218
|
-
onProgress: (status) => console.log("Status:", status),
|
|
219
|
-
});
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
### ai.pollJson<T>(jobId, options): Promise<T>
|
|
223
|
-
|
|
224
|
-
Poll a JSON generation job until completion with schema validation.
|
|
225
|
-
|
|
226
|
-
**Parameters:**
|
|
227
|
-
|
|
228
|
-
- **jobId**: `string` The job ID to poll
|
|
229
|
-
- **options.schema**: `ZodType` Zod schema for validation and type inference (required)
|
|
230
|
-
- **options.pollInterval**: `number` Polling interval in ms (default: 2000, min: 1000)
|
|
231
|
-
- **options.timeout**: `number` Timeout in ms (default: 120000)
|
|
232
|
-
- **options.onProgress**: `(status: AiJobStatus) => void` Progress callback
|
|
233
|
-
|
|
234
|
-
**Example:**
|
|
235
|
-
|
|
236
|
-
```typescript
|
|
237
|
-
const enemy = await kmClient.ai.pollJson(jobId, {
|
|
238
|
-
schema: enemySchema,
|
|
239
|
-
timeout: 60000,
|
|
240
|
-
onProgress: (status) => console.log("Status:", status),
|
|
241
|
-
});
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
### ai.pollImage(jobId, options?): Promise<Upload>
|
|
245
|
-
|
|
246
|
-
Poll an image generation job until completion.
|
|
247
|
-
|
|
248
|
-
**Parameters:**
|
|
249
|
-
|
|
250
|
-
- **jobId**: `string` The job ID to poll
|
|
251
|
-
- **options.pollInterval**: `number` Polling interval in ms (default: 2000, min: 1000)
|
|
252
|
-
- **options.timeout**: `number` Timeout in ms (default: 120000)
|
|
253
|
-
- **options.onProgress**: `(status: AiJobStatus) => void` Progress callback
|
|
254
|
-
|
|
255
|
-
**Example:**
|
|
256
|
-
|
|
257
|
-
```typescript
|
|
258
|
-
const upload = await kmClient.ai.pollImage(jobId, {
|
|
259
|
-
timeout: 60000,
|
|
260
|
-
onProgress: (status) => console.log("Status:", status),
|
|
261
|
-
});
|
|
262
|
-
```
|
|
263
|
-
|
|
264
203
|
## Common Patterns
|
|
265
204
|
|
|
266
205
|
### Example: Persist Jobs Across Page Reloads
|
|
267
206
|
|
|
268
207
|
```typescript
|
|
208
|
+
import { snapshot } from "@kokimoki/app";
|
|
209
|
+
|
|
269
210
|
// Store jobId in local store for persistence
|
|
270
211
|
const { jobId } = await kmClient.ai.generateJson({
|
|
271
212
|
schema: questSchema,
|
|
@@ -277,7 +218,7 @@ await kmClient.transact([localStore], ([state]) => {
|
|
|
277
218
|
});
|
|
278
219
|
|
|
279
220
|
// On page load, resume polling if job is pending
|
|
280
|
-
const pendingJobId = localStore.proxy.pendingQuestJobId;
|
|
221
|
+
const pendingJobId = snapshot(localStore.proxy).pendingQuestJobId;
|
|
281
222
|
if (pendingJobId) {
|
|
282
223
|
const quest = await kmClient.ai.pollJson(pendingJobId, {
|
|
283
224
|
schema: questSchema,
|
|
@@ -297,20 +238,24 @@ const { jobId } = await kmClient.ai.generateText({
|
|
|
297
238
|
prompt: "Write a story",
|
|
298
239
|
});
|
|
299
240
|
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
241
|
+
const checkInterval = setInterval(async () => {
|
|
242
|
+
const job = await kmClient.ai.getJob<string>(jobId);
|
|
243
|
+
if (job.status === "completed") {
|
|
244
|
+
console.log("Generated text:", job.result);
|
|
245
|
+
clearInterval(checkInterval);
|
|
246
|
+
setLoading(false);
|
|
247
|
+
} else if (job.status === "failed") {
|
|
248
|
+
console.error("AI generation failed:", job.error);
|
|
249
|
+
clearInterval(checkInterval);
|
|
250
|
+
setLoading(false);
|
|
251
|
+
}
|
|
252
|
+
}, 2000);
|
|
307
253
|
```
|
|
308
254
|
|
|
309
255
|
## Key Points
|
|
310
256
|
|
|
311
|
-
- **Job-based**: All generation methods return `{ jobId }` immediately, use poll
|
|
257
|
+
- **Job-based**: All generation methods return `{ jobId }` immediately, use `getJob()` to poll for results
|
|
312
258
|
- **Resumable**: Save `jobId` to store to resume polling after page reload
|
|
313
259
|
- **Zod Schemas**: Use Zod schemas for `generateJson` to get automatic type inference and validation
|
|
314
260
|
- **Temperature**: Range 0.0 (factual) to 1.0 (creative)
|
|
315
|
-
- **Polling Options**: Configure `pollInterval`, `timeout`, and `onProgress` callback
|
|
316
261
|
- **Image URLs**: Gemini models support multimodal input via `imageUrls` parameter
|
|
@@ -133,7 +133,7 @@ export function useDynamicStore<T extends object>(
|
|
|
133
133
|
## Basic Usage
|
|
134
134
|
|
|
135
135
|
```tsx
|
|
136
|
-
import { useSnapshot } from "
|
|
136
|
+
import { useSnapshot } from "@kokimoki/app";
|
|
137
137
|
import { useDynamicStore } from "@/hooks/useDynamicStore";
|
|
138
138
|
|
|
139
139
|
interface RoomState {
|
|
@@ -350,7 +350,7 @@ const BreakoutRoom = ({ roomId }: { roomId: string }) => {
|
|
|
350
350
|
|
|
351
351
|
## File Organization
|
|
352
352
|
|
|
353
|
-
**ALWAYS** organize dynamic store code using the standard
|
|
353
|
+
**ALWAYS** organize dynamic store code using the standard state/actions pattern:
|
|
354
354
|
|
|
355
355
|
**State definition** (`src/state/chat-store.ts`):
|
|
356
356
|
|
|
@@ -408,7 +408,7 @@ export const chatActions = {
|
|
|
408
408
|
**Usage in component** (`src/views/chat-view.tsx`):
|
|
409
409
|
|
|
410
410
|
```tsx
|
|
411
|
-
import { useSnapshot } from "
|
|
411
|
+
import { useSnapshot } from "@kokimoki/app";
|
|
412
412
|
import { useDynamicStore } from "@/hooks/useDynamicStore";
|
|
413
413
|
import { createChatState, type ChatState } from "@/state/chat-store";
|
|
414
414
|
import { chatActions } from "@/state/actions/chat-actions";
|
|
@@ -110,36 +110,12 @@ const url = kmClient.i18n.getNamespaceUrl("en", "game");
|
|
|
110
110
|
|
|
111
111
|
## AI Translation API
|
|
112
112
|
|
|
113
|
-
### i18n.
|
|
114
|
-
|
|
115
|
-
Get the status of all languages that have been requested for AI translation.
|
|
116
|
-
|
|
117
|
-
```typescript
|
|
118
|
-
interface AllLanguagesStatus {
|
|
119
|
-
languages: { lng: string; status: LanguageStatus }[];
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
type LanguageStatus = "available" | "processing" | "failed" | "partial";
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
**Example:**
|
|
126
|
-
|
|
127
|
-
```typescript
|
|
128
|
-
const { languages } = await kmClient.i18n.getAllLanguagesStatus();
|
|
129
|
-
// [{ lng: 'de', status: 'available' }, { lng: 'fr', status: 'processing' }]
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
### i18n.getTranslationStatus(lng): Promise<TranslationStatus>
|
|
113
|
+
### i18n.getTranslationStatus(lng): Promise<{ status: TranslationStatus }>
|
|
133
114
|
|
|
134
115
|
Get the translation status for a specific language.
|
|
135
116
|
|
|
136
117
|
```typescript
|
|
137
|
-
|
|
138
|
-
status: "available" | "processing" | "not_available";
|
|
139
|
-
namespaces: Record<string, NamespaceStatus>;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
type NamespaceStatus = "available" | "processing" | "failed" | "not_available";
|
|
118
|
+
type TranslationStatus = "available" | "processing" | "failed";
|
|
143
119
|
```
|
|
144
120
|
|
|
145
121
|
**Example:**
|
|
@@ -147,117 +123,34 @@ type NamespaceStatus = "available" | "processing" | "failed" | "not_available";
|
|
|
147
123
|
```typescript
|
|
148
124
|
const status = await kmClient.i18n.getTranslationStatus("de");
|
|
149
125
|
|
|
150
|
-
if (status
|
|
126
|
+
if (status === "available") {
|
|
151
127
|
i18next.changeLanguage("de");
|
|
152
|
-
} else if (status.status === "processing") {
|
|
153
|
-
// Show loading indicator
|
|
154
|
-
} else {
|
|
155
|
-
// Request translation
|
|
156
|
-
await kmClient.i18n.requestTranslation("de");
|
|
157
128
|
}
|
|
158
129
|
```
|
|
159
130
|
|
|
160
|
-
### i18n.requestTranslation(lng): Promise<
|
|
131
|
+
### i18n.requestTranslation(lng): Promise<TranslationStatus>
|
|
161
132
|
|
|
162
133
|
Request AI translation for a target language. Triggers background AI translation jobs for all namespaces.
|
|
163
134
|
|
|
164
135
|
```typescript
|
|
165
|
-
|
|
166
|
-
lng: string;
|
|
167
|
-
status: "started" | "already_processing" | "already_available";
|
|
168
|
-
}
|
|
136
|
+
type TranslationStatus = "available" | "processing" | "failed";
|
|
169
137
|
```
|
|
170
138
|
|
|
171
139
|
**Example:**
|
|
172
140
|
|
|
173
141
|
```typescript
|
|
174
|
-
const
|
|
142
|
+
const status = await kmClient.i18n.requestTranslation("de");
|
|
175
143
|
|
|
176
|
-
if (
|
|
144
|
+
if (status === "available") {
|
|
177
145
|
// Already translated, switch immediately
|
|
178
146
|
i18next.changeLanguage("de");
|
|
179
147
|
} else {
|
|
180
|
-
// Poll until ready
|
|
181
|
-
await kmClient.i18n.pollTranslation("de");
|
|
182
|
-
i18next.changeLanguage("de");
|
|
148
|
+
// Poll until ready using getTranslationStatus
|
|
183
149
|
}
|
|
184
150
|
```
|
|
185
151
|
|
|
186
|
-
### i18n.pollTranslation(lng, options?): Promise<void>
|
|
187
|
-
|
|
188
|
-
Poll a translation request until all namespaces are available.
|
|
189
|
-
|
|
190
|
-
**Parameters:**
|
|
191
|
-
|
|
192
|
-
- **lng**: `string` Language code to poll for
|
|
193
|
-
- **options.pollInterval**: `number` Polling interval in ms (default: 2000, min: 1000)
|
|
194
|
-
- **options.timeout**: `number` Timeout in ms (default: 120000)
|
|
195
|
-
- **options.onProgress**: `(status: TranslationStatus) => void` Progress callback
|
|
196
|
-
|
|
197
|
-
**Example:**
|
|
198
|
-
|
|
199
|
-
```typescript
|
|
200
|
-
await kmClient.i18n.pollTranslation("de", {
|
|
201
|
-
timeout: 60000,
|
|
202
|
-
onProgress: (status) => {
|
|
203
|
-
console.log("Overall:", status.status);
|
|
204
|
-
console.log("Namespaces:", status.namespaces);
|
|
205
|
-
},
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
// Now safe to switch
|
|
209
|
-
i18next.changeLanguage("de");
|
|
210
|
-
```
|
|
211
|
-
|
|
212
152
|
## Common Patterns
|
|
213
153
|
|
|
214
|
-
### Example: Language Switcher with AI Translation
|
|
215
|
-
|
|
216
|
-
```tsx
|
|
217
|
-
import { useState } from "react";
|
|
218
|
-
import { useTranslation } from "react-i18next";
|
|
219
|
-
import { getKmClient } from "@kokimoki/app";
|
|
220
|
-
|
|
221
|
-
const kmClient = getKmClient();
|
|
222
|
-
|
|
223
|
-
const LanguageSwitcher = () => {
|
|
224
|
-
const { i18n } = useTranslation();
|
|
225
|
-
const [loading, setLoading] = useState(false);
|
|
226
|
-
|
|
227
|
-
const switchLanguage = async (lng: string) => {
|
|
228
|
-
// Check if translation is available
|
|
229
|
-
const status = await kmClient.i18n.getTranslationStatus(lng);
|
|
230
|
-
|
|
231
|
-
if (status.status === "available") {
|
|
232
|
-
i18n.changeLanguage(lng);
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Request and wait for AI translation
|
|
237
|
-
setLoading(true);
|
|
238
|
-
await kmClient.i18n.requestTranslation(lng);
|
|
239
|
-
await kmClient.i18n.pollTranslation(lng, {
|
|
240
|
-
onProgress: (s) => console.log(`Translating: ${s.status}`),
|
|
241
|
-
});
|
|
242
|
-
setLoading(false);
|
|
243
|
-
|
|
244
|
-
i18n.changeLanguage(lng);
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
return (
|
|
248
|
-
<select
|
|
249
|
-
value={i18n.language}
|
|
250
|
-
onChange={(e) => switchLanguage(e.target.value)}
|
|
251
|
-
disabled={loading}
|
|
252
|
-
>
|
|
253
|
-
<option value="en">English</option>
|
|
254
|
-
<option value="de">Deutsch</option>
|
|
255
|
-
<option value="fr">Français</option>
|
|
256
|
-
</select>
|
|
257
|
-
);
|
|
258
|
-
};
|
|
259
|
-
```
|
|
260
|
-
|
|
261
154
|
### Example: Using Translations in Components
|
|
262
155
|
|
|
263
156
|
```tsx
|
|
@@ -16,7 +16,9 @@ The Kokimoki SDK is a comprehensive development toolkit for building real-time c
|
|
|
16
16
|
- Use `kmClient.transact` for atomic state updates across single store or multiple stores
|
|
17
17
|
- Use `kmClient.storage.upload` and related API methods to handle file uploads (media, JSON, etc.) in application
|
|
18
18
|
- Use `kmClient.serverTimestamp()` for time-related matters as this will be synced among players
|
|
19
|
-
- Use `useSnapshot` hook
|
|
19
|
+
- Use `useSnapshot` hook to get reactive state inside React components — import from `@kokimoki/app` (re-exports valtio)
|
|
20
|
+
- Use `snapshot` function to read immutable state outside React components (in actions, event handlers, etc.) — import from `@kokimoki/app` — **NEVER** read directly from `store.proxy.field`
|
|
21
|
+
- Use `z` and `ZodType` from `@kokimoki/app` for schema definitions (re-exports zod)
|
|
20
22
|
- Use AI integration API methods: `kmClient.ai.generateText`, `kmClient.ai.generateJson`, and `kmClient.ai.generateImage` with corresponding poll methods for AI capabilities
|
|
21
23
|
- Use i18n API methods: `kmClient.i18n.createI18n`, `kmClient.i18n.init`, and `kmClient.i18n.requestTranslation` for internationalization with AI-powered translations
|
|
22
24
|
- Use leaderboard API methods: `kmClient.leaderboard.insertEntry`, `kmClient.leaderboard.upsertEntry`, `kmClient.leaderboard.listEntries`, and `kmClient.leaderboard.getBestEntry` to add leaderboard capabilities to application
|
|
@@ -118,13 +120,13 @@ await kmClient.transact([store1, store2], ([state1, state2]) => {
|
|
|
118
120
|
|
|
119
121
|
### Reactive State in Components
|
|
120
122
|
|
|
121
|
-
- Use `useSnapshot` hook
|
|
123
|
+
- Use `useSnapshot` hook to get reactive state inside React components (import from `@kokimoki/app`)
|
|
122
124
|
- The component will re-render when the store state changes
|
|
123
125
|
|
|
124
126
|
**Example: Using State in Components**
|
|
125
127
|
|
|
126
128
|
```tsx
|
|
127
|
-
import { useSnapshot } from "
|
|
129
|
+
import { useSnapshot } from "@kokimoki/app";
|
|
128
130
|
import { store } from "../store";
|
|
129
131
|
|
|
130
132
|
const Component = () => {
|
|
@@ -140,6 +142,44 @@ const Component = () => {
|
|
|
140
142
|
};
|
|
141
143
|
```
|
|
142
144
|
|
|
145
|
+
### Reading State Outside Components
|
|
146
|
+
|
|
147
|
+
When reading state **outside React components** (in store actions, event handlers, initialization logic), use `snapshot()` from `@kokimoki/app` to get an immutable copy.
|
|
148
|
+
**NEVER** read directly from `store.proxy` — the proxy is mutable and values can change between read and use.
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { snapshot } from "@kokimoki/app";
|
|
152
|
+
|
|
153
|
+
// WRONG — mutable proxy, value can change unexpectedly
|
|
154
|
+
const value = store.proxy.someField;
|
|
155
|
+
|
|
156
|
+
// CORRECT — immutable snapshot, safe to use
|
|
157
|
+
const value = snapshot(store.proxy).someField;
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Example: Reading state in an store actions**
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import { snapshot } from "@kokimoki/app";
|
|
164
|
+
|
|
165
|
+
// Action function that reads state and updates it based on conditions
|
|
166
|
+
async function doSomething() {
|
|
167
|
+
const { count, completed } = snapshot(store.proxy);
|
|
168
|
+
|
|
169
|
+
if (count > 10 && !completed) {
|
|
170
|
+
await kmClient.transact([store], ([state]) => {
|
|
171
|
+
state.completed = true;
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Common Patterns
|
|
178
|
+
|
|
179
|
+
- **Inside React components** → `useSnapshot(store.proxy)` (reactive, triggers re-renders)
|
|
180
|
+
- **Outside React components** → `snapshot(store.proxy)` (immutable, read-only)
|
|
181
|
+
- **Writing state** → Always use `kmClient.transact()` (never assign to proxy directly)
|
|
182
|
+
|
|
143
183
|
## Dynamic Stores
|
|
144
184
|
|
|
145
185
|
For room-based state isolation (teams, chat rooms, breakout rooms), see [Dynamic Stores](./kokimoki-dynamic-stores.instructions.md).
|
|
@@ -166,7 +206,7 @@ Each Kokimoki store has a `connections` property that provides real-time presenc
|
|
|
166
206
|
### Example: Track Online Players
|
|
167
207
|
|
|
168
208
|
```tsx
|
|
169
|
-
import { useSnapshot } from "
|
|
209
|
+
import { useSnapshot } from "@kokimoki/app";
|
|
170
210
|
import { globalStore } from "@/state/stores/global-store";
|
|
171
211
|
|
|
172
212
|
const Component = () => {
|
|
@@ -186,7 +226,7 @@ const Component = () => {
|
|
|
186
226
|
### Example: Display Player List with Online Status
|
|
187
227
|
|
|
188
228
|
```tsx
|
|
189
|
-
import { useSnapshot } from "
|
|
229
|
+
import { useSnapshot } from "@kokimoki/app";
|
|
190
230
|
import { globalStore } from "@/state/stores/global-store";
|
|
191
231
|
|
|
192
232
|
const PlayerList = () => {
|
|
@@ -231,8 +271,7 @@ Store names with the `__km/` prefix are reserved for SDK internal use. Do not cr
|
|
|
231
271
|
### Using App Meta
|
|
232
272
|
|
|
233
273
|
```tsx
|
|
234
|
-
import { getKmClient } from "@kokimoki/app";
|
|
235
|
-
import { useSnapshot } from "valtio";
|
|
274
|
+
import { useSnapshot, getKmClient } from "@kokimoki/app";
|
|
236
275
|
|
|
237
276
|
const kmClient = getKmClient();
|
|
238
277
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kokimoki/app",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Kokimoki app",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -19,17 +19,23 @@
|
|
|
19
19
|
"prebuild": "node scripts/generate-version.js",
|
|
20
20
|
"build": "tsc",
|
|
21
21
|
"dev": "tsc -w",
|
|
22
|
-
"docs": "typedoc src/index.ts"
|
|
22
|
+
"docs": "typedoc src/index.ts",
|
|
23
|
+
"lint": "eslint .",
|
|
24
|
+
"typecheck": "tsc --noEmit"
|
|
23
25
|
},
|
|
24
26
|
"author": "Loquiz OÜ",
|
|
25
27
|
"license": "Apache-2.0",
|
|
26
28
|
"devDependencies": {
|
|
29
|
+
"eslint-config": "*",
|
|
27
30
|
"@types/chai": "^4",
|
|
28
31
|
"@types/mocha": "^10",
|
|
29
32
|
"@types/node": "^18",
|
|
30
33
|
"@types/ws": "^8.5.5",
|
|
31
34
|
"bson-objectid": "^2.0.4",
|
|
32
35
|
"chai": "^4",
|
|
36
|
+
"eslint": "^9.39.1",
|
|
37
|
+
"eslint-plugin-chai-friendly": "^1.1.0",
|
|
38
|
+
"eslint-plugin-valtio": "^0.8.0",
|
|
33
39
|
"mocha": "^10",
|
|
34
40
|
"ts-node": "^10",
|
|
35
41
|
"tsx": "^4",
|
package/dist/fields.d.ts
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
export interface FieldOptions {
|
|
2
|
-
label?: string;
|
|
3
|
-
}
|
|
4
|
-
export declare abstract class Field<T> {
|
|
5
|
-
readonly options: FieldOptions;
|
|
6
|
-
constructor(options: FieldOptions);
|
|
7
|
-
abstract get value(): T;
|
|
8
|
-
abstract get schema(): any;
|
|
9
|
-
}
|
|
10
|
-
export declare class BooleanField extends Field<boolean> {
|
|
11
|
-
value: boolean;
|
|
12
|
-
constructor(value: boolean, options?: FieldOptions);
|
|
13
|
-
get schema(): {
|
|
14
|
-
type: string;
|
|
15
|
-
default: boolean;
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
export declare class ConstField<T extends string> extends Field<
|
|
19
|
-
string extends T ? never : T
|
|
20
|
-
> {
|
|
21
|
-
value: string extends T ? never : T;
|
|
22
|
-
constructor(value: string extends T ? never : T, options?: FieldOptions);
|
|
23
|
-
get schema(): {
|
|
24
|
-
const: string extends T ? never : T;
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
export declare class ImageField extends Field<string> {
|
|
28
|
-
value: string;
|
|
29
|
-
constructor(value: string, options?: FieldOptions);
|
|
30
|
-
get schema(): {
|
|
31
|
-
type: string;
|
|
32
|
-
default: string;
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
export declare class TextField extends Field<string> {
|
|
36
|
-
value: string;
|
|
37
|
-
constructor(value: string, options?: FieldOptions);
|
|
38
|
-
get schema(): {
|
|
39
|
-
type: string;
|
|
40
|
-
default: string;
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
export declare class EnumField<T extends Record<string, string>> extends Field<
|
|
44
|
-
keyof T
|
|
45
|
-
> {
|
|
46
|
-
enumValues: T;
|
|
47
|
-
value: keyof T;
|
|
48
|
-
constructor(enumValues: T, value: keyof T, options?: FieldOptions);
|
|
49
|
-
get schema(): {
|
|
50
|
-
enum: string[];
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
export declare class IntegerField extends Field<number> {
|
|
54
|
-
value: number;
|
|
55
|
-
constructor(value: number, options?: FieldOptions);
|
|
56
|
-
get schema(): {
|
|
57
|
-
type: string;
|
|
58
|
-
default: number;
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
export declare class FloatField extends Field<number> {
|
|
62
|
-
value: number;
|
|
63
|
-
constructor(value: number, options?: FieldOptions);
|
|
64
|
-
get schema(): {
|
|
65
|
-
type: string;
|
|
66
|
-
default: number;
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
export declare class FormGroup<
|
|
70
|
-
T extends Record<string, Field<any>>,
|
|
71
|
-
O extends Record<string, Field<any>>,
|
|
72
|
-
> extends Field<
|
|
73
|
-
{
|
|
74
|
-
[key in keyof T]: T[key]["value"];
|
|
75
|
-
} & Partial<{
|
|
76
|
-
[key in keyof O]: O[key]["value"];
|
|
77
|
-
}>
|
|
78
|
-
> {
|
|
79
|
-
requiredFields: T;
|
|
80
|
-
optionalFields: O;
|
|
81
|
-
constructor(requiredFields: T, optionalFields?: O, options?: FieldOptions);
|
|
82
|
-
get value(): {
|
|
83
|
-
[key in keyof T]: T[key]["value"];
|
|
84
|
-
} & Partial<{
|
|
85
|
-
[key in keyof O]: O[key]["value"];
|
|
86
|
-
}>;
|
|
87
|
-
get schema(): {
|
|
88
|
-
type: string;
|
|
89
|
-
properties: any;
|
|
90
|
-
required: string[];
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
export declare class FormArray<T> extends Field<T[]> {
|
|
94
|
-
private factory;
|
|
95
|
-
value: Field<T>["value"][];
|
|
96
|
-
constructor(
|
|
97
|
-
factory: () => Field<T>,
|
|
98
|
-
value: Field<T>["value"][],
|
|
99
|
-
options?: FieldOptions,
|
|
100
|
-
);
|
|
101
|
-
get schema(): {
|
|
102
|
-
type: string;
|
|
103
|
-
items: any;
|
|
104
|
-
default: T[];
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
export declare class Form<
|
|
108
|
-
R extends Record<string, Field<any>>,
|
|
109
|
-
O extends Record<string, Field<any>>,
|
|
110
|
-
> extends FormGroup<R, O> {}
|