Initialize React SDK

This commit is contained in:
2026-09-15 19:00:10 +08:00
commit 592bf1a220
6 changed files with 168 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
node_modules/
dist/
.DS_Store
.env
.env.*
!.env.example
+32
View File
@@ -0,0 +1,32 @@
# React SDK
使用 TypeScript 开发的 React SDK,配套 `backend-server`。
## 开发
```sh
npm ci
npm run typecheck
npm run build
```
## 使用
本包尚未发布。先运行 `npm run build`,再在 React 项目中安装本地目录:
```sh
npm install /absolute/path/to/react-sdk
```
```tsx
import { useHealth } from "@entroia/react-sdk";
export function ServerStatus() {
const { data, error, loading } = useHealth("http://localhost:8000");
if (loading) return <p>加载中</p>;
if (error) return <p>{error.message}</p>;
return <p>{data?.status}</p>;
}
```
非组件场景可以使用 `createClient({ baseUrl }).health()`。
+61
View File
@@ -0,0 +1,61 @@
{
"name": "@entroia/react-sdk",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@entroia/react-sdk",
"version": "0.1.0",
"devDependencies": {
"@types/react": "^19.0.0",
"react": "^19.0.0",
"typescript": "^5.0.0"
},
"peerDependencies": {
"react": ">=18 <20"
}
},
"node_modules/@types/react": {
"version": "19.3.0",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz",
"integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==",
"dev": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.2.2"
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true,
"license": "MIT"
},
"node_modules/react": {
"version": "19.3.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz",
"integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
}
}
}
+13
View File
@@ -0,0 +1,13 @@
{
"name": "@entroia/react-sdk",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {".": {"types": "./dist/index.d.ts", "import": "./dist/index.js"}},
"files": ["dist"],
"scripts": {"build": "tsc -p tsconfig.json", "typecheck": "tsc --noEmit"},
"peerDependencies": {"react": ">=18 <20"},
"devDependencies": {"@types/react": "^19.0.0", "react": "^19.0.0", "typescript": "^5.0.0"}
}
+48
View File
@@ -0,0 +1,48 @@
import { useEffect, useState } from "react";
export interface HealthResponse {
status: "ok";
}
export interface ClientOptions {
baseUrl: string;
}
export function createClient({ baseUrl }: ClientOptions) {
const url = baseUrl.replace(/\/+$/, "");
return {
async health(signal?: AbortSignal): Promise<HealthResponse> {
const response = await fetch(`${url}/health`, { signal });
if (!response.ok) {
throw new Error(`Health request failed: HTTP ${response.status}`);
}
const data: unknown = await response.json();
if (typeof data !== "object" || data === null || !("status" in data) || data.status !== "ok") {
throw new Error("Invalid health response");
}
return { status: "ok" };
},
};
}
export function useHealth(baseUrl: string) {
const [data, setData] = useState<HealthResponse | null>(null);
const [error, setError] = useState<Error | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const controller = new AbortController();
setLoading(true);
setData(null);
setError(null);
createClient({ baseUrl }).health(controller.signal)
.then((result) => { if (!controller.signal.aborted) setData(result); })
.catch((cause: unknown) => {
if (!controller.signal.aborted) setError(cause instanceof Error ? cause : new Error(String(cause)));
})
.finally(() => { if (!controller.signal.aborted) setLoading(false); });
return () => controller.abort();
}, [baseUrl]);
return { data, error, loading };
}
+8
View File
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext",
"lib": ["ES2022", "DOM"], "strict": true, "declaration": true,
"outDir": "dist", "rootDir": "src", "skipLibCheck": true
},
"include": ["src"]
}