with one click
mbc-migrate
// Guide version migrations for MBC CQRS Serverless framework. Use this when upgrading framework versions, migrating from deprecated APIs, or understanding breaking changes between versions.
// Guide version migrations for MBC CQRS Serverless framework. Use this when upgrading framework versions, migrating from deprecated APIs, or understanding breaking changes between versions.
[HINT] Download the complete skill directory including SKILL.md and all related files
| name | mbc-migrate |
| description | Guide version migrations for MBC CQRS Serverless framework. Use this when upgrading framework versions, migrating from deprecated APIs, or understanding breaking changes between versions. |
Before executing this skill, check for updates:
mbc install-skills --check to check if a newer version is availablembc install-skills --force to updateNote: Skip this check if the user explicitly says to skip updates or if you've already checked in this session.
This skill helps migrate MBC CQRS Serverless projects between versions.
| From Version | To Version | Migration Complexity | Key Changes |
|---|---|---|---|
| v1.0.16 | v1.0.17 | Low | MasterDataService.search() fix |
| v1.0.17 | v1.0.18 | Low | ImportStatusHandler.sendTaskFailure() |
| v1.0.18 | v1.0.19 | Low | ImportQueueEventHandler error handling |
| v1.0.19 | v1.0.20 | Low | CsvImportSfnEventHandler status fix |
| v1.0.20 | v1.0.21 | Medium | ZIP Finalization Hooks |
| v1.0.21 | v1.0.22 | Low | DynamoDB export filter fix |
| v1.0.22 | v1.0.23 | Low | sendInlineTemplateEmail() |
| v1.0.x | v1.1.0 | High | TENANT_COMMON → lowercase; publish() removed |
| v1.1.x | v1.1.4 | Low | publishSync audit trail (transparent) |
| v1.1.x | v1.1.5 | Medium | CSV import v2 batch architecture |
| v1.1.x | v1.2.0 | High | publishSync null return; genNewSequence() removed |
| v1.2.0 | v1.2.1 | Low | SqsService added (new feature) |
| v1.2.1 | v1.2.2 | Low | CsvBatchProcessor Poison Pill fix (transparent) |
| v1.2.x | v1.2.4 | Medium | TaskModule.register() must be in AppModule |
| v1.2.4 | v1.2.5 | Low | ZIP import refactor (transparent); MCP AP016–AP020 added |
| v1.2.5 | v1.2.6 | Low | Repository RYW improvements (transparent); getVersion API |
Breaking Change: MasterDataService.search() settingCode behavior
Before (v1.0.16):
// settingCode was incorrectly using partial match
const results = await masterDataService.search({
settingCode: 'CONFIG', // Matched 'CONFIG', 'CONFIG_A', 'CONFIG_B'
});
After (v1.0.17):
// settingCode now uses exact match
const results = await masterDataService.search({
settingCode: 'CONFIG', // Only matches 'CONFIG' exactly
});
Migration Steps:
MasterDataService.search() calls// If you need multiple settings
const configs = await Promise.all([
masterDataService.search({ settingCode: 'CONFIG_A' }),
masterDataService.search({ settingCode: 'CONFIG_B' }),
]);
New Feature: ImportStatusHandler.sendTaskFailure()
Addition:
// New method available for explicit failure signaling
await importStatusHandler.sendTaskFailure({
taskToken: event.taskToken,
error: 'ValidationError',
cause: 'Invalid CSV format',
});
Migration Steps:
sendTaskFailure() for better Step Functions integrationBug Fix: ImportQueueEventHandler error handling
Before (v1.0.18):
After (v1.0.19):
Migration Steps:
Bug Fix: CsvImportSfnEventHandler status determination
Before (v1.0.19):
After (v1.0.20):
FAILURE status when processedRows === 0Migration Steps:
New Feature: ZIP Finalization Hooks
Addition:
// New interface for ZIP import finalization
interface IZipFinalizationHook {
afterFinalize(context: ZipFinalizationContext): Promise<void>;
}
// Module registration
@Module({
imports: [
ImportModule.register({
zipFinalizationHooks: [MyCustomFinalizationHook],
}),
],
})
export class AppModule {}
Migration Steps:
Example Implementation:
@Injectable()
export class NotificationFinalizationHook implements IZipFinalizationHook {
constructor(private readonly notificationService: NotificationService) {}
async afterFinalize(context: ZipFinalizationContext): Promise<void> {
const { results, status } = context;
if (status === ImportStatusEnum.SUCCESS) {
await this.notificationService.sendSuccess({
message: `Import completed: ${results.processedRows} rows processed`,
});
} else {
await this.notificationService.sendFailure({
message: `Import failed: ${results.failedRows} rows failed`,
});
}
}
}
Bug Fix: DynamoDB export S3 filter
Before (v1.0.21):
After (v1.0.22):
Migration Steps:
New Feature: sendInlineTemplateEmail()
Addition:
// New method for inline email templates
await notificationService.sendInlineTemplateEmail({
to: ['user@example.com'],
subject: 'Order Confirmation - {{orderId}}',
htmlBody: '<h1>Thank you for your order!</h1><p>Order ID: {{orderId}}</p>',
textBody: 'Thank you for your order! Order ID: {{orderId}}',
templateData: {
orderId: 'ORD-12345',
},
});
Migration Steps:
1. TENANT_COMMON renamed to lowercase
// Before (v1.0.x)
const pk = `MASTER_SETTING#COMMON#${settingCode}`
// After (v1.1.0+)
const pk = `MASTER_SETTING#common#${settingCode}`
#COMMON must be migrated to #commonnormalizeTenantCode(), isCommonTenant()2. publish() and publishPartialUpdate() removed
// Removed — compilation error in v1.1.0+
this.commandService.publish(...)
this.commandService.publishPartialUpdate(...)
// Use instead
this.commandService.publishAsync(...)
this.commandService.publishPartialUpdateAsync(...)
publishSync now writes a full audit trail to Command and History tables (parity with async pipeline). No migration required; behavior is transparent.
Step Functions state machine changes required:
finalize_parent_job state is now requiredimport_tmp table is removedprocessedRows, succeededRows, failedRows) are aggregated at completion1. publishSync / publishPartialUpdateSync return type change
// Before (v1.1.x) — always returned CommandModel
const result = await commandService.publishSync(entity, options)
console.log(result.pk) // safe
// After (v1.2.0+) — returns null when command is not dirty (no-op)
const result = await commandService.publishSync(entity, options)
if (!result) return // no-op
console.log(result.pk) // safe after null check
2. SequenceService.genNewSequence() removed
// Removed — compilation error in v1.2.0+
await sequenceService.genNewSequence(...)
// Use instead
await sequenceService.generateSequenceItem(...)
// or
await sequenceService.generateSequenceItemWithProvideSetting(...)
3. Read-Your-Writes (RYW) consistency (new optional feature)
import { DetailKey, Repository } from '@mbc-cqrs-serverless/core'
// Enable: set RYW_SESSION_TTL_MINUTES env var (e.g. "5")
// Session table {NODE_ENV}-{APP_NAME}-session must be created
// No effect if unset — zero impact on existing projects
singleImportProcessor.process() now receives event.importEvent instead of event.payloadBoth fixes are transparent — no migration steps needed.
Breaking for apps using @mbc-cqrs-serverless/master
TaskModule.register() is now global (global: true) and must be called exactly once in the host AppModule. MasterModule no longer registers TaskModule internally.
// Before (v1.2.3 and earlier) — worked automatically
@Module({
imports: [MasterModule.register({ enableController: true, prismaService: PrismaService })],
})
export class AppModule {}
// After (v1.2.4+) — must register explicitly
import { TaskModule, TaskQueueEventFactory } from '@mbc-cqrs-serverless/master'
@Module({
imports: [
TaskModule.register({ taskQueueEventFactory: MyTaskQueueEventFactory }),
MasterModule.register({ enableController: true, prismaService: PrismaService }),
],
})
export class AppModule {}
Migration steps:
TaskQueueEventFactory from @mbc-cqrs-serverless/masterTaskModule.register() once in AppModuleTaskModule.register() from all feature modulesSymptom if skipped: App crashes at startup with Nest can't resolve dependencies of MyTaskService (?).
Framework changes (transparent):
ZipImportQueueEventHandler removed; ZIP import jobs are now processed directly inside ImportService.ImportEventHandler no longer publishes SQS messages for ZIP_MASTER_JOB events.CreateZipImportDto and improved error handling/logging.No application code changes are required. If you previously imported ZipImportQueueEventHandler directly (uncommon), remove the import.
MCP server changes:
mbc_check_anti_patterns tool expanded from 15 to 20 detectors (AP016–AP020 added):
@ApiTags (Low)getCommandSource for Tracing (Low)@modelcontextprotocol/sdk updated 1.26.0 → 1.29.0.The Repository class now actively purges stale RYW sessions when the data table catches up to or surpasses the session version. This eliminates the "stale override" issue where a user could see their own older write even after an external update was already absorbed into the data table.
Behavior changes (transparent — no code changes required):
getItem: when existing.version >= session.version, the session is purged in the background and the persisted data is returned directly (skipping the unnecessary command-table read).listItemsByPk: synchronized sessions are cleaned up in-place during the merge loop.listItems (RDS path): per-session checks are now parallelized via Promise.all, eliminating sequential N+1 latency.New optional optimization for listItems:
// If your RDS rows already carry `version`, supply `getVersion` to skip the
// extra DynamoDB GetItem entirely when the existing RDS row proves caught-up.
await repository.listItems(
() => rdsQuery(),
{
latestFlg: true,
transformCommand: (cmd) => ({
id: cmd.id,
version: cmd.version,
// ...map other CommandModel fields to your RDS row shape
}),
getVersion: (item) => item.version, // ← new in v1.2.6
},
options,
)
Note: getVersion only short-circuits the update path (when the session's itemId matches an existing RDS row). Create-new items (session present but not yet reflected in RDS) still fetch the command as before — there is no existing row to derive a version from.
All changes are backward-compatible: getVersion is optional, and the cleanup behavior is automatic when RYW_SESSION_TTL_MINUTES is enabled. Projects with RYW disabled (env var unset) are unaffected.
Deprecated in: v1.0.0 Removed in: TBD
Before:
await this.commandService.publish(command, options);
After:
await this.commandService.publishAsync(command, options);
Migration Script:
# Find all occurrences
grep -r "\.publish(" --include="*.ts" src/
# Replace (use with caution)
find src/ -name "*.ts" -exec sed -i '' 's/\.publish(/\.publishAsync(/g' {} \;
Deprecated in: v1.0.0 Removed in: TBD
Before:
await this.commandService.publishPartialUpdate(command, options);
After:
await this.commandService.publishPartialUpdateAsync(command, options);
Status: Not deprecated, but use sparingly
Recommendation:
// Only use publishSync when immediate consistency is required
// Example: User registration where we need the user ID immediately
const result = await this.commandService.publishSync(command, options);
// For most cases, prefer publishAsync
await this.commandService.publishAsync(command, options);
npm installWhen using this skill, Claude will:
Analyze Current Version:
npm list @mbc-cqrs-serverless/core
Check for Deprecated Usage:
# Search for deprecated patterns
grep -r "\.publish(" --include="*.ts" src/
grep -r "\.publishPartialUpdate(" --include="*.ts" src/
grep -r "publishSync" --include="*.ts" src/
Identify Breaking Changes:
Generate Migration Plan:
Cause: Version field handling changed Solution:
// Always fetch current version before update
const existing = await this.dataService.getItem(key);
await this.commandService.publishPartialUpdateAsync({
...updateData,
version: existing.version,
}, options);
Cause: Type mismatch in decorator Solution:
// Ensure type matches entity's type field
@DataSyncHandler({ type: 'ORDER' }) // Must match command.type
export class OrderDataSyncRdsHandler implements IDataSyncHandler {}
Cause: Missing error handling in v1.0.18 Solution: Upgrade to v1.0.19+ for proper error propagation
| Core Version | CLI Version | NestJS | Node.js | TypeScript |
|---|---|---|---|---|
| v1.0.23 | v1.0.23 | 10.x | 18+ | 5.x |
| v1.0.22 | v1.0.22 | 10.x | 18+ | 5.x |
| v1.0.21 | v1.0.21 | 10.x | 18+ | 5.x |
| v1.0.20 | v1.0.20 | 10.x | 18+ | 5.x |
If you encounter migration issues: