AgentSkillsCN

Scaffold Entity

脚手架实体

SKILL.md

Scaffold Entity

Generate a DDD Entity class with proper identity, equality, and encapsulation.

Triggers

  • "scaffold entity"
  • "create entity"
  • "new entity"

Instructions

Step 1: Gather Information

Ask the student for:

  1. Entity name (e.g., Order, Customer, Product)
  2. Properties with types (e.g., name: string, email: string, status: OrderStatus)
  3. Invariants — business rules that must always be true (e.g., "email must be valid", "name cannot be empty")

If the student provides a name only, suggest reasonable properties based on the domain.

Step 2: Determine Output Location

Check the current working directory:

  • If in a before/ directory, create files there
  • If in a lesson directory, create in before/
  • Otherwise, create in the current directory

Step 3: Generate Entity File

Create {entity-name}.ts (kebab-case filename):

typescript
import { z } from 'zod';

// === Branded ID Type ===

declare const __brand: unique symbol;
type Brand<T, B extends string> = T & { [__brand]: B };

export type {EntityName}Id = Brand<string, '{EntityName}Id'>;

export function create{EntityName}Id(value: string): {EntityName}Id {
  return value as {EntityName}Id;
}

// === Validation Schema ===

const {EntityName}PropsSchema = z.object({
  // Properties with Zod validation
});

type {EntityName}Props = z.infer<typeof {EntityName}PropsSchema>;

// === Result Type ===

type Result<T, E = Error> =
  | { success: true; value: T }
  | { success: false; error: E };

// === Entity ===

export class {EntityName} {
  private constructor(
    private readonly _id: {EntityName}Id,
    private readonly props: {EntityName}Props
  ) {}

  // Factory method
  static create(
    id: {EntityName}Id,
    props: {EntityName}Props
  ): Result<{EntityName}> {
    const validation = {EntityName}PropsSchema.safeParse(props);
    if (!validation.success) {
      return {
        success: false,
        error: new Error(validation.error.errors.map(e => e.message).join(', ')),
      };
    }

    // Enforce invariants here

    return { success: true, value: new {EntityName}(id, validation.data) };
  }

  // Identity
  get id(): {EntityName}Id {
    return this._id;
  }

  // Property accessors (read-only)

  // Identity-based equality
  equals(other: {EntityName}): boolean {
    return this._id === other._id;
  }
}

Step 4: Generate Test File

Create {entity-name}.test.ts:

typescript
import { describe, it, expect } from 'vitest';
import { {EntityName}, create{EntityName}Id } from './{entity-name}';

describe('{EntityName}', () => {
  const validId = create{EntityName}Id('test-id-1');

  describe('create', () => {
    it('should create with valid properties', () => {
      const result = {EntityName}.create(validId, {
        // valid props
      });

      expect(result.success).toBe(true);
      if (result.success) {
        expect(result.value.id).toBe(validId);
      }
    });

    it('should fail with invalid properties', () => {
      const result = {EntityName}.create(validId, {
        // invalid props
      });

      expect(result.success).toBe(false);
    });
  });

  describe('equality', () => {
    it('should be equal when IDs match', () => {
      const entity1 = {EntityName}.create(validId, { /* props */ });
      const entity2 = {EntityName}.create(validId, { /* props */ });

      if (entity1.success && entity2.success) {
        expect(entity1.value.equals(entity2.value)).toBe(true);
      }
    });

    it('should not be equal when IDs differ', () => {
      const otherId = create{EntityName}Id('test-id-2');
      const entity1 = {EntityName}.create(validId, { /* props */ });
      const entity2 = {EntityName}.create(otherId, { /* props */ });

      if (entity1.success && entity2.success) {
        expect(entity1.value.equals(entity2.value)).toBe(false);
      }
    });
  });
});

Step 5: Explain the Pattern

After generating, briefly explain:

  1. Branded ID — prevents mixing up IDs from different entities
  2. Private constructor — forces creation through the factory method
  3. Factory method with Result — validates invariants before creating
  4. Identity-based equality — two entities are the same if their IDs match, regardless of property values
  5. Zod validation — enforces property constraints at the boundary

Customization Notes

  • Replace placeholder properties with the student's actual properties
  • Add specific invariant checks in the create method
  • Add domain-specific methods as needed
  • Keep the entity focused — if it's getting large, consider extracting Value Objects