rockets-crud-generator

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Rockets SDK CRUD Generator

Rockets SDK CRUD 生成器

Generate complete CRUD modules following Rockets SDK patterns with TypeORM, NestJS, and proper DTOs/interfaces.
遵循Rockets SDK规范,使用TypeORM、NestJS及标准DTO/接口生成完整的CRUD模块。

Quick Start

快速开始

bash
undefined
bash
undefined

Generate files (outputs JSON)

生成文件(输出JSON格式)

node skills/rockets-crud-generator/scripts/generate.js '{ "entityName": "Product", "fields": [...] }'
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
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
undefined
node skills/rockets-crud-generator/scripts/validate.js --project ./apps/api --build
undefined

Scripts

脚本说明

ScriptPurposeTokens
generate.js
Generate all files as JSON output0
integrate.js
Write files + wire into project (entities, modules, ACL, queryServices)0
validate.js
Post-generation checks (structure, build, ACL)0
脚本用途令牌
generate.js
以JSON格式输出所有生成的文件0
integrate.js
写入文件并集成到项目中(实体、模块、ACL、查询服务)0
validate.js
生成后检查(结构、构建、ACL)0

Configuration

配置项

typescript
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;
}
typescript
interface Config {
  // 必填项
  entityName: string;           // 大驼峰式实体名称

  // 可选命名配置
  pluralName?: string;          // API路径复数形式(自动生成复数)
  tableName?: string;           // 数据库表名(蛇形命名)

  // 输出路径(可按项目配置)
  paths?: {
    entity?: string;            // 默认值:"src/entities"
    module?: string;            // 默认值:"src/modules"
    shared?: string;            // 默认值:"src/shared"(设为null则跳过)
  };

  // 生成代码的共享包导入路径
  sharedPackage?: string;       // 示例:"@my-org/shared"(默认:相对导入)

  // 字段与关联关系
  fields: FieldConfig[];
  relations?: RelationConfig[];

  // 操作选项(默认:全部支持)
  operations?: ('readMany' | 'readOne' | 'createOne' | 'updateOne' | 'deleteOne' | 'recoverOne')[];

  // ACL(访问控制)
  acl?: Record<string, { possession: 'own' | 'any'; operations: ('create'|'read'|'update'|'delete')[] }>;
  ownerField?: string;          // 所有权检查字段(默认值:"userId")

  // 其他选项
  generateModelService?: boolean;
  isJunction?: boolean;
}

Field Configuration

字段配置

typescript
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)
}
typescript
interface FieldConfig {
  name: string;
  type: 'string' | 'text' | 'number' | 'float' | 'boolean' | 'date' | 'uuid' | 'json' | 'enum';
  required?: boolean;           // 默认值:true
  unique?: boolean;
  maxLength?: number;
  minLength?: number;
  min?: number;
  max?: number;
  precision?: number;           // 针对float类型
  scale?: number;               // 针对float类型
  default?: any;
  enumValues?: string[];        // enum类型必填
  apiDescription?: string;
  apiExample?: any;
  creatable?: boolean;          // 是否包含在CreateDto中(默认:true)
  updatable?: boolean;          // 是否包含在UpdateDto中(默认:true)
}

Relation Configuration

关联关系配置

typescript
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:
targetEntity
must be the base entity name (e.g.,
"User"
,
"Category"
). The generator appends
Entity
automatically. If you pass
"UserEntity"
, the suffix is stripped to prevent double-suffixing (
UserEntityEntity
).
typescript
interface RelationConfig {
  name: string;
  type: 'manyToOne' | 'oneToMany' | 'oneToOne';
  targetEntity: string;         // 基础实体名称,不带"Entity"后缀(例如:"User"而非"UserEntity")
  foreignKey?: string;          // 默认值:targetCamelId
  joinType?: 'LEFT' | 'INNER';
  onDelete?: 'CASCADE' | 'SET NULL' | 'RESTRICT';
  nullable?: boolean;
}
重要提示
targetEntity
必须是基础实体名称(例如:
"User"
"Category"
)。 生成器会自动追加
Entity
后缀。如果传入
"UserEntity"
,后缀会被去除,避免重复添加导致
UserEntityEntity
的错误。

ACL Configuration

ACL配置示例

