@tulipes/core 0.1.10 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,299 +1,328 @@
1
- # @tulipes/core
1
+ ```
2
+ _/\_/\_
3
+ ( \ || / ) _ _ _ _
4
+ \ || / | |_ _ _| (_)_ __ ___ ___ | |___
5
+ \_||_/ | __| | | | | | '_ \ / _ \/ __|_ | / __|
6
+ || | |_| |_| | | | |_) | __/\__ \ |_| \__ \
7
+ \ || / \__|\__,_|_|_| .__/ \___||___/\___/|___/
8
+ \||/ |_|
9
+ ||
10
+ _ _ ____ __
11
+ | |_/ |__ / __ ___ _ _ / _|
12
+ | / / ||_ \_/ _/ _ \ ' \| _|
13
+ |_\_\_|___(_)__\___/_||_|_|
14
+ ```
2
15
 
3
- **A module-based Express framework: convention-driven boot pipeline, declarative env contracts, ACL, MongoDB, queues, sockets, and a codegen CLI — fail-closed by design.**
16
+ <div align="center">
4
17
 
5
- Tulipes exists so you never rewrite Express boilerplate again. You write **modules** — self-contained feature folders with routes, models, queues, sockets, and a declared environment contract — and the framework discovers, validates, orders, and wires them into a running API. Everything that can be checked at boot *is* checked at boot, and every failure is reported **in aggregate**: ten missing env variables means one crash listing ten problems, not ten restarts.
18
+ **The Express framework that refuses to start when something is wrong.**
6
19
 
7
- ```ts
8
- // app.ts — the whole entrypoint
9
- import { boot } from "@tulipes/core/boot";
20
+ [![npm](https://img.shields.io/npm/v/@tulipes/core?color=%23cb3837&label=%40tulipes%2Fcore)](https://www.npmjs.com/package/@tulipes/core)
21
+ ![node](https://img.shields.io/badge/node-%E2%89%A5%2020.11-5FA04E)
22
+ ![types](https://img.shields.io/badge/types-included-3178C6)
23
+ ![express](https://img.shields.io/badge/express-5-000000)
24
+
25
+ </div>
26
+
27
+ ---
28
+
29
+ You write **modules** — one folder per feature, holding its own routes, models,
30
+ config, permissions and background jobs. Tulipes discovers them, orders them,
31
+ validates them, and wires them into a running API.
32
+
33
+ Everything checkable is checked at boot, and every failure is reported **in
34
+ aggregate**. Ten missing environment variables is one crash listing ten
35
+ problems, not ten restarts.
10
36
 
11
- await boot({ rootDir: import.meta.dirname, mode: "backend" });
12
37
  ```
38
+ TulipesBootError: Boot aborted during "environment" — 3 issue(s):
39
+ ✖ [core] required variable "MONGO_URI" has no value
40
+ ✖ [users] variable "USERS_MAX_SESSIONS": "abc" is not a number
41
+ ✖ [users] required variable "USERS_ADMIN_EMAIL" has no value
42
+ ```
43
+
44
+ ## Quick start
45
+
46
+ ```sh
47
+ npm install -g @tulipes/core
48
+ tulipes init my-api && cd my-api
49
+
50
+ corepack enable # once per machine — activates the pinned yarn 4
51
+ yarn install
52
+ yarn dev # → http://localhost:3000/api/v1/hello
53
+ ```
54
+
55
+ You get a running API with three modules, a queue worker, websockets, PM2
56
+ process definitions, nginx templates and agent instructions. It expects
57
+ MongoDB and Redis; both are declared in the generated environment contract.
13
58
 
14
59
  ```
15
60
  ▲ my-api development · backend mode
16
61
 
17
- ➜ Server http://localhost:4000 ● listening
62
+ ➜ Server http://localhost:3000 ● listening
18
63
  ➜ Database mongodb://127.0.0.1:27017/my-api ● connected
19
- ➜ Queues 1 (users.welcome) · 1 processor(s) registered
20
- ➜ Sockets /users
21
- ➜ Modules 4 (core*, security*, auth, users) · * sys tier
64
+ ➜ Queues 1 (hello.count-greeting) · 1 processor(s) registered
65
+ ➜ Sockets /hello
66
+ ➜ Modules 3 (core*, security*, hello) · * sys tier
22
67
  ➜ Roles 2 (admin, user)
