A Serverless Framework plugin that scans module registry files and stores feature mappings in DynamoDB for cross-module discovery
A Serverless Framework plugin that scans module registry files, discovers endpoints, and stores module feature mappings in DynamoDB for cross-module discovery and access control.
This plugin enables:
- Module Discovery: Automatically catalog all modules and their features
- Endpoint Mapping: Track which API endpoints belong to which features
- Access Control: Store IAM policies for cross-module permission management
- Service Registry: Provide a central registry for module-to-module communication
``bash`
npm install --save-dev serverless-plugin-module-registry
Add the plugin to your serverless.yml:
`yaml
plugins:
- '@hyperdrive.bot/serverless-composer' # Must be loaded first
- 'serverless-plugin-module-registry' # Load after composer
custom:
moduleRegistry:
tableName: ModuleRegistry # Optional: Custom table name
region: us-east-1 # Optional: Custom region
skipDynamoDB: false # Optional: Skip DynamoDB operations
`
Each module should have a registry/ folder:
``
serverless/modules/my-module/
โโโ functions/ # Existing serverless functions
โโโ resources/ # Existing serverless resources
โโโ registry/ # NEW: Registry definitions
โโโ module.yml # Module metadata
โโโ features/ # Feature definitions
โโโ user-management.yml
โโโ notification-system.yml
#### Module Metadata (registry/module.yml)
`yaml`
name: "My Module"
version: "1.0.0"
description: "Sample module for testing"
maintainer: "DevSquad Team"
tags:
- "sample"
- "testing"
#### Feature Definition (registry/features/user-management.yml)
`yaml
name: "User Management"
description: "CRUD operations for user lifecycle management"
version: "1.2.0"
endpoints:
- "POST/users"
- "GET/users"
- "GET/users/*"
- "PUT/users/*"
- "DELETE/users/*"
customPolicies:
- Effect: Allow
Action:
- "execute-api:Invoke"
Resource:
- "arn:aws:execute-api::://POST/users"
- "arn:aws:execute-api::://GET/users"
- "arn:aws:execute-api::://GET/users/*"
- Effect: Allow
Action:
- "dynamodb:PutItem"
- "dynamodb:GetItem"
- "dynamodb:Query"
Resource:
- "arn:aws:dynamodb:::table/Users"
`
The plugin creates an intermodular ModuleRegistry table using AWS SDK v3 (not CloudFormation) with the following structure:
โ Intermodular Independence: The table exists independently of any specific module's deployment lifecycle
โ Cross-Module Sharing: Multiple modules can write to the same registry without ownership conflicts
โ Deployment Flexibility: The table persists even if individual modules are removed or redeployed
โ No Stack Dependencies: No CloudFormation stack owns the table, preventing accidental deletion
(e.g., ds-api-live-module-registry)
- Compliance: Follows @dynamodb-tables.md standards (point-in-time recovery, tags)
- Lifecycle: Independent of individual module deployments$3
- PK: MODULE#{moduleName} (e.g., MODULE#sign)
- SK: FEATURE#{featureName} (e.g., FEATURE#user-management)$3
- GSI1PK: MODULES (for listing all modules)
- GSI1SK: MODULE#{moduleName} (for grouping by module)$3
- GSI2PK: FEATURES (for listing all features)
- GSI2SK: FEATURE#{featureId} (for direct feature lookup by ID)$3
`json
{
"PK": "MODULE#sign",
"SK": "FEATURE#user-management",
"GSI1PK": "MODULES",
"GSI1SK": "MODULE#sign",
"moduleName": "sign",
"featureName": "user-management",
"description": "CRUD operations for user lifecycle",
"version": "1.2.0",
"endpoints": [
"POST/users",
"GET/users",
"GET/users/*"
],
"customPolicies": [...],
"lastUpdated": "2025-01-01T00:00:00Z"
}
`๐ How It Works
1. After Composer: Plugin hooks after composer processes modules
2. Registry Scanning: Scans each module's
registry/ folder
3. Feature Processing: Reads and validates feature definitions
4. Table Management: Creates intermodular DynamoDB table via AWS SDK v3 (if doesn't exist)
5. Standards Compliance: Enables point-in-time recovery and applies compliance tags
6. Data Storage: Batch writes registry data to DynamoDB with optimized structure๐ Access Patterns
The stored data supports these query patterns:
$3
`javascript
// Query GSI1 with GSI1PK = "MODULES"
const modules = await dynamoClient.query({
IndexName: 'GSI1',
KeyConditionExpression: 'GSI1PK = :pk',
ExpressionAttributeValues: {
':pk': 'MODULES'
}
})
`$3
`javascript
// Query main table with PK = "MODULE#{name}"
const features = await dynamoClient.query({
KeyConditionExpression: 'PK = :pk',
ExpressionAttributeValues: {
':pk': MODULE#${moduleName}
}
})
`$3
`javascript
// Get item with PK + SK
const feature = await dynamoClient.getItem({
Key: {
PK: MODULE#${moduleName},
SK: FEATURE#${featureName}
}
})
`$3
`javascript
// Query GSI2 with featureId
const feature = await dynamoClient.query({
IndexName: 'GSI2',
KeyConditionExpression: 'GSI2PK = :pk AND GSI2SK = :sk',
ExpressionAttributeValues: {
':pk': 'FEATURES',
':sk': FEATURE#${featureId}
}
})
`โ๏ธ Configuration Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
|
tableName | string | {service}-{stage}-module-registry | DynamoDB table name |
| region | string | provider.region | AWS region for DynamoDB |
| skipDynamoDB | boolean | false | Skip DynamoDB operations |
| strict | boolean | false | Enforce registry folders on all modules |$3
Controls whether the plugin should enforce registry compliance:
strict: false (Default - Graceful Mode)
`yaml
custom:
moduleRegistry:
strict: false # Graceful: skip modules without registry folders
`
- โ
Graceful Migration: Modules without registry folders are silently skipped
- โ
Backwards Compatible: Existing modules continue to work unchanged
- โ
Gradual Adoption: Teams can add registry folders at their own pace
- โ
Production Safe: No deployment failures
strict: true (Enforcement Mode)
`yaml
custom:
moduleRegistry:
strict: true # Strict: error if modules are missing registry folders
`
- ๐จ Enforces Compliance: Deployment fails if modules don't have registry folders
- ๐ Quality Gate: Ensures all modules participate in the registry
- ๐ Documentation Requirement: Forces teams to document their module features
- โก Use After Migration: Enable once all modules have been updated$3
Use
strict: false when:
- ๐ Initial Rollout: Adding the plugin to existing systems
- ๐ Migration Phase: Teams are gradually adding registry folders
- ๐งช Testing Phase: Experimenting with the registry system
- ๐๏ธ Development: Working with modules that aren't fully documented yetUse
strict: true when:
- โ
Full Adoption: All modules have been migrated to use registry folders
- ๐ Governance: You want to enforce registry compliance as a quality gate
- ๐ Documentation Standards: Registry definitions are required for all modules
- ๐ Production Systems: Where complete module documentation is mandatory๐ Debugging
The plugin provides detailed logging with
[module-registry] prefix:`bash
serverless deploy --verbose
`Example output:
`
[module-registry] ๐ Scanning modules for registry definitions...
[module-registry] Found 3 modules: sample, sign, agents
[module-registry] ๐ Processing registry for module: sample
[module-registry] Found 2 feature definitions
[module-registry] โ user-management: 5 endpoints
[module-registry] โ notification-system: 9 endpoints
[module-registry] โ
Registry processing complete. Found 4 features across 3 modules
`๐ค Integration with Handlers - Direct Import Pattern
No need for separate service files! Import service functions directly from the plugin:
$3
`typescript
// In your handlers - import from the generated service package
import {
listAllModules,
getModuleFeatures,
getFeatureDetails,
createModuleRegistryLogger,
type ModuleInfo,
type FeatureInfo
} from 'module-registry'const logger = createModuleRegistryLogger('my-handler')
export const myHandler = async (event: any) => {
const modules = await listAllModules()
logger.info(
Found ${modules.length} modules)
return { modules }
}// Example: Get feature by ID without knowing module
export const getFeatureHandler = async (event: any) => {
const { featureId } = event.pathParameters
const feature = await getFeatureById(featureId)
if (!feature) {
return { statusCode: 404, body: { error: 'Feature not found' } }
}
return { statusCode: 200, body: feature }
}
`$3
`javascript
// In your handlers - require from the generated service package
const {
listAllModules,
getModuleFeatures,
createModuleRegistryLogger
} = require('module-registry')const logger = createModuleRegistryLogger('my-handler')
exports.myHandler = async (event) => {
const modules = await listAllModules()
logger.info(
Found ${modules.length} modules)
return { modules }
}
`$3
| Function | Description | Returns |
|----------|-------------|---------|
|
listAllModules() | List all deployed modules | Promise |
| getModuleFeatures(moduleName) | Get features for a module | Promise |
| getFeatureDetails(moduleName, featureName) | Get feature details | Promise |
| getFeatureById(featureId) | Get feature by ID only (uses GSI2) | Promise |
| getModuleMetadata(moduleName) | Get module metadata only | Promise |
| getAllEndpoints() | Get all endpoints across modules | Promise |
| createModuleRegistryLogger(context) | Create logger for service functions | Logger |$3
`javascript
// src/handlers/core/module-registry/listModules.js
const { http } = require('../../middlewares/http')
const { listAllModules, createModuleRegistryLogger } = require('serverless-plugin-module-registry')const logger = createModuleRegistryLogger('list-modules-handler')
export const listModules = http(async (event) => {
logger.info('Listing all deployed modules', {
userSub: event.requestContext.authorizer?.claims?.sub
})
try {
const modules = await listAllModules()
logger.info(
Successfully retrieved ${modules.length} modules)
return {
statusCode: 200,
body: {
modules,
count: modules.length,
timestamp: new Date().toISOString()
}
}
} catch (error) {
logger.error('Error listing modules:', error.message)
return {
statusCode: 500,
body: {
error: 'Failed to list modules',
message: error.message
}
}
}
})
`๐ CLI Commands
The plugin provides powerful CLI commands for managing module registries:
$3
Automatically generate registry files for a module using AI analysis:
`bash
Generate registry for a specific module
serverless registryGenerate --module workforceForce overwrite existing registry files
serverless registryGenerate --module workforce --force
`Prerequisites:
- Set
OPENROUTER_API_KEY environment variable for AI generation
- Module must have function descriptions in serverless function definitions
- Only functions with aws_iam authorizer are included in registryWhat it does:
1. Analyzes module structure (functions, resources, endpoints)
2. Uses AI (Claude 3.5 Sonnet) to generate AWS-style feature groupings
3. Creates
registry/module.yml and registry/features/*.yml files
4. Follows AWS IAM naming conventions (e.g., EmployeeReadAccess, UserSelfService)$3
Generate the virtual service package for importing registry functions:
`bash
Generate service package
serverless registryGeneratePackageForce regeneration
serverless registryGeneratePackage --force --verbose
`What it creates:
-
node_modules/module-registry/service.js - Service functions
- node_modules/module-registry/service.d.ts - TypeScript definitions
- node_modules/module-registry/env.js - Environment configuration
- node_modules/module-registry/package.json - Package manifest๐ ๏ธ Development
$3
- Node.js โฅ14.0.0
- TypeScript โฅ5.0
- Serverless Framework โฅ2.0.0
$3
`bash
Install dependencies
npm installBuild the plugin
npm run buildWatch for changes during development
npm run watchRun type checking
npm run typecheckRun linting
npm run lint
`$3
The project uses tsup for building:
- Entry:
src/index.ts (main plugin), src/service.ts (service functions)
- Output: dist/ directory with CommonJS and TypeScript definitions
- Target: Node.js 14+ compatibility
- External: Serverless Framework excluded from bundle$3
Tests are currently disabled but the framework is set up with Jest:
`bash
Tests are disabled - would run with:
npm test # Currently outputs: "Tests disabled"
`๐ง Troubleshooting
$3
Plugin not found
`
Error: Plugin "serverless-plugin-module-registry" not found
`
Solution: Ensure plugin is installed and listed in serverless.yml plugins sectionVariable resolution errors
`
Module Registry: Unresolved variables in configuration
`
Solution: Check that all variables in custom.moduleRegistry section can be resolvedDynamoDB table creation fails
`
Failed to create table: AccessDenied
`
Solution: Ensure AWS credentials have DynamoDB permissions:
- dynamodb:CreateTable
- dynamodb:DescribeTable
- dynamodb:UpdateContinuousBackups
- dynamodb:TagResourceAI generation fails
`
OPENROUTER_API_KEY environment variable is required
`
Solution: Set OpenRouter API key: export OPENROUTER_API_KEY=your_key_hereRegistry generation requires function descriptions
`
Function 'myFunction' is missing required 'description' field
`
Solution: Add descriptions to all functions in your serverless function definitions$3
Enable detailed logging:
`bash
serverless deploy --verbose
`Look for
[module-registry] prefixed logs for plugin-specific information.๐ Performance Considerations
$3
- Plugin creates one table per service/stage combination
- Uses Pay-Per-Request billing mode
- Table persists across deployments (not recreated)
- Point-in-time recovery enabled by default
$3
- Registry updates use batch writes (25 items per batch)
- Automatic cleanup of stale entries during deployment
- Optimized for sparse access patterns
$3
- IAM policies generated as CloudFormation resources
- Policy ARNs resolved at deployment time
- No runtime AWS API calls for policy management
๐ Tenant Role Creation
The module registry provides a utility function to create IAM roles for tenant onboarding with ABAC (Attribute-Based Access Control) tags.
$3
`typescript
import { createTenantRoles } from 'serverless-plugin-module-registry/tenant'// During tenant onboarding
const roleArns = await createTenantRoles({
tenantId: 'acme',
identityPoolId: 'us-east-1:12345678-1234-1234-1234-123456789012',
modules: ['sign', 'workforce'], // Optional: filter to specific modules
config: {
tableName: 'ModuleRegistry',
region: 'us-east-1',
policyPrefix: 'api'
},
logger: {
info: (msg, data) => console.log(msg, data),
warning: (msg, data) => console.warn(msg, data),
error: (msg, err) => console.error(msg, err)
}
})
console.log(roleArns)
// {
// authenticated: "arn:aws:iam::123456789012:role/api-acme-authenticated",
// unauthenticated: "arn:aws:iam::123456789012:role/api-acme-unauthenticated"
// }
`$3
1. Discovers Roles: Queries DynamoDB to find all role types (authenticated, unauthenticated, admin, etc.)
2. Collects Features: For each role, gathers all module features and builds ABAC tags
3. Creates IAM Roles: Creates roles with proper Cognito trust policies and ABAC tags
4. Attaches Policies: Attaches module ManagedPolicies to roles
5. Registers with Cognito: Attaches roles to the Cognito Identity Pool
$3
Roles follow the pattern:
{policyPrefix}-{tenantId}-{roleType}Examples:
-
api-acme-authenticated
- api-acme-unauthenticated
- api-acme-admin$3
Each role is tagged with module features in the format:
`
{moduleName}Features: "feature1:feature2:feature3"
`Example tags:
-
signFeatures: "7tmARMbS:BRUBT2SN:F8d3wY_v"
- workforceFeatures: "abc123:def456:ghi789"$3
The function is idempotent - it can be called multiple times safely:
- Existing roles are updated with new tags and policies
- No duplicate roles are created
- Cognito attachment is updated if needed
๐ Security Considerations
$3
- Generated policies follow least privilege principle
- Endpoint-specific resource ARNs (not wildcards)
- Custom policies allow fine-grained permission control
- Tenant roles use Cognito Identity Pool trust policies
$3
- Registry table access requires DynamoDB permissions
- Cross-module access controlled via IAM policy ARNs
- Service functions inherit Lambda execution role permissions
$3
1. Review generated policies before deployment
2. Use strict mode (
strict: true) in production
3. Regularly audit endpoint permissions
4. Implement proper IAM role boundaries๐ค Contributing
$3
1. Fork the repository
2. Create a feature branch:
git checkout -b feature/my-feature`- Follow TypeScript best practices
- Use provided ESLint configuration
- Add JSDoc comments for public APIs
- Maintain backward compatibility
When contributing:
- Add test cases for new features
- Ensure existing functionality isn't broken
- Test with multiple Serverless Framework versions
| Plugin Version | Serverless Framework | Node.js |
|----------------|---------------------|---------|
| 1.0.x | 2.x, 3.x, 4.x | โฅ14.0 |
- v2: Basic hook support, CloudFormation resources
- v3: Enhanced variable resolution, improved logging
- v4: Full ESM support, performance optimizations
MIT License - see the LICENSE file for details.