Loading...
Loading...
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.
npx skill4agent add btwld/skills rockets-crud-generator# 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 all files as JSON output | 0 |
| Write files + wire into project (entities, modules, ACL, queryServices) | 0 |
| 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:must be the base entity name (e.g.,targetEntity,"User"). The generator appends"Category"automatically. If you passEntity, the suffix is stripped to prevent double-suffixing ("UserEntity").UserEntityEntity
{
"entityName": "Task",
"ownerField": "userId",
"acl": {
"admin": { "possession": "any", "operations": ["create","read","update","delete"] },
"user": { "possession": "own", "operations": ["create","read","update","delete"] }
}
}acl@InjectDynamicRepositoryapp.acl.tsqueryServices{
"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"]
}Productsrc/
├── 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@UseGuards(AccessControlGuard)@AccessControlQuery@AccessControlReadManyqueryServicesAccessControlModule.forRoot()@InjectDynamicRepositoryqueryServicesAccessControlGuard@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],
}integrate.jsgenerate.jsnode generate.js '{ ... }' | node integrate.js --project ./apps/apientities/index.tstypeorm.settings.tsapp.module.tsapp.acl.tsaclqueryServicesnode validate.js --project ./apps/api # Static checks only
node validate.js --project ./apps/api --build # Static checks + TypeScript build@InjectRepository*-typeorm-crud.adapter.tsentities/index.tsapp.module.tsapp.acl.tsownerFieldCrudModule.forRoot({})CrudModule.forFeature()dist/*SqliteEntity{ passed: boolean, issues: [{ severity, rule, message, file, line }] }CrudRelationsCrudRelationRegistryUserCrudServiceRocketsAuthModuleCrudRelationsCrudRelationRegistry@ManyToOne@JoinColumnuserIdcategoryIdqueryServices