tspace-spear 1.2.5 โ 1.2.6-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 +300 -26
- package/dist/cli/app.d.ts +1 -0
- package/dist/cli/app.js +24 -0
- package/dist/cli/app.js.map +1 -0
- package/dist/cli/controller.d.ts +1 -0
- package/dist/cli/controller.js +158 -0
- package/dist/cli/controller.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +138 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/lib/core/client/index.d.ts +82 -0
- package/dist/lib/core/client/index.js +142 -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 +356 -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 +12 -0
- package/dist/lib/core/compiler/index.js.map +1 -0
- package/dist/lib/core/compiler/pre-routes.d.ts +59 -0
- package/dist/lib/core/compiler/pre-routes.js +18 -0
- package/dist/lib/core/compiler/pre-routes.js.map +1 -0
- package/dist/lib/core/compiler/types.d.ts +14 -0
- package/dist/lib/core/compiler/types.js +3 -0
- package/dist/lib/core/compiler/types.js.map +1 -0
- package/dist/lib/core/decorators/context.d.ts +50 -1
- package/dist/lib/core/decorators/context.js +87 -3
- package/dist/lib/core/decorators/context.js.map +1 -1
- package/dist/lib/core/decorators/swagger.d.ts +1 -1
- package/dist/lib/core/decorators/swagger.js +1 -1
- package/dist/lib/core/decorators/swagger.js.map +1 -1
- package/dist/lib/core/server/fast-router.js +11 -2
- package/dist/lib/core/server/fast-router.js.map +1 -1
- package/dist/lib/core/server/index.d.ts +16 -0
- package/dist/lib/core/server/index.js +118 -38
- package/dist/lib/core/server/index.js.map +1 -1
- package/dist/lib/core/server/parser-factory.d.ts +2 -2
- package/dist/lib/core/server/parser-factory.js +236 -4
- package/dist/lib/core/server/parser-factory.js.map +1 -1
- package/dist/lib/core/types/index.d.ts +13 -9
- package/dist/tests/e2e..test.d.ts +1 -0
- package/dist/tests/e2e..test.js +16 -0
- package/dist/tests/e2e..test.js.map +1 -0
- package/package.json +32 -4
package/README.md
CHANGED
|
@@ -3,10 +3,23 @@
|
|
|
3
3
|
[](https://www.npmjs.com)
|
|
4
4
|
[](https://www.npmjs.com)
|
|
5
5
|
|
|
6
|
-
tspace-spear is a lightweight
|
|
7
|
-
built on the native HTTP server with optional support for uWebSockets.js (C++) to achieve maximum speed and efficiency.
|
|
6
|
+
**tspace-spear** is a lightweight, high-performance API framework for Node.js, built on the native HTTP server with optional support for uWebSockets.js (C++) to achieve maximum speed and efficiency.
|
|
8
7
|
|
|
9
|
-
It is designed with a strong focus on
|
|
8
|
+
It is designed with a strong focus on developer experience and provides end-to-end (E2E) type safety and testing support across the full request lifecycle, from request input to response output (see [E2E](#e2e)).
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- โก High-performance core built on native Node.js HTTP
|
|
15
|
+
- ๐ Optional uWebSockets.js support via adapter for ultra-low latency and maximum throughput
|
|
16
|
+
- ๐ง End-to-end (E2E) type safety across request โ response lifecycle
|
|
17
|
+
- ๐งช Built-in testing support for E2E validation
|
|
18
|
+
- ๐งฉ Simple and intuitive developer experience
|
|
19
|
+
- ๐ Flexible architecture for plugins and extensions
|
|
20
|
+
- ๐ Auto-generated Swagger documentation via `app.useSwagger()` (zero manual configuration required)
|
|
21
|
+
|
|
22
|
+
---
|
|
10
23
|
|
|
11
24
|
## Install
|
|
12
25
|
|
|
@@ -41,6 +54,7 @@ See the [`docs`](https://thanathip41.github.io/tspace-spear) directory for full
|
|
|
41
54
|
- [Router](#router)
|
|
42
55
|
- [Swagger](#swagger)
|
|
43
56
|
- [WebSocket](#websocket)
|
|
57
|
+
- [E2E](#e2e)
|
|
44
58
|
- [Example CRUD](#example-crud)
|
|
45
59
|
|
|
46
60
|
## Start Server
|
|
@@ -508,8 +522,12 @@ import CatController from './cat-controller.ts'
|
|
|
508
522
|
controllers: [ CatController ]
|
|
509
523
|
// if you want to import controllers with a directory can you follow the example
|
|
510
524
|
// controllers : {
|
|
511
|
-
// folder : `${__dirname}/controllers`,
|
|
512
|
-
// name : /controller\.(ts|js)$/i
|
|
525
|
+
// folder : `${__dirname}/controllers`, // nestjs style `${__dirname}/modules/*`
|
|
526
|
+
// name : /controller\.(ts|js)$/i,
|
|
527
|
+
|
|
528
|
+
// *Auto-generate route metadata for type-safe E2E usage,
|
|
529
|
+
// *and swagger documentation. By default if use .useSwagger() in app no need to set any description
|
|
530
|
+
// preRouteTypes : true
|
|
513
531
|
// }
|
|
514
532
|
})
|
|
515
533
|
|
|
@@ -531,40 +549,48 @@ import CatController from './cat-controller.ts'
|
|
|
531
549
|
})()
|
|
532
550
|
```
|
|
533
551
|
|
|
534
|
-
|
|
535
552
|
## Dto
|
|
536
553
|
DTO (Data Transfer Object) is used to validate and transform incoming request data before it reaches your controller logic.
|
|
537
554
|
```js
|
|
538
555
|
import {
|
|
539
556
|
Controller ,
|
|
540
557
|
Post,
|
|
541
|
-
createDtoDecorator
|
|
558
|
+
createDtoDecorator,
|
|
559
|
+
Validate,
|
|
542
560
|
type T
|
|
543
561
|
} from 'tspace-spear';
|
|
544
562
|
|
|
545
563
|
import z from "zod";
|
|
546
564
|
|
|
547
|
-
|
|
565
|
+
import {
|
|
566
|
+
IsString,
|
|
567
|
+
IsInt,
|
|
568
|
+
validate
|
|
569
|
+
} from "class-validator";
|
|
570
|
+
|
|
571
|
+
import { ClassConstructor, plainToInstance } from 'class-transformer';
|
|
572
|
+
|
|
573
|
+
const ValidateDtoCustomBody = (keys: string[]) => {
|
|
548
574
|
return createDtoDecorator((ctx) => {
|
|
549
575
|
const body = ctx.body ?? {};
|
|
550
576
|
const issues: Array<{ path: string; message: string }> = [];
|
|
551
577
|
|
|
552
578
|
for (let i = 0; i < keys.length; i++) {
|
|
553
|
-
|
|
579
|
+
const key = keys[i];
|
|
554
580
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
581
|
+
if (body[key] == null) {
|
|
582
|
+
issues.push({
|
|
583
|
+
path: key,
|
|
584
|
+
message: "Missing field",
|
|
585
|
+
});
|
|
586
|
+
}
|
|
561
587
|
}
|
|
562
588
|
|
|
563
589
|
if (issues.length > 0) {
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
590
|
+
throw {
|
|
591
|
+
message : "Validation failed",
|
|
592
|
+
issues
|
|
593
|
+
}
|
|
568
594
|
}
|
|
569
595
|
});
|
|
570
596
|
}
|
|
@@ -580,7 +606,12 @@ const ValidateDtoPromiseBody = (keys: string[]) => {
|
|
|
580
606
|
return createDtoDecorator(async (ctx) => {
|
|
581
607
|
await new Promise(resolve => setTimeout(resolve,500));
|
|
582
608
|
// check in DB or other async operation
|
|
583
|
-
|
|
609
|
+
const cats = await catRepository.findMany({ where : { name : ctx.body.name }});
|
|
610
|
+
|
|
611
|
+
if(!cats.length) {
|
|
612
|
+
throw new Error('Validation failed in promise!');
|
|
613
|
+
}
|
|
614
|
+
|
|
584
615
|
}, (ctx, error) => {
|
|
585
616
|
// you implement your custom error handling for async validation here
|
|
586
617
|
return ctx.res.status(400).json({
|
|
@@ -590,18 +621,70 @@ const ValidateDtoPromiseBody = (keys: string[]) => {
|
|
|
590
621
|
});
|
|
591
622
|
}
|
|
592
623
|
|
|
624
|
+
const ValidateDtoClsBody = <T extends object>(
|
|
625
|
+
cls: ClassConstructor<T>
|
|
626
|
+
) => {
|
|
627
|
+
return createDtoDecorator(async (ctx) => {
|
|
628
|
+
|
|
629
|
+
const dto = plainToInstance(
|
|
630
|
+
cls,
|
|
631
|
+
ctx.body
|
|
632
|
+
);
|
|
633
|
+
|
|
634
|
+
const errors = await validate(dto);
|
|
635
|
+
|
|
636
|
+
if (errors.length > 0) {
|
|
637
|
+
throw {
|
|
638
|
+
message: "Validation failed",
|
|
639
|
+
issues: errors.flatMap((error) => {
|
|
640
|
+
const constraints = error.constraints ?? {};
|
|
641
|
+
|
|
642
|
+
const firstError = Object.values(constraints)[0] ?? "Validation error";
|
|
643
|
+
|
|
644
|
+
return {
|
|
645
|
+
path: error.property,
|
|
646
|
+
message: firstError,
|
|
647
|
+
};
|
|
648
|
+
})
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
ctx.body = dto;
|
|
653
|
+
}
|
|
654
|
+
);
|
|
655
|
+
};
|
|
656
|
+
|
|
593
657
|
const catSchema = z.object({
|
|
594
658
|
name: z.string(),
|
|
595
659
|
age: z.number(),
|
|
596
660
|
})
|
|
597
661
|
|
|
662
|
+
class CreateCatDto {
|
|
663
|
+
@IsString()
|
|
664
|
+
name!: string;
|
|
665
|
+
|
|
666
|
+
@IsInt()
|
|
667
|
+
age!: number;
|
|
668
|
+
}
|
|
669
|
+
|
|
598
670
|
|
|
599
671
|
// file cat-controller.ts
|
|
600
672
|
@Controller('/cats')
|
|
601
673
|
export class CatController {
|
|
674
|
+
|
|
602
675
|
@Post('/')
|
|
603
|
-
|
|
604
|
-
|
|
676
|
+
// only required validation without type checking
|
|
677
|
+
@Validate(["name", "age"], { required: { allowEmptyString: false, allowNull: false } })
|
|
678
|
+
public async basic(ctx : T.Context<{ body : { name : any , age : any }}>) {
|
|
679
|
+
const body = ctx.body;
|
|
680
|
+
return {
|
|
681
|
+
body
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
@Post('/')
|
|
686
|
+
@ValidateDtoCustomBody(["name", "age"])
|
|
687
|
+
public async custom(ctx : T.Context<{ body : { name : string , age : number }}>) {
|
|
605
688
|
const body = ctx.body;
|
|
606
689
|
return {
|
|
607
690
|
body
|
|
@@ -610,16 +693,26 @@ export class CatController {
|
|
|
610
693
|
|
|
611
694
|
@Post('/zod')
|
|
612
695
|
@ValidateDtoZodBody(catSchema)
|
|
613
|
-
public async zod(ctx : T.Context) {
|
|
614
|
-
const body = ctx.body
|
|
696
|
+
public async zod(ctx : T.Context<z.infer<typeof catSchema>>) {
|
|
697
|
+
const body = ctx.body;
|
|
615
698
|
return {
|
|
616
699
|
body
|
|
617
700
|
}
|
|
618
701
|
}
|
|
619
702
|
|
|
620
703
|
@Post('/promise')
|
|
621
|
-
@ValidateDtoPromiseBody(['name'
|
|
622
|
-
public async promise(ctx : T.Context) {
|
|
704
|
+
@ValidateDtoPromiseBody(['name'])
|
|
705
|
+
public async promise(ctx : T.Context<{ body : { name : string }}>) {
|
|
706
|
+
const body = ctx.body;
|
|
707
|
+
|
|
708
|
+
return {
|
|
709
|
+
body
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
@Post('/cls')
|
|
714
|
+
@ValidateDtoClsBody(CreateCatDto)
|
|
715
|
+
public async cls(ctx : T.Context<{ body : CreateCatDto }>) {
|
|
623
716
|
const body = ctx.body;
|
|
624
717
|
|
|
625
718
|
return {
|
|
@@ -992,6 +1085,187 @@ new Spear()
|
|
|
992
1085
|
})
|
|
993
1086
|
|
|
994
1087
|
|
|
1088
|
+
```
|
|
1089
|
+
|
|
1090
|
+
## E2E
|
|
1091
|
+
Provides end-to-end type safety and testing support across the full request lifecycle, from request input to
|
|
1092
|
+
response output. It allows you to:
|
|
1093
|
+
```js
|
|
1094
|
+
// file cat-controller.ts
|
|
1095
|
+
import z from 'zod';
|
|
1096
|
+
import {
|
|
1097
|
+
type T,
|
|
1098
|
+
Controller,
|
|
1099
|
+
Get,
|
|
1100
|
+
Post,
|
|
1101
|
+
Put,
|
|
1102
|
+
Delete,
|
|
1103
|
+
createDtoDecorator
|
|
1104
|
+
} from "tspace-spear";
|
|
1105
|
+
|
|
1106
|
+
const catSchema = z.object({
|
|
1107
|
+
id: z.number(),
|
|
1108
|
+
name: z.string(),
|
|
1109
|
+
age: z.number(),
|
|
1110
|
+
});
|
|
1111
|
+
|
|
1112
|
+
const catSchemaAction = z.object({
|
|
1113
|
+
name: z.string(),
|
|
1114
|
+
age: z.number(),
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
type Cat = z.infer<typeof catSchema>
|
|
1118
|
+
|
|
1119
|
+
let cats: z.infer<typeof catSchema>[] = [
|
|
1120
|
+
{ id: 1, name: 'cat1', age: 1.6 },
|
|
1121
|
+
{ id: 2, name: 'cat2', age: 1.8 },
|
|
1122
|
+
];
|
|
1123
|
+
|
|
1124
|
+
const ValidateDtoBody = (schema: z.ZodTypeAny) => {
|
|
1125
|
+
return createDtoDecorator((ctx) => {
|
|
1126
|
+
const result = schema.parse(ctx.body);
|
|
1127
|
+
ctx.body = result as T.Body;
|
|
1128
|
+
});
|
|
1129
|
+
};
|
|
1130
|
+
@Controller('/cats')
|
|
1131
|
+
class CatController {
|
|
1132
|
+
|
|
1133
|
+
@Get('/')
|
|
1134
|
+
public async index({
|
|
1135
|
+
query,
|
|
1136
|
+
}: T.Context) {
|
|
1137
|
+
return {
|
|
1138
|
+
cats,
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
@Get('/:id')
|
|
1143
|
+
public async show({ res, params }: T.Context<{ params: { id: number } }>) : Promise<{
|
|
1144
|
+
cat : Cat
|
|
1145
|
+
}> {
|
|
1146
|
+
const cat = cats.find((d) => d.id === Number(params.id));
|
|
1147
|
+
|
|
1148
|
+
if(cat == null) {
|
|
1149
|
+
return res.notFound('not found cat')
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
return {
|
|
1153
|
+
cat
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
@Post('/')
|
|
1158
|
+
@ValidateDtoBody(catSchemaAction)
|
|
1159
|
+
public async create({
|
|
1160
|
+
body,
|
|
1161
|
+
}: T.Context<{ body: z.infer<typeof catSchemaAction> }>) {
|
|
1162
|
+
|
|
1163
|
+
const cat = {
|
|
1164
|
+
id: cats.length + 1,
|
|
1165
|
+
...body
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
cats.push(cat);
|
|
1169
|
+
|
|
1170
|
+
return {
|
|
1171
|
+
cat,
|
|
1172
|
+
message: 'created',
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
@Put('/:id')
|
|
1177
|
+
@ValidateDtoBody(catSchemaAction.partial())
|
|
1178
|
+
public async update({
|
|
1179
|
+
res,
|
|
1180
|
+
params,
|
|
1181
|
+
body,
|
|
1182
|
+
}: T.Context<{
|
|
1183
|
+
params: { id: number };
|
|
1184
|
+
body: Partial<z.infer<typeof catSchemaAction>>;
|
|
1185
|
+
}>) {
|
|
1186
|
+
const id = Number(params.id);
|
|
1187
|
+
|
|
1188
|
+
const index = cats.findIndex((d) => d.id === id);
|
|
1189
|
+
|
|
1190
|
+
if (index === -1) {
|
|
1191
|
+
return res.notFound('not found cat')
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
cats[index] = {
|
|
1195
|
+
...cats[index],
|
|
1196
|
+
...body,
|
|
1197
|
+
id
|
|
1198
|
+
};
|
|
1199
|
+
|
|
1200
|
+
const cat = cats[index]
|
|
1201
|
+
|
|
1202
|
+
return {
|
|
1203
|
+
message: 'updated',
|
|
1204
|
+
cat,
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
@Delete('/:id')
|
|
1209
|
+
public async remove({ res, params }: T.Context<{ params: { id: number } }>) {
|
|
1210
|
+
const id = Number(params.id);
|
|
1211
|
+
|
|
1212
|
+
const index = cats.findIndex((d) => d.id === id);
|
|
1213
|
+
|
|
1214
|
+
if (index === -1) {
|
|
1215
|
+
throw res.notFound('not found cat')
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
cats = cats.filter((d) => d.id !== id);
|
|
1219
|
+
|
|
1220
|
+
return {
|
|
1221
|
+
message: 'deleted',
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
export { CatController };
|
|
1227
|
+
export default CatController;
|
|
1228
|
+
|
|
1229
|
+
// file server/app.ts
|
|
1230
|
+
import Spear from "tspace-spear";
|
|
1231
|
+
const app = new Spear({
|
|
1232
|
+
logger : true,
|
|
1233
|
+
controllers: {
|
|
1234
|
+
folder : `${__dirname}/controllers`,
|
|
1235
|
+
name:/controller\.(ts|js)$/i,
|
|
1236
|
+
// don't forget to set this option for auto-generate route metadata for type-safe E2E usage,
|
|
1237
|
+
// and swagger documentation. By default if use .useSwagger() in app no need to set any description
|
|
1238
|
+
preRouteTypes: true
|
|
1239
|
+
}
|
|
1240
|
+
})
|
|
1241
|
+
|
|
1242
|
+
app.useGlobalPrefix('api');
|
|
1243
|
+
app.useBodyParser();
|
|
1244
|
+
app.listen(3000 , () => console.log(`Server is now listening http://localhost:3000`));
|
|
1245
|
+
|
|
1246
|
+
type AppRouter = typeof app.contract;
|
|
1247
|
+
export { AppRouter }
|
|
1248
|
+
export default app;
|
|
1249
|
+
|
|
1250
|
+
// file frontend/index.ts
|
|
1251
|
+
import { AppRouter } from "./server/app";
|
|
1252
|
+
import { ApiClient } from "tspace-spear/client";
|
|
1253
|
+
|
|
1254
|
+
const client: ApiClient<AppRouter> = new ApiClient(
|
|
1255
|
+
`http://localhost:3000/api`
|
|
1256
|
+
);
|
|
1257
|
+
|
|
1258
|
+
const test = await client.get("/catsq"); // Type error: Argument of type '"/catsq"' is not assignable to parameter of type '"/cats" | "/cats/:id" | ... 3 more
|
|
1259
|
+
const res = await client.get("/cats");
|
|
1260
|
+
res.data.cats = 1 // Type error: Type 'number' is not assignable to type '{ id: number; name: string; age: number; }[]'
|
|
1261
|
+
res.data.cats[0].name = 1 // Type error: Type 'number' is not assignable to type 'string'
|
|
1262
|
+
res.data.cats[0].age = "1.6" // Type error: Type 'string' is not assignable to type 'number'
|
|
1263
|
+
|
|
1264
|
+
console.log(res)
|
|
1265
|
+
// res.ok -> boolean
|
|
1266
|
+
// res.status -> number
|
|
1267
|
+
// res.data -> { cats: [{ id: 1, name: 'cat1', age: 1.6 },{ id: 2, name: 'cat2', age: 1.8 }] }
|
|
1268
|
+
|
|
995
1269
|
```
|
|
996
1270
|
|
|
997
1271
|
## Example CRUD
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const app = "\nimport Spear from \"tspace-spear\";\n\nexport const app = new Spear({\n logger: true,\n controllers: {\n folder: `${__dirname}/controllers`,\n name: /controller\\\\.(ts|js)$/i,\n preRouteTypes: true\n }\n})\n\napp.useGlobalPrefix(\"api\");\n\napp.useBodyParser();\n\napp.listen(8000 , ({ port , server }) => {\n console.log(`server listening on : http://localhost:${port}`)\n})\n";
|
package/dist/cli/app.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.app = void 0;
|
|
4
|
+
exports.app = `
|
|
5
|
+
import Spear from "tspace-spear";
|
|
6
|
+
|
|
7
|
+
export const app = new Spear({
|
|
8
|
+
logger: true,
|
|
9
|
+
controllers: {
|
|
10
|
+
folder: \`\${__dirname}/controllers\`,
|
|
11
|
+
name: /controller\\\\.(ts|js)$/i,
|
|
12
|
+
preRouteTypes: true
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
app.useGlobalPrefix("api");
|
|
17
|
+
|
|
18
|
+
app.useBodyParser();
|
|
19
|
+
|
|
20
|
+
app.listen(8000 , ({ port , server }) => {
|
|
21
|
+
console.log(\`server listening on : http://localhost:\${port}\`)
|
|
22
|
+
})
|
|
23
|
+
`;
|
|
24
|
+
//# sourceMappingURL=app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/cli/app.ts"],"names":[],"mappings":";;;AAAa,QAAA,GAAG,GAAG;;;;;;;;;;;;;;;;;;;CAmBlB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const CatController = "\nimport {\n type T,\n Controller,\n Get,\n Post,\n Put,\n Delete\n} from \"tspace-spear\";\n\ntype Cat = {\n id: number;\n name: string;\n age: number;\n};\n\nlet cats: Cat[] = [\n {\n id: 1,\n name: \"cat 1\",\n age: 2\n },\n {\n id: 2,\n name: \"cat 2\",\n age: 4\n }\n];\n\n@Controller(\"/cats\")\nexport default class CatController {\n\n @Get(\"/\")\n async index() {\n return {\n cats\n };\n }\n\n @Get(\"/:id\")\n async show({\n res,\n params\n }: T.Context<{\n params: {\n id: number;\n };\n }>) {\n\n const cat = cats.find(\n d => d.id === Number(params.id)\n );\n\n if (!cat) {\n throw res.notFound(\n \"Cat not found\"\n );\n }\n\n return {\n cat\n };\n }\n\n @Post(\"/\")\n async create({\n body\n }: T.Context<{\n body: {\n name: string;\n age: number;\n };\n }>) {\n\n const cat: Cat = {\n id: cats.length + 1,\n name: body.name,\n age: body.age\n };\n\n cats.push(cat);\n\n return {\n message: \"Created\",\n cat\n };\n }\n\n @Put(\"/:id\")\n async update({\n res,\n params,\n body\n }: T.Context<{\n params: {\n id: number;\n };\n body: Partial<{\n name: string;\n age: number;\n }>;\n }>) {\n\n const index = cats.findIndex(\n d => d.id === Number(params.id)\n );\n\n if (index === -1) {\n throw res.notFound(\n \"Cat not found\"\n );\n }\n\n cats[index] = {\n ...cats[index],\n ...body\n };\n\n return {\n message: \"Updated\",\n cat: cats[index]\n };\n }\n\n @Delete(\"/:id\")\n async remove({\n res,\n params\n }: T.Context<{\n params: {\n id: number;\n };\n }>) {\n\n const index = cats.findIndex(\n d => d.id === Number(params.id)\n );\n\n if (index === -1) {\n throw res.notFound(\n \"Cat not found\"\n );\n }\n\n cats = cats.filter(\n d => d.id !== Number(params.id)\n );\n\n return {\n message: \"Deleted\"\n };\n }\n}\n";
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CatController = void 0;
|
|
4
|
+
exports.CatController = `
|
|
5
|
+
import {
|
|
6
|
+
type T,
|
|
7
|
+
Controller,
|
|
8
|
+
Get,
|
|
9
|
+
Post,
|
|
10
|
+
Put,
|
|
11
|
+
Delete
|
|
12
|
+
} from "tspace-spear";
|
|
13
|
+
|
|
14
|
+
type Cat = {
|
|
15
|
+
id: number;
|
|
16
|
+
name: string;
|
|
17
|
+
age: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
let cats: Cat[] = [
|
|
21
|
+
{
|
|
22
|
+
id: 1,
|
|
23
|
+
name: "cat 1",
|
|
24
|
+
age: 2
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 2,
|
|
28
|
+
name: "cat 2",
|
|
29
|
+
age: 4
|
|
30
|
+
}
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
@Controller("/cats")
|
|
34
|
+
export default class CatController {
|
|
35
|
+
|
|
36
|
+
@Get("/")
|
|
37
|
+
async index() {
|
|
38
|
+
return {
|
|
39
|
+
cats
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@Get("/:id")
|
|
44
|
+
async show({
|
|
45
|
+
res,
|
|
46
|
+
params
|
|
47
|
+
}: T.Context<{
|
|
48
|
+
params: {
|
|
49
|
+
id: number;
|
|
50
|
+
};
|
|
51
|
+
}>) {
|
|
52
|
+
|
|
53
|
+
const cat = cats.find(
|
|
54
|
+
d => d.id === Number(params.id)
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
if (!cat) {
|
|
58
|
+
throw res.notFound(
|
|
59
|
+
"Cat not found"
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
cat
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@Post("/")
|
|
69
|
+
async create({
|
|
70
|
+
body
|
|
71
|
+
}: T.Context<{
|
|
72
|
+
body: {
|
|
73
|
+
name: string;
|
|
74
|
+
age: number;
|
|
75
|
+
};
|
|
76
|
+
}>) {
|
|
77
|
+
|
|
78
|
+
const cat: Cat = {
|
|
79
|
+
id: cats.length + 1,
|
|
80
|
+
name: body.name,
|
|
81
|
+
age: body.age
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
cats.push(cat);
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
message: "Created",
|
|
88
|
+
cat
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@Put("/:id")
|
|
93
|
+
async update({
|
|
94
|
+
res,
|
|
95
|
+
params,
|
|
96
|
+
body
|
|
97
|
+
}: T.Context<{
|
|
98
|
+
params: {
|
|
99
|
+
id: number;
|
|
100
|
+
};
|
|
101
|
+
body: Partial<{
|
|
102
|
+
name: string;
|
|
103
|
+
age: number;
|
|
104
|
+
}>;
|
|
105
|
+
}>) {
|
|
106
|
+
|
|
107
|
+
const index = cats.findIndex(
|
|
108
|
+
d => d.id === Number(params.id)
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
if (index === -1) {
|
|
112
|
+
throw res.notFound(
|
|
113
|
+
"Cat not found"
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
cats[index] = {
|
|
118
|
+
...cats[index],
|
|
119
|
+
...body
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
message: "Updated",
|
|
124
|
+
cat: cats[index]
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@Delete("/:id")
|
|
129
|
+
async remove({
|
|
130
|
+
res,
|
|
131
|
+
params
|
|
132
|
+
}: T.Context<{
|
|
133
|
+
params: {
|
|
134
|
+
id: number;
|
|
135
|
+
};
|
|
136
|
+
}>) {
|
|
137
|
+
|
|
138
|
+
const index = cats.findIndex(
|
|
139
|
+
d => d.id === Number(params.id)
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
if (index === -1) {
|
|
143
|
+
throw res.notFound(
|
|
144
|
+
"Cat not found"
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
cats = cats.filter(
|
|
149
|
+
d => d.id !== Number(params.id)
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
message: "Deleted"
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
`;
|
|
158
|
+
//# sourceMappingURL=controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"controller.js","sourceRoot":"","sources":["../../src/cli/controller.ts"],"names":[],"mappings":";;;AAAa,QAAA,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyJ5B,CAAC"}
|