@mbc-cqrs-serverless/core 0.1.49-beta.0 → 0.1.50-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/README.md CHANGED
@@ -1,99 +1,206 @@
1
1
  ![MBC CQRS serverless framework](https://mbc-cqrs-serverless.mbc-net.com/img/mbc-cqrs-serverless.png)
2
2
 
3
- # MBC CQRS serverless framework CORE package
3
+ # MBC CQRS serverless framework Core package
4
4
 
5
5
  ## Description
6
6
 
7
- This is the core package of the MBC CQRS Serverless framework. It provides the basic functionality of CQRS.
7
+ The Core package provides the foundational functionality for the MBC CQRS Serverless framework. It implements:
8
8
 
9
- ## Installation
9
+ - Command Query Responsibility Segregation (CQRS) patterns
10
+ - Event-driven architecture support
11
+ - Data persistence and retrieval
12
+ - Authentication and authorization
13
+ - Multi-tenant data isolation
14
+ - AWS service integrations
10
15
 
11
- To install `mbc` command, run:
16
+ ## Installation
12
17
 
13
18
  ```bash
14
- npm install -g @mbc-cqrs-serverless/cli
19
+ npm install @mbc-cqrs-serverless/core
15
20
  ```
16
21
 
17
22
  ## Usage
18
23
 
19
- ### `mbc new|n [projectName@version]`
24
+ ### Basic Setup
25
+
26
+ 1. Import and configure the core module:
27
+ ```typescript
28
+ import { CoreModule } from '@mbc-cqrs-serverless/core';
29
+ import { Module } from '@nestjs/common';
30
+
31
+ @Module({
32
+ imports: [
33
+ CoreModule.forRoot({
34
+ region: 'ap-northeast-1',
35
+ stage: 'dev',
36
+ }),
37
+ ],
38
+ })
39
+ export class AppModule {}
40
+ ```
20
41
 
21
- There are 3 usages for the new command:
42
+ ### Command Handling
43
+
44
+ 1. Create a command:
45
+ ```typescript
46
+ import { CommandInputModel } from '@mbc-cqrs-serverless/core';
47
+
48
+ export class CreateUserCommand implements CommandInputModel {
49
+ readonly pk: string;
50
+ readonly sk: string;
51
+ readonly id: string;
52
+
53
+ constructor(public readonly userId: string, public readonly userData: any) {
54
+ this.pk = `USER#${userId}`;
55
+ this.sk = `METADATA#${userId}`;
56
+ this.id = userId;
57
+ }
58
+ }
59
+ ```
22
60
 
23
- - `mbc new`
24
- - Creates a new project in the current folder using a default name with the latest framework version.
25
- - `mbc new [projectName]`
26
- - Creates a new project in the `projectName` folder using the latest framework version.
27
- - `mbc new [projectName@version]`
28
- - If the specified version exists, the CLI uses that exact version.
29
- - If the provided version is a prefix, the CLI uses the latest version matching that prefix.
30
- - If no matching version is found, the CLI logs an error and provides a list of available versions for the user.
61
+ 2. Implement a command handler:
62
+ ```typescript
63
+ import { CommandHandler, ICommandHandler } from '@mbc-cqrs-serverless/core';
31
64
 
32
- To change current directory
65
+ @CommandHandler(CreateUserCommand)
66
+ export class CreateUserHandler implements ICommandHandler<CreateUserCommand> {
67
+ async execute(command: CreateUserCommand): Promise<void> {
68
+ // Implementation
69
+ }
70
+ }
71
+ ```
33
72
 
34
- ```bash
35
- cd [projectName]
73
+ ### Event Handling
74
+
75
+ 1. Create an event handler:
76
+ ```typescript
77
+ import { EventsHandler, IEventHandler } from '@mbc-cqrs-serverless/core';
78
+ import { UserCreatedEvent } from './user-created.event';
79
+
80
+ @EventsHandler(UserCreatedEvent)
81
+ export class UserCreatedHandler implements IEventHandler<UserCreatedEvent> {
82
+ async handle(event: UserCreatedEvent): Promise<void> {
83
+ // Implementation
84
+ }
85
+ }
36
86
  ```
37
87
 
