-
Notifications
You must be signed in to change notification settings - Fork 10
import rules
- Keep module graphs small for editor responsiveness, bundling, and typechecking.
- Avoid loading full domain trees when only one submodule is needed.
- Do not use
export *barrels at domain roots. - Do not use self re-exports like
export * as X from "./X.js". - Import from the smallest module path possible (e.g.
#Domain/WorkflowA/modelsfor a single submodule, not a domain-wide barrel). - Local sibling imports are fine and usually preferred (
./models.js,./services/X.js,./resources/X.js) when the consumer lives in the same module tree. - Prefer
import * as Xaliases instead of{ X as Y }renames. - Keep namespace names explicit and context-aware when needed.
- Do not destructure helpers from namespace modules (avoid
const { copy } = Utils,const { pipe } = Fn). Import helpers directly from their module instead. - Domain-level and workflow-level
services.tsbarrels are removed. Import concrete service modules directly. - For cross-workflow DB/repo imports, use
import * as XDB from ".../services/DBContext.js"(where the target is a sibling workflow or a child workflow).DBContextfiles re-export workflow repos, so repo/persistence-layer consumers should useXDB.OrderRepo,XDB.CartRepo, andXDB.DBContext.- Within the same workflow, keep local sibling imports (
./services/OrderRepo.js). - When importing upward from a workflow into a parent/shared scope (e.g.
Domain/WorkflowA/...importing../services/CartRepo.js), keep named imports — don't wrap parent helpers in a workflow namespace inside files that already live under that scope. Direct imports from concrete service modules still make sense for non-repo services likeDashboard,Mailer, orImport.
- Within the same workflow, keep local sibling imports (
-
One umbrella alias per
srcroot — no per-file or per-symbol aliases. Each package exposes a single#<Root>/*subpath alias per top-levelsrcdirectory (#Amazon/*→src/Amazon/*,#Empasa/*,#models/*,#resources/*, …), declared once inpackage.json#importsand mirrored in everytsconfig*.jsonpaths. Do not add narrow aliases like#services/PickImport,#OnePick/*, or#messages/PrintOnePickthat point into a subtree the umbrella already covers — they duplicate the umbrella and silently rot when files move. Reach moved code through the umbrella (#Amazon/services/PickImport,#Amazon/OnePick/models). The frontend follows the same shape: one nuxt alias per aliasedapi/src/<root>(#Amazon→../api/src/Amazon), enforced by theguard_fe_alias_coverageCI step (ROOTSin.github/workflows/_build.yml+api_sharedin.github/build-filters.yml). -
A refactor that moves or renames a module goes all the way through. Update every reference in the same change — internal
#aliases, relative paths,package.json#imports, alltsconfig*.jsonpaths, thepackage.json#exportsfield used by external consumers (e2e, frontend), and the consumers themselves. Then delete the old aliases and re-exports. Never leave a back-compat shim: no alias still pointing at the pre-move location, no./OldName/*export forwarding to the new path, no re-export "for consumers". A move is complete only whengrepfor the old path returns zero hits acrossapi,e2e, andfrontend.
"Cross-workflow" means the target lives in a different workflow than the consumer. Practically, this is:
-
Sibling workflow (e.g.
Domain/WorkflowAimporting fromDomain/WorkflowB). -
Parent reaching into a child workflow (e.g.
Domain/services/Export.tsimporting fromDomain/WorkflowA/models).
In both cases use the workflow-prefixed namespace form (WorkflowAModels, WorkflowBDB, …).
The workflow-namespace form does not apply to:
-
Same workflow — local sibling imports (
./models.js,./services/X.js) are preferred (rule 4). E.g. insideDomain/WorkflowA/useimport { OrderRepo } from "./services/OrderRepo.js", not* as WorkflowADB from .... -
Child reaching outward to a parent / shared scope — keep named imports. E.g. inside
Domain/WorkflowA/importing from../services/CartHelpers.jsstaysimport { cartHelpers } from "../services/CartHelpers.js". Wrapping parent helpers in aDomainDB/DomainModelsnamespace inside a file that already lives underDomain/...adds noise without disambiguating anything.
The ts-plugin-prefer-namespace-import refactor enforces this directionality automatically — it only offers the conversion for sibling or parent-into-child imports.
Inside the same workflow, prefer local sibling named imports:
import { Order } from "./models.js"import { OrderRepo } from "./services/OrderRepo.js"
A short namespace alias (import * as DB from "./services/DBContext.js") is acceptable only when the file imports from a single workflow (its own) and several repos from the same DBContext are used together. The moment a second workflow appears in scope, rename the local one to the explicit workflow-aware form (WorkflowADB, WorkflowBDB) so both sides are unambiguous. Named imports from ./... are preferred otherwise.
Across workflows, keep the workflow in the namespace:
import * as WorkflowAModels from "#Domain/WorkflowA/models"import * as WorkflowADB from "#Domain/WorkflowA/services/DBContext"import * as WorkflowACore from "#Domain/WorkflowA/core"import * as WorkflowAEvents from "#Domain/WorkflowA/events"import * as DomainWorkflowAModels from "#Domain/WorkflowA/models"
Use short names only for local context. When multiple workflows are in scope, prefer explicit workflow-aware names like WorkflowAModels, WorkflowBDB, WorkflowCModels, WorkflowDDB.
If a symbol name collides, keep the symbol unchanged and resolve the collision via the namespace, for example WorkflowACore.Orders vs WorkflowBCore.Orders.
Avoid renaming individual imports in application code like OrderInput as WorkflowAOrderInput or Orders as WorkflowAOrders.
If a collision appears, rename or add the namespace, not the imported symbol.
Exports must not bake the owning module, workflow, or domain name into their identifier. The import path and (for cross-workflow consumers) the namespace alias already provide that context — repeating it in the symbol creates noise like Domain.DomainCartBridge, WorkflowA.WorkflowAOrder, OtherDomain.OtherDomainPrintService.
- Inside
Domain/services/CartBridge.ts→export class CartBridge, notDomainCartBridge. - Inside
Domain/WorkflowA/models.ts→export const Partner, notWorkflowACompany;export const Carriers, notWorkflowACarriers. - Inside
OtherDomain/services/PrintService.ts→export class PrintService, notOtherDomainPrintService.
Consumers in the same workflow use the bare name. Cross-workflow consumers reach for it through the namespace (Domain.CartBridge, OtherDomain.PrintService). Same-file collisions (e.g. a service class plus its Work Layer export) are the only case where an internal disambiguator like WorkService is acceptable; do not introduce module-prefixed names just to avoid the disambiguator.
The naming rule above governs the class/symbol name — never bake the workflow or domain into it. The Context.Service tag string (the runtime DI identifier passed to Context.Tag(...)) is a separate axis and follows the opposite rule when disambiguation is needed:
-
Class name (what you
import): always bare —CartRepo, neverWorkflowACartRepo. -
DI tag string (runtime identity): workflow prefix when two workflows in the same process expose the same concept —
"WorkflowACartRepo"vs"WorkflowBCartRepo"vs"SubWorkflowCartRepo"all coexist in a single domain process. Domain prefixes do not belong in the tag: a process only runs one domain, so unprefixed tags like"Import","Work", and"ExternalSystem"are sufficient — no"DomainImport".
The class name is disambiguated by the import path + namespace alias (WorkflowADB.CartRepo vs WorkflowBDB.CartRepo). The tag string has no such context at runtime, so it must carry the workflow itself.
import { Dashboard } from "#Domain/services/Dashboard"
import * as WorkflowAModels from "#Domain/WorkflowA/models"
import * as WorkflowADB from "#Domain/WorkflowA/services/DBContext"import { ImportOrdersBwc } from "./rootSchemas.js"
import { Dashboard } from "./services/Dashboard.js"
import { Reset } from "./services/Reset.js"import * as WorkflowBCore from "../WorkflowB/core.js"
import * as WorkflowBDB from "../WorkflowB/services/DBContext.js"
import * as WorkflowACore from "../WorkflowA/core.js"
import * as WorkflowADB from "../WorkflowA/services/DBContext.js"import { DBContext as WorkflowBDBContext, OrderRepo as WorkflowBOrderRepo } from "../WorkflowB/services/DBContext.js" // avoid
import { Orders as WorkflowAOrders } from "../WorkflowA/core.js" // avoid
import { OrderInput as WorkflowAOrderInput } from "../WorkflowA/events.js" // avoidPrefer:
import * as WorkflowBDB from "../WorkflowB/services/DBContext.js"
import * as WorkflowACore from "../WorkflowA/core.js"
import type * as WorkflowAEvents from "../WorkflowA/events.js"All three below are cross-workflow imports (consumer lives in a different workflow than the target). Use the workflow-namespace form from the parent DBContext.js instead.
import { CartRepo as SubWorkflowACartRepo } from "../../SubWorkflowA/services/CartRepo.js" // avoid (cross-workflow named)
import { DBContext as SubWorkflowBDBContext } from "../../SubWorkflowB/services/DBContext.js" // avoid (cross-workflow rename)
import { OrderRepo } from "../WorkflowA/services/OrderRepo.js" // avoid (cross-workflow sibling — consumer is NOT inside WorkflowA/)Prefer:
import * as SubWorkflowADB from "../../SubWorkflowA/services/DBContext.js"
import * as SubWorkflowBDB from "../../SubWorkflowB/services/DBContext.js"const subWorkflowA = {
deliveryNoteRepo: yield * SubWorkflowADB.DeliveryNoteRepo,
cartRepo: yield * SubWorkflowADB.CartRepo
}
const subWorkflowB = { pickItemRepo: yield * SubWorkflowBDB.PickItemRepo }import * as Utils from "effect-app/utils"
const { copy } = Utils // avoidPrefer:
import { copy } from "effect-app/utils"If a lint rule conflicts with this convention, prefer configuring lint to allow direct named imports for helper functions from small utility modules (instead of forcing namespace+destructure patterns).
- Index
- Import Rules
- Resource & Controller Layout
- Command Pattern
- Command Input Validation
- Query Shape: List vs Get
- Database Query Guidelines
- List Layout
- Streams & Progress
- Vue Conventions
- E2E State Pattern
- E2E
- E2E Toast Wait Audit
- Flow Documentation
- (project-local — create
flows/when first workflow lands)