Imported from gotch27/xfree (
AGENTS.md). Install upstream withnpx skills add gotch27/xfree. Copyright stays with the author.
NextSignal App Agent Guide
This is a Next.js app built with @gotch/nextsignal. Backend behavior is modeled as named processes with explicit lifecycle hooks, result envelopes, app-owned services, and infrastructure adapters.
Commands
- Install dependencies:
npm install - Start the Next.js dev server:
npm run dev - Type-check the app:
npm run check - Build for production:
npm run build - Start the production Next.js server after build:
npm run start - Start the background worker host:
npm run worker
There is no test script in this starter yet. If you add tests, add the script to package.json and document the command here.
Project Map
app/api/**/route.tscontains thin Next.js route handlers.nextsignal/app.tsis the composition root created withcreateNextSignalApp.nextsignal/config.tsis the only place that wires configuration providers.nextsignal/schemas.tscontains shared validation schemas.nextsignal/processes/api/contains route-facing API processes.nextsignal/processes/business/contains reusable business processes.nextsignal/processes/distributed/should be added when queue-backed work is needed.nextsignal/processes/recurring/should be added when scheduled work is needed.nextsignal/services/is the app-owned data and provider boundary.nextsignal/adapters/connects auth, logging, validation, queue, and scheduler infrastructure.worker/index.tsstarts the separate Node background host.config/default.jsoncontains committed default configuration..env.localcontains local development overrides..env.production.local.exampledocuments production-style overrides for local simulation.
Configuration Rules
Configuration is loaded through nextsignal/config.ts:
loadEnvConfig(process.cwd());
export const config = createConfig({
providers: [
jsonConfigProvider("config/default.json"),
envConfigProvider({ prefix: "NEXTSIGNAL_" })
]
});
When adding config:
- Add safe defaults to
config/default.json. - Add local development values to root
.env.local. - Add production-style examples to root
.env.production.local.example. - Use the
NEXTSIGNAL_prefix for environment values. - Use double underscores for nested config paths. Example:
NEXTSIGNAL_LOGGING__LEVEL=debugmaps tologging.level. - Read config in processes and services through
ctx.config.get("path", fallback)orctx.config.require("path"). - Keep
loadEnvConfig(process.cwd())innextsignal/config.tsso workers and scripts load the same root env files as Next.js. - Do not scatter
process.envreads through process files. Keep config provider wiring centralized innextsignal/config.ts. - Do not put
.envfiles insideconfig/; Next.js only loads env files from the project root. - Do not use
.env.prod; Next.js recognizes.env.productionand.env.production.local.
Process Rules
API, business, and distributed processes should explicitly define:
authvalidatehandle
If a hook has nothing to do, return ok().
export const createThing = businessProcess({
name: "things.createBusiness",
auth: () => ok(),
validate: () => ok(),
async handle(ctx, input) {
return value(await ctx.services.things.create(input));
}
});
Recurring processes are different because they are triggered by the scheduler. They define schedule, optional overlap, optional shouldRun, and handle.
Use stable process names in the form resource.action or resource.actionKind, such as users.register, users.createBusiness, or maintenance.pruneLogs.
Route Rules
Keep route handlers thin. A route should usually only import the app, set runtime, and export a handler from createNextRoute.
import { createNextRoute } from "@gotch/nextsignal/next";
import { app } from "@/nextsignal/app";
export const runtime = "nodejs";
export const POST = createNextRoute(app, "things.create");
Do not put business logic, validation logic, database calls, or auth provider calls in route files.
API Process Pattern
API processes are request-facing. They own request authorization, input validation, and orchestration.
export const createThingApi = apiProcess<CreateThingInput, CreateThingOutput, AppServices>({
name: "things.create",
auth: requireUser(),
validate: validateWith(createThingSchema),
async handle(ctx, input) {
const created = await ctx.mediator.dispatch<CreateThingInput, Thing>("things.createBusiness", input);
if (!created.ok) return forwardFault(created);
if (!created.data) return systemFail(new Error("Business process returned no data."));
return value({ thing: created.data });
}
});
Business Process Pattern
Business processes are reusable application operations. API, distributed, recurring, and test code can dispatch them through the mediator.
export const createThingBusiness = businessProcess<CreateThingInput, Thing, AppServices>({
name: "things.createBusiness",
auth: () => ok(),
validate: validateWith(createThingSchema),
async handle(ctx, input) {
return value(await ctx.services.things.create(input));
}
});
Extract handle to a named function when it grows past roughly 10 lines.
Services Rules
Services are the app-owned data and integration layer. Put database repositories, ORM clients, provider SDK calls, mailers, storage clients, and external API clients in nextsignal/services.
Processes should call services through ctx.services.
Do not import database clients, provider SDKs, queues, or mailers directly into process files unless the app has intentionally made that service the boundary.
Adapter Rules
Adapters own infrastructure behavior:
- auth provider lookup
- logger sinks and formatting
- schema validation integration
- queue enqueueing, retries, locks, polling, and dead letters
- scheduler timing, missed-run behavior, and distributed locking
Processes own lifecycle, result envelopes, mediator orchestration, and business intent.
Result Rules
Return result envelopes from processes:
value(data)for success with dataok()for success without datavalidationFail(...)for expected validation failuresauthFail(...)for unauthenticated callersforbidden(...)for unauthorized callersnotFound(...)for missing resourcesfail(...)for expected business failuressystemFail(error)for unexpected system failures
Avoid throwing for expected business, validation, auth, forbidden, or not-found outcomes. Throw only for unexpected system failures, or convert them to systemFail(error).
When forwarding a failed mediator result from one process to another, use:
if (!result.ok) return forwardFault(result);
This preserves the original fault metadata while matching the current process output type.
Validation Rules
The starter uses Zod in nextsignal/schemas.ts, but NextSignal only depends on the validation adapter contract.
Use validateWith(schema) in process definitions. Keep input schemas focused on process input: body fields, query values, route params, and command payloads.
Do not validate auth headers as process input. Auth adapters should read request headers through ctx.request.
Background Work Rules
Use distributed processes for queue-backed work and recurring processes for scheduled work.
Run background work in the separate worker runtime:
npm run worker
Do not run durable queue consumers or schedulers inside route handlers. The background host in worker/index.ts is the place to grow production worker behavior.
Import And Style Rules
- Use the
@/alias for app imports. - Keep TypeScript strict-friendly and avoid
anyunless the boundary truly requires it. - Keep process files focused on lifecycle and flow.
- Keep services boring and provider-oriented.
- Keep adapters provider-specific and infrastructure-oriented.
- After changing imports, process types, config, or adapters, run
npm run check. - After larger changes, run
npm run build.
Framework Docs
Use these docs for deeper framework details:
- Process lifecycle: https://github.com/gotch27/gotch-nextsignal/blob/main/docs/concepts/process-lifecycle.md
- Result envelopes: https://github.com/gotch27/gotch-nextsignal/blob/main/docs/concepts/result-envelopes.md
- Mediator chaining: https://github.com/gotch27/gotch-nextsignal/blob/main/docs/concepts/mediator-chaining.md
- Services: https://github.com/gotch27/gotch-nextsignal/blob/main/docs/concepts/services.md
- Configuration: https://github.com/gotch27/gotch-nextsignal/blob/main/docs/concepts/configuration.md
- Next.js routes: https://github.com/gotch27/gotch-nextsignal/blob/main/docs/runtime/next-routes.md
- Auth: https://github.com/gotch27/gotch-nextsignal/blob/main/docs/runtime/auth.md
- Validation: https://github.com/gotch27/gotch-nextsignal/blob/main/docs/runtime/validation.md
- Background workflows: https://github.com/gotch27/gotch-nextsignal/blob/main/docs/runtime/background-workflows.md
- Recurring processes: https://github.com/gotch27/gotch-nextsignal/blob/main/docs/runtime/recurring-processes.md
- Adapters: https://github.com/gotch27/gotch-nextsignal/blob/main/docs/infrastructure/adapters.md
- Conventions: https://github.com/gotch27/gotch-nextsignal/blob/main/docs/guides/conventions.md