tspace-spear 1.2.7 → 1.2.9-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 +347 -155
- package/dist/cli/generators/app/index.d.ts +1 -0
- package/dist/cli/generators/app/index.js +124 -0
- package/dist/cli/generators/app/index.js.map +1 -0
- package/dist/cli/generators/app/template.d.ts +1 -0
- package/dist/cli/generators/app/template.js +42 -0
- package/dist/cli/generators/app/template.js.map +1 -0
- package/dist/cli/generators/client/template.d.ts +1 -0
- package/dist/cli/generators/client/template.js +30 -0
- package/dist/cli/generators/client/template.js.map +1 -0
- package/dist/cli/generators/controller/index.d.ts +1 -0
- package/dist/cli/generators/controller/index.js +78 -0
- package/dist/cli/generators/controller/index.js.map +1 -0
- package/dist/cli/generators/controller/template.d.ts +1 -0
- package/dist/cli/generators/controller/template.js +108 -0
- package/dist/cli/generators/controller/template.js.map +1 -0
- package/dist/cli/generators/dto/index.d.ts +1 -0
- package/dist/cli/generators/dto/index.js +57 -0
- package/dist/cli/generators/dto/index.js.map +1 -0
- package/dist/cli/generators/dto/template.d.ts +1 -0
- package/dist/cli/generators/dto/template.js +32 -0
- package/dist/cli/generators/dto/template.js.map +1 -0
- package/dist/cli/generators/middleware/index.d.ts +1 -0
- package/dist/cli/generators/middleware/index.js +38 -0
- package/dist/cli/generators/middleware/index.js.map +1 -0
- package/dist/cli/generators/middleware/template.d.ts +1 -0
- package/dist/cli/generators/middleware/template.js +16 -0
- package/dist/cli/generators/middleware/template.js.map +1 -0
- package/dist/cli/generators/module/index.d.ts +1 -0
- package/dist/cli/generators/module/index.js +173 -0
- package/dist/cli/generators/module/index.js.map +1 -0
- package/dist/cli/generators/service/index.d.ts +1 -0
- package/dist/cli/generators/service/index.js +53 -0
- package/dist/cli/generators/service/index.js.map +1 -0
- package/dist/cli/generators/service/template.d.ts +1 -0
- package/dist/cli/generators/service/template.js +74 -0
- package/dist/cli/generators/service/template.js.map +1 -0
- package/dist/cli/generators/shared/index.d.ts +3 -0
- package/dist/cli/generators/shared/index.js +22 -0
- package/dist/cli/generators/shared/index.js.map +1 -0
- package/dist/cli/index.js +55 -96
- package/dist/cli/index.js.map +1 -1
- package/dist/lib/core/client/index.d.ts +13 -43
- package/dist/lib/core/client/index.js +70 -33
- package/dist/lib/core/client/index.js.map +1 -1
- package/dist/lib/core/client/types.d.ts +43 -0
- package/dist/lib/core/client/types.js +3 -0
- package/dist/lib/core/client/types.js.map +1 -0
- package/dist/lib/core/compiler/generator.d.ts +4 -6
- package/dist/lib/core/compiler/generator.js +190 -39
- package/dist/lib/core/compiler/generator.js.map +1 -1
- package/dist/lib/core/compiler/index.d.ts +2 -2
- package/dist/lib/core/compiler/index.js +2 -2
- package/dist/lib/core/compiler/index.js.map +1 -1
- package/dist/lib/core/compiler/pre-routes.d.ts +12 -130
- package/dist/lib/core/compiler/pre-routes.js +13 -35
- package/dist/lib/core/compiler/pre-routes.js.map +1 -1
- package/dist/lib/core/compiler/types.d.ts +25 -0
- package/dist/lib/core/decorators/context.d.ts +78 -8
- package/dist/lib/core/decorators/context.js +84 -9
- package/dist/lib/core/decorators/context.js.map +1 -1
- package/dist/lib/core/decorators/controller.js +2 -1
- package/dist/lib/core/decorators/controller.js.map +1 -1
- package/dist/lib/core/decorators/index.d.ts +1 -0
- package/dist/lib/core/decorators/index.js +1 -0
- package/dist/lib/core/decorators/index.js.map +1 -1
- package/dist/lib/core/decorators/methods.js +4 -3
- package/dist/lib/core/decorators/methods.js.map +1 -1
- package/dist/lib/core/decorators/middleware.d.ts +40 -2
- package/dist/lib/core/decorators/middleware.js +54 -9
- package/dist/lib/core/decorators/middleware.js.map +1 -1
- package/dist/lib/core/decorators/service.d.ts +28 -0
- package/dist/lib/core/decorators/service.js +36 -0
- package/dist/lib/core/decorators/service.js.map +1 -0
- package/dist/lib/core/decorators/statusCode.js +3 -1
- package/dist/lib/core/decorators/statusCode.js.map +1 -1
- package/dist/lib/core/decorators/swagger.js +4 -3
- package/dist/lib/core/decorators/swagger.js.map +1 -1
- package/dist/lib/core/exception/index.d.ts +68 -0
- package/dist/lib/core/exception/index.js +123 -0
- package/dist/lib/core/exception/index.js.map +1 -0
- package/dist/lib/core/metadata/index.d.ts +6 -0
- package/dist/lib/core/metadata/index.js +13 -0
- package/dist/lib/core/metadata/index.js.map +1 -0
- package/dist/lib/core/package/index.d.ts +11 -0
- package/dist/lib/core/package/index.js +42 -0
- package/dist/lib/core/package/index.js.map +1 -0
- package/dist/lib/core/server/fast-router.d.ts +2 -2
- package/dist/lib/core/server/fast-router.js +17 -3
- package/dist/lib/core/server/fast-router.js.map +1 -1
- package/dist/lib/core/server/index.d.ts +32 -5
- package/dist/lib/core/server/index.js +188 -45
- package/dist/lib/core/server/index.js.map +1 -1
- package/dist/lib/core/server/parser-factory.js +59 -10
- package/dist/lib/core/server/parser-factory.js.map +1 -1
- package/dist/lib/core/server/response.js +21 -1
- package/dist/lib/core/server/response.js.map +1 -1
- package/dist/lib/core/types/index.d.ts +42 -29
- package/package.json +15 -6
package/README.md
CHANGED
|
@@ -12,12 +12,20 @@ It is designed with a strong focus on developer experience and provides end-to-e
|
|
|
12
12
|
## Features
|
|
13
13
|
|
|
14
14
|
- ⚡ High-performance core built on native Node.js HTTP
|
|
15
|
-
- 🚀 Optional uWebSockets.js support
|
|
16
|
-
- 🧠 End-to-end (
|
|
17
|
-
-
|
|
15
|
+
- 🚀 Optional [uWebSockets.js](#adapter) adapter support for ultra-low latency and maximum throughput
|
|
16
|
+
- 🧠 End-to-end [E2E](#e2e) type safety across the entire request → response lifecycle
|
|
17
|
+
- 🎮 Built-in support for [Controllers](#controller) and route-based architecture
|
|
18
|
+
- 🏷️ Powerful Decorator system for [routes](#router), [middleware](#middleware), validation, and metadata
|
|
19
|
+
- 💉 Built-in constructor-based dependency injection (DI) for [Services](#service)
|
|
20
|
+
- 📦 [DTO](#dto) (Data Transfer Object) support for structured and type-safe request handling
|
|
21
|
+
- 📂 Built-in [File Upload](#file-upload) support via `useFileUpload()` with zero configuration required
|
|
22
|
+
- 🔌 Native [WebSocket](#web-socket) support for real-time applications and event-driven systems
|
|
23
|
+
- ⚛️ [GraphQL](#graphql) support with flexible schema integration and HTTP adapters
|
|
24
|
+
- 🖥️ Built-in [Cluster mode](#cluster) support for multi-core scalability and higher throughput
|
|
25
|
+
- 🧪 Built-in testing utilities for [E2E](#e2e) validation
|
|
18
26
|
- 🧩 Simple and intuitive developer experience
|
|
19
|
-
-
|
|
20
|
-
-
|
|
27
|
+
- 📘 Auto-generated [Swagger](#swagger) documentation via `app.useSwagger()` with zero manual configuration
|
|
28
|
+
- 🔥 Lightweight and optimized for high-performance APIs and microservices
|
|
21
29
|
|
|
22
30
|
---
|
|
23
31
|
|
|
@@ -36,7 +44,7 @@ See the [`docs`](https://thanathip41.github.io/tspace-spear) directory for full
|
|
|
36
44
|
|
|
37
45
|
## Basic Usage
|
|
38
46
|
- [Getting Started](#getting-started)
|
|
39
|
-
- [
|
|
47
|
+
- [Quick Started](#quick-started)
|
|
40
48
|
- [Adapter](#adapter)
|
|
41
49
|
- [Cluster](#cluster)
|
|
42
50
|
- [Global Prefix](#global-prefix)
|
|
@@ -51,12 +59,13 @@ See the [`docs`](https://thanathip41.github.io/tspace-spear) directory for full
|
|
|
51
59
|
- [Cookie](#cookie)
|
|
52
60
|
- [Middleware](#middleware)
|
|
53
61
|
- [Controller](#controller)
|
|
62
|
+
- [Service](#service)
|
|
54
63
|
- [Dto](#dto)
|
|
55
64
|
- [Router](#router)
|
|
56
65
|
- [Swagger](#swagger)
|
|
57
66
|
- [WebSocket](#websocket)
|
|
67
|
+
- [Graphql](#graphql)
|
|
58
68
|
- [E2E](#e2e)
|
|
59
|
-
- [Example CRUD](#example-crud)
|
|
60
69
|
|
|
61
70
|
## Getting Started
|
|
62
71
|
```js
|
|
@@ -72,29 +81,47 @@ new Spear()
|
|
|
72
81
|
.listen(8000 , () => console.log(`Server is now listening http://localhost:8000`))
|
|
73
82
|
```
|
|
74
83
|
|
|
75
|
-
##
|
|
84
|
+
## Quick Started
|
|
85
|
+
Generate applications, modules, controllers, services, and middleware with the Spear CLI.
|
|
76
86
|
```sh
|
|
77
87
|
# Install CLI globally
|
|
78
88
|
npm install -g tspace-spear
|
|
79
89
|
|
|
80
90
|
# Create a new application structure
|
|
81
|
-
|
|
91
|
+
spear create new my-app
|
|
82
92
|
|
|
83
|
-
|
|
84
|
-
|
|
93
|
+
✔ Successfully created project "my-app"
|
|
94
|
+
|
|
95
|
+
📦 Project Structure
|
|
85
96
|
|
|
86
|
-
# Generated structure
|
|
87
97
|
src/
|
|
88
|
-
├──
|
|
89
|
-
│ └──
|
|
90
|
-
│
|
|
98
|
+
├── common/
|
|
99
|
+
│ └── middlewares/
|
|
100
|
+
│ └── log.middleware.ts
|
|
101
|
+
│
|
|
102
|
+
├── modules/
|
|
103
|
+
│ └── cats/
|
|
104
|
+
│ ├── cat.controller.ts
|
|
105
|
+
│ ├── cat.service.ts
|
|
106
|
+
│ └── cat.dto.ts
|
|
107
|
+
│
|
|
108
|
+
├── client.ts
|
|
91
109
|
└── index.ts
|
|
92
110
|
|
|
93
|
-
|
|
111
|
+
🚀 Next Steps
|
|
112
|
+
|
|
113
|
+
cd my-app
|
|
114
|
+
|
|
115
|
+
npm run dev
|
|
94
116
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
117
|
+
✔ Server is running at:
|
|
118
|
+
http://localhost:8000
|
|
119
|
+
|
|
120
|
+
✔ Swagger Docs:
|
|
121
|
+
http://localhost:8000/api/docs
|
|
122
|
+
|
|
123
|
+
✔ Run E2E client:
|
|
124
|
+
ts-node src/client.ts
|
|
98
125
|
```
|
|
99
126
|
|
|
100
127
|
## Adapter
|
|
@@ -149,12 +176,31 @@ Global Prefix allows you to define a base path for all routes in your applicatio
|
|
|
149
176
|
It helps keep your API structured and consistent (e.g. /api, /v1, /app).
|
|
150
177
|
```js
|
|
151
178
|
const app = new Spear({
|
|
152
|
-
globalPrefix : '/api' // prefix all routes
|
|
179
|
+
globalPrefix : '/api', // prefix all routes
|
|
153
180
|
})
|
|
154
|
-
.get('/' , () => 'Hello world!')
|
|
181
|
+
.get('/' , () => 'Hello world!') // http://localhost:8000/api
|
|
182
|
+
.get('/cats' , () => `Hello all cats`) // http://localhost:8000/api/cats
|
|
183
|
+
.get('/cats/:id' , ({ params }) => `Hello cat: ${params.id}`) // http://localhost:8000/api/cats/1
|
|
155
184
|
.listen(8000 , () => console.log(`Server is now listening http://localhost:8000`))
|
|
156
185
|
|
|
157
186
|
// http://localhost:8000/api => 'Hello world!'
|
|
187
|
+
|
|
188
|
+
// Or this
|
|
189
|
+
|
|
190
|
+
const app = new Spear()
|
|
191
|
+
.useGlobalPrefix('api', {
|
|
192
|
+
exclude : [
|
|
193
|
+
{
|
|
194
|
+
path : '/cats/*',
|
|
195
|
+
// methods : '*'
|
|
196
|
+
// methods : ['GET','POST']
|
|
197
|
+
}
|
|
198
|
+
]
|
|
199
|
+
})
|
|
200
|
+
.get('/' , () => 'Hello world!') // http://localhost:8000/api
|
|
201
|
+
.get('/cats' , () => `Hello all cats`) // http://localhost:8000/cats
|
|
202
|
+
.get('/cats/:id' , ({ params }) => `Hello cat: ${params.id}`) // http://localhost:8000/cats/1
|
|
203
|
+
.listen(8000 , () => console.log(`Server is now listening http://localhost:8000`))
|
|
158
204
|
```
|
|
159
205
|
|
|
160
206
|
## Logger
|
|
@@ -574,14 +620,77 @@ import CatController from './cat-controller.ts'
|
|
|
574
620
|
})()
|
|
575
621
|
```
|
|
576
622
|
|
|
623
|
+
## Service
|
|
624
|
+
Registers one or more service classes for Dependency Injection.
|
|
625
|
+
|
|
626
|
+
All registered services will be available in the controller constructor
|
|
627
|
+
without manual instantiation.
|
|
628
|
+
```js
|
|
629
|
+
// cat-service.ts
|
|
630
|
+
class CatService {
|
|
631
|
+
public index () {
|
|
632
|
+
return [
|
|
633
|
+
{
|
|
634
|
+
id: 1,
|
|
635
|
+
name: 'cat1'
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
id: 2,
|
|
639
|
+
name: 'cat2'
|
|
640
|
+
}
|
|
641
|
+
]
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// cat-controller.ts
|
|
646
|
+
import {
|
|
647
|
+
Controller,
|
|
648
|
+
Get
|
|
649
|
+
} from 'tspace-spear';
|
|
650
|
+
import CatService from './cat-service.ts'
|
|
651
|
+
|
|
652
|
+
@Service([CatService]) // don't forgot this to send CatService for Dependency Injection(DI)
|
|
653
|
+
@Controller('/cats')
|
|
654
|
+
class CatController {
|
|
655
|
+
|
|
656
|
+
constructor(
|
|
657
|
+
private catService: CatService
|
|
658
|
+
) {}
|
|
659
|
+
|
|
660
|
+
@Get('/')
|
|
661
|
+
public index() {
|
|
662
|
+
return this.catService.index();
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// app.ts
|
|
667
|
+
import { Spear } from "tspace-spear";
|
|
668
|
+
|
|
669
|
+
(async () => {
|
|
670
|
+
|
|
671
|
+
const app = new Spear({
|
|
672
|
+
controllers : {
|
|
673
|
+
folder : `${__dirname}/controllers`,
|
|
674
|
+
name : /controller\.(ts|js)$/i,
|
|
675
|
+
preRouteTypes : true
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
app.useSwagger();
|
|
680
|
+
|
|
681
|
+
app.listen(8000 , () => console.log(`Server is now listening http://localhost:8000`));
|
|
682
|
+
})()
|
|
683
|
+
```
|
|
684
|
+
|
|
577
685
|
## Dto
|
|
578
686
|
DTO (Data Transfer Object) is used to validate and transform incoming request data before it reaches your controller logic.
|
|
579
687
|
```js
|
|
580
688
|
import {
|
|
581
689
|
Controller ,
|
|
582
690
|
Post,
|
|
583
|
-
createDtoDecorator,
|
|
584
691
|
Validate,
|
|
692
|
+
ValidateDto,
|
|
693
|
+
createDtoDecorator,
|
|
585
694
|
type T
|
|
586
695
|
} from 'tspace-spear';
|
|
587
696
|
|
|
@@ -593,8 +702,6 @@ import {
|
|
|
593
702
|
validate
|
|
594
703
|
} from "class-validator";
|
|
595
704
|
|
|
596
|
-
import { ClassConstructor, plainToInstance } from 'class-transformer';
|
|
597
|
-
|
|
598
705
|
const ValidateDtoCustomBody = (keys: string[]) => {
|
|
599
706
|
return createDtoDecorator((ctx) => {
|
|
600
707
|
const body = ctx.body ?? {};
|
|
@@ -620,13 +727,6 @@ const ValidateDtoCustomBody = (keys: string[]) => {
|
|
|
620
727
|
});
|
|
621
728
|
}
|
|
622
729
|
|
|
623
|
-
const ValidateDtoZodBody = (schema: z.ZodTypeAny) => {
|
|
624
|
-
return createDtoDecorator((ctx) => {
|
|
625
|
-
const result = schema.parse(ctx.body);
|
|
626
|
-
ctx.body = result as T.Body;
|
|
627
|
-
});
|
|
628
|
-
}
|
|
629
|
-
|
|
630
730
|
const ValidateDtoPromiseBody = (keys: string[]) => {
|
|
631
731
|
return createDtoDecorator(async (ctx) => {
|
|
632
732
|
await new Promise(resolve => setTimeout(resolve,500));
|
|
@@ -646,39 +746,6 @@ const ValidateDtoPromiseBody = (keys: string[]) => {
|
|
|
646
746
|
});
|
|
647
747
|
}
|
|
648
748
|
|
|
649
|
-
const ValidateDtoClsBody = <T extends object>(
|
|
650
|
-
cls: ClassConstructor<T>
|
|
651
|
-
) => {
|
|
652
|
-
return createDtoDecorator(async (ctx) => {
|
|
653
|
-
|
|
654
|
-
const dto = plainToInstance(
|
|
655
|
-
cls,
|
|
656
|
-
ctx.body
|
|
657
|
-
);
|
|
658
|
-
|
|
659
|
-
const errors = await validate(dto);
|
|
660
|
-
|
|
661
|
-
if (errors.length > 0) {
|
|
662
|
-
throw {
|
|
663
|
-
message: "Validation failed",
|
|
664
|
-
issues: errors.flatMap((error) => {
|
|
665
|
-
const constraints = error.constraints ?? {};
|
|
666
|
-
|
|
667
|
-
const firstError = Object.values(constraints)[0] ?? "Validation error";
|
|
668
|
-
|
|
669
|
-
return {
|
|
670
|
-
path: error.property,
|
|
671
|
-
message: firstError,
|
|
672
|
-
};
|
|
673
|
-
})
|
|
674
|
-
};
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
ctx.body = dto;
|
|
678
|
-
}
|
|
679
|
-
);
|
|
680
|
-
};
|
|
681
|
-
|
|
682
749
|
const catSchema = z.object({
|
|
683
750
|
name: z.string(),
|
|
684
751
|
age: z.number(),
|
|
@@ -692,7 +759,6 @@ class CreateCatDto {
|
|
|
692
759
|
age!: number;
|
|
693
760
|
}
|
|
694
761
|
|
|
695
|
-
|
|
696
762
|
// file cat-controller.ts
|
|
697
763
|
@Controller('/cats')
|
|
698
764
|
export class CatController {
|
|
@@ -701,47 +767,40 @@ export class CatController {
|
|
|
701
767
|
// only required validation without type checking
|
|
702
768
|
@Validate(["name", "age"], { required: { allowEmptyString: false, allowNull: false } })
|
|
703
769
|
public async basic(ctx : T.Context<{ body : { name : any , age : any }}>) {
|
|
704
|
-
const body = ctx.body;
|
|
705
770
|
return {
|
|
706
|
-
body
|
|
771
|
+
body : ctx.body
|
|
707
772
|
}
|
|
708
773
|
}
|
|
709
774
|
|
|
710
|
-
@Post('/')
|
|
775
|
+
@Post('/custom')
|
|
711
776
|
@ValidateDtoCustomBody(["name", "age"])
|
|
712
777
|
public async custom(ctx : T.Context<{ body : { name : string , age : number }}>) {
|
|
713
|
-
const body = ctx.body;
|
|
714
|
-
return {
|
|
715
|
-
body
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
@Post('/zod')
|
|
720
|
-
@ValidateDtoZodBody(catSchema)
|
|
721
|
-
public async zod(ctx : T.Context<z.infer<typeof catSchema>>) {
|
|
722
|
-
const body = ctx.body;
|
|
723
778
|
return {
|
|
724
|
-
body
|
|
779
|
+
body : ctx.body
|
|
725
780
|
}
|
|
726
781
|
}
|
|
727
782
|
|
|
728
783
|
@Post('/promise')
|
|
729
784
|
@ValidateDtoPromiseBody(['name'])
|
|
730
785
|
public async promise(ctx : T.Context<{ body : { name : string }}>) {
|
|
731
|
-
|
|
786
|
+
return {
|
|
787
|
+
body : ctx.body
|
|
788
|
+
}
|
|
789
|
+
}
|
|
732
790
|
|
|
791
|
+
@Post('/zod')
|
|
792
|
+
@ValidateDto(catSchema, { adaptor : "zod" })
|
|
793
|
+
public async zod(ctx : T.Context<{ body : z.infer<typeof catSchema>}>) {
|
|
733
794
|
return {
|
|
734
|
-
body
|
|
795
|
+
body : ctx.body
|
|
735
796
|
}
|
|
736
797
|
}
|
|
737
798
|
|
|
738
799
|
@Post('/cls')
|
|
739
|
-
@
|
|
800
|
+
@ValidateDto(CreateCatDto)
|
|
740
801
|
public async cls(ctx : T.Context<{ body : CreateCatDto }>) {
|
|
741
|
-
const body = ctx.body;
|
|
742
|
-
|
|
743
802
|
return {
|
|
744
|
-
body
|
|
803
|
+
body : ctx.body
|
|
745
804
|
}
|
|
746
805
|
}
|
|
747
806
|
}
|
|
@@ -1112,6 +1171,163 @@ new Spear()
|
|
|
1112
1171
|
|
|
1113
1172
|
```
|
|
1114
1173
|
|
|
1174
|
+
## Graphql
|
|
1175
|
+
GraphQL CRUD Example with graphql-yoga + tspace-spear
|
|
1176
|
+
|
|
1177
|
+
This example shows how to build a simple GraphQL CRUD API using graphql-yoga and tspace-spear.
|
|
1178
|
+
|
|
1179
|
+
It includes:
|
|
1180
|
+
|
|
1181
|
+
- GraphQL schema setup
|
|
1182
|
+
- Query and Mutation examples
|
|
1183
|
+
- Create / Read / Update / Delete operations
|
|
1184
|
+
- HTTP integration with graphql-yoga
|
|
1185
|
+
|
|
1186
|
+
The server uses an in-memory array as a fake database for simplicity.
|
|
1187
|
+
|
|
1188
|
+
Features
|
|
1189
|
+
- High performance HTTP server with tspace-spear
|
|
1190
|
+
- Native GraphQL schema definitions
|
|
1191
|
+
- Full CRUD operations
|
|
1192
|
+
- Simple and dependency-light setup
|
|
1193
|
+
- Works with standard GraphQL clients and tools
|
|
1194
|
+
|
|
1195
|
+
```sh
|
|
1196
|
+
npm install graphql graphql-yoga
|
|
1197
|
+
```
|
|
1198
|
+
|
|
1199
|
+
```js
|
|
1200
|
+
import { type T, Spear } from "tspace-spear";
|
|
1201
|
+
|
|
1202
|
+
import {
|
|
1203
|
+
createYoga,
|
|
1204
|
+
createSchema,
|
|
1205
|
+
} from 'graphql-yoga';
|
|
1206
|
+
|
|
1207
|
+
type User = {
|
|
1208
|
+
id: number;
|
|
1209
|
+
name: string;
|
|
1210
|
+
email: string;
|
|
1211
|
+
};
|
|
1212
|
+
|
|
1213
|
+
const users: User[] = [
|
|
1214
|
+
{
|
|
1215
|
+
id: 1,
|
|
1216
|
+
name: 'John',
|
|
1217
|
+
email: 'john@gmail.com',
|
|
1218
|
+
},
|
|
1219
|
+
];
|
|
1220
|
+
|
|
1221
|
+
const yoga = createYoga({
|
|
1222
|
+
graphqlEndpoint: '/graphql',
|
|
1223
|
+
|
|
1224
|
+
schema: createSchema({
|
|
1225
|
+
typeDefs: /* GraphQL */ `
|
|
1226
|
+
type User {
|
|
1227
|
+
id: Int!
|
|
1228
|
+
name: String!
|
|
1229
|
+
email: String!
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
input CreateUserInput {
|
|
1233
|
+
name: String!
|
|
1234
|
+
email: String!
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
input UpdateUserInput {
|
|
1238
|
+
id: Int!
|
|
1239
|
+
name: String
|
|
1240
|
+
email: String
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
type Query {
|
|
1244
|
+
users: [User!]!
|
|
1245
|
+
user(id: Int!): User
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
type Mutation {
|
|
1249
|
+
createUser(input: CreateUserInput!): User!
|
|
1250
|
+
updateUser(input: UpdateUserInput!): User!
|
|
1251
|
+
removeUser(id: Int!): User!
|
|
1252
|
+
}
|
|
1253
|
+
`,
|
|
1254
|
+
|
|
1255
|
+
resolvers: {
|
|
1256
|
+
Query: {
|
|
1257
|
+
users: () => users,
|
|
1258
|
+
|
|
1259
|
+
user: (_root, args) => {
|
|
1260
|
+
return users.find(
|
|
1261
|
+
(u) => u.id === args.id,
|
|
1262
|
+
);
|
|
1263
|
+
},
|
|
1264
|
+
},
|
|
1265
|
+
|
|
1266
|
+
Mutation: {
|
|
1267
|
+
createUser: (_root, args) => {
|
|
1268
|
+
const user = {
|
|
1269
|
+
id: Date.now(),
|
|
1270
|
+
name: args.input.name,
|
|
1271
|
+
email: args.input.email,
|
|
1272
|
+
};
|
|
1273
|
+
|
|
1274
|
+
users.push(user);
|
|
1275
|
+
|
|
1276
|
+
return user;
|
|
1277
|
+
},
|
|
1278
|
+
|
|
1279
|
+
updateUser: (_root, args) => {
|
|
1280
|
+
const user = users.find(
|
|
1281
|
+
(u) => u.id === args.input.id,
|
|
1282
|
+
);
|
|
1283
|
+
|
|
1284
|
+
if (!user) {
|
|
1285
|
+
throw new Error(
|
|
1286
|
+
'User not found',
|
|
1287
|
+
);
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
if (args.input.name !== undefined) {
|
|
1291
|
+
user.name = args.input.name;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
if (args.input.email !== undefined) {
|
|
1295
|
+
user.email = args.input.email;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
return user;
|
|
1299
|
+
},
|
|
1300
|
+
|
|
1301
|
+
removeUser: (_root, args) => {
|
|
1302
|
+
const index = users.findIndex(
|
|
1303
|
+
(u) => u.id === args.id,
|
|
1304
|
+
);
|
|
1305
|
+
|
|
1306
|
+
if (index === -1) {
|
|
1307
|
+
throw new Error(
|
|
1308
|
+
'User not found',
|
|
1309
|
+
);
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
const deleted = users[index];
|
|
1313
|
+
|
|
1314
|
+
users.splice(index, 1);
|
|
1315
|
+
|
|
1316
|
+
return deleted;
|
|
1317
|
+
},
|
|
1318
|
+
},
|
|
1319
|
+
},
|
|
1320
|
+
}),
|
|
1321
|
+
});
|
|
1322
|
+
|
|
1323
|
+
const app = new Spear()
|
|
1324
|
+
.get('/graphql',({ req , res }) => yoga(req , res))
|
|
1325
|
+
.post('/graphql',({ req , res }) => yoga(req , res))
|
|
1326
|
+
.listen(4000 , ({ port , server }) => {
|
|
1327
|
+
console.log(`server listening on : http://localhost:${port}/graphql`)
|
|
1328
|
+
})
|
|
1329
|
+
```
|
|
1330
|
+
|
|
1115
1331
|
## E2E
|
|
1116
1332
|
Provides end-to-end type safety and testing support across the full request lifecycle, from request input to
|
|
1117
1333
|
response output. It allows you to:
|
|
@@ -1171,7 +1387,7 @@ class CatController {
|
|
|
1171
1387
|
const cat = cats.find((d) => d.id === Number(params.id));
|
|
1172
1388
|
|
|
1173
1389
|
if(cat == null) {
|
|
1174
|
-
|
|
1390
|
+
throw res.notFound('not found cat')
|
|
1175
1391
|
}
|
|
1176
1392
|
|
|
1177
1393
|
return {
|
|
@@ -1213,7 +1429,7 @@ class CatController {
|
|
|
1213
1429
|
const index = cats.findIndex((d) => d.id === id);
|
|
1214
1430
|
|
|
1215
1431
|
if (index === -1) {
|
|
1216
|
-
|
|
1432
|
+
throw res.notFound('not found cat')
|
|
1217
1433
|
}
|
|
1218
1434
|
|
|
1219
1435
|
cats[index] = {
|
|
@@ -1256,7 +1472,7 @@ import Spear from "tspace-spear";
|
|
|
1256
1472
|
const app = new Spear({
|
|
1257
1473
|
logger : true,
|
|
1258
1474
|
controllers: {
|
|
1259
|
-
folder : `${__dirname}/controllers
|
|
1475
|
+
folder : `${__dirname}/controllers/*`,
|
|
1260
1476
|
name:/controller\.(ts|js)$/i,
|
|
1261
1477
|
// don't forget to set this option for auto-generate route metadata for type-safe E2E usage,
|
|
1262
1478
|
// and swagger documentation. By default if use .useSwagger() in app no need to set any description
|
|
@@ -1280,79 +1496,55 @@ const client: ApiClient<AppRouter> = new ApiClient(
|
|
|
1280
1496
|
`http://localhost:8000/api`
|
|
1281
1497
|
);
|
|
1282
1498
|
|
|
1283
|
-
|
|
1499
|
+
await client.get("/catsq"); ❌ // Type error: Argument of type '"/catsq"' is not assignable to parameter of type '"/cats" | "/cats/:id" | ... 3 more
|
|
1284
1500
|
const res = await client.get("/cats");
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
## Example CRUD
|
|
1297
|
-
```js
|
|
1298
|
-
import { Spear } from "tspace-spear";
|
|
1299
|
-
|
|
1300
|
-
const spears = [
|
|
1301
|
-
{
|
|
1302
|
-
id : 1,
|
|
1303
|
-
damage : 100
|
|
1304
|
-
},
|
|
1305
|
-
{
|
|
1306
|
-
id : 2,
|
|
1307
|
-
damage : 75
|
|
1308
|
-
},
|
|
1309
|
-
{
|
|
1310
|
-
id : 3,
|
|
1311
|
-
damage : 50
|
|
1312
|
-
}
|
|
1313
|
-
]
|
|
1314
|
-
|
|
1315
|
-
new Spear()
|
|
1316
|
-
// enable body payload
|
|
1317
|
-
.useBodyParser()
|
|
1318
|
-
.get('/' , () => spears)
|
|
1319
|
-
.get('/:id' , ({ params }) => spears.find(spear => spear.id === Number(params.id ?? 0)))
|
|
1320
|
-
.post('/' , ({ body }) => {
|
|
1321
|
-
// please validation the your body
|
|
1322
|
-
const damage = Number(body.damage ?? (Math.random() * 100).toFixed(0))
|
|
1323
|
-
|
|
1324
|
-
const id = spears.reduce((max, spear) => spear.id > max ? spear.id : max, 0) + 1
|
|
1325
|
-
|
|
1326
|
-
spears.push({ id , damage })
|
|
1327
|
-
|
|
1328
|
-
return spears.find(spear => spear.id === id)
|
|
1329
|
-
})
|
|
1330
|
-
.patch('/:id' , ({ params , body , res }) => {
|
|
1331
|
-
|
|
1332
|
-
const damage = Number(body.damage ?? (Math.random() * 100).toFixed(0))
|
|
1333
|
-
|
|
1334
|
-
const id = Number(params.id)
|
|
1335
|
-
|
|
1336
|
-
const spear = spears.find(spear => spear.id === id)
|
|
1337
|
-
|
|
1338
|
-
if (spear == null) return res.status(404).json({ message : 'Spear not found'})
|
|
1501
|
+
// res.ok -> true or false
|
|
1502
|
+
// res.status -> number
|
|
1503
|
+
// res.headers -> Hearders
|
|
1504
|
+
// res.data -> { cats: [{ id: 1, name: 'cat1', age: 1.6 },{ id: 2, name: 'cat2', age: 1.8 }] }
|
|
1505
|
+
|
|
1506
|
+
// Without checking `res.ok`, `res.data` is always typed as `any`.
|
|
1507
|
+
if(res.ok) {
|
|
1508
|
+
res.data.cats = 1 ❌ // Type error: Type 'number' is not assignable to type '{ id: number; name: string; age: number; }[]'
|
|
1509
|
+
res.data.cats[0].name = 1 ❌ // Type error: Type 'number' is not assignable to type 'string'
|
|
1510
|
+
res.data.cats[0].age = "1.6" ❌ // Type error: Type 'string' is not assignable to type 'number'
|
|
1511
|
+
}
|
|
1339
1512
|
|
|
1340
|
-
|
|
1513
|
+
await client.get("/cats/:id") ❌ // Expected 2 arguments, but got 1.
|
|
1514
|
+
await client.get("/cats/:id", { params : { id : "1" }}) ❌
|
|
1515
|
+
// The expected type comes from property 'id' which is declared here on type '{ id: number; }'
|
|
1516
|
+
await client.get("/cats/:id", { params : { id : 1 }}) ✅
|
|
1341
1517
|
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1518
|
+
await client.post("/cats") ❌ // Expected 2 arguments, but got 1.
|
|
1519
|
+
await client.post("/cats",{
|
|
1520
|
+
body : {} ❌ // Type '{}' is missing the following properties from type '{ name: string; age: number; }'
|
|
1521
|
+
});
|
|
1522
|
+
await client.post("/cats",{
|
|
1523
|
+
body : { name : "super cat" } ❌
|
|
1524
|
+
// Property 'age' is missing in type '{ name: string; }'
|
|
1525
|
+
// but required in type '{ name: string; age: number; }'.
|
|
1526
|
+
});
|
|
1527
|
+
await client.post("/cats",{
|
|
1528
|
+
body : { name : "super cat" , age : 5 } ✅
|
|
1529
|
+
});
|
|
1345
1530
|
|
|
1346
|
-
|
|
1531
|
+
await client.put("/cats") ✅
|
|
1347
1532
|
|
|
1348
|
-
|
|
1533
|
+
await client.put("/cats",{
|
|
1534
|
+
body : {} ✅
|
|
1535
|
+
});
|
|
1349
1536
|
|
|
1350
|
-
|
|
1537
|
+
await client.put("/cats",{
|
|
1538
|
+
body : { name : 1 } ❌ // Type 'number' is not assignable to type 'string'.
|
|
1539
|
+
});
|
|
1351
1540
|
|
|
1352
|
-
|
|
1541
|
+
await client.put("/cats",{
|
|
1542
|
+
body : { name : "super cat" } ✅
|
|
1543
|
+
});
|
|
1353
1544
|
|
|
1354
|
-
|
|
1355
|
-
}
|
|
1356
|
-
|
|
1545
|
+
await client.put("/cats",{
|
|
1546
|
+
body : { name : "super cat" , age : 5 } ✅
|
|
1547
|
+
});
|
|
1548
|
+
|
|
1549
|
+
```
|
|
1357
1550
|
|
|
1358
|
-
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function createApp(inputPath?: string): Promise<void>;
|