@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.
- package/LICENSE +21 -0
- package/README.md +52 -0
- package/dist/api/index.d.ts +122 -0
- package/dist/api/index.js +108 -0
- package/dist/api/index.js.map +1 -0
- package/dist/config/index.d.ts +39 -0
- package/dist/config/index.js +404 -0
- package/dist/config/index.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +627 -0
- package/dist/index.js.map +1 -0
- package/dist/input/index.d.ts +70 -0
- package/dist/input/index.js +41 -0
- package/dist/input/index.js.map +1 -0
- package/dist/realms/cosmos/components/index.d.ts +68 -0
- package/dist/realms/cosmos/components/index.js +172 -0
- package/dist/realms/cosmos/components/index.js.map +1 -0
- package/dist/realms/cosmos/index.d.ts +8 -0
- package/dist/realms/cosmos/index.js +76 -0
- package/dist/realms/cosmos/index.js.map +1 -0
- package/dist/realms/index.d.ts +109 -0
- package/dist/realms/index.js +23 -0
- package/dist/realms/index.js.map +1 -0
- package/dist/realms/wilds/components/index.d.ts +88 -0
- package/dist/realms/wilds/components/index.js +359 -0
- package/dist/realms/wilds/components/index.js.map +1 -0
- package/dist/realms/wilds/index.d.ts +8 -0
- package/dist/realms/wilds/index.js +70 -0
- package/dist/realms/wilds/index.js.map +1 -0
- package/dist/themes/index.d.ts +46 -0
- package/dist/themes/index.js +63 -0
- package/dist/themes/index.js.map +1 -0
- package/dist/types/index.d.ts +292 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/ui/index.d.ts +71 -0
- package/dist/ui/index.js +551 -0
- package/dist/ui/index.js.map +1 -0
- 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 };
|