@snail-js/api
Version:
Http Request with Decorators Api, build on axios
607 lines (502 loc) • 14.4 kB
Markdown
<p>
<img src="https://img.shields.io/badge/TypeScript-1e80ff"></img>
<img src="https://img.shields.io/npm/v/axios?label=axios&labelColor=1e80ff&color=67C23A"></img>
<img src="https://img.shields.io/npm/v/reflect-metadata?label=reflect-metadata&labelColor=1e80ff&color=67C23A"></img>
</p>
<a href='./README.md'>中文文档</a>|English Document
## Project Introduction
- Secondary encapsulation based on Axios
- Use `reflect-metadata` to create and process metadata
- Provide decorators to define request methods, supporting all HTTP methods and SSE
## Installation
`npm install -js/api`
## Usage
1. Enable TypeScript decorator configuration:
```json
// tsconfig.json
{
"module": "ESNext",
// Module resolution strategy
"moduleResolution": "node",
"baseUrl": ".",
// Target must be higher than ES6
"target": "ESNext",
// lib should include ES versions above ES6
"lib": ["ESNext", "DOM"],
// Include reflect-metadata types
"types": ["reflect-metadata"],
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"strictNullChecks": false
}
```
2. Create Snail backend configuration instance:
```ts
// service.ts
import { SnailServer, Server } from "@snail-js/api";
({
baseURL: "/api",
timeout: 5000,
})
class BackEnd extends SnailServer {}
export const Service = new BackEnd();
```
3. Create API instance:
```ts
// user.ts
import { Api, Get, Post, Query, Data, SnailApi } from "@snail-js/api";
import { Service } from "./service";
("user")
class UserApi extends SnailApi {
()
get(("id") id: string) {}
()
create(() user: User) {}
}
// Create and export API
export const userApi = Service.createApi(UserApi);
```
4. Send requests:
```ts
import { userApi } from "./user";
const { send:getUser, onSuccess, onError, onHitCache } = await userApi.get();
const data = await getUser("1");
```
## `SnailMethod` Instance
- When calling `Service.createApi(ApiInstance)`, creates a proxy for methods decorated with `RequestMethod` (e.g. , )
- Returns a function containing request parameters, which when called returns a `SnailMethod` instance
### `SnailMethod` Methods
- `send`: Send request
_Async function that executes the current request_
- `onSuccess`: Request success callback
_Register success event handler_
- `onError`: Request failure callback
_Register error event handler_
- `onHitCache`: Cache hit callback
_Register cache hit event handler_
- `onFinish`: Request completion callback
_Register completion event handler (fires on both success/error)_
- `registerStrategies` Register Strategies
_Register Strategies of this method instance_
### `SnailMethod` Properties
- `response`: AxiosResponse - Raw response object
- `request`: AxiosRequestConfig - Final processed request after applying Versioning and Strategies
- `version`: string - Effective request version (undefined if Versioning disabled)
- `name`: string - Full method identifier in `ServerName.ApiName.MethodName` format
- `error`: Error | null - Error object if request failed, null otherwise
### Server Configuration
<table>
<tr>
<th>Option</th>
<th>Type</th>
<th>Required</th>
<th>Default Value</th>
<th>Description</th>
</tr>
<tr>
<td>name</td>
<td>string</td>
<td>No</td>
<td>Class name inheriting SnailServer</td>
<td>Unique server identifier</td>
</tr>
<tr>
<td>baseUrl</td>
<td>string</td>
<td>No</td>
<td>'\'</td>
<td>API prefix (same as axios baseURL)</td>
</tr>
<tr>
<td>Versioning</td>
<td><a href="#versioningoption">VersioningOption</a></td>
<td>No</td>
<td>undefine</td>
<td>Versioning configuration</td>
</tr>
<tr>
<td>timeout</td>
<td>number</td>
<td>No</td>
<td>5000</td>
<td>Global timeout (ms)</td>
</tr>
<tr>
<td>cacheManage</td>
<td>{type:CacheType,ttl:number}</td>
<td>No</td>
<td>{
type: CacheType.Memory,
ttl: 500
}</td>
<td>Cache manager (ttl in seconds)</td>
</tr>
<tr>
<td>cacheFor</td>
<td>RequestMethod | RequestMethod[] | 'All' | 'all' </td>
<td>No</td>
<td>Get</td>
<td>Methods to enable caching</td>
</tr>
<tr>
<td>serverStatusCodeRule</td>
<td><a href="#SnailServerStatusCodeRuleOptions">SnailServerStatusCodeRuleOptions</a></td>
<td>否</td>
<td>undefined</td>
<td>Server status code validation rules (triggers error when rule function returns false)</td>
</tr>
<tr>
<td>enableLog</td>
<td>boolean</td>
<td>No</td>
<td>false</td>
<td>Enable debug logs</td>
</tr>
</table>
### API Configuration
- Decorate API classes with `()` and extend `SnailApi`
<table>
<tr>
<th>Option</th>
<th>Type</th>
<th>Required</th>
<th>Default Value</th>
<th>Description</th>
</tr>
<tr>
<td>name</td>
<td>string</td>
<td>No</td>
<td>default use extends`SnailApi` class name</td>
<td>Unique API identifier</td>
</tr>
<tr>
<td>timeout</td>
<td>number</td>
<td>No</td>
<td></td>
<td>Overrides server timeout</td>
</tr>
<tr>
<td>version</td>
<td>string</td>
<td>No</td>
<td></td>
<td>Overrides server defaultVersion</td>
</tr>
</table>
## Request Method Decorators
- Used in API classes to mark request methods
- Supports all axios methods: Get, Post, Head, Put, Delete, Patch, Options
- Parameter: path?: string - Endpoint path (combined with baseURL)
## Parameter Decorators
### Query Parameters
- (key?:string)
Example:
```ts
("user")
class UserApi {
()
get(("id") id: string, ("sign") sign: string) {}
}
```
> Parameters will be appended as ?k1=v1&k2=v2
### Route Parameters
- (key?:string)
Example with object:
```ts
class RouteParams {
id: string;
sign: string;
}
("user/:id/:sign")
class UserApi {
()
get(() params: RouteParams) {}
}
```
> Automatically maps object properties to route parameters
### Request Data
- `(key?:string)`
- Usage pattern same as ``, supports mixed usage
## Strategy Decorator ``
- `(...Strategy[])`
### Request Strategy
- Executed before request sending, subsequent strategy results override previous ones
- If returns processed request, uses it for sending; otherwise uses original or previous strategy's result
```typescript
class CustomStrategy extends Strategy {
applyRequest(request: AxiosRequestConfig) {
request.headers["Access-Token"] = "abcde";
return request;
}
}
// Applied to Snail for global request strategies
({
baseURL: "/api",
timeout: 5000,
})
(CustomStrategy)
class BackEnd extends Snail<ShanheResponse> {}
export const Service = new BackEnd();
// Register strategy after instance creation
Service.registerStrategies(CustomStrategy);
// Applied to API for method-specific strategies
("test")
(CustomStrategy)
class Test {}
const TestApi = Service.createApi(Test);
TestApi.registerStrategies(CustomStrategy);
// Applied to method for endpoint-level strategies
("test")
(CustomStrategy)
class Test {
()
(CustomStrategy)
get() {}
}
// Register strategy before request
const TestApi = Service.createApi(Test);
const getSomething = TestApi.get();
{ send, registerStrategies } = getSomething;
registerStrategies(CustomStrategy);
```
### Response Strategy
- Executed after receiving server response
- Processed response propagates through strategy chain
```typescript
class CustomStrategy extends Strategy {
applyResponse(response: AxiosResponse) {
const { status } = response;
if (status == 200) {
// Custom processing
}
return response;
}
}
```
## Version Management Decorators &
### Version Manager (VersioningOption)
- Global version management
```typescript
({
baseURL: "/api",
timeout: 5000,
})
({
type: VersioningType.Header,
defaultVersion: "0.1.0",
})
class BackEnd extends Snail<ShanheResponse> {}
export const Service = new BackEnd();
```
### <a id='versioningOption'>VersioningOption Type</a>
```typescript
export enum VersioningType {
Uri,
Header,
Query,
Custom,
}
interface VersioningCommonOption {
defaultVersion: string;
}
export interface VersioningUriOption extends VersioningCommonOption {
type: VersioningType.Uri;
prefix?: string;
}
export interface VersioningHeaderOption extends VersioningCommonOption {
type: VersioningType.Header;
header?: string;
}
export interface VersioningQueryOption extends VersioningCommonOption {
type: VersioningType.Query;
key?: string;
}
export interface VersioningCustomOption extends VersioningCommonOption {
type: VersioningType.Custom;
extractor: (requestOptions: unknown) => {
url: string;
headers: Record<string, any>;
};
}
export type VersioningOption =
| VersioningUriOption
| VersioningHeaderOption
| VersioningQueryOption
| VersioningCustomOption;
```
### <a id="SnailServerStatusCodeRuleOptions">SnailServerStatusCodeRuleOptions</a> Type
```typescript
export class SnailServerStatusCodeRuleOptions {
// Defines validation rules for server status codes
// Triggers error when rule function returns false
rule: (statusCode: number) => boolean;
// Specifies the key in response.data for status code (default: 'code')
key?: string;
}
```
### Temporary Version Modifier
- Override version for specific methods
```typescript
("test")
class Test {
("HelloWorld")
("0.2.0")
test() {}
}
```
> Enables temporary version override for testing
### Cache Decorator
- (name:string)
- Defines cache invalidation sources
- Name format: serverName:apiName:methodName
> Note: Use configured names if available, otherwise class names
```typescript
("test",{name:'api1'})
("api1")
class Test {
("HelloWorld")
("api1.test2")
test1() {}
()
test2() {}
()
// Invalidates cache for all Test class methods
("api1")
test3() {}
}
```
> Successful [Post]test calls invalidate [Get]test/HelloWorld cache Default caching only for GET methods. Use ({cacheFor:'all'}) for other methods
> When `test3` method request succeeds, it won't be cached
> Note: To enable caching, configure `({cacheManage})` cache manager
### Upload Progress Decorator ``
- `((progressEvent: AxiosProgressEvent) => void)`
### Download Progress Decorator ``
- `((progressEvent: AxiosProgressEvent) => void)`
## Server-Sent Events (SSE)
### Create SSE Endpoint
```typescript
("sse")
class ServerSend extends SnailSse {
()
handleOpen(event: Event) {
console.log("SSE connection opened:", event);
}
()
handleError(event: Event) {
console.log("SSE error occurred:", event);
}
// Handle default message events
()
handleEvent(event: MessageEvent) {
console.log("SSE message event:", event.data);
}
// Handle custom named events
("chunk")
handleChunkEvent(event: Event) {
console.log("SSE chunk event:", event);
}
}
export const Sse = Service.createSse(ServerSend);
```
### SSE Decorator
- (path: string, options?: { withCredentials?: boolean, version?: string })
- Creates a server-sent events connection, returns a function to open SSE connection:
- Returns { eventSource: EventSource, close: () => void } when called:
- eventSource: SSE connection instance
- close: Method to close the connection
### SSE Open Handler Decorator
- Registers decorated method as onopen handler for EventSource instances created by decorated methods
### SSE Error Handler Decorator
- Registers decorated method as onerror handler for EventSource instances created by decorated methods
### SSE Event Handler Decorator
- (eventName?: string)
- Without eventName : Registers as default message event handler
- With eventName : Registers as handler for specified custom event
------
## TypeScript Support
### Default Response Type
```typescript
export type StandardResponseData<
T extends ResponseJsonData = Record<string, any>
> = {
code: number;
message: string;
data: T;
};
```
### Custom Response Types
1. Define backend response format:
```typescript
export class CustomResponse {
status_code: number;
msg: string;
}
```
2. Apply type when creating service:
```typescript
// service.ts
import { SnailServer, Server } from "@snail-js/api";
({
baseURL: "/api",
timeout: 5000,
})
class BackEnd extends SnailServer<CustomResponse> {}
export const Service = new BackEnd();
```
3. Type annotation when calling APIs:
```typescript
import { userApi } from "./user";
class User {
id: number;
name: string;
tel: string;
age: number;
}
const getUser = userApi.get<User>();
const { send } = getUser;
const res = await send("1");
// Default data key:
// res.data => CustomResponse & { data: User }
```
> API response format:
```typescript
const getUser = userApi.get<User>();
const { send } = getUser;
const res = await send("1");
// res.data => CustomResponse & { data: User }
const getUser = userApi.get<Blob>();
const { send } = getUser;
const res = await send("1");
// res => AxiosResponse<Blob>
```
4. Custom data key configuration:
```typescript
({
baseURL: "/api",
timeout: 5000,
})
class BackEnd extends Snail<CustomResponse, "record"> {}
const { send } = userApi.get<User>();
const res = await send("1");
// Custom data key:
// res.data => CustomResponse & { record: User }
```
### Non-JSON Responses
- For non-JSON content-type responses: send() returns AxiosResponse
- For JSON content-type responses: send() returns AxiosResponse.data
### Repository
<p>
<a href="https://gitee.com/limich/snail">
<img src="https://img.shields.io/badge/snail-js?style=flat&label=gitee&labelColor=F56C6C&link=https%3A%2F%2Fgitee.com%2Flimich%2Fsnail"></img>
</a>
</p>
<p>
<a href="https://github.com/limingchang/snail">
<img src="https://img.shields.io/badge/snail-js?style=flat&label=github&labelColor=F56C6C&link=https%3A%2F%2Fgihub.com%2Flimingchang%2Fsnail"></img>
</a>
</p>
### Author
- mc.lee