Coding conventions
Most of these are enforced by CI (pnpm lint, pnpm typecheck, pnpm test),
not just documented — if you can get a PR past those three, you've already
satisfied most of this page.
TypeScript
tsconfig.base.json runs strict plus a few settings stricter than the
default:
noUncheckedIndexedAccess— indexing an array or record gives youT | undefined, notT. This catches a real class of bugs at the type level (a lookup that silently returnsundefinedtreated as present).noImplicitOverride— overriding a base method requires theoverridekeyword.verbatimModuleSyntax— type-only imports must say so (import type { Foo }), which ESLint's@typescript-eslint/consistent-type-importsalso enforces.
Money is numeric(19,4) in PostgreSQL and string in TypeScript — never
number. Floating-point and currency don't mix, and this is the one rule in
this document that's about correctness rather than style.
Architecture guards (ESLint)
Two rules exist specifically to keep the layering in Architecture real rather than aspirational:
- Files under
apps/api/src/**/routes/**andapps/api/src/**/service/**can't import Kysely or the connection helpers directly — a handler or service that needs new data access gets a repository method, not a raw query. (Sanctioned exceptions: the plugin host adapter and the DB connectivity healthcheck, which are infrastructure that hands out a connection or checks reachability, not business logic.) - Browser code (
apps/web, plugins,packages/*) can't callcrypto.randomUUIDdirectly — it only exists over HTTPS orlocalhost, and Wicker Money is meant to also run over plain HTTP on a LAN. UsenewUuid()instead, which falls back togetRandomValues.
Testing
- Unit tests (
pnpm test) — no database. Services are tested against in-memory fakes of their repositories. - Integration tests (
pnpm test:integration) — files named*.integration.test.ts, run against a real PostgreSQL with row-level security actually enabled. Anything touching tenant isolation belongs here, not behind a mock — a mock can't catch a policy that's missing or wrong.
Commits
Conventional Commits for commit
subjects — feat:, fix:, docs:, chore:, etc. Release notes are
generated from them, so an inaccurate prefix produces an inaccurate changelog
entry, not just a style nit.
Formatting
ESLint runs on save/CI (eslint.config.mjs, flat config); there's no separate
Prettier pass documented here yet — if your editor already formats on save
against the ESLint config, you're set.