restix 0.0.2 → 0.0.3
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 +137 -59
- package/api.d.ts +4 -0
- package/api.js +47 -10
- package/api.ts +63 -9
- package/package.json +1 -1
- package/status.d.ts +128 -0
- package/status.js +131 -0
- package/status.ts +151 -0
- package/test.test.js +11 -15
- package/test.test.ts +14 -17
- package/types.d.ts +3 -3
- package/types.ts +3 -3
package/README.md
CHANGED
|
@@ -1,61 +1,68 @@
|
|
|
1
1
|
# restix
|
|
2
2
|
|
|
3
3
|
**restix** is a strict, type-safe HTTP client builder designed for **CLI tools and backend applications**.
|
|
4
|
-
It lets you define your API **once** as a contract (routes + mappings) and then make requests without rewriting fetch logic, URL building, or manual payload handling.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
It allows you to define your API **once** as a contract and interact with it safely without rewriting fetch logic, URL builders, or request payload handling.
|
|
7
6
|
|
|
8
|
-
>
|
|
7
|
+
> Design your API → map it → call it safely
|
|
9
8
|
|
|
10
|
-
No runtime magic
|
|
9
|
+
No runtime magic.
|
|
10
|
+
No hidden abstractions.
|
|
11
|
+
No guessing.
|
|
11
12
|
|
|
12
13
|
---
|
|
13
14
|
|
|
14
15
|
## Why restix?
|
|
15
16
|
|
|
16
17
|
When building CLI tools or backend services, you often need to:
|
|
18
|
+
|
|
17
19
|
- Call many HTTP endpoints
|
|
18
|
-
- Inject params, query strings, headers, and body data
|
|
20
|
+
- Inject URL params, query strings, headers, and body data
|
|
19
21
|
- Keep everything type-safe
|
|
20
|
-
- Avoid
|
|
22
|
+
- Avoid request boilerplate
|
|
21
23
|
|
|
22
24
|
**restix solves this by:**
|
|
25
|
+
|
|
23
26
|
- Enforcing **exact input types** per endpoint
|
|
24
27
|
- Preventing extra or missing fields at compile time
|
|
25
|
-
-
|
|
26
|
-
-
|
|
28
|
+
- Supporting multiple injection strategies
|
|
29
|
+
- Centralizing request logic
|
|
30
|
+
- Staying minimal and predictable
|
|
27
31
|
|
|
28
32
|
---
|
|
29
33
|
|
|
30
34
|
## Core Concepts
|
|
31
35
|
|
|
32
|
-
### 1. API
|
|
36
|
+
### 1. API Contract (Type First)
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
Each key represents
|
|
38
|
+
Your API is defined as a TypeScript interface.
|
|
39
|
+
Each key represents an endpoint, and its value represents the **exact allowed input**.
|
|
36
40
|
|
|
37
41
|
```ts
|
|
38
42
|
interface API_INTERFACE {
|
|
39
|
-
'GET /
|
|
40
|
-
|
|
43
|
+
'GET /packages/cli/name/{name}': {
|
|
44
|
+
name: string
|
|
41
45
|
}
|
|
42
46
|
|
|
43
|
-
'POST /
|
|
47
|
+
'POST /packages/cli/create': {
|
|
44
48
|
name: string
|
|
45
|
-
|
|
49
|
+
version: string
|
|
50
|
+
developer: string
|
|
46
51
|
}
|
|
47
52
|
}
|
|
48
53
|
````
|
|
49
54
|
|
|
50
|
-
This interface
|
|
55
|
+
This interface is the **single source of truth**.
|
|
51
56
|
|
|
52
57
|
---
|
|
53
58
|
|
|
54
59
|
### 2. API Mapping
|
|
55
60
|
|
|
56
|
-
Each endpoint must
|
|
61
|
+
Each endpoint must declare how its data is injected.
|
|
62
|
+
|
|
63
|
+
Supported injection targets:
|
|
57
64
|
|
|
58
|
-
* `params` → URL
|
|
65
|
+
* `params` → URL parameters
|
|
59
66
|
* `query` → query string
|
|
60
67
|
* `body` → request body
|
|
61
68
|
* `headers` → request headers
|
|
@@ -64,19 +71,15 @@ Each endpoint must be mapped to explain **where data goes**:
|
|
|
64
71
|
import { apiMap, type API_MAPS, type GET_API } from 'restix'
|
|
65
72
|
|
|
66
73
|
const api_maps: API_MAPS<API_INTERFACE> = {
|
|
67
|
-
'GET /
|
|
68
|
-
map: apiMap<GET_API<API_INTERFACE, 'GET /
|
|
69
|
-
params: ['
|
|
74
|
+
'GET /packages/cli/name/{name}': {
|
|
75
|
+
map: apiMap<GET_API<API_INTERFACE, 'GET /packages/cli/name/{name}'>>({
|
|
76
|
+
params: ['name']
|
|
70
77
|
}),
|
|
71
|
-
res: 'json'
|
|
72
|
-
status: {
|
|
73
|
-
safe: 200,
|
|
74
|
-
404: () => 'User not found'
|
|
75
|
-
}
|
|
78
|
+
res: 'json'
|
|
76
79
|
},
|
|
77
80
|
|
|
78
|
-
'POST /
|
|
79
|
-
map: apiMap<GET_API<API_INTERFACE, 'POST /
|
|
81
|
+
'POST /packages/cli/create': {
|
|
82
|
+
map: apiMap<GET_API<API_INTERFACE, 'POST /packages/cli/create'>>({
|
|
80
83
|
body: ['*']
|
|
81
84
|
}),
|
|
82
85
|
res: 'json',
|
|
@@ -87,75 +90,150 @@ const api_maps: API_MAPS<API_INTERFACE> = {
|
|
|
87
90
|
}
|
|
88
91
|
```
|
|
89
92
|
|
|
90
|
-
|
|
91
|
-
|
|
93
|
+
`'*'` means **all fields**
|
|
94
|
+
Explicit keys mean **only those fields**
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
### 3. Query Injection Strategies
|
|
99
|
+
|
|
100
|
+
#### A) Automatic Query Injection
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
map: apiMap({
|
|
104
|
+
query: ['q', 'v']
|
|
105
|
+
})
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
api.request('GET /search', {
|
|
110
|
+
q: 'nex',
|
|
111
|
+
v: '1'
|
|
112
|
+
})
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
➡️ `?q=nex&v=1`
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
#### B) URL-Positioned Query Injection
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
'GET /packages/cli/search?selam={v}&VERSION={v}'
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
api.request(
|
|
127
|
+
'GET /packages/cli/search?selam={v}&VERSION={v}',
|
|
128
|
+
{
|
|
129
|
+
q: 'nex',
|
|
130
|
+
v: 'sa'
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
➡️ `?selam=sa&VERSION=sa`
|
|
136
|
+
|
|
137
|
+
This is especially useful for:
|
|
138
|
+
|
|
139
|
+
* Legacy APIs
|
|
140
|
+
* Fixed query naming conventions
|
|
141
|
+
* Enterprise-style endpoints
|
|
92
142
|
|
|
93
143
|
---
|
|
94
144
|
|
|
95
|
-
###
|
|
145
|
+
### 4. Creating the Client
|
|
96
146
|
|
|
97
147
|
```ts
|
|
98
148
|
import { NexAPI } from 'restix'
|
|
99
149
|
|
|
100
150
|
const api = new NexAPI<API_INTERFACE>(
|
|
101
|
-
'
|
|
151
|
+
'http://localhost:5000',
|
|
102
152
|
api_maps
|
|
103
153
|
)
|
|
104
154
|
```
|
|
105
155
|
|
|
106
|
-
|
|
156
|
+
#### With Defaults
|
|
107
157
|
|
|
108
158
|
```ts
|
|
109
159
|
const api = new NexAPI<API_INTERFACE>(
|
|
110
|
-
'
|
|
160
|
+
'http://localhost:5000',
|
|
111
161
|
api_maps,
|
|
112
162
|
{
|
|
113
163
|
headers: {
|
|
114
|
-
|
|
164
|
+
'User-Agent': 'restix-cli'
|
|
165
|
+
},
|
|
166
|
+
status: {
|
|
167
|
+
404: () => 'Global not found'
|
|
115
168
|
}
|
|
116
169
|
}
|
|
117
170
|
)
|
|
118
171
|
```
|
|
119
172
|
|
|
173
|
+
Defaults are **merged**, not replaced.
|
|
174
|
+
|
|
120
175
|
---
|
|
121
176
|
|
|
122
|
-
###
|
|
177
|
+
### 5. Making Requests (Type-Safe)
|
|
123
178
|
|
|
124
179
|
```ts
|
|
125
|
-
await api.request('GET /
|
|
126
|
-
|
|
180
|
+
await api.request('GET /packages/cli/name/{name}', {
|
|
181
|
+
name: 'xnex'
|
|
127
182
|
})
|
|
128
183
|
```
|
|
129
184
|
|
|
130
185
|
```ts
|
|
131
|
-
await api.request('POST /
|
|
132
|
-
name: '
|
|
133
|
-
|
|
186
|
+
await api.request('POST /packages/cli/create', {
|
|
187
|
+
name: 'xnex',
|
|
188
|
+
version: '0.0.1',
|
|
189
|
+
developer: 'Signor P'
|
|
134
190
|
})
|
|
135
191
|
```
|
|
136
192
|
|
|
137
|
-
❌
|
|
193
|
+
❌ Compile-time error (extra field):
|
|
138
194
|
|
|
139
195
|
```ts
|
|
140
|
-
api.request('
|
|
141
|
-
name: '
|
|
142
|
-
|
|
143
|
-
extra: 'not allowed'
|
|
196
|
+
api.request('GET /packages/cli/name/{name}', {
|
|
197
|
+
name: 'xnex',
|
|
198
|
+
extra: 'nope'
|
|
144
199
|
})
|
|
145
200
|
```
|
|
146
201
|
|
|
147
|
-
|
|
202
|
+
❌ Compile-time error (missing field):
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
api.request('POST /packages/cli/create', {
|
|
206
|
+
name: 'xnex'
|
|
207
|
+
})
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Status Handling
|
|
213
|
+
|
|
214
|
+
Each endpoint can define status handlers:
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
status: {
|
|
218
|
+
safe: 200,
|
|
219
|
+
404: () => 'Not found',
|
|
220
|
+
400: () => 'Bad request'
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
You can also define **global status defaults** when creating the client.
|
|
148
225
|
|
|
149
226
|
---
|
|
150
227
|
|
|
151
228
|
## Key Features
|
|
152
229
|
|
|
153
|
-
* ✅ Exact object
|
|
230
|
+
* ✅ Exact object typing (no extra keys)
|
|
154
231
|
* ✅ Route-based type inference
|
|
155
|
-
* ✅
|
|
156
|
-
* ✅ Automatic query
|
|
157
|
-
* ✅
|
|
158
|
-
* ✅
|
|
232
|
+
* ✅ URL param injection
|
|
233
|
+
* ✅ Automatic & positional query injection
|
|
234
|
+
* ✅ Header & body extraction
|
|
235
|
+
* ✅ Global + local status handling
|
|
236
|
+
* ✅ CLI-friendly (Bun / Node)
|
|
159
237
|
* ✅ No runtime dependencies
|
|
160
238
|
* ✅ No decorators, no reflection
|
|
161
239
|
|
|
@@ -165,12 +243,12 @@ Because **restix enforces exact types**.
|
|
|
165
243
|
|
|
166
244
|
* CLI tools
|
|
167
245
|
* Backend services
|
|
168
|
-
* Internal tooling
|
|
169
246
|
* Automation scripts
|
|
170
|
-
*
|
|
171
|
-
*
|
|
247
|
+
* Internal SDKs
|
|
248
|
+
* API clients
|
|
249
|
+
* Monorepo shared tooling
|
|
172
250
|
|
|
173
|
-
|
|
251
|
+
Not designed for heavy frontend frameworks.
|
|
174
252
|
|
|
175
253
|
---
|
|
176
254
|
|
|
@@ -181,7 +259,7 @@ restix follows a **contract-first** approach:
|
|
|
181
259
|
* Types are the contract
|
|
182
260
|
* Mapping defines behavior
|
|
183
261
|
* Requests are deterministic
|
|
184
|
-
* Errors are caught at compile time
|
|
262
|
+
* Errors are caught at compile time
|
|
185
263
|
|
|
186
264
|
If TypeScript allows it, restix allows it.
|
|
187
265
|
If TypeScript rejects it, restix rejects it too.
|
|
@@ -190,10 +268,10 @@ If TypeScript rejects it, restix rejects it too.
|
|
|
190
268
|
|
|
191
269
|
## Status
|
|
192
270
|
|
|
193
|
-
This
|
|
271
|
+
This project is actively evolving.
|
|
194
272
|
The API is intentionally minimal and strict.
|
|
195
273
|
|
|
196
|
-
|
|
274
|
+
Ideas, issues, and contributions are welcome.
|
|
197
275
|
|
|
198
276
|
---
|
|
199
277
|
|
package/api.d.ts
CHANGED
|
@@ -8,6 +8,10 @@ export declare class NexAPI<API_INTERFACE extends {
|
|
|
8
8
|
params?: Record<string, string>;
|
|
9
9
|
query?: Record<string, string>;
|
|
10
10
|
headers?: Record<string, string>;
|
|
11
|
+
status?: {
|
|
12
|
+
safe?: number;
|
|
13
|
+
[n: number]: (res: Response) => any;
|
|
14
|
+
};
|
|
11
15
|
});
|
|
12
16
|
request<URL extends (keyof API_INTERFACE) & string>(prefix: URL, data: Exact<API_INTERFACE[URL], API_INTERFACE[URL]>): Promise<any>;
|
|
13
17
|
}
|
package/api.js
CHANGED
|
@@ -1,16 +1,42 @@
|
|
|
1
|
+
import { Status } from './status';
|
|
1
2
|
export class NexAPI {
|
|
2
3
|
#base = 'http://localhost:5000';
|
|
3
4
|
#api_maps;
|
|
4
|
-
#defaults = {
|
|
5
|
+
#defaults = {
|
|
6
|
+
headers: { 'X-Client-Signature': 'restix/0.0.2 (by SignorP)' },
|
|
7
|
+
status: {
|
|
8
|
+
safe: Status.OK(undefined).status,
|
|
9
|
+
201: Status.CREATED,
|
|
10
|
+
202: Status.ACCEPTED,
|
|
11
|
+
204: Status.NO_CONTENT,
|
|
12
|
+
400: () => Status.BAD_REQUEST('Invalid request data', 'Check required parameters'),
|
|
13
|
+
401: () => Status.UNAUTHORIZED('Authentication required'),
|
|
14
|
+
403: () => Status.FORBIDDEN(),
|
|
15
|
+
404: () => Status.NOT_FOUND(),
|
|
16
|
+
405: () => Status.METHOD_NOT_ALLOWED(),
|
|
17
|
+
408: () => Status.REQUEST_TIMEOUT(),
|
|
18
|
+
409: () => Status.CONFLICT(),
|
|
19
|
+
413: () => Status.PAYLOAD_TOO_LARGE(),
|
|
20
|
+
415: () => Status.UNSUPPORTED_MEDIA_TYPE(),
|
|
21
|
+
422: () => Status.UNPROCESSABLE_ENTITY('Validation failed'),
|
|
22
|
+
429: () => Status.TOO_MANY_REQUESTS('Rate limit exceeded', 'Try again later'),
|
|
23
|
+
500: () => Status.INTERNAL_ERROR(),
|
|
24
|
+
501: () => Status.NOT_IMPLEMENTED(),
|
|
25
|
+
502: () => Status.BAD_GATEWAY(),
|
|
26
|
+
503: () => Status.SERVICE_UNAVAILABLE(),
|
|
27
|
+
504: () => Status.GATEWAY_TIMEOUT()
|
|
28
|
+
}
|
|
29
|
+
};
|
|
5
30
|
constructor(base, api_maps, defaults) {
|
|
6
31
|
this.#base = base;
|
|
7
32
|
this.#api_maps = api_maps;
|
|
8
|
-
this.#defaults = { ...defaults, headers: { ...defaults?.headers ?? {}, ...this.#defaults.headers } };
|
|
33
|
+
this.#defaults = { ...defaults, headers: { ...defaults?.headers ?? {}, ...this.#defaults.headers }, status: { ...this.#defaults.status, ...defaults?.status ?? {} } };
|
|
9
34
|
}
|
|
10
35
|
async request(prefix, data) {
|
|
11
36
|
const api = this.#api_maps[prefix];
|
|
12
37
|
if (!api)
|
|
13
38
|
return;
|
|
39
|
+
api.status = { ...this.#defaults.status, ...(api.status ?? {}) };
|
|
14
40
|
data = {
|
|
15
41
|
...data,
|
|
16
42
|
...this.#defaults.body ?? {},
|
|
@@ -32,10 +58,12 @@ export class NexAPI {
|
|
|
32
58
|
url = this.#injectUrlData(url, api.map.params, data);
|
|
33
59
|
if (api.map.query.length > 0)
|
|
34
60
|
url = this.#injectQuery(url, api.map.query, data);
|
|
35
|
-
if (api.map.body.length > 0)
|
|
61
|
+
if (api.map.body.length > 0) {
|
|
36
62
|
fetchOptoin.body = JSON.stringify(this.#injectData(api.map.body, data));
|
|
63
|
+
fetchOptoin.headers = { 'Content-type': 'application/json' };
|
|
64
|
+
}
|
|
37
65
|
if (api.map.headers.length > 0)
|
|
38
|
-
fetchOptoin.headers = this.#injectData(api.map.headers, data);
|
|
66
|
+
fetchOptoin.headers = { ...this.#injectData(api.map.headers, data), ...(fetchOptoin.headers ?? {}) };
|
|
39
67
|
const res = await fetch(url, fetchOptoin);
|
|
40
68
|
if (res.status === api.status.safe)
|
|
41
69
|
return await res[api.res]();
|
|
@@ -49,13 +77,22 @@ export class NexAPI {
|
|
|
49
77
|
return url;
|
|
50
78
|
}
|
|
51
79
|
#injectQuery(url, queries, data) {
|
|
52
|
-
for (const
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
80
|
+
for (const query of queries[0] == '*' ? Object.keys(data) : queries) {
|
|
81
|
+
if (!data[query])
|
|
82
|
+
continue;
|
|
83
|
+
const key = `={${query}}`;
|
|
84
|
+
if (url.includes(key)) {
|
|
85
|
+
url = url.replaceAll(key, `=${data[query]}`);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (!url.includes('?'))
|
|
89
|
+
url = `${url}?${query}=${data[query]}&`;
|
|
90
|
+
else if (!url.endsWith('&'))
|
|
91
|
+
url = `${url}&${query}=${data[query]}&`;
|
|
92
|
+
else if (url.endsWith('&'))
|
|
93
|
+
url = `${url}${query}=${data[query]}&`;
|
|
57
94
|
}
|
|
58
|
-
return url;
|
|
95
|
+
return url.endsWith('&') ? url.slice(0, url.length - 1) : url;
|
|
59
96
|
}
|
|
60
97
|
#injectData(body, data) {
|
|
61
98
|
const res = {};
|
package/api.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Status } from './status';
|
|
2
|
+
import type { API_MAPS, Exact } from './types';
|
|
2
3
|
|
|
3
4
|
export class NexAPI<API_INTERFACE extends { [k: string]: any }> {
|
|
4
5
|
#base = 'http://localhost:5000'
|
|
@@ -8,22 +9,65 @@ export class NexAPI<API_INTERFACE extends { [k: string]: any }> {
|
|
|
8
9
|
params?: Record<string, string>
|
|
9
10
|
query?: Record<string, string>
|
|
10
11
|
headers?: Record<string, string>
|
|
11
|
-
|
|
12
|
+
status: {
|
|
13
|
+
safe: number,
|
|
14
|
+
[n: number]: (res: Response) => any
|
|
15
|
+
}
|
|
16
|
+
} = {
|
|
17
|
+
headers: { 'X-Client-Signature': 'restix/0.0.2 (by SignorP)' },
|
|
18
|
+
status: {
|
|
19
|
+
safe: Status.OK(undefined).status,
|
|
20
|
+
201: Status.CREATED,
|
|
21
|
+
202: Status.ACCEPTED,
|
|
22
|
+
204: Status.NO_CONTENT,
|
|
23
|
+
400: () => Status.BAD_REQUEST(
|
|
24
|
+
'Invalid request data',
|
|
25
|
+
'Check required parameters'
|
|
26
|
+
),
|
|
27
|
+
401: () => Status.UNAUTHORIZED(
|
|
28
|
+
'Authentication required'
|
|
29
|
+
),
|
|
30
|
+
403: () => Status.FORBIDDEN(),
|
|
31
|
+
404: () => Status.NOT_FOUND(),
|
|
32
|
+
405: () => Status.METHOD_NOT_ALLOWED(),
|
|
33
|
+
408: () => Status.REQUEST_TIMEOUT(),
|
|
34
|
+
409: () => Status.CONFLICT(),
|
|
35
|
+
413: () => Status.PAYLOAD_TOO_LARGE(),
|
|
36
|
+
415: () => Status.UNSUPPORTED_MEDIA_TYPE(),
|
|
37
|
+
422: () => Status.UNPROCESSABLE_ENTITY(
|
|
38
|
+
'Validation failed'
|
|
39
|
+
),
|
|
40
|
+
429: () => Status.TOO_MANY_REQUESTS(
|
|
41
|
+
'Rate limit exceeded',
|
|
42
|
+
'Try again later'
|
|
43
|
+
),
|
|
44
|
+
500: () => Status.INTERNAL_ERROR(),
|
|
45
|
+
501: () => Status.NOT_IMPLEMENTED(),
|
|
46
|
+
502: () => Status.BAD_GATEWAY(),
|
|
47
|
+
503: () => Status.SERVICE_UNAVAILABLE(),
|
|
48
|
+
504: () => Status.GATEWAY_TIMEOUT()
|
|
49
|
+
}
|
|
50
|
+
}
|
|
12
51
|
|
|
13
52
|
constructor(base: string, api_maps: API_MAPS<API_INTERFACE>, defaults?: {
|
|
14
53
|
body?: Record<string, string>
|
|
15
54
|
params?: Record<string, string>
|
|
16
55
|
query?: Record<string, string>
|
|
17
56
|
headers?: Record<string, string>
|
|
57
|
+
status?: {
|
|
58
|
+
safe?: number,
|
|
59
|
+
[n: number]: (res: Response) => any
|
|
60
|
+
}
|
|
18
61
|
}) {
|
|
19
62
|
this.#base = base
|
|
20
63
|
this.#api_maps = api_maps
|
|
21
|
-
this.#defaults = { ...defaults, headers: { ...defaults?.headers ?? {}, ...this.#defaults.headers } }
|
|
64
|
+
this.#defaults = { ...defaults, headers: { ...defaults?.headers ?? {}, ...this.#defaults.headers }, status: { ...this.#defaults.status, ...defaults?.status ?? {} } }
|
|
22
65
|
}
|
|
23
66
|
|
|
24
67
|
async request<URL extends (keyof API_INTERFACE) & string>(prefix: URL, data: Exact<API_INTERFACE[URL], API_INTERFACE[URL]>) {
|
|
25
68
|
const api = this.#api_maps[prefix]
|
|
26
69
|
if (!api) return
|
|
70
|
+
api.status = { ...this.#defaults.status, ...(api.status ?? {}) }
|
|
27
71
|
data = {
|
|
28
72
|
...data,
|
|
29
73
|
...this.#defaults.body ?? {},
|
|
@@ -43,8 +87,11 @@ export class NexAPI<API_INTERFACE extends { [k: string]: any }> {
|
|
|
43
87
|
url = `${this.#base}${url}`
|
|
44
88
|
if (api.map.params.length > 0) url = this.#injectUrlData(url, api.map.params, data)
|
|
45
89
|
if (api.map.query.length > 0) url = this.#injectQuery(url, api.map.query, data)
|
|
46
|
-
if (api.map.body.length > 0)
|
|
47
|
-
|
|
90
|
+
if (api.map.body.length > 0) {
|
|
91
|
+
fetchOptoin.body = JSON.stringify(this.#injectData(api.map.body, data))
|
|
92
|
+
fetchOptoin.headers = { 'Content-type': 'application/json' }
|
|
93
|
+
}
|
|
94
|
+
if (api.map.headers.length > 0) fetchOptoin.headers = { ...this.#injectData(api.map.headers, data), ...(fetchOptoin.headers ?? {}) }
|
|
48
95
|
const res = await fetch(url, fetchOptoin)
|
|
49
96
|
if (res.status === api.status.safe) return await res[api.res]()
|
|
50
97
|
else if (api.status && api.status[res.status]) return await api.status[res.status]?.(res)
|
|
@@ -57,11 +104,18 @@ export class NexAPI<API_INTERFACE extends { [k: string]: any }> {
|
|
|
57
104
|
}
|
|
58
105
|
|
|
59
106
|
#injectQuery(url: string, queries: string[], data: Record<string, string>) {
|
|
60
|
-
for (const
|
|
61
|
-
if (
|
|
62
|
-
|
|
107
|
+
for (const query of queries[0] == '*' ? Object.keys(data) : queries) {
|
|
108
|
+
if (!data[query]) continue
|
|
109
|
+
const key = `={${query}}`
|
|
110
|
+
if (url.includes(key)) {
|
|
111
|
+
url = url.replaceAll(key, `=${data[query]}`)
|
|
112
|
+
continue
|
|
113
|
+
}
|
|
114
|
+
if (!url.includes('?')) url = `${url}?${query}=${data[query]}&`
|
|
115
|
+
else if (!url.endsWith('&')) url = `${url}&${query}=${data[query]}&`
|
|
116
|
+
else if (url.endsWith('&')) url = `${url}${query}=${data[query]}&`
|
|
63
117
|
}
|
|
64
|
-
return url
|
|
118
|
+
return url.endsWith('&') ? url.slice(0, url.length - 1) : url
|
|
65
119
|
}
|
|
66
120
|
|
|
67
121
|
#injectData(body: string[], data: Record<string, any>) {
|
package/package.json
CHANGED
package/status.d.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
export declare const Status: {
|
|
2
|
+
readonly OK: <T>(data: T) => {
|
|
3
|
+
ok: boolean;
|
|
4
|
+
status: number;
|
|
5
|
+
data: T;
|
|
6
|
+
};
|
|
7
|
+
readonly CREATED: <T>(data: T) => {
|
|
8
|
+
ok: boolean;
|
|
9
|
+
status: number;
|
|
10
|
+
data: T;
|
|
11
|
+
};
|
|
12
|
+
readonly ACCEPTED: <T>(data: T) => {
|
|
13
|
+
ok: boolean;
|
|
14
|
+
status: number;
|
|
15
|
+
data: T;
|
|
16
|
+
};
|
|
17
|
+
readonly NO_CONTENT: () => {
|
|
18
|
+
ok: boolean;
|
|
19
|
+
status: number;
|
|
20
|
+
data: null;
|
|
21
|
+
};
|
|
22
|
+
readonly BAD_REQUEST: (message?: string, hint?: string) => {
|
|
23
|
+
ok: boolean;
|
|
24
|
+
status: number;
|
|
25
|
+
code: string;
|
|
26
|
+
message: string;
|
|
27
|
+
hint: string | undefined;
|
|
28
|
+
};
|
|
29
|
+
readonly UNAUTHORIZED: (message?: string, hint?: string) => {
|
|
30
|
+
ok: boolean;
|
|
31
|
+
status: number;
|
|
32
|
+
code: string;
|
|
33
|
+
message: string;
|
|
34
|
+
hint: string | undefined;
|
|
35
|
+
};
|
|
36
|
+
readonly FORBIDDEN: (message?: string) => {
|
|
37
|
+
ok: boolean;
|
|
38
|
+
status: number;
|
|
39
|
+
code: string;
|
|
40
|
+
message: string;
|
|
41
|
+
};
|
|
42
|
+
readonly NOT_FOUND: (message?: string) => {
|
|
43
|
+
ok: boolean;
|
|
44
|
+
status: number;
|
|
45
|
+
code: string;
|
|
46
|
+
message: string;
|
|
47
|
+
};
|
|
48
|
+
readonly METHOD_NOT_ALLOWED: (message?: string) => {
|
|
49
|
+
ok: boolean;
|
|
50
|
+
status: number;
|
|
51
|
+
code: string;
|
|
52
|
+
message: string;
|
|
53
|
+
};
|
|
54
|
+
readonly NOT_ACCEPTABLE: (message?: string) => {
|
|
55
|
+
ok: boolean;
|
|
56
|
+
status: number;
|
|
57
|
+
code: string;
|
|
58
|
+
message: string;
|
|
59
|
+
};
|
|
60
|
+
readonly REQUEST_TIMEOUT: (message?: string) => {
|
|
61
|
+
ok: boolean;
|
|
62
|
+
status: number;
|
|
63
|
+
code: string;
|
|
64
|
+
message: string;
|
|
65
|
+
};
|
|
66
|
+
readonly CONFLICT: (message?: string) => {
|
|
67
|
+
ok: boolean;
|
|
68
|
+
status: number;
|
|
69
|
+
code: string;
|
|
70
|
+
message: string;
|
|
71
|
+
};
|
|
72
|
+
readonly PAYLOAD_TOO_LARGE: (message?: string) => {
|
|
73
|
+
ok: boolean;
|
|
74
|
+
status: number;
|
|
75
|
+
code: string;
|
|
76
|
+
message: string;
|
|
77
|
+
};
|
|
78
|
+
readonly UNSUPPORTED_MEDIA_TYPE: (message?: string) => {
|
|
79
|
+
ok: boolean;
|
|
80
|
+
status: number;
|
|
81
|
+
code: string;
|
|
82
|
+
message: string;
|
|
83
|
+
};
|
|
84
|
+
readonly UNPROCESSABLE_ENTITY: (message?: string, hint?: string) => {
|
|
85
|
+
ok: boolean;
|
|
86
|
+
status: number;
|
|
87
|
+
code: string;
|
|
88
|
+
message: string;
|
|
89
|
+
hint: string | undefined;
|
|
90
|
+
};
|
|
91
|
+
readonly TOO_MANY_REQUESTS: (message?: string, hint?: string) => {
|
|
92
|
+
ok: boolean;
|
|
93
|
+
status: number;
|
|
94
|
+
code: string;
|
|
95
|
+
message: string;
|
|
96
|
+
hint: string | undefined;
|
|
97
|
+
};
|
|
98
|
+
readonly INTERNAL_ERROR: (message?: string) => {
|
|
99
|
+
ok: boolean;
|
|
100
|
+
status: number;
|
|
101
|
+
code: string;
|
|
102
|
+
message: string;
|
|
103
|
+
};
|
|
104
|
+
readonly NOT_IMPLEMENTED: (message?: string) => {
|
|
105
|
+
ok: boolean;
|
|
106
|
+
status: number;
|
|
107
|
+
code: string;
|
|
108
|
+
message: string;
|
|
109
|
+
};
|
|
110
|
+
readonly BAD_GATEWAY: (message?: string) => {
|
|
111
|
+
ok: boolean;
|
|
112
|
+
status: number;
|
|
113
|
+
code: string;
|
|
114
|
+
message: string;
|
|
115
|
+
};
|
|
116
|
+
readonly SERVICE_UNAVAILABLE: (message?: string) => {
|
|
117
|
+
ok: boolean;
|
|
118
|
+
status: number;
|
|
119
|
+
code: string;
|
|
120
|
+
message: string;
|
|
121
|
+
};
|
|
122
|
+
readonly GATEWAY_TIMEOUT: (message?: string) => {
|
|
123
|
+
ok: boolean;
|
|
124
|
+
status: number;
|
|
125
|
+
code: string;
|
|
126
|
+
message: string;
|
|
127
|
+
};
|
|
128
|
+
};
|
package/status.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
export const Status = {
|
|
2
|
+
// ✅ SUCCESS
|
|
3
|
+
OK: (data) => ({
|
|
4
|
+
ok: true,
|
|
5
|
+
status: 200,
|
|
6
|
+
data
|
|
7
|
+
}),
|
|
8
|
+
CREATED: (data) => ({
|
|
9
|
+
ok: true,
|
|
10
|
+
status: 201,
|
|
11
|
+
data
|
|
12
|
+
}),
|
|
13
|
+
ACCEPTED: (data) => ({
|
|
14
|
+
ok: true,
|
|
15
|
+
status: 202,
|
|
16
|
+
data
|
|
17
|
+
}),
|
|
18
|
+
NO_CONTENT: () => ({
|
|
19
|
+
ok: true,
|
|
20
|
+
status: 204,
|
|
21
|
+
data: null
|
|
22
|
+
}),
|
|
23
|
+
// ❌ CLIENT ERRORS
|
|
24
|
+
BAD_REQUEST: (message = 'Bad request', hint) => ({
|
|
25
|
+
ok: false,
|
|
26
|
+
status: 400,
|
|
27
|
+
code: 'BAD_REQUEST',
|
|
28
|
+
message,
|
|
29
|
+
hint
|
|
30
|
+
}),
|
|
31
|
+
UNAUTHORIZED: (message = 'Unauthorized', hint) => ({
|
|
32
|
+
ok: false,
|
|
33
|
+
status: 401,
|
|
34
|
+
code: 'UNAUTHORIZED',
|
|
35
|
+
message,
|
|
36
|
+
hint
|
|
37
|
+
}),
|
|
38
|
+
FORBIDDEN: (message = 'Forbidden') => ({
|
|
39
|
+
ok: false,
|
|
40
|
+
status: 403,
|
|
41
|
+
code: 'FORBIDDEN',
|
|
42
|
+
message
|
|
43
|
+
}),
|
|
44
|
+
NOT_FOUND: (message = 'Not found') => ({
|
|
45
|
+
ok: false,
|
|
46
|
+
status: 404,
|
|
47
|
+
code: 'NOT_FOUND',
|
|
48
|
+
message
|
|
49
|
+
}),
|
|
50
|
+
METHOD_NOT_ALLOWED: (message = 'Method not allowed') => ({
|
|
51
|
+
ok: false,
|
|
52
|
+
status: 405,
|
|
53
|
+
code: 'METHOD_NOT_ALLOWED',
|
|
54
|
+
message
|
|
55
|
+
}),
|
|
56
|
+
NOT_ACCEPTABLE: (message = 'Not acceptable') => ({
|
|
57
|
+
ok: false,
|
|
58
|
+
status: 406,
|
|
59
|
+
code: 'NOT_ACCEPTABLE',
|
|
60
|
+
message
|
|
61
|
+
}),
|
|
62
|
+
REQUEST_TIMEOUT: (message = 'Request timeout') => ({
|
|
63
|
+
ok: false,
|
|
64
|
+
status: 408,
|
|
65
|
+
code: 'REQUEST_TIMEOUT',
|
|
66
|
+
message
|
|
67
|
+
}),
|
|
68
|
+
CONFLICT: (message = 'Conflict') => ({
|
|
69
|
+
ok: false,
|
|
70
|
+
status: 409,
|
|
71
|
+
code: 'CONFLICT',
|
|
72
|
+
message
|
|
73
|
+
}),
|
|
74
|
+
PAYLOAD_TOO_LARGE: (message = 'Payload too large') => ({
|
|
75
|
+
ok: false,
|
|
76
|
+
status: 413,
|
|
77
|
+
code: 'PAYLOAD_TOO_LARGE',
|
|
78
|
+
message
|
|
79
|
+
}),
|
|
80
|
+
UNSUPPORTED_MEDIA_TYPE: (message = 'Unsupported media type') => ({
|
|
81
|
+
ok: false,
|
|
82
|
+
status: 415,
|
|
83
|
+
code: 'UNSUPPORTED_MEDIA_TYPE',
|
|
84
|
+
message
|
|
85
|
+
}),
|
|
86
|
+
UNPROCESSABLE_ENTITY: (message = 'Unprocessable entity', hint) => ({
|
|
87
|
+
ok: false,
|
|
88
|
+
status: 422,
|
|
89
|
+
code: 'UNPROCESSABLE_ENTITY',
|
|
90
|
+
message,
|
|
91
|
+
hint
|
|
92
|
+
}),
|
|
93
|
+
TOO_MANY_REQUESTS: (message = 'Too many requests', hint) => ({
|
|
94
|
+
ok: false,
|
|
95
|
+
status: 429,
|
|
96
|
+
code: 'TOO_MANY_REQUESTS',
|
|
97
|
+
message,
|
|
98
|
+
hint
|
|
99
|
+
}),
|
|
100
|
+
// 💥 SERVER ERRORS
|
|
101
|
+
INTERNAL_ERROR: (message = 'Internal server error') => ({
|
|
102
|
+
ok: false,
|
|
103
|
+
status: 500,
|
|
104
|
+
code: 'INTERNAL_ERROR',
|
|
105
|
+
message
|
|
106
|
+
}),
|
|
107
|
+
NOT_IMPLEMENTED: (message = 'Not implemented') => ({
|
|
108
|
+
ok: false,
|
|
109
|
+
status: 501,
|
|
110
|
+
code: 'NOT_IMPLEMENTED',
|
|
111
|
+
message
|
|
112
|
+
}),
|
|
113
|
+
BAD_GATEWAY: (message = 'Bad gateway') => ({
|
|
114
|
+
ok: false,
|
|
115
|
+
status: 502,
|
|
116
|
+
code: 'BAD_GATEWAY',
|
|
117
|
+
message
|
|
118
|
+
}),
|
|
119
|
+
SERVICE_UNAVAILABLE: (message = 'Service unavailable') => ({
|
|
120
|
+
ok: false,
|
|
121
|
+
status: 503,
|
|
122
|
+
code: 'SERVICE_UNAVAILABLE',
|
|
123
|
+
message
|
|
124
|
+
}),
|
|
125
|
+
GATEWAY_TIMEOUT: (message = 'Gateway timeout') => ({
|
|
126
|
+
ok: false,
|
|
127
|
+
status: 504,
|
|
128
|
+
code: 'GATEWAY_TIMEOUT',
|
|
129
|
+
message
|
|
130
|
+
})
|
|
131
|
+
};
|
package/status.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
export const Status = {
|
|
2
|
+
// ✅ SUCCESS
|
|
3
|
+
OK: <T>(data: T) => ({
|
|
4
|
+
ok: true,
|
|
5
|
+
status: 200,
|
|
6
|
+
data
|
|
7
|
+
}),
|
|
8
|
+
|
|
9
|
+
CREATED: <T>(data: T) => ({
|
|
10
|
+
ok: true,
|
|
11
|
+
status: 201,
|
|
12
|
+
data
|
|
13
|
+
}),
|
|
14
|
+
|
|
15
|
+
ACCEPTED: <T>(data: T) => ({
|
|
16
|
+
ok: true,
|
|
17
|
+
status: 202,
|
|
18
|
+
data
|
|
19
|
+
}),
|
|
20
|
+
|
|
21
|
+
NO_CONTENT: () => ({
|
|
22
|
+
ok: true,
|
|
23
|
+
status: 204,
|
|
24
|
+
data: null
|
|
25
|
+
}),
|
|
26
|
+
|
|
27
|
+
// ❌ CLIENT ERRORS
|
|
28
|
+
BAD_REQUEST: (message = 'Bad request', hint?: string) => ({
|
|
29
|
+
ok: false,
|
|
30
|
+
status: 400,
|
|
31
|
+
code: 'BAD_REQUEST',
|
|
32
|
+
message,
|
|
33
|
+
hint
|
|
34
|
+
}),
|
|
35
|
+
|
|
36
|
+
UNAUTHORIZED: (message = 'Unauthorized', hint?: string) => ({
|
|
37
|
+
ok: false,
|
|
38
|
+
status: 401,
|
|
39
|
+
code: 'UNAUTHORIZED',
|
|
40
|
+
message,
|
|
41
|
+
hint
|
|
42
|
+
}),
|
|
43
|
+
|
|
44
|
+
FORBIDDEN: (message = 'Forbidden') => ({
|
|
45
|
+
ok: false,
|
|
46
|
+
status: 403,
|
|
47
|
+
code: 'FORBIDDEN',
|
|
48
|
+
message
|
|
49
|
+
}),
|
|
50
|
+
|
|
51
|
+
NOT_FOUND: (message = 'Not found') => ({
|
|
52
|
+
ok: false,
|
|
53
|
+
status: 404,
|
|
54
|
+
code: 'NOT_FOUND',
|
|
55
|
+
message
|
|
56
|
+
}),
|
|
57
|
+
|
|
58
|
+
METHOD_NOT_ALLOWED: (message = 'Method not allowed') => ({
|
|
59
|
+
ok: false,
|
|
60
|
+
status: 405,
|
|
61
|
+
code: 'METHOD_NOT_ALLOWED',
|
|
62
|
+
message
|
|
63
|
+
}),
|
|
64
|
+
|
|
65
|
+
NOT_ACCEPTABLE: (message = 'Not acceptable') => ({
|
|
66
|
+
ok: false,
|
|
67
|
+
status: 406,
|
|
68
|
+
code: 'NOT_ACCEPTABLE',
|
|
69
|
+
message
|
|
70
|
+
}),
|
|
71
|
+
|
|
72
|
+
REQUEST_TIMEOUT: (message = 'Request timeout') => ({
|
|
73
|
+
ok: false,
|
|
74
|
+
status: 408,
|
|
75
|
+
code: 'REQUEST_TIMEOUT',
|
|
76
|
+
message
|
|
77
|
+
}),
|
|
78
|
+
|
|
79
|
+
CONFLICT: (message = 'Conflict') => ({
|
|
80
|
+
ok: false,
|
|
81
|
+
status: 409,
|
|
82
|
+
code: 'CONFLICT',
|
|
83
|
+
message
|
|
84
|
+
}),
|
|
85
|
+
|
|
86
|
+
PAYLOAD_TOO_LARGE: (message = 'Payload too large') => ({
|
|
87
|
+
ok: false,
|
|
88
|
+
status: 413,
|
|
89
|
+
code: 'PAYLOAD_TOO_LARGE',
|
|
90
|
+
message
|
|
91
|
+
}),
|
|
92
|
+
|
|
93
|
+
UNSUPPORTED_MEDIA_TYPE: (message = 'Unsupported media type') => ({
|
|
94
|
+
ok: false,
|
|
95
|
+
status: 415,
|
|
96
|
+
code: 'UNSUPPORTED_MEDIA_TYPE',
|
|
97
|
+
message
|
|
98
|
+
}),
|
|
99
|
+
|
|
100
|
+
UNPROCESSABLE_ENTITY: (message = 'Unprocessable entity', hint?: string) => ({
|
|
101
|
+
ok: false,
|
|
102
|
+
status: 422,
|
|
103
|
+
code: 'UNPROCESSABLE_ENTITY',
|
|
104
|
+
message,
|
|
105
|
+
hint
|
|
106
|
+
}),
|
|
107
|
+
|
|
108
|
+
TOO_MANY_REQUESTS: (message = 'Too many requests', hint?: string) => ({
|
|
109
|
+
ok: false,
|
|
110
|
+
status: 429,
|
|
111
|
+
code: 'TOO_MANY_REQUESTS',
|
|
112
|
+
message,
|
|
113
|
+
hint
|
|
114
|
+
}),
|
|
115
|
+
|
|
116
|
+
// 💥 SERVER ERRORS
|
|
117
|
+
INTERNAL_ERROR: (message = 'Internal server error') => ({
|
|
118
|
+
ok: false,
|
|
119
|
+
status: 500,
|
|
120
|
+
code: 'INTERNAL_ERROR',
|
|
121
|
+
message
|
|
122
|
+
}),
|
|
123
|
+
|
|
124
|
+
NOT_IMPLEMENTED: (message = 'Not implemented') => ({
|
|
125
|
+
ok: false,
|
|
126
|
+
status: 501,
|
|
127
|
+
code: 'NOT_IMPLEMENTED',
|
|
128
|
+
message
|
|
129
|
+
}),
|
|
130
|
+
|
|
131
|
+
BAD_GATEWAY: (message = 'Bad gateway') => ({
|
|
132
|
+
ok: false,
|
|
133
|
+
status: 502,
|
|
134
|
+
code: 'BAD_GATEWAY',
|
|
135
|
+
message
|
|
136
|
+
}),
|
|
137
|
+
|
|
138
|
+
SERVICE_UNAVAILABLE: (message = 'Service unavailable') => ({
|
|
139
|
+
ok: false,
|
|
140
|
+
status: 503,
|
|
141
|
+
code: 'SERVICE_UNAVAILABLE',
|
|
142
|
+
message
|
|
143
|
+
}),
|
|
144
|
+
|
|
145
|
+
GATEWAY_TIMEOUT: (message = 'Gateway timeout') => ({
|
|
146
|
+
ok: false,
|
|
147
|
+
status: 504,
|
|
148
|
+
code: 'GATEWAY_TIMEOUT',
|
|
149
|
+
message
|
|
150
|
+
})
|
|
151
|
+
} as const
|
package/test.test.js
CHANGED
|
@@ -9,11 +9,7 @@ const api_maps = {
|
|
|
9
9
|
map: apiMap({
|
|
10
10
|
params: ['name']
|
|
11
11
|
}),
|
|
12
|
-
res: 'json'
|
|
13
|
-
status: {
|
|
14
|
-
safe: 200,
|
|
15
|
-
404: () => 'PACKAGE NOT FOUND'
|
|
16
|
-
}
|
|
12
|
+
res: 'json'
|
|
17
13
|
},
|
|
18
14
|
// MULTIPLE PARAMS
|
|
19
15
|
'GET /packages/cli/name/{name}/{version}': {
|
|
@@ -22,7 +18,6 @@ const api_maps = {
|
|
|
22
18
|
}),
|
|
23
19
|
res: 'json',
|
|
24
20
|
status: {
|
|
25
|
-
safe: 200,
|
|
26
21
|
404: () => 'VERSION NOT FOUND'
|
|
27
22
|
}
|
|
28
23
|
},
|
|
@@ -38,15 +33,12 @@ const api_maps = {
|
|
|
38
33
|
}
|
|
39
34
|
},
|
|
40
35
|
// QUERY + HEADER INJECTION
|
|
41
|
-
'GET /packages/cli/search': {
|
|
36
|
+
'GET /packages/cli/search?selam={v}&VERSION={v}': {
|
|
42
37
|
map: apiMap({
|
|
43
|
-
query: ['q'],
|
|
38
|
+
query: ['q', 'v'],
|
|
44
39
|
headers: ['Authorization']
|
|
45
40
|
}),
|
|
46
|
-
res: 'json'
|
|
47
|
-
status: {
|
|
48
|
-
safe: 200
|
|
49
|
-
}
|
|
41
|
+
res: 'json'
|
|
50
42
|
}
|
|
51
43
|
};
|
|
52
44
|
/* -------------------------------------------------------
|
|
@@ -55,6 +47,9 @@ const api_maps = {
|
|
|
55
47
|
const api = new NexAPI('http://localhost:5000', api_maps, {
|
|
56
48
|
headers: {
|
|
57
49
|
'User-Agent': 'restix-cli'
|
|
50
|
+
},
|
|
51
|
+
status: {
|
|
52
|
+
404: () => '404 not found for request.'
|
|
58
53
|
}
|
|
59
54
|
});
|
|
60
55
|
/* -------------------------------------------------------
|
|
@@ -64,9 +59,9 @@ const api = new NexAPI('http://localhost:5000', api_maps, {
|
|
|
64
59
|
// SIMPLE PARAM REQUEST
|
|
65
60
|
// GET /packages/cli/name/xnex
|
|
66
61
|
// -----------------------------------------
|
|
67
|
-
await api.request('GET /packages/cli/name/{name}', {
|
|
62
|
+
console.log(await api.request('GET /packages/cli/name/{name}', {
|
|
68
63
|
name: 'xnex'
|
|
69
|
-
});
|
|
64
|
+
}));
|
|
70
65
|
// -----------------------------------------
|
|
71
66
|
// MULTI PARAM REQUEST
|
|
72
67
|
// GET /packages/cli/name/xnex/0.0.1
|
|
@@ -89,8 +84,9 @@ await api.request('POST /packages/cli/create', {
|
|
|
89
84
|
// GET /packages/cli/search?query=nex
|
|
90
85
|
// Header: token
|
|
91
86
|
// -----------------------------------------
|
|
92
|
-
await api.request('GET /packages/cli/search', {
|
|
87
|
+
await api.request('GET /packages/cli/search?selam={v}&VERSION={v}', {
|
|
93
88
|
q: 'nex',
|
|
89
|
+
v: 'sa',
|
|
94
90
|
Authorization: 'Bearer AUTH_TOKEN'
|
|
95
91
|
});
|
|
96
92
|
// -----------------------------------------
|
package/test.test.ts
CHANGED
|
@@ -22,8 +22,9 @@ interface API_INTERFACE {
|
|
|
22
22
|
developer: string
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
'GET /packages/cli/search': {
|
|
25
|
+
'GET /packages/cli/search?selam={v}&VERSION={v}': {
|
|
26
26
|
q: string
|
|
27
|
+
v?: string,
|
|
27
28
|
Authorization: string
|
|
28
29
|
}
|
|
29
30
|
}
|
|
@@ -39,11 +40,7 @@ const api_maps: API_MAPS<API_INTERFACE> = {
|
|
|
39
40
|
map: apiMap<GET_API<API_INTERFACE, 'GET /packages/cli/name/{name}'>>({
|
|
40
41
|
params: ['name']
|
|
41
42
|
}),
|
|
42
|
-
res: 'json'
|
|
43
|
-
status: {
|
|
44
|
-
safe: 200,
|
|
45
|
-
404: () => 'PACKAGE NOT FOUND'
|
|
46
|
-
}
|
|
43
|
+
res: 'json'
|
|
47
44
|
},
|
|
48
45
|
|
|
49
46
|
// MULTIPLE PARAMS
|
|
@@ -53,7 +50,6 @@ const api_maps: API_MAPS<API_INTERFACE> = {
|
|
|
53
50
|
}),
|
|
54
51
|
res: 'json',
|
|
55
52
|
status: {
|
|
56
|
-
safe: 200,
|
|
57
53
|
404: () => 'VERSION NOT FOUND'
|
|
58
54
|
}
|
|
59
55
|
},
|
|
@@ -71,15 +67,12 @@ const api_maps: API_MAPS<API_INTERFACE> = {
|
|
|
71
67
|
},
|
|
72
68
|
|
|
73
69
|
// QUERY + HEADER INJECTION
|
|
74
|
-
'GET /packages/cli/search': {
|
|
75
|
-
map: apiMap<GET_API<API_INTERFACE, 'GET /packages/cli/search'>>({
|
|
76
|
-
query: ['q'],
|
|
70
|
+
'GET /packages/cli/search?selam={v}&VERSION={v}': {
|
|
71
|
+
map: apiMap<GET_API<API_INTERFACE, 'GET /packages/cli/search?selam={v}&VERSION={v}'>>({
|
|
72
|
+
query: ['q', 'v'],
|
|
77
73
|
headers: ['Authorization']
|
|
78
74
|
}),
|
|
79
|
-
res: 'json'
|
|
80
|
-
status: {
|
|
81
|
-
safe: 200
|
|
82
|
-
}
|
|
75
|
+
res: 'json'
|
|
83
76
|
}
|
|
84
77
|
}
|
|
85
78
|
|
|
@@ -93,6 +86,9 @@ const api = new NexAPI<API_INTERFACE>(
|
|
|
93
86
|
{
|
|
94
87
|
headers: {
|
|
95
88
|
'User-Agent': 'restix-cli'
|
|
89
|
+
},
|
|
90
|
+
status: {
|
|
91
|
+
404: () => '404 not found for request.'
|
|
96
92
|
}
|
|
97
93
|
}
|
|
98
94
|
)
|
|
@@ -106,12 +102,12 @@ const api = new NexAPI<API_INTERFACE>(
|
|
|
106
102
|
// SIMPLE PARAM REQUEST
|
|
107
103
|
// GET /packages/cli/name/xnex
|
|
108
104
|
// -----------------------------------------
|
|
109
|
-
await api.request(
|
|
105
|
+
console.log(await api.request(
|
|
110
106
|
'GET /packages/cli/name/{name}',
|
|
111
107
|
{
|
|
112
108
|
name: 'xnex'
|
|
113
109
|
}
|
|
114
|
-
)
|
|
110
|
+
))
|
|
115
111
|
|
|
116
112
|
|
|
117
113
|
// -----------------------------------------
|
|
@@ -147,9 +143,10 @@ await api.request(
|
|
|
147
143
|
// Header: token
|
|
148
144
|
// -----------------------------------------
|
|
149
145
|
await api.request(
|
|
150
|
-
'GET /packages/cli/search',
|
|
146
|
+
'GET /packages/cli/search?selam={v}&VERSION={v}',
|
|
151
147
|
{
|
|
152
148
|
q: 'nex',
|
|
149
|
+
v: 'sa',
|
|
153
150
|
Authorization: 'Bearer AUTH_TOKEN'
|
|
154
151
|
}
|
|
155
152
|
)
|
package/types.d.ts
CHANGED
|
@@ -8,9 +8,9 @@ export type Exact<T, Shape extends T> = T & Record<Exclude<keyof Shape, keyof T>
|
|
|
8
8
|
export interface API_MAP_TYPE<K extends string> {
|
|
9
9
|
map: ApiMapShape<K>;
|
|
10
10
|
res: keyof Pick<Response, 'json' | 'text' | 'blob'>;
|
|
11
|
-
status
|
|
12
|
-
safe
|
|
13
|
-
[
|
|
11
|
+
status?: {
|
|
12
|
+
safe?: number;
|
|
13
|
+
[n: number]: (res: Response) => any;
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
16
|
export type API_MAPS<T extends Record<string, any>> = {
|
package/types.ts
CHANGED
|
@@ -11,9 +11,9 @@ export type Exact<T, Shape extends T> =
|
|
|
11
11
|
export interface API_MAP_TYPE<K extends string> {
|
|
12
12
|
map: ApiMapShape<K>
|
|
13
13
|
res: keyof Pick<Response, 'json' | 'text' | 'blob'>
|
|
14
|
-
status
|
|
15
|
-
safe
|
|
16
|
-
[
|
|
14
|
+
status?: {
|
|
15
|
+
safe?: number,
|
|
16
|
+
[n: number]: (res: Response) => any
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|