create-domain-module
π―Skillfrom gilbertopsantosjr/fullstacknextjs
Generates production-ready, state-isolated feature modules for Next.js with DynamoDB, following strict architectural principles and runtime-agnostic design.
Part of
gilbertopsantosjr/fullstacknextjs(14 items)
Installation
npx vitest run src/features/{feature-name}npx sst deploySkill Details
Creates complete, production-ready feature modules following the 10 architectural principles, with emphasis on state isolation, service layer patterns, and runtime-agnostic design for deployment flexibility. Adapted for Next.js 15+, DynamoDB/OneTable, ZSA, and Vitest.
Overview
# Feature Module Generator (Next.js + DynamoDB)
You are an expert in creating feature modules that comply with the 10 architectural principles, emphasizing state isolation, well-defined boundaries, and deployment independence. This skill is adapted for the modern stack: Next.js 15+, DynamoDB with OneTable, ZSA server actions, and Vitest.
Technology Stack
| Component | Technology |
|-----------|------------|
| Framework | Next.js 15+ / React 19+ |
| Database | DynamoDB (single-table design) |
| ORM | OneTable |
| Server Actions | ZSA (Zod Server Actions) |
| Validation | Zod schemas |
| Testing | Vitest |
| ID Generation | ULID |
| Deployment | SST (Serverless Stack) |
Core Principles Applied
This skill enforces these critical architectural principles:
| # | Principle | How Applied |
|---|-----------|-------------|
| 1 | Well-Defined Boundaries | Clear feature exports, internal data hidden via service layer |
| 3 | Independence | No cross-feature DAL imports, autonomous operation |
| 5 | Explicit Communication | Service layer defines public API contracts |
| 7 | Deployment Independence | Runtime-agnostic DAL works in Next.js, Lambda, or queues |
| 8 | State Isolation | Feature-prefixed DynamoDB keys (pk/sk patterns) |
CRITICAL: State Isolation (Principle 8) is the most frequently violated principle. Feature-prefixed DynamoDB key patterns are MANDATORY.
When to Use This Skill
Use this skill when:
- Creating a new feature module from scratch
- User asks to "create a feature", "scaffold a feature", or "generate a module"
- User mentions needing a new business domain (accounts, billing, notifications, etc.)
- Starting a new bounded context that needs its own data and logic
Requirements Gathering
Before generating any code, ask the user these questions using the AskQuestion tool:
- Feature name (kebab-case, e.g., "accounts", "billing", "notifications")
- Initial entities (comma-separated list, e.g., "Account, Transaction, Balance")
- External integrations (any third-party services? e.g., "Stripe, SendGrid")
- Cross-feature access needed? (will other features need to access this feature's data?)
- Key access patterns (how will data be queried? e.g., "by userId", "by date range")
Folder Structure Generation
Generate this structure for feature modules:
```
src/features/
βββ actions/ # Server actions (ZSA pattern)
β βββ create-
β βββ update-
β βββ delete-
β βββ get-
β βββ index.ts # Public action exports
βββ dal/ # Data Access Layer (runtime-agnostic)
β βββ create_
β βββ find_
β βββ find_
β βββ update_
β βββ delete_
β βββ
β βββ index.ts # DAL exports
βββ service/ # Public API for cross-feature access
β βββ
βββ model/ # Zod schemas and types
β βββ
β βββ
β βββ
βββ components/ # UI components (if needed)
β βββ
β βββ
β βββ index.ts
βββ hooks/ # React hooks (if needed)
β βββ use-
βββ index.ts # Public feature exports
```
Component Generation Instructions
1. DynamoDB Schema (in `src/features/database/db-schema.ts`)
Add the entity model to the shared schema:
```typescript
// Add to existing db-schema.ts
export const schema = {
// ... existing models
{EntityName}: {
pk: { type: String, value: 'USER#${userId}' },
sk: { type: String, value: '{FEATURE}#{entityName}#${id}' },
id: { type: String, required: true, generate: 'ulid' },
userId: { type: String, required: true },
// Add entity-specific fields
name: { type: String, required: true },
description: { type: String },
status: { type: String, enum: ['{EntityName}Status'], default: 'active' },
metadata: { type: Object },
// Standard timestamps
createdAt: { type: String },
updatedAt: { type: String },
// GSI for queries
gs1pk: { type: String, value: '{FEATURE}#${status}' },
gs1sk: { type: String, value: '${createdAt}' },
},
} as const
```
Key Pattern Rules:
- pk: Always starts with entity type (e.g.,
USER#,ACCOUNT#) - sk: Feature prefix + entity type + ID (e.g.,
ACCOUNTS#account#01HXYZ...) - GSI patterns: For alternate access patterns (by status, by date, etc.)
2. Zod Schemas (model/<feature>-schemas.ts)
```typescript
import { z } from 'zod'
// Base entity schema (what comes from DB)
export const {EntityName}Schema = z.object({
id: z.string().ulid(),
userId: z.string().ulid(),
name: z.string().min(1).max(255),
description: z.string().max(1000).optional(),
status: z.enum(['active', 'inactive', 'archived']).default('active'),
metadata: z.record(z.unknown()).optional(),
createdAt: z.string().datetime(),
updatedAt: z.string().datetime(),
})
export type {EntityName}Entity = z.infer
// Create input schema (what client sends)
export const Create{EntityName}Schema = z.object({
name: z.string().min(1).max(255),
description: z.string().max(1000).optional(),
metadata: z.record(z.unknown()).optional(),
})
export type Create{EntityName}Input = z.infer
// Update input schema
export const Update{EntityName}Schema = z.object({
name: z.string().min(1).max(255).optional(),
description: z.string().max(1000).optional(),
status: z.enum(['active', 'inactive', 'archived']).optional(),
metadata: z.record(z.unknown()).optional(),
})
export type Update{EntityName}Input = z.infer
// Query params schema
export const {EntityName}QuerySchema = z.object({
status: z.enum(['active', 'inactive', 'archived']).optional(),
limit: z.coerce.number().min(1).max(100).default(20),
cursor: z.string().optional(),
})
export type {EntityName}QueryParams = z.infer
```
3. DAL Functions (dal/create_<entity>.ts)
CRITICAL: DAL files must be runtime-agnostic (NO 'server-only' import).
```typescript
// dal/create_account.ts
import { getDynamoDbTable } from '@/features/database/db-config'
import { Create{EntityName}Schema, type Create{EntityName}Input, type {EntityName}Entity } from '../model/{feature}-schemas'
import type { RepositoryResult } from '@/types'
import { ZodError } from 'zod'
import { log } from '@/lib/logger'
export const create{EntityName} = async (
input: Create{EntityName}Input & { userId: string }
): Promise
const startTime = Date.now()
try {
// Validate input
const validatedInput = Create{EntityName}Schema.extend({
userId: z.string().ulid(),
}).parse(input)
// Get the model from OneTable
const {EntityName}Model = getDynamoDbTable().getModel('{EntityName}')
// Create the entity
const entity = await {EntityName}Model.create({
userId: validatedInput.userId,
name: validatedInput.name,
description: validatedInput.description,
metadata: validatedInput.metadata,
status: 'active',
})
log.debug('[{EntityName}.create] Success', {
id: entity.id,
userId: entity.userId,
duration: Date.now() - startTime,
})
return { success: true, data: entity as {EntityName}Entity }
} catch (error) {
log.error('[{EntityName}.create] Failed', { error, duration: Date.now() - startTime })
if (error instanceof ZodError) {
return {
success: false,
error: error.errors,
code: 'VALIDATION_ERROR'
}
}
return {
success: false,
error: 'Failed to create {entityName}',
code: 'CREATE_{ENTITY_NAME}_ERROR'
}
}
}
```
DAL Pattern Rules:
- Files use snake_case:
create_account.ts,find_account_by_id.ts - Functions use camelCase:
createAccount(),findAccountById() - Return
RepositoryResultfor consistent error handling - NO
'server-only'import - DAL must work in Lambda - Include logging with timing
- Handle Zod validation errors explicitly
4. DAL Query Functions (dal/find_<entity>_by_id.ts)
```typescript
// dal/find_account_by_id.ts
import { getDynamoDbTable } from '@/features/database/db-config'
import type { {EntityName}Entity } from '../model/{feature}-schemas'
import type { RepositoryResult } from '@/types'
import { log } from '@/lib/logger'
export const find{EntityName}ById = async (
id: string,
userId: string
): Promise
const startTime = Date.now()
try {
const {EntityName}Model = getDynamoDbTable().getModel('{EntityName}')
const entity = await {EntityName}Model.get({
pk: USER#${userId},
sk: {FEATURE}#{entityName}#${id},
})
log.debug('[{EntityName}.findById] Complete', {
id,
found: !!entity,
duration: Date.now() - startTime,
})
return { success: true, data: entity as {EntityName}Entity | null }
} catch (error) {
log.error('[{EntityName}.findById] Failed', { error, id, duration: Date.now() - startTime })
return {
success: false,
error: 'Failed to find {entityName}',
code: 'FIND_{ENTITY_NAME}_ERROR'
}
}
}
```
5. DAL List Functions (dal/find_<entity>s_by_user.ts)
```typescript
// dal/find_accounts_by_user.ts
import { getDynamoDbTable } from '@/features/database/db-config'
import { {EntityName}QuerySchema, type {EntityName}QueryParams, type {EntityName}Entity } from '../model/{feature}-schemas'
import type { RepositoryResult, PaginatedResult } from '@/types'
import { log } from '@/lib/logger'
export const find{EntityName}sByUser = async (
userId: string,
params: {EntityName}QueryParams = {}
): Promise
const startTime = Date.now()
try {
const validatedParams = {EntityName}QuerySchema.parse(params)
const {EntityName}Model = getDynamoDbTable().getModel('{EntityName}')
const queryOptions: any = {
pk: USER#${userId},
sk: { begins: '{FEATURE}#{entityName}#' },
limit: validatedParams.limit,
}
if (validatedParams.cursor) {
queryOptions.start = JSON.parse(Buffer.from(validatedParams.cursor, 'base64').toString())
}
const result = await {EntityName}Model.find(queryOptions)
const nextCursor = result.next
? Buffer.from(JSON.stringify(result.next)).toString('base64')
: undefined
log.debug('[{EntityName}.findByUser] Complete', {
userId,
count: result.length,
hasMore: !!nextCursor,
duration: Date.now() - startTime,
})
return {
success: true,
data: {
items: result as {EntityName}Entity[],
nextCursor,
hasMore: !!nextCursor,
}
}
} catch (error) {
log.error('[{EntityName}.findByUser] Failed', { error, userId, duration: Date.now() - startTime })
return {
success: false,
error: 'Failed to find {entityName}s',
code: 'FIND_{ENTITY_NAME}S_ERROR'
}
}
}
```
6. DAL Index (dal/index.ts)
```typescript
// dal/index.ts
export { create{EntityName} } from './create_{entity_name}'
export { find{EntityName}ById } from './find_{entity_name}_by_id'
export { find{EntityName}sByUser } from './find_{entity_name}s_by_user'
export { update{EntityName} } from './update_{entity_name}'
export { delete{EntityName} } from './delete_{entity_name}'
```
7. Server Actions (actions/create-<entity>.ts)
Server actions MUST be lean (<20 lines in handler).
```typescript
// actions/create-account.ts
'use server'
import 'server-only'
import { authedProcedure } from '@/lib/zsa'
import { Create{EntityName}Schema } from '../model/{feature}-schemas'
import { create{EntityName} } from '../dal'
export const create{EntityName}Action = authedProcedure
.input(Create{EntityName}Schema)
.handler(async ({ input, ctx }) => {
const result = await create{EntityName}({
...input,
userId: ctx.user.id,
})
if (!result.success) {
throw new Error(result.error as string)
}
return result.data
})
```
Server Action Rules:
- Files use kebab-case:
create-account.ts - Always include
'use server'andimport 'server-only' - Use
authedProcedurefor authenticated actions - Use
publicProcedurefor unauthenticated actions - Handler should be <20 lines
- Delegate ALL business logic to DAL
- Transform
RepositoryResultto action response
8. Server Actions for Queries (actions/get-<entity>.ts)
```typescript
// actions/get-account.ts
'use server'
import 'server-only'
import { authedProcedure } from '@/lib/zsa'
import { z } from 'zod'
import { find{EntityName}ById } from '../dal'
export const get{EntityName}Action = authedProcedure
.input(z.object({ id: z.string().ulid() }))
.handler(async ({ input, ctx }) => {
const result = await find{EntityName}ById(input.id, ctx.user.id)
if (!result.success) {
throw new Error(result.error as string)
}
if (!result.data) {
throw new Error('{EntityName} not found')
}
return result.data
})
```
9. Service Layer (service/<feature>-service.ts)
CRITICAL: Create service layer if other features need access to this feature's data.
```typescript
// service/accounts-service.ts
import { find{EntityName}ById, find{EntityName}sByUser } from '../dal'
import type { {EntityName}Entity } from '../model/{feature}-schemas'
/**
* Public API for cross-feature access.
* Other features import this service, NEVER the DAL directly.
*
* @example
* // In another feature:
* import { {featureName}Service } from '@/features/{feature-name}'
* const account = await {featureName}Service.get{EntityName}ById(id, userId)
*/
export const {featureName}Service = {
/**
* Get entity by ID (simplified return for external callers)
* Returns null if not found instead of throwing
*/
async get{EntityName}ById(id: string, userId: string): Promise<{EntityName}Summary | null> {
const result = await find{EntityName}ById(id, userId)
if (!result.success || !result.data) {
return null
}
// Only expose necessary fields to other features
return {
id: result.data.id,
name: result.data.name,
status: result.data.status,
}
},
/**
* Check if entity exists (for validation from other features)
*/
async {entityName}Exists(id: string, userId: string): Promise
const result = await find{EntityName}ById(id, userId)
return result.success && result.data !== null
},
/**
* Get entities by user with pagination
*/
async get{EntityName}sByUser(
userId: string,
options?: { limit?: number; cursor?: string }
): Promise<{EntityName}Summary[]> {
const result = await find{EntityName}sByUser(userId, options)
if (!result.success) {
return []
}
return result.data.items.map(item => ({
id: item.id,
name: item.name,
status: item.status,
}))
},
}
// Type for external consumption (limited fields)
export type {EntityName}Summary = {
id: string
name: string
status: string
}
```
Service Layer Rules:
- Return simplified types (not full entities)
- Return
nullinstead of throwing for not-found cases - Only expose methods other features actually need
- Document each method with JSDoc
- Never expose DAL functions directly
10. Feature Index (index.ts)
```typescript
// index.ts - Public feature exports
// Actions (for use in components)
export {
create{EntityName}Action,
get{EntityName}Action,
get{EntityName}sAction,
update{EntityName}Action,
delete{EntityName}Action,
} from './actions'
// Service layer (for cross-feature access)
export { {featureName}Service, type {EntityName}Summary } from './service/{feature}-service'
// Types (for TypeScript consumers)
export type {
{EntityName}Entity,
Create{EntityName}Input,
Update{EntityName}Input,
} from './model/{feature}-schemas'
// Components (if any)
export { {EntityName}Form } from './components/{entity-name}-form'
export { {EntityName}List } from './components/{entity-name}-list'
// --------------------------------------------------------
// NEVER export: DAL functions, internal schemas, constants
// Other features MUST use the service layer for data access
// --------------------------------------------------------
```
11. Co-located Tests (dal/<entity>.test.ts)
```typescript
// dal/account.test.ts
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest'
import { create{EntityName} } from './create_{entity_name}'
import { find{EntityName}ById } from './find_{entity_name}_by_id'
import { find{EntityName}sByUser } from './find_{entity_name}s_by_user'
import { setupTestDb, cleanupTestDb, clearTestData } from '@/test/db-helpers'
describe('{EntityName} DAL', () => {
const testUserId = '01HXYZ123456789ABCDEFGHIJK'
beforeAll(async () => {
await setupTestDb()
})
afterAll(async () => {
await cleanupTestDb()
})
beforeEach(async () => {
await clearTestData('{EntityName}')
})
describe('create{EntityName}', () => {
it('creates a new {entityName} successfully', async () => {
const input = {
userId: testUserId,
name: 'Test {EntityName}',
description: 'A test {entityName}',
}
const result = await create{EntityName}(input)
expect(result.success).toBe(true)
expect(result.data).toMatchObject({
name: input.name,
description: input.description,
userId: testUserId,
status: 'active',
})
expect(result.data?.id).toBeDefined()
})
it('returns validation error for invalid input', async () => {
const input = {
userId: testUserId,
name: '', // Invalid: empty name
}
const result = await create{EntityName}(input)
expect(result.success).toBe(false)
expect(result.code).toBe('VALIDATION_ERROR')
})
})
describe('find{EntityName}ById', () => {
it('finds an existing {entityName}', async () => {
// Arrange
const createResult = await create{EntityName}({
userId: testUserId,
name: 'Test {EntityName}',
})
const entityId = createResult.data!.id
// Act
const result = await find{EntityName}ById(entityId, testUserId)
// Assert
expect(result.success).toBe(true)
expect(result.data?.id).toBe(entityId)
})
it('returns null for non-existent {entityName}', async () => {
const result = await find{EntityName}ById('nonexistent', testUserId)
expect(result.success).toBe(true)
expect(result.data).toBeNull()
})
})
describe('find{EntityName}sByUser', () => {
it('returns paginated results', async () => {
// Arrange: Create multiple entities
await Promise.all([
create{EntityName}({ userId: testUserId, name: '{EntityName} 1' }),
create{EntityName}({ userId: testUserId, name: '{EntityName} 2' }),
create{EntityName}({ userId: testUserId, name: '{EntityName} 3' }),
])
// Act
const result = await find{EntityName}sByUser(testUserId, { limit: 2 })
// Assert
expect(result.success).toBe(true)
expect(result.data?.items).toHaveLength(2)
expect(result.data?.hasMore).toBe(true)
})
})
})
```
Naming Conventions
Files and Folders
| Type | Convention | Example |
|------|------------|---------|
| Feature folders | kebab-case | src/features/user-accounts/ |
| Action files | kebab-case | create-account.ts |
| DAL files | snake_case | create_account.ts |
| Schema files | kebab-case | account-schemas.ts |
| Service files | kebab-case | accounts-service.ts |
| Test files | kebab-case | account.test.ts |
| Component files | kebab-case | account-form.tsx |
Code
| Type | Convention | Example |
|------|------------|---------|
| DAL functions | camelCase | createAccount() |
| Action exports | camelCase + Action | createAccountAction |
| Service exports | camelCase + Service | accountsService |
| Zod schemas | PascalCase + Schema | CreateAccountSchema |
| Types | PascalCase | AccountEntity, CreateAccountInput |
| Constants | UPPER_SNAKE_CASE | MAX_ACCOUNTS_PER_USER |
| DynamoDB pk/sk | PREFIX#value | USER#123, ACCOUNTS#account#456 |
Common Anti-Patterns to Avoid
Cross-Feature DAL Imports (CRITICAL VIOLATION)
BAD:
```typescript
// In billing feature
import { findAccountById } from '@/features/accounts/dal' // VIOLATION!
```
GOOD:
```typescript
// In billing feature
import { accountsService } from '@/features/accounts'
const account = await accountsService.getAccountById(id, userId)
```
`'server-only'` in DAL (Breaks Lambda)
BAD:
```typescript
// dal/create_account.ts
import 'server-only' // VIOLATION - breaks Lambda deployment
import { getDynamoDbTable } from '@/features/database/db-config'
```
GOOD:
```typescript
// dal/create_account.ts
// NO 'server-only' here - DAL is runtime-agnostic
import { getDynamoDbTable } from '@/features/database/db-config'
```
Fat Server Actions (>20 lines)
BAD:
```typescript
export const createAccountAction = authedProcedure
.input(CreateAccountSchema)
.handler(async ({ input, ctx }) => {
// Validation logic here...
// Business rules here...
// Database operations here...
// More logic...
// 50+ lines
})
```
GOOD:
```typescript
export const createAccountAction = authedProcedure
.input(CreateAccountSchema)
.handler(async ({ input, ctx }) => {
const result = await createAccount({ ...input, userId: ctx.user.id })
if (!result.success) throw new Error(result.error as string)
return result.data
})
```
Generic DynamoDB Keys
BAD:
```typescript
pk: ${userId}, // No type prefix
sk: ${id}, // No feature prefix
```
GOOD:
```typescript
pk: USER#${userId}, // Type prefix
sk: ACCOUNTS#account#${id}, // Feature + entity prefix
```
Missing Service Layer
BAD:
```typescript
// index.ts
export { findAccountById } from './dal' // Exposing DAL directly
```
GOOD:
```typescript
// index.ts
export { accountsService } from './service/accounts-service'
// DAL is NEVER exported
```
Missing RepositoryResult Pattern
BAD:
```typescript
export const createAccount = async (input) => {
const entity = await Model.create(input)
return entity // Direct return, no error handling
}
```
GOOD:
```typescript
export const createAccount = async (input): Promise
try {
const entity = await Model.create(input)
return { success: true, data: entity }
} catch (error) {
return { success: false, error: 'Failed to create', code: 'CREATE_ERROR' }
}
}
```
Verification Commands
After generating the feature, run these verification commands. All checks MUST pass before claiming compliance.
Critical Checks (P0 - Must Pass)
#### 1. Cross-Feature DAL Imports (MOST CRITICAL)
```bash
# Find cross-feature DAL imports
grep -r "from '@/features/[^']*dal'" src/features/ | \
awk -F: '{
match($1, /features\/([^/]+)/, feat);
match($2, /features\/([^/]+)\/dal/, imported);
if (feat[1] != imported[1] && imported[1] != "") {
print "VIOLATION: " $1 " imports from " imported[1] "/dal"
}
}'
```
Expected: Empty output (no cross-feature DAL imports)
#### 2. DAL Runtime-Agnostic (No 'server-only')
```bash
# Check for 'server-only' in DAL files
grep -l "server-only" src/features//dal/.ts
```
Expected: Empty output (no 'server-only' in DAL)
#### 3. Lean Server Actions (<20 lines in handler)
```bash
# Check action file sizes
find src/features/{feature-name}/actions -name "*.ts" ! -name "index.ts" -exec wc -l {} \;
```
Expected: All files under 50 lines total
High Priority Checks (P1)
#### 4. RepositoryResult Pattern
```bash
# Check DAL files return RepositoryResult
grep -L "RepositoryResult" src/features/{feature-name}/dal/*.ts | grep -v test | grep -v index
```
Expected: Empty output (all DAL functions return RepositoryResult)
#### 5. Service Layer Exists (if cross-feature access needed)
```bash
ls src/features/{feature-name}/service/*.ts
```
Expected: {feature}-service.ts exists
#### 6. DynamoDB Key Prefixes
```bash
# Check for proper key patterns in schema
grep -E "(pk|sk):" src/features/database/db-schema.ts | grep {FEATURE}
```
Expected: All pk/sk have proper prefixes
Standard Checks (P2)
#### 7. Index Exports (No DAL)
```bash
# Check index.ts doesn't export dal
grep "from.*dal" src/features/{feature-name}/index.ts
```
Expected: Empty output (DAL never exported from index)
#### 8. Zod Schemas Exist
```bash
ls src/features/{feature-name}/model/*-schemas.ts
```
Expected: Schema file exists
#### 9. Tests Exist and Pass
```bash
# Run feature tests
npx vitest run src/features/{feature-name}
```
Expected: All tests pass
Pre-Commit Verification Script
```bash
#!/bin/bash
# verify-feature.sh
FEATURE=$1
echo "Verifying feature: $FEATURE"
# P0: Cross-feature DAL imports
echo "Checking cross-feature DAL imports..."
VIOLATIONS=$(grep -r "from '@/features/" src/features/$FEATURE/ 2>/dev/null | grep "/dal'" | grep -v "@/features/$FEATURE")
if [ ! -z "$VIOLATIONS" ]; then
echo "CRITICAL: Cross-feature DAL imports found:"
echo "$VIOLATIONS"
exit 1
fi
# P0: 'server-only' in DAL
echo "Checking DAL runtime-agnostic..."
SERVER_ONLY=$(grep -l "server-only" src/features/$FEATURE/dal/*.ts 2>/dev/null)
if [ ! -z "$SERVER_ONLY" ]; then
echo "CRITICAL: 'server-only' found in DAL:"
echo "$SERVER_ONLY"
exit 1
fi
# P1: RepositoryResult pattern
echo "Checking RepositoryResult pattern..."
NO_RESULT=$(grep -L "RepositoryResult" src/features/$FEATURE/dal/*.ts 2>/dev/null | grep -v test | grep -v index)
if [ ! -z "$NO_RESULT" ]; then
echo "WARNING: DAL files without RepositoryResult:"
echo "$NO_RESULT"
fi
echo "Feature verification passed"
```
Generation Process
Follow these steps in order:
- Gather requirements using AskQuestion tool
- Create folder structure with all necessary directories
- Add DynamoDB model to db-schema.ts with proper key patterns
- Generate Zod schemas for input validation and types
- Generate DAL functions (snake_case files, RepositoryResult returns)
- Generate server actions (kebab-case files, lean handlers)
- Generate service layer if cross-feature access is needed
- Generate feature index with public exports only
- Generate tests co-located with DAL functions
- Run verification commands to check compliance
- Report results to user with next steps
Success Criteria
A successfully generated feature should:
- Pass all verification commands (no violations)
- Have proper DynamoDB key patterns (pk/sk with prefixes)
- Have runtime-agnostic DAL (no
'server-only') - Have lean server actions (<20 lines per handler)
- Return
RepositoryResultfrom all DAL functions - Export only public APIs (no DAL in index.ts)
- Have service layer if other features need access
- Have passing tests for all DAL functions
- Follow all 10 architectural principles
Next Steps After Generation
Inform the user to:
- Update db-schema.ts with the new entity model
- Run tests to verify DAL functions:
npx vitest run src/features/{feature-name} - Deploy schema changes (if using SST):
npx sst deploy - Use in components: Import from
@/features/{feature-name}
References
- Architecture Overview:
docs/ARCHITECTURE-OVERVIEW.md - State Isolation:
docs/STATE-ISOLATION.md - Coding Patterns:
docs/CODING-PATTERNS.md - DynamoDB Design:
docs/DYNAMODB-DESIGN.md - Testing Patterns:
docs/TESTING-PATTERNS.md
More from this repository10
Provides expert React Query guidance for efficient data fetching, mutations, and server state management with seamless server action integration.
Designs and structures feature-based architecture for scalable and modular Next.js applications with organized component and module layouts.
Deploys and optimizes AWS Lambda functions using Bun runtime, enabling efficient serverless development with TypeScript support and multiple event source integrations.
Simplifies DynamoDB interactions using the OneTable library for efficient, type-safe data modeling in Next.js applications.
Generates a Next.js web client with pre-configured routing, components, and server-side rendering for efficient full-stack application development.
Generates feature modules for Next.js applications with DynamoDB integration, streamlining backend and frontend component scaffolding.
Implements server-side actions in Next.js for handling form submissions and data mutations with simplified server-client interactions.
Evaluates modular architecture compliance across 10 principles, focusing on state isolation, service layers, runtime independence, and explicit communication for Next.js and DynamoDB projects.
Generates comprehensive e2e and integration tests with strict isolation, mocking cross-feature dependencies, and following modular testing principles for Next.js and DynamoDB projects.
Monitors and tracks application performance, logs, and metrics in a comprehensive observability solution for Next.js projects.