@snail-js/api 0.1.1 → 0.1.2
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/README.md +268 -103
- package/dist/cache/index.d.ts +1 -1
- package/dist/core/index.d.ts +0 -1
- package/dist/core/snail.d.ts +19 -19
- package/dist/decorators/api.d.ts +12 -0
- package/dist/decorators/cache.d.ts +4 -0
- package/dist/decorators/param.d.ts +4 -0
- package/dist/decorators/server.d.ts +4 -0
- package/dist/decorators/strategy.d.ts +4 -0
- package/dist/decorators/versioning.d.ts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/{snail-api.mjs → snail-api.js} +1513 -425
- package/dist/{snail-api.umd.js → snail-api.umd.cjs} +1515 -427
- package/dist/strategies/jwtStrategy.d.ts +4 -0
- package/dist/typings/api.config.d.ts +5 -24
- package/dist/typings/apiProxy.d.ts +11 -0
- package/dist/typings/cache.management.option.d.ts +19 -0
- package/dist/typings/index.d.ts +5 -3
- package/dist/typings/response.data.d.ts +3 -3
- package/dist/typings/snail.option.d.ts +9 -0
- package/dist/typings/strategy.d.ts +5 -0
- package/dist/typings/versioning.option.d.ts +30 -0
- package/dist/utils/function.d.ts +0 -2
- package/dist/versioning/versioning.d.ts +3 -2
- package/package.json +17 -7
- package/dist/core/api.d.ts +0 -40
- package/dist/typings/cache.management.config.d.ts +0 -19
- package/dist/typings/snail.config.d.ts +0 -21
- package/dist/typings/versioning.config.d.ts +0 -30
package/README.md
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
## 项目介绍
|
|
6
6
|
|
|
7
7
|
- 基于 Axios 二次封装
|
|
8
|
-
-
|
|
8
|
+
- 使用`reflect-metadata`创建和处理元数据
|
|
9
|
+
- 提供装饰器方式定义请求,基本实例`Snail`,请求实例`Api`
|
|
9
10
|
|
|
10
11
|
## 安装
|
|
11
12
|
|
|
@@ -13,134 +14,298 @@
|
|
|
13
14
|
|
|
14
15
|
## 使用
|
|
15
16
|
|
|
16
|
-
1.
|
|
17
|
+
1. 请开启`TypeScript`相关装饰器配置
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
// tsconfig.json
|
|
21
|
+
{
|
|
22
|
+
"module": "ESNext",
|
|
23
|
+
// 模块解析策略
|
|
24
|
+
"moduleResolution": "node",
|
|
25
|
+
"baseUrl": ".",
|
|
26
|
+
// target 必须大于ES6
|
|
27
|
+
"target": "ESNext",
|
|
28
|
+
// lib 需要包含大于ES6的ES版本
|
|
29
|
+
"lib": ["ESNext", "DOM"],
|
|
30
|
+
// 包含reflect-metadata类型
|
|
31
|
+
"types": ["reflect-metadata"],
|
|
32
|
+
"emitDecoratorMetadata": true,
|
|
33
|
+
"experimentalDecorators": true,
|
|
34
|
+
|
|
35
|
+
"skipLibCheck": true,
|
|
36
|
+
"strictNullChecks": false
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
2. 创建`Snail`后端基本配置实例
|
|
17
41
|
|
|
18
42
|
```typescript
|
|
19
|
-
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
Versioning: {
|
|
25
|
-
type: VersioningType.Uri,
|
|
26
|
-
prefix: "v",
|
|
27
|
-
defaultVersion: "0.1.0",
|
|
28
|
-
},
|
|
43
|
+
// service.ts
|
|
44
|
+
import { Snail, Server } from "@snail-js/api";
|
|
45
|
+
|
|
46
|
+
@Server({
|
|
47
|
+
baseURL: "/api",
|
|
29
48
|
timeout: 5000,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return config
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
responseInterceptors: {
|
|
37
|
-
onFulfilled(response) {
|
|
38
|
-
console.log(response);
|
|
39
|
-
return response
|
|
40
|
-
},
|
|
41
|
-
onRejected(error) {
|
|
42
|
-
console.log(error);
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
CacheManage: {
|
|
46
|
-
type: CacheType.LocalStorage,
|
|
47
|
-
ttl: 60, //缓存过期时间
|
|
48
|
-
},
|
|
49
|
-
}
|
|
50
|
-
export SnailInstance = new Snail(options)
|
|
49
|
+
})
|
|
50
|
+
class BackEnd extends Snail {}
|
|
51
|
+
export const Service = new BackEnd();
|
|
51
52
|
```
|
|
52
53
|
|
|
53
|
-
|
|
54
|
+
3. 创建请求实例
|
|
54
55
|
|
|
55
56
|
```typescript
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
import { SnailInstance } from "./snail";
|
|
59
|
-
const pipe: RequestPipe = (input) => {
|
|
60
|
-
const { data, headers } = input;
|
|
61
|
-
const newHeaders = {
|
|
62
|
-
...headers,
|
|
63
|
-
pipe: "RequestPipe",
|
|
64
|
-
};
|
|
65
|
-
return {
|
|
66
|
-
data,
|
|
67
|
-
headers: newHeaders,
|
|
68
|
-
};
|
|
69
|
-
};
|
|
57
|
+
// user.ts
|
|
58
|
+
import { Api, Get, Post, Params, Data } from "@snail-js/api";
|
|
70
59
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
Api.use(pipe);
|
|
84
|
-
export const testApi = Api;
|
|
85
|
-
|
|
86
|
-
// 配置了hitSource,Api2请求成功时会使Api的缓存失效
|
|
87
|
-
// 当你更新了数据时,get请求的缓存失效,会发起请求,这很有用
|
|
88
|
-
const Api2 = Snail.Post("test",{hitSource:Api})
|
|
89
|
-
export const testApi2 = Api2
|
|
60
|
+
import { Service } from "./service";
|
|
61
|
+
|
|
62
|
+
@Api("user")
|
|
63
|
+
class UserApi {
|
|
64
|
+
@Get()
|
|
65
|
+
get(@Params("id") id: string) {}
|
|
66
|
+
|
|
67
|
+
@Post()
|
|
68
|
+
create(@Data() user: User) {}
|
|
69
|
+
}
|
|
70
|
+
// 创建并导出api
|
|
71
|
+
export const userApi = Service.createApi(UserApi);
|
|
90
72
|
```
|
|
91
73
|
|
|
92
|
-
3.
|
|
74
|
+
3. 发送请求
|
|
93
75
|
|
|
94
76
|
```typescript
|
|
95
|
-
import {
|
|
96
|
-
|
|
97
|
-
const res = await
|
|
98
|
-
const {
|
|
99
|
-
if (error
|
|
100
|
-
console.log(
|
|
101
|
-
}
|
|
102
|
-
Catch((error) => {
|
|
103
|
-
console.log(error);
|
|
104
|
-
});
|
|
105
|
-
// success {error:null,data}
|
|
106
|
-
// error {error,data:null} 附带的相关错误数据会保存在error.cause中
|
|
107
|
-
// 某些服务端标记的code !=0的请求,会被捕获为错误,而且携带数据
|
|
77
|
+
import { userApi } from "./user";
|
|
78
|
+
|
|
79
|
+
const res = await userApi.get("1");
|
|
80
|
+
const { error, data } = res;
|
|
81
|
+
if (error !== null) {
|
|
82
|
+
console.log(data);
|
|
83
|
+
}
|
|
108
84
|
```
|
|
109
85
|
|
|
110
|
-
###
|
|
86
|
+
### Server 配置
|
|
111
87
|
|
|
112
|
-
- `baseUrl`:同`Axios
|
|
88
|
+
- `baseUrl`:同`Axios`,使用`vite.proxy`时,请使用`\`开头,直接跨域请求请填写完整地址
|
|
113
89
|
- `Versioning`:版本管理器
|
|
114
|
-
- type:管理器类型,enum:Uri,Head,Query,Custom
|
|
115
|
-
- prifix:前缀
|
|
116
|
-
- defaultVersion:全局默认版本
|
|
90
|
+
- type:管理器类型,enum:Uri,Head,Query,Custom
|
|
91
|
+
- prifix:前缀
|
|
92
|
+
- defaultVersion:全局默认版本
|
|
117
93
|
- timenout:全局超时时间,会被 Api 的 timeout 值覆盖
|
|
118
|
-
- requestInterceptors:全局请求拦截器
|
|
119
|
-
- responseInterceptors:全局响应拦截器
|
|
120
94
|
- CacheManage:缓存管理器
|
|
121
|
-
- type:缓存管理器类型,CacheType,enum:localStorage,IndexDB,Memory
|
|
122
|
-
- ttl: 缓存过期时间
|
|
95
|
+
- type:缓存管理器类型,CacheType,enum:localStorage,IndexDB,Memory
|
|
96
|
+
- ttl: 缓存过期时间
|
|
123
97
|
|
|
124
98
|
## Api 配置
|
|
125
99
|
|
|
126
|
-
-
|
|
127
|
-
- timeout?: 请求超时时间;会覆盖`
|
|
128
|
-
- version?: 请求版本,会覆盖`
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
-
|
|
100
|
+
- url?: api 请求端点,与 Server 中的`baseUrl`拼接请求地址,,不要使用`/`开头
|
|
101
|
+
- timeout?: 请求超时时间;会覆盖`Server.timeout`
|
|
102
|
+
- version?: 请求版本,会覆盖`Server.Versioning.defaultVersion`;
|
|
103
|
+
|
|
104
|
+
## 请求方法装饰器
|
|
105
|
+
|
|
106
|
+
- 提供 axios 的全部请求方法`Get,Post,Head,Put,Delete,Patch,Options`
|
|
107
|
+
- path?: string; 请求端点路径,与`baseUrl,api.url`共同拼接组成最终请求路径,不要使用`/`开头
|
|
108
|
+
|
|
109
|
+
## 参数装饰器
|
|
110
|
+
|
|
111
|
+
### 查询参数 `@Params`
|
|
112
|
+
|
|
113
|
+
- `@Params(key?:string)`
|
|
114
|
+
|
|
115
|
+
- 单个参数使用
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
@Api("user")
|
|
119
|
+
class UserApi {
|
|
120
|
+
@Get()
|
|
121
|
+
get(@Params("id") id: string, @Params("sign") sign: string) {}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
> 传入 key,标记单个查询参数,拼接到请求`?k1=v1&k2=v2`
|
|
126
|
+
|
|
127
|
+
- 对象参数使用
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
class QueryParams {
|
|
131
|
+
id: string;
|
|
132
|
+
sign: string;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@Api("user")
|
|
136
|
+
class UserApi {
|
|
137
|
+
@Get()
|
|
138
|
+
get(@Params() params: QueryParams) {}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
> 不传入 key,会被标记为对象类型查询参数;也能自动拼接到请求
|
|
143
|
+
|
|
144
|
+
- 混合使用
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
class QueryParams {
|
|
148
|
+
id: string;
|
|
149
|
+
sign: string;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
@Api("user")
|
|
153
|
+
class UserApi {
|
|
154
|
+
@Get()
|
|
155
|
+
get(@Params() params: QueryParams, @Params("a") a: number) {}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 请求数据
|
|
160
|
+
|
|
161
|
+
- `@Data(key?:string)`
|
|
162
|
+
- 使用方式和`@Params`相同,也支持混合使用
|
|
133
163
|
|
|
134
|
-
##
|
|
164
|
+
## 策略装饰器`@UseStrategy`
|
|
165
|
+
|
|
166
|
+
- `@UseStrategy(Strategy[])`
|
|
167
|
+
|
|
168
|
+
### 请求策略
|
|
169
|
+
|
|
170
|
+
- 在请求发送前执行,后面的策略返回结果会覆盖前面的策略
|
|
171
|
+
- 必须将处理后的 request 返回
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
class CustomStrategy extends Strategy {
|
|
175
|
+
applyRequest(request: AxiosRequestConfig) {
|
|
176
|
+
request.headers["Access-Token"] = "abcde";
|
|
177
|
+
return request;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
135
180
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
181
|
+
// 用在Snail,全局的请求策略
|
|
182
|
+
|
|
183
|
+
@Server({
|
|
184
|
+
baseURL: "/api",
|
|
185
|
+
timeout: 5000,
|
|
186
|
+
})
|
|
187
|
+
@UseStrategy(new CustomStrategy())
|
|
188
|
+
class BackEnd extends Snail<ShanheResponse> {}
|
|
189
|
+
export const Service = new BackEnd();
|
|
190
|
+
|
|
191
|
+
// 用在Api, 当Api下的方法请求时生效
|
|
192
|
+
@Api("test")
|
|
193
|
+
@UseStrategy(new CustomStrategy())
|
|
194
|
+
class Test {}
|
|
195
|
+
|
|
196
|
+
// 用在方法,此方法请求时生效
|
|
197
|
+
@Api("test")
|
|
198
|
+
@UseStrategy(new CustomStrategy())
|
|
199
|
+
class Test {
|
|
200
|
+
@Get()
|
|
201
|
+
@UseStrategy(new CustomStrategy())
|
|
202
|
+
get() {}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### 响应策略
|
|
207
|
+
|
|
208
|
+
- 在收到服务器响应后执行
|
|
209
|
+
- 必须将处理后的 response 返回
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
// 如何定义
|
|
213
|
+
class CustomStrategy extends Strategy {
|
|
214
|
+
applyResponse(response: AxiosResponse) {
|
|
215
|
+
const { status } = response;
|
|
216
|
+
if (status == 200) {
|
|
217
|
+
// do something
|
|
218
|
+
}
|
|
219
|
+
return response;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## 版本管理装饰器`@Versioning`和`@Version`
|
|
225
|
+
|
|
226
|
+
### 版本管理器`@Versioning(VersioningOption)`
|
|
227
|
+
|
|
228
|
+
- 全局管理版本
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
@Server({
|
|
232
|
+
baseURL: "/api",
|
|
233
|
+
timeout: 5000,
|
|
234
|
+
})
|
|
235
|
+
@Versioning({
|
|
236
|
+
type: VersioningType.Header,
|
|
237
|
+
defaultVersion: "0.1.0",
|
|
238
|
+
})
|
|
239
|
+
class BackEnd extends Snail<ShanheResponse> {}
|
|
240
|
+
|
|
241
|
+
export const Service = new BackEnd();
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
#### `VersioningOption`类型
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
export enum VersioningType {
|
|
248
|
+
Uri,
|
|
249
|
+
Header,
|
|
250
|
+
Query,
|
|
251
|
+
Custom,
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
interface VersioningCommonOption {
|
|
255
|
+
defaultVersion: string;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export interface VersioningUriOption extends VersioningCommonOption {
|
|
259
|
+
type: VersioningType.Uri;
|
|
260
|
+
prefix?: string;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export interface VersioningHeaderOption extends VersioningCommonOption {
|
|
264
|
+
type: VersioningType.Header;
|
|
265
|
+
header?: string;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export interface VersioningQueryOption extends VersioningCommonOption {
|
|
269
|
+
type: VersioningType.Query;
|
|
270
|
+
key?: string;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export interface VersioningCustomOption extends VersioningCommonOption {
|
|
274
|
+
type: VersioningType.Custom;
|
|
275
|
+
extractor: (requestOptions: unknown) => {
|
|
276
|
+
url: string;
|
|
277
|
+
headers: Record<string, any>;
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export type VersioningOption =
|
|
282
|
+
| VersioningUriOption
|
|
283
|
+
| VersioningHeaderOption
|
|
284
|
+
| VersioningQueryOption
|
|
285
|
+
| VersioningCustomOption;
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### 临时版本修改器`@Version`
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
@Api("test")
|
|
292
|
+
class Test {
|
|
293
|
+
@Get("HelloWorld")
|
|
294
|
+
@Version("0.2.0")
|
|
295
|
+
test() {}
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
> 临时改变 api 版本,便于测试
|
|
300
|
+
|
|
301
|
+
### 缓存装饰器`@Cache`
|
|
302
|
+
- 待测试,测试后发布文档
|
|
139
303
|
|
|
140
304
|
### 代码仓库
|
|
305
|
+
|
|
141
306
|
- 
|
|
142
307
|
- 
|
|
143
308
|
|
|
144
|
-
|
|
145
309
|
### 作者
|
|
146
|
-
|
|
310
|
+
|
|
311
|
+
- mc.lee
|
package/dist/cache/index.d.ts
CHANGED
|
@@ -2,4 +2,4 @@ import MemoryCache from "./memoryCache";
|
|
|
2
2
|
import LocalStorageCache from "./localstorageCache";
|
|
3
3
|
import IndexDBCache from "./indexDBCache";
|
|
4
4
|
import { CacheType } from "../typings";
|
|
5
|
-
export declare function createCache(type: CacheType, ttl: number):
|
|
5
|
+
export declare function createCache(type: CacheType, ttl: number): LocalStorageCache | IndexDBCache | MemoryCache | undefined;
|
package/dist/core/index.d.ts
CHANGED
package/dist/core/snail.d.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
constructor(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
1
|
+
import "reflect-metadata";
|
|
2
|
+
import { Strategy, ApiProxy, ResponseData } from "../typings";
|
|
3
|
+
export declare class Snail<R extends {
|
|
4
|
+
data: any;
|
|
5
|
+
} = ResponseData> {
|
|
6
|
+
private axiosInstance;
|
|
7
|
+
private strategies;
|
|
8
|
+
private cacheStorage?;
|
|
9
|
+
private version?;
|
|
10
|
+
registerStrategy(strategy: Strategy): void;
|
|
11
|
+
createApi<T extends object>(constructor: new () => T): ApiProxy<T, R>;
|
|
12
|
+
private buildRequestArgs;
|
|
13
|
+
private getStrategies;
|
|
14
|
+
private applyStrategies;
|
|
15
|
+
private applyVersion;
|
|
16
|
+
private applyCache;
|
|
17
|
+
private getCacheConfig;
|
|
18
|
+
private handleResponse;
|
|
19
|
+
private handleError;
|
|
19
20
|
}
|
|
20
|
-
export declare const createSnail: (option: SnailConfig) => Snail;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
2
|
+
import { ApiConfig } from "../typings";
|
|
3
|
+
export declare const METHOD_KEY = "SNAIL_METHOD_KEY";
|
|
4
|
+
export declare const API_CONFIG_KEY = "SNAIL_API_CONFIG_KEY";
|
|
5
|
+
export declare const Api: (url?: string, config?: ApiConfig) => ClassDecorator;
|
|
6
|
+
export declare const Get: (path?: string) => (target: any, propertyKey: string | symbol) => void;
|
|
7
|
+
export declare const Post: (path?: string) => (target: any, propertyKey: string | symbol) => void;
|
|
8
|
+
export declare const Put: (path?: string) => (target: any, propertyKey: string | symbol) => void;
|
|
9
|
+
export declare const Delete: (path?: string) => (target: any, propertyKey: string | symbol) => void;
|
|
10
|
+
export declare const Patch: (path?: string) => (target: any, propertyKey: string | symbol) => void;
|
|
11
|
+
export declare const Options: (path?: string) => (target: any, propertyKey: string | symbol) => void;
|
|
12
|
+
export declare const Head: (path?: string) => (target: any, propertyKey: string | symbol) => void;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
2
|
+
export declare const REQUEST_ARGS_KEY: unique symbol;
|
|
3
|
+
export declare const Params: (key?: string) => (target: any, propertyKey: string | symbol, parameterIndex: number) => void;
|
|
4
|
+
export declare const Data: () => (target: any, propertyKey: string | symbol, parameterIndex: number) => void;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
2
|
+
import { VersioningOption } from "../typings";
|
|
3
|
+
export declare const VERSIONING_KEY: unique symbol;
|
|
4
|
+
export declare const VERSION_KEY: unique symbol;
|
|
5
|
+
export declare const Versioning: (options: VersioningOption) => (target: any) => void;
|
|
6
|
+
export declare const Version: (version: string) => (target: any, propertyKey: string) => void;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
export * from "./core";
|
|
2
|
+
export * from "./decorators/server";
|
|
3
|
+
export * from "./decorators/api";
|
|
4
|
+
export * from "./decorators/cache";
|
|
5
|
+
export * from "./decorators/param";
|
|
6
|
+
export * from "./decorators/strategy";
|
|
7
|
+
export * from "./decorators/versioning";
|
|
2
8
|
export * from "./typings";
|
|
3
9
|
export * from "./utils";
|