38
- ## Run the Development Server
39
- 1. Run npm run build to the build project using development mode.
40
- 2. Open in other terminal session and run npm run offline:docker
41
- 3. Open in other terminal session and run npm run migrate to migrate RDS and dynamoDB table
42
- 4. Finally, run npm run offline:sls to start serverless offline mode.
88
+ ### Data Access
89
+
90
+ 1. Use the DataService for persistence:
91
+ ```typescript
92
+ import { DataService, InjectDataService } from '@mbc-cqrs-serverless/core';
93
+
94
+ @Injectable()
95
+ export class UserService {
96
+ constructor(
97
+ @InjectDataService() private readonly dataService: DataService
98
+ ) {}
99
+
100
+ async getUser(userId: string): Promise<User> {
101
+ return this.dataService.findOne({
102
+ pk: `USER#${userId}`,
103
+ sk: `METADATA#${userId}`,
104
+ });
105
+ }
106
+ }
107
+ ```
43
108
 
44
- After the server runs successfully, you can see:
109
+ ### Authentication & Authorization
110
+
111
+ 1. Implement role-based access:
112
+ ```typescript
113
+ import { RolesGuard, Roles } from '@mbc-cqrs-serverless/core';
114
+
115
+ @Controller('users')
116
+ @UseGuards(RolesGuard)
117
+ export class UserController {
118
+ @Post()
119
+ @Roles('admin')
120
+ async createUser(@Body() userData: CreateUserDto): Promise<void> {
121
+ // Implementation
122
+ }
123
+ }
124
+ ```
45
125
 
46
- ```bash
47
- DEBUG[serverless-offline-sns][adapter]: successfully subscribed queue "http://localhost:9324/101010101010/notification-queue" to topic: "arn:aws:sns:ap-northeast-1:101010101010:MySnsTopic"
48
- Offline Lambda Server listening on http://localhost:4000
49
- serverless-offline-aws-eventbridge :: Plugin ready
50
- serverless-offline-aws-eventbridge :: Mock server running at port: 4010
51
- Starting Offline SQS at stage dev (ap-northeast-1)
52
- Starting Offline Dynamodb Streams at stage dev (ap-northeast-1)
53
-
54
- Starting Offline at stage dev (ap-northeast-1)
55
-
56
- Offline [http for lambda] listening on http://localhost:3002
57
- Function names exposed for local invocation by aws-sdk:
58
- * main: serverless-example-dev-main
59
- Configuring JWT Authorization: ANY /{proxy+}
60
-
61
- ┌────────────────────────────────────────────────────────────────────────┐
62
- │ │
63
- │ ANY | http://localhost:3000/api/public │
64
- │ POST | http://localhost:3000/2015-03-31/functions/main/invocations │
65
- │ ANY | http://localhost:3000/swagger-ui/{proxy*} │
66
- │ POST | http://localhost:3000/2015-03-31/functions/main/invocations │
67
- │ ANY | http://localhost:3000/{proxy*} │
68
- │ POST | http://localhost:3000/2015-03-31/functions/main/invocations │
69
- │ │
70
- └────────────────────────────────────────────────────────────────────────┘
71
-
72
- Server ready: http://localhost:3000 🚀
126
+ ### Multi-tenancy
127
+
128
+ 1. Configure tenant isolation:
129
+ ```typescript
130
+ import { TenantContext, UseTenant } from '@mbc-cqrs-serverless/core';
131
+
132
+ @Injectable()
133
+ export class UserService {
134
+ @UseTenant()
135
+ async getUsersForTenant(
136
+ @TenantContext() tenantId: string
137
+ ): Promise<User[]> {
138
+ // Implementation with automatic tenant isolation
139
+ }
140
+ }
73
141
  ```
74
142
 
75
- You can also use several endpoints:
76
-
77
- - API gateway: http://localhost:3000
78
- - Offline Lambda Server: http://localhost:4000
79
- - HTTP for lambda: http://localhost:3002
80
- - Step functions: http://localhost:8083
81
- - DynamoDB: http://localhost:8000
82
- - DynamoDB admin: http://localhost:8001
83
- - SNS: http://localhost:4002
84
- - SQS: http://localhost:9324
85
- - SQS admin: http://localhost:9325
86
- - Localstack: http://localhost:4566
87
- - AppSync: http://localhost:4001
88
- - Cognito: http://localhost:9229
89
- - EventBridge: http://localhost:4010
90
- - Simple Email Service: http://localhost:8005
91
- - Run `npx prisma studio` to open studio web: http://localhost:5000
143
+ ### AWS Integration
144
+
145
+ 1. Use AWS services:
146
+ ```typescript
147
+ import {
148
+ StepFunctionService,
149
+ NotificationService
150
+ } from '@mbc-cqrs-serverless/core';
151
+
152
+ @Injectable()
153
+ export class WorkflowService {
154
+ constructor(
155
+ private readonly stepFunctions: StepFunctionService,
156
+ private readonly notifications: NotificationService
157
+ ) {}
158
+
159
+ async startWorkflow(data: any): Promise<void> {
160
+ await this.stepFunctions.startExecution({
161
+ stateMachineArn: 'your-state-machine-arn',
162
+ input: JSON.stringify(data),
163
+ });
164
+ }
165
+ }
166
+ ```
92
167
 
