Initialize React SDK
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.DS_Store
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
@@ -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()`。
|
||||
Generated
+61
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
Reference in New Issue
Block a user