name: core
description: Self-contained skill for designing and maintaining the @*/core package in a hexagonal-architecture TypeScript monorepo built on the @efesto-cloud/* libraries (Entity, Result, Maybe, Population, Publisher, Observable, Usecase), Inversify, pnpm workspaces, and Biome. Use this skill on any task that touches the core package or its adapter packages — adding or changing an entity, value object, type/enum/dict, DTO, domain error, repository port, service port, use case, DI binding, module assembly, the composition root, the Prisma persistence adapter, soft-delete behaviour, population/eager-loading, the Result/Maybe error-handling pattern, or any question about how the layers fit together. Also use whenever the user asks where a new file should live, how DI/Inversify/Symbols work in this project, what InternalSymbols vs UseCaseSymbols are for, how resolveUseCase("…") gets its types, what an installer/ContainerModule is, how the webapp wires everything at boot, how mappers reconstruct value objects, or how soft-delete is enforced. Triggers even when the user does not say "core" — phrases like "add a new feature", "wire up X", "where do I put", "how do I bind", "how does this codebase work" all qualify if the project uses this stack.
Core Skill
Front door for working in a @*/core package — the domain heart of a
hexagonal-architecture TypeScript monorepo built on the @efesto-cloud/*
libraries, Inversify, pnpm workspaces, Biome, and nodenext module
resolution.
This skill is self-contained. Every layer you need to touch — entity,
value object, type/enum/dict, DTO, error, repository port, service port,
use case, DI binding, persistence adapter, population — is documented in
references/. Open the relevant reference when you start working on that
layer.
When to use
Reach for this skill on any task touching the core package or its adapters:
- "Add a
Fooentity" / "add a field toBar" / "renameBaz" - "Add a value object for X" / "validate Y as a domain primitive"
- "Add a use case that does Z" / "I need a new endpoint backed by …"
- "Wire up a new repo / a new service"
- "Where does the mapper go?" / "Why doesn't
resolveUseCaseautocomplete?" - "Explain how DI is set up here" / "What is
InternalSymbols?" - "Add population for Foo's bars" / "make findById eager-load X"
- "Soft-delete is broken" / "show me the soft-delete flow"
- Anything starting with "where do I put …" in this codebase.
Trigger even when the user does not say "core" — anything that implicates the core package or its surrounding adapters fits.
The three-package picture
┌─────────────────────────────────────────────────────────────┐
│ @efesto-cloud/* ── Entity · Result · Maybe · Population │
│ PrismaContext · Publisher · Observable │
│ Usecase │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ @*/core ── Domain: entities, value objects, │
│ types/enums/dicts, DTOs, errors │
│ Application: use cases │
│ Ports: repository interfaces, │
│ service interfaces │
│ DI: InternalSymbols + UseCaseSymbols │
│ + per-domain ContainerModules │
└─────────────────────────────────────────────────────────────┘
│
┌───────────┴───────────┐
▼ ▼
┌──────────────────────────┐ ┌──────────────────────────┐
│ @*/prisma │ │ @*/stub │
│ ── Prisma adapters: │ │ ── in-memory adapters │
│ repo impls, mappers, │ │ for tests / mocks │
│ install() module │ │ │
└──────────────────────────┘ └──────────────────────────┘
│ │
└───────────┬───────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ @*/webapp ── React Router 7 + Cloudflare Workers │
│ container.server.ts is the composition │
│ root: initContainer() + installPrisma() │
│ or installStub() + installServices() │
└─────────────────────────────────────────────────────────────┘
@efesto-cloud/*— shared libraries.Entitybase class,Result/Maybemonads,Populate<T>types,IPrismaContext,Publisher/Observable,IUseCase.@*/core— domain + application + ports. Every interface lives here. No knowledge of any DB or framework.@*/prisma— Prisma adapter.XRepoImpl+XMapperfiles, exports aninstall({ DB })ContainerModule. Imports core but core does not import it.@*/stub— in-memory adapter for mock environments and tests. Same shape:install()returns aContainerModule.@*/webapp— composition root. Boots the container, loads core's own modules + one adapter module + services module, exposes a typedresolveUseCase("domain.UseCaseName")to route loaders/actions.
The dependency arrow only points downward. core never imports from
prisma, stub, or webapp. See references/architecture.md.
The hard rules
Six rules pull their weight everywhere; everything else is detail.
Result<T, E>everywhere — never throw. Domain factories, entity mutators, use cases all returnResult. Throwing escapes the type system and bypasses the caller's error-handling. Exceptions exist (mapperfromon corrupted DB rows,unwrapOrThrowat the HTTP boundary) and are flagged where they apply..jsextensions on every local import.nodenextmodule resolution +tsc-aliasrequire it.import Foo from "~/entity/Foo.js"— even though the source file isFoo.ts.- Soft-delete via
entity.delete()+repo.save().delete()setsdeleted_atto now;save()persists the field. There is norepo.delete().findByIdfiltersdeleted_at IS NULLby default; pass{ includeDeleted: true }(used only by restore) to opt in. - Use cases never call other use cases. Shared behaviour goes on entities or in a domain service. Calling a peer use case smuggles one transaction into another and hides dependencies.
- Mappers live in the adapter package, not in core. Core defines
the repository interface (port) and what an entity looks like;
XMapper.from/tois Prisma-specific and lives in@*/prisma/src/mapper/. - Constructor injection only.
@inject(InternalSymbols.X) private readonly x: IX. No property injection, nocontainer.get(...)inside a use case.
The why for each rule is in references/conventions.md.
The 12-step recipe for a new feature
You're adding a Foo aggregate with a CreateFoo use case backed by
Prisma. The order matters: each step compiles on its own and feeds the
next.
| # | Step | File(s) | Reference |
|---|---|---|---|
| 1 | Value objects the entity needs | value_object/FooName.ts |
references/value-object.md |
| 2 | Constrained string fields (status, kind, role) | type/FooStatus.ts + enum/FooStatusEnum.ts + dict/FooStatusDesc.ts |
references/type-enum-dict.md |
| 3 | The entity itself | entity/Foo.ts |
references/entity.md |
| 4 | Its DTO | dto/FooDto.ts |
references/dto.md |
| 5 | Domain errors the entity / use case can return | errors/FooNotFoundError.ts, etc. |
references/errors.md |
| 6 | Repository port (interface) | repo/IFooRepository.ts |
references/repository-port.md |
| 7 | Service ports needed (clock, hasher, …) | already exist; add if new | references/service-port.md |
| 8 | Use case interface | useCase/foos/ICreateFooUseCase.ts |
references/usecase.md |
| 9 | Use case impl + binding + registry augmentation | useCase/foos/impl/CreateFooUseCase.ts + useCase/foos/FoosModule.ts |
references/usecase.md + references/di-layer.md |
| 10 | DI symbol entries | di/UseCaseSymbols.ts (new foos.CreateFoo entry); di/InternalSymbols.ts (new Repo.Foo entry) |
references/di-layer.md |
| 11 | Adapter: repo impl + mapper + installer edit | @*/prisma/src/repository/FooRepoImpl.ts + @*/prisma/src/mapper/FooMapper.ts + edit @*/prisma/src/install.ts |
references/prisma-persistence.md |
| 12 | Route loader / action wiring | @*/webapp/app/routes/foos/...tsx calling context.resolveUseCase("foos.CreateFoo").execute(...) |
references/composition-root.md |
A full end-to-end trace with code at every step is in
references/feature-walkthrough.md.
Topic index
Architecture & cross-cutting
| Reference | What it covers |
|---|---|
references/architecture.md |
The three-package picture in depth; what may import what. |
references/folder-structure.md |
Canonical src/ layout with one line per folder. |
references/conventions.md |
.js, Result/Maybe, soft-delete, Europe/Rome, never-throw, …with the why for each. |
references/di-layer.md |
InternalSymbols, UseCaseSymbols, <Domain>Module.ts, UseCaseRegistry augmentation, scope decisions. Largest unique value-add. |
references/composition-root.md |
How webapp/container.server.ts boots the container, switches between Prisma and stub, exposes resolveUseCase. |
references/feature-walkthrough.md |
One synthetic Foo feature traced through every file the 12-step recipe touches. |
Domain layer
| Reference | What it covers |
|---|---|
references/entity.md |
@efesto-cloud/entity Entity base class, create() factory, mutators, toDTO(), soft-delete, audit fields. |
references/value-object.md |
Immutable scalar / composite VOs, private constructor + static create(), toRaw(). |
references/type-enum-dict.md |
T* string union, *Enum runtime record, *Desc label map. |
references/dto.md |
<Entity>Dto shape, marshalling rules, when to use a DTO vs a use-case input/response type. |
references/errors.md |
DomainError base class, per-condition parameterless subclasses, where to put them. |
Application layer
| Reference | What it covers |
|---|---|
references/usecase.md |
IUseCase<I, O> from @efesto-cloud/usecase, @injectable() impl, the "load → mutate → save → Result" body, decorators. |
references/repository-port.md |
IFooRepository interface conventions, return types, the IEntityMapper<E, R> contract. |
references/service-port.md |
How to design a non-repo port (clock, hasher, codec, mailer): port location vs impl location, scope decisions. |
Persistence (Prisma)
| Reference | What it covers |
|---|---|
references/prisma-persistence.md |
FooRepoImpl + FooMapper, IPrismaContext, transactions, soft-delete. |
references/population.md |
Generic Populate<T> / Shape types — DB-agnostic. |
references/prisma-population.md |
BasePrismaPopulator, toPrismaInclude(), patches to entity/DTO/mapper/repo. |
Error & null handling
| Reference | What it covers |
|---|---|
references/monad-result.md |
Result<T, E> from @efesto-cloud/result: construction, branching, transforms, serialisation. |
references/monad-maybe.md |
Maybe<T> from @efesto-cloud/maybe: when to prefer over null. |
Reactive primitives
| Reference | What it covers |
|---|---|
references/publisher.md |
Publisher<ARGS> — stateless event broadcasting. |
references/observer.md |
IObservable<T> — stateful reactive value. |
The DI nutshell
Two symbol files split by audience:
di/InternalSymbols.ts— repo + service + DB-context tokens. Consumed by use-case implementations via@inject(InternalSymbols.Repo.X). Bound at composition time by the adapter package'sinstall.ts.di/UseCaseSymbols.ts— use-case tokens, organised by domain (UseCase.<domain>.<Name>). Bound by per-feature<Domain>Module.tsfiles insideuseCase/<domain>/.
A type-only registry (di/UseCaseRegistry.ts) starts empty. Every
<Domain>Module.ts augments it with declare module "~/di/UseCaseRegistry.js" — that's what gives
resolveUseCase("members.GetMember") its static return type.
The webapp's composition root calls initContainer() (loads core's
modules), then container.load(installServices(), installPrisma({…}))
to bind the adapters, then creates a typed resolver with
createUseCaseResolver(container).
Full story in references/di-layer.md and references/composition-root.md.
How to use the references
Open them on demand. Don't read everything up-front — each reference is
self-contained and focuses on its layer. The cross-references between
references go to other files under references/, never out to a
separate skill.