npm package: @hero-dynamic-form/sequelize-inspector
Description: Sequelize entity inspector adapter for @hero-dynamic-form/core
Author: hero-truong
Latest version: 1.0.43

# @hero-dynamic-form/sequelize-inspector

Sequelize entity inspector adapter for `@hero-dynamic-form/core`.

## Installation

```bash
npm install @hero-dynamic-form/sequelize-inspector @hero-dynamic-form/core sequelize
```

## CLI Usage

### Generate Sequelize model from database

```bash
# List all tables
npx sequelize-inspector generate

# Generate model from table
npx sequelize-inspector generate users --output ./src/models

# Overwrite existing file
npx sequelize-inspector generate users --force
```

Options:

- `--output, -o <path>` - Output directory (default: `./src/models`)
- `--force, -f` - Overwrite existing files
- `--env <path>` - Path to .env file (default: `.env`)

### Environment Variables

```env
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASS=postgres
DB_NAME=your_database
```

### Example Output

```typescript
import { Model, DataTypes, Optional } from "sequelize";
import { Table, Column } from "sequelize-typescript";

export interface UserAttributes {
  id?: number;
  email: string;
  name?: string;
  role: string;
  createdAt?: Date;
  updatedAt?: Date;
}

export interface UserCreationAttributes
  extends Optional<UserAttributes, "id" | "createdAt" | "updatedAt"> {}

@Table({ tableName: "users", timestamps: true, underscored: true })
export class User extends Model<UserAttributes, UserCreationAttributes> {
  @Column({
    type: DataTypes.INTEGER,
    primaryKey: true,
    autoIncrement: true,
  })
  id!: number;

  @Column({
    type: DataTypes.STRING,
    allowNull: false,
  })
  email!: string;

  @Column({
    type: DataTypes.STRING,
    allowNull: true,
  })
  name?: string;

  @Column({
    type: DataTypes.STRING,
    allowNull: false,
    validate: { isIn: [["USER", "ADMIN", "MODERATOR"]] },
  })
  role!: string;
}
```

## Library Usage

### Basic Usage

```typescript
import { DynamicFormCore } from "@hero-dynamic-form/core";
import { SequelizeEntityInspector } from "@hero-dynamic-form/sequelize-inspector";
import { Sequelize, DataTypes, Model } from "sequelize";

// Define Sequelize models
class User extends Model {}

User.init(
  {
    id: {
      type: DataTypes.INTEGER,
      primaryKey: true,
      autoIncrement: true,
    },
    email: {
      type: DataTypes.STRING,
      allowNull: false,
      unique: true,
      validate: {
        isEmail: true,
      },
    },
    name: {
      type: DataTypes.STRING,
      allowNull: true,
    },
    role: {
      type: DataTypes.ENUM("USER", "ADMIN", "MODERATOR"),
      defaultValue: "USER",
    },
    age: {
      type: DataTypes.INTEGER,
      validate: {
        min: 0,
        max: 150,
      },
    },
  },
  {
    sequelize: new Sequelize("sqlite::memory:"),
    modelName: "User",
  }
);

// Create inspector
const inspector = new SequelizeEntityInspector();

const config = {
  entityInspector: inspector,
  resources: [
    {
      name: "users",
      entity: User, // Pass Sequelize model
    },
  ],
  // ... rest of config
};

const core = new DynamicFormCore(config);
await core.init();
await core.start();
```

## Features

- Extracts Sequelize model metadata from `rawAttributes`/`tableAttributes`
- Generates validation rules based on field types and constraints
- Supports all Sequelize DataTypes:

  - STRING/TEXT/CHAR → text
  - INTEGER → int
  - BIGINT → bigint
  - FLOAT/REAL → float
  - DOUBLE → double
  - DECIMAL → decimal
  - BOOLEAN → boolean
  - DATE → timestamp
  - DATEONLY → date
  - TIME → time
  - JSON → json
  - JSONB → jsonb
  - BLOB → bytea
  - UUID → uuid
  - ENUM → enum
  - ARRAY → array
  - GEOMETRY → geometry
  - GEOGRAPHY → geography

- Supports field options:

  - `primaryKey` - Primary key detection
  - `allowNull` - Nullable fields
  - `defaultValue` - Default values
  - `unique` - Unique constraints
  - `autoIncrement` - Auto-increment fields
  - `values` - Enum values

- Automatic validation from Sequelize `validate` option:

  - `isEmail` - Email format
  - `isUrl` - URL format
  - `isUUID` - UUID format
  - `len: [min, max]` - String length
  - `min` - Numeric minimum
  - `max` - Numeric maximum

- Convention-based validation:
  - Fields named `email`: email pattern
  - Fields named `url`: URL pattern
  - Fields named `phone`/`tel`: phone pattern

## Sequelize Model Example

