@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.
Files changed (38) hide show
  1. package/README.md +72 -0
  2. package/dist/core/kokimoki-client.d.ts +75 -15
  3. package/dist/core/kokimoki-client.js +137 -22
  4. package/dist/index.d.ts +6 -1
  5. package/dist/index.js +4 -0
  6. package/dist/kokimoki.min.d.ts +322 -2
  7. package/dist/kokimoki.min.js +1884 -72
  8. package/dist/kokimoki.min.js.map +1 -1
  9. package/dist/llms.txt +6 -0
  10. package/dist/protocol/ws-message/reader.d.ts +1 -1
  11. package/dist/services/index.d.ts +1 -0
  12. package/dist/services/index.js +1 -0
  13. package/dist/services/kokimoki-ai.d.ts +185 -122
  14. package/dist/services/kokimoki-ai.js +201 -109
  15. package/dist/services/kokimoki-i18n.d.ts +259 -0
  16. package/dist/services/kokimoki-i18n.js +325 -0
  17. package/dist/stores/kokimoki-local-store.d.ts +1 -1
  18. package/dist/types/common.d.ts +9 -0
  19. package/dist/types/env.d.ts +36 -0
  20. package/dist/types/env.js +1 -0
  21. package/dist/types/index.d.ts +1 -0
  22. package/dist/types/index.js +1 -0
  23. package/dist/utils/kokimoki-client.d.ts +31 -0
  24. package/dist/utils/kokimoki-client.js +38 -0
  25. package/dist/utils/kokimoki-dev.d.ts +30 -0
  26. package/dist/utils/kokimoki-dev.js +75 -0
  27. package/dist/utils/kokimoki-env.d.ts +20 -0
  28. package/dist/utils/kokimoki-env.js +30 -0
  29. package/dist/version.d.ts +1 -1
  30. package/dist/version.js +1 -1
  31. package/docs/kokimoki-ai.instructions.md +316 -0
  32. package/docs/kokimoki-dynamic-stores.instructions.md +439 -0
  33. package/docs/kokimoki-i18n.instructions.md +285 -0
  34. package/docs/kokimoki-leaderboard.instructions.md +189 -0
  35. package/docs/kokimoki-sdk.instructions.md +221 -0
  36. package/docs/kokimoki-storage.instructions.md +162 -0
  37. package/llms.txt +43 -0
  38. 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<ArrayBufferLike> | undefined;
9
+ initialUpdate: Uint8Array | undefined;
10
10
  };
11
11
  }
@@ -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 {};
@@ -1,3 +1,4 @@
1
1
  export * from "./common";
2
+ export * from "./env";
2
3
  export * from "./events";
3
4
  export * from "./upload";
@@ -1,3 +1,4 @@
1
1
  export * from "./common";
2
+ export * from "./env";
2
3
  export * from "./events";
3
4
  export * from "./upload";
@@ -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 = "2.1.0";
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.1.0';
2
+ export const KOKIMOKI_APP_VERSION = '3.0.0';