tspace-spear 1.2.3 → 1.2.5-beta.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/README.md +268 -19
- package/dist/lib/core/client/index.d.ts +23 -0
- package/dist/lib/core/client/index.js +45 -0
- package/dist/lib/core/client/index.js.map +1 -0
- package/dist/lib/core/compiler/generator.d.ts +18 -0
- package/dist/lib/core/compiler/generator.js +142 -0
- package/dist/lib/core/compiler/generator.js.map +1 -0
- package/dist/lib/core/compiler/index.d.ts +14 -0
- package/dist/lib/core/compiler/index.js +11 -0
- package/dist/lib/core/compiler/index.js.map +1 -0
- package/dist/lib/core/compiler/pre-routes.d.ts +3 -0
- package/dist/lib/core/compiler/pre-routes.js +5 -0
- package/dist/lib/core/compiler/pre-routes.js.map +1 -0
- package/dist/lib/core/compiler/types.d.ts +12 -0
- package/dist/lib/core/compiler/types.js +3 -0
- package/dist/lib/core/compiler/types.js.map +1 -0
- package/dist/lib/core/const/index.d.ts +153 -0
- package/dist/lib/core/const/index.js +105 -0
- package/dist/lib/core/const/index.js.map +1 -0
- package/dist/lib/core/decorators/context.d.ts +16 -9
- package/dist/lib/core/decorators/context.js +85 -59
- package/dist/lib/core/decorators/context.js.map +1 -1
- package/dist/lib/core/decorators/headers.d.ts +2 -2
- package/dist/lib/core/decorators/headers.js +1 -1
- package/dist/lib/core/decorators/headers.js.map +1 -1
- package/dist/lib/core/decorators/methods.d.ts +7 -7
- package/dist/lib/core/decorators/methods.js.map +1 -1
- package/dist/lib/core/decorators/middleware.d.ts +3 -3
- package/dist/lib/core/decorators/middleware.js +2 -2
- package/dist/lib/core/decorators/middleware.js.map +1 -1
- package/dist/lib/core/decorators/statusCode.d.ts +1 -1
- package/dist/lib/core/decorators/statusCode.js.map +1 -1
- package/dist/lib/core/decorators/swagger.d.ts +1 -1
- package/dist/lib/core/decorators/swagger.js.map +1 -1
- package/dist/lib/core/server/fast-router.d.ts +133 -0
- package/dist/lib/core/server/fast-router.js +277 -0
- package/dist/lib/core/server/fast-router.js.map +1 -0
- package/dist/lib/core/server/index.d.ts +39 -37
- package/dist/lib/core/server/index.js +201 -501
- package/dist/lib/core/server/index.js.map +1 -1
- package/dist/lib/core/server/net/index.d.ts +20 -0
- package/dist/lib/core/server/net/index.js +393 -0
- package/dist/lib/core/server/net/index.js.map +1 -0
- package/dist/lib/core/server/parser-factory.d.ts +10 -11
- package/dist/lib/core/server/parser-factory.js +259 -437
- package/dist/lib/core/server/parser-factory.js.map +1 -1
- package/dist/lib/core/server/response.d.ts +6 -0
- package/dist/lib/core/server/response.js +168 -0
- package/dist/lib/core/server/response.js.map +1 -0
- package/dist/lib/core/server/router.d.ts +2 -12
- package/dist/lib/core/server/router.js +2 -13
- package/dist/lib/core/server/router.js.map +1 -1
- package/dist/lib/core/server/uWS/index.d.ts +30 -0
- package/dist/lib/core/server/uWS/index.js +357 -0
- package/dist/lib/core/server/uWS/index.js.map +1 -0
- package/dist/lib/core/types/index.d.ts +150 -48
- package/dist/lib/core/utils/index.d.ts +12 -0
- package/dist/lib/core/utils/index.js +137 -0
- package/dist/lib/core/utils/index.js.map +1 -0
- package/dist/lib/index.d.ts +4 -3
- package/dist/lib/index.js +4 -2
- package/dist/lib/index.js.map +1 -1
- package/package.json +20 -14
package/README.md
CHANGED
|
@@ -16,6 +16,11 @@ Install with [npm](https://www.npmjs.com/):
|
|
|
16
16
|
npm install tspace-spear --save
|
|
17
17
|
|
|
18
18
|
```
|
|
19
|
+
|
|
20
|
+
## Documentation
|
|
21
|
+
|
|
22
|
+
See the [`docs`](https://thanathip41.github.io/tspace-spear) directory for full documentation.
|
|
23
|
+
|
|
19
24
|
## Basic Usage
|
|
20
25
|
- [Start Server](#start-server)
|
|
21
26
|
- [Adapter](#adapter)
|
|
@@ -32,6 +37,7 @@ npm install tspace-spear --save
|
|
|
32
37
|
- [Cookie](#cookie)
|
|
33
38
|
- [Middleware](#middleware)
|
|
34
39
|
- [Controller](#controller)
|
|
40
|
+
- [Dto](#dto)
|
|
35
41
|
- [Router](#router)
|
|
36
42
|
- [Swagger](#swagger)
|
|
37
43
|
- [WebSocket](#websocket)
|
|
@@ -82,6 +88,7 @@ new Spear({ adapter: uWS })
|
|
|
82
88
|
```
|
|
83
89
|
|
|
84
90
|
## Cluster
|
|
91
|
+
Cluster mode allows tspace-spear to run multiple worker processes to fully utilize multi-core CPU performance.
|
|
85
92
|
```js
|
|
86
93
|
import { Spear } from "tspace-spear";
|
|
87
94
|
new Spear({
|
|
@@ -98,6 +105,9 @@ new Spear({
|
|
|
98
105
|
```
|
|
99
106
|
|
|
100
107
|
## Global Prefix
|
|
108
|
+
Global Prefix allows you to define a base path for all routes in your application.
|
|
109
|
+
|
|
110
|
+
It helps keep your API structured and consistent (e.g. /api, /v1, /app).
|
|
101
111
|
```js
|
|
102
112
|
const app = new Spear({
|
|
103
113
|
globalPrefix : '/api' // prefix all routes
|
|
@@ -109,6 +119,16 @@ const app = new Spear({
|
|
|
109
119
|
```
|
|
110
120
|
|
|
111
121
|
## Logger
|
|
122
|
+
The built-in Logger provides request logging for incoming HTTP requests.
|
|
123
|
+
|
|
124
|
+
It helps you monitor:
|
|
125
|
+
|
|
126
|
+
Request method
|
|
127
|
+
Request path
|
|
128
|
+
Performance tracking
|
|
129
|
+
Debugging and observability
|
|
130
|
+
|
|
131
|
+
You can enable a simple logger or configure advanced logging behavior.
|
|
112
132
|
```js
|
|
113
133
|
const app = new Spear({
|
|
114
134
|
logger : true
|
|
@@ -124,8 +144,17 @@ const app = new Spear({
|
|
|
124
144
|
```
|
|
125
145
|
|
|
126
146
|
## Format Response
|
|
147
|
+
Provides a consistent response structure system to standardize how responses are handled across your application.
|
|
148
|
+
|
|
149
|
+
It ensures that:
|
|
150
|
+
|
|
151
|
+
* All responses are predictable
|
|
152
|
+
* Errors are properly structured
|
|
153
|
+
* Missing routes are handled cleanly
|
|
154
|
+
* Global error catching is supported
|
|
127
155
|
|
|
128
156
|
### Notfound
|
|
157
|
+
The NotFound handler is triggered when no route matches the incoming request.
|
|
129
158
|
```js
|
|
130
159
|
const app = new Spear()
|
|
131
160
|
.get('/' , () => {
|
|
@@ -142,6 +171,13 @@ const app = new Spear()
|
|
|
142
171
|
```
|
|
143
172
|
|
|
144
173
|
### Response
|
|
174
|
+
The response system ensures that all returned values are automatically formatted and sent to the client.
|
|
175
|
+
|
|
176
|
+
You can return:
|
|
177
|
+
|
|
178
|
+
* String
|
|
179
|
+
* Object (JSON)
|
|
180
|
+
* Custom response via ctx.res
|
|
145
181
|
```js
|
|
146
182
|
import { Spear } from "tspace-spear";
|
|
147
183
|
|
|
@@ -183,6 +219,10 @@ const app = new Spear()
|
|
|
183
219
|
```
|
|
184
220
|
|
|
185
221
|
### Catch
|
|
222
|
+
The Catch handler is used to handle unexpected runtime errors globally.
|
|
223
|
+
|
|
224
|
+
It acts as a safety layer to prevent server crashes and standardize error responses.
|
|
225
|
+
|
|
186
226
|
```js
|
|
187
227
|
import { Spear } from "tspace-spear";
|
|
188
228
|
import { z } from "zod";
|
|
@@ -217,7 +257,9 @@ const app = new Spear()
|
|
|
217
257
|
```
|
|
218
258
|
|
|
219
259
|
## Cors
|
|
260
|
+
CORS (Cross-Origin Resource Sharing) controls which origins are allowed to access your API.
|
|
220
261
|
|
|
262
|
+
It helps secure your server by restricting or allowing cross-domain requests.
|
|
221
263
|
```js
|
|
222
264
|
const app = new Spear()
|
|
223
265
|
.cors({
|
|
@@ -232,6 +274,9 @@ const app = new Spear()
|
|
|
232
274
|
```
|
|
233
275
|
|
|
234
276
|
## Body
|
|
277
|
+
Body parsing allows your server to read incoming request payloads (JSON) and access them via ctx.body.
|
|
278
|
+
|
|
279
|
+
It enables handling requests with structured data.
|
|
235
280
|
```js
|
|
236
281
|
|
|
237
282
|
new Spear()
|
|
@@ -247,7 +292,14 @@ new Spear()
|
|
|
247
292
|
```
|
|
248
293
|
|
|
249
294
|
## File Upload
|
|
295
|
+
File upload support allows handling multipart/form-data requests and working with uploaded files via ctx.files.
|
|
296
|
+
|
|
297
|
+
It provides:
|
|
250
298
|
|
|
299
|
+
* Temporary file handling
|
|
300
|
+
* File size limits
|
|
301
|
+
* Manual file movement
|
|
302
|
+
* Auto cleanup option
|
|
251
303
|
```js
|
|
252
304
|
|
|
253
305
|
import { Spear, type T } from 'tspace-spear';
|
|
@@ -284,6 +336,14 @@ new Spear()
|
|
|
284
336
|
```
|
|
285
337
|
|
|
286
338
|
## Cookie
|
|
339
|
+
Cookie support allows you to read and manage HTTP cookies from incoming requests via ctx.cookies.
|
|
340
|
+
|
|
341
|
+
It is useful for:
|
|
342
|
+
|
|
343
|
+
* Session handling
|
|
344
|
+
* Authentication
|
|
345
|
+
* User preferences
|
|
346
|
+
* Stateful requests
|
|
287
347
|
```js
|
|
288
348
|
|
|
289
349
|
new Spear()
|
|
@@ -297,6 +357,12 @@ new Spear()
|
|
|
297
357
|
```
|
|
298
358
|
|
|
299
359
|
## Middleware
|
|
360
|
+
Middleware is a function that runs before the controller handler and is used to:
|
|
361
|
+
|
|
362
|
+
* Intercept requests
|
|
363
|
+
* Modify ctx
|
|
364
|
+
* Validate or block execution
|
|
365
|
+
* Handle authentication / logging / transformations
|
|
300
366
|
```js
|
|
301
367
|
import { type T } from "tspace-spear"
|
|
302
368
|
// file cat-middleware.ts
|
|
@@ -348,6 +414,9 @@ import CatMiddleware from './cat-middleware.ts'
|
|
|
348
414
|
```
|
|
349
415
|
|
|
350
416
|
## Controller
|
|
417
|
+
A Controller is used to group related routes and define request handlers in a structured way.
|
|
418
|
+
|
|
419
|
+
It helps organize your application into modules (similar to NestJS / Express routers), while keeping a clean and readable API design.
|
|
351
420
|
```js
|
|
352
421
|
import {
|
|
353
422
|
Controller ,
|
|
@@ -462,8 +531,131 @@ import CatController from './cat-controller.ts'
|
|
|
462
531
|
})()
|
|
463
532
|
```
|
|
464
533
|
|
|
534
|
+
|
|
535
|
+
## Dto
|
|
536
|
+
DTO (Data Transfer Object) is used to validate and transform incoming request data before it reaches your controller logic.
|
|
537
|
+
```js
|
|
538
|
+
import {
|
|
539
|
+
Controller ,
|
|
540
|
+
Post,
|
|
541
|
+
createDtoDecorator
|
|
542
|
+
type T
|
|
543
|
+
} from 'tspace-spear';
|
|
544
|
+
|
|
545
|
+
import z from "zod";
|
|
546
|
+
|
|
547
|
+
const ValidateDtoBody = (keys: string[]) => {
|
|
548
|
+
return createDtoDecorator((ctx) => {
|
|
549
|
+
const body = ctx.body ?? {};
|
|
550
|
+
const issues: Array<{ path: string; message: string }> = [];
|
|
551
|
+
|
|
552
|
+
for (let i = 0; i < keys.length; i++) {
|
|
553
|
+
const key = keys[i];
|
|
554
|
+
|
|
555
|
+
if (body[key] == null) {
|
|
556
|
+
issues.push({
|
|
557
|
+
path: key,
|
|
558
|
+
message: "Missing field",
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (issues.length > 0) {
|
|
564
|
+
throw {
|
|
565
|
+
message : "Validation failed",
|
|
566
|
+
issues
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const ValidateDtoZodBody = (schema: z.ZodTypeAny) => {
|
|
573
|
+
return createDtoDecorator((ctx) => {
|
|
574
|
+
const result = schema.parse(ctx.body);
|
|
575
|
+
ctx.body = result as T.Body;
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const ValidateDtoPromiseBody = (keys: string[]) => {
|
|
580
|
+
return createDtoDecorator(async (ctx) => {
|
|
581
|
+
await new Promise(resolve => setTimeout(resolve,500));
|
|
582
|
+
// check in DB or other async operation
|
|
583
|
+
throw new Error('Validation failed in promise!');
|
|
584
|
+
}, (ctx, error) => {
|
|
585
|
+
// you implement your custom error handling for async validation here
|
|
586
|
+
return ctx.res.status(400).json({
|
|
587
|
+
message: error.message || "Validation failed",
|
|
588
|
+
issues: error.issues || [],
|
|
589
|
+
});
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const catSchema = z.object({
|
|
594
|
+
name: z.string(),
|
|
595
|
+
age: z.number(),
|
|
596
|
+
})
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
// file cat-controller.ts
|
|
600
|
+
@Controller('/cats')
|
|
601
|
+
export class CatController {
|
|
602
|
+
@Post('/')
|
|
603
|
+
@ValidateDtoBody(["name", "age"])
|
|
604
|
+
public async basic(ctx : T.Context) {
|
|
605
|
+
const body = ctx.body;
|
|
606
|
+
return {
|
|
607
|
+
body
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
@Post('/zod')
|
|
612
|
+
@ValidateDtoZodBody(catSchema)
|
|
613
|
+
public async zod(ctx : T.Context) {
|
|
614
|
+
const body = ctx.body as z.infer<typeof catSchema>;
|
|
615
|
+
return {
|
|
616
|
+
body
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
@Post('/promise')
|
|
621
|
+
@ValidateDtoPromiseBody(['name', 'age'])
|
|
622
|
+
public async promise(ctx : T.Context) {
|
|
623
|
+
const body = ctx.body;
|
|
624
|
+
|
|
625
|
+
return {
|
|
626
|
+
body
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
import { Spear } , { Router, type T } from "tspace-spear";
|
|
632
|
+
|
|
633
|
+
import CatController from './cat-controller.ts'
|
|
634
|
+
|
|
635
|
+
(async () => {
|
|
636
|
+
|
|
637
|
+
new Spear({
|
|
638
|
+
controllers: [ CatController ]
|
|
639
|
+
})
|
|
640
|
+
.useBodyParser()
|
|
641
|
+
.listen(3000 , () => console.log(`Server is now listening http://localhost:3000`))
|
|
642
|
+
|
|
643
|
+
// localhost:3000/cats // basic implete
|
|
644
|
+
// localhost:3000/cats/zod // zod implete
|
|
645
|
+
// localhost:3000/cats/promise // promise implete
|
|
646
|
+
|
|
647
|
+
})()
|
|
648
|
+
```
|
|
649
|
+
|
|
465
650
|
## Router
|
|
651
|
+
The Router allows you to organize routes into modular groups, making your application more scalable and maintainable.
|
|
652
|
+
|
|
653
|
+
It supports:
|
|
466
654
|
|
|
655
|
+
* Grouped routes
|
|
656
|
+
* Nested route prefixes
|
|
657
|
+
* Reusable router modules
|
|
658
|
+
* Separation of concerns
|
|
467
659
|
```js
|
|
468
660
|
import { Spear, Router, type T } from "tspace-spear";
|
|
469
661
|
|
|
@@ -507,6 +699,14 @@ app.listen(port , () => console.log(`Server is now listening http://localhost:30
|
|
|
507
699
|
```
|
|
508
700
|
|
|
509
701
|
## Swagger
|
|
702
|
+
Provides built-in Swagger support to document your API endpoints.
|
|
703
|
+
|
|
704
|
+
It allows you to:
|
|
705
|
+
|
|
706
|
+
* Describe request parameters (query, body, params)
|
|
707
|
+
* Generate API documentation
|
|
708
|
+
* Improve developer experience
|
|
709
|
+
* Standardize API contracts
|
|
510
710
|
```js
|
|
511
711
|
|
|
512
712
|
// file cat-controller.ts
|
|
@@ -574,7 +774,22 @@ class CatController {
|
|
|
574
774
|
name : {
|
|
575
775
|
type : 'string',
|
|
576
776
|
example : "xxxxx"
|
|
577
|
-
}
|
|
777
|
+
},
|
|
778
|
+
status : {
|
|
779
|
+
type : 'string',
|
|
780
|
+
example: "active",
|
|
781
|
+
enum: ['active', 'inactive'],
|
|
782
|
+
description: "User status (active = enabled, inactive = disabled)",
|
|
783
|
+
required : true
|
|
784
|
+
},
|
|
785
|
+
roles: {
|
|
786
|
+
type: 'array',
|
|
787
|
+
items : {
|
|
788
|
+
type: 'string',
|
|
789
|
+
example: "roleA",
|
|
790
|
+
enum: ['roleA', 'roleB']
|
|
791
|
+
}
|
|
792
|
+
},
|
|
578
793
|
}
|
|
579
794
|
}
|
|
580
795
|
})
|
|
@@ -699,44 +914,78 @@ class CatController {
|
|
|
699
914
|
```
|
|
700
915
|
|
|
701
916
|
## WebSocket
|
|
917
|
+
Provides built-in WebSocket support for real-time communication.
|
|
918
|
+
|
|
919
|
+
It allows you to:
|
|
920
|
+
|
|
921
|
+
* Handle client connections
|
|
922
|
+
* Send/receive messages
|
|
923
|
+
* Build chat systems
|
|
924
|
+
* Manage real-time events
|
|
702
925
|
```js
|
|
703
926
|
import { Spear } from "tspace-spear";
|
|
927
|
+
import fs from 'fs';
|
|
928
|
+
import path from 'path';
|
|
704
929
|
|
|
705
930
|
new Spear()
|
|
706
|
-
.get('/',
|
|
707
|
-
|
|
708
|
-
|
|
931
|
+
.get('/',(ctx) => {
|
|
932
|
+
// you can serve a HTML file for testing WebSocket connection
|
|
933
|
+
const htmlWs = fs.readFileSync(path.join(path.resolve(), 'public', 'ws.html'), 'utf-8');
|
|
934
|
+
return ctx.res.html(htmlWs);
|
|
709
935
|
})
|
|
710
936
|
.ws(() => {
|
|
937
|
+
const clients = new Map<string, any>();
|
|
711
938
|
return {
|
|
712
939
|
connection: (ws) => {
|
|
713
|
-
|
|
940
|
+
console.log("connected");
|
|
714
941
|
},
|
|
942
|
+
|
|
715
943
|
message: (ws, msg) => {
|
|
716
944
|
const data = JSON.parse(msg.toString());
|
|
717
945
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
946
|
+
if (data.type === "register") {
|
|
947
|
+
ws.userId = data.userId;
|
|
948
|
+
clients.set(data.userId, ws);
|
|
949
|
+
|
|
950
|
+
ws.send(JSON.stringify({
|
|
951
|
+
type: "system",
|
|
952
|
+
message: `registered as ${data.userId}`
|
|
953
|
+
}));
|
|
954
|
+
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
if (data.type === "chat") {
|
|
959
|
+
const targetWs = clients.get(data.to);
|
|
723
960
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
961
|
+
if (!targetWs) {
|
|
962
|
+
ws.send(JSON.stringify({
|
|
963
|
+
type: "error",
|
|
964
|
+
message: "User not online"
|
|
965
|
+
}));
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
targetWs.send(JSON.stringify({
|
|
970
|
+
type: "chat",
|
|
971
|
+
from: ws.userId,
|
|
972
|
+
text: data.text
|
|
973
|
+
}));
|
|
728
974
|
|
|
729
|
-
|
|
730
|
-
ws.send(JSON.stringify({ type: 'error', message: 'Unknown message type' }));
|
|
975
|
+
return;
|
|
731
976
|
}
|
|
732
977
|
},
|
|
733
|
-
|
|
734
|
-
|
|
978
|
+
|
|
979
|
+
close: (ws) => {
|
|
980
|
+
if (ws.userId) {
|
|
981
|
+
clients.delete(ws.userId);
|
|
982
|
+
}
|
|
735
983
|
},
|
|
984
|
+
|
|
736
985
|
error: (ws, error) => {
|
|
737
986
|
console.error('WebSocket error:', error);
|
|
738
987
|
}
|
|
739
|
-
}
|
|
988
|
+
};
|
|
740
989
|
})
|
|
741
990
|
.listen(3000 , ({ port , server }) => {
|
|
742
991
|
console.log(`server listening on : http://localhost:${port}`)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AppRoutes } from "../compiler/pre-routes";
|
|
2
|
+
import type { RoutesWithMethod, RequestBody, RequestQuery, RequestParams, RequestFiles, ResponseType } from "../compiler/types";
|
|
3
|
+
type RequestInput<TPath extends keyof AppRoutes, TMethod extends keyof AppRoutes[TPath]> = RequestParams<TPath, TMethod> extends never ? {
|
|
4
|
+
body?: RequestBody<TPath, TMethod>;
|
|
5
|
+
query?: RequestQuery<TPath, TMethod>;
|
|
6
|
+
files?: RequestFiles<TPath, TMethod>;
|
|
7
|
+
} : {
|
|
8
|
+
params: RequestParams<TPath, TMethod>;
|
|
9
|
+
body?: RequestBody<TPath, TMethod>;
|
|
10
|
+
query?: RequestQuery<TPath, TMethod>;
|
|
11
|
+
files?: RequestFiles<TPath, TMethod>;
|
|
12
|
+
};
|
|
13
|
+
export declare class ApiClient {
|
|
14
|
+
private baseURL;
|
|
15
|
+
constructor(baseURL: string);
|
|
16
|
+
protected request<TPath extends keyof AppRoutes, TMethod extends keyof AppRoutes[TPath]>(method: TMethod, path: TPath, input?: RequestInput<TPath, TMethod>): Promise<ResponseType<TPath, TMethod>>;
|
|
17
|
+
get<TPath extends RoutesWithMethod<"GET">>(path: TPath, input?: RequestInput<TPath, "GET">): Promise<ResponseType<TPath, "GET">>;
|
|
18
|
+
post<TPath extends RoutesWithMethod<"POST">>(path: TPath, input: RequestInput<TPath, "POST">): Promise<ResponseType<TPath, "POST">>;
|
|
19
|
+
put<TPath extends RoutesWithMethod<"PUT">>(path: TPath, input: RequestInput<TPath, "PUT">): Promise<ResponseType<TPath, "PUT">>;
|
|
20
|
+
patch<TPath extends RoutesWithMethod<"PATCH">>(path: TPath, input: RequestInput<TPath, "PATCH">): Promise<ResponseType<TPath, "PATCH">>;
|
|
21
|
+
delete<TPath extends RoutesWithMethod<"DELETE">>(path: TPath, input?: RequestInput<TPath, "DELETE">): Promise<ResponseType<TPath, "DELETE">>;
|
|
22
|
+
}
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ApiClient = void 0;
|
|
4
|
+
class ApiClient {
|
|
5
|
+
baseURL;
|
|
6
|
+
constructor(baseURL) {
|
|
7
|
+
this.baseURL = baseURL;
|
|
8
|
+
}
|
|
9
|
+
async request(method, path, input) {
|
|
10
|
+
const url = this.baseURL + path;
|
|
11
|
+
const res = await fetch(url, {
|
|
12
|
+
method: method,
|
|
13
|
+
headers: {
|
|
14
|
+
"Content-Type": "application/json",
|
|
15
|
+
},
|
|
16
|
+
body: input?.body ? JSON.stringify(input.body) : undefined,
|
|
17
|
+
});
|
|
18
|
+
const contentType = res.headers.get("content-type");
|
|
19
|
+
const isJson = contentType?.includes("application/json");
|
|
20
|
+
const data = isJson ? await res.json() : await res.text();
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
throw new Error(data?.message ||
|
|
23
|
+
data?.error ||
|
|
24
|
+
(typeof data === "string" ? data : `HTTP ${res.status}`));
|
|
25
|
+
}
|
|
26
|
+
return data;
|
|
27
|
+
}
|
|
28
|
+
async get(path, input) {
|
|
29
|
+
return this.request("GET", path, input);
|
|
30
|
+
}
|
|
31
|
+
async post(path, input) {
|
|
32
|
+
return this.request("POST", path, input);
|
|
33
|
+
}
|
|
34
|
+
async put(path, input) {
|
|
35
|
+
return this.request("PUT", path, input);
|
|
36
|
+
}
|
|
37
|
+
async patch(path, input) {
|
|
38
|
+
return this.request("PATCH", path, input);
|
|
39
|
+
}
|
|
40
|
+
async delete(path, input) {
|
|
41
|
+
return this.request("DELETE", path, input);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.ApiClient = ApiClient;
|
|
45
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/lib/core/client/index.ts"],"names":[],"mappings":";;;AA4BA,MAAa,SAAS;IACZ,OAAO,CAAS;IAExB,YAAY,OAAe;QACzB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAES,KAAK,CAAC,OAAO,CAIrB,MAAe,EACf,IAAW,EACX,KAAoC;QAEpC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QAEhC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC3B,MAAM,EAAE,MAAgB;YACxB,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;aACnC;YACD,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;SAC3D,CAAC,CAAC;QAEH,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAEpD,MAAM,MAAM,GAAG,WAAW,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC;QAEzD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAE1D,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CACb,IAAI,EAAE,OAAO;gBACX,IAAI,EAAE,KAAK;gBACX,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC,CAC3D,CAAC;QACJ,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAEM,KAAK,CAAC,GAAG,CACd,IAAW,EACX,KAAkC;QAElC,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IAC1C,CAAC;IAEM,KAAK,CAAC,IAAI,CACf,IAAW,EACX,KAAkC;QAElC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IAC3C,CAAC;IAEM,KAAK,CAAC,GAAG,CACd,IAAW,EACX,KAAiC;QAEjC,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IAC1C,CAAC;IAEM,KAAK,CAAC,KAAK,CAChB,IAAW,EACX,KAAmC;QAEnC,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IAC5C,CAAC;IAEM,KAAK,CAAC,MAAM,CACjB,IAAW,EACX,KAAqC;QAErC,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IAC7C,CAAC;CACF;AA5ED,8BA4EC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type Route = {
|
|
2
|
+
method: string;
|
|
3
|
+
path: string;
|
|
4
|
+
response: string;
|
|
5
|
+
body: string;
|
|
6
|
+
params: string;
|
|
7
|
+
query: string;
|
|
8
|
+
files: string;
|
|
9
|
+
};
|
|
10
|
+
type Options = {
|
|
11
|
+
controllers: {
|
|
12
|
+
folder: string;
|
|
13
|
+
name: RegExp;
|
|
14
|
+
};
|
|
15
|
+
output?: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function generateRoutes(options: Options): Promise<Route[]>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generateRoutes = generateRoutes;
|
|
7
|
+
const ts_morph_1 = require("ts-morph");
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const HTTP_METHODS = {
|
|
11
|
+
Get: "GET",
|
|
12
|
+
Post: "POST",
|
|
13
|
+
Put: "PUT",
|
|
14
|
+
Patch: "PATCH",
|
|
15
|
+
Delete: "DELETE",
|
|
16
|
+
Options: "OPTIONS",
|
|
17
|
+
Head: "HEAD",
|
|
18
|
+
};
|
|
19
|
+
function normalizePath(p) {
|
|
20
|
+
return ("/" +
|
|
21
|
+
p
|
|
22
|
+
.replace(/['"`]/g, "")
|
|
23
|
+
.replace(/\/+/g, "/")
|
|
24
|
+
.replace(/\/$/, "")
|
|
25
|
+
.replace(/^\//, ""));
|
|
26
|
+
}
|
|
27
|
+
function extractPropertyType(type, key, node) {
|
|
28
|
+
const prop = type.getProperty(key);
|
|
29
|
+
if (!prop)
|
|
30
|
+
return "never";
|
|
31
|
+
const t = prop.getTypeAtLocation(node);
|
|
32
|
+
const text = t.getText(node);
|
|
33
|
+
if (!text || text.includes("undefined"))
|
|
34
|
+
return "never";
|
|
35
|
+
return text;
|
|
36
|
+
}
|
|
37
|
+
async function generateRoutes(options) {
|
|
38
|
+
const project = new ts_morph_1.Project({
|
|
39
|
+
tsConfigFilePath: path_1.default.resolve(process.cwd(), "tsconfig.json"),
|
|
40
|
+
});
|
|
41
|
+
project.addSourceFilesAtPaths(path_1.default.join(options.controllers.folder, "**/*"));
|
|
42
|
+
const files = project.getSourceFiles();
|
|
43
|
+
if (!files.length) {
|
|
44
|
+
throw new Error("No controller files found");
|
|
45
|
+
}
|
|
46
|
+
const routes = [];
|
|
47
|
+
for (const file of files) {
|
|
48
|
+
const filename = file.getBaseName();
|
|
49
|
+
if (!options.controllers.name.test(filename))
|
|
50
|
+
continue;
|
|
51
|
+
for (const cls of file.getClasses()) {
|
|
52
|
+
const controller = cls.getDecorator("Controller");
|
|
53
|
+
if (!controller)
|
|
54
|
+
continue;
|
|
55
|
+
const basePath = controller.getArguments()[0]
|
|
56
|
+
?.getText()
|
|
57
|
+
.replace(/['"`]/g, "") || "";
|
|
58
|
+
for (const method of cls.getMethods()) {
|
|
59
|
+
for (const [decName, http] of Object.entries(HTTP_METHODS)) {
|
|
60
|
+
const decorator = method.getDecorator(decName);
|
|
61
|
+
if (!decorator)
|
|
62
|
+
continue;
|
|
63
|
+
const methodPath = decorator
|
|
64
|
+
.getArguments()[0]
|
|
65
|
+
?.getText()
|
|
66
|
+
.replace(/['"`]/g, "") || "";
|
|
67
|
+
const fullPath = normalizePath(`${basePath}/${methodPath}`);
|
|
68
|
+
const response = method.getReturnType().getText(method);
|
|
69
|
+
let body = "never";
|
|
70
|
+
let params = "never";
|
|
71
|
+
let query = "never";
|
|
72
|
+
let files = "never";
|
|
73
|
+
const firstParam = method.getParameters()[0];
|
|
74
|
+
if (firstParam) {
|
|
75
|
+
const type = firstParam.getType();
|
|
76
|
+
body = extractPropertyType(type, "body", firstParam);
|
|
77
|
+
params = extractPropertyType(type, "params", firstParam);
|
|
78
|
+
query = extractPropertyType(type, "query", firstParam);
|
|
79
|
+
files = extractPropertyType(type, "files", firstParam);
|
|
80
|
+
}
|
|
81
|
+
routes.push({
|
|
82
|
+
method: http,
|
|
83
|
+
path: fullPath,
|
|
84
|
+
response,
|
|
85
|
+
body,
|
|
86
|
+
params,
|
|
87
|
+
query,
|
|
88
|
+
files,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const grouped = {};
|
|
95
|
+
for (const r of routes) {
|
|
96
|
+
grouped[r.path] ??= {};
|
|
97
|
+
grouped[r.path][r.method] = {
|
|
98
|
+
response: r.response,
|
|
99
|
+
body: r.body,
|
|
100
|
+
params: r.params,
|
|
101
|
+
query: r.query,
|
|
102
|
+
files: r.files,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const routeMap = Object.entries(grouped)
|
|
106
|
+
.map(([path, methods]) => {
|
|
107
|
+
const methodBlock = Object.entries(methods)
|
|
108
|
+
.map(([method, c]) => `
|
|
109
|
+
${method}: {
|
|
110
|
+
params: ${c.params}
|
|
111
|
+
query: ${c.query}
|
|
112
|
+
body: ${c.body}
|
|
113
|
+
files: ${c.files}
|
|
114
|
+
response: ${c.response}
|
|
115
|
+
}`)
|
|
116
|
+
.join("\n");
|
|
117
|
+
return `
|
|
118
|
+
"${path}": {
|
|
119
|
+
${methodBlock}
|
|
120
|
+
}`;
|
|
121
|
+
})
|
|
122
|
+
.join("\n");
|
|
123
|
+
const output = `
|
|
124
|
+
// AUTO GENERATED FILE
|
|
125
|
+
// DO NOT EDIT
|
|
126
|
+
|
|
127
|
+
export interface AppRoutes {
|
|
128
|
+
${routeMap}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export type AppRoute = keyof AppRoutes
|
|
132
|
+
`;
|
|
133
|
+
const outPath = options.output
|
|
134
|
+
? `${__dirname}/${options.output}/pre-routes.ts`
|
|
135
|
+
: `${__dirname}/pre-routes.ts`;
|
|
136
|
+
await fs_1.default.promises.mkdir(path_1.default.dirname(outPath), {
|
|
137
|
+
recursive: true,
|
|
138
|
+
});
|
|
139
|
+
await fs_1.default.promises.writeFile(outPath, output);
|
|
140
|
+
return routes;
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generator.js","sourceRoot":"","sources":["../../../../src/lib/core/compiler/generator.ts"],"names":[],"mappings":"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"}
|