tx-agent-kit
Packages

Core

DDD domain slices with strict layer direction, named exports, and determinism governance.

Core (packages/core)

The core package contains the domain logic for the entire system. It is organized as DDD domain slices under src/domains/, with each domain following a strict folder structure and layer dependency direction.

Domain Structure

Each domain slice must contain these folders:

packages/core/src/domains/<domain>/
  domain/       # Entities, value objects, pure business rules
  ports/        # Capability contracts (Effect Context.Tag services)
  application/  # Use-case orchestration (Effect services)
  adapters/     # External system adapter implementations

The runtime/ folder (layer wiring and composition) and ui/ folder (presentation-facing helpers) are optional.

The repositories/ and services/ folders are forbidden. Concrete persistence lives in packages/infra/db, and use-case orchestration belongs in application/.

Current Domains

DomainPurpose
authUser authentication, principal extraction, session management
organizationOrganization CRUD, membership, billing, subscriptions
invitationInvitation management and acceptance

Layer Dependency Direction

Dependencies must flow inward. The enforcement script validates every import path:

domain     -> domain only
ports      -> domain, ports
application -> domain, ports, application
adapters   -> domain, ports, adapters
runtime    -> all layers
ui         -> all layers

Cross-domain imports are forbidden. Each domain is a self-contained unit.

Domain Layer

The domain layer defines entities, value objects, and pure business rules. It must not import anything outside its own domain layer.

// packages/core/src/domains/organization/domain/organization.ts
export interface OrganizationRecord {
  id: string
  name: string
  billingEmail: string | null
  isSubscribed: boolean
  subscriptionStatus: SubscriptionStatus
  createdAt: Date
  updatedAt: Date
}

export const isValidOrganizationName = (name: string): boolean => {
  const trimmed = name.trim()
  return trimmed.length >= 2 && trimmed.length <= 64
}

Determinism constraint: Date.now(), new Date(), and Math.random() are forbidden in domain-layer code. Inject time and randomness through ports.

Ports Layer

Ports define abstract capability contracts using Effect's Context.Tag:

// packages/core/src/domains/organization/ports/organization-ports.ts
export class OrganizationStorePort extends Context.Tag('OrganizationStorePort')<
  OrganizationStorePort,
  {
    list: (userId: string, params: ListParams) => Effect.Effect<PaginatedResult<OrganizationRecord>, unknown>
    getById: (id: string) => Effect.Effect<OrganizationRecord | null, unknown>
    create: (input: CreateOrganizationInput) => Effect.Effect<OrganizationRecord | null, unknown>
    update: (input: UpdateOrganizationInput) => Effect.Effect<OrganizationRecord | null, unknown>
    remove: (id: string) => Effect.Effect<{ deleted: true }, unknown>
  }
>() {}

Ports must return Effect.Effect (never Promise), declare an explicit kind marker (crud or custom), avoid implementing layers with Layer.succeed/Layer.effect, and import port types from the domain layer via *Record interfaces.

Application Layer

Application modules orchestrate use cases by combining ports:

// packages/core/src/domains/organization/application/organization-service.ts
export class OrganizationService extends Context.Tag('OrganizationService')<OrganizationService, {
  getById: (principal: Principal, organizationId: string) => Effect.Effect<Organization, CoreError>
  create: (principal: Principal, input: CreateOrganizationCommand) => Effect.Effect<Organization, CoreError>
  // ...
}>() {}

Governance Rules

All domain layer files must use named exports; export default is forbidden. Environment reads must go through dedicated config modules rather than accessing process.env directly. Type assertions to any are not permitted. Model unknowns explicitly and decode with schema at boundaries. Suppression directives such as @ts-ignore are likewise forbidden; fix the underlying type issue instead of suppressing the error.

On this page