json
{
  "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:
  • Access query service uses
    @InjectDynamicRepository
    for ownership checks
  • Generator outputs wiring snippets for
    app.acl.ts
    (resource enum + grants)
  • Generator outputs wiring for
    queryServices
    in AccessControlModule
json
{
  "entityName": "Task",
  "ownerField": "userId",
  "acl": {
    "admin": { "possession": "any", "operations": ["create","read","update","delete"] },
    "user": { "possession": "own", "operations": ["create","read","update","delete"] }
  }
}
当配置了
acl
时:
  • 访问查询服务使用
    @InjectDynamicRepository
    进行所有权检查
  • 生成器会输出
    app.acl.ts
    的配置代码片段(资源枚举 + 权限规则)
  • 生成器会输出AccessControlModule中
    queryServices
    的配置代码

Examples

示例

Basic Entity

基础实体示例

json
{
  "entityName": "Tag",
  "fields": [
    { "name": "name", "type": "string", "required": true, "maxLength": 50, "unique": true },
    { "name": "color", "type": "string", "maxLength": 7, "apiExample": "#FF5733" }
  ]
}
json
{
  "entityName": "Tag",
  "fields": [
    { "name": "name", "type": "string", "required": true, "maxLength": 50, "unique": true },
    { "name": "color", "type": "string", "maxLength": 7, "apiExample": "#FF5733" }
  ]
}

With ACL + Custom Paths (monorepo)

带ACL和自定义路径的示例(单体仓库)

json
{
  "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 }
  ]
}
json
{
  "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 }
  ]
}

Junction Table

关联表示例

json
{
  "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"]
}
json
{
  "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"]
}

Generated Files

生成的文件结构

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
以实体
Product
为例,使用默认路径时的文件结构:
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}/          (若设置了paths.shared)
    ├── 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

AccessControl Integration (queryServices pattern)

AccessControl集成(查询服务模式)

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()
.
生成器会生成带有完整ACL装饰器的控制器(
@UseGuards(AccessControlGuard)
@AccessControlQuery
@AccessControlReadMany
等)。当访问查询服务通过AccessControlModule.forRoot()的
queryServices
注册后,这些装饰器即可正常工作。

How it works

工作原理

  1. Generator creates the access query service with
    @InjectDynamicRepository
    (database-agnostic)
  2. integrate.js registers the service in
    queryServices
    of the AccessControlModule config
  3. The
    AccessControlGuard
    resolves the service from its own scope (no hack needed)
  1. 生成器创建带有
    @InjectDynamicRepository
    的访问查询服务(数据库无关)
  2. integrate.js在AccessControlModule配置的
    queryServices
    中注册该服务
  3. AccessControlGuard
    从自身作用域中解析服务(无需额外技巧)

Access Query Service pattern

访问查询服务模式示例

typescript
@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;
  }
}
typescript
@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') {
      // 通过动态仓库进行所有权检查(数据库无关)
      const entity = await this.taskRepo.findOne({ where: { id: entityId } });
      return entity?.userId === user.id;
    }
    return false;
  }
}

Required wiring in app.module.ts

app.module.ts中所需的配置

typescript
// AccessControlModule config (or via RocketsAuthModule):
accessControl: {
  settings: { rules: acRules },
  queryServices: [TaskAccessQueryService, CategoryAccessQueryService],
}
The
integrate.js
script handles this automatically.
typescript
// AccessControlModule配置(或通过RocketsAuthModule):
accessControl: {
  settings: { rules: acRules },
  queryServices: [TaskAccessQueryService, CategoryAccessQueryService],
}
integrate.js
脚本会自动处理上述配置。

integrate.js — Auto-wiring

integrate.js — 自动配置

Takes the JSON output from
generate.js
and wires everything:
bash
node generate.js '{ ... }' | node integrate.js --project ./apps/api
What it does:
  1. Writes all generated files to disk
  2. Adds entity export to
    entities/index.ts
  3. Adds entity to
    typeorm.settings.ts
    entities array
  4. Adds module import to
    app.module.ts
  5. Adds resource + grants to
    app.acl.ts
    (if
    acl
    config present)
  6. Adds access query service to
    queryServices
    in AccessControlModule config
接收
generate.js
输出的JSON内容并完成所有集成操作:
bash
node generate.js '{ ... }' | node integrate.js --project ./apps/api
具体操作包括:
  1. 将所有生成的文件写入磁盘
  2. entities/index.ts
    中添加实体导出
  3. typeorm.settings.ts
    的entities数组中添加该实体
  4. app.module.ts
    中添加模块导入
  5. app.acl.ts
    中添加资源和权限规则(若配置了acl)
  6. 在AccessControlModule配置的
    queryServices
    中添加访问查询服务

