From rockets-sdk-config
This skill should be used when the user asks to generate a CRUD module, create a new entity, scaffold a domain object, add a new resource with endpoints, or create a junction table. Generates complete Rockets SDK modules including TypeORM entities, NestJS modules, controllers, services, DTOs, interfaces, and ACL wiring using generate.js, integrate.js, and validate.js scripts.
How this skill is triggered — by the user, by Claude, or both
Slash command
/rockets-sdk-config:rockets-crud-generatorThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Generate complete CRUD modules following Rockets SDK patterns with TypeORM, NestJS, and proper DTOs/interfaces.
examples/basic-entity.jsonexamples/entity-with-relations.jsonexamples/junction-table.jsonscripts/generate.jsscripts/generators/access-query.jsscripts/generators/adapter.jsscripts/generators/constants.jsscripts/generators/controller.jsscripts/generators/creatable-interface.jsscripts/generators/create-dto.jsscripts/generators/crud-service.jsscripts/generators/dto.jsscripts/generators/entity.jsscripts/generators/interface.jsscripts/generators/migration.jsscripts/generators/model-service.jsscripts/generators/module.jsscripts/generators/paginated-dto.jsscripts/generators/shared-index.jsscripts/generators/updatable-interface.jsGenerate complete CRUD modules following Rockets SDK patterns with TypeORM, NestJS, and proper DTOs/interfaces.
# Generate files (outputs JSON)
node skills/rockets-crud-generator/scripts/generate.js '{ "entityName": "Product", "fields": [...] }'
# Generate + integrate into project
node skills/rockets-crud-generator/scripts/generate.js '{ ... }' | node skills/rockets-crud-generator/scripts/integrate.js --project ./apps/api
# Validate after generation
node skills/rockets-crud-generator/scripts/validate.js --project ./apps/api --build
| Script | Purpose | Tokens |
|---|---|---|
generate.js | Generate all files as JSON output | 0 |
integrate.js | Write files + wire into project (entities, modules, ACL, queryServices) | 0 |
validate.js | Post-generation checks (structure, build, ACL) | 0 |
interface Config {
// Required
entityName: string; // PascalCase entity name
// Optional naming
pluralName?: string; // API path plural (auto-pluralized)
tableName?: string; // Database table (snake_case)
// Output paths (configurable per project)
paths?: {
entity?: string; // Default: "src/entities"
module?: string; // Default: "src/modules"
shared?: string; // Default: "src/shared" (set to null to skip)
};
// Shared package import path for generated code
sharedPackage?: string; // e.g., "@my-org/shared" (default: relative import)
// Fields & Relations
fields: FieldConfig[];
relations?: RelationConfig[];
// Operations (default: all)
operations?: ('readMany' | 'readOne' | 'createOne' | 'updateOne' | 'deleteOne' | 'recoverOne')[];
// ACL (access control)
acl?: Record<string, { possession: 'own' | 'any'; operations: ('create'|'read'|'update'|'delete')[] }>;
ownerField?: string; // Field for ownership check (default: "userId")
// Options
generateModelService?: boolean;
isJunction?: boolean;
}
interface FieldConfig {
name: string;
type: 'string' | 'text' | 'number' | 'float' | 'boolean' | 'date' | 'uuid' | 'json' | 'enum';
required?: boolean; // Default: true
unique?: boolean;
maxLength?: number;
minLength?: number;
min?: number;
max?: number;
precision?: number; // For float
scale?: number; // For float
default?: any;
enumValues?: string[]; // Required for enum type
apiDescription?: string;
apiExample?: any;
creatable?: boolean; // Include in CreateDto (default: true)
updatable?: boolean; // Include in UpdateDto (default: true)
}
interface RelationConfig {
name: string;
type: 'manyToOne' | 'oneToMany' | 'oneToOne';
targetEntity: string; // Base name WITHOUT "Entity" suffix (e.g., "User" not "UserEntity")
foreignKey?: string; // Default: targetCamelId
joinType?: 'LEFT' | 'INNER';
onDelete?: 'CASCADE' | 'SET NULL' | 'RESTRICT';
nullable?: boolean;
}
Important:
targetEntitymust be the base entity name (e.g.,"User","Category"). The generator appendsEntityautomatically. If you pass"UserEntity", the suffix is stripped to prevent double-suffixing (UserEntityEntity).
{
"entityName": "Task",
"ownerField": "userId",
"acl": {
"admin": { "possession": "any", "operations": ["create","read","update","delete"] },
"user": { "possession": "own", "operations": ["create","read","update","delete"] }
}
}
When acl is provided:
@InjectDynamicRepository for ownership checksapp.acl.ts (resource enum + grants)queryServices in AccessControlModule{
"entityName": "Tag",
"fields": [
{ "name": "name", "type": "string", "required": true, "maxLength": 50, "unique": true },
{ "name": "color", "type": "string", "maxLength": 7, "apiExample": "#FF5733" }
]
}
{
"entityName": "Product",
"paths": {
"entity": "apps/api/src/entities",
"module": "apps/api/src/modules",
"shared": "packages/shared/src"
},
"ownerField": "createdById",
"acl": {
"admin": { "possession": "any", "operations": ["create","read","update","delete"] },
"user": { "possession": "own", "operations": ["create","read","update","delete"] }
},
"fields": [
{ "name": "name", "type": "string", "required": true },
{ "name": "price", "type": "float", "precision": 10, "scale": 2 }
]
}
{
"entityName": "ProductTag",
"tableName": "product_tag",
"isJunction": true,
"fields": [],
"relations": [
{ "name": "product", "type": "manyToOne", "targetEntity": "Product", "onDelete": "CASCADE" },
{ "name": "tag", "type": "manyToOne", "targetEntity": "Tag", "onDelete": "CASCADE" }
],
"operations": ["readMany", "readOne", "createOne", "deleteOne"]
}
For a given entity (e.g. Product) with default paths:
src/
├── entities/
│ └── {entity}.entity.ts
├── modules/{entity}/
│ ├── constants/{entity}.constants.ts
│ ├── {entity}.module.ts
│ ├── {entity}.crud.controller.ts
│ ├── {entity}.crud.service.ts
│ ├── {entity}-typeorm-crud.adapter.ts
│ └── {entity}-access-query.service.ts
└── shared/{entity}/ (if paths.shared is set)
├── dtos/
│ ├── {entity}.dto.ts
│ ├── {entity}-create.dto.ts
│ ├── {entity}-update.dto.ts
│ └── {entity}-paginated.dto.ts
├── interfaces/
│ ├── {entity}.interface.ts
│ ├── {entity}-creatable.interface.ts
│ └── {entity}-updatable.interface.ts
└── index.ts
The generator produces controllers with full ACL decorators (@UseGuards(AccessControlGuard), @AccessControlQuery, @AccessControlReadMany, etc.). These work correctly when the access query service is registered via queryServices in AccessControlModule.forRoot().
@InjectDynamicRepository (database-agnostic)queryServices of the AccessControlModule configAccessControlGuard resolves the service from its own scope (no hack needed)@Injectable()
export class TaskAccessQueryService implements CanAccess {
constructor(
@InjectDynamicRepository(TASK_MODULE_TASK_ENTITY_KEY)
private taskRepo: RepositoryInterface<TaskEntity>,
) {}
async canAccess(context: AccessControlContextInterface): Promise<boolean> {
const query = context.getQuery();
if (query.possession === 'any') return true;
if (query.possession === 'own') {
// Ownership check via dynamic repository (database-agnostic)
const entity = await this.taskRepo.findOne({ where: { id: entityId } });
return entity?.userId === user.id;
}
return false;
}
}
// AccessControlModule config (or via RocketsAuthModule):
accessControl: {
settings: { rules: acRules },
queryServices: [TaskAccessQueryService, CategoryAccessQueryService],
}
The integrate.js script handles this automatically.
Takes the JSON output from generate.js and wires everything:
node generate.js '{ ... }' | node integrate.js --project ./apps/api
What it does:
entities/index.tstypeorm.settings.ts entities arrayapp.module.tsapp.acl.ts (if acl config present)queryServices in AccessControlModule configValidates project structure and patterns after generation:
node validate.js --project ./apps/api # Static checks only
node validate.js --project ./apps/api --build # Static checks + TypeScript build
@InjectRepository only in *-typeorm-crud.adapter.tsentities/index.tsapp.module.tsapp.acl.tsownerField column in entityCrudModule.forRoot({}) present when CrudModule.forFeature() is useddist/ paths*SqliteEntity) in a Postgres projectOutput: { passed: boolean, issues: [{ severity, rule, message, file, line }] }
The generator produces CrudRelations decorators and CrudRelationRegistry providers for modules with relations. These reference the related module's CRUD service (e.g., UserCrudService), which must exist as an importable module. If the related entity is managed by the SDK (e.g., User from RocketsAuthModule) rather than by a standalone module you wrote, the generated relation wiring will fail.
Workaround for SDK-managed entities: Remove the CrudRelations decorator, the CrudRelationRegistry provider, and all references to non-existent related modules/services. Instead, rely on TypeORM @ManyToOne/@JoinColumn decorators on the entity and include the FK column (userId, categoryId) directly in the DTO. The CRUD endpoints will accept and persist the FK; TypeORM handles the join at query time.
queryServices of AccessControlModule confignpx claudepluginhub btwld/skills --plugin rockets-sdk-configGenerates complete CRUD modules for NestJS with Drizzle ORM, including controllers, services, Zod DTOs, schemas, and Jest tests. Use for new database-backed entities and endpoints.
Generates production-ready CRUD scaffolding and API endpoints for entities, including validation (Zod), authorization, tests, and relational support. Detects and adapts to existing schema (Prisma, Drizzle, raw SQL) and framework (Next.js, Express, Hono).
Applies opinionated NestJS conventions for backends: modules, dependency injection, controllers/services, DTOs with class-validator, guards/interceptors/pipes, JWT auth, TypeORM/Prisma. Use for REST/GraphQL APIs.