Skip to content
View as Markdown

Building Apps ​

This page describes how ERP Kit expects an app to be built, so that your team, your coding agent, and the ERP Kit skills follow the same conventions. The skills installed in your project under node_modules/@tailor-erp/kit/skills/ are the authority for your version.

The development loop ​

Two skills cover day-to-day work. Use them in any order, one story at a time:

  • erp-kit-app-impl builds or extends a feature. It wires modules, writes resolvers and app-owned models, connects screens, and adds tests.
  • erp-kit-app-docs records the feature as actor, business flow, story, screen, and resolver docs. Run it when the specification changes, not after every edit.

Each iteration runs these commands:

StepDirectoryCommand
Change models, modules, or resolvers, then regenerate typesapps/<app>/backendpnpm run generate
Deploy to your development workspace (also publishes the frontend)apps/<app>/backendpnpm run deploy
Refresh the frontend GraphQL schema after an API changeapps/<app>/frontendpnpm run generate
Typecheck and testbackend and frontendpnpm run typecheck, then the tests in Testing

Keep each run to one module or one story.

Where logic goes ​

You needPut it in
An operation a module already offers, such as creating a sales orderA resolver that calls the module command
Extra attributes on a module entity, such as a model code on an itemThe module's custom fields, declared when the module is defined
Logic that combines several operationsAn app resolver, with app-local functions where needed
Data that is yours alone, with its own lifecycle, such as a returns caseAn app-owned table plus resolvers
A reaction to a data changeA module event handler or an app executor

The kit relies on three rules:

  1. Write module data only through module commands. Never reimplement a state transition, a permission check, or an audit entry in the app.
  2. Use one transaction per resolver. Pass the same transaction to every command and app write that must succeed or fail together, and throw inside the transaction callback when a command returns ok: false.
  3. Map every error code. Map each error code a command can return to a DomainError with a message that is safe to show, and end the switch with default: throw result.error satisfies never, so that a new error code fails typecheck instead of slipping through.

The createRole resolver in the user-management template (backend/src/resolver/createRole.ts) shows this shape.

Composing modules ​

Modules are composed once in backend/src/modules.ts with each module's define...Module function, with dependencies passed explicitly. Their tables are re-exported from backend/src/modules-db.ts so the SDK discovers them. Resolvers import from @/modules, never from modules-db.ts.

Read a module's contract before you wire it. It is faster than letting the agent discover it from source:

bash
pnpm exec erp-kit doc module <module-name>
pnpm exec erp-kit doc module <module-name> command <command-name>

App-owned data ​

  • Use custom fields to add attributes to a module entity. They are declared on the module definition, and the module's commands still own the row.
  • Use an app-owned table when the records are independent, several per parent, or have their own lifecycle. It lives in backend/src/tailordb/<model>.ts, relates to module tables through the module object, is re-exported from modules-db.ts, and declares its own permission and gqlPermission.

Testing ​

LayerChecksLocation and command
Backend storyBusiness rules, public API, permissions, persisted result, rollbackbackend/src/tests/story/<flow>/<actor>--<story>.test.ts. Runs against the deployed development backend: pnpm test:integration in the backend
Frontend componentRendering, input, validation, actions, failure recovery<name>.test.tsx next to the component: pnpm test in the frontend
Browser E2EThe main path of a flow through the screensfrontend/e2e/tests/<flow>.spec.ts: pnpm test:e2e in the frontend
  • Query after a mutation to check what was persisted. Do not trust the mutation's response alone.
  • Create test data inside each test with a unique suffix. Use seeds only for master data and sign-in users.
  • Test permissions with a token that has the actor's permissions. A test that passes with an admin machine user proves nothing about ordinary users.

When story and resolver docs exist, erp-kit app generate code scaffolds the missing resolvers and story tests; you fill in the logic and assertions. Before calling a story done, check that docs, code, and tests still agree:

bash
pnpm exec erp-kit app check --path apps/<app>
pnpm exec erp-kit app sync-check --path apps/<app>

Security ​

The templates are set up for a first deploy by one developer. Before anyone else signs in, go through Security and sign-in.

Review checklist ​

Ask these at the end of every story:

  • Which ERP Kit modules are wired, and is each defined once in modules.ts?
  • Is any module table written outside a module command?
  • Does every resolver map the full error union and throw inside its transaction?
  • Is app-only data a custom field or an app-owned table, and is that the right choice?
  • Which story tests exist, and do they run against the deployed development backend?