@kokimoki/app 2.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -0
- package/dist/core/kokimoki-client.d.ts +75 -15
- package/dist/core/kokimoki-client.js +137 -22
- package/dist/index.d.ts +6 -1
- package/dist/index.js +4 -0
- package/dist/kokimoki.min.d.ts +322 -2
- package/dist/kokimoki.min.js +1884 -72
- package/dist/kokimoki.min.js.map +1 -1
- package/dist/llms.txt +6 -0
- package/dist/protocol/ws-message/reader.d.ts +1 -1
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/services/kokimoki-ai.d.ts +185 -122
- package/dist/services/kokimoki-ai.js +201 -109
- package/dist/services/kokimoki-i18n.d.ts +259 -0
- package/dist/services/kokimoki-i18n.js +325 -0
- package/dist/stores/kokimoki-local-store.d.ts +1 -1
- package/dist/types/common.d.ts +9 -0
- package/dist/types/env.d.ts +36 -0
- package/dist/types/env.js +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/kokimoki-client.d.ts +31 -0
- package/dist/utils/kokimoki-client.js +38 -0
- package/dist/utils/kokimoki-dev.d.ts +30 -0
- package/dist/utils/kokimoki-dev.js +75 -0
- package/dist/utils/kokimoki-env.d.ts +20 -0
- package/dist/utils/kokimoki-env.js +30 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/kokimoki-ai.instructions.md +316 -0
- package/docs/kokimoki-dynamic-stores.instructions.md +439 -0
- package/docs/kokimoki-i18n.instructions.md +285 -0
- package/docs/kokimoki-leaderboard.instructions.md +189 -0
- package/docs/kokimoki-sdk.instructions.md +221 -0
- package/docs/kokimoki-storage.instructions.md +162 -0
- package/llms.txt +43 -0
- package/package.json +9 -13
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import i18next from "i18next";
|
|
2
|
+
import HttpBackend from "i18next-http-backend";
|
|
3
|
+
import { getKmEnv } from "../utils/kokimoki-env";
|
|
4
|
+
/**
|
|
5
|
+
* Kokimoki i18n Service
|
|
6
|
+
*
|
|
7
|
+
* Provides translation loading via HTTP backend. Both development and production
|
|
8
|
+
* use HTTP to load translations consistently.
|
|
9
|
+
*
|
|
10
|
+
* In development, translations are served by @kokimoki/kit's dev server middleware.
|
|
11
|
+
* In production, translations are served from the assets CDN.
|
|
12
|
+
*
|
|
13
|
+
* **Key Features:**
|
|
14
|
+
* - Pre-configured i18next instance creation
|
|
15
|
+
* - Consistent HTTP-based loading in dev and prod
|
|
16
|
+
* - URL resolution for translation namespaces
|
|
17
|
+
* - AI-powered translation requests with polling support
|
|
18
|
+
*
|
|
19
|
+
* Access via `kmClient.i18n`
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* // Setup with React
|
|
24
|
+
* import { initReactI18next } from 'react-i18next';
|
|
25
|
+
*
|
|
26
|
+
* export const i18n = kmClient.i18n.createI18n({
|
|
27
|
+
* use: [initReactI18next]
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* // Initialize with primary language
|
|
31
|
+
* await kmClient.i18n.init('en');
|
|
32
|
+
*
|
|
33
|
+
* // Request AI translation and wait for it
|
|
34
|
+
* await kmClient.i18n.requestTranslation('de');
|
|
35
|
+
* await kmClient.i18n.pollTranslation('de', {
|
|
36
|
+
* onProgress: (status) => console.log('Translation status:', status.status)
|
|
37
|
+
* });
|
|
38
|
+
*
|
|
39
|
+
* // Now safe to switch language
|
|
40
|
+
* i18next.changeLanguage('de');
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export class KokimokiI18nService {
|
|
44
|
+
client;
|
|
45
|
+
initPromise = null;
|
|
46
|
+
instance = null;
|
|
47
|
+
options = {};
|
|
48
|
+
constructor(client) {
|
|
49
|
+
this.client = client;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Create and configure an i18next instance.
|
|
53
|
+
*
|
|
54
|
+
* This sets up the instance with plugins but does NOT initialize it.
|
|
55
|
+
* Call `init(lng)` to initialize with a specific language.
|
|
56
|
+
*
|
|
57
|
+
* @param options - Configuration options (plugins, fallback, defaultNS)
|
|
58
|
+
* @returns Configured i18next instance (not yet initialized)
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* // With React
|
|
63
|
+
* import { initReactI18next } from 'react-i18next';
|
|
64
|
+
*
|
|
65
|
+
* export const i18n = kmClient.i18n.createI18n({
|
|
66
|
+
* use: [initReactI18next]
|
|
67
|
+
* });
|
|
68
|
+
*
|
|
69
|
+
* // Later, when you know the language:
|
|
70
|
+
* await kmClient.i18n.init('en');
|
|
71
|
+
*
|
|
72
|
+
* // Then in components:
|
|
73
|
+
* const { t } = useTranslation('game');
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
createI18n(options) {
|
|
77
|
+
const env = getKmEnv();
|
|
78
|
+
const namespaces = env.i18nNamespaces ?? [];
|
|
79
|
+
if (namespaces.length === 0) {
|
|
80
|
+
console.warn("[KokimokiI18n] No i18n namespaces found. Make sure i18n is configured in @kokimoki/kit plugin.");
|
|
81
|
+
}
|
|
82
|
+
this.options = options ?? {};
|
|
83
|
+
this.instance = i18next.createInstance();
|
|
84
|
+
// Apply plugins
|
|
85
|
+
if (options?.use) {
|
|
86
|
+
for (const plugin of options.use) {
|
|
87
|
+
this.instance.use(plugin);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Add HTTP backend
|
|
91
|
+
this.instance.use(HttpBackend);
|
|
92
|
+
return this.instance;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Initialize the i18next instance with a specific language.
|
|
96
|
+
*
|
|
97
|
+
* Must call `createI18n()` first to set up the instance.
|
|
98
|
+
*
|
|
99
|
+
* @param lng - The language code to initialize with (e.g., 'en', 'de')
|
|
100
|
+
* @returns Promise that resolves when i18n is ready
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```typescript
|
|
104
|
+
* // Create instance first
|
|
105
|
+
* const i18n = kmClient.i18n.createI18n({ use: [initReactI18next] });
|
|
106
|
+
*
|
|
107
|
+
* // Then initialize when you know the language
|
|
108
|
+
* await kmClient.i18n.init('en');
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
async init(lng) {
|
|
112
|
+
if (!this.instance) {
|
|
113
|
+
throw new Error("Call createI18n() before init()");
|
|
114
|
+
}
|
|
115
|
+
if (this.initPromise) {
|
|
116
|
+
return this.initPromise;
|
|
117
|
+
}
|
|
118
|
+
const env = getKmEnv();
|
|
119
|
+
const namespaces = env.i18nNamespaces ?? [];
|
|
120
|
+
const fallbackLng = this.options.fallbackLng ?? lng;
|
|
121
|
+
const defaultNS = this.options.defaultNS;
|
|
122
|
+
this.initPromise = this.instance.init({
|
|
123
|
+
lng,
|
|
124
|
+
fallbackLng,
|
|
125
|
+
ns: namespaces,
|
|
126
|
+
defaultNS,
|
|
127
|
+
backend: {
|
|
128
|
+
// i18next-http-backend passes lng as array, extract first element
|
|
129
|
+
loadPath: (lngs, ns) => {
|
|
130
|
+
const language = Array.isArray(lngs) ? lngs[0] : lngs;
|
|
131
|
+
return this.getNamespaceUrl(language, ns);
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
interpolation: {
|
|
135
|
+
escapeValue: false,
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
return this.initPromise;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get the URL for a translation namespace.
|
|
142
|
+
*
|
|
143
|
+
* Returns the appropriate URL based on environment and language source:
|
|
144
|
+
* - Development (local language): `/__kokimoki/i18n/{lng}/{ns}.json`
|
|
145
|
+
* - Development (AI-translated): `{buildUrl}km-i18n/{lng}/{ns}.json`
|
|
146
|
+
* - Production: `{assets}/km-i18n/{lng}/{ns}.json`
|
|
147
|
+
*
|
|
148
|
+
* @param lng - Language code (e.g., 'en', 'et', 'de')
|
|
149
|
+
* @param ns - Namespace (e.g., 'ui', 'game', 'setup')
|
|
150
|
+
* @returns Full URL to the translation JSON file
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```typescript
|
|
154
|
+
* const url = kmClient.i18n.getNamespaceUrl('en', 'game');
|
|
155
|
+
* // Dev (local): "/__kokimoki/i18n/en/game.json"
|
|
156
|
+
* // Dev (AI): "https://builds.kokimoki.com/build-123/km-i18n/de/game.json"
|
|
157
|
+
* // Prod: "https://cdn.kokimoki.com/build-123/km-i18n/en/game.json"
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
getNamespaceUrl(lng, ns) {
|
|
161
|
+
const env = getKmEnv();
|
|
162
|
+
if (env.dev) {
|
|
163
|
+
// Check if this is a local language (served by kit middleware)
|
|
164
|
+
const localLanguages = env.i18nLanguages ?? [];
|
|
165
|
+
if (localLanguages.includes(lng)) {
|
|
166
|
+
return `/__kokimoki/i18n/${lng}/${ns}.json`;
|
|
167
|
+
}
|
|
168
|
+
// AI-translated language: use buildUrl (S3)
|
|
169
|
+
if (env.buildUrl) {
|
|
170
|
+
return `${env.buildUrl}km-i18n/${lng}/${ns}.json`;
|
|
171
|
+
}
|
|
172
|
+
// Fallback to local path (will 404 but better than crashing)
|
|
173
|
+
return `/__kokimoki/i18n/${lng}/${ns}.json`;
|
|
174
|
+
}
|
|
175
|
+
// Production: use assets CDN with configured i18n path
|
|
176
|
+
const i18nPath = env.i18nPath ?? "km-i18n";
|
|
177
|
+
return `${env.assets}/${i18nPath}/${lng}/${ns}.json`;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Get the list of available namespaces.
|
|
181
|
+
*
|
|
182
|
+
* @returns Array of namespace names configured in @kokimoki/kit
|
|
183
|
+
*/
|
|
184
|
+
getNamespaces() {
|
|
185
|
+
return getKmEnv().i18nNamespaces ?? [];
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Get the list of available languages.
|
|
189
|
+
*
|
|
190
|
+
* @returns Array of language codes configured in @kokimoki/kit
|
|
191
|
+
*/
|
|
192
|
+
getLanguages() {
|
|
193
|
+
return getKmEnv().i18nLanguages ?? [];
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Get the status of all languages that have been requested for AI translation.
|
|
197
|
+
*
|
|
198
|
+
* @returns Promise with array of language statuses
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```typescript
|
|
202
|
+
* const { languages } = await kmClient.i18n.getAllLanguagesStatus();
|
|
203
|
+
* // [{ lng: 'de', status: 'available' }, { lng: 'fr', status: 'processing' }]
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
async getAllLanguagesStatus() {
|
|
207
|
+
const res = await fetch(`${this.client.apiUrl}/i18n`, {
|
|
208
|
+
method: "GET",
|
|
209
|
+
headers: this.client.apiHeaders,
|
|
210
|
+
});
|
|
211
|
+
return await res.json();
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Get the translation status for a specific language.
|
|
215
|
+
*
|
|
216
|
+
* Returns the overall status and per-namespace status for the given language.
|
|
217
|
+
*
|
|
218
|
+
* @param lng - Target language code (e.g., 'de', 'fr', 'es')
|
|
219
|
+
* @returns Promise with translation status
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* ```typescript
|
|
223
|
+
* const status = await kmClient.i18n.getTranslationStatus('de');
|
|
224
|
+
* if (status.status === 'available') {
|
|
225
|
+
* // All translations ready, can switch language
|
|
226
|
+
* i18next.changeLanguage('de');
|
|
227
|
+
* } else if (status.status === 'processing') {
|
|
228
|
+
* // Show loading indicator
|
|
229
|
+
* } else {
|
|
230
|
+
* // Request translation
|
|
231
|
+
* await kmClient.i18n.requestTranslation('de');
|
|
232
|
+
* }
|
|
233
|
+
* ```
|
|
234
|
+
*/
|
|
235
|
+
async getTranslationStatus(lng) {
|
|
236
|
+
const res = await fetch(`${this.client.apiUrl}/i18n/${encodeURIComponent(lng)}/status`, {
|
|
237
|
+
method: "GET",
|
|
238
|
+
headers: this.client.apiHeaders,
|
|
239
|
+
});
|
|
240
|
+
return await res.json();
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Request AI translation for a target language.
|
|
244
|
+
*
|
|
245
|
+
* Triggers background AI translation jobs for all namespaces that are not yet available.
|
|
246
|
+
* Uses the build's configured primary language as the source.
|
|
247
|
+
*
|
|
248
|
+
* @param lng - Target language code (e.g., 'de', 'fr', 'es')
|
|
249
|
+
* @returns Promise with the result of the request
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* ```typescript
|
|
253
|
+
* const result = await kmClient.i18n.requestTranslation('de');
|
|
254
|
+
*
|
|
255
|
+
* if (result.status === 'already_available') {
|
|
256
|
+
* // Already translated, switch immediately
|
|
257
|
+
* i18next.changeLanguage('de');
|
|
258
|
+
* } else {
|
|
259
|
+
* // Poll until ready
|
|
260
|
+
* await kmClient.i18n.pollTranslation('de', {
|
|
261
|
+
* onProgress: (status) => console.log('Status:', status.status)
|
|
262
|
+
* });
|
|
263
|
+
* i18next.changeLanguage('de');
|
|
264
|
+
* }
|
|
265
|
+
* ```
|
|
266
|
+
*/
|
|
267
|
+
async requestTranslation(lng) {
|
|
268
|
+
const res = await fetch(`${this.client.apiUrl}/i18n/${encodeURIComponent(lng)}/translate`, {
|
|
269
|
+
method: "POST",
|
|
270
|
+
headers: this.client.apiHeaders,
|
|
271
|
+
});
|
|
272
|
+
const data = await res.json();
|
|
273
|
+
return { lng, ...data };
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Poll a translation request until all namespaces are available.
|
|
277
|
+
*
|
|
278
|
+
* @param lng - The language code to poll for.
|
|
279
|
+
* @param options - Polling options (interval, timeout, progress callback).
|
|
280
|
+
* @returns Promise that resolves when all namespaces are available.
|
|
281
|
+
* @throws If the translation fails or times out.
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* ```typescript
|
|
285
|
+
* // Request and poll with progress
|
|
286
|
+
* await kmClient.i18n.requestTranslation('de');
|
|
287
|
+
* await kmClient.i18n.pollTranslation('de', {
|
|
288
|
+
* timeout: 60000,
|
|
289
|
+
* onProgress: (status) => {
|
|
290
|
+
* console.log('Overall:', status.status);
|
|
291
|
+
* console.log('Namespaces:', status.namespaces);
|
|
292
|
+
* }
|
|
293
|
+
* });
|
|
294
|
+
*
|
|
295
|
+
* // Now safe to switch
|
|
296
|
+
* i18next.changeLanguage('de');
|
|
297
|
+
* ```
|
|
298
|
+
*/
|
|
299
|
+
async pollTranslation(lng, options) {
|
|
300
|
+
const pollInterval = Math.max(1000, options?.pollInterval ?? 2000);
|
|
301
|
+
const timeout = options?.timeout ?? 120000;
|
|
302
|
+
const startTime = Date.now();
|
|
303
|
+
while (true) {
|
|
304
|
+
const status = await this.getTranslationStatus(lng);
|
|
305
|
+
// Call progress callback
|
|
306
|
+
options?.onProgress?.(status);
|
|
307
|
+
if (status.status === "available") {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
// Check for failed namespaces
|
|
311
|
+
const failedNamespaces = Object.entries(status.namespaces)
|
|
312
|
+
.filter(([_, ns]) => ns === "failed")
|
|
313
|
+
.map(([name]) => name);
|
|
314
|
+
if (failedNamespaces.length > 0) {
|
|
315
|
+
throw new Error(`Translation failed for namespaces: ${failedNamespaces.join(", ")}`);
|
|
316
|
+
}
|
|
317
|
+
// Check timeout
|
|
318
|
+
if (Date.now() - startTime > timeout) {
|
|
319
|
+
throw new Error(`Translation timed out after ${timeout}ms`);
|
|
320
|
+
}
|
|
321
|
+
// Wait before next poll
|
|
322
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
@@ -6,6 +6,6 @@ export declare class KokimokiLocalStore<T extends object> extends KokimokiStore<
|
|
|
6
6
|
constructor(localRoomName: string, defaultState: T);
|
|
7
7
|
getInitialUpdate(appId: string, clientId: string): {
|
|
8
8
|
roomHash: number;
|
|
9
|
-
initialUpdate: Uint8Array
|
|
9
|
+
initialUpdate: Uint8Array | undefined;
|
|
10
10
|
};
|
|
11
11
|
}
|
package/dist/types/common.d.ts
CHANGED
|
@@ -2,3 +2,12 @@ export interface Paginated<T> {
|
|
|
2
2
|
items: T[];
|
|
3
3
|
total: number;
|
|
4
4
|
}
|
|
5
|
+
/** Common options for polling methods */
|
|
6
|
+
export interface PollOptions<T = void> {
|
|
7
|
+
/** Polling interval in milliseconds. Default: 2000ms, minimum: 1000ms */
|
|
8
|
+
pollInterval?: number;
|
|
9
|
+
/** Timeout in milliseconds. Default: 120000ms (2 minutes) */
|
|
10
|
+
timeout?: number;
|
|
11
|
+
/** Progress callback */
|
|
12
|
+
onProgress?: (status: T) => void;
|
|
13
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kokimoki environment configuration injected into the HTML by @kokimoki/kit.
|
|
3
|
+
*
|
|
4
|
+
* Parsed from `#kokimoki-env` script tag at runtime.
|
|
5
|
+
* In production, placeholder values (%KM_*%) are replaced by the server.
|
|
6
|
+
*/
|
|
7
|
+
export interface KokimokiEnv {
|
|
8
|
+
/** Whether running in development mode */
|
|
9
|
+
dev: boolean;
|
|
10
|
+
/** Whether running in test/preview mode (no real deploy code) */
|
|
11
|
+
test: boolean;
|
|
12
|
+
/** WebSocket server host */
|
|
13
|
+
host: string;
|
|
14
|
+
/** Application identifier */
|
|
15
|
+
appId: string;
|
|
16
|
+
/** Base URL for routing */
|
|
17
|
+
base: string;
|
|
18
|
+
/** Base URL for static assets (CDN in production) */
|
|
19
|
+
assets: string;
|
|
20
|
+
/** Deploy code slug (production only) */
|
|
21
|
+
code?: string;
|
|
22
|
+
/** Client context JSON string - mode, player/presenter codes (production only) */
|
|
23
|
+
clientContext?: string;
|
|
24
|
+
/** App configuration JSON string (production, legacy) */
|
|
25
|
+
config?: string;
|
|
26
|
+
/** Parsed app configuration object (dev only) */
|
|
27
|
+
configObject?: unknown;
|
|
28
|
+
/** List of i18n namespace names */
|
|
29
|
+
i18nNamespaces?: string[];
|
|
30
|
+
/** List of i18n language codes */
|
|
31
|
+
i18nLanguages?: string[];
|
|
32
|
+
/** Path to i18n files (e.g., "km-i18n") */
|
|
33
|
+
i18nPath?: string;
|
|
34
|
+
/** Build URL for dev mode (S3 URL where AI translations are stored) */
|
|
35
|
+
buildUrl?: string;
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types/index.d.ts
CHANGED
package/dist/types/index.js
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { KokimokiClient } from "../core";
|
|
2
|
+
/**
|
|
3
|
+
* Returns the singleton Kokimoki client instance.
|
|
4
|
+
*
|
|
5
|
+
* Creates and caches a single KokimokiClient instance for the application.
|
|
6
|
+
* This is the recommended way to access the client throughout your app.
|
|
7
|
+
*
|
|
8
|
+
* @typeParam T - The client context type for storing custom data per client
|
|
9
|
+
* @returns The singleton KokimokiClient instance
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { getKmClient } from '@kokimoki/app';
|
|
14
|
+
*
|
|
15
|
+
* interface MyClientContext {
|
|
16
|
+
* name: string;
|
|
17
|
+
* avatar: string;
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* const kmClient = getKmClient<MyClientContext>();
|
|
21
|
+
*
|
|
22
|
+
* // Create stores
|
|
23
|
+
* const gameStore = kmClient.store<GameState>('game', initialState);
|
|
24
|
+
*
|
|
25
|
+
* // Use in components or actions
|
|
26
|
+
* await kmClient.transact([gameStore], ([game]) => {
|
|
27
|
+
* game.score += 10;
|
|
28
|
+
* });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function getKmClient<T = any>(): KokimokiClient<T>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { KokimokiClient } from "../core";
|
|
2
|
+
let _kmClient;
|
|
3
|
+
/**
|
|
4
|
+
* Returns the singleton Kokimoki client instance.
|
|
5
|
+
*
|
|
6
|
+
* Creates and caches a single KokimokiClient instance for the application.
|
|
7
|
+
* This is the recommended way to access the client throughout your app.
|
|
8
|
+
*
|
|
9
|
+
* @typeParam T - The client context type for storing custom data per client
|
|
10
|
+
* @returns The singleton KokimokiClient instance
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { getKmClient } from '@kokimoki/app';
|
|
15
|
+
*
|
|
16
|
+
* interface MyClientContext {
|
|
17
|
+
* name: string;
|
|
18
|
+
* avatar: string;
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* const kmClient = getKmClient<MyClientContext>();
|
|
22
|
+
*
|
|
23
|
+
* // Create stores
|
|
24
|
+
* const gameStore = kmClient.store<GameState>('game', initialState);
|
|
25
|
+
*
|
|
26
|
+
* // Use in components or actions
|
|
27
|
+
* await kmClient.transact([gameStore], ([game]) => {
|
|
28
|
+
* game.score += 10;
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export function getKmClient() {
|
|
33
|
+
if (_kmClient) {
|
|
34
|
+
return _kmClient;
|
|
35
|
+
}
|
|
36
|
+
_kmClient = new KokimokiClient();
|
|
37
|
+
return _kmClient;
|
|
38
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev mode utilities for Kokimoki client.
|
|
3
|
+
* Handles URL-based context injection and error forwarding when running in dev frame.
|
|
4
|
+
*/
|
|
5
|
+
export interface DevModeConfig {
|
|
6
|
+
/** Unique key for this dev session (from ?key= URL param) */
|
|
7
|
+
key: string;
|
|
8
|
+
/** Token storage key */
|
|
9
|
+
tokenKey: string;
|
|
10
|
+
/** Parsed context from URL (from ?context= URL param) */
|
|
11
|
+
context?: unknown;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Parse dev mode configuration from URL parameters.
|
|
15
|
+
* Returns undefined if not in dev mode or missing required params.
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseDevModeConfig(): DevModeConfig | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Set up console prefix for dev mode logging.
|
|
20
|
+
*/
|
|
21
|
+
export declare function setupDevConsolePrefix(key: string): void;
|
|
22
|
+
/**
|
|
23
|
+
* Set up error handlers to forward errors to parent dev frame.
|
|
24
|
+
*/
|
|
25
|
+
export declare function setupDevErrorHandlers(): void;
|
|
26
|
+
/**
|
|
27
|
+
* Initialize dev mode. Call this before connecting if env.dev is true.
|
|
28
|
+
* Returns the dev config if successfully initialized, undefined otherwise.
|
|
29
|
+
*/
|
|
30
|
+
export declare function initDevMode(): DevModeConfig | undefined;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev mode utilities for Kokimoki client.
|
|
3
|
+
* Handles URL-based context injection and error forwarding when running in dev frame.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Parse dev mode configuration from URL parameters.
|
|
7
|
+
* Returns undefined if not in dev mode or missing required params.
|
|
8
|
+
*/
|
|
9
|
+
export function parseDevModeConfig() {
|
|
10
|
+
const url = new URL(window.location.href);
|
|
11
|
+
const key = url.searchParams.get("key");
|
|
12
|
+
if (!key) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
const contextBase64 = url.searchParams.get("context");
|
|
16
|
+
let context;
|
|
17
|
+
if (contextBase64) {
|
|
18
|
+
try {
|
|
19
|
+
context = JSON.parse(atob(contextBase64));
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
console.warn("[Kokimoki] Failed to parse dev context:", e);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
key,
|
|
27
|
+
tokenKey: `KM_TOKEN/${key}`,
|
|
28
|
+
context,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Set up console prefix for dev mode logging.
|
|
33
|
+
*/
|
|
34
|
+
export function setupDevConsolePrefix(key) {
|
|
35
|
+
console.log = console.log.bind(console, `[${key}]`);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Set up error handlers to forward errors to parent dev frame.
|
|
39
|
+
*/
|
|
40
|
+
export function setupDevErrorHandlers() {
|
|
41
|
+
const notifyError = (error) => {
|
|
42
|
+
if (window.parent && window.self !== window.parent) {
|
|
43
|
+
const message = error instanceof Error ? error.message : error;
|
|
44
|
+
const stack = error instanceof Error ? error.stack : undefined;
|
|
45
|
+
window.parent.postMessage({ type: "km:error", message, stack }, "*");
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
window.onerror = (_message, _source, _lineno, _colno, error) => {
|
|
49
|
+
if (error) {
|
|
50
|
+
notifyError(error);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
window.onunhandledrejection = (event) => {
|
|
54
|
+
const error = event.reason;
|
|
55
|
+
if (error instanceof Error) {
|
|
56
|
+
notifyError(error);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
notifyError(String(error));
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Initialize dev mode. Call this before connecting if env.dev is true.
|
|
65
|
+
* Returns the dev config if successfully initialized, undefined otherwise.
|
|
66
|
+
*/
|
|
67
|
+
export function initDevMode() {
|
|
68
|
+
const config = parseDevModeConfig();
|
|
69
|
+
if (!config) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
setupDevConsolePrefix(config.key);
|
|
73
|
+
setupDevErrorHandlers();
|
|
74
|
+
return config;
|
|
75
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { KokimokiEnv } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* Parses and returns the Kokimoki environment configuration.
|
|
4
|
+
*
|
|
5
|
+
* The environment is injected into the HTML by @kokimoki/kit as a JSON script tag
|
|
6
|
+
* with id `kokimoki-env`. In production, placeholder values are replaced by the server.
|
|
7
|
+
*
|
|
8
|
+
* @returns The parsed KokimokiEnv object
|
|
9
|
+
* @throws Error if the kokimoki-env element is not found
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { getKmEnv } from '@kokimoki/app';
|
|
14
|
+
*
|
|
15
|
+
* const env = getKmEnv();
|
|
16
|
+
* console.log(env.dev); // true in development
|
|
17
|
+
* console.log(env.assets); // CDN URL in production
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function getKmEnv(): KokimokiEnv;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
let _kmEnv;
|
|
2
|
+
/**
|
|
3
|
+
* Parses and returns the Kokimoki environment configuration.
|
|
4
|
+
*
|
|
5
|
+
* The environment is injected into the HTML by @kokimoki/kit as a JSON script tag
|
|
6
|
+
* with id `kokimoki-env`. In production, placeholder values are replaced by the server.
|
|
7
|
+
*
|
|
8
|
+
* @returns The parsed KokimokiEnv object
|
|
9
|
+
* @throws Error if the kokimoki-env element is not found
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { getKmEnv } from '@kokimoki/app';
|
|
14
|
+
*
|
|
15
|
+
* const env = getKmEnv();
|
|
16
|
+
* console.log(env.dev); // true in development
|
|
17
|
+
* console.log(env.assets); // CDN URL in production
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function getKmEnv() {
|
|
21
|
+
if (_kmEnv) {
|
|
22
|
+
return _kmEnv;
|
|
23
|
+
}
|
|
24
|
+
const element = document.getElementById("kokimoki-env");
|
|
25
|
+
if (!element) {
|
|
26
|
+
throw new Error("kokimoki-env element not found. Ensure the app is built with @kokimoki/kit.");
|
|
27
|
+
}
|
|
28
|
+
_kmEnv = JSON.parse(element.textContent);
|
|
29
|
+
return _kmEnv;
|
|
30
|
+
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const KOKIMOKI_APP_VERSION = "
|
|
1
|
+
export declare const KOKIMOKI_APP_VERSION = "3.0.0";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated file. Do not edit manually.
|
|
2
|
-
export const KOKIMOKI_APP_VERSION = '
|
|
2
|
+
export const KOKIMOKI_APP_VERSION = '3.0.0';
|