From Prisma
Create or modify domain entities using the @efesto-cloud/entity package. Use this skill whenever the user asks to add a new entity, update an existing entity, add properties or methods to an entity, or work on the entity/dto layer. Trigger when the user says things like "create a Foo entity", "add a field to Bar", "I need a new domain object", or "add entity X". Also trigger for DTO creation or modification.
How this skill is triggered — by the user, by Claude, or both
Slash command
/Prisma:entityThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill helps you create or modify domain entities following the hexagonal architecture conventions using the **`@efesto-cloud/entity`** package. **Scope: entity class + DTO interface only** — persistence (repository, mapper, MongoDB document) is a separate concern handled elsewhere.
This skill helps you create or modify domain entities following the hexagonal architecture conventions using the @efesto-cloud/entity package. Scope: entity class + DTO interface only — persistence (repository, mapper, MongoDB document) is a separate concern handled elsewhere.
Installation: If not already installed, add the package with pnpm add @efesto-cloud/entity (peer dependency: luxon).
Read the existing codebase before generating code:
packages/core/src/entity/) and read a similar existing entity as a referencepackages/core/src/dto/) and read a similar DTOCLAUDE.md inside the entity directory — it may have project-specific conventionsIf the project is new or the entity/dto directories are empty, use the reference files in this skill's references/ directory as your canonical examples instead:
references/entity-example.ts — fully annotated entity covering every patternreferences/dto-example.ts — fully annotated DTO covering every patternreferences/entity-union.ts — entity union pattern: abstract base + multiple concrete subtypes discriminated by a type literalreferences/dto-union.ts — DTO union pattern: base interface + concrete sub-interfaces each carrying a type literal discriminantThese reference files cover all the patterns described below. Read them before generating code on a fresh project — they contain inline comments explaining the why behind each convention.
For a new entity you'll create two files:
src/dto/IEntityName.ts — the public data contract (DTO interface)src/entity/EntityName.ts — the domain entity classThen export both from their respective index files.
For modifying an existing entity, read the files first, then apply changes consistently across both.
The DTO is the public serialization contract. It has no methods — pure native data types.
// src/dto/IEntityName.ts
export default interface IEntityName {
_id: string; // always string (serialized from ObjectId or UUID)
name: string;
description: string;
slug?: string;
deleted_at: string | null; // DateTime serialized as ISO string
// foreign keys end with _id: string
parent_id: string | null;
}
Rules:
snake_case_id: stringparent_id, elemento_id, etc.DateTime → string (ISO); nullable → string | null// src/entity/EntityName.ts
import { Entity, IEntity } from "@efesto-cloud/entity";
import Maybe from "@efesto-cloud/maybe";
import Result from "@efesto-cloud/result";
import { DateTime } from "luxon";
import { ObjectId } from "mongodb"; // or UUID — check what the project uses
import IEntityName from "~/dto/IEntityName.js";
type EntityNameProps = {
name: string;
description: string;
deleted_at: DateTime<true> | null;
// Use domain types, not primitives for complex concepts
// e.g. email: EmailAddress (not string)
};
export default class EntityName extends Entity<EntityNameProps, ObjectId> implements IEntity<ObjectId> {
constructor(props: EntityNameProps, id?: ObjectId) {
super(props, new ObjectId(id)); // always wrap in ID constructor
}
// Getters — one per prop, no setters unless business-justified
get name(): string { return this.props.name; }
get description(): string { return this.props.description; }
// Business methods — describe intent, not data access
// Return Result<T, Error> for fallible operations
// Return void for infallible mutations
someOperation(input: string): Result<void, Error> {
if (!input) return Result.err(new Error("Invalid input"));
this.props.name = input;
return Result.ok(undefined);
}
toDTO(): IEntityName {
return {
_id: this._id.toHexString(), // ObjectId → string
// _id: this._id.toString(), // UUID → string
name: this.props.name,
description: this.props.description,
deleted_at: this.deleted_at?.toISO() ?? null,
// nested entity: this.props.nested?.toDTO() ?? null
// array: this.props.items.map(i => i.toDTO())
};
}
static create(props: {
name?: string;
description?: string;
deleted_at?: DateTime<true> | null;
// all props optional — use ?? to apply defaults
}, id?: ObjectId) {
return new EntityName({
name: props.name ?? "",
description: props.description ?? "",
deleted_at: props.deleted_at ?? null,
}, id);
}
}
Key rules:
super(props, new ObjectId(id)) — wraps id even if already the right typestatic create()create() props are optional with ?? defaults, required fields are the ones strictly necessary (usually dictated by business rules, not technical ones)deleted_at pattern: DateTime<true> | null for soft deletesAll entities inherit these from the Entity base class — do not redefine them:
| Member | Type | Description |
|---|---|---|
v | number | null | Version number — 0 for new (unsaved) entities, > 0 after first persist |
isNew() | () => boolean | Returns true when v === 0 |
isUpdated() | () => boolean | Returns true when v !== 0 |
delete() | () => void | Soft-deletes: sets deleted_at to now |
restore() | () => void | Undoes a soft-delete: sets deleted_at to null |
isDeleted() | () => boolean | Returns true when deleted_at !== null |
When extending a base entity class:
type EntityNameProps = BaseProps & { extraField: string; };
class EntityName extends BaseClass<EntityNameProps, ObjectId> {
override toDTO(): IEntityName {
return { ...super.toDTO(), extraField: this.props.extraField };
}
}
For the union + abstract base pattern (multiple concrete subtypes distinguished by a type literal), read references/entity-union.ts (entities) and references/dto-union.ts (DTOs). Both files use matching type names and discriminants so you can see the entity↔DTO correspondence directly.
For entities that track who changed what:
// In props:
created_at: DateTime;
updated_at: DateTime;
updated_by: Operator | null; // import the Operator entity
// In business methods that mutate state:
this.props.updated_at = DateTime.now();
this.props.updated_by = operator;
Prefer value objects for complex domain concepts rather than raw primitives. Check the project's src/value_object/ directory for what's available before defaulting to string. Common ones:
EmailAddressPasswordAdd to src/entity/index.ts:
export { default as EntityName } from "./EntityName.js";
Add to src/dto/index.ts:
export type { default as IEntityName } from "./IEntityName.js";
src/dto/IEntityName.ts with _id: stringsrc/entity/EntityName.ts with create() factorycreate() has optional props with ?? defaultstoDTO() maps every DTO field; ID converted to stringEntityNameProps typecreate() updated with optional prop + defaulttoDTO() updated to include new fieldCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub efesto-cloud/lib --plugin Prisma