23
- ➜ Runtime Node v24.14.0 · PID 43054 · ready in 76ms
68
+ ➜ Runtime Node v24.14.0 · PID 43054 · ready in 61ms
24
69
  ```
25
70
 
26
- ---
27
-
28
- ## Why Tulipes
71
+ ## Why
29
72
 
30
- - **Modules, not layers.** A feature lives in one folder: its routes, models, config, permissions, background jobs, and env contract. Delete the folder, the feature is gone.
31
- - **Fail-closed everywhere.** Missing env vars, dependency cycles, duplicate ACL grants, colliding queue names all crash the boot with a full report. Reading an undeclared variable or model throws at the call site. Unknown roles deny. Unrouted requests 404. Unexpected errors never leak internals outside development.
32
- - **One codebase, three modes.** The same app boots as a `backend` server, a queue-consuming `worker`, or a one-shot `script` — same modules, same config, forked at the last pipeline phase. A maintenance script therefore runs against exactly the configuration the server has, never a hand-rolled approximation of it.
33
- - **Types generated, not hand-written.** `tulipes sync` reads your modules and emits a `config.d.ts` that types every env variable and every config namespace — enum variables become literal unions, config shapes are inferred from your factories' return types.
34
- - **The framework stays out of your request path.** Core mounts *no* middleware. Your sys-tier modules own the pipeline head; core only guarantees the tail (404 + terminal error handler).
35
-
36
- ## Requirements
73
+ **Modules, not layers.** A feature lives in one folder. Delete the folder and
74
+ the feature is goneno dangling registration left in six other files.
37
75
 
38
- - Node.js 20.11 (ESM, `import.meta.dirname`)
39
- - Yarn workspaces (modules are workspace packages)
40
- - MongoDB and Redis, when your modules use models/queues
76
+ **Fail closed, everywhere.** Missing variables, dependency cycles, duplicate
77
+ ACL grants, colliding queue names: all crash the boot with a full report.
78
+ Reading an undeclared variable or an unregistered model throws at the call
79
+ site. Unknown roles deny. Unrouted requests 404. Unexpected errors never leak
80
+ internals outside development.
41
81
 
42
- ## Installation
43
-
44
- ```sh
45
- yarn add @tulipes/core
46
- ```
82
+ **One codebase, three processes.** The same app boots as a `backend` server, a
83
+ queue-consuming `worker`, or a one-shot `script` — same modules, same config,
84
+ forked at the last pipeline phase. A maintenance script therefore runs against
85
+ exactly the configuration the server has, never an approximation of it.
47
86
 
48
- ## Application layout
87
+ **Types generated, not hand-written.** `tulipes sync` reads your modules and
88
+ emits a `config.d.ts`: enum variables become literal unions, config namespaces
89
+ are inferred from your factories' return types.
49
90
 
50
- ```
51
- my-api/
52
- ├── app.ts # boot({ mode: "backend" })
53
- ├── worker.ts # boot({ mode: "worker" })
54
- ├── config/
55
- │ └── app.config.ts # optional: seeds global config before any module
56
- ├── lib/ # your cross-cutting glue (cache, mongoose plugins…)
57
- ├── modules/ # ← the application
58
- │ ├── core/ # sys tier: infra env vars, roles, global middleware
59
- │ ├── security/ # sys tier: headers, logging, body parsing
60
- │ └── users/ # app tier: a feature
61
- ├── types/config.d.ts # GENERATED — tulipes sync
62
- ├── .env.example # GENERATED — tulipes sync
63
- ├── .envs/
64
- │ └── .env.development # per-mode env files, picked by APP_ENV
65
- └── package.json # "workspaces": ["modules/*"]
66
- ```
91
+ **The framework stays out of your request path.** Core mounts *no* middleware.
92
+ Your sys-tier modules own the head of the pipeline; core guarantees only the
93
+ fail-closed tail a 404 for anything unrouted, and a terminal error handler.
67
94
 
68
- ## Module anatomy
95
+ ## A module
69
96
 
70
- Every module is a yarn workspace package under `modules/`. Every file below is optional except `package.json`.
97
+ Every module is a workspace package under `modules/`. Every file is optional
98
+ except `package.json` — `tulipes new module <name>` generates them all, so you
99
+ delete what this feature does not need.
71
100
 
72
101
  ```
