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.
Files changed (68) hide show
  1. package/README.md +342 -200
  2. package/dist/cli/generators/app/index.js +1 -1
  3. package/dist/cli/generators/app/index.js.map +1 -1
  4. package/dist/cli/generators/controller/template.d.ts +1 -1
  5. package/dist/cli/generators/controller/template.js +11 -9
  6. package/dist/cli/generators/controller/template.js.map +1 -1
  7. package/dist/cli/index.js +10 -8
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/lib/core/client/index.js +18 -6
  10. package/dist/lib/core/client/index.js.map +1 -1
  11. package/dist/lib/core/client/types.d.ts +4 -3
  12. package/dist/lib/core/compiler/generator.js +8 -4
  13. package/dist/lib/core/compiler/generator.js.map +1 -1
  14. package/dist/lib/core/compiler/pre-routes.js +10 -2
  15. package/dist/lib/core/compiler/pre-routes.js.map +1 -1
  16. package/dist/lib/core/const/index.d.ts +12 -0
  17. package/dist/lib/core/const/index.js +6 -1
  18. package/dist/lib/core/const/index.js.map +1 -1
  19. package/dist/lib/core/decorators/controller.js +2 -1
  20. package/dist/lib/core/decorators/controller.js.map +1 -1
  21. package/dist/lib/core/decorators/headers.js +1 -0
  22. package/dist/lib/core/decorators/headers.js.map +1 -1
  23. package/dist/lib/core/decorators/index.d.ts +1 -0
  24. package/dist/lib/core/decorators/index.js +1 -0
  25. package/dist/lib/core/decorators/index.js.map +1 -1
  26. package/dist/lib/core/decorators/methods.js +4 -3
  27. package/dist/lib/core/decorators/methods.js.map +1 -1
  28. package/dist/lib/core/decorators/middleware.d.ts +46 -2
  29. package/dist/lib/core/decorators/middleware.js +100 -15
  30. package/dist/lib/core/decorators/middleware.js.map +1 -1
  31. package/dist/lib/core/decorators/service.d.ts +28 -0
  32. package/dist/lib/core/decorators/service.js +36 -0
  33. package/dist/lib/core/decorators/service.js.map +1 -0
  34. package/dist/lib/core/decorators/statusCode.js.map +1 -1
  35. package/dist/lib/core/decorators/swagger.js +4 -3
  36. package/dist/lib/core/decorators/swagger.js.map +1 -1
  37. package/dist/lib/core/exception/index.d.ts +68 -0
  38. package/dist/lib/core/exception/index.js +123 -0
  39. package/dist/lib/core/exception/index.js.map +1 -0
  40. package/dist/lib/core/metadata/index.d.ts +6 -0
  41. package/dist/lib/core/metadata/index.js +13 -0
  42. package/dist/lib/core/metadata/index.js.map +1 -0
  43. package/dist/lib/core/server/fast-router.d.ts +5 -5
  44. package/dist/lib/core/server/fast-router.js +2 -2
  45. package/dist/lib/core/server/fast-router.js.map +1 -1
  46. package/dist/lib/core/server/http/index.d.ts +22 -0
  47. package/dist/lib/core/server/http/index.js +220 -0
  48. package/dist/lib/core/server/http/index.js.map +1 -0
  49. package/dist/lib/core/server/index.d.ts +35 -7
  50. package/dist/lib/core/server/index.js +252 -75
  51. package/dist/lib/core/server/index.js.map +1 -1
  52. package/dist/lib/core/server/net/index.d.ts +13 -8
  53. package/dist/lib/core/server/net/index.js +2 -2
  54. package/dist/lib/core/server/net/index.js.map +1 -1
  55. package/dist/lib/core/server/parser-factory.d.ts +3 -4
  56. package/dist/lib/core/server/parser-factory.js +52 -143
  57. package/dist/lib/core/server/parser-factory.js.map +1 -1
  58. package/dist/lib/core/server/response.d.ts +1 -2
  59. package/dist/lib/core/server/response.js +173 -171
  60. package/dist/lib/core/server/response.js.map +1 -1
  61. package/dist/lib/core/server/uWS/index.d.ts +2 -3
  62. package/dist/lib/core/server/uWS/index.js +15 -10
  63. package/dist/lib/core/server/uWS/index.js.map +1 -1
  64. package/dist/lib/core/types/index.d.ts +25 -8
  65. package/dist/lib/core/utils/index.d.ts +3 -3
  66. package/dist/lib/core/utils/index.js +19 -16
  67. package/dist/lib/core/utils/index.js.map +1 -1
  68. 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 [cluster mode](#cluster) support for multi-core scalability and higher throughput
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-http + tspace-spear
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-http and tspace-spear.
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-http
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-http
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
- GraphQLSchema,
1123
- GraphQLObjectType,
1124
- GraphQLString,
1125
- GraphQLList,
1126
- GraphQLNonNull,
1127
- GraphQLID,
1128
- } from 'graphql';
1129
-
1130
- import { createHandler } from 'graphql-http/lib/use/http';
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
- * Fake database
1135
- */
1136
- const users : {
1137
- id : string;
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
- fields: {
1162
- users: {
1163
- type: new GraphQLList(UserType),
1324
+ const yoga = createYoga({
1325
+ graphqlEndpoint: '/graphql',
1164
1326
 
1165
- resolve: () => {
1166
- return users;
1167
- },
1168
- },
1169
-
1170
- user: {
1171
- type: UserType,
1327
+ schema: createSchema({
1328
+ typeDefs: /* GraphQL */ `
1329
+ type User {
1330
+ id: Int!
1331
+ name: String!
1332
+ email: String!
1333
+ }
1172
1334
 
1173
- args: {
1174
- id: { type: new GraphQLNonNull(GraphQLID) },
1175
- },
1335
+ input CreateUserInput {
1336
+ name: String!
1337
+ email: String!
1338
+ }
1176
1339
 
1177
- resolve: (_, args) => {
1178
- return users.find((v) => v.id === args.id);
1179
- },
1180
- },
1181
- },
1182
- });
1340
+ input UpdateUserInput {
1341
+ id: Int!
1342
+ name: String
1343
+ email: String
1344
+ }
1183
1345
 
1184
- /**
1185
- * Mutations (CREATE UPDATE DELETE)
1186
- */
1187
- const MutationType = new GraphQLObjectType({
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
- resolve: (_, args) => {
1203
- const user = {
1204
- id: String(users.length + 1),
1205
- name: args.name,
1206
- email: args.email,
1207
- };
1351
+ type Mutation {
1352
+ createUser(input: CreateUserInput!): User!
1353
+ updateUser(input: UpdateUserInput!): User!
1354
+ removeUser(id: Int!): User!
1355
+ }
1356
+ `,
1208
1357
 
1209
- users.push(user);
1358
+ resolvers: {
1359
+ Query: {
1360
+ users: () => users,
1210
1361
 
1211
- return user;
1362
+ user: (_root, args) => {
1363
+ return users.find(
1364
+ (u) => u.id === args.id,
1365
+ );
1366
+ },
1212
1367
  },
1213
- },
1214
1368
 
1215
- /**
1216
- * UPDATE
1217
- */
1218
- updateUser: {
1219
- type: UserType,
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
- args: {
1222
- id: { type: new GraphQLNonNull(GraphQLID) },
1223
- name: { type: GraphQLString },
1224
- email: { type: GraphQLString },
1225
- },
1377
+ users.push(user);
1226
1378
 
1227
- resolve: (_, args) => {
1228
- const user = users.find((v) => v.id === args.id);
1379
+ return user;
1380
+ },
1229
1381
 
1230
- if (!user) {
1231
- throw new Error('User not found');
1232
- }
1382
+ updateUser: (_root, args) => {
1383
+ const user = users.find(
1384
+ (u) => u.id === args.input.id,
1385
+ );
1233
1386
 
1234
- if (args.name !== undefined) {
1235
- user.name = args.name;
1236
- }
1387
+ if (!user) {
1388
+ throw new Error(
1389
+ 'User not found',
1390
+ );
1391
+ }
1237
1392
 
1238
- if (args.email !== undefined) {
1239
- user.email = args.email;
1240
- }
1393
+ if (args.input.name !== undefined) {
1394
+ user.name = args.input.name;
1395
+ }
1241
1396
 
1242
- return user;
1243
- },
1244
- },
1397
+ if (args.input.email !== undefined) {
1398
+ user.email = args.input.email;
1399
+ }
1245
1400
 
1246
- /**
1247
- * DELETE
1248
- */
1249
- deleteUser: {
1250
- type: GraphQLString,
1401
+ return user;
1402
+ },
1251
1403
 
1252
- args: {
1253
- id: { type: new GraphQLNonNull(GraphQLID) },
1254
- },
1404
+ removeUser: (_root, args) => {
1405
+ const index = users.findIndex(
1406
+ (u) => u.id === args.id,
1407
+ );
1255
1408
 
1256
- resolve: (_, args) => {
1257
- const index = users.findIndex((v) => v.id === args.id);
1409
+ if (index === -1) {
1410
+ throw new Error(
1411
+ 'User not found',
1412
+ );
1413
+ }
1258
1414
 
1259
- if (index === -1) {
1260
- throw new Error('User not found');
1261
- }
1415
+ const deleted = users[index];
1262
1416
 
1263
- users.splice(index, 1);
1417
+ users.splice(index, 1);
1264
1418
 
1265
- return 'Deleted';
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
- * Handler
1281
- */
1282
- const graphqlHandler = createHandler({
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
- return res.notFound('not found cat')
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
- return res.notFound('not found cat')
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
- const test = await client.get("/catsq"); // Type error: Argument of type '"/catsq"' is not assignable to parameter of type '"/cats" | "/cats/:id" | ... 3 more
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
- res.data.cats = 1 // Type error: Type 'number' is not assignable to type '{ id: number; name: string; age: number; }[]'
1502
- res.data.cats[0].name = 1 // Type error: Type 'number' is not assignable to type 'string'
1503
- res.data.cats[0].age = "1.6" // Type error: Type 'string' is not assignable to type 'number'
1504
-
1505
- console.log(res)
1506
- // res.ok -> boolean
1507
- // res.status -> number
1508
- // res.data -> { cats: [{ id: 1, name: 'cat1', age: 1.6 },{ id: 2, name: 'cat2', age: 1.8 }] }
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 g app src");
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);