@mbc-cqrs-serverless/import 1.0.16 → 1.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +313 -26
  2. package/package.json +3 -3
package/README.md CHANGED
@@ -1,55 +1,342 @@
1
1
  ![MBC CQRS serverless framework](https://mbc-cqrs-serverless.mbc-net.com/img/mbc-cqrs-serverless.png)
2
2
 
3
+ # @mbc-cqrs-serverless/import
3
4
 
4
- # MBC CQRS serverless framework Import package
5
+ [![npm version](https://badge.fury.io/js/@mbc-cqrs-serverless%2Fimport.svg)](https://www.npmjs.com/package/@mbc-cqrs-serverless/import)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
7
 
6
- A flexible and extensible module for handling data import within the `@mbc-cqrs-serverless` framework.
8
+ Flexible data import module for the MBC CQRS Serverless framework. Import data from REST APIs, CSV files, and ZIP archives with validation, transformation, and async processing.
7
9
 
8
- ## Core Features
10
+ ## Features
9
11
 
10
- - **Unified Architectural Design**: Facilitates the processing of data from REST API endpoints and CSV files through a singular, consistent set of core business logic.
12
+ - **Multi-Source Import**: Support for REST API, CSV files, and ZIP archives
13
+ - **Strategy Pattern**: Customizable validation and transformation per entity type
14
+ - **Two-Phase Processing**: Separate ingestion and business logic phases
15
+ - **Dual Processing Modes**: DIRECT mode for small files, STEP_FUNCTION for large-scale imports
16
+ - **Progress Tracking**: Real-time status updates via SNS notifications
17
+ - **Error Handling**: Built-in alarm notifications and row-level error tracking
11
18
 
12
- - **Strategy Pattern Implementation**: Enables comprehensive customization of validation, transformation, and processing logic for each data entity via NestJS providers.
19
+ ## Installation
13
20
 
14
- - **Asynchronous, Event-Driven Processing**: Guarantees maximum scalability and resilience by processing all tasks asynchronously through a pipeline of DynamoDB Streams, SNS, and SQS.
21
+ ```bash
22
+ npm install @mbc-cqrs-serverless/import
23
+ ```
15
24
 
16
- - **Biphasic Processing Model**: Establishes a distinct separation of concerns between the initial data ingestion and validation phase and the subsequent business logic execution.
25
+ ## Quick Start
17
26
 
18
- - **Dual CSV Processing Modes**: Offers the flexibility to select between `DIRECT` processing for smaller files and a resilient `STEP_FUNCTION` workflow for large-scale, mission-critical import operations.
27
+ ### 1. Register the Module
19
28
 
20
- ## Installation
29
+ ```typescript
30
+ import { Module } from '@nestjs/common';
31
+ import { ImportModule } from '@mbc-cqrs-serverless/import';
32
+ import { ProductImportStrategy } from './strategies/product-import.strategy';
33
+ import { ProductProcessStrategy } from './strategies/product-process.strategy';
34
+ import { ProductModule } from './product/product.module';
21
35
 
22
- ```sh
23
- npm install @mbc-cqrs-serverless/import
36
+ @Module({
37
+ imports: [
38
+ ImportModule.register({
39
+ profiles: [
40
+ {
41
+ tableName: 'product',
42
+ importStrategy: ProductImportStrategy,
43
+ processStrategy: ProductProcessStrategy,
44
+ },
45
+ ],
46
+ imports: [ProductModule], // Optional: modules that provide strategy dependencies
47
+ enableController: true, // Optional: enable REST endpoints
48
+ }),
49
+ ],
50
+ })
51
+ export class AppModule {}
52
+ ```
53
+
54
+ ### 2. Implement Import Strategy
55
+
56
+ ```typescript
57
+ import { Injectable } from '@nestjs/common';
58
+ import { IImportStrategy } from '@mbc-cqrs-serverless/import';
59
+
60
+ @Injectable()
61
+ export class ProductImportStrategy implements IImportStrategy<RawProductInput, ProductDto> {
62
+ async transform(input: RawProductInput): Promise<ProductDto> {
63
+ return {
64
+ code: input.product_code?.trim(),
65
+ name: input.product_name?.trim(),
66
+ price: parseFloat(input.price),
67
+ category: input.category?.toUpperCase(),
68
+ };
69
+ }
70
+
71
+ async validate(dto: ProductDto): Promise<void> {
72
+ if (!dto.code) throw new Error('Product code is required');
73
+ if (!dto.name) throw new Error('Product name is required');
74
+ if (isNaN(dto.price) || dto.price < 0) throw new Error('Invalid price');
75
+ }
76
+ }
77
+ ```
78
+
79
+ ### 3. Implement Process Strategy
80
+
81
+ ```typescript
82
+ import { Injectable } from '@nestjs/common';
83
+ import {
84
+ IProcessStrategy,
85
+ ComparisonStatus,
86
+ ComparisonResult,
87
+ } from '@mbc-cqrs-serverless/import';
88
+ import { CommandService, DataModel, CommandInputModel } from '@mbc-cqrs-serverless/core';
89
+
90
+ // Define your entity model
91
+ interface ProductModel extends DataModel {
92
+ code: string;
93
+ name: string;
94
+ price: number;
95
+ }
96
+
97
+ @Injectable()
98
+ export class ProductProcessStrategy implements IProcessStrategy<ProductModel, ProductDto> {
99
+ constructor(
100
+ private readonly productService: ProductService,
101
+ private readonly commandService: CommandService,
102
+ ) {}
103
+
104
+ async compare(dto: ProductDto, tenantCode: string): Promise<ComparisonResult<ProductModel>> {
105
+ const existing = await this.productService.findByCode(dto.code, tenantCode);
106
+ if (!existing) {
107
+ return { status: ComparisonStatus.NOT_EXIST };
108
+ }
109
+ if (this.hasChanges(existing, dto)) {
110
+ return { status: ComparisonStatus.CHANGED, existingData: existing };
111
+ }
112
+ return { status: ComparisonStatus.EQUAL };
113
+ }
114
+
115
+ async map(
116
+ status: ComparisonStatus.NOT_EXIST | ComparisonStatus.CHANGED,
117
+ dto: ProductDto,
118
+ tenantCode: string,
119
+ existingData?: ProductModel,
120
+ ): Promise<CommandInputModel> {
121
+ return {
122
+ pk: `PRODUCT#${tenantCode}`,
123
+ sk: `PRODUCT#${dto.code}`,
124
+ code: dto.code,
125
+ name: dto.name,
126
+ attributes: { price: dto.price },
127
+ };
128
+ }
129
+
130
+ getCommandService(): CommandService {
131
+ return this.commandService;
132
+ }
133
+ }
24
134
  ```
25
- ## Architecture Overview
26
135
 
27
- The module operates on a powerful two-phase architecture, ensuring a clean separation of concerns.
136
+ ## Architecture
28
137
 
29
- 1. The Import Phase (Ingestion)
138
+ The import module uses a two-phase architecture:
30
139
 
31
- This phase is responsible for getting data into the system. It's handled by a class that implements the `IImportStrategy` interface.
140
+ ```
141
+ ┌─────────────────────────────────────────────────────────────────┐
142
+ │ Import Architecture │
143
+ ├─────────────────────────────────────────────────────────────────┤
144
+ │ │
145
+ │ Phase 1: Ingestion │
146
+ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
147
+ │ │ REST API / │────▶│ ImportStrategy│────▶│ Temp Table │ │
148
+ │ │ CSV / ZIP │ │ transform() │ │ (CREATED) │ │
149
+ │ └──────────────┘ │ validate() │ └──────────────┘ │
150
+ │ └──────────────┘ │ │
151
+ │ ▼ DynamoDB │
152
+ │ Phase 2: Processing │ Streams │
153
+ │ ┌──────────────┐ ┌──────────────┐ ┌─────┴────────┐ │
154
+ │ │ ProcessStrategy│◀───│ SNS/SQS │◀────│ Lambda │ │
155
+ │ │ compare() │ │ Event │ │ Trigger │ │
156
+ │ │ map() │ └──────────────┘ └──────────────┘ │
157
+ │ └──────────────┘ │
158
+ │ │ │
159
+ │ ▼ │
160
+ │ ┌──────────────┐ ┌──────────────┐ │
161
+ │ │ CommandService│────▶│ Final Table │ │
162
+ │ │ publish() │ │ (Data Store) │ │
163
+ │ └──────────────┘ └──────────────┘ │
164
+ │ │
165
+ └─────────────────────────────────────────────────────────────────┘
166
+ ```
167
+
168
+ ## API Reference
169
+
170
+ ### ImportService
171
+
172
+ | Method | Description |
173
+ |--------|-------------|
174
+ | `createWithApi(dto, options)` | Import single record from REST API |
175
+ | `handleCsvImport(dto, options)` | Import from CSV file (DIRECT or STEP_FUNCTION) |
176
+ | `createCsvJob(dto, options)` | Create CSV import job for Step Functions |
177
+ | `createZipJob(dto, options)` | Create ZIP import job for Step Functions |
178
+ | `createImport(dto, options)` | Create import record in temp table |
179
+ | `updateStatus(key, status, payload?, attributes?, notifyId?)` | Update import status |
180
+ | `getImportByKey(key)` | Get import record by key |
181
+ | `incrementParentJobCounters(parentKey, childSucceeded)` | Update parent job progress |
182
+
183
+ ### IImportStrategy Interface
184
+
185
+ ```typescript
186
+ interface IImportStrategy<TInput extends object, TAttributesDto extends object> {
187
+ transform(input: TInput): Promise<TAttributesDto>;
188
+ validate(data: TAttributesDto): Promise<void>;
189
+ }
190
+ ```
32
191
 
33
- `transform(input)`: Takes a raw input object (from a JSON body or a CSV row) and transforms it into a standardized, validated DTO.
192
+ ### IProcessStrategy Interface
34
193
 
35
- `validate(dto)`: Validates the transformed DTO.
194
+ ```typescript
195
+ interface IProcessStrategy<TEntity extends DataModel, TAttributesDto extends object> {
196
+ compare(importAttributes: TAttributesDto, tenantCode: string): Promise<ComparisonResult<TEntity>>;
197
+ map(status: ComparisonStatus, importAttributes: TAttributesDto, tenantCode: string, existingData?: TEntity): Promise<CommandInputModel | CommandPartialInputModel>;
198
+ getCommandService(): CommandService;
199
+ }
200
+
201
+ interface ComparisonResult<TEntity> {
202
+ status: ComparisonStatus;
203
+ existingData?: TEntity; // Provided when status is CHANGED
204
+ }
205
+
206
+ enum ComparisonStatus {
207
+ NOT_EXIST = 'NOT_EXIST',
208
+ CHANGED = 'CHANGED',
209
+ EQUAL = 'EQUAL',
210
+ }
211
+ ```
212
+
213
+ ### ImportStatusEnum
214
+
215
+ | Status | Description |
216
+ |--------|-------------|
217
+ | `CREATED` | Import record created |
218
+ | `PROCESSING` | Being processed |
219
+ | `COMPLETED` | Successfully completed |
220
+ | `FAILED` | Processing failed |
221
+
222
+ ## Usage Examples
223
+
224
+ ### REST API Import
225
+
226
+ ```typescript
227
+ @Controller('import')
228
+ export class ImportController {
229
+ constructor(private readonly importService: ImportService) {}
230
+
231
+ @Post('product')
232
+ async importProduct(
233
+ @Body() dto: ImportProductDto,
234
+ @InvokeContext() ctx: IInvoke,
235
+ ) {
236
+ return this.importService.createWithApi(
237
+ {
238
+ tableName: 'product',
239
+ tenantCode: dto.tenantCode,
240
+ attributes: dto,
241
+ },
242
+ { invokeContext: ctx },
243
+ );
244
+ }
245
+ }
246
+ ```
247
+
248
+ ### CSV Import (Direct Mode)
249
+
250
+ Process small CSV files immediately:
251
+
252
+ ```typescript
253
+ async importSmallCsv(bucket: string, key: string, opts: { invokeContext: IInvoke }) {
254
+ return this.importService.handleCsvImport(
255
+ {
256
+ tenantCode: 'MBC',
257
+ tableName: 'product',
258
+ bucket,
259
+ key,
260
+ processingMode: 'DIRECT',
261
+ },
262
+ opts,
263
+ );
264
+ }
265
+ ```
266
+
267
+ ### CSV Import (Step Functions Mode)
268
+
269
+ Process large CSV files with Step Functions orchestration:
270
+
271
+ ```typescript
272
+ async importLargeCsv(bucket: string, key: string, opts: { invokeContext: IInvoke }) {
273
+ return this.importService.handleCsvImport(
274
+ {
275
+ tenantCode: 'MBC',
276
+ tableName: 'product',
277
+ bucket,
278
+ key,
279
+ processingMode: 'STEP_FUNCTION',
280
+ },
281
+ opts,
282
+ );
283
+ }
284
+ ```
285
+
286
+ ### ZIP Import
287
+
288
+ Import multiple CSV files from a ZIP archive:
289
+
290
+ ```typescript
291
+ async importZip(bucket: string, key: string, opts: { invokeContext: IInvoke }) {
292
+ return this.importService.createZipJob(
293
+ {
294
+ tenantCode: 'MBC',
295
+ bucket,
296
+ key,
297
+ },
298
+ opts,
299
+ );
300
+ }
301
+ ```
302
+
303
+ ### Check Import Status
304
+
305
+ ```typescript
306
+ async checkStatus(pk: string, sk: string) {
307
+ const importJob = await this.importService.getImportByKey({ pk, sk });
308
+ return {
309
+ status: importJob.status,
310
+ totalRows: importJob.totalRows,
311
+ processedRows: importJob.processedRows,
312
+ succeededRows: importJob.succeededRows,
313
+ failedRows: importJob.failedRows,
314
+ };
315
+ }
316
+ ```
36
317
 
37
- The result of this phase is a record in a temporary DynamoDB table with a CREATED status.
318
+ ## Processing Modes
38
319
 
39
- 2. The Process Phase (Business Logic)
320
+ | Mode | Use Case | Processing |
321
+ |------|----------|------------|
322
+ | `DIRECT` | Small files (< 1000 rows) | Immediate, synchronous |
323
+ | `STEP_FUNCTION` | Large files, mission-critical | Async, resilient, tracked |
40
324
 
41
- Once a record is in the temporary table, an event is triggered, and this phase begins. It's handled by a class that implements the `IProcessStrategy` interface.
325
+ ## Related Packages
42
326
 
43
- `compare(dto)`: Compares the data from the temporary table with data in the final destination table to determine if it's a new record (`NOT_EXIST`), a changed one (`CHANGED`), or identical (`EQUAL`).
327
+ | Package | Description |
328
+ |---------|-------------|
329
+ | [@mbc-cqrs-serverless/core](https://www.npmjs.com/package/@mbc-cqrs-serverless/core) | Core CQRS framework |
330
+ | [@mbc-cqrs-serverless/task](https://www.npmjs.com/package/@mbc-cqrs-serverless/task) | Task processing for async jobs |
44
331
 
45
- `map(status, dto)`: Based on the comparison, it constructs the final payload for a create or update command.
332
+ ## Documentation
46
333
 
47
- `getCommandService()`: Provides the correct `CommandService` to execute the final write operation.
334
+ Full documentation available at [https://mbc-cqrs-serverless.mbc-net.com/](https://mbc-cqrs-serverless.mbc-net.com/)
48
335
 
49
- After this phase, the record in the temporary table is updated to `COMPLETED` or `FAILED`, and the result is stored for auditing.
336
+ - [Import Service Guide](https://mbc-cqrs-serverless.mbc-net.com/docs/import-service)
50
337
 
51
338
  ## License
52
339
 
53
- Copyright © 2024, Murakami Business Consulting, Inc. https://www.mbc-net.com/
340
+ Copyright © 2024-2025, Murakami Business Consulting, Inc. [https://www.mbc-net.com/](https://www.mbc-net.com/)
54
341
 
55
- This project and sub projects are under the MIT License.
342
+ This project is under the [MIT License](../../LICENSE.txt).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mbc-cqrs-serverless/import",
3
- "version": "1.0.16",
3
+ "version": "1.0.17",
4
4
  "description": "Import module",
5
5
  "keywords": [
6
6
  "mbc",
@@ -41,8 +41,8 @@
41
41
  "access": "public"
42
42
  },
43
43
  "dependencies": {
44
- "@mbc-cqrs-serverless/core": "1.0.16",
44
+ "@mbc-cqrs-serverless/core": "1.0.17",
45
45
  "csv-parser": "^3.2.0"
46
46
  },
47
- "gitHead": "4ab967474160873735b8cb816272a7012b8940d5"
47
+ "gitHead": "1870821803cfb045c9c4d6bc18d513fa2558de1c"
48
48
  }