validate.js — Post-generation Checks

validate.js — 生成后检查

Validates project structure and patterns after generation:
bash
node validate.js --project ./apps/api           # Static checks only
node validate.js --project ./apps/api --build   # Static checks + TypeScript build
生成完成后验证项目结构和规范:
bash
node validate.js --project ./apps/api           # 仅静态检查
node validate.js --project ./apps/api --build   # 静态检查 + TypeScript构建

Generated Code Checks

生成代码检查项

  1. @InjectRepository
    only in
    *-typeorm-crud.adapter.ts
  2. All entities exported in
    entities/index.ts
  3. All modules imported in
    app.module.ts
  4. ACL resources defined in
    app.acl.ts
  5. Access query services registered in feature module providers
  6. No ACL workaround providers in feature modules
  7. ACL own-scope entities have matching
    ownerField
    column in entity
  8. CrudModule.forRoot({})
    present when
    CrudModule.forFeature()
    is used
  1. @InjectRepository
    仅出现在
    *-typeorm-crud.adapter.ts
  2. 所有实体都在
    entities/index.ts
    中导出
  3. 所有模块都在
    app.module.ts
    中导入
  4. ACL资源在
    app.acl.ts
    中定义
  5. 访问查询服务在功能模块的providers中注册
  6. 功能模块中无ACL兼容的providers
  7. ACL自有作用域实体在实体中存在对应的
    ownerField
  8. 当使用
    CrudModule.forFeature()
    时,必须存在
    CrudModule.forRoot({})

Template Integrity Checks (safety nets — should never fire on a correct template)

模板完整性检查(安全机制 — 正确模板下不会触发)

  1. No imports from internal
    dist/
    paths
  2. No stale template placeholder strings (Music Management, PetAccessQueryService, etc.)
  3. All entity tables have corresponding migrations (severity: error)
  4. No SQLite base classes (
    *SqliteEntity
    ) in a Postgres project
Output:
{ passed: boolean, issues: [{ severity, rule, message, file, line }] }
  1. 无来自内部
    dist/
    路径的导入
  2. 无过期的模板占位符字符串(如Music Management、PetAccessQueryService等)
  3. 所有实体表都有对应的迁移文件(严重级别:错误
  4. Postgres项目中无SQLite基类(
    *SqliteEntity
输出格式:
{ passed: boolean, issues: [{ severity, rule, message, file, line }] }

Known Limitations — Relations

已知限制 — 关联关系

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.
生成器会为带有关联关系的模块生成
CrudRelations
装饰器和
CrudRelationRegistry
提供者,这些会引用关联模块的CRUD服务(例如
UserCrudService
),而该服务必须是可导入的模块。如果关联实体由SDK管理(例如
RocketsAuthModule
中的User)而非你编写的独立模块,则生成的关联配置会失效。
SDK管理实体的解决方法:移除
CrudRelations
装饰器、
CrudRelationRegistry
提供者以及所有对不存在的关联模块/服务的引用。转而依赖实体上的TypeORM
@ManyToOne
/
@JoinColumn
装饰器,并在DTO中直接包含外键列(
userId
categoryId
)。CRUD端点会接收并持久化外键;TypeORM会在查询时处理关联。

Post-Generation (manual steps if not using integrate.js)

生成后手动步骤(未使用integrate.js时)

  1. Export entity from entities index
  2. Import module in app.module.ts
  3. Add entity to typeorm.settings.ts
  4. Register access query service in
    queryServices
    of AccessControlModule config
  5. Add resource + grants to app.acl.ts (if using ACL)
  6. Remove CrudRelations if related entity is SDK-managed (see above)
  7. Export from shared index (if using shared package)
  1. 在实体索引文件中导出该实体
  2. 在app.module.ts中导入模块
  3. 在typeorm.settings.ts中添加实体
  4. 在AccessControlModule配置的
    queryServices
    中注册访问查询服务
  5. 在app.acl.ts中添加资源和权限规则(若使用ACL)
  6. 若关联实体由SDK管理,移除CrudRelations(见上文)
  7. 在共享包索引中导出(若使用共享包)