tspace-spear 1.2.9 โ 1.3.0
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 +342 -200
- package/dist/cli/generators/app/index.js +1 -1
- package/dist/cli/generators/app/index.js.map +1 -1
- package/dist/cli/generators/controller/template.d.ts +1 -1
- package/dist/cli/generators/controller/template.js +11 -9
- package/dist/cli/generators/controller/template.js.map +1 -1
- package/dist/cli/index.js +10 -8
- package/dist/cli/index.js.map +1 -1
- package/dist/lib/core/client/index.js +18 -6
- package/dist/lib/core/client/index.js.map +1 -1
- package/dist/lib/core/client/types.d.ts +4 -3
- package/dist/lib/core/compiler/generator.js +8 -4
- package/dist/lib/core/compiler/generator.js.map +1 -1
- package/dist/lib/core/compiler/pre-routes.js +10 -2
- package/dist/lib/core/compiler/pre-routes.js.map +1 -1
- package/dist/lib/core/const/index.d.ts +12 -0
- package/dist/lib/core/const/index.js +6 -1
- package/dist/lib/core/const/index.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/headers.js +1 -0
- package/dist/lib/core/decorators/headers.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 +46 -2
- package/dist/lib/core/decorators/middleware.js +100 -15
- 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.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/server/fast-router.d.ts +5 -5
- package/dist/lib/core/server/fast-router.js +2 -2
- package/dist/lib/core/server/fast-router.js.map +1 -1
- package/dist/lib/core/server/http/index.d.ts +22 -0
- package/dist/lib/core/server/http/index.js +220 -0
- package/dist/lib/core/server/http/index.js.map +1 -0
- package/dist/lib/core/server/index.d.ts +35 -7
- package/dist/lib/core/server/index.js +252 -75
- package/dist/lib/core/server/index.js.map +1 -1
- package/dist/lib/core/server/net/index.d.ts +13 -8
- package/dist/lib/core/server/net/index.js +2 -2
- package/dist/lib/core/server/net/index.js.map +1 -1
- package/dist/lib/core/server/parser-factory.d.ts +3 -4
- package/dist/lib/core/server/parser-factory.js +52 -143
- package/dist/lib/core/server/parser-factory.js.map +1 -1
- package/dist/lib/core/server/response.d.ts +1 -2
- package/dist/lib/core/server/response.js +173 -171
- package/dist/lib/core/server/response.js.map +1 -1
- package/dist/lib/core/server/uWS/index.d.ts +2 -3
- package/dist/lib/core/server/uWS/index.js +15 -10
- package/dist/lib/core/server/uWS/index.js.map +1 -1
- package/dist/lib/core/types/index.d.ts +25 -8
- package/dist/lib/core/utils/index.d.ts +3 -3
- package/dist/lib/core/utils/index.js +19 -16
- package/dist/lib/core/utils/index.js.map +1 -1
- package/package.json +35 -12
package/README.md
CHANGED
|
@@ -15,12 +15,13 @@ It is designed with a strong focus on developer experience and provides end-to-e
|
|
|
15
15
|
- ๐ Optional [uWebSockets.js](#adapter) adapter support for ultra-low latency and maximum throughput
|
|
16
16
|
- ๐ง End-to-end [E2E](#e2e) type safety across the entire request โ response lifecycle
|
|
17
17
|
- ๐ฎ Built-in support for [Controllers](#controller) and route-based architecture
|
|
18
|
-
- ๐ท๏ธ Powerful Decorator system for routes, middleware, validation, and metadata
|
|
18
|
+
- ๐ท๏ธ Powerful Decorator system for [routes](#router), [middleware](#middleware), validation, and metadata
|
|
19
|
+
- ๐ Built-in constructor-based dependency injection (DI) for [Services](#service)
|
|
19
20
|
- ๐ฆ [DTO](#dto) (Data Transfer Object) support for structured and type-safe request handling
|
|
20
21
|
- ๐ Built-in [File Upload](#file-upload) support via `useFileUpload()` with zero configuration required
|
|
21
22
|
- ๐ Native [WebSocket](#web-socket) support for real-time applications and event-driven systems
|
|
22
23
|
- โ๏ธ [GraphQL](#graphql) support with flexible schema integration and HTTP adapters
|
|
23
|
-
- ๐ฅ๏ธ Built-in [
|
|
24
|
+
- ๐ฅ๏ธ Built-in [Cluster mode](#cluster) support for multi-core scalability and higher throughput
|
|
24
25
|
- ๐งช Built-in testing utilities for [E2E](#e2e) validation
|
|
25
26
|
- ๐งฉ Simple and intuitive developer experience
|
|
26
27
|
- ๐ Auto-generated [Swagger](#swagger) documentation via `app.useSwagger()` with zero manual configuration
|
|
@@ -58,8 +59,11 @@ See the [`docs`](https://thanathip41.github.io/tspace-spear) directory for full
|
|
|
58
59
|
- [Cookie](#cookie)
|
|
59
60
|
- [Middleware](#middleware)
|
|
60
61
|
- [Controller](#controller)
|
|
62
|
+
- [Service](#service)
|
|
63
|
+
- [Exception](#exception)
|
|
61
64
|
- [Dto](#dto)
|
|
62
65
|
- [Router](#router)
|
|
66
|
+
- [Serve Static Files](#serve-static-files)
|
|
63
67
|
- [Swagger](#swagger)
|
|
64
68
|
- [WebSocket](#websocket)
|
|
65
69
|
- [Graphql](#graphql)
|
|
@@ -120,8 +124,6 @@ npm run dev
|
|
|
120
124
|
|
|
121
125
|
โ Run E2E client:
|
|
122
126
|
ts-node src/client.ts
|
|
123
|
-
|
|
124
|
-
ts-node src/client.ts // for E2E
|
|
125
127
|
```
|
|
126
128
|
|
|
127
129
|
## Adapter
|
|
@@ -176,12 +178,31 @@ Global Prefix allows you to define a base path for all routes in your applicatio
|
|
|
176
178
|
It helps keep your API structured and consistent (e.g. /api, /v1, /app).
|
|
177
179
|
```js
|
|
178
180
|
const app = new Spear({
|
|
179
|
-
globalPrefix : '/api' // prefix all routes
|
|
181
|
+
globalPrefix : '/api', // prefix all routes
|
|
180
182
|
})
|
|
181
|
-
.get('/' , () => 'Hello world!')
|
|
183
|
+
.get('/' , () => 'Hello world!') // http://localhost:8000/api
|
|
184
|
+
.get('/cats' , () => `Hello all cats`) // http://localhost:8000/api/cats
|
|
185
|
+
.get('/cats/:id' , ({ params }) => `Hello cat: ${params.id}`) // http://localhost:8000/api/cats/1
|
|
182
186
|
.listen(8000 , () => console.log(`Server is now listening http://localhost:8000`))
|
|
183
187
|
|
|
184
188
|
// http://localhost:8000/api => 'Hello world!'
|
|
189
|
+
|
|
190
|
+
// Or this
|
|
191
|
+
|
|
192
|
+
const app = new Spear()
|
|
193
|
+
.useGlobalPrefix('api', {
|
|
194
|
+
exclude : [
|
|
195
|
+
{
|
|
196
|
+
path : '/cats/*',
|
|
197
|
+
// methods : '*'
|
|
198
|
+
// methods : ['GET','POST']
|
|
199
|
+
}
|
|
200
|
+
]
|
|
201
|
+
})
|
|
202
|
+
.get('/' , () => 'Hello world!') // http://localhost:8000/api
|
|
203
|
+
.get('/cats' , () => `Hello all cats`) // http://localhost:8000/cats
|
|
204
|
+
.get('/cats/:id' , ({ params }) => `Hello cat: ${params.id}`) // http://localhost:8000/cats/1
|
|
205
|
+
.listen(8000 , () => console.log(`Server is now listening http://localhost:8000`))
|
|
185
206
|
```
|
|
186
207
|
|
|
187
208
|
## Logger
|
|
@@ -601,6 +622,142 @@ import CatController from './cat-controller.ts'
|
|
|
601
622
|
})()
|
|
602
623
|
```
|
|
603
624
|
|
|
625
|
+
## Service
|
|
626
|
+
Registers one or more service classes for Dependency Injection.
|
|
627
|
+
|
|
628
|
+
All registered services will be available in the controller constructor
|
|
629
|
+
without manual instantiation.
|
|
630
|
+
```js
|
|
631
|
+
// cat-service.ts
|
|
632
|
+
class CatService {
|
|
633
|
+
public index () {
|
|
634
|
+
return [
|
|
635
|
+
{
|
|
636
|
+
id: 1,
|
|
637
|
+
name: 'cat1'
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
id: 2,
|
|
641
|
+
name: 'cat2'
|
|
642
|
+
}
|
|
643
|
+
]
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// cat-controller.ts
|
|
648
|
+
import {
|
|
649
|
+
Controller,
|
|
650
|
+
Get
|
|
651
|
+
} from 'tspace-spear';
|
|
652
|
+
import CatService from './cat-service.ts'
|
|
653
|
+
|
|
654
|
+
@Service([CatService]) // don't forgot this to send CatService for Dependency Injection(DI)
|
|
655
|
+
@Controller('/cats')
|
|
656
|
+
class CatController {
|
|
657
|
+
|
|
658
|
+
constructor(
|
|
659
|
+
private catService: CatService
|
|
660
|
+
) {}
|
|
661
|
+
|
|
662
|
+
@Get('/')
|
|
663
|
+
public index() {
|
|
664
|
+
return this.catService.index();
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// app.ts
|
|
669
|
+
import { Spear } from "tspace-spear";
|
|
670
|
+
|
|
671
|
+
(async () => {
|
|
672
|
+
|
|
673
|
+
const app = new Spear({
|
|
674
|
+
controllers : {
|
|
675
|
+
folder : `${__dirname}/controllers`,
|
|
676
|
+
name : /controller\.(ts|js)$/i,
|
|
677
|
+
preRouteTypes : true
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
app.useSwagger();
|
|
682
|
+
|
|
683
|
+
app.listen(8000 , () => console.log(`Server is now listening http://localhost:8000`));
|
|
684
|
+
})()
|
|
685
|
+
```
|
|
686
|
+
## Exception
|
|
687
|
+
Exceptions are used to return HTTP errors from your application.
|
|
688
|
+
```js
|
|
689
|
+
import {
|
|
690
|
+
BadRequestException,
|
|
691
|
+
UnauthorizedException,
|
|
692
|
+
ForbiddenException,
|
|
693
|
+
NotFoundException,
|
|
694
|
+
MethodNotAllowedException,
|
|
695
|
+
ConflictException,
|
|
696
|
+
GoneException,
|
|
697
|
+
UnsupportedMediaTypeException,
|
|
698
|
+
UnprocessableEntityException,
|
|
699
|
+
TooManyRequestsException,
|
|
700
|
+
InternalServerErrorException,
|
|
701
|
+
NotImplementedException,
|
|
702
|
+
BadGatewayException,
|
|
703
|
+
ServiceUnavailableException,
|
|
704
|
+
GatewayTimeoutException,
|
|
705
|
+
} from 'tspace-spear/exception';
|
|
706
|
+
|
|
707
|
+
@Controller('/users')
|
|
708
|
+
class UserController {
|
|
709
|
+
@Get('/:id')
|
|
710
|
+
public async show({ params }) {
|
|
711
|
+
|
|
712
|
+
if (!params.id) {
|
|
713
|
+
throw new BadRequestException('User id is required');
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
return {
|
|
717
|
+
id: params.id,
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
### Exception
|
|
724
|
+
Exceptions are used to return HTTP errors from your application.
|
|
725
|
+
```js
|
|
726
|
+
import {
|
|
727
|
+
BadRequestException,
|
|
728
|
+
UnauthorizedException,
|
|
729
|
+
ForbiddenException,
|
|
730
|
+
NotFoundException,
|
|
731
|
+
MethodNotAllowedException,
|
|
732
|
+
ConflictException,
|
|
733
|
+
GoneException,
|
|
734
|
+
UnsupportedMediaTypeException,
|
|
735
|
+
UnprocessableEntityException,
|
|
736
|
+
TooManyRequestsException,
|
|
737
|
+
InternalServerErrorException,
|
|
738
|
+
NotImplementedException,
|
|
739
|
+
BadGatewayException,
|
|
740
|
+
ServiceUnavailableException,
|
|
741
|
+
GatewayTimeoutException,
|
|
742
|
+
} from 'tspace-spear/exception';
|
|
743
|
+
|
|
744
|
+
@Controller('/users')
|
|
745
|
+
class UserController {
|
|
746
|
+
@Get('/:id')
|
|
747
|
+
public async show({ params }) {
|
|
748
|
+
|
|
749
|
+
if (!params.id) {
|
|
750
|
+
throw new BadRequestException('User id is required');
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
return {
|
|
754
|
+
id: params.id,
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
```
|
|
760
|
+
|
|
604
761
|
## Dto
|
|
605
762
|
DTO (Data Transfer Object) is used to validate and transform incoming request data before it reaches your controller logic.
|
|
606
763
|
```js
|
|
@@ -794,6 +951,33 @@ app.listen(port , () => console.log(`Server is now listening http://localhost:80
|
|
|
794
951
|
|
|
795
952
|
```
|
|
796
953
|
|
|
954
|
+
## Serve Static Files
|
|
955
|
+
Serve files directly from a local directory.
|
|
956
|
+
```js
|
|
957
|
+
|
|
958
|
+
const path = require('path');
|
|
959
|
+
|
|
960
|
+
new Spear({
|
|
961
|
+
logger: true
|
|
962
|
+
})
|
|
963
|
+
.get('/file/*', (ctx) => {
|
|
964
|
+
// Maps:
|
|
965
|
+
// /file/example.pdf
|
|
966
|
+
// -> ./example/example.pdf
|
|
967
|
+
const filePath = path.join(
|
|
968
|
+
path.resolve(),
|
|
969
|
+
'example',
|
|
970
|
+
String(ctx.params['*'])
|
|
971
|
+
);
|
|
972
|
+
|
|
973
|
+
return ctx.res.serveMedia(filePath);
|
|
974
|
+
})
|
|
975
|
+
.listen(3000 , ({ port }) => {
|
|
976
|
+
console.log(`server listening on : http://localhost:${port}`)
|
|
977
|
+
})
|
|
978
|
+
|
|
979
|
+
```
|
|
980
|
+
|
|
797
981
|
## Swagger
|
|
798
982
|
Provides built-in Swagger support to document your API endpoints.
|
|
799
983
|
|
|
@@ -1091,17 +1275,16 @@ new Spear()
|
|
|
1091
1275
|
```
|
|
1092
1276
|
|
|
1093
1277
|
## Graphql
|
|
1094
|
-
GraphQL CRUD Example with graphql-
|
|
1278
|
+
GraphQL CRUD Example with graphql-yoga + tspace-spear
|
|
1095
1279
|
|
|
1096
|
-
This example shows how to build a simple GraphQL CRUD API using graphql-
|
|
1280
|
+
This example shows how to build a simple GraphQL CRUD API using graphql-yoga and tspace-spear.
|
|
1097
1281
|
|
|
1098
1282
|
It includes:
|
|
1099
1283
|
|
|
1100
1284
|
- GraphQL schema setup
|
|
1101
1285
|
- Query and Mutation examples
|
|
1102
1286
|
- Create / Read / Update / Delete operations
|
|
1103
|
-
- HTTP integration with graphql-
|
|
1104
|
-
- cURL testing examples
|
|
1287
|
+
- HTTP integration with graphql-yoga
|
|
1105
1288
|
|
|
1106
1289
|
The server uses an in-memory array as a fake database for simplicity.
|
|
1107
1290
|
|
|
@@ -1113,221 +1296,141 @@ Features
|
|
|
1113
1296
|
- Works with standard GraphQL clients and tools
|
|
1114
1297
|
|
|
1115
1298
|
```sh
|
|
1116
|
-
npm install graphql graphql-
|
|
1299
|
+
npm install graphql graphql-yoga
|
|
1117
1300
|
```
|
|
1118
1301
|
|
|
1119
1302
|
```js
|
|
1303
|
+
import { type T, Spear } from "tspace-spear";
|
|
1120
1304
|
|
|
1121
1305
|
import {
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
import { type T, Spear } from "tspace-spear";
|
|
1306
|
+
createYoga,
|
|
1307
|
+
createSchema,
|
|
1308
|
+
} from 'graphql-yoga';
|
|
1309
|
+
|
|
1310
|
+
type User = {
|
|
1311
|
+
id: number;
|
|
1312
|
+
name: string;
|
|
1313
|
+
email: string;
|
|
1314
|
+
};
|
|
1132
1315
|
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
name : string;
|
|
1139
|
-
email :string
|
|
1140
|
-
}[] = [];
|
|
1141
|
-
|
|
1142
|
-
/**
|
|
1143
|
-
* User Type
|
|
1144
|
-
*/
|
|
1145
|
-
const UserType = new GraphQLObjectType({
|
|
1146
|
-
name: 'User',
|
|
1147
|
-
|
|
1148
|
-
fields: {
|
|
1149
|
-
id: { type: GraphQLID },
|
|
1150
|
-
name: { type: GraphQLString },
|
|
1151
|
-
email: { type: GraphQLString },
|
|
1316
|
+
const users: User[] = [
|
|
1317
|
+
{
|
|
1318
|
+
id: 1,
|
|
1319
|
+
name: 'John',
|
|
1320
|
+
email: 'john@gmail.com',
|
|
1152
1321
|
},
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
/**
|
|
1156
|
-
* Queries (READ)
|
|
1157
|
-
*/
|
|
1158
|
-
const QueryType = new GraphQLObjectType({
|
|
1159
|
-
name: 'Query',
|
|
1322
|
+
];
|
|
1160
1323
|
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
type: new GraphQLList(UserType),
|
|
1324
|
+
const yoga = createYoga({
|
|
1325
|
+
graphqlEndpoint: '/graphql',
|
|
1164
1326
|
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1327
|
+
schema: createSchema({
|
|
1328
|
+
typeDefs: /* GraphQL */ `
|
|
1329
|
+
type User {
|
|
1330
|
+
id: Int!
|
|
1331
|
+
name: String!
|
|
1332
|
+
email: String!
|
|
1333
|
+
}
|
|
1172
1334
|
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1335
|
+
input CreateUserInput {
|
|
1336
|
+
name: String!
|
|
1337
|
+
email: String!
|
|
1338
|
+
}
|
|
1176
1339
|
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
});
|
|
1340
|
+
input UpdateUserInput {
|
|
1341
|
+
id: Int!
|
|
1342
|
+
name: String
|
|
1343
|
+
email: String
|
|
1344
|
+
}
|
|
1183
1345
|
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
name: 'Mutation',
|
|
1189
|
-
|
|
1190
|
-
fields: {
|
|
1191
|
-
/**
|
|
1192
|
-
* CREATE
|
|
1193
|
-
*/
|
|
1194
|
-
createUser: {
|
|
1195
|
-
type: UserType,
|
|
1196
|
-
|
|
1197
|
-
args: {
|
|
1198
|
-
name: { type: new GraphQLNonNull(GraphQLString) },
|
|
1199
|
-
email: { type: new GraphQLNonNull(GraphQLString) },
|
|
1200
|
-
},
|
|
1346
|
+
type Query {
|
|
1347
|
+
users: [User!]!
|
|
1348
|
+
user(id: Int!): User
|
|
1349
|
+
}
|
|
1201
1350
|
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1351
|
+
type Mutation {
|
|
1352
|
+
createUser(input: CreateUserInput!): User!
|
|
1353
|
+
updateUser(input: UpdateUserInput!): User!
|
|
1354
|
+
removeUser(id: Int!): User!
|
|
1355
|
+
}
|
|
1356
|
+
`,
|
|
1208
1357
|
|
|
1209
|
-
|
|
1358
|
+
resolvers: {
|
|
1359
|
+
Query: {
|
|
1360
|
+
users: () => users,
|
|
1210
1361
|
|
|
1211
|
-
|
|
1362
|
+
user: (_root, args) => {
|
|
1363
|
+
return users.find(
|
|
1364
|
+
(u) => u.id === args.id,
|
|
1365
|
+
);
|
|
1366
|
+
},
|
|
1212
1367
|
},
|
|
1213
|
-
},
|
|
1214
1368
|
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1369
|
+
Mutation: {
|
|
1370
|
+
createUser: (_root, args) => {
|
|
1371
|
+
const user = {
|
|
1372
|
+
id: Date.now(),
|
|
1373
|
+
name: args.input.name,
|
|
1374
|
+
email: args.input.email,
|
|
1375
|
+
};
|
|
1220
1376
|
|
|
1221
|
-
|
|
1222
|
-
id: { type: new GraphQLNonNull(GraphQLID) },
|
|
1223
|
-
name: { type: GraphQLString },
|
|
1224
|
-
email: { type: GraphQLString },
|
|
1225
|
-
},
|
|
1377
|
+
users.push(user);
|
|
1226
1378
|
|
|
1227
|
-
|
|
1228
|
-
|
|
1379
|
+
return user;
|
|
1380
|
+
},
|
|
1229
1381
|
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1382
|
+
updateUser: (_root, args) => {
|
|
1383
|
+
const user = users.find(
|
|
1384
|
+
(u) => u.id === args.input.id,
|
|
1385
|
+
);
|
|
1233
1386
|
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1387
|
+
if (!user) {
|
|
1388
|
+
throw new Error(
|
|
1389
|
+
'User not found',
|
|
1390
|
+
);
|
|
1391
|
+
}
|
|
1237
1392
|
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1393
|
+
if (args.input.name !== undefined) {
|
|
1394
|
+
user.name = args.input.name;
|
|
1395
|
+
}
|
|
1241
1396
|
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1397
|
+
if (args.input.email !== undefined) {
|
|
1398
|
+
user.email = args.input.email;
|
|
1399
|
+
}
|
|
1245
1400
|
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
*/
|
|
1249
|
-
deleteUser: {
|
|
1250
|
-
type: GraphQLString,
|
|
1401
|
+
return user;
|
|
1402
|
+
},
|
|
1251
1403
|
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1404
|
+
removeUser: (_root, args) => {
|
|
1405
|
+
const index = users.findIndex(
|
|
1406
|
+
(u) => u.id === args.id,
|
|
1407
|
+
);
|
|
1255
1408
|
|
|
1256
|
-
|
|
1257
|
-
|
|
1409
|
+
if (index === -1) {
|
|
1410
|
+
throw new Error(
|
|
1411
|
+
'User not found',
|
|
1412
|
+
);
|
|
1413
|
+
}
|
|
1258
1414
|
|
|
1259
|
-
|
|
1260
|
-
throw new Error('User not found');
|
|
1261
|
-
}
|
|
1415
|
+
const deleted = users[index];
|
|
1262
1416
|
|
|
1263
|
-
|
|
1417
|
+
users.splice(index, 1);
|
|
1264
1418
|
|
|
1265
|
-
|
|
1419
|
+
return deleted;
|
|
1420
|
+
},
|
|
1266
1421
|
},
|
|
1267
1422
|
},
|
|
1268
|
-
},
|
|
1269
|
-
});
|
|
1270
|
-
|
|
1271
|
-
/**
|
|
1272
|
-
* Schema
|
|
1273
|
-
*/
|
|
1274
|
-
const schema = new GraphQLSchema({
|
|
1275
|
-
query: QueryType,
|
|
1276
|
-
mutation: MutationType,
|
|
1423
|
+
}),
|
|
1277
1424
|
});
|
|
1278
1425
|
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
schema,
|
|
1284
|
-
});
|
|
1285
|
-
|
|
1286
|
-
const app = new Spear()
|
|
1287
|
-
.post('/graphql',({ req , res }) => graphqlHandler(req , res))
|
|
1288
|
-
|
|
1289
|
-
app.listen(4000 , ({ port , server }) => {
|
|
1426
|
+
const app = new Spear()
|
|
1427
|
+
.get('/graphql',({ req , res }) => yoga(req , res))
|
|
1428
|
+
.post('/graphql',({ req , res }) => yoga(req , res))
|
|
1429
|
+
.listen(4000 , ({ port , server }) => {
|
|
1290
1430
|
console.log(`server listening on : http://localhost:${port}/graphql`)
|
|
1291
1431
|
})
|
|
1292
1432
|
```
|
|
1293
1433
|
|
|
1294
|
-
```sh
|
|
1295
|
-
## Create
|
|
1296
|
-
curl -X POST http://localhost:4000/graphql \
|
|
1297
|
-
-H "Content-Type: application/json" \
|
|
1298
|
-
-d '{
|
|
1299
|
-
"query": "mutation { createUser(name:\"John\", email:\"john@example.com\") { id name email } }"
|
|
1300
|
-
}'
|
|
1301
|
-
|
|
1302
|
-
## Read all
|
|
1303
|
-
curl -X POST http://localhost:4000/graphql \
|
|
1304
|
-
-H "Content-Type: application/json" \
|
|
1305
|
-
-d '{
|
|
1306
|
-
"query": "query { users { id name email } }"
|
|
1307
|
-
}'
|
|
1308
|
-
|
|
1309
|
-
## Read one
|
|
1310
|
-
curl -X POST http://localhost:4000/graphql \
|
|
1311
|
-
-H "Content-Type: application/json" \
|
|
1312
|
-
-d '{
|
|
1313
|
-
"query": "query { user(id:\"1\") { id name email } }"
|
|
1314
|
-
}'
|
|
1315
|
-
|
|
1316
|
-
## Update
|
|
1317
|
-
curl -X POST http://localhost:4000/graphql \
|
|
1318
|
-
-H "Content-Type: application/json" \
|
|
1319
|
-
-d '{
|
|
1320
|
-
"query": "mutation { updateUser(id:\"1\", name:\"Johnny\") { id name email } }"
|
|
1321
|
-
}'
|
|
1322
|
-
|
|
1323
|
-
## Delete
|
|
1324
|
-
curl -X POST http://localhost:4000/graphql \
|
|
1325
|
-
-H "Content-Type: application/json" \
|
|
1326
|
-
-d '{
|
|
1327
|
-
"query": "mutation { deleteUser(id:\"1\") }"
|
|
1328
|
-
}'
|
|
1329
|
-
```
|
|
1330
|
-
|
|
1331
1434
|
## E2E
|
|
1332
1435
|
Provides end-to-end type safety and testing support across the full request lifecycle, from request input to
|
|
1333
1436
|
response output. It allows you to:
|
|
@@ -1387,7 +1490,7 @@ class CatController {
|
|
|
1387
1490
|
const cat = cats.find((d) => d.id === Number(params.id));
|
|
1388
1491
|
|
|
1389
1492
|
if(cat == null) {
|
|
1390
|
-
|
|
1493
|
+
throw res.notFound('not found cat')
|
|
1391
1494
|
}
|
|
1392
1495
|
|
|
1393
1496
|
return {
|
|
@@ -1429,7 +1532,7 @@ class CatController {
|
|
|
1429
1532
|
const index = cats.findIndex((d) => d.id === id);
|
|
1430
1533
|
|
|
1431
1534
|
if (index === -1) {
|
|
1432
|
-
|
|
1535
|
+
throw res.notFound('not found cat')
|
|
1433
1536
|
}
|
|
1434
1537
|
|
|
1435
1538
|
cats[index] = {
|
|
@@ -1472,7 +1575,7 @@ import Spear from "tspace-spear";
|
|
|
1472
1575
|
const app = new Spear({
|
|
1473
1576
|
logger : true,
|
|
1474
1577
|
controllers: {
|
|
1475
|
-
folder : `${__dirname}/controllers
|
|
1578
|
+
folder : `${__dirname}/controllers/*`,
|
|
1476
1579
|
name:/controller\.(ts|js)$/i,
|
|
1477
1580
|
// don't forget to set this option for auto-generate route metadata for type-safe E2E usage,
|
|
1478
1581
|
// and swagger documentation. By default if use .useSwagger() in app no need to set any description
|
|
@@ -1496,16 +1599,55 @@ const client: ApiClient<AppRouter> = new ApiClient(
|
|
|
1496
1599
|
`http://localhost:8000/api`
|
|
1497
1600
|
);
|
|
1498
1601
|
|
|
1499
|
-
|
|
1602
|
+
await client.get("/catsq"); โ // Type error: Argument of type '"/catsq"' is not assignable to parameter of type '"/cats" | "/cats/:id" | ... 3 more
|
|
1500
1603
|
const res = await client.get("/cats");
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1604
|
+
// res.ok -> true or false
|
|
1605
|
+
// res.status -> number
|
|
1606
|
+
// res.headers -> Hearders
|
|
1607
|
+
// res.data -> { cats: [{ id: 1, name: 'cat1', age: 1.6 },{ id: 2, name: 'cat2', age: 1.8 }] }
|
|
1608
|
+
|
|
1609
|
+
// Without checking `res.ok`, `res.data` is always typed as `any`.
|
|
1610
|
+
if(res.ok) {
|
|
1611
|
+
res.data.cats = 1 โ // Type error: Type 'number' is not assignable to type '{ id: number; name: string; age: number; }[]'
|
|
1612
|
+
res.data.cats[0].name = 1 โ // Type error: Type 'number' is not assignable to type 'string'
|
|
1613
|
+
res.data.cats[0].age = "1.6" โ // Type error: Type 'string' is not assignable to type 'number'
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
await client.get("/cats/:id") โ // Expected 2 arguments, but got 1.
|
|
1617
|
+
await client.get("/cats/:id", { params : { id : "1" }}) โ
|
|
1618
|
+
// The expected type comes from property 'id' which is declared here on type '{ id: number; }'
|
|
1619
|
+
await client.get("/cats/:id", { params : { id : 1 }}) โ
|
|
1620
|
+
|
|
1621
|
+
await client.post("/cats") โ // Expected 2 arguments, but got 1.
|
|
1622
|
+
await client.post("/cats",{
|
|
1623
|
+
body : {} โ // Type '{}' is missing the following properties from type '{ name: string; age: number; }'
|
|
1624
|
+
});
|
|
1625
|
+
await client.post("/cats",{
|
|
1626
|
+
body : { name : "super cat" } โ
|
|
1627
|
+
// Property 'age' is missing in type '{ name: string; }'
|
|
1628
|
+
// but required in type '{ name: string; age: number; }'.
|
|
1629
|
+
});
|
|
1630
|
+
await client.post("/cats",{
|
|
1631
|
+
body : { name : "super cat" , age : 5 } โ
|
|
1632
|
+
});
|
|
1633
|
+
|
|
1634
|
+
await client.put("/cats") โ
|
|
1635
|
+
|
|
1636
|
+
await client.put("/cats",{
|
|
1637
|
+
body : {} โ
|
|
1638
|
+
});
|
|
1639
|
+
|
|
1640
|
+
await client.put("/cats",{
|
|
1641
|
+
body : { name : 1 } โ // Type 'number' is not assignable to type 'string'.
|
|
1642
|
+
});
|
|
1643
|
+
|
|
1644
|
+
await client.put("/cats",{
|
|
1645
|
+
body : { name : "super cat" } โ
|
|
1646
|
+
});
|
|
1647
|
+
|
|
1648
|
+
await client.put("/cats",{
|
|
1649
|
+
body : { name : "super cat" , age : 5 } โ
|
|
1650
|
+
});
|
|
1509
1651
|
|
|
1510
1652
|
```
|
|
1511
1653
|
|
|
@@ -24,7 +24,7 @@ const c = {
|
|
|
24
24
|
};
|
|
25
25
|
async function createApp(inputPath) {
|
|
26
26
|
if (!inputPath) {
|
|
27
|
-
console.log("Missing target path, try: spear
|
|
27
|
+
console.log("Missing target path, try: spear create new app");
|
|
28
28
|
process.exit(1);
|
|
29
29
|
}
|
|
30
30
|
const root = path_1.default.resolve(process.cwd(), inputPath);
|