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 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
- The goal is simple:
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
- > **Design your API → map it → call it safely**
7
+ > Design your API → map it → call it safely
9
8
 
10
- No runtime magic, no hidden abstractions, no guessing.
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 rewriting request logic over and over
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
- - Automatically injecting params, query, body, and headers
26
- - Keeping request logic centralized and reusable
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 Interface (Contract)
36
+ ### 1. API Contract (Type First)
33
37
 
34
- You define your API as a **TypeScript interface**.
35
- Each key represents a route, and its value represents the **exact data shape** allowed.
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 /users/{id}': {
40
- id: string
43
+ 'GET /packages/cli/name/{name}': {
44
+ name: string
41
45
  }
42
46
 
43
- 'POST /users/create': {
47
+ 'POST /packages/cli/create': {
44
48
  name: string
45
- email: string
49
+ version: string
50
+ developer: string
46
51
  }
47
52
  }
48
53
  ````
49
54
 
50
- This interface becomes the **single source of truth**.
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 be mapped to explain **where data goes**:
61
+ Each endpoint must declare how its data is injected.
62
+
63
+ Supported injection targets:
57
64
 
58
- * `params` → URL params
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 /users/{id}': {
68
- map: apiMap<GET_API<API_INTERFACE, 'GET /users/{id}'>>({
69
- params: ['id']
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 /users/create': {
79
- map: apiMap<GET_API<API_INTERFACE, 'POST /users/create'>>({
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
- * `'*'` means **all fields**
91
- * Explicit keys mean **only those fields**
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
- ### 3. Creating the Client
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
- 'https://api.example.com',
151
+ 'http://localhost:5000',
102
152
  api_maps
103
153
  )
104
154
  ```
105
155
 
106
- You can also provide **default injected data** (headers, query, body, params):
156
+ #### With Defaults
107
157
 
108
158
  ```ts
109
159
  const api = new NexAPI<API_INTERFACE>(
110
- 'https://api.example.com',
160
+ 'http://localhost:5000',
111
161
  api_maps,
112
162
  {
113
163
  headers: {
114
- authorization: 'Bearer TOKEN'
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
- ### 4. Making Requests (Type-Safe)
177
+ ### 5. Making Requests (Type-Safe)
123
178
 
124
179
  ```ts
125
- await api.request('GET /users/{id}', {
126
- id: '123'
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 /users/create', {
132
- name: 'John',
133
- email: 'john@example.com'
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
- This will **fail at compile time**:
193
+ Compile-time error (extra field):
138
194
 
139
195
  ```ts
140
- api.request('POST /users/create', {
141
- name: 'John',
142
- email: 'john@example.com',
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
- Because **restix enforces exact types**.
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 validation (no extra keys)
230
+ * ✅ Exact object typing (no extra keys)
154
231
  * ✅ 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)
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
- * API SDKs
171
- * Monorepo shared clients
247
+ * Internal SDKs
248
+ * API clients
249
+ * Monorepo shared tooling
172
250
 
173
- **Not** designed for browser-heavy frontend frameworks.
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, not runtime
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 package is **actively evolving**.
271
+ This project is actively evolving.
194
272
  The API is intentionally minimal and strict.
195
273
 
196
- Contributions, ideas, and feedback are welcome.
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 = { headers: { 'X-Client-Signature': 'restix/0.0.2 (by SignorP)' } };
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 [i, query] of queries.entries()) {
53
- if (i == 0)
54
- url = `${url}?${query}=${data[query]}`;
55
- else
56
- url = `${url}&${query}=${data[query]}`;
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 type { API_MAPS, Exact } from './types'
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
- } = { headers: { 'X-Client-Signature': 'restix/0.0.2 (by SignorP)' } }
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) fetchOptoin.body = JSON.stringify(this.#injectData(api.map.body, data))
47
- if (api.map.headers.length > 0) fetchOptoin.headers = this.#injectData(api.map.headers, data)
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 [i, query] of queries.entries()) {
61
- if (i == 0) url = `${url}?${query}=${data[query]}`
62
- else url = `${url}&${query}=${data[query]}`
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "restix",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Type-safe HTTP client builder for CLI tools and backend applications",
5
5
  "main": "index.js",
6
6
  "types": "./index.d.ts",
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: number;
13
- [k: number]: (res: Response) => any;
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: number
16
- [k: number]: (res: Response) => any
14
+ status?: {
15
+ safe?: number,
16
+ [n: number]: (res: Response) => any
17
17
  }
18
18
  }
19
19