restix 0.0.1
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/.gitattributes +2 -0
- package/LICENSE +21 -0
- package/README.md +202 -0
- package/api.d.ts +13 -0
- package/api.js +67 -0
- package/api.ts +74 -0
- package/bun.lock +26 -0
- package/index.d.ts +3 -0
- package/index.js +3 -0
- package/index.ts +3 -0
- package/package.json +14 -0
- package/test.test.d.ts +1 -0
- package/test.test.js +13 -0
- package/test.test.ts +19 -0
- package/tsconfig.json +29 -0
- package/types.d.ts +19 -0
- package/types.js +0 -0
- package/types.ts +24 -0
- package/utils.d.ts +2 -0
- package/utils.js +6 -0
- package/utils.ts +8 -0
package/.gitattributes
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Z3XS0N
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# restix
|
|
2
|
+
|
|
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
|
+
|
|
6
|
+
The goal is simple:
|
|
7
|
+
|
|
8
|
+
> **Design your API → map it → call it safely**
|
|
9
|
+
|
|
10
|
+
No runtime magic, no hidden abstractions, no guessing.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Why restix?
|
|
15
|
+
|
|
16
|
+
When building CLI tools or backend services, you often need to:
|
|
17
|
+
- Call many HTTP endpoints
|
|
18
|
+
- Inject params, query strings, headers, and body data
|
|
19
|
+
- Keep everything type-safe
|
|
20
|
+
- Avoid rewriting request logic over and over
|
|
21
|
+
|
|
22
|
+
**restix solves this by:**
|
|
23
|
+
- Enforcing **exact input types** per endpoint
|
|
24
|
+
- Preventing extra or missing fields at compile time
|
|
25
|
+
- Automatically injecting params, query, body, and headers
|
|
26
|
+
- Keeping request logic centralized and reusable
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Core Concepts
|
|
31
|
+
|
|
32
|
+
### 1. API Interface (Contract)
|
|
33
|
+
|
|
34
|
+
You define your API as a **TypeScript interface**.
|
|
35
|
+
Each key represents a route, and its value represents the **exact data shape** allowed.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
interface API_INTERFACE {
|
|
39
|
+
'GET /users/{id}': {
|
|
40
|
+
id: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
'POST /users/create': {
|
|
44
|
+
name: string
|
|
45
|
+
email: string
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
````
|
|
49
|
+
|
|
50
|
+
This interface becomes the **single source of truth**.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
### 2. API Mapping
|
|
55
|
+
|
|
56
|
+
Each endpoint must be mapped to explain **where data goes**:
|
|
57
|
+
|
|
58
|
+
* `params` → URL params
|
|
59
|
+
* `query` → query string
|
|
60
|
+
* `body` → request body
|
|
61
|
+
* `headers` → request headers
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { apiMap, type API_MAPS, type GET_API } from 'restix'
|
|
65
|
+
|
|
66
|
+
const api_maps: API_MAPS<API_INTERFACE> = {
|
|
67
|
+
'GET /users/{id}': {
|
|
68
|
+
map: apiMap<GET_API<API_INTERFACE, 'GET /users/{id}'>>({
|
|
69
|
+
params: ['id']
|
|
70
|
+
}),
|
|
71
|
+
res: 'json',
|
|
72
|
+
status: {
|
|
73
|
+
safe: 200,
|
|
74
|
+
404: () => 'User not found'
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
'POST /users/create': {
|
|
79
|
+
map: apiMap<GET_API<API_INTERFACE, 'POST /users/create'>>({
|
|
80
|
+
body: ['*']
|
|
81
|
+
}),
|
|
82
|
+
res: 'json',
|
|
83
|
+
status: {
|
|
84
|
+
safe: 201
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
* `'*'` means **all fields**
|
|
91
|
+
* Explicit keys mean **only those fields**
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
### 3. Creating the Client
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import { NexAPI } from 'restix'
|
|
99
|
+
|
|
100
|
+
const api = new NexAPI<API_INTERFACE>(
|
|
101
|
+
'https://api.example.com',
|
|
102
|
+
api_maps
|
|
103
|
+
)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
You can also provide **default injected data** (headers, query, body, params):
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
const api = new NexAPI<API_INTERFACE>(
|
|
110
|
+
'https://api.example.com',
|
|
111
|
+
api_maps,
|
|
112
|
+
{
|
|
113
|
+
headers: {
|
|
114
|
+
authorization: 'Bearer TOKEN'
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
### 4. Making Requests (Type-Safe)
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
await api.request('GET /users/{id}', {
|
|
126
|
+
id: '123'
|
|
127
|
+
})
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
await api.request('POST /users/create', {
|
|
132
|
+
name: 'John',
|
|
133
|
+
email: 'john@example.com'
|
|
134
|
+
})
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
❌ This will **fail at compile time**:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
api.request('POST /users/create', {
|
|
141
|
+
name: 'John',
|
|
142
|
+
email: 'john@example.com',
|
|
143
|
+
extra: 'not allowed'
|
|
144
|
+
})
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Because **restix enforces exact types**.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Key Features
|
|
152
|
+
|
|
153
|
+
* ✅ Exact object validation (no extra keys)
|
|
154
|
+
* ✅ Route-based type inference
|
|
155
|
+
* ✅ Automatic URL param injection
|
|
156
|
+
* ✅ Automatic query string building
|
|
157
|
+
* ✅ Automatic body & header extraction
|
|
158
|
+
* ✅ CLI-friendly (works perfectly with Bun / Node)
|
|
159
|
+
* ✅ No runtime dependencies
|
|
160
|
+
* ✅ No decorators, no reflection
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Designed For
|
|
165
|
+
|
|
166
|
+
* CLI tools
|
|
167
|
+
* Backend services
|
|
168
|
+
* Internal tooling
|
|
169
|
+
* Automation scripts
|
|
170
|
+
* API SDKs
|
|
171
|
+
* Monorepo shared clients
|
|
172
|
+
|
|
173
|
+
**Not** designed for browser-heavy frontend frameworks.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Philosophy
|
|
178
|
+
|
|
179
|
+
restix follows a **contract-first** approach:
|
|
180
|
+
|
|
181
|
+
* Types are the contract
|
|
182
|
+
* Mapping defines behavior
|
|
183
|
+
* Requests are deterministic
|
|
184
|
+
* Errors are caught at compile time, not runtime
|
|
185
|
+
|
|
186
|
+
If TypeScript allows it, restix allows it.
|
|
187
|
+
If TypeScript rejects it, restix rejects it too.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Status
|
|
192
|
+
|
|
193
|
+
This package is **actively evolving**.
|
|
194
|
+
The API is intentionally minimal and strict.
|
|
195
|
+
|
|
196
|
+
Contributions, ideas, and feedback are welcome.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## License
|
|
201
|
+
|
|
202
|
+
MIT
|
package/api.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { API_MAPS, Exact } from './types';
|
|
2
|
+
export declare class NexAPI<API_INTERFACE extends {
|
|
3
|
+
[k: string]: any;
|
|
4
|
+
}> {
|
|
5
|
+
#private;
|
|
6
|
+
constructor(base: string, api_maps: API_MAPS<API_INTERFACE>, defaults?: {
|
|
7
|
+
body?: Record<string, string>;
|
|
8
|
+
params?: Record<string, string>;
|
|
9
|
+
query?: Record<string, string>;
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
});
|
|
12
|
+
request<URL extends (keyof API_INTERFACE) & string>(prefix: URL, data: Exact<API_INTERFACE[URL], API_INTERFACE[URL]>): Promise<any>;
|
|
13
|
+
}
|
package/api.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export class NexAPI {
|
|
2
|
+
#base = 'http://localhost:5000';
|
|
3
|
+
#api_maps;
|
|
4
|
+
#defaults;
|
|
5
|
+
constructor(base, api_maps, defaults) {
|
|
6
|
+
this.#base = base;
|
|
7
|
+
this.#api_maps = api_maps;
|
|
8
|
+
this.#defaults = defaults ?? {};
|
|
9
|
+
}
|
|
10
|
+
async request(prefix, data) {
|
|
11
|
+
const api = this.#api_maps[prefix];
|
|
12
|
+
if (!api)
|
|
13
|
+
return;
|
|
14
|
+
data = {
|
|
15
|
+
...data,
|
|
16
|
+
...this.#defaults.body ?? {},
|
|
17
|
+
...this.#defaults.headers ?? {},
|
|
18
|
+
...this.#defaults.params ?? {},
|
|
19
|
+
...this.#defaults.query ?? {}
|
|
20
|
+
};
|
|
21
|
+
api.map = {
|
|
22
|
+
body: [...api.map.body, ...Object.keys(this.#defaults.body ?? {})],
|
|
23
|
+
params: [...api.map.params, ...Object.keys(this.#defaults.params ?? {})],
|
|
24
|
+
query: [...api.map.query, ...Object.keys(this.#defaults.query ?? {})],
|
|
25
|
+
headers: [...api.map.headers, ...Object.keys(this.#defaults.headers ?? {})]
|
|
26
|
+
};
|
|
27
|
+
const fetchOptoin = {};
|
|
28
|
+
let [method, url] = prefix.split(' ');
|
|
29
|
+
fetchOptoin.method = method;
|
|
30
|
+
url = `${this.#base}${url}`;
|
|
31
|
+
if (api.map.params.length > 0)
|
|
32
|
+
url = this.#injectUrlData(url, api.map.params, data);
|
|
33
|
+
if (api.map.query.length > 0)
|
|
34
|
+
url = this.#injectQuery(url, api.map.query, data);
|
|
35
|
+
if (api.map.body.length > 0)
|
|
36
|
+
fetchOptoin.body = JSON.stringify(this.#injectData(api.map.body, data));
|
|
37
|
+
if (api.map.headers.length > 0)
|
|
38
|
+
fetchOptoin.headers = this.#injectData(api.map.headers, data);
|
|
39
|
+
console.log(url, method, { ...fetchOptoin });
|
|
40
|
+
const res = await fetch(url, fetchOptoin);
|
|
41
|
+
if (res.status === api.status.safe)
|
|
42
|
+
return await res[api.res]();
|
|
43
|
+
else if (api.status && api.status[res.status])
|
|
44
|
+
return api.status[res.status]?.();
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
#injectUrlData(url, params, data) {
|
|
48
|
+
for (const param of params[0] == '*' ? Object.keys(data) : params)
|
|
49
|
+
url = url.replace(`{${param}}`, data[param]);
|
|
50
|
+
return url;
|
|
51
|
+
}
|
|
52
|
+
#injectQuery(url, queries, data) {
|
|
53
|
+
for (const [i, query] of queries.entries()) {
|
|
54
|
+
if (i == 0)
|
|
55
|
+
url = `${url}?${query}=${data[query]}`;
|
|
56
|
+
else
|
|
57
|
+
url = `${url}&${query}=${data[query]}`;
|
|
58
|
+
}
|
|
59
|
+
return url;
|
|
60
|
+
}
|
|
61
|
+
#injectData(body, data) {
|
|
62
|
+
const res = {};
|
|
63
|
+
for (const k of body[0] == '*' ? Object.keys(data) : body)
|
|
64
|
+
res[k] = data[k];
|
|
65
|
+
return res;
|
|
66
|
+
}
|
|
67
|
+
}
|
package/api.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { API_MAPS, Exact } from './types'
|
|
2
|
+
|
|
3
|
+
export class NexAPI<API_INTERFACE extends { [k: string]: any }> {
|
|
4
|
+
#base = 'http://localhost:5000'
|
|
5
|
+
|
|
6
|
+
#api_maps: API_MAPS<API_INTERFACE>
|
|
7
|
+
#defaults: {
|
|
8
|
+
body?: Record<string, string>
|
|
9
|
+
params?: Record<string, string>
|
|
10
|
+
query?: Record<string, string>
|
|
11
|
+
headers?: Record<string, string>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
constructor(base: string, api_maps: API_MAPS<API_INTERFACE>, defaults?: {
|
|
15
|
+
body?: Record<string, string>
|
|
16
|
+
params?: Record<string, string>
|
|
17
|
+
query?: Record<string, string>
|
|
18
|
+
headers?: Record<string, string>
|
|
19
|
+
}) {
|
|
20
|
+
this.#base = base
|
|
21
|
+
this.#api_maps = api_maps
|
|
22
|
+
this.#defaults = defaults ?? {}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async request<URL extends (keyof API_INTERFACE) & string>(prefix: URL, data: Exact<API_INTERFACE[URL], API_INTERFACE[URL]>) {
|
|
26
|
+
const api = this.#api_maps[prefix]
|
|
27
|
+
if (!api) return
|
|
28
|
+
data = {
|
|
29
|
+
...data,
|
|
30
|
+
...this.#defaults.body ?? {},
|
|
31
|
+
...this.#defaults.headers ?? {},
|
|
32
|
+
...this.#defaults.params ?? {},
|
|
33
|
+
...this.#defaults.query ?? {}
|
|
34
|
+
}
|
|
35
|
+
api.map = {
|
|
36
|
+
body: [...api.map.body, ...Object.keys(this.#defaults.body ?? {})] as typeof api.map.body,
|
|
37
|
+
params: [...api.map.params, ...Object.keys(this.#defaults.params ?? {})] as typeof api.map.params,
|
|
38
|
+
query: [...api.map.query, ...Object.keys(this.#defaults.query ?? {})] as typeof api.map.query,
|
|
39
|
+
headers: [...api.map.headers, ...Object.keys(this.#defaults.headers ?? {})] as typeof api.map.headers
|
|
40
|
+
}
|
|
41
|
+
const fetchOptoin: BunFetchRequestInit = {}
|
|
42
|
+
let [method, url] = prefix.split(' ') as [string, string]
|
|
43
|
+
fetchOptoin.method = method
|
|
44
|
+
url = `${this.#base}${url}`
|
|
45
|
+
if (api.map.params.length > 0) url = this.#injectUrlData(url, api.map.params, data)
|
|
46
|
+
if (api.map.query.length > 0) url = this.#injectQuery(url, api.map.query, data)
|
|
47
|
+
if (api.map.body.length > 0) fetchOptoin.body = JSON.stringify(this.#injectData(api.map.body, data))
|
|
48
|
+
if (api.map.headers.length > 0) fetchOptoin.headers = this.#injectData(api.map.headers, data)
|
|
49
|
+
console.log(url, method, { ...fetchOptoin })
|
|
50
|
+
const res = await fetch(url, fetchOptoin)
|
|
51
|
+
if (res.status === api.status.safe) return await res[api.res]()
|
|
52
|
+
else if (api.status && api.status[res.status]) return api.status[res.status]?.()
|
|
53
|
+
return undefined
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#injectUrlData(url: string, params: string[], data: Record<string, string>) {
|
|
57
|
+
for (const param of params[0] == '*' ? Object.keys(data) : params) url = url.replace(`{${param}}`, data[param]!)
|
|
58
|
+
return url
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
#injectQuery(url: string, queries: string[], data: Record<string, string>) {
|
|
62
|
+
for (const [i, query] of queries.entries()) {
|
|
63
|
+
if (i == 0) url = `${url}?${query}=${data[query]}`
|
|
64
|
+
else url = `${url}&${query}=${data[query]}`
|
|
65
|
+
}
|
|
66
|
+
return url
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
#injectData(body: string[], data: Record<string, any>) {
|
|
70
|
+
const res: typeof data = {}
|
|
71
|
+
for (const k of body[0] == '*' ? Object.keys(data) : body) res[k] = data[k]
|
|
72
|
+
return res
|
|
73
|
+
}
|
|
74
|
+
}
|
package/bun.lock
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"configVersion": 1,
|
|
4
|
+
"workspaces": {
|
|
5
|
+
"": {
|
|
6
|
+
"name": "restix",
|
|
7
|
+
"devDependencies": {
|
|
8
|
+
"@types/bun": "latest",
|
|
9
|
+
},
|
|
10
|
+
"peerDependencies": {
|
|
11
|
+
"typescript": "^5",
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
"packages": {
|
|
16
|
+
"@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="],
|
|
17
|
+
|
|
18
|
+
"@types/node": ["@types/node@25.2.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ=="],
|
|
19
|
+
|
|
20
|
+
"bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="],
|
|
21
|
+
|
|
22
|
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
23
|
+
|
|
24
|
+
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
|
25
|
+
}
|
|
26
|
+
}
|
package/index.d.ts
ADDED
package/index.js
ADDED
package/index.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "restix",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Type-safe HTTP client builder for CLI tools and backend applications",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "./index.d.ts",
|
|
7
|
+
"devDependencies": {
|
|
8
|
+
"@types/bun": "latest"
|
|
9
|
+
},
|
|
10
|
+
"peerDependencies": {
|
|
11
|
+
"typescript": "^5"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT"
|
|
14
|
+
}
|
package/test.test.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/test.test.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { NexAPI } from './api';
|
|
2
|
+
import { apiMap } from './utils';
|
|
3
|
+
const api_maps = {
|
|
4
|
+
'GET /users': {
|
|
5
|
+
map: apiMap(),
|
|
6
|
+
res: 'json',
|
|
7
|
+
status: {
|
|
8
|
+
safe: 200
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
const api = new NexAPI('http://localhost:5000', api_maps);
|
|
13
|
+
console.log(JSON.stringify(await api.request('GET /users', { selam: 'selam' }), null, 4));
|
package/test.test.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { NexAPI } from './api'
|
|
2
|
+
import type { API_MAPS } from './types'
|
|
3
|
+
import { apiMap } from './utils'
|
|
4
|
+
|
|
5
|
+
interface API_INTERFACE {
|
|
6
|
+
'GET /users': {}
|
|
7
|
+
}
|
|
8
|
+
const api_maps: API_MAPS<API_INTERFACE> = {
|
|
9
|
+
'GET /users': {
|
|
10
|
+
map: apiMap(),
|
|
11
|
+
res: 'json',
|
|
12
|
+
status: {
|
|
13
|
+
safe: 200
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const api = new NexAPI<API_INTERFACE>('http://localhost:5000', api_maps)
|
|
19
|
+
console.log(JSON.stringify(await api.request('GET /users', { selam: 'selam' }), null, 4))
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Environment setup & latest features
|
|
4
|
+
"lib": [
|
|
5
|
+
"ESNext"
|
|
6
|
+
],
|
|
7
|
+
"target": "ESNext",
|
|
8
|
+
"module": "Preserve",
|
|
9
|
+
"moduleDetection": "force",
|
|
10
|
+
"jsx": "react-jsx",
|
|
11
|
+
"allowJs": true,
|
|
12
|
+
// Bundler mode
|
|
13
|
+
"moduleResolution": "bundler",
|
|
14
|
+
"allowImportingTsExtensions": false,
|
|
15
|
+
"verbatimModuleSyntax": true,
|
|
16
|
+
"noEmit": false,
|
|
17
|
+
"declaration": true,
|
|
18
|
+
// Best practices
|
|
19
|
+
"strict": true,
|
|
20
|
+
"skipLibCheck": true,
|
|
21
|
+
"noFallthroughCasesInSwitch": true,
|
|
22
|
+
"noUncheckedIndexedAccess": true,
|
|
23
|
+
"noImplicitOverride": true,
|
|
24
|
+
// Some stricter flags (disabled by default)
|
|
25
|
+
"noUnusedLocals": false,
|
|
26
|
+
"noUnusedParameters": false,
|
|
27
|
+
"noPropertyAccessFromIndexSignature": false
|
|
28
|
+
}
|
|
29
|
+
}
|
package/types.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type ApiMapShape<K extends string> = {
|
|
2
|
+
body: (K | '*')[];
|
|
3
|
+
params: (K | '*')[];
|
|
4
|
+
query: (K | '*')[];
|
|
5
|
+
headers: (K | '*')[];
|
|
6
|
+
};
|
|
7
|
+
export type Exact<T, Shape extends T> = T & Record<Exclude<keyof Shape, keyof T>, never>;
|
|
8
|
+
export interface API_MAP_TYPE<K extends string> {
|
|
9
|
+
map: ApiMapShape<K>;
|
|
10
|
+
res: keyof Pick<Response, 'json' | 'text' | 'blob'>;
|
|
11
|
+
status: {
|
|
12
|
+
safe: number;
|
|
13
|
+
[k: number]: () => any;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export type API_MAPS<T extends Record<string, any>> = {
|
|
17
|
+
[K in keyof T]: API_MAP_TYPE<Extract<keyof T[K], string>>;
|
|
18
|
+
};
|
|
19
|
+
export type GET_API<T extends Record<string, any>, K extends keyof T> = keyof T[K];
|
package/types.js
ADDED
|
File without changes
|
package/types.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type ApiMapShape<K extends string> = {
|
|
2
|
+
body: (K | '*')[]
|
|
3
|
+
params: (K | '*')[]
|
|
4
|
+
query: (K | '*')[]
|
|
5
|
+
headers: (K | '*')[]
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type Exact<T, Shape extends T> =
|
|
9
|
+
T & Record<Exclude<keyof Shape, keyof T>, never>
|
|
10
|
+
|
|
11
|
+
export interface API_MAP_TYPE<K extends string> {
|
|
12
|
+
map: ApiMapShape<K>
|
|
13
|
+
res: keyof Pick<Response, 'json' | 'text' | 'blob'>
|
|
14
|
+
status: {
|
|
15
|
+
safe: number
|
|
16
|
+
[k: number]: () => any
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type API_MAPS<T extends Record<string, any>> = {
|
|
21
|
+
[K in keyof T]: API_MAP_TYPE<Extract<keyof T[K], string>>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type GET_API<T extends Record<string, any>, K extends keyof T> = keyof T[K]
|
package/utils.d.ts
ADDED
package/utils.js
ADDED
package/utils.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ApiMapShape } from './types';
|
|
2
|
+
|
|
3
|
+
export const apiMap = <K extends string>(options: Partial<ApiMapShape<K>> = {}): ApiMapShape<K> => ({
|
|
4
|
+
body: options.body ?? [],
|
|
5
|
+
params: options.params ?? [],
|
|
6
|
+
query: options.query ?? [],
|
|
7
|
+
headers: options.headers ?? []
|
|
8
|
+
})
|