@kokimoki/app 3.0.0 → 3.1.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.
@@ -2,6 +2,33 @@ import type TypedEmitter from "typed-emitter";
2
2
  import { KokimokiAiService, KokimokiI18nService, KokimokiLeaderboardService, KokimokiStorageService } from "../services";
3
3
  import { KokimokiLocalStore, KokimokiStore } from "../stores";
4
4
  import type { KokimokiClientEvents, KokimokiEnv } from "../types";
5
+ /**
6
+ * Reserved store name for the App Meta store.
7
+ * Stores with the `__km/` prefix are reserved for SDK internal use.
8
+ */
9
+ export declare const APP_META_STORE_NAME = "__km/app-meta";
10
+ /**
11
+ * State type for the App Meta store.
12
+ * Used by the server to inject meta tags into the HTML response.
13
+ *
14
+ * All fields are optional - missing fields will fall back to defaults in index.html.
15
+ */
16
+ export interface AppMetaState {
17
+ /** HTML lang attribute (e.g., 'en', 'et', 'de') */
18
+ lang?: string;
19
+ /** Document title (browser tab) */
20
+ title?: string;
21
+ /** Meta description */
22
+ description?: string;
23
+ /** Open Graph title (defaults to title if not set) */
24
+ ogTitle?: string;
25
+ /** Open Graph description (defaults to description if not set) */
26
+ ogDescription?: string;
27
+ /** Open Graph image URL */
28
+ ogImage?: string;
29
+ /** Favicon URL */
30
+ favicon?: string;
31
+ }
5
32
  type Mutable<T> = {
6
33
  -readonly [K in keyof T]: T[K] extends object ? Mutable<T[K]> : T[K];
7
34
  };
@@ -183,6 +210,7 @@ export declare class KokimokiClient<ClientContextT = any> extends KokimokiClient
183
210
  private _i18n?;
184
211
  private _storage?;
185
212
  private _leaderboard?;
213
+ private _metaStore?;
186
214
  /**
187
215
  * Dev mode config - set when running in dev frame with ?key= param
188
216
  */
@@ -203,6 +231,30 @@ export declare class KokimokiClient<ClientContextT = any> extends KokimokiClient
203
231
  * The deploy code slug.
204
232
  */
205
233
  readonly code: string;
234
+ /**
235
+ * Built-in App Meta store for managing HTML meta tags.
236
+ *
237
+ * This store is automatically joined when the client connects.
238
+ * The server uses this store to inject meta tags into the HTML response
239
+ * for proper SEO and social sharing previews.
240
+ *
241
+ * @example
242
+ * ```typescript
243
+ * import { useSnapshot } from 'valtio';
244
+ *
245
+ * const kmClient = getKmClient();
246
+ *
247
+ * // Read meta state
248
+ * const { title, description } = useSnapshot(kmClient.metaStore.proxy);
249
+ *
250
+ * // Update meta via transaction
251
+ * await kmClient.transact([kmClient.metaStore], ([meta]) => {
252
+ * meta.title = 'My Game';
253
+ * meta.description = 'An awesome multiplayer game';
254
+ * });
255
+ * ```
256
+ */
257
+ get metaStore(): KokimokiStore<AppMetaState>;
206
258
  constructor(env?: KokimokiEnv);
207
259
  get id(): string;
208
260
  get connectionId(): string;
@@ -7,6 +7,11 @@ import { initDevMode } from "../utils/kokimoki-dev";
7
7
  import { getKmEnv } from "../utils/kokimoki-env";
8
8
  import { KOKIMOKI_APP_VERSION } from "../version";
9
9
  import { RoomSubscription } from "./room-subscription";
10
+ /**
11
+ * Reserved store name for the App Meta store.
12
+ * Stores with the `__km/` prefix are reserved for SDK internal use.
13
+ */
14
+ export const APP_META_STORE_NAME = "__km/app-meta";
10
15
  /**
11
16
  * Kokimoki Client - Real-time Collaborative Game Development SDK
12
17
  *
@@ -183,6 +188,7 @@ export class KokimokiClient extends EventEmitter {
183
188
  _i18n;
184
189
  _storage;
185
190
  _leaderboard;
191
+ _metaStore;
186
192
  /**
187
193
  * Dev mode config - set when running in dev frame with ?key= param
188
194
  */
