@mbc-cqrs-serverless/cli 0.1.50-beta.0 → 0.1.52-beta.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/dist/actions/generate.action.js +44 -0
- package/dist/actions/new.action.get-package-version.spec.js +29 -0
- package/dist/actions/new.action.is-latest-version.spec.js +36 -0
- package/dist/actions/new.action.js +12 -6
- package/dist/actions/new.action.spec.js +84 -0
- package/dist/actions/new.action.update-env-local.spec.js +35 -0
- package/dist/actions/new.action.use-package-version.spec.js +46 -0
- package/dist/actions/ui.action.js +7 -5
- package/dist/commands/command.input.js +2 -0
- package/dist/commands/generate.command.js +24 -0
- package/dist/commands/index.js +2 -0
- package/dist/index.js +13 -2
- package/dist/runners/abstract.runner.js +44 -0
- package/dist/runners/schematic.runner.js +21 -0
- package/dist/schematics/collection.json +34 -0
- package/dist/schematics/index.js +17 -0
- package/dist/schematics/lib/controller/controller.factory.js +40 -0
- package/dist/schematics/lib/controller/controller.factory.spec.js +79 -0
- package/dist/schematics/lib/controller/files/__name@dasherize__.controller.ts +11 -0
- package/dist/schematics/lib/controller/schema.json +18 -0
- package/dist/schematics/lib/controller/units/__name@dasherize__.controller.__specFileSuffix__.ts +19 -0
- package/dist/schematics/lib/dto/dto.factory.js +32 -0
- package/dist/schematics/lib/dto/dto.factory.spec.js +128 -0
- package/dist/schematics/lib/dto/files/dto/__name@dasherize__-attributes.dto.ts +6 -0
- package/dist/schematics/lib/dto/files/dto/__name@dasherize__-command.dto.ts +16 -0
- package/dist/schematics/lib/dto/files/dto/__name@dasherize__-create.dto.ts +18 -0
- package/dist/schematics/lib/dto/files/dto/__name@dasherize__-search.dto.ts +2 -0
- package/dist/schematics/lib/dto/files/dto/__name@dasherize__-update.dto.ts +34 -0
- package/dist/schematics/lib/dto/schema.json +18 -0
- package/dist/schematics/lib/entity/entity.factory.js +30 -0
- package/dist/schematics/lib/entity/entity.factory.spec.js +86 -0
- package/dist/schematics/lib/entity/files/entity/__name@dasherize__-command.entity.ts +12 -0
- package/dist/schematics/lib/entity/files/entity/__name@dasherize__-data-list.entity.ts +12 -0
- package/dist/schematics/lib/entity/files/entity/__name@dasherize__-data.entity.ts +12 -0
- package/dist/schematics/lib/entity/schema.json +18 -0
- package/dist/schematics/lib/module/files/async/__name@dasherize__.controller.ts +74 -0
- package/dist/schematics/lib/module/files/async/__name@dasherize__.module.ts +19 -0
- package/dist/schematics/lib/module/files/async/__name@dasherize__.service.ts +164 -0
- package/dist/schematics/lib/module/files/async/dto/__name@dasherize__-attributes.dto.ts +6 -0
- package/dist/schematics/lib/module/files/async/dto/__name@dasherize__-command.dto.ts +16 -0
- package/dist/schematics/lib/module/files/async/dto/__name@dasherize__-create.dto.ts +18 -0
- package/dist/schematics/lib/module/files/async/dto/__name@dasherize__-search.dto.ts +2 -0
- package/dist/schematics/lib/module/files/async/dto/__name@dasherize__-update.dto.ts +34 -0
- package/dist/schematics/lib/module/files/async/entity/__name@dasherize__-command.entity.ts +12 -0
- package/dist/schematics/lib/module/files/async/entity/__name@dasherize__-data-list.entity.ts +12 -0
- package/dist/schematics/lib/module/files/async/entity/__name@dasherize__-data.entity.ts +12 -0
- package/dist/schematics/lib/module/files/async/handler/__name@dasherize__-rds.handler.ts +62 -0
- package/dist/schematics/lib/module/files/sync/__name@dasherize__.controller.ts +74 -0
- package/dist/schematics/lib/module/files/sync/__name@dasherize__.module.ts +19 -0
- package/dist/schematics/lib/module/files/sync/__name@dasherize__.service.ts +164 -0
- package/dist/schematics/lib/module/files/sync/dto/__name@dasherize__-attributes.dto.ts +6 -0
- package/dist/schematics/lib/module/files/sync/dto/__name@dasherize__-command.dto.ts +16 -0
- package/dist/schematics/lib/module/files/sync/dto/__name@dasherize__-create.dto.ts +18 -0
- package/dist/schematics/lib/module/files/sync/dto/__name@dasherize__-search.dto.ts +2 -0
- package/dist/schematics/lib/module/files/sync/dto/__name@dasherize__-update.dto.ts +34 -0
- package/dist/schematics/lib/module/files/sync/entity/__name@dasherize__-command.entity.ts +12 -0
- package/dist/schematics/lib/module/files/sync/entity/__name@dasherize__-data-list.entity.ts +12 -0
- package/dist/schematics/lib/module/files/sync/entity/__name@dasherize__-data.entity.ts +12 -0
- package/dist/schematics/lib/module/files/sync/handler/__name@dasherize__-rds.handler.ts +62 -0
- package/dist/schematics/lib/module/module.factory.js +204 -0
- package/dist/schematics/lib/module/module.factory.spec.js +188 -0
- package/dist/schematics/lib/module/schema.json +28 -0
- package/dist/schematics/lib/module/units/__name@dasherize__.controller.__specFileSuffix__.ts +19 -0
- package/dist/schematics/lib/module/units/__name@dasherize__.service.__specFileSuffix__.ts +19 -0
- package/dist/schematics/lib/service/files/__name@dasherize__.service.ts +12 -0
- package/dist/schematics/lib/service/schema.json +18 -0
- package/dist/schematics/lib/service/service.factory.js +40 -0
- package/dist/schematics/lib/service/service.factory.spec.js +81 -0
- package/dist/schematics/lib/service/units/__name@dasherize__.service.__specFileSuffix__.ts +19 -0
- package/dist/schematics/schematic.colection.js +60 -0
- package/dist/schematics/schematic.option.js +44 -0
- package/dist/schematics/utils/check-files-exist.js +13 -0
- package/dist/schematics/utils/index.js +17 -0
- package/dist/ui/index.js +18 -0
- package/dist/ui/logger.js +54 -0
- package/dist/ui/message.js +6 -0
- package/dist/utils/formatting.js +18 -0
- package/dist/utils/index.js +18 -0
- package/dist/utils/local-binaries.js +20 -0
- package/package.json +13 -4
- package/templates/.env.local +1 -1
- package/templates/README.md +1 -1
- package/templates/infra/README.md +6 -1
- package/templates/infra/libs/infra-stack.ts +1 -1
- package/templates/infra-local/docker-compose.yml +1 -1
- package/templates/infra-local/serverless.yml +19 -21
- package/templates/infra-local/swagger.json +396 -0
- package/templates/package.json +1 -0
- package/templates/prisma/dynamodbs/cqrs.json +1 -1
- package/templates/prisma/schema.prisma +4 -7
- package/templates/src/helpers/id.ts +12 -0
- package/templates/src/helpers/index.ts +1 -0
- package/templates/src/main.module.ts +2 -2
- package/templates/src/{master/dto/master-attributes.dto.ts → sample/dto/sample-attributes.dto.ts} +2 -2
- package/templates/src/{master/dto/master-command.dto.ts → sample/dto/sample-command.dto.ts} +4 -4
- package/templates/src/sample/entity/sample-command.entity.ts +13 -0
- package/templates/src/sample/entity/sample-data-list.entity.ts +13 -0
- package/templates/src/sample/entity/sample-data.entity.ts +13 -0
- package/templates/src/{master/handler/master-rds.handler.ts → sample/handler/sample-rds.handler.ts} +5 -7
- package/templates/src/{master/master.controller.ts → sample/sample.controller.ts} +22 -22
- package/templates/src/sample/sample.module.ts +19 -0
- package/templates/src/{master/master.service.ts → sample/sample.service.ts} +12 -12
- package/templates/test/api.http +25 -0
- package/templates/tsconfig.json +1 -1
- package/templates/src/master/entity/master-command.entity.ts +0 -13
- package/templates/src/master/entity/master-data-list.entity.ts +0 -13
- package/templates/src/master/entity/master-data.entity.ts +0 -13
- package/templates/src/master/master.module.ts +0 -19
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { PartialType } from '@nestjs/swagger'
|
|
2
|
+
import { Transform, Type } from 'class-transformer'
|
|
3
|
+
import {
|
|
4
|
+
IsBoolean,
|
|
5
|
+
IsOptional,
|
|
6
|
+
IsString,
|
|
7
|
+
ValidateNested,
|
|
8
|
+
} from 'class-validator'
|
|
9
|
+
|
|
10
|
+
import { <%= classify(name) %>Attributes } from './<%= dasherize(name) %>-attributes.dto'
|
|
11
|
+
|
|
12
|
+
export class <%= classify(name) %>UpdateAttributes extends PartialType(<%= classify(name) %>Attributes) {}
|
|
13
|
+
|
|
14
|
+
export class <%= classify(name) %>UpdateDto {
|
|
15
|
+
@IsString()
|
|
16
|
+
@IsOptional()
|
|
17
|
+
name?: string
|
|
18
|
+
|
|
19
|
+
@IsBoolean()
|
|
20
|
+
@Transform(({ value }) =>
|
|
21
|
+
value === 'true' ? true : value === 'false' ? false : value,
|
|
22
|
+
)
|
|
23
|
+
@IsOptional()
|
|
24
|
+
isDeleted?: boolean
|
|
25
|
+
|
|
26
|
+
@Type(() => <%= classify(name) %>UpdateAttributes)
|
|
27
|
+
@ValidateNested()
|
|
28
|
+
@IsOptional()
|
|
29
|
+
attributes?: <%= classify(name) %>UpdateAttributes
|
|
30
|
+
|
|
31
|
+
constructor(partial: Partial<<%= classify(name) %>UpdateDto>) {
|
|
32
|
+
Object.assign(this, partial)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { CommandEntity } from '@mbc-cqrs-serverless/core'
|
|
2
|
+
|
|
3
|
+
import { <%= classify(name) %>Attributes } from '../dto/<%= dasherize(name) %>-attributes.dto'
|
|
4
|
+
|
|
5
|
+
export class <%= classify(name) %>CommandEntity extends CommandEntity {
|
|
6
|
+
attributes: <%= classify(name) %>Attributes
|
|
7
|
+
|
|
8
|
+
constructor(partial: Partial<<%= classify(name) %>CommandEntity>) {
|
|
9
|
+
super()
|
|
10
|
+
Object.assign(this, partial)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { DataListEntity } from '@mbc-cqrs-serverless/core'
|
|
2
|
+
|
|
3
|
+
import { <%= classify(name) %>DataEntity } from './<%= dasherize(name) %>-data.entity'
|
|
4
|
+
|
|
5
|
+
export class <%= classify(name) %>DataListEntity extends DataListEntity {
|
|
6
|
+
items: <%= classify(name) %>DataEntity[]
|
|
7
|
+
|
|
8
|
+
constructor(partial: Partial<<%= classify(name) %>DataListEntity>) {
|
|
9
|
+
super(partial)
|
|
10
|
+
Object.assign(this, partial)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { DataEntity } from '@mbc-cqrs-serverless/core'
|
|
2
|
+
|
|
3
|
+
import { <%= classify(name) %>Attributes } from '../dto/<%= dasherize(name) %>-attributes.dto'
|
|
4
|
+
|
|
5
|
+
export class <%= classify(name) %>DataEntity extends DataEntity {
|
|
6
|
+
attributes: <%= classify(name) %>Attributes
|
|
7
|
+
|
|
8
|
+
constructor(partial: Partial<<%= classify(name) %>DataEntity>) {
|
|
9
|
+
super(partial)
|
|
10
|
+
Object.assign(this, partial)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CommandModel,
|
|
3
|
+
IDataSyncHandler,
|
|
4
|
+
removeSortKeyVersion,
|
|
5
|
+
} from '@mbc-cqrs-serverless/core'
|
|
6
|
+
import { Injectable, Logger } from '@nestjs/common'
|
|
7
|
+
import { PrismaService } from 'src/prisma'
|
|
8
|
+
|
|
9
|
+
import { <%= classify(name) %>Attributes } from '../dto/<%= dasherize(name) %>-attributes.dto'
|
|
10
|
+
|
|
11
|
+
@Injectable()
|
|
12
|
+
export class <%= classify(name) %>DataSyncRdsHandler implements IDataSyncHandler {
|
|
13
|
+
private readonly logger = new Logger(<%= classify(name) %>DataSyncRdsHandler.name)
|
|
14
|
+
|
|
15
|
+
constructor(private readonly prismaService: PrismaService) {}
|
|
16
|
+
|
|
17
|
+
async up(cmd: CommandModel): Promise<any> {
|
|
18
|
+
this.logger.debug(cmd)
|
|
19
|
+
const sk = removeSortKeyVersion(cmd.sk)
|
|
20
|
+
const attrs = cmd.attributes as <%= classify(name) %>Attributes
|
|
21
|
+
await this.prismaService.<%= camelize(name) %>.upsert({
|
|
22
|
+
where: {
|
|
23
|
+
id: cmd.id,
|
|
24
|
+
},
|
|
25
|
+
update: {
|
|
26
|
+
csk: cmd.sk,
|
|
27
|
+
name: cmd.name,
|
|
28
|
+
version: cmd.version,
|
|
29
|
+
seq: cmd.seq,
|
|
30
|
+
isDeleted: cmd.isDeleted || false,
|
|
31
|
+
updatedAt: cmd.updatedAt,
|
|
32
|
+
updatedBy: cmd.updatedBy,
|
|
33
|
+
updatedIp: cmd.updatedIp,
|
|
34
|
+
// attributes
|
|
35
|
+
attributes: attrs.value,
|
|
36
|
+
},
|
|
37
|
+
create: {
|
|
38
|
+
id: cmd.id,
|
|
39
|
+
cpk: cmd.pk,
|
|
40
|
+
csk: cmd.sk,
|
|
41
|
+
pk: cmd.pk,
|
|
42
|
+
sk,
|
|
43
|
+
code: sk,
|
|
44
|
+
name: cmd.name,
|
|
45
|
+
version: cmd.version,
|
|
46
|
+
tenantCode: cmd.tenantCode,
|
|
47
|
+
seq: cmd.seq,
|
|
48
|
+
createdAt: cmd.createdAt,
|
|
49
|
+
createdBy: cmd.createdBy,
|
|
50
|
+
createdIp: cmd.createdIp,
|
|
51
|
+
updatedAt: cmd.updatedAt,
|
|
52
|
+
updatedBy: cmd.updatedBy,
|
|
53
|
+
updatedIp: cmd.updatedIp,
|
|
54
|
+
// attributes
|
|
55
|
+
attributes: attrs.value,
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
async down(cmd: CommandModel): Promise<any> {
|
|
60
|
+
this.logger.debug(cmd)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.main = main;
|
|
37
|
+
const core_1 = require("@angular-devkit/core");
|
|
38
|
+
const schematics_1 = require("@angular-devkit/schematics");
|
|
39
|
+
const ts = __importStar(require("typescript"));
|
|
40
|
+
const yaml_1 = require("yaml");
|
|
41
|
+
function main(options) {
|
|
42
|
+
return (tree, _context) => {
|
|
43
|
+
const filePath = (0, core_1.normalize)(`/src/${core_1.strings.dasherize(options.name)}/${core_1.strings.dasherize(options.name)}.module.ts`);
|
|
44
|
+
const isFileExists = tree.exists(filePath);
|
|
45
|
+
if (isFileExists) {
|
|
46
|
+
_context.logger.info(`Module file already exists at: ${filePath}`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
updateMainModule(tree, options);
|
|
50
|
+
updateCdkInfraStack(tree, options);
|
|
51
|
+
updateCqrsTable(tree, options);
|
|
52
|
+
updateServerlessYaml(tree, options);
|
|
53
|
+
if (options.schema) {
|
|
54
|
+
updatePrismaSchema(tree, options);
|
|
55
|
+
}
|
|
56
|
+
return (0, schematics_1.chain)([createModule(options), createUnitTest(options)]);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// create rule
|
|
60
|
+
function createModule(options) {
|
|
61
|
+
return (0, schematics_1.mergeWith)((0, schematics_1.apply)((0, schematics_1.url)(`./files/${options.mode}`), [
|
|
62
|
+
options.schema ? (0, schematics_1.noop)() : (0, schematics_1.filter)((path) => !path.endsWith('.handler.ts')),
|
|
63
|
+
(0, schematics_1.template)({
|
|
64
|
+
...core_1.strings,
|
|
65
|
+
...options,
|
|
66
|
+
}),
|
|
67
|
+
(0, schematics_1.move)((0, core_1.normalize)(`/src/${core_1.strings.dasherize(options.name)}`)),
|
|
68
|
+
]));
|
|
69
|
+
}
|
|
70
|
+
function createUnitTest(options) {
|
|
71
|
+
return (0, schematics_1.mergeWith)((0, schematics_1.apply)((0, schematics_1.url)('./units'), [
|
|
72
|
+
(0, schematics_1.template)({
|
|
73
|
+
...core_1.strings,
|
|
74
|
+
...options,
|
|
75
|
+
specFileSuffix: 'spec',
|
|
76
|
+
}),
|
|
77
|
+
(0, schematics_1.move)((0, core_1.normalize)(`/test/unit/${core_1.strings.dasherize(options.name)}`)),
|
|
78
|
+
]));
|
|
79
|
+
}
|
|
80
|
+
// modify main.module.ts
|
|
81
|
+
function updateMainModule(tree, options) {
|
|
82
|
+
const mainModulePath = 'src/main.module.ts';
|
|
83
|
+
const isMainModulePathExists = tree.exists(mainModulePath);
|
|
84
|
+
if (isMainModulePathExists) {
|
|
85
|
+
const fileBuffer = tree.read(mainModulePath);
|
|
86
|
+
const content = fileBuffer.toString('utf-8');
|
|
87
|
+
const lines = content.split('\n');
|
|
88
|
+
lines.splice(5, 0, `import { ${core_1.strings.classify(options.name)}Module } from './${core_1.strings.dasherize(options.name)}/${core_1.strings.dasherize(options.name)}.module'`);
|
|
89
|
+
lines.splice(23, 0, ` ${core_1.strings.classify(options.name)}Module,`);
|
|
90
|
+
const newContent = lines.join('\n');
|
|
91
|
+
tree.overwrite(mainModulePath, newContent);
|
|
92
|
+
return tree;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// modify infra cdk
|
|
96
|
+
function updateCdkInfraStack(tree, options) {
|
|
97
|
+
const infraStackPath = 'infra/libs/infra-stack.ts';
|
|
98
|
+
const isInfraStackExists = tree.exists(infraStackPath);
|
|
99
|
+
if (isInfraStackExists) {
|
|
100
|
+
const fileBuffer = tree.read(infraStackPath);
|
|
101
|
+
const content = fileBuffer.toString('utf-8');
|
|
102
|
+
const sourceFile = ts.createSourceFile(infraStackPath, content, ts.ScriptTarget.Latest, true);
|
|
103
|
+
const updatedContent = updateTableNamesArray(sourceFile, [`${core_1.strings.dasherize(options.name)}-command`], content);
|
|
104
|
+
tree.overwrite(infraStackPath, updatedContent);
|
|
105
|
+
return tree;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function updateTableNamesArray(sourceFile, newTableNames, content) {
|
|
109
|
+
let updatedContent = content;
|
|
110
|
+
const visit = (node) => {
|
|
111
|
+
if (ts.isVariableDeclaration(node) &&
|
|
112
|
+
node.name.getText() === 'tableNames' &&
|
|
113
|
+
ts.isArrayLiteralExpression(node.initializer)) {
|
|
114
|
+
// Extract existing table names
|
|
115
|
+
const existingElements = node.initializer.elements.map((element) => element.getText().replace(/['"]/g, ''));
|
|
116
|
+
// Append new table names, ensuring no duplicates
|
|
117
|
+
const updatedTableNames = Array.from(new Set([...existingElements, ...newTableNames]));
|
|
118
|
+
// Generate the updated array string
|
|
119
|
+
const newArray = `[${updatedTableNames.map((name) => `'${name}'`).join(', ')}]`;
|
|
120
|
+
// Replace the existing array with the new array
|
|
121
|
+
updatedContent =
|
|
122
|
+
updatedContent.slice(0, node.initializer.getStart()) +
|
|
123
|
+
newArray +
|
|
124
|
+
updatedContent.slice(node.initializer.getEnd());
|
|
125
|
+
}
|
|
126
|
+
ts.forEachChild(node, visit);
|
|
127
|
+
};
|
|
128
|
+
visit(sourceFile);
|
|
129
|
+
return updatedContent;
|
|
130
|
+
}
|
|
131
|
+
// modify cqrs.json
|
|
132
|
+
function updateCqrsTable(tree, options) {
|
|
133
|
+
const cqrsTablePath = 'prisma/dynamodbs/cqrs.json';
|
|
134
|
+
const isCqrsTableExists = tree.exists(cqrsTablePath);
|
|
135
|
+
if (isCqrsTableExists) {
|
|
136
|
+
const fileContent = tree.read(cqrsTablePath)?.toString();
|
|
137
|
+
const jsonContent = JSON.parse(fileContent.toString());
|
|
138
|
+
jsonContent.push(core_1.strings.dasherize(options.name));
|
|
139
|
+
tree.overwrite(cqrsTablePath, JSON.stringify(jsonContent, null, 2));
|
|
140
|
+
return tree;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// modify prisma.schema
|
|
144
|
+
function updatePrismaSchema(tree, options) {
|
|
145
|
+
const schemaPath = 'prisma/schema.prisma';
|
|
146
|
+
const isSchemaExists = tree.exists(schemaPath);
|
|
147
|
+
if (isSchemaExists) {
|
|
148
|
+
const fileContent = tree.read(schemaPath)?.toString('utf-8');
|
|
149
|
+
const stringToAppend = generateModelTemplate(options.name);
|
|
150
|
+
const updatedContent = fileContent + stringToAppend;
|
|
151
|
+
tree.overwrite(schemaPath, updatedContent);
|
|
152
|
+
return tree;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// modify serverless.yaml
|
|
156
|
+
function updateServerlessYaml(tree, options) {
|
|
157
|
+
const serverlessPath = 'infra-local/serverless.yml';
|
|
158
|
+
const isServerlessExists = tree.exists(serverlessPath);
|
|
159
|
+
if (isServerlessExists) {
|
|
160
|
+
const fileContent = tree.read(serverlessPath)?.toString('utf-8');
|
|
161
|
+
const newStreamEvent = {
|
|
162
|
+
type: 'dynamodb',
|
|
163
|
+
maximumRetryAttempts: 10,
|
|
164
|
+
arn: '${env:LOCAL_DDB_%%TABLE_NAME%%_STREAM}'.replace('%%TABLE_NAME%%', options.name.toUpperCase()),
|
|
165
|
+
filterPatterns: [{ eventName: ['INSERT'] }],
|
|
166
|
+
};
|
|
167
|
+
const doc = (0, yaml_1.parseDocument)(fileContent);
|
|
168
|
+
const mainFunction = doc.getIn(['functions', 'main']);
|
|
169
|
+
const events = mainFunction.get('events');
|
|
170
|
+
events.items.push({ stream: newStreamEvent });
|
|
171
|
+
const updatedYamlContent = (0, yaml_1.stringify)(doc);
|
|
172
|
+
tree.overwrite(serverlessPath, updatedYamlContent);
|
|
173
|
+
return tree;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const generateModelTemplate = (name) => `
|
|
177
|
+
model ${core_1.strings.classify(name)} {
|
|
178
|
+
id String @id
|
|
179
|
+
cpk String // コマンド用PK
|
|
180
|
+
csk String // コマンド用SK
|
|
181
|
+
pk String // データ用PK, ${name.toUpperCase()}#tenantCode (テナントコード)
|
|
182
|
+
sk String // データ用SK, マスタ種別コード#マスタコード
|
|
183
|
+
tenantCode String @map("tenant_code") // テナントコード, 【テナントコードマスタ】
|
|
184
|
+
seq Int @default(0) // 並び順, 採番機能を使用する
|
|
185
|
+
code String // レコードのコード, マスタ種別コード#マスタコード
|
|
186
|
+
name String // レコード名, 名前
|
|
187
|
+
version Int // バージョン
|
|
188
|
+
isDeleted Boolean @default(false) @map("is_deleted") // 削除フラグ
|
|
189
|
+
createdBy String @default("") @map("created_by") // 作成者
|
|
190
|
+
createdIp String @default("") @map("created_ip") // 作成IP, IPv6も考慮する
|
|
191
|
+
createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) // 作成日時
|
|
192
|
+
updatedBy String @default("") @map("updated_by") // 更新者
|
|
193
|
+
updatedIp String @default("") @map("updated_ip") // 更新IP, IPv6も考慮する
|
|
194
|
+
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamp(0) // 更新日時
|
|
195
|
+
|
|
196
|
+
attributes Json? @map("attributes")
|
|
197
|
+
|
|
198
|
+
@@unique([cpk, csk])
|
|
199
|
+
@@unique([pk, sk])
|
|
200
|
+
@@unique([tenantCode, code])
|
|
201
|
+
@@index([tenantCode, name])
|
|
202
|
+
@@map("${core_1.strings.underscore(name)}s")
|
|
203
|
+
}
|
|
204
|
+
`;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const testing_1 = require("@angular-devkit/schematics/testing");
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
describe('Entity Factory', () => {
|
|
39
|
+
const runner = new testing_1.SchematicTestRunner('.', path.join(__dirname, '../../collection.json'));
|
|
40
|
+
it('should generate full async template', async () => {
|
|
41
|
+
const options = {
|
|
42
|
+
name: 'foo',
|
|
43
|
+
mode: 'async',
|
|
44
|
+
schema: true,
|
|
45
|
+
};
|
|
46
|
+
const tree = await runner.runSchematic('module', options);
|
|
47
|
+
const files = tree.files;
|
|
48
|
+
// dto
|
|
49
|
+
expect(files.find((filename) => filename === '/src/foo/dto/foo-attributes.dto.ts')).toBeDefined();
|
|
50
|
+
expect(files.find((filename) => filename === '/src/foo/dto/foo-command.dto.ts')).toBeDefined();
|
|
51
|
+
expect(files.find((filename) => filename === '/src/foo/dto/foo-create.dto.ts')).toBeDefined();
|
|
52
|
+
expect(files.find((filename) => filename === '/src/foo/dto/foo-search.dto.ts')).toBeDefined();
|
|
53
|
+
expect(files.find((filename) => filename === '/src/foo/dto/foo-update.dto.ts')).toBeDefined();
|
|
54
|
+
// entity
|
|
55
|
+
expect(files.find((filename) => filename === '/src/foo/entity/foo-command.entity.ts')).toBeDefined();
|
|
56
|
+
expect(files.find((filename) => filename === '/src/foo/entity/foo-data-list.entity.ts')).toBeDefined();
|
|
57
|
+
expect(files.find((filename) => filename === '/src/foo/entity/foo-data.entity.ts')).toBeDefined();
|
|
58
|
+
// controller
|
|
59
|
+
expect(files.find((filename) => filename === '/src/foo/foo.controller.ts')).toBeDefined();
|
|
60
|
+
expect(files.find((filename) => filename === '/test/unit/foo/foo.controller.spec.ts')).toBeDefined();
|
|
61
|
+
// service
|
|
62
|
+
expect(files.find((filename) => filename === '/src/foo/foo.service.ts')).toBeDefined();
|
|
63
|
+
expect(files.find((filename) => filename === '/test/unit/foo/foo.service.spec.ts')).toBeDefined();
|
|
64
|
+
// handler
|
|
65
|
+
expect(files.find((filename) => filename === '/src/foo/handler/foo-rds.handler.ts')).toBeDefined();
|
|
66
|
+
// module
|
|
67
|
+
expect(files.find((filename) => filename === '/src/foo/foo.module.ts')).toBeDefined();
|
|
68
|
+
// Verify content of foo.service.ts
|
|
69
|
+
const serviceContent = tree.readContent('/src/foo/foo.service.ts');
|
|
70
|
+
// Check occurrences of specific strings
|
|
71
|
+
const publishPartialUpdateAsyncMatches = (serviceContent.match(/publishPartialUpdateAsync/g) || []).length;
|
|
72
|
+
const publishAsyncMatches = (serviceContent.match(/publishAsync/g) || [])
|
|
73
|
+
.length;
|
|
74
|
+
expect(publishPartialUpdateAsyncMatches).toBe(2);
|
|
75
|
+
expect(publishAsyncMatches).toBe(1);
|
|
76
|
+
});
|
|
77
|
+
it('should generate async template with no schema', async () => {
|
|
78
|
+
const options = {
|
|
79
|
+
name: 'foo',
|
|
80
|
+
mode: 'async',
|
|
81
|
+
schema: false,
|
|
82
|
+
};
|
|
83
|
+
const tree = await runner.runSchematic('module', options);
|
|
84
|
+
const files = tree.files;
|
|
85
|
+
// dto
|
|
86
|
+
expect(files.find((filename) => filename === '/src/foo/dto/foo-attributes.dto.ts')).toBeDefined();
|
|
87
|
+
expect(files.find((filename) => filename === '/src/foo/dto/foo-command.dto.ts')).toBeDefined();
|
|
88
|
+
expect(files.find((filename) => filename === '/src/foo/dto/foo-create.dto.ts')).toBeDefined();
|
|
89
|
+
expect(files.find((filename) => filename === '/src/foo/dto/foo-search.dto.ts')).toBeDefined();
|
|
90
|
+
expect(files.find((filename) => filename === '/src/foo/dto/foo-update.dto.ts')).toBeDefined();
|
|
91
|
+
// entity
|
|
92
|
+
expect(files.find((filename) => filename === '/src/foo/entity/foo-command.entity.ts')).toBeDefined();
|
|
93
|
+
expect(files.find((filename) => filename === '/src/foo/entity/foo-data-list.entity.ts')).toBeDefined();
|
|
94
|
+
expect(files.find((filename) => filename === '/src/foo/entity/foo-data.entity.ts')).toBeDefined();
|
|
95
|
+
// controller
|
|
96
|
+
expect(files.find((filename) => filename === '/src/foo/foo.controller.ts')).toBeDefined();
|
|
97
|
+
expect(files.find((filename) => filename === '/test/unit/foo/foo.controller.spec.ts')).toBeDefined();
|
|
98
|
+
// service
|
|
99
|
+
expect(files.find((filename) => filename === '/src/foo/foo.service.ts')).toBeDefined();
|
|
100
|
+
expect(files.find((filename) => filename === '/test/unit/foo/foo.service.spec.ts')).toBeDefined();
|
|
101
|
+
// handler
|
|
102
|
+
expect(files.find((filename) => filename === '/src/foo/handler/foo-rds.handler.ts')).not.toBeDefined();
|
|
103
|
+
// module
|
|
104
|
+
expect(files.find((filename) => filename === '/src/foo/foo.module.ts')).toBeDefined();
|
|
105
|
+
// Verify content of foo.service.ts
|
|
106
|
+
const serviceContent = tree.readContent('/src/foo/foo.service.ts');
|
|
107
|
+
// Check occurrences of specific strings
|
|
108
|
+
const publishPartialUpdateAsyncMatches = (serviceContent.match(/publishPartialUpdateAsync/g) || []).length;
|
|
109
|
+
const publishAsyncMatches = (serviceContent.match(/publishAsync/g) || [])
|
|
110
|
+
.length;
|
|
111
|
+
expect(publishPartialUpdateAsyncMatches).toBe(2);
|
|
112
|
+
expect(publishAsyncMatches).toBe(1);
|
|
113
|
+
});
|
|
114
|
+
it('should generate full sync template', async () => {
|
|
115
|
+
const options = {
|
|
116
|
+
name: 'foo',
|
|
117
|
+
mode: 'sync',
|
|
118
|
+
schema: true,
|
|
119
|
+
};
|
|
120
|
+
const tree = await runner.runSchematic('module', options);
|
|
121
|
+
const files = tree.files;
|
|
122
|
+
// dto
|
|
123
|
+
expect(files.find((filename) => filename === '/src/foo/dto/foo-attributes.dto.ts')).toBeDefined();
|
|
124
|
+
expect(files.find((filename) => filename === '/src/foo/dto/foo-command.dto.ts')).toBeDefined();
|
|
125
|
+
expect(files.find((filename) => filename === '/src/foo/dto/foo-create.dto.ts')).toBeDefined();
|
|
126
|
+
expect(files.find((filename) => filename === '/src/foo/dto/foo-search.dto.ts')).toBeDefined();
|
|
127
|
+
expect(files.find((filename) => filename === '/src/foo/dto/foo-update.dto.ts')).toBeDefined();
|
|
128
|
+
// entity
|
|
129
|
+
expect(files.find((filename) => filename === '/src/foo/entity/foo-command.entity.ts')).toBeDefined();
|
|
130
|
+
expect(files.find((filename) => filename === '/src/foo/entity/foo-data-list.entity.ts')).toBeDefined();
|
|
131
|
+
expect(files.find((filename) => filename === '/src/foo/entity/foo-data.entity.ts')).toBeDefined();
|
|
132
|
+
// controller
|
|
133
|
+
expect(files.find((filename) => filename === '/src/foo/foo.controller.ts')).toBeDefined();
|
|
134
|
+
expect(files.find((filename) => filename === '/test/unit/foo/foo.controller.spec.ts')).toBeDefined();
|
|
135
|
+
// service
|
|
136
|
+
expect(files.find((filename) => filename === '/src/foo/foo.service.ts')).toBeDefined();
|
|
137
|
+
expect(files.find((filename) => filename === '/test/unit/foo/foo.service.spec.ts')).toBeDefined();
|
|
138
|
+
// handler
|
|
139
|
+
expect(files.find((filename) => filename === '/src/foo/handler/foo-rds.handler.ts')).toBeDefined();
|
|
140
|
+
// module
|
|
141
|
+
expect(files.find((filename) => filename === '/src/foo/foo.module.ts')).toBeDefined();
|
|
142
|
+
// Verify content of foo.service.ts
|
|
143
|
+
const serviceContent = tree.readContent('/src/foo/foo.service.ts');
|
|
144
|
+
// Check occurrences of specific strings
|
|
145
|
+
const publishPartialUpdateAsyncMatches = (serviceContent.match(/publishPartialUpdateSync/g) || []).length;
|
|
146
|
+
const publishAsyncMatches = (serviceContent.match(/publishSync/g) || [])
|
|
147
|
+
.length;
|
|
148
|
+
expect(publishPartialUpdateAsyncMatches).toBe(2);
|
|
149
|
+
expect(publishAsyncMatches).toBe(1);
|
|
150
|
+
});
|
|
151
|
+
it('should generate full sync template with no schema', async () => {
|
|
152
|
+
const options = {
|
|
153
|
+
name: 'foo',
|
|
154
|
+
mode: 'sync',
|
|
155
|
+
schema: false,
|
|
156
|
+
};
|
|
157
|
+
const tree = await runner.runSchematic('module', options);
|
|
158
|
+
const files = tree.files;
|
|
159
|
+
// dto
|
|
160
|
+
expect(files.find((filename) => filename === '/src/foo/dto/foo-attributes.dto.ts')).toBeDefined();
|
|
161
|
+
expect(files.find((filename) => filename === '/src/foo/dto/foo-command.dto.ts')).toBeDefined();
|
|
162
|
+
expect(files.find((filename) => filename === '/src/foo/dto/foo-create.dto.ts')).toBeDefined();
|
|
163
|
+
expect(files.find((filename) => filename === '/src/foo/dto/foo-search.dto.ts')).toBeDefined();
|
|
164
|
+
expect(files.find((filename) => filename === '/src/foo/dto/foo-update.dto.ts')).toBeDefined();
|
|
165
|
+
// entity
|
|
166
|
+
expect(files.find((filename) => filename === '/src/foo/entity/foo-command.entity.ts')).toBeDefined();
|
|
167
|
+
expect(files.find((filename) => filename === '/src/foo/entity/foo-data-list.entity.ts')).toBeDefined();
|
|
168
|
+
expect(files.find((filename) => filename === '/src/foo/entity/foo-data.entity.ts')).toBeDefined();
|
|
169
|
+
// controller
|
|
170
|
+
expect(files.find((filename) => filename === '/src/foo/foo.controller.ts')).toBeDefined();
|
|
171
|
+
expect(files.find((filename) => filename === '/test/unit/foo/foo.controller.spec.ts')).toBeDefined();
|
|
172
|
+
// service
|
|
173
|
+
expect(files.find((filename) => filename === '/src/foo/foo.service.ts')).toBeDefined();
|
|
174
|
+
expect(files.find((filename) => filename === '/test/unit/foo/foo.service.spec.ts')).toBeDefined();
|
|
175
|
+
// handler
|
|
176
|
+
expect(files.find((filename) => filename === '/src/foo/handler/foo-rds.handler.ts')).not.toBeDefined();
|
|
177
|
+
// module
|
|
178
|
+
expect(files.find((filename) => filename === '/src/foo/foo.module.ts')).toBeDefined();
|
|
179
|
+
// Verify content of foo.service.ts
|
|
180
|
+
const serviceContent = tree.readContent('/src/foo/foo.service.ts');
|
|
181
|
+
// Check occurrences of specific strings
|
|
182
|
+
const publishPartialUpdateAsyncMatches = (serviceContent.match(/publishPartialUpdateSync/g) || []).length;
|
|
183
|
+
const publishAsyncMatches = (serviceContent.match(/publishSync/g) || [])
|
|
184
|
+
.length;
|
|
185
|
+
expect(publishPartialUpdateAsyncMatches).toBe(2);
|
|
186
|
+
expect(publishAsyncMatches).toBe(1);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"$id": "SchematicsMbcModule",
|
|
4
|
+
"title": "Mbc-cqrs-serverless Module Options Schema",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"name": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"description": "The name of the module.",
|
|
10
|
+
"$default": {
|
|
11
|
+
"$source": "argv",
|
|
12
|
+
"index": 0
|
|
13
|
+
},
|
|
14
|
+
"x-prompt": "What name would you like to use for the module?"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"mode": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "Command processing mode (async,sync).",
|
|
20
|
+
"default": "async"
|
|
21
|
+
},
|
|
22
|
+
"schema": {
|
|
23
|
+
"type": "boolean",
|
|
24
|
+
"description": "Flag to indicate if prisma schema is created.",
|
|
25
|
+
"default": true
|
|
26
|
+
},
|
|
27
|
+
"required": ["name"]
|
|
28
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createMock } from '@golevelup/ts-jest'
|
|
2
|
+
import { Test, TestingModule } from '@nestjs/testing'
|
|
3
|
+
import { <%= classify(name) %>Controller } from 'src/<%= dasherize(name) %>/<%= dasherize(name) %>.controller'
|
|
4
|
+
|
|
5
|
+
describe('<%= classify(name) %>Controller', () => {
|
|
6
|
+
let controller: <%= classify(name) %>Controller
|
|
7
|
+
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
const module: TestingModule = await Test.createTestingModule({
|
|
10
|
+
controllers: [<%= classify(name) %>Controller],
|
|
11
|
+
}).useMocker(createMock).compile()
|
|
12
|
+
|
|
13
|
+
controller = module.get<<%= classify(name) %>Controller>(<%= classify(name) %>Controller)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('should be defined', () => {
|
|
17
|
+
expect(controller).toBeDefined()
|
|
18
|
+
})
|
|
19
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createMock } from '@golevelup/ts-jest'
|
|
2
|
+
import { Test, TestingModule } from '@nestjs/testing'
|
|
3
|
+
import { <%= classify(name) %>Service } from 'src/<%= dasherize(name) %>/<%= dasherize(name) %>.service'
|
|
4
|
+
|
|
5
|
+
describe('<%= classify(name) %>Service', () => {
|
|
6
|
+
let service: <%= classify(name) %>Service
|
|
7
|
+
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
const module: TestingModule = await Test.createTestingModule({
|
|
10
|
+
controllers: [<%= classify(name) %>Service],
|
|
11
|
+
}).useMocker(createMock).compile()
|
|
12
|
+
|
|
13
|
+
service = module.get<<%= classify(name) %>Service>(<%= classify(name) %>Service)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('should be defined', () => {
|
|
17
|
+
expect(service).toBeDefined()
|
|
18
|
+
})
|
|
19
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { CommandService, DataService } from '@mbc-cqrs-serverless/core'
|
|
2
|
+
import { Injectable, Logger } from '@nestjs/common'
|
|
3
|
+
|
|
4
|
+
@Injectable()
|
|
5
|
+
export class <%= classify(name) %>Service {
|
|
6
|
+
private readonly logger = new Logger(<%= classify(name) %>Service.name)
|
|
7
|
+
|
|
8
|
+
constructor(
|
|
9
|
+
private readonly commandService: CommandService,
|
|
10
|
+
private readonly dataService: DataService,
|
|
11
|
+
) {}
|
|
12
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"$id": "SchematicsMbcService",
|
|
4
|
+
"title": "Mbc-cqrs-serverless Service Options Schema",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"name": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"description": "The name of the service.",
|
|
10
|
+
"$default": {
|
|
11
|
+
"$source": "argv",
|
|
12
|
+
"index": 0
|
|
13
|
+
},
|
|
14
|
+
"x-prompt": "What name would you like to use for the service?"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"required": ["name"]
|
|
18
|
+
}
|