```typescript
import { Sequelize, DataTypes, Model } from "sequelize";

const sequelize = new Sequelize("sqlite::memory:");

class User extends Model {}

User.init(
  {
    id: {
      type: DataTypes.INTEGER,
      primaryKey: true,
      autoIncrement: true,
    },
    email: {
      type: DataTypes.STRING,
      allowNull: false,
      unique: true,
      validate: {
        isEmail: true,
      },
    },
    username: {
      type: DataTypes.STRING(50),
      allowNull: false,
      unique: true,
      validate: {
        len: [3, 50],
      },
    },
    password: {
      type: DataTypes.STRING,
      allowNull: false,
      validate: {
        len: [8, 100],
      },
    },
    name: {
      type: DataTypes.STRING,
      allowNull: true,
    },
    bio: {
      type: DataTypes.TEXT,
      allowNull: true,
    },
    age: {
      type: DataTypes.INTEGER,
      validate: {
        min: 0,
        max: 150,
      },
    },
    role: {
      type: DataTypes.ENUM("USER", "ADMIN", "MODERATOR"),
      defaultValue: "USER",
    },
    isActive: {
      type: DataTypes.BOOLEAN,
      defaultValue: true,
    },
    metadata: {
      type: DataTypes.JSON,
      allowNull: true,
    },
    website: {
      type: DataTypes.STRING,
      validate: {
        isUrl: true,
      },
    },
    phoneNumber: {
      type: DataTypes.STRING,
    },
    createdAt: {
      type: DataTypes.DATE,
      allowNull: false,
    },
    updatedAt: {
      type: DataTypes.DATE,
      allowNull: false,
    },
  },
  {
    sequelize,
    modelName: "User",
    tableName: "users",
    timestamps: true,
  }
);

class Post extends Model {}

Post.init(
  {
    id: {
      type: DataTypes.UUID,
      defaultValue: DataTypes.UUIDV4,
      primaryKey: true,
    },
    title: {
      type: DataTypes.STRING,
      allowNull: false,
      validate: {
        len: [1, 200],
      },
    },
    content: {
      type: DataTypes.TEXT,
      allowNull: true,
    },
    published: {
      type: DataTypes.BOOLEAN,
      defaultValue: false,
    },
    views: {
      type: DataTypes.INTEGER,
      defaultValue: 0,
      validate: {
        min: 0,
      },
    },
    tags: {
      type: DataTypes.ARRAY(DataTypes.STRING),
      defaultValue: [],
    },
    userId: {
      type: DataTypes.INTEGER,
      allowNull: false,
      references: {
        model: User,
        key: "id",
      },
    },
  },
  {
    sequelize,
    modelName: "Post",
    tableName: "posts",
  }
);

// Define associations
User.hasMany(Post, { foreignKey: "userId" });
Post.belongsTo(User, { foreignKey: "userId" });
```

## Full Example

```typescript
import { DynamicFormCore, defineConfig } from "@hero-dynamic-form/core";
import { SequelizeEntityInspector } from "@hero-dynamic-form/sequelize-inspector";
import { Sequelize, DataTypes, Model } from "sequelize";

// Setup Sequelize
const sequelize = new Sequelize({
  dialect: "postgres",
  host: "localhost",
  database: "mydb",
  username: "user",
  password: "password",
});

// Define models (User, Post, etc.)
// ...

// Create inspector
const inspector = new SequelizeEntityInspector();

const config = defineConfig({
  dataSource: sequelize, // Pass Sequelize instance
  entityInspector: inspector,
  resources: [
    {
      name: "users",
      entity: User,
      label: "Users",
    },
    {
      name: "posts",
      entity: Post,
      label: "Posts",
    },
  ],
  auth: {
    enabled: true,
    adapter: myAuthAdapter,
  },
  backend: {
    port: 3030,
  },
});

const core = new DynamicFormCore(config);
await core.init();
await core.start();
```

## Type Mappings

| Sequelize Type   | Normalized Type |
| ---------------- | --------------- |
| STRING/TEXT/CHAR | text            |
| INTEGER          | int             |
| BIGINT           | bigint          |
| FLOAT/REAL       | float           |
| DOUBLE           | double          |
| DECIMAL          | decimal         |
| BOOLEAN          | boolean         |
| DATE             | timestamp       |
| DATEONLY         | date            |
| TIME             | time            |
| JSON             | json            |
| JSONB            | jsonb           |
| BLOB             | bytea           |
| UUID             | uuid            |
| ENUM             | enum            |
| ARRAY            | array           |
| GEOMETRY         | geometry        |
| GEOGRAPHY        | geography       |

## Validation Rules

The inspector automatically generates validation rules from:

1. **Field Options**:

   - `allowNull: false` → required
   - `defaultValue` → skips required if present
   - `values` (for ENUM) → enum validation

2. **Sequelize Validate Options**:

   - `isEmail: true` → email pattern
   - `isUrl: true` → URL pattern
   - `isUUID: true` → UUID pattern
   - `len: [min, max]` → minLength/maxLength
   - `min: number` → numeric minimum
   - `max: number` → numeric maximum

3. **Type-based Validation**:

   - Numeric types: min/max ranges
   - DATE: valid date check
   - BOOLEAN: boolean check
   - JSON/JSONB: valid JSON check
   - UUID: UUID pattern check

4. **Convention-based Validation**:
   - Fields named `email`: email pattern
   - Fields named `url`: URL pattern
   - Fields named `phone`/`tel`: phone pattern

## API

### SequelizeEntityInspector

```typescript
class SequelizeEntityInspector implements EntityInspector {
  // Inspect Sequelize model (required by EntityInspector interface)
  inspectEntity(entity: any): ResourceSchema;
}
```

## Notes

- Works with Sequelize v6+
- Supports all dialects: PostgreSQL, MySQL, SQLite, MSSQL, MariaDB
- Extracts metadata from `model.rawAttributes` or `model.tableAttributes`
- Respects Sequelize's built-in validation rules

## License

MIT