@@ -203,6 +209,35 @@ export class KokimokiClient extends EventEmitter {
203
209
  * The deploy code slug.
204
210
  */
205
211
  code;
212
+ /**
213
+ * Built-in App Meta store for managing HTML meta tags.
214
+ *
215
+ * This store is automatically joined when the client connects.
216
+ * The server uses this store to inject meta tags into the HTML response
217
+ * for proper SEO and social sharing previews.
218
+ *
219
+ * @example
220
+ * ```typescript
221
+ * import { useSnapshot } from 'valtio';
222
+ *
223
+ * const kmClient = getKmClient();
224
+ *
225
+ * // Read meta state
226
+ * const { title, description } = useSnapshot(kmClient.metaStore.proxy);
227
+ *
228
+ * // Update meta via transaction
229
+ * await kmClient.transact([kmClient.metaStore], ([meta]) => {
230
+ * meta.title = 'My Game';
231
+ * meta.description = 'An awesome multiplayer game';
232
+ * });
233
+ * ```
234
+ */
235
+ get metaStore() {
236
+ if (!this._metaStore) {
237
+ throw new Error("Client not connected");
238
+ }
239
+ return this._metaStore;
240
+ }
206
241
  constructor(env = getKmEnv()) {
207
242
  super();
208
243
  this._env = env;
@@ -443,6 +478,8 @@ export class KokimokiClient extends EventEmitter {
443
478
  console.error(`[Kokimoki] Failed to restore subscription for "${subscription.roomName}":`, err);
444
479
  }
445
480
  }
481
+ // Auto-join the built-in meta store with defaults from config
482
+ this._metaStore = this.store(APP_META_STORE_NAME, this._env.defaultAppMeta ?? {});
446
483
  // Emit connected event
447
484
  this._reconnectTimeout = 0;
448
485
  this.emit("connected");
@@ -1,5 +1 @@
1
- export declare enum RoomSubscriptionMode {
2
- Read = "r",
3
- Write = "w",
4
- ReadWrite = "b"
5
- }
1
+ export { RoomSubscriptionMode } from "../stores/kokimoki-store";
@@ -1,6 +1,3 @@
1
- export var RoomSubscriptionMode;
2
- (function (RoomSubscriptionMode) {
3
- RoomSubscriptionMode["Read"] = "r";
4
- RoomSubscriptionMode["Write"] = "w";
5
- RoomSubscriptionMode["ReadWrite"] = "b";
6
- })(RoomSubscriptionMode || (RoomSubscriptionMode = {}));
1
+ // Re-export from stores to maintain backwards compatibility
2
+ // The enum is defined in kokimoki-store.ts to avoid circular dependencies
3
+ export { RoomSubscriptionMode } from "../stores/kokimoki-store";
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export { KokimokiClient } from "./core";
2
+ export { APP_META_STORE_NAME } from "./core/kokimoki-client";
3
+ export type { AppMetaState } from "./core/kokimoki-client";
2
4
  export { KokimokiStore } from "./stores";
3
5
  export type { KokimokiClientEvents, KokimokiEnv, Paginated, PollOptions, Upload, } from "./types";
4
6
  export type { AllLanguagesStatus, I18nOptions, LanguageStatus, NamespaceStatus, RequestTranslationResult, TranslationStatus, } from "./services/kokimoki-i18n";
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { KokimokiClient } from "./core";
2
+ export { APP_META_STORE_NAME } from "./core/kokimoki-client";
2
3
  export { KokimokiStore } from "./stores";
3
4
  export { getKmClient } from "./utils/kokimoki-client";
4
5
  export { getKmEnv } from "./utils/kokimoki-env";
@@ -124,7 +124,7 @@ export declare class KokimokiI18nService {
124
124
  *
125
125
  * Must call `createI18n()` first to set up the instance.
126
126
  *
127
- * @param lng - The language code to initialize with (e.g., 'en', 'de')
127
+ * @param lng - The language code to initialize with (e.g., 'en', 'de'). If not provided, reads from the HTML element's lang attribute.
128
128
  * @returns Promise that resolves when i18n is ready
129
129
  *
130
130
  * @example
@@ -132,11 +132,14 @@ export declare class KokimokiI18nService {
132
132
  * // Create instance first
133
133
  * const i18n = kmClient.i18n.createI18n({ use: [initReactI18next] });
134
134
  *
135
- * // Then initialize when you know the language
135
+ * // Then initialize (reads lang from <html> element if not specified)
136
+ * await kmClient.i18n.init();
137
+ *
138
+ * // Or with explicit language
136
139
  * await kmClient.i18n.init('en');
137
140
  * ```
138
141
  */
139
- init(lng: string): Promise<void>;
142
+ init(lng?: string): Promise<void>;
140
143
  /**
141
144
  * Get the URL for a translation namespace.
142
145
  *
@@ -96,7 +96,7 @@ export class KokimokiI18nService {
96
96
  *
97
97
  * Must call `createI18n()` first to set up the instance.
98
98
  *
99
- * @param lng - The language code to initialize with (e.g., 'en', 'de')
99
+ * @param lng - The language code to initialize with (e.g., 'en', 'de'). If not provided, reads from the HTML element's lang attribute.
100
100
  * @returns Promise that resolves when i18n is ready
101
101
  *
102
102
  * @example
@@ -104,7 +104,10 @@ export class KokimokiI18nService {
104
104
  * // Create instance first
105
105
  * const i18n = kmClient.i18n.createI18n({ use: [initReactI18next] });
106
106
  *
107
- * // Then initialize when you know the language
107
+ * // Then initialize (reads lang from <html> element if not specified)
108
+ * await kmClient.i18n.init();
109
+ *
110
+ * // Or with explicit language
108
111
  * await kmClient.i18n.init('en');
109
112
  * ```
110
113
  */
@@ -115,20 +118,22 @@ export class KokimokiI18nService {
115
118
  if (this.initPromise) {
116
119
  return this.initPromise;
117
120
  }
121
+ // Default to HTML lang attribute if not provided
122
+ const language = lng ?? document.documentElement.lang ?? "en";
118
123
  const env = getKmEnv();
119
124
  const namespaces = env.i18nNamespaces ?? [];
120
- const fallbackLng = this.options.fallbackLng ?? lng;
125
+ const fallbackLng = this.options.fallbackLng ?? language;
121
126
  const defaultNS = this.options.defaultNS;
122
127
  this.initPromise = this.instance.init({
123
- lng,
128
+ lng: language,
124
129
  fallbackLng,
125
130
  ns: namespaces,
126
131
  defaultNS,
127
132
  backend: {
128
133
  // i18next-http-backend passes lng as array, extract first element
129
134
  loadPath: (lngs, ns) => {
130
- const language = Array.isArray(lngs) ? lngs[0] : lngs;
131
- return this.getNamespaceUrl(language, ns);
135
+ const lang = Array.isArray(lngs) ? lngs[0] : lngs;
136
+ return this.getNamespaceUrl(lang, ns);
132
137
  },
133
138
  },
134
139
  interpolation: {
@@ -1,6 +1,15 @@
1
1
  import { Snapshot } from "valtio/vanilla";
2
2
  import * as Y from "yjs";
3
- import { type KokimokiClient, RoomSubscriptionMode } from "../core";
3
+ import type { KokimokiClient } from "../core";
4
+ /**
5
+ * Mode for room subscription - determines read/write permissions.
6
+ * Re-exported here to avoid circular dependency issues.
7
+ */
8
+ export declare enum RoomSubscriptionMode {
9
+ Read = "r",
10
+ Write = "w",
11
+ ReadWrite = "b"
12
+ }
4
13
  export declare class KokimokiStore<T extends object> {
5
14
  readonly roomName: string;
6
15
  readonly defaultValue: T;
@@ -1,7 +1,16 @@
1
1
  import { bind as yjsBind } from "valtio-yjs";
2
2
  import { proxy, snapshot, subscribe } from "valtio/vanilla";
3
3
  import * as Y from "yjs";
4
- import { RoomSubscriptionMode } from "../core";
4
+ /**
5
+ * Mode for room subscription - determines read/write permissions.
6
+ * Re-exported here to avoid circular dependency issues.
7
+ */
8
+ export var RoomSubscriptionMode;
9
+ (function (RoomSubscriptionMode) {
10
+ RoomSubscriptionMode["Read"] = "r";
11
+ RoomSubscriptionMode["Write"] = "w";
12
+ RoomSubscriptionMode["ReadWrite"] = "b";
13
+ })(RoomSubscriptionMode || (RoomSubscriptionMode = {}));
5
14
  export class KokimokiStore {
6
15
  roomName;
7
16
  defaultValue;
@@ -33,4 +33,14 @@ export interface KokimokiEnv {
33
33
  i18nPath?: string;
34
34
  /** Build URL for dev mode (S3 URL where AI translations are stored) */
35
35
  buildUrl?: string;
36
+ /** Default app meta values from kokimoki.config.ts */
37
+ defaultAppMeta?: {
38
+ lang?: string;
39
+ title?: string;
40
+ description?: string;
41
+ ogTitle?: string;
42
+ ogDescription?: string;
43
+ ogImage?: string;
44
+ favicon?: string;
45
+ };
36
46
  }
@@ -5,6 +5,10 @@ import { KokimokiClient } from "../core";
5
5
  * Creates and caches a single KokimokiClient instance for the application.
6
6
  * This is the recommended way to access the client throughout your app.
7
7
  *
8
+ * The client includes a built-in `metaStore` for managing HTML meta tags.
9
+ * If `defaultAppMeta` is configured in kokimoki.config.ts, it will be used
10
+ * as the initial state for the metaStore when the client connects.
11
+ *
8
12
  * @typeParam T - The client context type for storing custom data per client
9
13
  * @returns The singleton KokimokiClient instance
10
14
  *
@@ -22,9 +26,9 @@ import { KokimokiClient } from "../core";
22
26
  * // Create stores
23
27
  * const gameStore = kmClient.store<GameState>('game', initialState);
24
28
  *
25
- * // Use in components or actions
26
- * await kmClient.transact([gameStore], ([game]) => {
27
- * game.score += 10;
29
+ * // Access the built-in meta store
30
+ * await kmClient.transact([kmClient.metaStore], ([meta]) => {
31
+ * meta.title = 'My Game';
28
32
  * });
29
33
  * ```
30
34
  */
@@ -6,6 +6,10 @@ let _kmClient;
6
6
  * Creates and caches a single KokimokiClient instance for the application.
7
7
  * This is the recommended way to access the client throughout your app.
8
8
  *
9
+ * The client includes a built-in `metaStore` for managing HTML meta tags.
10
+ * If `defaultAppMeta` is configured in kokimoki.config.ts, it will be used
11
+ * as the initial state for the metaStore when the client connects.
12
+ *
9
13
  * @typeParam T - The client context type for storing custom data per client
10
14
  * @returns The singleton KokimokiClient instance
11
15
  *
@@ -23,9 +27,9 @@ let _kmClient;
23
27
  * // Create stores
24
28
  * const gameStore = kmClient.store<GameState>('game', initialState);
25
29
  *
26
- * // Use in components or actions
27
- * await kmClient.transact([gameStore], ([game]) => {
28
- * game.score += 10;
30
+ * // Access the built-in meta store
31
+ * await kmClient.transact([kmClient.metaStore], ([meta]) => {
32
+ * meta.title = 'My Game';
29
33
  * });
30
34
  * ```
31
35
  */
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const KOKIMOKI_APP_VERSION = "3.0.0";
1
+ export declare const KOKIMOKI_APP_VERSION = "3.1.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 = '3.0.0';
2
+ export const KOKIMOKI_APP_VERSION = '3.1.0';
@@ -219,3 +219,42 @@ const PlayerList = () => {
219
219
  - Use `useSnapshot(store.connections)` to get reactive updates
220
220
  - Players can have multiple browser tabs open, but all share the same `clientId`
221
221
  - A player is considered online if their `clientId` is in the `clientIds` set
222
+
223
+ ## App Meta Store
224
+
225
+ The SDK provides a built-in `metaStore` on the client for managing HTML meta tags. The server uses this store to inject meta tags (title, description, Open Graph, favicon) into the HTML response for proper SEO and social sharing previews.
226
+
227
+ ### Reserved Store Names
228
+
229
+ Store names with the `__km/` prefix are reserved for SDK internal use. Do not create stores with this prefix.
230
+
231
+ ### Using App Meta
232
+
233
+ ```tsx
234
+ import { getKmClient } from "@kokimoki/app";
235
+ import { useSnapshot } from "valtio";
236
+
237
+ const kmClient = getKmClient();
238
+
239
+ // Read meta state anywhere
240
+ const { title, description } = useSnapshot(kmClient.metaStore.proxy);
241
+
242
+ // Update meta via transaction
243
+ await kmClient.transact([kmClient.metaStore], ([meta]) => {
244
+ meta.title = "My Game";
245
+ meta.description = "An awesome multiplayer game";
246
+ meta.ogImage = "https://example.com/og-image.png";
247
+ });
248
+ ```
249
+
250
+ ### App Meta Fields
251
+
252
+ | Field | Description |
253
+ | --------------- | -------------------------------------------------- |
254
+ | `lang` | HTML lang attribute (e.g., 'en', 'et', 'de') |
255
+ | `title` | Document title (browser tab) |
256
+ | `description` | Meta description |
257
+ | `ogTitle` | Open Graph title (defaults to `title` if not set) |
258
+ | `ogDescription` | Open Graph description (defaults to `description`) |
259
+ | `ogImage` | Open Graph image URL |
260
+ | `favicon` | Favicon URL |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kokimoki/app",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "type": "module",
5
5
  "description": "Kokimoki app",
6
6
  "main": "dist/index.js",