@chuzi/shared 0.2.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 (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +52 -0
  3. package/dist/api/index.d.ts +122 -0
  4. package/dist/api/index.js +108 -0
  5. package/dist/api/index.js.map +1 -0
  6. package/dist/config/index.d.ts +39 -0
  7. package/dist/config/index.js +404 -0
  8. package/dist/config/index.js.map +1 -0
  9. package/dist/index.d.ts +6 -0
  10. package/dist/index.js +627 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/input/index.d.ts +70 -0
  13. package/dist/input/index.js +41 -0
  14. package/dist/input/index.js.map +1 -0
  15. package/dist/realms/cosmos/components/index.d.ts +68 -0
  16. package/dist/realms/cosmos/components/index.js +172 -0
  17. package/dist/realms/cosmos/components/index.js.map +1 -0
  18. package/dist/realms/cosmos/index.d.ts +8 -0
  19. package/dist/realms/cosmos/index.js +76 -0
  20. package/dist/realms/cosmos/index.js.map +1 -0
  21. package/dist/realms/index.d.ts +109 -0
  22. package/dist/realms/index.js +23 -0
  23. package/dist/realms/index.js.map +1 -0
  24. package/dist/realms/wilds/components/index.d.ts +88 -0
  25. package/dist/realms/wilds/components/index.js +359 -0
  26. package/dist/realms/wilds/components/index.js.map +1 -0
  27. package/dist/realms/wilds/index.d.ts +8 -0
  28. package/dist/realms/wilds/index.js +70 -0
  29. package/dist/realms/wilds/index.js.map +1 -0
  30. package/dist/themes/index.d.ts +46 -0
  31. package/dist/themes/index.js +63 -0
  32. package/dist/themes/index.js.map +1 -0
  33. package/dist/types/index.d.ts +292 -0
  34. package/dist/types/index.js +3 -0
  35. package/dist/types/index.js.map +1 -0
  36. package/dist/ui/index.d.ts +71 -0
  37. package/dist/ui/index.js +551 -0
  38. package/dist/ui/index.js.map +1 -0
  39. package/package.json +107 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 CHUZI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # @chuzi/shared
2
+
3
+ Shared TypeScript types, realm configuration, and theme tokens for CHUZI apps.
4
+
5
+ ## Contents
6
+
7
+ ### Types (`@chuzi/shared/types`)
8
+
9
+ TypeScript interfaces matching the CHUZI API v1 responses:
10
+
11
+ - **Auth** — `LoginRequest`, `LoginResponse`
12
+ - **Catalog** — `StoryListItem`, `CatalogResponse`, `StoryPreview`, `StoryProgress`
13
+ - **Watch** — `SceneMapResponse`, `SceneMapEntry`, `SceneChoice`, `WatchSnapshot`
14
+ - **Bookmarks** — `BookmarkResponse`, `BookmarkListResponse`
15
+ - **User** — `UserProfile`, `UpdateRealmRequest`
16
+ - **Realm** — `RealmId`, `RealmDefinition`, `RealmConfigResponse`
17
+
18
+ ### Config (`@chuzi/shared/config`)
19
+
20
+ Realm/lexicon constants ported from the CHUZI backend `config/chuzi_realms.php`:
21
+
22
+ - `REALMS` — full realm definitions with lexicons
23
+ - `FALLBACK_LEXICON` — neutral labels when no realm is chosen
24
+ - `t(realmId, key)` — lexicon lookup with fallback
25
+ - `lexiconForRealm(realmId)` — merged lexicon for a realm
26
+
27
+ ### Themes (`@chuzi/shared/themes`)
28
+
29
+ Design tokens mirroring `chuzi-realms.css` and the scene tree viewer:
30
+
31
+ - `THEME_TOKENS` — CSS variable equivalents (bgDeep, accent, text, etc.)
32
+ - `SCENE_TREE_THEMES` — d3 scene tree colors, shapes, borders
33
+ - `getThemeTokens(realmId)` / `getSceneTreeTheme(realmId)` — lookup helpers
34
+
35
+ ## Usage
36
+
37
+ ```typescript
38
+ import { t, lexiconForRealm, getThemeTokens } from "@chuzi/shared";
39
+ import type { CatalogResponse, RealmId } from "@chuzi/shared";
40
+
41
+ const realm: RealmId = "cosmos";
42
+ console.log(t(realm, "story")); // "Star System"
43
+ console.log(getThemeTokens(realm).accent); // "#7eb8ff"
44
+ ```
45
+
46
+ ## Build
47
+
48
+ ```bash
49
+ npm install
50
+ npm run build # Outputs to dist/
51
+ npm run typecheck # Type-check only
52
+ ```
@@ -0,0 +1,122 @@
1
+ import { LoginRequest, LoginResponse, UserProfile, CatalogResponse, LocaleId, RealmConfigResponse, PaginatedResponse, StoryListItem, CreateStoryRequest, SceneMapResponse, TrackEngagementRequest, EngagementResponse, SaveBookmarkRequest, BookmarkResponse, BookmarkListResponse, UpdateRealmRequest, UpdateRealmResponse, UpdateLocaleRequest, UpdateLocaleResponse, UpdateProfileRequest, UpdateProfileResponse } from '../types/index.js';
2
+
3
+ /**
4
+ * Token resolver returned by the host app. Called on every request; may be
5
+ * synchronous (in-memory) or async (SecureStore on RN-tvOS, AsyncStorage on
6
+ * RN). Return null when the user is unauthenticated.
7
+ */
8
+ type TokenResolver = () => string | null | Promise<string | null>;
9
+ interface ChuziClientConfig {
10
+ /** e.g. "https://api.dev.chuzi.app" — no trailing slash required. */
11
+ baseUrl: string;
12
+ /** Returns the current bearer token, or null if unauthenticated. */
13
+ getToken?: TokenResolver;
14
+ /** Override fetch for testing or RN polyfills. Defaults to globalThis.fetch. */
15
+ fetch?: typeof fetch;
16
+ /** Sent as User-Agent (web), or X-Client header (RN). Optional. */
17
+ client?: string;
18
+ }
19
+ declare class ChuziApiError extends Error {
20
+ status: number;
21
+ body: unknown;
22
+ constructor(status: number, body: unknown, message?: string);
23
+ }
24
+ interface ChuziClient {
25
+ auth: {
26
+ login(req: LoginRequest): Promise<LoginResponse>;
27
+ register(req: LoginRequest & {
28
+ name: string;
29
+ }): Promise<LoginResponse>;
30
+ logout(): Promise<void>;
31
+ forgotPassword(req: {
32
+ email: string;
33
+ }): Promise<{
34
+ status: string;
35
+ }>;
36
+ resetPassword(req: {
37
+ token: string;
38
+ email: string;
39
+ password: string;
40
+ password_confirmation: string;
41
+ }): Promise<{
42
+ status: string;
43
+ }>;
44
+ user(): Promise<UserProfile>;
45
+ };
46
+ catalog: {
47
+ index(opts?: {
48
+ signal?: AbortSignal;
49
+ }): Promise<CatalogResponse>;
50
+ };
51
+ config: {
52
+ realms(opts?: {
53
+ locale?: LocaleId;
54
+ signal?: AbortSignal;
55
+ }): Promise<RealmConfigResponse>;
56
+ };
57
+ stories: {
58
+ index(opts?: {
59
+ signal?: AbortSignal;
60
+ }): Promise<PaginatedResponse<StoryListItem>>;
61
+ show(id: string, opts?: {
62
+ signal?: AbortSignal;
63
+ }): Promise<StoryListItem>;
64
+ mine(opts?: {
65
+ signal?: AbortSignal;
66
+ }): Promise<PaginatedResponse<StoryListItem>>;
67
+ create(req: CreateStoryRequest): Promise<{
68
+ data: StoryListItem;
69
+ }>;
70
+ };
71
+ watch: {
72
+ sceneMap(storyId: string, opts?: {
73
+ signal?: AbortSignal;
74
+ }): Promise<SceneMapResponse>;
75
+ trackEngagement(storyId: string, req: TrackEngagementRequest): Promise<EngagementResponse>;
76
+ saveBookmark(storyId: string, req: SaveBookmarkRequest): Promise<BookmarkResponse>;
77
+ listBookmarks(storyId: string, opts?: {
78
+ signal?: AbortSignal;
79
+ }): Promise<BookmarkListResponse>;
80
+ };
81
+ user: {
82
+ profile(opts?: {
83
+ signal?: AbortSignal;
84
+ }): Promise<UserProfile>;
85
+ updateRealm(req: UpdateRealmRequest): Promise<UpdateRealmResponse>;
86
+ updateLocale(req: UpdateLocaleRequest): Promise<UpdateLocaleResponse>;
87
+ updateProfile(req: UpdateProfileRequest): Promise<UpdateProfileResponse>;
88
+ };
89
+ credits: {
90
+ balance(opts?: {
91
+ signal?: AbortSignal;
92
+ }): Promise<{
93
+ balance: number;
94
+ }>;
95
+ ledger(opts?: {
96
+ signal?: AbortSignal;
97
+ cursor?: string;
98
+ limit?: number;
99
+ }): Promise<{
100
+ data: unknown[];
101
+ next_cursor: string | null;
102
+ }>;
103
+ packs(opts?: {
104
+ signal?: AbortSignal;
105
+ }): Promise<{
106
+ data: unknown[];
107
+ }>;
108
+ };
109
+ }
110
+ /**
111
+ * Construct a typed CHUZI API client. Bearer-token auth via Authorization
112
+ * header (works for web/SPA and native apps). The host owns token storage
113
+ * and lifecycle — pass `getToken` to plug in localStorage / AsyncStorage /
114
+ * SecureStore as appropriate.
115
+ *
116
+ * Surfaces not yet wired: scenes, scene-actions, media, exports, admin,
117
+ * reports. Add them here as the migration reaches each surface; the route
118
+ * shapes are documented in chuzi-api/routes/api.php.
119
+ */
120
+ declare function createChuziClient(config: ChuziClientConfig): ChuziClient;
121
+
122
+ export { ChuziApiError, type ChuziClient, type ChuziClientConfig, type TokenResolver, createChuziClient };
@@ -0,0 +1,108 @@
1
+ // src/api/index.ts
2
+ var ChuziApiError = class extends Error {
3
+ constructor(status, body, message) {
4
+ super(message ?? `chuzi-api ${status}`);
5
+ this.status = status;
6
+ this.body = body;
7
+ this.name = "ChuziApiError";
8
+ }
9
+ status;
10
+ body;
11
+ };
12
+ function buildQuery(query) {
13
+ if (!query) return "";
14
+ const params = new URLSearchParams();
15
+ for (const [k, v] of Object.entries(query)) {
16
+ if (v === void 0) continue;
17
+ params.set(k, String(v));
18
+ }
19
+ const s = params.toString();
20
+ return s ? `?${s}` : "";
21
+ }
22
+ function makeRequester(config) {
23
+ const fetchFn = config.fetch ?? globalThis.fetch;
24
+ const baseUrl = config.baseUrl.replace(/\/+$/, "");
25
+ return async function request(method, path, opts = {}) {
26
+ const headers = {
27
+ Accept: "application/json"
28
+ };
29
+ if (config.client) headers["X-Chuzi-Client"] = config.client;
30
+ if (config.getToken) {
31
+ const token = await config.getToken();
32
+ if (token) headers["Authorization"] = `Bearer ${token}`;
33
+ }
34
+ if (opts.body !== void 0) headers["Content-Type"] = "application/json";
35
+ const res = await fetchFn(`${baseUrl}${path}${buildQuery(opts.query)}`, {
36
+ method,
37
+ headers,
38
+ body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0,
39
+ signal: opts.signal
40
+ });
41
+ if (res.status === 204) return void 0;
42
+ let parsed = null;
43
+ const text = await res.text();
44
+ if (text.length > 0) {
45
+ try {
46
+ parsed = JSON.parse(text);
47
+ } catch {
48
+ parsed = text;
49
+ }
50
+ }
51
+ if (!res.ok) {
52
+ throw new ChuziApiError(res.status, parsed);
53
+ }
54
+ return parsed;
55
+ };
56
+ }
57
+ function createChuziClient(config) {
58
+ const request = makeRequester(config);
59
+ return {
60
+ auth: {
61
+ login: (req) => request("POST", "/api/v1/auth/login", { body: req }),
62
+ register: (req) => request("POST", "/api/v1/auth/register", { body: req }),
63
+ logout: () => request("POST", "/api/v1/auth/logout"),
64
+ forgotPassword: (req) => request("POST", "/api/v1/auth/forgot-password", { body: req }),
65
+ resetPassword: (req) => request("POST", "/api/v1/auth/reset-password", { body: req }),
66
+ user: () => request("GET", "/api/v1/auth/user")
67
+ },
68
+ catalog: {
69
+ index: (opts) => request("GET", "/api/v1/catalog", opts)
70
+ },
71
+ config: {
72
+ realms: (opts) => request("GET", "/api/v1/config/realms", {
73
+ signal: opts?.signal,
74
+ query: opts?.locale ? { locale: opts.locale } : void 0
75
+ })
76
+ },
77
+ stories: {
78
+ index: (opts) => request("GET", "/api/v1/stories", opts),
79
+ show: (id, opts) => request("GET", `/api/v1/stories/${encodeURIComponent(id)}`, opts),
80
+ mine: (opts) => request("GET", "/api/v1/stories/mine", opts),
81
+ create: (req) => request("POST", "/api/v1/stories", { body: req })
82
+ },
83
+ watch: {
84
+ sceneMap: (storyId, opts) => request("GET", `/api/v1/stories/${encodeURIComponent(storyId)}/scene-map`, opts),
85
+ trackEngagement: (storyId, req) => request("POST", `/api/v1/stories/${encodeURIComponent(storyId)}/engagement`, { body: req }),
86
+ saveBookmark: (storyId, req) => request("POST", `/api/v1/stories/${encodeURIComponent(storyId)}/bookmark`, { body: req }),
87
+ listBookmarks: (storyId, opts) => request("GET", `/api/v1/stories/${encodeURIComponent(storyId)}/bookmarks`, opts)
88
+ },
89
+ user: {
90
+ profile: (opts) => request("GET", "/api/v1/user/profile", opts),
91
+ updateRealm: (req) => request("PUT", "/api/v1/user/realm", { body: req }),
92
+ updateLocale: (req) => request("PUT", "/api/v1/user/locale", { body: req }),
93
+ updateProfile: (req) => request("PUT", "/api/v1/user/profile", { body: req })
94
+ },
95
+ credits: {
96
+ balance: (opts) => request("GET", "/api/v1/credits/balance", opts),
97
+ ledger: (opts) => request("GET", "/api/v1/credits/ledger", {
98
+ signal: opts?.signal,
99
+ query: { cursor: opts?.cursor, limit: opts?.limit }
100
+ }),
101
+ packs: (opts) => request("GET", "/api/v1/credits/packs", opts)
102
+ }
103
+ };
104
+ }
105
+
106
+ export { ChuziApiError, createChuziClient };
107
+ //# sourceMappingURL=index.js.map
108
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/api/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type {\n BookmarkListResponse,\n BookmarkResponse,\n CatalogResponse,\n CreateStoryRequest,\n EngagementResponse,\n LocaleId,\n LoginRequest,\n LoginResponse,\n PaginatedResponse,\n RealmConfigResponse,\n SaveBookmarkRequest,\n SceneMapResponse,\n StoryListItem,\n TrackEngagementRequest,\n UpdateLocaleRequest,\n UpdateLocaleResponse,\n UpdateProfileRequest,\n UpdateProfileResponse,\n UpdateRealmRequest,\n UpdateRealmResponse,\n UserProfile,\n} from \"../types/index.js\";\n\n/**\n * Token resolver returned by the host app. Called on every request; may be\n * synchronous (in-memory) or async (SecureStore on RN-tvOS, AsyncStorage on\n * RN). Return null when the user is unauthenticated.\n */\nexport type TokenResolver = () => string | null | Promise<string | null>;\n\nexport interface ChuziClientConfig {\n /** e.g. \"https://api.dev.chuzi.app\" — no trailing slash required. */\n baseUrl: string;\n /** Returns the current bearer token, or null if unauthenticated. */\n getToken?: TokenResolver;\n /** Override fetch for testing or RN polyfills. Defaults to globalThis.fetch. */\n fetch?: typeof fetch;\n /** Sent as User-Agent (web), or X-Client header (RN). Optional. */\n client?: string;\n}\n\nexport class ChuziApiError extends Error {\n constructor(\n public status: number,\n public body: unknown,\n message?: string,\n ) {\n super(message ?? `chuzi-api ${status}`);\n this.name = \"ChuziApiError\";\n }\n}\n\ninterface RequestOptions {\n query?: Record<string, string | number | boolean | undefined>;\n body?: unknown;\n signal?: AbortSignal;\n}\n\nfunction buildQuery(query: RequestOptions[\"query\"]): string {\n if (!query) return \"\";\n const params = new URLSearchParams();\n for (const [k, v] of Object.entries(query)) {\n if (v === undefined) continue;\n params.set(k, String(v));\n }\n const s = params.toString();\n return s ? `?${s}` : \"\";\n}\n\nfunction makeRequester(config: ChuziClientConfig) {\n const fetchFn = config.fetch ?? globalThis.fetch;\n const baseUrl = config.baseUrl.replace(/\\/+$/, \"\");\n\n return async function request<T>(\n method: string,\n path: string,\n opts: RequestOptions = {},\n ): Promise<T> {\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n };\n if (config.client) headers[\"X-Chuzi-Client\"] = config.client;\n if (config.getToken) {\n const token = await config.getToken();\n if (token) headers[\"Authorization\"] = `Bearer ${token}`;\n }\n if (opts.body !== undefined) headers[\"Content-Type\"] = \"application/json\";\n\n const res = await fetchFn(`${baseUrl}${path}${buildQuery(opts.query)}`, {\n method,\n headers,\n body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,\n signal: opts.signal,\n });\n\n if (res.status === 204) return undefined as T;\n\n let parsed: unknown = null;\n const text = await res.text();\n if (text.length > 0) {\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = text;\n }\n }\n\n if (!res.ok) {\n throw new ChuziApiError(res.status, parsed);\n }\n return parsed as T;\n };\n}\n\nexport interface ChuziClient {\n auth: {\n login(req: LoginRequest): Promise<LoginResponse>;\n register(req: LoginRequest & { name: string }): Promise<LoginResponse>;\n logout(): Promise<void>;\n forgotPassword(req: { email: string }): Promise<{ status: string }>;\n resetPassword(req: {\n token: string;\n email: string;\n password: string;\n password_confirmation: string;\n }): Promise<{ status: string }>;\n user(): Promise<UserProfile>;\n };\n catalog: {\n index(opts?: { signal?: AbortSignal }): Promise<CatalogResponse>;\n };\n config: {\n realms(opts?: { locale?: LocaleId; signal?: AbortSignal }): Promise<RealmConfigResponse>;\n };\n stories: {\n index(opts?: { signal?: AbortSignal }): Promise<PaginatedResponse<StoryListItem>>;\n show(id: string, opts?: { signal?: AbortSignal }): Promise<StoryListItem>;\n mine(opts?: { signal?: AbortSignal }): Promise<PaginatedResponse<StoryListItem>>;\n create(req: CreateStoryRequest): Promise<{ data: StoryListItem }>;\n };\n watch: {\n sceneMap(storyId: string, opts?: { signal?: AbortSignal }): Promise<SceneMapResponse>;\n trackEngagement(storyId: string, req: TrackEngagementRequest): Promise<EngagementResponse>;\n saveBookmark(storyId: string, req: SaveBookmarkRequest): Promise<BookmarkResponse>;\n listBookmarks(storyId: string, opts?: { signal?: AbortSignal }): Promise<BookmarkListResponse>;\n };\n user: {\n profile(opts?: { signal?: AbortSignal }): Promise<UserProfile>;\n updateRealm(req: UpdateRealmRequest): Promise<UpdateRealmResponse>;\n updateLocale(req: UpdateLocaleRequest): Promise<UpdateLocaleResponse>;\n updateProfile(req: UpdateProfileRequest): Promise<UpdateProfileResponse>;\n };\n credits: {\n balance(opts?: { signal?: AbortSignal }): Promise<{ balance: number }>;\n ledger(opts?: {\n signal?: AbortSignal;\n cursor?: string;\n limit?: number;\n }): Promise<{ data: unknown[]; next_cursor: string | null }>;\n packs(opts?: { signal?: AbortSignal }): Promise<{ data: unknown[] }>;\n };\n}\n\n/**\n * Construct a typed CHUZI API client. Bearer-token auth via Authorization\n * header (works for web/SPA and native apps). The host owns token storage\n * and lifecycle — pass `getToken` to plug in localStorage / AsyncStorage /\n * SecureStore as appropriate.\n *\n * Surfaces not yet wired: scenes, scene-actions, media, exports, admin,\n * reports. Add them here as the migration reaches each surface; the route\n * shapes are documented in chuzi-api/routes/api.php.\n */\nexport function createChuziClient(config: ChuziClientConfig): ChuziClient {\n const request = makeRequester(config);\n\n return {\n auth: {\n login: (req) => request(\"POST\", \"/api/v1/auth/login\", { body: req }),\n register: (req) => request(\"POST\", \"/api/v1/auth/register\", { body: req }),\n logout: () => request(\"POST\", \"/api/v1/auth/logout\"),\n forgotPassword: (req) => request(\"POST\", \"/api/v1/auth/forgot-password\", { body: req }),\n resetPassword: (req) => request(\"POST\", \"/api/v1/auth/reset-password\", { body: req }),\n user: () => request(\"GET\", \"/api/v1/auth/user\"),\n },\n catalog: {\n index: (opts) => request(\"GET\", \"/api/v1/catalog\", opts),\n },\n config: {\n realms: (opts) =>\n request(\"GET\", \"/api/v1/config/realms\", {\n signal: opts?.signal,\n query: opts?.locale ? { locale: opts.locale } : undefined,\n }),\n },\n stories: {\n index: (opts) => request(\"GET\", \"/api/v1/stories\", opts),\n show: (id, opts) => request(\"GET\", `/api/v1/stories/${encodeURIComponent(id)}`, opts),\n mine: (opts) => request(\"GET\", \"/api/v1/stories/mine\", opts),\n create: (req) => request(\"POST\", \"/api/v1/stories\", { body: req }),\n },\n watch: {\n sceneMap: (storyId, opts) =>\n request(\"GET\", `/api/v1/stories/${encodeURIComponent(storyId)}/scene-map`, opts),\n trackEngagement: (storyId, req) =>\n request(\"POST\", `/api/v1/stories/${encodeURIComponent(storyId)}/engagement`, { body: req }),\n saveBookmark: (storyId, req) =>\n request(\"POST\", `/api/v1/stories/${encodeURIComponent(storyId)}/bookmark`, { body: req }),\n listBookmarks: (storyId, opts) =>\n request(\"GET\", `/api/v1/stories/${encodeURIComponent(storyId)}/bookmarks`, opts),\n },\n user: {\n profile: (opts) => request(\"GET\", \"/api/v1/user/profile\", opts),\n updateRealm: (req) => request(\"PUT\", \"/api/v1/user/realm\", { body: req }),\n updateLocale: (req) => request(\"PUT\", \"/api/v1/user/locale\", { body: req }),\n updateProfile: (req) => request(\"PUT\", \"/api/v1/user/profile\", { body: req }),\n },\n credits: {\n balance: (opts) => request(\"GET\", \"/api/v1/credits/balance\", opts),\n ledger: (opts) =>\n request(\"GET\", \"/api/v1/credits/ledger\", {\n signal: opts?.signal,\n query: { cursor: opts?.cursor, limit: opts?.limit },\n }),\n packs: (opts) => request(\"GET\", \"/api/v1/credits/packs\", opts),\n },\n };\n}\n"]}
@@ -0,0 +1,39 @@
1
+ import { ContentRating, ContentRatingDefinition, LocaleId, RealmId, RealmDefinition } from '../types/index.js';
2
+
3
+ declare const REALM_IDS: readonly RealmId[];
4
+ declare const SUPPORTED_LOCALES: readonly LocaleId[];
5
+ declare const LOCALE_LABELS: Record<LocaleId, string>;
6
+ declare const DEFAULT_LOCALE: LocaleId;
7
+ declare function isSupportedLocale(value: unknown): value is LocaleId;
8
+ /**
9
+ * Normalize a raw locale tag (e.g. "en-US", "pt_BR") to a supported LocaleId,
10
+ * matching the PHP `LocaleResolver::normalize` behavior.
11
+ */
12
+ declare function normalizeLocale(value: string | null | undefined): LocaleId | null;
13
+ /**
14
+ * Pick the best supported locale from a browser Accept-Language string.
15
+ * Mirrors PHP `LocaleResolver::matchAcceptLanguage` (q-value aware).
16
+ */
17
+ declare function matchAcceptLanguage(accept: string | null | undefined): LocaleId | null;
18
+ declare const CONTENT_RATING_IDS: readonly ContentRating[];
19
+ declare const CONTENT_RATINGS: Record<ContentRating, ContentRatingDefinition>;
20
+ declare function isContentRating(value: unknown): value is ContentRating;
21
+ declare const REALMS: Record<RealmId, RealmDefinition>;
22
+ declare const FALLBACK_LEXICON: Record<string, string>;
23
+ /**
24
+ * Per-locale overrides for FALLBACK_LEXICON. Missing keys fall through to
25
+ * the English fallback. Mirrors PHP `chuzi_realms.fallback_locales`.
26
+ */
27
+ declare const FALLBACK_LOCALES: Partial<Record<LocaleId, Record<string, string>>>;
28
+ /**
29
+ * Get a lexicon value for a realm + locale, falling back to the neutral
30
+ * lexicon (which itself respects locale).
31
+ */
32
+ declare function t(realmId: RealmId | null | undefined, key: string, fallback?: string, locale?: LocaleId | null | undefined): string;
33
+ /**
34
+ * Get the full merged lexicon for a realm + locale (realm lexicon on top of
35
+ * locale-aware fallback).
36
+ */
37
+ declare function lexiconForRealm(realmId: RealmId | null | undefined, locale?: LocaleId | null | undefined): Record<string, string>;
38
+
39
+ export { CONTENT_RATINGS, CONTENT_RATING_IDS, DEFAULT_LOCALE, FALLBACK_LEXICON, FALLBACK_LOCALES, LOCALE_LABELS, REALMS, REALM_IDS, SUPPORTED_LOCALES, isContentRating, isSupportedLocale, lexiconForRealm, matchAcceptLanguage, normalizeLocale, t };