@avijs/cli 0.1.1 → 0.2.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 +7 -7
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +15 -5
- package/dist/cli.js.map +1 -1
- package/dist/generate.d.ts +1 -1
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +372 -7
- package/dist/generate.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/templates.d.ts +3 -0
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +258 -8
- package/dist/templates.js.map +1 -1
- package/dist/validators.d.ts +27 -0
- package/dist/validators.d.ts.map +1 -0
- package/dist/validators.js +56 -0
- package/dist/validators.js.map +1 -0
- package/package.json +7 -3
- package/src/cli.ts +16 -5
- package/src/generate.ts +402 -7
- package/src/index.ts +1 -0
- package/src/templates.ts +271 -13
- package/src/validators.ts +85 -0
package/src/generate.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import { existsSync, readdirSync } from 'node:fs';
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
2
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { parseArgs } from 'node:util';
|
|
5
5
|
import ts from 'typescript';
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
import {
|
|
7
|
+
SYNTAX,
|
|
8
|
+
VALIDATORS,
|
|
9
|
+
detectValidator,
|
|
10
|
+
type SchemaSyntax,
|
|
11
|
+
type Validator,
|
|
12
|
+
} from './validators.js';
|
|
13
|
+
|
|
14
|
+
type Kind = 'module' | 'controller' | 'service' | 'resource';
|
|
8
15
|
type Layout = 'modular' | 'minimal';
|
|
9
16
|
|
|
10
17
|
const KINDS: Record<string, Kind> = {
|
|
@@ -14,14 +21,17 @@ const KINDS: Record<string, Kind> = {
|
|
|
14
21
|
co: 'controller',
|
|
15
22
|
service: 'service',
|
|
16
23
|
s: 'service',
|
|
24
|
+
resource: 'resource',
|
|
25
|
+
res: 'resource',
|
|
17
26
|
};
|
|
18
27
|
const SEGMENT = /^[a-z][a-z0-9-]*$/;
|
|
19
28
|
|
|
20
|
-
export const GENERATE_USAGE = ` avi generate <module|controller|service> <path> [--root .] [--dry-run]
|
|
29
|
+
export const GENERATE_USAGE = ` avi generate <module|controller|service|resource> <path> [--root .] [--dry-run]
|
|
21
30
|
Create a module, controller or service and register it. Short: avi g co admin/posts.
|
|
22
31
|
Modular projects get src/modules/<path>/<name>.<kind>.ts, registered in the nearest
|
|
23
32
|
module; minimal projects get src/routes/<path>.ts or src/services/<path>.ts, registered
|
|
24
|
-
in src/app.ts
|
|
33
|
+
in src/app.ts. A resource (avi g res posts) creates all of them with CRUD routes, validated
|
|
34
|
+
with the project's valibot or zod (--validator valibot|zod|none to choose).`;
|
|
25
35
|
|
|
26
36
|
interface Names {
|
|
27
37
|
/** kebab-case file name, e.g. `user-profile`. */
|
|
@@ -77,6 +87,7 @@ export async function generate(args: readonly string[]): Promise<number> {
|
|
|
77
87
|
options: {
|
|
78
88
|
root: { type: 'string', default: '.' },
|
|
79
89
|
'dry-run': { type: 'boolean', default: false },
|
|
90
|
+
validator: { type: 'string' },
|
|
80
91
|
},
|
|
81
92
|
});
|
|
82
93
|
const [kindArg, target] = positionals;
|
|
@@ -108,12 +119,20 @@ export async function generate(args: readonly string[]): Promise<number> {
|
|
|
108
119
|
return 1;
|
|
109
120
|
}
|
|
110
121
|
|
|
122
|
+
const validator = (values.validator ??
|
|
123
|
+
detectValidator(readJson(path.join(root, 'package.json')))) as Validator;
|
|
124
|
+
if (!VALIDATORS.includes(validator)) {
|
|
125
|
+
console.error(`Unknown validator "${validator}". Use ${VALIDATORS.join(', ')}.`);
|
|
126
|
+
return 2;
|
|
127
|
+
}
|
|
128
|
+
const syntax = validator === 'none' ? undefined : SYNTAX[validator];
|
|
129
|
+
|
|
111
130
|
const names = toNames(segments.at(-1) as string);
|
|
112
131
|
const dirs = segments.slice(0, -1);
|
|
113
132
|
const plan = new Plan();
|
|
114
133
|
try {
|
|
115
|
-
if (layout === 'modular') await modular(plan, src, kind, dirs, names);
|
|
116
|
-
else await minimal(plan, src, kind, dirs, names);
|
|
134
|
+
if (layout === 'modular') await modular(plan, src, kind, dirs, names, syntax);
|
|
135
|
+
else await minimal(plan, src, kind, dirs, names, syntax);
|
|
117
136
|
} catch (error) {
|
|
118
137
|
if (!(error instanceof GenerateError)) throw error;
|
|
119
138
|
console.error(error.message);
|
|
@@ -129,8 +148,34 @@ async function modular(
|
|
|
129
148
|
kind: Kind,
|
|
130
149
|
dirs: readonly string[],
|
|
131
150
|
names: Names,
|
|
151
|
+
syntax: SchemaSyntax | undefined,
|
|
132
152
|
): Promise<void> {
|
|
133
153
|
const folder = path.join(src, 'modules', ...dirs, names.file);
|
|
154
|
+
|
|
155
|
+
if (kind === 'resource') {
|
|
156
|
+
const module = path.join(folder, `${names.file}.module.ts`);
|
|
157
|
+
plan.create(module, resourceModule(names));
|
|
158
|
+
if (syntax) {
|
|
159
|
+
const schemas = `./${names.file}.schemas.js`;
|
|
160
|
+
plan.create(path.join(folder, `${names.file}.schemas.ts`), resourceSchemas(names, syntax));
|
|
161
|
+
plan.create(
|
|
162
|
+
path.join(folder, `${names.file}.service.ts`),
|
|
163
|
+
typedService(names, true, schemas),
|
|
164
|
+
);
|
|
165
|
+
plan.create(
|
|
166
|
+
path.join(folder, `${names.file}.controller.ts`),
|
|
167
|
+
typedController(names, schemas),
|
|
168
|
+
);
|
|
169
|
+
} else {
|
|
170
|
+
plan.create(path.join(folder, `${names.file}.service.ts`), crudService(names, true));
|
|
171
|
+
plan.create(path.join(folder, `${names.file}.controller.ts`), crudController(names));
|
|
172
|
+
plan.notes.push(UNVALIDATED);
|
|
173
|
+
}
|
|
174
|
+
const parent = nearestModule(plan, src, path.dirname(folder));
|
|
175
|
+
await register(plan, parent, 'imports', `${names.pascal}Module`, module);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
134
179
|
const file = path.join(folder, `${names.file}.${kind}.ts`);
|
|
135
180
|
const className = `${names.pascal}${capitalize(kind)}`;
|
|
136
181
|
|
|
@@ -206,6 +251,7 @@ async function minimal(
|
|
|
206
251
|
kind: Kind,
|
|
207
252
|
dirs: readonly string[],
|
|
208
253
|
names: Names,
|
|
254
|
+
syntax: SchemaSyntax | undefined,
|
|
209
255
|
): Promise<void> {
|
|
210
256
|
if (kind === 'module') {
|
|
211
257
|
throw new GenerateError(
|
|
@@ -216,6 +262,26 @@ async function minimal(
|
|
|
216
262
|
const service = path.join(src, 'services', ...dirs, `${names.file}.ts`);
|
|
217
263
|
const serviceClass = `${names.pascal}Service`;
|
|
218
264
|
|
|
265
|
+
if (kind === 'resource') {
|
|
266
|
+
const routes = path.join(src, 'routes', ...dirs, `${names.file}.ts`);
|
|
267
|
+
if (syntax) {
|
|
268
|
+
const schemas = path.join(src, 'schemas', ...dirs, `${names.file}.ts`);
|
|
269
|
+
plan.create(schemas, resourceSchemas(names, syntax));
|
|
270
|
+
plan.create(service, typedService(names, false, importPath(service, schemas)));
|
|
271
|
+
plan.create(
|
|
272
|
+
routes,
|
|
273
|
+
typedRoutes(names, importPath(routes, service), importPath(routes, schemas)),
|
|
274
|
+
);
|
|
275
|
+
} else {
|
|
276
|
+
plan.create(service, crudService(names, false));
|
|
277
|
+
plan.create(routes, crudRoutes(names, importPath(routes, service)));
|
|
278
|
+
plan.notes.push(UNVALIDATED);
|
|
279
|
+
}
|
|
280
|
+
await register(plan, app, 'providers', serviceClass, service);
|
|
281
|
+
await register(plan, app, 'controllers', `${names.camel}Routes`, routes);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
219
285
|
if (kind === 'service') {
|
|
220
286
|
plan.create(
|
|
221
287
|
service,
|
|
@@ -392,3 +458,332 @@ function toNames(file: string): Names {
|
|
|
392
458
|
function capitalize(value: string): string {
|
|
393
459
|
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
394
460
|
}
|
|
461
|
+
|
|
462
|
+
/** `posts` → `Post`, `categories` → `Category`; names without a plural `s` stay as they are. */
|
|
463
|
+
function singular(pascal: string): string {
|
|
464
|
+
if (pascal.endsWith('ies')) return `${pascal.slice(0, -3)}y`;
|
|
465
|
+
if (pascal.endsWith('s') && !pascal.endsWith('ss')) return pascal.slice(0, -1);
|
|
466
|
+
return pascal;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function resourceModule(names: Names): string {
|
|
470
|
+
return `import { Module } from '@avijs/core';
|
|
471
|
+
import { ${names.pascal}Controller } from './${names.file}.controller.js';
|
|
472
|
+
import { ${names.pascal}Service } from './${names.file}.service.js';
|
|
473
|
+
|
|
474
|
+
@Module({ controllers: [${names.pascal}Controller], providers: [${names.pascal}Service] })
|
|
475
|
+
export class ${names.pascal}Module {}
|
|
476
|
+
`;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function crudService(names: Names, decorated: boolean): string {
|
|
480
|
+
const item = singular(names.pascal);
|
|
481
|
+
const imports = decorated ? 'HttpError, Injectable' : 'HttpError';
|
|
482
|
+
return `import { ${imports} } from '@avijs/core';
|
|
483
|
+
|
|
484
|
+
export interface ${item} {
|
|
485
|
+
id: number;
|
|
486
|
+
[field: string]: unknown;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/** Keeps ${names.file} in memory. Swap the Map for your database; the routes stay the same. */
|
|
490
|
+
${decorated ? '@Injectable()\n' : ''}export class ${names.pascal}Service {
|
|
491
|
+
readonly #items = new Map<number, ${item}>();
|
|
492
|
+
#nextId = 1;
|
|
493
|
+
|
|
494
|
+
findAll(): ${item}[] {
|
|
495
|
+
return [...this.#items.values()];
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
findOne(id: number): ${item} {
|
|
499
|
+
const item = this.#items.get(id);
|
|
500
|
+
if (!item) throw new HttpError(404, \`${item} \${String(id)} not found\`);
|
|
501
|
+
return item;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
create(data: Record<string, unknown>): ${item} {
|
|
505
|
+
const item = { ...data, id: this.#nextId++ };
|
|
506
|
+
this.#items.set(item.id, item);
|
|
507
|
+
return item;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
update(id: number, data: Record<string, unknown>): ${item} {
|
|
511
|
+
const item = { ...this.findOne(id), ...data, id };
|
|
512
|
+
this.#items.set(id, item);
|
|
513
|
+
return item;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
remove(id: number): void {
|
|
517
|
+
this.findOne(id);
|
|
518
|
+
this.#items.delete(id);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
`;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Shared by the generated controller and routes. Replace them with route schemas
|
|
525
|
+
// (see the validation chapter) once the shape of the data is known.
|
|
526
|
+
const INPUT_HELPERS = `function idOf(value: string | undefined): number {
|
|
527
|
+
const id = Number(value);
|
|
528
|
+
if (!Number.isInteger(id)) throw new HttpError(400, 'id must be an integer');
|
|
529
|
+
return id;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function bodyOf(value: unknown): Record<string, unknown> {
|
|
533
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
534
|
+
throw new HttpError(400, 'Expected a JSON object');
|
|
535
|
+
}
|
|
536
|
+
return value as Record<string, unknown>;
|
|
537
|
+
}`;
|
|
538
|
+
|
|
539
|
+
function crudController(names: Names): string {
|
|
540
|
+
const service = `${names.pascal}Service`;
|
|
541
|
+
const field = names.camel;
|
|
542
|
+
return `import { Controller, Delete, Get, HttpError, Patch, Post, reply, type Context } from '@avijs/core';
|
|
543
|
+
import { ${service} } from './${names.file}.service.js';
|
|
544
|
+
|
|
545
|
+
${INPUT_HELPERS}
|
|
546
|
+
|
|
547
|
+
@Controller('/${names.file}')
|
|
548
|
+
export class ${names.pascal}Controller {
|
|
549
|
+
static inject = [${service}] as const;
|
|
550
|
+
|
|
551
|
+
constructor(private readonly ${field}: ${service}) {}
|
|
552
|
+
|
|
553
|
+
@Get()
|
|
554
|
+
findAll() {
|
|
555
|
+
return this.${field}.findAll();
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
@Get('/:id')
|
|
559
|
+
findOne(ctx: Context) {
|
|
560
|
+
return this.${field}.findOne(idOf(ctx.params['id']));
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
@Post()
|
|
564
|
+
async create(ctx: Context) {
|
|
565
|
+
return reply(this.${field}.create(bodyOf(await ctx.json())), { status: 201 });
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
@Patch('/:id')
|
|
569
|
+
async update(ctx: Context) {
|
|
570
|
+
return this.${field}.update(idOf(ctx.params['id']), bodyOf(await ctx.json()));
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
@Delete('/:id')
|
|
574
|
+
remove(ctx: Context) {
|
|
575
|
+
this.${field}.remove(idOf(ctx.params['id']));
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
`;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function crudRoutes(names: Names, serviceImport: string): string {
|
|
582
|
+
const service = `${names.pascal}Service`;
|
|
583
|
+
const field = names.camel;
|
|
584
|
+
return `import { HttpError, defineController, reply, route } from '@avijs/core';
|
|
585
|
+
import { ${service} } from '${serviceImport}';
|
|
586
|
+
|
|
587
|
+
${INPUT_HELPERS}
|
|
588
|
+
|
|
589
|
+
export const ${field}Routes = defineController({
|
|
590
|
+
prefix: '/${names.file}',
|
|
591
|
+
inject: [${service}],
|
|
592
|
+
routes: (${field}) => [
|
|
593
|
+
route({ method: 'GET', path: '/', handler: () => ${field}.findAll() }),
|
|
594
|
+
route({ method: 'GET', path: '/:id', handler: (ctx) => ${field}.findOne(idOf(ctx.params.id)) }),
|
|
595
|
+
route({
|
|
596
|
+
method: 'POST',
|
|
597
|
+
path: '/',
|
|
598
|
+
handler: async (ctx) => reply(${field}.create(bodyOf(await ctx.json())), { status: 201 }),
|
|
599
|
+
}),
|
|
600
|
+
route({
|
|
601
|
+
method: 'PATCH',
|
|
602
|
+
path: '/:id',
|
|
603
|
+
handler: async (ctx) => ${field}.update(idOf(ctx.params.id), bodyOf(await ctx.json())),
|
|
604
|
+
}),
|
|
605
|
+
route({
|
|
606
|
+
method: 'DELETE',
|
|
607
|
+
path: '/:id',
|
|
608
|
+
handler: (ctx) => {
|
|
609
|
+
${field}.remove(idOf(ctx.params.id));
|
|
610
|
+
},
|
|
611
|
+
}),
|
|
612
|
+
],
|
|
613
|
+
});
|
|
614
|
+
`;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function readJson(file: string): unknown {
|
|
618
|
+
try {
|
|
619
|
+
return JSON.parse(readFileSync(file, 'utf8')) as unknown;
|
|
620
|
+
} catch {
|
|
621
|
+
return undefined;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const UNVALIDATED =
|
|
626
|
+
'NOTE No validation library in package.json: request bodies are stored as sent. Install valibot\n or zod and generate with --validator to get schemas that reject unknown fields.';
|
|
627
|
+
|
|
628
|
+
/** Names of a resource's schemas: `posts` → `PostId`, `CreatePost`, `UpdatePost`. */
|
|
629
|
+
function schemaNames(names: Names) {
|
|
630
|
+
const item = singular(names.pascal);
|
|
631
|
+
return { item, id: `${item}Id`, create: `Create${item}`, update: `Update${item}` };
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function resourceSchemas(names: Names, syntax: SchemaSyntax): string {
|
|
635
|
+
const { id, create, update } = schemaNames(names);
|
|
636
|
+
return `${syntax.import}
|
|
637
|
+
|
|
638
|
+
export const ${id} = ${syntax.id};
|
|
639
|
+
|
|
640
|
+
// Add your fields here. Fields not listed are dropped from request bodies.
|
|
641
|
+
export const ${create} = ${syntax.object([['title', syntax.text(200, 'title is required')]])};
|
|
642
|
+
export type ${create} = ${syntax.infer(create)};
|
|
643
|
+
|
|
644
|
+
export const ${update} = ${syntax.partialNonEmpty(create)};
|
|
645
|
+
export type ${update} = ${syntax.infer(update)};
|
|
646
|
+
`;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function typedService(names: Names, decorated: boolean, schemas: string): string {
|
|
650
|
+
const { item, create, update } = schemaNames(names);
|
|
651
|
+
const imports = decorated ? 'HttpError, Injectable' : 'HttpError';
|
|
652
|
+
return `import { ${imports} } from '@avijs/core';
|
|
653
|
+
import type { ${create}, ${update} } from '${schemas}';
|
|
654
|
+
|
|
655
|
+
export type ${item} = { id: number } & ${create};
|
|
656
|
+
|
|
657
|
+
/** Keeps ${names.file} in memory. Swap the Map for your database; the routes stay the same. */
|
|
658
|
+
${decorated ? '@Injectable()\n' : ''}export class ${names.pascal}Service {
|
|
659
|
+
readonly #items = new Map<number, ${item}>();
|
|
660
|
+
#nextId = 1;
|
|
661
|
+
|
|
662
|
+
findAll(): ${item}[] {
|
|
663
|
+
return [...this.#items.values()];
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
findOne(id: number): ${item} {
|
|
667
|
+
const item = this.#items.get(id);
|
|
668
|
+
if (!item) throw new HttpError(404, \`${item} \${String(id)} not found\`);
|
|
669
|
+
return item;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
create(data: ${create}): ${item} {
|
|
673
|
+
const item = { ...data, id: this.#nextId++ };
|
|
674
|
+
this.#items.set(item.id, item);
|
|
675
|
+
return item;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
update(id: number, data: ${update}): ${item} {
|
|
679
|
+
const item = { ...this.findOne(id), ...data, id };
|
|
680
|
+
this.#items.set(id, item);
|
|
681
|
+
return item;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
remove(id: number): void {
|
|
685
|
+
this.findOne(id);
|
|
686
|
+
this.#items.delete(id);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
`;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function typedController(names: Names, schemas: string): string {
|
|
693
|
+
const { id, create, update } = schemaNames(names);
|
|
694
|
+
const service = `${names.pascal}Service`;
|
|
695
|
+
const field = names.camel;
|
|
696
|
+
return `import {
|
|
697
|
+
Controller,
|
|
698
|
+
Delete,
|
|
699
|
+
Get,
|
|
700
|
+
Patch,
|
|
701
|
+
Post,
|
|
702
|
+
reply,
|
|
703
|
+
type RouteContext,
|
|
704
|
+
type RouteSchema,
|
|
705
|
+
} from '@avijs/core';
|
|
706
|
+
import { ${create}, ${id}, ${update} } from '${schemas}';
|
|
707
|
+
import { ${service} } from './${names.file}.service.js';
|
|
708
|
+
|
|
709
|
+
const byId = { params: ${id} } satisfies RouteSchema;
|
|
710
|
+
const create = { body: ${create} } satisfies RouteSchema;
|
|
711
|
+
const update = { params: ${id}, body: ${update} } satisfies RouteSchema;
|
|
712
|
+
|
|
713
|
+
@Controller('/${names.file}')
|
|
714
|
+
export class ${names.pascal}Controller {
|
|
715
|
+
static inject = [${service}] as const;
|
|
716
|
+
|
|
717
|
+
constructor(private readonly ${field}: ${service}) {}
|
|
718
|
+
|
|
719
|
+
@Get()
|
|
720
|
+
findAll() {
|
|
721
|
+
return this.${field}.findAll();
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
@Get('/:id', { schema: byId })
|
|
725
|
+
findOne(ctx: RouteContext<typeof byId>) {
|
|
726
|
+
return this.${field}.findOne(ctx.input.params.id);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
@Post('/', { schema: create })
|
|
730
|
+
create(ctx: RouteContext<typeof create>) {
|
|
731
|
+
return reply(this.${field}.create(ctx.input.body), { status: 201 });
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
@Patch('/:id', { schema: update })
|
|
735
|
+
update(ctx: RouteContext<typeof update>) {
|
|
736
|
+
return this.${field}.update(ctx.input.params.id, ctx.input.body);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
@Delete('/:id', { schema: byId })
|
|
740
|
+
remove(ctx: RouteContext<typeof byId>) {
|
|
741
|
+
this.${field}.remove(ctx.input.params.id);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
`;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function typedRoutes(names: Names, serviceImport: string, schemas: string): string {
|
|
748
|
+
const { id, create, update } = schemaNames(names);
|
|
749
|
+
const service = `${names.pascal}Service`;
|
|
750
|
+
const field = names.camel;
|
|
751
|
+
return `import { defineController, reply, route } from '@avijs/core';
|
|
752
|
+
import { ${create}, ${id}, ${update} } from '${schemas}';
|
|
753
|
+
import { ${service} } from '${serviceImport}';
|
|
754
|
+
|
|
755
|
+
export const ${field}Routes = defineController({
|
|
756
|
+
prefix: '/${names.file}',
|
|
757
|
+
inject: [${service}],
|
|
758
|
+
routes: (${field}) => [
|
|
759
|
+
route({ method: 'GET', path: '/', handler: () => ${field}.findAll() }),
|
|
760
|
+
route({
|
|
761
|
+
method: 'GET',
|
|
762
|
+
path: '/:id',
|
|
763
|
+
schema: { params: ${id} },
|
|
764
|
+
handler: (ctx) => ${field}.findOne(ctx.input.params.id),
|
|
765
|
+
}),
|
|
766
|
+
route({
|
|
767
|
+
method: 'POST',
|
|
768
|
+
path: '/',
|
|
769
|
+
schema: { body: ${create} },
|
|
770
|
+
handler: (ctx) => reply(${field}.create(ctx.input.body), { status: 201 }),
|
|
771
|
+
}),
|
|
772
|
+
route({
|
|
773
|
+
method: 'PATCH',
|
|
774
|
+
path: '/:id',
|
|
775
|
+
schema: { params: ${id}, body: ${update} },
|
|
776
|
+
handler: (ctx) => ${field}.update(ctx.input.params.id, ctx.input.body),
|
|
777
|
+
}),
|
|
778
|
+
route({
|
|
779
|
+
method: 'DELETE',
|
|
780
|
+
path: '/:id',
|
|
781
|
+
schema: { params: ${id} },
|
|
782
|
+
handler: (ctx) => {
|
|
783
|
+
${field}.remove(ctx.input.params.id);
|
|
784
|
+
},
|
|
785
|
+
}),
|
|
786
|
+
],
|
|
787
|
+
});
|
|
788
|
+
`;
|
|
789
|
+
}
|
package/src/index.ts
CHANGED