73
- modules/users/
74
- ├── package.json # name + "tulipes" ordering key
75
- ├── meta.variables.json # the module's env contract
76
- ├── module.config.ts # config factory + onReady/onShutdown hooks
77
- ├── module.acl.ts # permission grants
78
- ├── routes/*.routes.ts # (ctx) => Router
79
- ├── models/*.model.ts # { name, schema }
80
- ├── bootstrap/*.bootstrap.ts# post-DB, pre-server tasks (indexes, seeds)
81
- ├── queues/*.queues.ts # (ctx, queues) => define + process
82
- ├── sockets/*.sockets.ts # (ctx, sockets) => claim namespaces
83
- └── controllers/ # convention only routes import them directly
102
+ modules/billing/
103
+ ├── package.json name + "tulipes" ordering key
104
+ ├── meta.variables.json this module's environment contract
105
+ ├── module.config.ts config factory + onReady / onShutdown
106
+ ├── module.acl.ts permission grants
107
+ ├── routes/*.routes.ts (ctx) => Router
108
+ ├── controllers/ the work; routes import it directly
109
+ ├── helpers/ pure logic, no framework
110
+ ├── models/*.model.ts { name, schema } a declaration, not a registration
111
+ ├── bootstrap/*.bootstrap.ts post-database, pre-traffic: indexes, seeds
112
+ ├── queues/*.queues.ts (ctx, queues) => define + process
113
+ └── sockets/*.sockets.ts (ctx, sockets) => claim a namespace
84
114
  ```
85
115
 
86
116
  ```json
87
117
  {
88
- "name": "@app/users",
89
- "tulipes": { "tier": "app", "priority": 10, "dependsOn": ["auth"] },
90
- "dependencies": { "@app/auth": "workspace:*" }
118
+ "name": "@app/billing",
119
+ "tulipes": { "tier": "app", "priority": 100, "dependsOn": ["users"] },
120
+ "dependencies": { "@app/users": "workspace:*" }
91
121
  }
92
122
  ```
93
123
 
94
- **Load order** is three sort keys: tier (`sys` before `app`) → `priority` (lower first, default 100) topological sort on `dependsOn`. Cycles, unknown dependencies, and a sys module depending on an app module all crash the boot. Cross-module imports go through package names, so the package manager enforces that a module only uses what it declares.
124
+ **Load order** is three keys: tier (`sys` before `app`) → `priority` → a
125
+ topological sort on `dependsOn`. Cycles, unknown dependencies, and a sys module
126
+ depending on an app module all crash the boot. Cross-module imports go through
127
+ package names, so the package manager enforces that a module only uses what it
128
+ declares.
95
129
 
96
130
  ## The environment contract
97
131
 
98
- Each module declares its variables in `meta.variables.json` plain JSON so tooling can read it without executing code:
132
+ Each module declares the variables it needs. Plain JSON, so tooling can read a
133
+ module's contract without executing its code.
99
134
 
100
135
  ```json
101
136
  {
102
137
  "variables": [
103
138
  {
104
- "name": "USERS_SIGNUP_MODE",
105
- "type": "enum",
106
- "enum": ["open", "invite", "closed"],
107
- "group": "signup",
108
- "description": "Who can register",
109
- "default": "open"
139
+ "name": "STRIPE_API_KEY",
140
+ "type": "secret",
141
+ "group": "billing",
142
+ "required": true,
143
+ "description": "Server-side Stripe key"
110
144
  },
111
145
  {
112
- "name": "USERS_ADMIN_EMAIL",
113
- "type": "string",
114
- "required": true,
115
- "description": "Seeded admin account email"
146
+ "name": "BILLING_TRIAL_DAYS",
147
+ "type": "number",
148
+ "group": "billing",
149
+ "description": "Free trial length",
150
+ "default": 14
116
151
  }
117
152
  ]
118
153
  }
119
154
  ```
120
155
 
121
- | Field | Rules |
122
- |---|---|
123
- | `name` | UPPER_SNAKE. **One owner per variable across the app** — two modules declaring the same name is a boot crash naming both. Shared infra vars (`MONGO_URI`, `REDIS_URL`, `PORT`) belong to your sys core module. |
124
- | `type` | `string` · `number` · `boolean` · `enum` · `url` · `secret` (redacted in every log, banner, and report) |
125
- | `enum` | required with `type: "enum"` |
126
- | `required` | no resolved value → boot crash; mutually exclusive with `default` |
127
- | `default` | used when nothing else provides a value |
128
- | `group` | free-form label; drives section grouping in the generated `.env.example` |
156
+ ```ts
157
+ ctx.Environment.get("BILLING_TRIAL_DAYS"); // number, after `tulipes sync`
158
+ ctx.Environment.get("TYPO_NAME"); // throws never declared
159
+ ```
129
160
 
130
- Resolution precedence: **`process.env` › `.envs/.env.<APP_ENV>` › `default`** — container-injected values always win. The mode comes from `APP_ENV` (`development` / `staging` / `production` / `test`), deliberately not `NODE_ENV` (core mirrors a sane `NODE_ENV` for third-party libraries).
161
+ Resolution is **`process.env` › `.envs/.env.<APP_ENV>` › `default`**, so
162
+ container-injected values always win. Values are coerced and validated against
163
+ their declared type, and `secret` values are redacted in logs, reports and the
164
+ startup banner.
131
165
 
132
- Values are validated and coerced (`"5"` → `5`, `"true"` → `true`, enum membership, URL shape). Reads are typed after codegen and fail-closed always:
166
+ | Field | Rules |
167
+ |---|---|
168
+ | `name` | UPPER_SNAKE, **one owner per variable** — two modules declaring the same name is a boot crash naming both |
169
+ | `type` | `string` · `number` · `boolean` · `enum` · `url` · `secret` |
170
+ | `enum` | required with `type: "enum"`; generates a literal union |
171
+ | `required` | no value anywhere → boot crash; mutually exclusive with `default` |
172
+ | `default` | used when nothing supplies a value |
173
+ | `group` | free-form label; groups the variable in the generated `.env.example` |
133
174
 
134
- ```ts
135
- ctx.Environment.get("USERS_SIGNUP_MODE"); // "open" | "invite" | "closed"
136
- ctx.Environment.get("TYPO_NAME"); // throws: never declared by any module
137
- ```
175
+ The mode comes from `APP_ENV`, deliberately **not** `NODE_ENV` — bundlers and
176
+ libraries overload that one as an optimisation flag.
138
177
 
139
178
  ## The boot pipeline
140
179
 
141
180
  ```
142
- 1. Environment load .env.<APP_ENV>, every meta.variables.json → validate (aggregate crash)
143
- 2. Module graph read "tulipes" keys → tier/priority/topo sort (cycles crash)
144
- 3. Exploration resolve conventional file paths, load order, no imports yet
145
- 4. Global config config/app.config.ts seed → each module.config.ts factory,
146
- in load order (each factory sees its dependencies' config)
147
- 5. ACL merge module.acl.ts in load order (conflicts crash)
148
- 6. Database connect Mongoose model store → bootstrap tasks
149
- 7. Queues every *.queues.ts registers in BOTH modes
150
- 8. ── mode fork ──
151
- backend module routers (load order) 404 → error handler → listen
152
- worker start a BullMQ worker per registered processor
153
- script start nothing; the caller owns the process and calls
154
- handle.shutdown() when done (also skips bootstrap
155
- tasks, the banner and onReady)
156
- 9. onReady hooks in load order → startup banner
157
- shutdown SIGTERM/SIGINT → onShutdown hooks in REVERSE order →
158
- disconnect sockets → close http → drain queues → close redis → close mongo
181
+ 1 Environment .env.<APP_ENV> + every meta.variables.json → validate
182
+ 2 Module graph "tulipes" keys → tier / priority / topological sort
183
+ 3 Exploration resolve conventional paths in load order, import nothing
184
+ 4 Global config config/app.config.ts seed → each module.config.ts factory
185
+ 5 ACL module.acl.ts in load order
186
+ 6 Database connect model store bootstrap tasks
187
+ 7 Queues every *.queues.ts registers, in all modes
188
+ 8 ── fork ──
189
+ backend module routers 404 error handler → listen
190
+ worker one BullMQ worker per registered processor
191
+ script start nothing; the caller owns the process
192
+ 9 onReady hooks in load order startup banner
193
+ shutdown SIGTERM onShutdown in REVERSE order → sockets →
194
+ http queues redis → mongo
159
195
  ```
160
196
 
161
- Each phase crashes with its own aggregate report before the next begins. In dev the pipeline discovers `.ts` files (run under tsx); compiled deployments discover `.js` — detected automatically.
162
-
163
- ## Contracts
197
+ Each phase crashes with its own aggregate report before the next begins.
164
198
 
165
- Everything a module exports receives the shared context:
199
+ <details>
200
+ <summary><b>Contracts — what each file exports</b></summary>
166
201
 
167
202
  ```ts
168
203
  interface Ctx {
169
- Environment: Environment; // typed variable store
170
- config: GlobalConfig; // namespaced by module: config.users
171
- acl?: Acl; // after phase 5
172
- models?: ModelStore; // after phase 6
173
- queues?: QueueManager; // after phase 7
174
- sockets?: SocketManager; // backend only
175
- app?: Express; // backend only
204
+ mode: ProcessMode; // "backend" | "worker" | "script"
205
+ Environment: Environment; // typed variable store
206
+ config: GlobalConfig; // namespaced by module: config.billing
207
+ acl?: Acl; // after phase 5
208
+ models?: ModelStore; // after phase 6
209
+ queues?: QueueManager; // after phase 7
210
+ sockets?: SocketManager; // backend only
211
+ app?: Express; // backend only
176
212
  }
177
- ```
178
213
 
179
- ```ts
180
- // module.config.ts namespace stored at config.<module>; hooks optional
181
- export default function usersConfig({ Environment }: Ctx) {
182
- return { maxSessions: Environment.get("USERS_MAX_SESSIONS") };
214
+ // module.config.ts — the namespace stored at config.<module>
215
+ export default function billingConfig({ Environment }: Ctx) {
216
+ return { trialDays: Environment.get("BILLING_TRIAL_DAYS") };
183
217
  }
184
218
  export async function onReady(ctx: Ctx) {}
185
- export async function onShutdown(ctx: Ctx) {} // runs in reverse load order
219
+ export async function onShutdown(ctx: Ctx) {} // reverse load order
186
220
 
187
- // routes/users.routes.ts — return a Router, or mount on ctx.app directly
188
- export default function usersRoutes({ config, models }: Ctx): Router {
221
+ // routes/*.routes.ts — return a Router, or mount on ctx.app directly
222
+ export default function billingRoutes({ config, models }: Ctx): Router {
189
223
  const router = Router();
190
- router.get("/users", async (_req, res) => {
191
- res.json(await models!.get("User").find().lean());
224
+ router.get(`${config.api!.prefix}/invoices`, async (_req, res) => {
225
+ res.json(await models!.get("Invoice").find().lean());
192
226
  });
193
227
  return router;
194
228
  }
195
229
 
196
- // models/user.model.ts — pure definition, no factory
197
- export default { name: "User", schema: userSchema } satisfies ModelDef;
230
+ // models/*.model.ts — a declaration; never call mongoose.model() yourself
231
+ export default { name: "Invoice", schema } satisfies ModelDef;
198
232
 
199
- // bootstrap/seed-admin.bootstrap.ts — post-DB, pre-server; make it idempotent
200
- export default async function seedAdmin({ models, Environment }: Ctx) {}
233
+ // bootstrap/*.bootstrap.ts — runs in every process, so make it idempotent
234
+ export default async function seed({ models }: Ctx) {}
201
235
 
202
- // queues/users.queues.ts — ONE file for both process modes
203
- export default function usersQueues(_ctx: Ctx, queues: QueueRegistry) {
204
- queues.define("users.welcome"); // producers, every mode
205
- queues.process("users.welcome", async (job) => {}); // consumed in worker mode
236
+ // queues/*.queues.ts — ONE file, both sides; the process mode decides
237
+ export default function billingQueues(ctx: Ctx, queues: QueueRegistry) {
238
+ queues.define("billing.send-invoice"); // producers, always
239
+ queues.process("billing.send-invoice", async (job) => {}); // consumed in worker
206
240
  }
207
241
 
208
- // sockets/users.sockets.ts — claim a namespace exactly once
209
- export default function usersSockets(_ctx: Ctx, sockets: SocketRegistry) {
210
- sockets.namespace("/users", (nsp) => {
211
- nsp.on("connection", (socket) => socket.emit("welcome"));
212
- });
242
+ // sockets/*.sockets.ts — a namespace is claimed by exactly one module
243
+ export default function billingSockets(ctx: Ctx, sockets: SocketRegistry) {
244
+ sockets.namespace("/billing", (nsp) => nsp.on("connection", () => {}));
213
245
  }
214
246
 
215
- // module.acl.ts — roles are global (define in your sys module), grants are local
216
- export default function usersAcl(acl: AclBuilder) {
217
- acl.allow("user", "users:read"); // resources namespaced by module
247
+ // module.acl.ts — roles are global, grants are local and namespaced
248
+ export default function billingAcl(acl: AclBuilder) {
249
+ acl.allow("user", "invoices:read");
218
250
  }
219
251
  ```
220
252
 
221
- Producing a job from anywhere: `await ctx.queues.add("users.welcome", "welcome", { email })`.
222
- Emitting from anywhere: `ctx.sockets.of("/users").emit("event", data)` unclaimed namespaces throw.
223
-
224
- ## Global middleware
225
-
226
- Core mounts nothing. Sys modules load before every app module, so a sys module's routes file mounting on `ctx.app` **is** the "before all routes" slot:
227
-
228
- ```ts
229
- // modules/security/routes/security.routes.ts (tier: "sys")
230
- export default function securityRoutes({ app }: Ctx): void {
231
- app!.use(securityHeaders());
232
- app!.use(pinoHttp({ /* … */ }));
233
- app!.use(express.json({ limit: "1mb" }));
234
- }
235
- ```
236
-
237
- Core guarantees only the fail-closed tail: a 404 for anything unrouted, and a terminal error handler that catches thrown/rejected async handlers. Modules signal errors with one class:
253
+ Errors are one class. Express 5 forwards rejected async handlers to the
254
+ framework's terminal handler, so `throw` is the whole story:
238
255
 
239
256
  ```ts
