Tooling system
Here’s one of the most useful patterns in Refract: every external dependency your app cares about — queues, loggers, metrics — is wired through a pluggable tooling layer. Swap the underlying vendor by changing one config value. The rest of your code doesn’t notice. The contract lives inapps/shared. The implementation lives in an isolated workspace package under apps/tools/*. A thin loader in the backend reads config and import()s the right one at runtime. Your app only ever sees a typed tools object.
How it works
Each tool exposes a discriminator in config (client, strategy, etc.). The schema is a validated union in apps/shared, so every branch is type-safe and Zod-checked before it reaches your app. The backend loader reads the discriminator and dynamically imports the right package — nothing is bundled that you’re not using.
All pluggable tools
Every tool follows the same three-layer pattern: a contract inapps/shared, one or more implementations (either workspace packages under apps/tools/ or loaders in apps/backend/src/tools/), and a loader that reads config and wires up the right one.
The
tools object — assembled at startup — is passed down to every resolver and utility function. No part of the app imports a vendor SDK directly.
Example: swapping the queue adapter
To switch from BullMQ to SQS:- Add
tooling-queue-sqstoapps/backend/package.json. - Run
make deps-install. - Update
tools.queueinapps/backend/src/configuration/production.ts: - Set the required env vars in your environment.
ConfigType.tools.queue field is a discriminated union — TypeScript will fail to compile if the config shape doesn’t match the selected adapter’s Zod schema.
Extending this system
Adding a new tool — or a new implementation of an existing one — is a four-step pattern that stays consistent across every tool in the repo.- Define or extend the contract in
apps/shared(types + Zod schema). - Add an implementation package under
apps/tools/<tool>/<name>/— pull in any vendor SDKs here, not onapps/backend. - Add the workspace package to
apps/backend/package.jsonand wire it into the loader. - Run
make deps-install, thenmake test module=backendandmake test module=<tooling-package>.
What not to do
❌ Never duplicate vendor SDKs onapps/backendfor a tool that’s already encapsulated in atooling-*package. Those packages exist to keep the backend clean.
❌ Never paste production credentials intodevelopment.tsor.env.development. If it ever touches version control, rotate it.
File conventions
What’s next?
- Configuration system — how
ConfigTypeand per-environment modules wire into the tool layer. - BullMQ — default queue adapter; SQS — full SQS adoption guide.
- Pino — structured logger; Console — minimal dev logger.
- Stripe — payment processor setup and webhook handling.
- Twilio SendGrid — production mailer; Local mailer — dev mailer.
- StatsD — metrics adapter.
- AI tooling — multi-provider LLM access via
tools.ai. - Redis cache — built-in cache helpers.
- Architecture overview — the full system map.