168
+ ## Error Handling
169
+
170
+ The core package provides standardized error handling:
171
+
172
+ ```typescript
173
+ import {
174
+ CommandError,
175
+ ValidationError,
176
+ NotFoundError
177
+ } from '@mbc-cqrs-serverless/core';
178
+
179
+ @CommandHandler(UpdateUserCommand)
180
+ export class UpdateUserHandler implements ICommandHandler<UpdateUserCommand> {
181
+ async execute(command: UpdateUserCommand): Promise<void> {
182
+ if (!command.userId) {
183
+ throw new ValidationError('User ID is required');
184
+ }
185
+
186
+ const user = await this.userService.findById(command.userId);
187
+ if (!user) {
188
+ throw new NotFoundError(`User ${command.userId} not found`);
189
+ }
190
+
191
+ // Implementation
192
+ }
193
+ }
194
+ ```
93
195
 
94
196
  ## Documentation
95
197
 
96
- Visit https://mbc-cqrs-serverless.mbc-net.com/ to view the full documentation.
198
+ Visit https://mbc-cqrs-serverless.mbc-net.com/ to view the full documentation, including:
199
+ - Architecture overview
200
+ - Core concepts and patterns
201
+ - AWS integration details
202
+ - Security features
203
+ - API reference
97
204
 
98
205
  ## License
99
206
 
@@ -2,5 +2,7 @@ export * from './datetime';
2
2
  export * from './event-type';
3
3
  export * from './key';
4
4
  export * from './object';
5
+ export * from './serializer';
5
6
  export * from './source';
7
+ export { serializeToExternal, deserializeToInternal } from './serializer';
6
8
  export declare const IS_LAMBDA_RUNNING: boolean;
@@ -14,12 +14,17 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.IS_LAMBDA_RUNNING = void 0;
17
+ exports.IS_LAMBDA_RUNNING = exports.deserializeToInternal = exports.serializeToExternal = void 0;
18
18
  __exportStar(require("./datetime"), exports);
19
19
  __exportStar(require("./event-type"), exports);
20
20
  __exportStar(require("./key"), exports);
21
21
  __exportStar(require("./object"), exports);
22
+ __exportStar(require("./serializer"), exports);
22
23
  __exportStar(require("./source"), exports);
24
+ // Re-export serialization helpers for convenience
25
+ var serializer_1 = require("./serializer");
26
+ Object.defineProperty(exports, "serializeToExternal", { enumerable: true, get: function () { return serializer_1.serializeToExternal; } });
27
+ Object.defineProperty(exports, "deserializeToInternal", { enumerable: true, get: function () { return serializer_1.deserializeToInternal; } });
23
28
  exports.IS_LAMBDA_RUNNING = !!process.env.AWS_LAMBDA_FUNCTION_NAME &&
24
29
  !!process.env.AWS_LAMBDA_FUNCTION_VERSION &&
25
30
  !!process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/helpers/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,6CAA0B;AAC1B,+CAA4B;AAC5B,wCAAqB;AACrB,2CAAwB;AACxB,2CAAwB;AAEX,QAAA,iBAAiB,GAC5B,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB;IACtC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,2BAA2B;IACzC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,+BAA+B,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/helpers/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,6CAA0B;AAC1B,+CAA4B;AAC5B,wCAAqB;AACrB,2CAAwB;AACxB,+CAA4B;AAC5B,2CAAwB;AAExB,kDAAkD;AAClD,2CAAyE;AAAhE,iHAAA,mBAAmB,OAAA;AAAE,mHAAA,qBAAqB,OAAA;AAEtC,QAAA,iBAAiB,GAC5B,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB;IACtC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,2BAA2B;IACzC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,+BAA+B,CAAA"}