240
257
  import { HttpError } from "@tulipes/core/http";
241
258
 
242
- router.get("/users/:id", async (req) => {
243
- const user = await findUser(req.params.id);
244
- if (!user) throw new HttpError(404, "no such user", { id: req.params.id });
245
- });
259
+ if (!invoice) throw new HttpError(404, "no such invoice", { id });
246
260
  ```
247
261
 
248
- `HttpError` renders `{ error, details? }` with its status. Errors raised by express middleware that carry a 4xx `status` are honored too — an oversized body stays a `413`, malformed JSON a `400` — since those describe the caller's request, not your internals. Everything else becomes an anonymous `500 internal server error`, with the real message only in `development`/`test`.
262
+ `HttpError` renders `{ error, details? }` with its status. Middleware errors
263
+ carrying a 4xx are honoured too — an oversized body stays a `413`, malformed
264
+ JSON a `400`. Everything else becomes an anonymous `500`, with the real message
265
+ only in development.
249
266
 
250
- ## The CLI
267
+ </details>
251
268
 
252
- ```
253
- tulipes init [dir] scaffold a new application (needs mongo + redis)
254
- tulipes sync regenerate types/config.d.ts and .env.example
255
- tulipes env:check validate env against every meta contract — CI gate, exit 1 on failure
256
- tulipes new module <name> scaffold a module (scope auto-detected from siblings)
257
- tulipes update upgrade the global cli and, inside an app, the app itself
258
- tulipes -v print the cli version (and the app's, when they differ)
259
- tulipes dev [entry] sync, then run the entry under tsx watch
260
- ```
261
-
262
- The scaffolded `modules/hello` is a worked example of every module
263
- contract — config factory with lifecycle hooks, ACL grants, routes with
264
- `HttpError` and permission checks, a controller, a pure helper, a model,
265
- an idempotent bootstrap task, a queue with its processor, and a socket
266
- namespace. The generated app expects mongo and redis to be running.
269
+ ## The CLI
267
270
 
268
- ### Built for AI-assisted work
271
+ | Command | What it does |
272
+ |---|---|
273
+ | `tulipes init [dir]` | scaffold a new application |
274
+ | `tulipes dev [entry]` | regenerate types, then run under tsx watch |
275
+ | `tulipes sync` | regenerate `types/config.d.ts` and `.env.example` |
276
+ | `tulipes env:check` | validate the environment without booting — a CI gate |
277
+ | `tulipes new module <name>` | scaffold a module with every contract |
278
+ | `tulipes update` | upgrade the global CLI and, inside an app, the app itself |
279
+ | `tulipes -v` | print the CLI version, and the app's when they differ |
280
+
281
+ `update` handles two separate installs: the global `tulipes` on your PATH, and
282
+ the `@tulipes/core` an app depends on — including every module, since modules
283
+ declare it themselves. It reinstalls the CLI into the prefix it already lives
284
+ in, which is what makes it work under nvm, where a plain `npm install -g` may
285
+ target a different Node version than the one holding your binary.
286
+
287
+ ## What `tulipes init` gives you
288
+
289
+ The framework is the engine; the scaffold is a complete application you own and
290
+ edit:
291
+
292
+ - **`modules/core`** (sys, priority 0) — infrastructure variables, the global
293
+ role vocabulary, request ids, and shared helpers exported as `@app/core`: a
294
+ cache (`Cache` contract, Redis and LRU-capped memory drivers), Mongoose base
295
+ plugins (snake_case timestamps, soft delete), and a **logger** writing a
296
+ coloured human-readable console *and* rotating JSON-line files, plus an
297
+ errors-only copy. Log files carry the process mode, because backend and
298
+ worker run at once and two processes rotating one file will race.
299
+ - **`modules/security`** (sys, priority 10) — helmet tuned for an API, CORS
300
+ that is fail-closed by default, pino-http request logging, and a body-size
301
+ cap. Core mounts no middleware; this module *is* the head of the pipeline.
302
+ - **`modules/hello`** — a worked example of every contract.
303
+ - **`scripts/`** — one-off tasks that boot in `script` mode, plus an nginx
304
+ deploy script that renders a per-environment template, links `sites-enabled`,
305
+ runs `nginx -t` and reloads.
306
+ - **`ecosystem.config.cjs`** — PM2 definitions for both processes across
307
+ development, staging and production, with `kill_timeout` long enough for
308
+ graceful shutdown to drain queues and close connections.
309
+
310
+ ## Built for AI-assisted work
269
311
 
270
312
  `tulipes init` also writes a `CLAUDE.md` and eight task-scoped skills under
271
- `.claude/skills/` — module, env variable, endpoint, model, queue, socket,
272
- permissions, and reading boot errors. They encode the conventions an agent
273
- would otherwise guess wrong: declaring variables instead of reaching for
274
- `process.env`, where global middleware belongs, why `mongoose.model()` is
275
- never called directly, and how to read an aggregate boot report.
313
+ `.claude/skills/` — module, environment variable, endpoint, model, queue,
314
+ socket, permissions, and reading boot errors. They encode the conventions an
315
+ agent would otherwise guess wrong: declaring variables instead of reaching for
316
+ `process.env`, why `mongoose.model()` is never called directly, where global
317
+ middleware belongs, and how to read an aggregate boot report.
276
318
 
277
- The global CLI on your PATH and the `@tulipes/core` an app installs are
278
- **separate installs** that drift apart. `tulipes update` handles both: it
279
- reinstalls the global CLI into the prefix it already lives in (which is what
280
- makes it work under nvm, where a plain `npm install -g` may target a
281
- different node version than the one holding your `tulipes` binary), and,
282
- when run inside an app, rewrites the range in the app root and every module
283
- before installing. Outside an app it still updates the CLI. `tulipes -v`
284
- prints both numbers whenever they differ.
285
-
286
- `sync` output — committed, marked generated:
287
-
288
- - **`types/config.d.ts`** — augments `KnownVariables` (every variable typed from its spec) and `GlobalConfig` (each namespace typed as its factory's return type via `typeof import(...)` — inference, never execution).
289
- - **`.env.example`** — every variable with description, type, `REQUIRED` markers, grouped by module and `group`.
290
-
291
- ## Subpath exports
319
+ <details>
320
+ <summary><b>Subpath exports</b></summary>
292
321
 
293
322
  | Import | Contents |
294
323
  |---|---|
295
324
  | `@tulipes/core` | everything below |
296
- | `@tulipes/core/boot` | `boot`, `BootOptions`, `BootHandle`, `Ctx`, contract types |
325
+ | `@tulipes/core/boot` | `boot`, `Ctx`, `BootHandle`, contract types |
297
326
  | `@tulipes/core/env` | `Environment`, `KnownVariables` (codegen target), spec types |
298
327
  | `@tulipes/core/http` | `HttpError`, `notFoundHandler`, `errorHandler` |
299
328
  | `@tulipes/core/acl` | `Acl`, `AclBuilder`, `AclFn` |
@@ -304,10 +333,19 @@ prints both numbers whenever they differ.
304
333
  | `@tulipes/core/config` | `GlobalConfig` (codegen target), config builder |
305
334
  | `@tulipes/core/errors` | `TulipesBootError`, `BootReport` |
306
335
 
307
- ## The committed stack
336
+ </details>
308
337
 
309
- Express 5 · Mongoose 8 · BullMQ 5 · ioredis 5 · Socket.IO 4 · Zod. Deliberately opinionated in v1 — a framework that wires everything can only do so by choosing.
338
+ ## Requirements
310
339
 
311
- ## Well-known variables
340
+ Node.js 20.11 · Yarn workspaces · MongoDB and Redis when your modules use
341
+ models or queues.
342
+
343
+ The stack is deliberately opinionated in v1: **Express 5 · Mongoose 8 ·
344
+ BullMQ 5 · ioredis 5 · Socket.IO 4 · Zod**. A framework that wires everything
345
+ together can only do so by choosing.
346
+
347
+ ---
312
348
 
313
- Core reads three variable names when present (declare them in your sys core module): `PORT` (backend listen port, default 3000), `MONGO_URI` (required if any module ships models), `REDIS_URL` (required if any module ships queues). No models and no `MONGO_URI` is a valid database-less app; models without `MONGO_URI` is a boot refusal.
349
+ <div align="center">
350
+ <sub>Built by <b>k13.conf</b> · <a href="https://www.npmjs.com/package/@tulipes/core">npm</a></sub>
351
+ </div>
package/dist/boot/boot.js CHANGED
@@ -46,7 +46,7 @@ export async function boot(options) {
46
46
  // Phase 5 — exploration (no failure modes)
47
47
  const explored = exploreModules(order);
48
48
  // Phase 6 — global config: app-level seed first, then module factories
49
- const ctx = { Environment: env, config: {} };
49
+ const ctx = { mode, Environment: env, config: {} };
50
50
  const configReport = new BootReport();
51
51
  await loadAppConfigSeed(options.rootDir, ctx, configReport);
52
52
  const lifecycles = await buildGlobalConfig(explored, ctx, configReport);
@@ -1 +1 @@
1
- {"version":3,"file":"boot.js","sourceRoot":"","sources":["../../src/boot/boot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAe,MAAM,WAAW,CAAC;AAEtD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,OAAO,EAAE,EAAgB,MAAM,SAAS,CAAC;AAEhD,OAAO,EAAE,WAAW,EAAe,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAoB,MAAM,wBAAwB,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAU,CAAC;AAuBrE;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,OAAoB;IAC7C,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACjC,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvC,kEAAkE;IAClE,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC;QAC3B,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;KAClD,CAAC,CAAC;IAEH,yBAAyB;IACzB,MAAM,WAAW,GAAG,IAAI,UAAU,EAAE,CAAC;IACrC,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE,WAAW,CAAC,CAAC;IACjF,MAAM,KAAK,GAAG,WAAW,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IAClD,WAAW,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;IAEvC,2CAA2C;IAC3C,MAAM,QAAQ,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IAEvC,uEAAuE;IACvE,MAAM,GAAG,GAAQ,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAClD,MAAM,YAAY,GAAG,IAAI,UAAU,EAAE,CAAC;IACtC,MAAM,iBAAiB,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,YAAY,CAAC,CAAC;IAC5D,MAAM,UAAU,GAAG,MAAM,iBAAiB,CAAC,QAAQ,EAAE,GAAG,EAAE,YAAY,CAAC,CAAC;IACxE,YAAY,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAElC,sBAAsB;IACtB,MAAM,SAAS,GAAG,IAAI,UAAU,EAAE,CAAC;IACnC,GAAG,CAAC,GAAG,GAAG,MAAM,OAAO,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAC7C,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IAE5B,oCAAoC;IACpC,MAAM,QAAQ,GAAG,IAAI,UAAU,EAAE,CAAC;IAClC,MAAM,UAAU,GAAG,MAAM,YAAY,CAAC,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE;QAC7D,YAAY,EAAE,IAAI,KAAK,QAAQ;KAChC,CAAC,CAAC;IACH,QAAQ,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;IAEhC,sBAAsB;IACtB,MAAM,MAAM,GAAe;QACzB,IAAI;QACJ,GAAG;QACH,GAAG;QACH,UAAU;QACV,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;KAC7C,CAAC;IAEF,qEAAqE;IACrE,sDAAsD;IACtD,MAAM,WAAW,GAAG,IAAI,UAAU,EAAE,CAAC;IACrC,MAAM,UAAU,CAAC,QAAQ,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC;IAC7C,WAAW,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAEjC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACvC,CAAC;SAAM,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,MAAM,YAAY,GAAG,IAAI,UAAU,EAAE,CAAC;QACtC,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QAC5D,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;QACnC,oEAAoE;QACpE,iEAAiE;QACjE,IAAI,OAAO,KAAK,CAAC,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CACb,4EAA4E,CAC7E,CAAC;QACJ,CAAC;IACH,CAAC;IACD,uEAAuE;IACvE,6CAA6C;IAE7C,0EAA0E;IAC1E,2DAA2D;IAC3D,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,KAAK,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,UAAU,EAAE,CAAC;YAC7C,IAAI,CAAC;gBACH,MAAM,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;YACvB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,qEAAqE;gBACrE,yDAAyD;gBACzD,MAAM,MAAM,CAAC,QAAQ,EAAE,CAAC;gBACxB,MAAM,IAAI,KAAK,CAAC,oBAAoB,MAAM,aAAc,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;YACrF,CAAC;QACH,CAAC;QAED,kBAAkB,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,aAAa,CAAC,CAAC;IAChE,CAAC;IAED,IAAI,OAAO,CAAC,aAAa,KAAK,KAAK,EAAE,CAAC;QACpC,KAAK,MAAM,MAAM,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAU,EAAE,CAAC;YACpD,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE;gBACxB,OAAO,CAAC,GAAG,CAAC,eAAe,MAAM,kBAAkB,CAAC,CAAC;gBACrD,MAAM,CAAC,QAAQ,EAAE,CAAC,IAAI,CACpB,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EACrB,CAAC,KAAK,EAAE,EAAE;oBACR,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,KAAK,CAAC,CAAC;oBACnD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAClB,CAAC,CACF,CAAC;YACJ,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,WAAW,CAAC,QAAiC;IACpD,MAAM,GAAG,GAAG,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,SAAS,CAAC;IAC9D,IAAI,CAAE,YAAkC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACvD,uEAAuE;QACvE,gEAAgE;QAChE,MAAM,IAAI,GAAG,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,mCAAmC,CAAC,CAAC,CAAC,EAAE,CAAC;QACtE,MAAM,IAAI,KAAK,CACb,iBAAiB,GAAG,mCAAmC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,CACxF,CAAC;IACJ,CAAC;IACD,OAAO,GAAkB,CAAC;AAC5B,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,KAAK,UAAU,YAAY,CACzB,MAAkB,EAClB,QAAgC;IAEhC,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;IACtB,MAAM,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,MAAM,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC;IAErB,MAAM,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;IAChC,KAAK,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,QAAQ,EAAE,CAAC;QAC9C,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;YAC9B,MAAM,QAAQ,GAAG,MAAM,UAAU,CAC/B,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAClC,CAAC;YACF,IAAI,CAAC,QAAQ;gBAAE,SAAS;YACxB,IAAI,OAAO,QAAQ,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;gBAC3C,MAAM,CAAC,GAAG,CAAC;oBACT,KAAK,EAAE,MAAM;oBACb,MAAM,EAAE,MAAM,CAAC,IAAI;oBACnB,OAAO,EAAE,GAAG,IAAI,wCAAwC;iBACzD,CAAC,CAAC;gBACH,SAAS;YACX,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAO,QAAQ,CAAC,OAAoB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAChE,gEAAgE;gBAChE,kEAAkE;gBAClE,6DAA6D;gBAC7D,IAAI,OAAO,MAAM,KAAK,UAAU;oBAAE,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACpD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,GAAG,CAAC;oBACT,KAAK,EAAE,MAAM;oBACb,MAAM,EAAE,MAAM,CAAC,IAAI;oBACnB,OAAO,EAAE,0BAA2B,KAAe,CAAC,OAAO,EAAE;iBAC9D,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,CAAC;IAC3B,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAEzC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAE5B,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC;QACvC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAChC,CAAC,CAAC,IAAI,CAAC;IAET,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IACjC,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;IAEvB,sEAAsE;IACtE,uCAAuC;IACvC,MAAM,YAAY,GAAG,IAAI,UAAU,EAAE,CAAC;IACtC,MAAM,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;IAC9D,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IAEnC,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC7B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,8EAA8E;AAC9E,KAAK,UAAU,QAAQ,CACrB,MAAkB,EAClB,UAAsC;IAEtC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,KAAK,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,UAAU,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QAC/D,IAAI,CAAC;YACH,MAAM,UAAU,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,IAAI,CAAC,GAAG,MAAM,KAAM,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,uCAAuC;IACvC,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;IAE5B,IAAI,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC;QAC7B,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC1C,MAAM,CAAC,MAAO,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACvE,CAAC,CAAC,CAAC;IACL,CAAC;IAED,yEAAyE;IACzE,yEAAyE;IACzE,+CAA+C;IAC/C,MAAM,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC;IACjC,MAAM,MAAM,CAAC,UAAU,EAAE,KAAK,EAAE,CAAC;IAEjC,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,+BAA+B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACxE,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"boot.js","sourceRoot":"","sources":["../../src/boot/boot.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import type { Express, Router } from "express";
2
2
  import type { Environment } from "../env/environment.js";
3
+ import type { ProcessMode } from "./boot.js";
3
4
  import type { GlobalConfig } from "../config/index.js";
4
5
  import type { Acl } from "../acl/acl.js";
5
6
  import type { ModelStore } from "../db/model-store.js";
@@ -11,6 +12,12 @@ import type { SocketManager } from "../sockets/socket-manager.js";
11
12
  * (phase 6) see neither `acl` (phase 7) nor `app` (backend branch, phase 9).
12
13
  */
13
14
  export interface Ctx {
15
+ /**
16
+ * Which process this is. Modules that must behave differently per
17
+ * process read it here — a logger naming its file per mode, so backend
18
+ * and worker never rotate the same file, is the motivating case.
19
+ */
20
+ mode: ProcessMode;
14
21
  Environment: Environment;
15
22
  config: GlobalConfig;
16
23
  acl?: Acl;