@@ -0,0 +1,19 @@
1
+ import { CommandEntity, DataEntity } from '../interfaces';
2
+ export interface SerializerOptions {
3
+ keepAttributes?: boolean;
4
+ flattenDepth?: number;
5
+ }
6
+ /**
7
+ * Converts internal DynamoDB structure to external flat structure
8
+ * @param item Internal item (CommandEntity or DataEntity)
9
+ * @param options Serialization options
10
+ * @returns Flattened external structure
11
+ */
12
+ export declare function serializeToExternal<T extends CommandEntity | DataEntity>(item: T | null | undefined, options?: SerializerOptions): Record<string, any> | null;
13
+ /**
14
+ * Converts external flat structure to internal DynamoDB structure
15
+ * @param data External flat structure
16
+ * @param EntityClass Entity class to instantiate (CommandEntity or DataEntity)
17
+ * @returns Internal structure
18
+ */
19
+ export declare function deserializeToInternal<T extends CommandEntity | DataEntity>(data: Record<string, any> | null | undefined, EntityClass: new () => T): T | null;
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.serializeToExternal = serializeToExternal;
4
+ exports.deserializeToInternal = deserializeToInternal;
5
+ /**
6
+ * Converts internal DynamoDB structure to external flat structure
7
+ * @param item Internal item (CommandEntity or DataEntity)
8
+ * @param options Serialization options
9
+ * @returns Flattened external structure
10
+ */
11
+ function serializeToExternal(item, options = {}) {
12
+ if (!item)
13
+ return null;
14
+ const result = {
15
+ id: `${item.pk}#${item.sk}`,
16
+ code: item.sk,
17
+ name: item.name,
18
+ };
19
+ // Copy first level properties
20
+ Object.keys(item).forEach(key => {
21
+ if (key !== 'attributes' && typeof item[key] !== 'undefined') {
22
+ result[key] = item[key];
23
+ }
24
+ });
25
+ // Handle attributes
26
+ if (item.attributes) {
27
+ Object.entries(item.attributes).forEach(([key, value]) => {
28
+ result[key] = value;
29
+ });
30
+ }
31
+ return result;
32
+ }
33
+ /**
34
+ * Converts external flat structure to internal DynamoDB structure
35
+ * @param data External flat structure
36
+ * @param EntityClass Entity class to instantiate (CommandEntity or DataEntity)
37
+ * @returns Internal structure
38
+ */
39
+ function deserializeToInternal(data, EntityClass) {
40
+ if (!data)
41
+ return null;
42
+ const [pk, sk] = (data.id || '').split('#');
43
+ const entity = new EntityClass();
44
+ const attributes = {};
45
+ // Set basic properties
46
+ entity.pk = pk;
47
+ entity.sk = sk || data.code;
48
+ entity.name = data.name;
49
+ // Copy entity-specific fields first
50
+ ['version', 'tenantCode', 'type', 'isDeleted', 'status', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy', 'createdIp', 'updatedIp'].forEach(key => {
51
+ if (key in data) {
52
+ entity[key] = data[key];
53
+ }
54
+ });
55
+ // Separate remaining fields into attributes
56
+ Object.keys(data).forEach(key => {
57
+ if (!['id', 'code', 'pk', 'sk', 'name', 'version', 'tenantCode', 'type', 'isDeleted', 'status', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy', 'createdIp', 'updatedIp'].includes(key)) {
58
+ if (key in entity) {
59
+ entity[key] = data[key];
60
+ }
61
+ else {
62
+ attributes[key] = data[key];
63
+ }
64
+ }
65
+ });
66
+ entity.attributes = attributes;
67
+ return entity;
68
+ }
69
+ //# sourceMappingURL=serializer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serializer.js","sourceRoot":"","sources":["../../src/helpers/serializer.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mbc-cqrs-serverless/core",
3
- "version": "0.1.49-beta.0",
3
+ "version": "0.1.50-beta.0",
4
4
  "description": "CQRS and event base core",
5
5
  "keywords": [
6
6
  "mbc",
@@ -15,7 +15,7 @@
15
15
  "fargate",
16
16
  "step-functions",
17
17
  "sqs",
18
- "typpescript"
18
+ "typescript"
19
19
  ],
20
20
  "main": "./dist/index.js",
21
21
  "types": "./dist/index.d.ts",
@@ -86,5 +86,5 @@
86
86
  "serverless-step-functions-local": "^0.5.1",
87
87
  "supertest": "^7.0.0"
88
88
  },
89
- "gitHead": "4fb13a54044022d4423830c985b51dd8ec58259a"
89
+ "gitHead": "d61475400eb1b00a1c427769875d2f056e48c73c"
90
90
  }