proteum 2.0.0-1 → 2.1.0

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.
Files changed (94) hide show
  1. package/AGENTS.md +13 -1
  2. package/README.md +375 -0
  3. package/agents/framework/AGENTS.md +917 -0
  4. package/agents/project/AGENTS.md +138 -0
  5. package/agents/{codex → project}/CODING_STYLE.md +3 -2
  6. package/agents/project/client/AGENTS.md +108 -0
  7. package/agents/{codex → project}/client/pages/AGENTS.md +8 -8
  8. package/agents/{codex → project}/server/routes/AGENTS.md +2 -1
  9. package/agents/project/server/services/AGENTS.md +170 -0
  10. package/agents/{codex → project}/tests/AGENTS.md +1 -0
  11. package/cli/app/config.ts +3 -2
  12. package/cli/app/index.ts +6 -66
  13. package/cli/bin.js +7 -2
  14. package/cli/commands/build.ts +94 -27
  15. package/cli/commands/check.ts +15 -1
  16. package/cli/commands/dev.ts +288 -132
  17. package/cli/commands/doctor.ts +108 -0
  18. package/cli/commands/explain.ts +226 -0
  19. package/cli/commands/init.ts +76 -70
  20. package/cli/commands/lint.ts +18 -1
  21. package/cli/commands/refresh.ts +16 -6
  22. package/cli/commands/typecheck.ts +14 -1
  23. package/cli/compiler/artifacts/controllers.ts +150 -0
  24. package/cli/compiler/artifacts/discovery.ts +132 -0
  25. package/cli/compiler/artifacts/manifest.ts +267 -0
  26. package/cli/compiler/artifacts/routing.ts +315 -0
  27. package/cli/compiler/artifacts/services.ts +480 -0
  28. package/cli/compiler/artifacts/shared.ts +12 -0
  29. package/cli/compiler/client/identite.ts +2 -1
  30. package/cli/compiler/client/index.ts +13 -3
  31. package/cli/compiler/common/controllers.ts +23 -28
  32. package/cli/compiler/common/files/style.ts +3 -4
  33. package/cli/compiler/common/generatedRouteModules.ts +333 -19
  34. package/cli/compiler/common/proteumManifest.ts +133 -0
  35. package/cli/compiler/index.ts +33 -896
  36. package/cli/compiler/server/index.ts +21 -4
  37. package/cli/context.ts +71 -0
  38. package/cli/index.ts +39 -181
  39. package/cli/presentation/commands.ts +208 -0
  40. package/cli/presentation/compileReporter.ts +65 -0
  41. package/cli/presentation/devSession.ts +70 -0
  42. package/cli/presentation/help.ts +193 -0
  43. package/cli/presentation/ink.ts +69 -0
  44. package/cli/presentation/layout.ts +83 -0
  45. package/cli/runtime/argv.ts +49 -0
  46. package/cli/runtime/command.ts +25 -0
  47. package/cli/runtime/commands.ts +221 -0
  48. package/cli/runtime/importEsm.ts +7 -0
  49. package/cli/runtime/verbose.ts +15 -0
  50. package/cli/utils/agents.ts +5 -4
  51. package/cli/utils/keyboard.ts +12 -6
  52. package/client/app/index.ts +0 -6
  53. package/client/services/router/index.tsx +1 -1
  54. package/client/services/router/response/index.tsx +2 -2
  55. package/common/dev/serverHotReload.ts +12 -0
  56. package/common/router/index.ts +3 -2
  57. package/common/router/layouts.ts +1 -1
  58. package/common/router/pageSetup.ts +1 -0
  59. package/package.json +10 -8
  60. package/prettier/router-registration-plugin.cjs +52 -0
  61. package/prettier.config.cjs +1 -0
  62. package/scripts/cleanup-generated-controllers.ts +2 -2
  63. package/scripts/fix-reference-app-typing.ts +2 -2
  64. package/scripts/format-router-registrations.ts +119 -0
  65. package/scripts/migrate-explicit-controllers-and-request.ts +423 -0
  66. package/scripts/refactor-server-controllers.ts +19 -18
  67. package/scripts/refactor-server-runtime-aliases.ts +1 -1
  68. package/server/app/commands.ts +309 -25
  69. package/server/app/container/config.ts +1 -1
  70. package/server/app/container/index.ts +2 -2
  71. package/server/app/controller/index.ts +13 -4
  72. package/server/app/index.ts +53 -37
  73. package/server/app/service/container.ts +26 -28
  74. package/server/app/service/index.ts +10 -20
  75. package/server/app.tsconfig.json +9 -2
  76. package/server/index.ts +32 -1
  77. package/server/services/auth/index.ts +234 -15
  78. package/server/services/auth/router/index.ts +39 -7
  79. package/server/services/auth/router/request.ts +40 -8
  80. package/server/services/disks/index.ts +1 -1
  81. package/server/services/prisma/Facet.ts +2 -2
  82. package/server/services/prisma/index.ts +22 -5
  83. package/server/services/prisma/mariadb.ts +47 -0
  84. package/server/services/router/http/index.ts +9 -1
  85. package/server/services/router/index.ts +10 -4
  86. package/server/services/router/response/index.ts +26 -6
  87. package/types/auth-check-rules.test.ts +51 -0
  88. package/types/controller-request-context.test.ts +55 -0
  89. package/types/service-config.test.ts +39 -0
  90. package/agents/codex/AGENTS.md +0 -95
  91. package/agents/codex/client/AGENTS.md +0 -102
  92. package/agents/codex/server/services/AGENTS.md +0 -137
  93. package/server/services/models.7z +0 -0
  94. /package/agents/{codex → project}/agents.md.zip +0 -0
@@ -0,0 +1,917 @@
1
+ # Proteum Framework Guide
2
+
3
+ This document is the framework-level contract for building and maintaining a Proteum project.
4
+
5
+ It is based on the current Proteum core plus the two real apps used as the reference surface:
6
+
7
+ - `crosspath/platform`
8
+ - `unique.domains/website`
9
+
10
+ Use this guide for new work. Treat older patterns in the apps as legacy unless they are explicitly described here as still-supported.
11
+
12
+ # What Proteum is
13
+
14
+ Proteum is a server-first SSR framework with:
15
+
16
+ - explicit `Router.page(...)` client page registration
17
+ - explicit `server/controllers/**/*.ts` server API entrypoints
18
+ - service classes for business logic
19
+ - generated controller trees and generated route modules
20
+ - request-scoped server context
21
+ - strong bias toward SEO-friendly SSR HTML and minimal client runtime assumptions
22
+
23
+ The main rule for new work is:
24
+
25
+ - keep routing, data loading, validation, request access, and service boundaries explicit
26
+
27
+ # Non-negotiable rules
28
+
29
+ - Client pages live in `client/pages/**` and register routes with `Router.page(...)` or `Router.error(...)`.
30
+ - Page URLs come from the explicit `Router.page('/path', ...)` call, not from the file path.
31
+ - Server business logic lives in service classes that extend `Service`.
32
+ - Callable API entrypoints live only in `server/controllers/**/*.ts` files that extend `Controller`.
33
+ - Controllers validate input with `this.input(schema)` inside the method body.
34
+ - Call `this.input(...)` at most once per controller method.
35
+ - Controller methods are exposed to the client under `/api/...` and are always called as `POST` fetchers.
36
+ - Manual server routes belong in `server/routes/**` and use `Router.get/post/put/patch/delete(...)`.
37
+ - Import Prisma types from `@models/types`.
38
+ - Do not import runtime values from `@models`.
39
+ - Do not use `@request` runtime globals.
40
+ - Do not use `@app` on the client.
41
+ - Do not use `api.fetch(...)` inside page route files for SSR data loading.
42
+ - Do not edit generated files under `.proteum` by hand.
43
+
44
+ # Source Of Truth Files
45
+
46
+ The framework actually reads these files and folders:
47
+
48
+ - `package.json`: CLI scripts and dependency entrypoint
49
+ - `identity.yaml`: app identity and web metadata
50
+ - `env.yaml`: runtime environment config that Proteum loads directly
51
+ - `server/config/*.ts`: plain typed config exports consumed by the explicit app bootstrap
52
+ - `server/index.ts`: default-exported `Application` subclass that instantiates root services and router plugins
53
+ - `server/services/**/service.json`: root service or router-plugin metadata
54
+ - `server/controllers/**/*.ts`: generated API surface
55
+ - `server/routes/**/*.ts`: manual server routes
56
+ - `client/pages/**/*.ts(x)`: page and error registration
57
+ - `client/pages/**/_layout/index.tsx`: generated layouts
58
+ - `public/**`: copied or symlinked to dev/build output
59
+
60
+ Files Proteum generates and owns:
61
+
62
+ - `.proteum/manifest.json`
63
+ - `.proteum/client/routes*`
64
+ - `.proteum/client/layouts*`
65
+ - `.proteum/client/context.ts`
66
+ - `.proteum/client/models.ts`
67
+ - `.proteum/client/services.d.ts`
68
+ - `.proteum/common/controllers.ts`
69
+ - `.proteum/common/models.ts`
70
+ - `.proteum/server/routes*`
71
+ - `.proteum/server/models.ts`
72
+
73
+ The `.proteum` directory should be ignored by git.
74
+
75
+ Generated files now live physically under `.proteum/client/*`, `.proteum/common/*`, and `.proteum/server/*`.
76
+ Project code should reference the generated surface through `@generated/client/*`, `@generated/common/*`, and `@generated/server/*`.
77
+ Keep `@/client/context` mapped to `.proteum/client/context.ts`.
78
+ The legacy `@/client/.generated/*`, `@/common/.generated/*`, and `@/server/.generated/*` aliases may exist temporarily as migration shims, but new code should not use them.
79
+
80
+ # Project Structure
81
+
82
+ Use this shape for real work:
83
+
84
+ - `client/pages`: route files and page-local UI
85
+ - `client/components`: reusable UI
86
+ - `client/hooks` or local hooks near the feature
87
+ - `common`: shared types, catalogs, utilities safe across client/server
88
+ - `server/config`: typed service config exports used by `server/index.ts`
89
+ - `server/index.ts`: explicit app bootstrap and root service graph
90
+ - `server/services`: business services
91
+ - `server/controllers`: callable API entrypoints
92
+ - `server/routes`: manual HTTP routes that should not be generated from controllers
93
+ - `public`: static assets
94
+ - `prisma`: schema and Prisma assets
95
+ - `tests/e2e`: end-to-end tests
96
+
97
+ The two reference apps both follow this split even when their internal feature folders differ.
98
+
99
+ # Project Creation Today
100
+
101
+ What the code says today:
102
+
103
+ - Proteum exposes `proteum init`.
104
+ - The current `init` command expects a `cli/skeleton` directory.
105
+ - This repository currently does not contain that `cli/skeleton` directory.
106
+
107
+ Practical consequence:
108
+
109
+ - In this checkout, the reliable way to start a new app is to copy a working Proteum project structure from an existing app, then replace the identity, env, config, services, pages, and Prisma schema.
110
+
111
+ If `proteum init` is restored later, re-check the skeleton before relying on it.
112
+
113
+ # Required Root Files
114
+
115
+ ## `package.json`
116
+
117
+ The reference apps use these Proteum commands:
118
+
119
+ - `proteum dev`
120
+ - `npx proteum build prod`
121
+ - `npx proteum refresh`
122
+ - `npx proteum doctor`
123
+ - `npx proteum explain`
124
+ - `npx proteum typecheck`
125
+ - `npx proteum lint`
126
+ - `npx proteum check`
127
+
128
+ ## `proteum explain`
129
+
130
+ Proteum now generates a machine-readable manifest at `.proteum/manifest.json` during refresh/build/dev/explain flows.
131
+
132
+ Use `proteum explain` as the first inspection command when an agent needs to understand a project without re-deriving framework conventions from source.
133
+
134
+ Useful commands:
135
+
136
+ - `npx proteum explain`
137
+ - `npx proteum explain --json`
138
+ - `npx proteum explain routes --json`
139
+ - `npx proteum explain services`
140
+ - `npx proteum explain controllers`
141
+ - `npx proteum explain env`
142
+
143
+ What the manifest exposes:
144
+
145
+ - app root and installed Proteum root
146
+ - identity file path and identity summary
147
+ - env file path plus loaded and required top-level keys
148
+ - top-level services and router plugins with source file ownership and scope
149
+ - generated controller endpoints with client accessor and HTTP path
150
+ - client pages, error pages, and manual server routes with explicit route expressions
151
+ - route and controller source locations
152
+ - static route-target resolution state: literal, statically-resolved expression, or dynamic expression
153
+ - client layout ownership and chunk ids
154
+ - manifest-backed diagnostics for duplicate routes, invalid option keys, and controller/server-route collisions
155
+
156
+ ## `proteum doctor`
157
+
158
+ Use `npx proteum doctor` to inspect manifest diagnostics in a human-readable form.
159
+
160
+ Use:
161
+
162
+ - `npx proteum doctor`
163
+ - `npx proteum doctor --json`
164
+ - `npx proteum doctor --strict`
165
+
166
+ Current strict-mode behavior:
167
+
168
+ - exits non-zero if any manifest warning or error exists
169
+ - intended for CI and agent guardrails
170
+
171
+ ## `identity.yaml`
172
+
173
+ Proteum loads `identity.yaml` directly. It must define:
174
+
175
+ - `name`
176
+ - `identifier`
177
+ - `description`
178
+ - `author`
179
+ - `language`
180
+ - optional `locale`
181
+ - `maincolor`
182
+ - optional `iconsPack`
183
+ - `web.title`
184
+ - `web.titleSuffix`
185
+ - `web.fullTitle`
186
+ - `web.description`
187
+ - `web.version`
188
+ - optional `web.metas`
189
+ - optional `web.jsonld`
190
+
191
+ The generated app type uses `identifier`.
192
+
193
+ ## `env.yaml`
194
+
195
+ Proteum currently loads `env.yaml` directly through `ConfigParser`.
196
+
197
+ The core parser expects at least:
198
+
199
+ - `name`
200
+ - `profile`
201
+ - `router.port`
202
+ - `router.domains`
203
+ - `console`
204
+
205
+ Observed reality:
206
+
207
+ - the apps also keep files like `env.prod.yaml` and `env.testing.yaml`
208
+ - the current core parser shown here does not automatically merge those files
209
+
210
+ So for framework-level correctness:
211
+
212
+ - treat `env.yaml` as the authoritative runtime config file unless you also own a custom deploy flow around it
213
+
214
+ # App Bootstrap
215
+
216
+ Bootstrap is explicit.
217
+
218
+ Use `server/config/*.ts` for typed config exports and `server/index.ts` for the application class that wires services together.
219
+
220
+ Example config module:
221
+
222
+ ```ts
223
+ import { Services, type ServiceConfig } from '@server/app';
224
+ import AppContainer from '@server/app/container';
225
+ import Router from '@server/services/router';
226
+ import Users from '@/server/services/Users';
227
+
228
+ type RouterBaseConfig = Omit<ServiceConfig<typeof Router>, 'plugins'>;
229
+
230
+ export const usersConfig = Services.config(Users, {});
231
+
232
+ export const routerBaseConfig = {
233
+ domains: AppContainer.Environment.router.domains,
234
+ http: {
235
+ domain: 'example.com',
236
+ port: AppContainer.Environment.router.port,
237
+ ssl: true,
238
+ upload: { maxSize: '10mb' },
239
+ },
240
+ context: () => ({}),
241
+ } satisfies RouterBaseConfig;
242
+ ```
243
+
244
+ Example app bootstrap:
245
+
246
+ ```ts
247
+ import { Application } from '@server/app';
248
+ import Router from '@server/services/router';
249
+ import SchemaRouter from '@server/services/schema/router';
250
+ import Users from '@/server/services/Users';
251
+ import * as userConfig from '@/server/config/user';
252
+
253
+ export default class MyApp extends Application {
254
+ public Users = new Users(this, userConfig.usersConfig, this);
255
+ public Router = new Router(this, {
256
+ ...userConfig.routerBaseConfig,
257
+ plugins: {
258
+ schema: new SchemaRouter({}, this),
259
+ },
260
+ }, this);
261
+ }
262
+ ```
263
+
264
+ Important bootstrap rules:
265
+
266
+ - `server/index.ts` must default-export the app `Application` subclass.
267
+ - Each root service is a public class field instantiated with `new ServiceClass(this, config, this)`.
268
+ - `server/config/*.ts` should export plain typed constants with `Services.config(ServiceClass, { ... })`.
269
+ - Router base config can use `Omit<ServiceConfig<typeof Router>, 'plugins'>` so router plugins are instantiated explicitly in `server/index.ts`.
270
+ - Router plugins are configured inside the `Router` config `plugins` object as explicit `new PluginClass(config, this)` instances.
271
+ - Router `context(request, app)` returns SSR-safe values exposed to both page setup/render and the client runtime.
272
+ - Both reference apps expose a SSR-safe `user` object through `Router.context(...)`.
273
+ - Generated service typings and manifests are derived from `server/index.ts` plus `server/services/**/service.json`.
274
+
275
+ # Services
276
+
277
+ ## Root service contract
278
+
279
+ Each root app service normally has:
280
+
281
+ - `server/services/<Feature>/index.ts`
282
+ - `server/services/<Feature>/service.json`
283
+
284
+ Example `service.json`:
285
+
286
+ ```json
287
+ {
288
+ "id": "UniqueDomains/Founder",
289
+ "name": "UniqueDomainsFounder",
290
+ "parent": "app",
291
+ "dependences": []
292
+ }
293
+ ```
294
+
295
+ Rules:
296
+
297
+ - `parent` is `"app"` for normal root services.
298
+ - `id` is the service identifier declared in `service.json` and used by manifests plus `Service.use('Service/Id')`.
299
+ - `name` is metadata for generated registration.
300
+ - `priority` is optional and used by some services.
301
+
302
+ ## Service class contract
303
+
304
+ Service classes extend `Service<Config, Hooks, App, Parent>`.
305
+
306
+ Use services for:
307
+
308
+ - database reads and writes
309
+ - orchestration
310
+ - feature logic
311
+ - subservice composition
312
+ - startup work in `ready()`
313
+
314
+ Available on a service instance:
315
+
316
+ - `this.app`: application instance
317
+ - `this.services`: same application instance as a service registry
318
+ - `this.models`: runtime Prisma client, if `Models` is registered
319
+
320
+ Example:
321
+
322
+ ```ts
323
+ import Service from '@server/app/service';
324
+
325
+ export type Config = {
326
+ pageSize: number;
327
+ };
328
+
329
+ export default class FounderService extends Service<Config, {}, MyApp, MyApp> {
330
+ public async ListProjects(input: { userId: number }) {
331
+ return this.models.project.findMany({
332
+ where: { userId: input.userId },
333
+ select: {
334
+ id: true,
335
+ name: true,
336
+ },
337
+ });
338
+ }
339
+ }
340
+ ```
341
+
342
+ Service rules:
343
+
344
+ - Prefer `this.services.OtherService` over hidden globals.
345
+ - Prefer `this.models` or `this.app.Models.client` for Prisma runtime access.
346
+ - Keep auth, input parsing, and request handling in controllers.
347
+ - Pass explicit typed values into services instead of reading request state inside services.
348
+ - Prefer service inputs that are easy to test without a live request context.
349
+
350
+ ## Subservices
351
+
352
+ Both reference apps use service-owned subservices heavily.
353
+
354
+ Example:
355
+
356
+ ```ts
357
+ export default class DomainsService extends Service<Config, {}, UniqueDomains, UniqueDomains> {
358
+ public search = new DomainsSearchService(this, this.config, this.app);
359
+ public radar = new DomainsRadarService(this, null, this.app);
360
+ }
361
+ ```
362
+
363
+ Use subservices when:
364
+
365
+ - a feature has multiple coherent domains
366
+ - you want controller paths like `Domains.search.*` or `Founder.projects.*`
367
+ - you want smaller files and explicit ownership
368
+
369
+ # Controllers
370
+
371
+ ## File contract
372
+
373
+ Controller files must:
374
+
375
+ - live under `server/controllers/**/*.ts`
376
+ - default-export a class extending `Controller`
377
+
378
+ Example:
379
+
380
+ ```ts
381
+ import Controller, { schema } from '@server/app/controller';
382
+
383
+ export default class FounderProjectsController extends Controller<MyApp> {
384
+ public async createProject() {
385
+ const { Founder } = this.services;
386
+
387
+ const data = this.input(
388
+ schema.object({
389
+ name: schema.string(),
390
+ }),
391
+ );
392
+
393
+ return Founder.projects.createProject(data);
394
+ }
395
+ }
396
+ ```
397
+
398
+ ## Validation contract
399
+
400
+ Use `this.input(...)` exactly once per controller method.
401
+
402
+ Supported forms:
403
+
404
+ - `this.input(zodSchema)`
405
+ - `this.input({ ...shape })`
406
+
407
+ Do not:
408
+
409
+ - validate in decorators
410
+ - call `this.input(...)` twice
411
+ - parse request data manually unless you are in a manual `server/routes` handler
412
+
413
+ ## Request contract
414
+
415
+ Controllers receive request scope through `this.request`.
416
+
417
+ Typical values:
418
+
419
+ - `this.request.request`: raw request object wrapper
420
+ - `this.request.response`
421
+ - `this.request.user`
422
+ - `this.request.auth`
423
+ - `this.request.schema`
424
+ - `this.request.metrics`
425
+ - `this.request.request.data`
426
+
427
+ The exact plugin fields depend on the router plugins configured in `server/index.ts`.
428
+
429
+ ## Route generation
430
+
431
+ Proteum generates controller endpoints automatically.
432
+
433
+ Key facts:
434
+
435
+ - Only `server/controllers/**/*.ts` files are indexed.
436
+ - Only class methods with bodies become routes.
437
+ - The client-facing route is always prefixed with `/api/`.
438
+ - Generated client calls use `POST`, even for read methods such as `Get`, `List`, or `Search`.
439
+
440
+ Route path derivation:
441
+
442
+ - base path comes from the controller file path
443
+ - method name becomes the final route segment
444
+ - `export const controllerPath = 'Custom/path'` overrides the base path
445
+
446
+ Examples from the reference apps:
447
+
448
+ - `server/controllers/Auth.ts#Session()` becomes `Auth.Session()` on the client and maps to `/api/Auth/Session`
449
+ - `server/controllers/Domains/search.ts#Search()` becomes `Domains.search.Search()`
450
+ - `server/controllers/Companies/Persons.ts` can override the base path with `controllerPath = 'Companies/Persons'`
451
+
452
+ Naming rule:
453
+
454
+ - method names become public API names
455
+ - choose method names deliberately because client code will call them directly
456
+
457
+ # Client Pages
458
+
459
+ ## File contract
460
+
461
+ Client pages live in `client/pages/**`.
462
+
463
+ Proteum scans page files for top-level `Router.page(...)` and `Router.error(...)` calls.
464
+
465
+ Important compiler rule:
466
+
467
+ - the file path controls chunk identity and layout discovery
468
+ - the URL comes from the explicit route path string in `Router.page(...)`
469
+
470
+ Do not hide route registration inside helper abstractions that remove the direct top-level `Router.page(...)` call.
471
+
472
+ ## Import contract
473
+
474
+ Use:
475
+
476
+ ```ts
477
+ import Router from '@/client/router';
478
+ ```
479
+
480
+ Do not use `@app` on the client.
481
+
482
+ ## Supported signatures
483
+
484
+ Proteum supports these `Router.page(...)` signatures:
485
+
486
+ ```ts
487
+ Router.page('/path', render);
488
+ Router.page('/path', setup, render);
489
+ Router.page('/path', options, render);
490
+ Router.page('/path', options, setup, render);
491
+ ```
492
+
493
+ New work should usually prefer:
494
+
495
+ ```ts
496
+ Router.page('/path', setup, render);
497
+ ```
498
+
499
+ or:
500
+
501
+ ```ts
502
+ Router.page('/path', options, setup, render);
503
+ ```
504
+
505
+ ## Setup function
506
+
507
+ `setup` is the SSR contract. It receives:
508
+
509
+ - router context
510
+ - generated controller tree
511
+ - custom router context values like `user`
512
+ - request query/path params in `data`
513
+
514
+ Return one flat object.
515
+
516
+ Proteum splits that object into:
517
+
518
+ - route options
519
+ - SSR data providers
520
+
521
+ Supported route option keys are:
522
+
523
+ - `_priority`
524
+ - `_preload`
525
+ - `_domain`
526
+ - `_accept`
527
+ - `_raw`
528
+ - `_auth`
529
+ - `_redirectLogged`
530
+ - `_static`
531
+ - `_whenStatic`
532
+ - `_canonicalParams`
533
+ - `_layout`
534
+ - `_TESTING`
535
+ - `_logging`
536
+
537
+ The underscore is optional in the framework code, but both reference apps use the underscore form and new work should do the same.
538
+
539
+ Everything else returned from `setup` is treated as page data.
540
+
541
+ Example:
542
+
543
+ ```ts
544
+ Router.page('/pricing', ({ Plans }) => ({
545
+ _auth: false,
546
+ _layout: false,
547
+ plans: Plans.getPlans(),
548
+ }), ({ plans }) => <PricingPage plans={plans} />);
549
+ ```
550
+
551
+ ## Data loading rules
552
+
553
+ Use page `setup` for SSR data.
554
+
555
+ Good:
556
+
557
+ ```ts
558
+ Router.page('/app/projects/:projectId', ({ Founder }) => ({
559
+ _auth: 'USER',
560
+ projectsResponse: Founder.projects.getProjects(),
561
+ }), ({ projectsResponse }) => <ProjectsPage projects={projectsResponse.projects} />);
562
+ ```
563
+
564
+ Bad:
565
+
566
+ - calling `api.fetch(...)` inside the page file to preload SSR data
567
+ - moving SSR data fetching into random effects when the page can know it up front
568
+
569
+ How setup data works:
570
+
571
+ - controller fetchers and promises are resolved before render
572
+ - SSR fetchers are batched through a single `/api` request internally
573
+ - plain values can also be returned from `setup`
574
+
575
+ ## Render function
576
+
577
+ `render` receives:
578
+
579
+ - the same router context
580
+ - resolved setup data
581
+ - the generated controller tree
582
+ - `page`
583
+ - `request`
584
+ - `api`
585
+ - custom router context like `user`
586
+
587
+ Use it for:
588
+
589
+ - page-local React/Preact state
590
+ - calling controller methods on interaction
591
+ - assigning page metadata on `page`
592
+
593
+ Example:
594
+
595
+ ```ts
596
+ ({ request, page, Founder }) => {
597
+ page.metas.robots = 'noindex';
598
+
599
+ return <Page />;
600
+ }
601
+ ```
602
+
603
+ ## Error pages
604
+
605
+ Use `Router.error(code, options, render)` in `client/pages/_messages/**`.
606
+
607
+ Example:
608
+
609
+ ```ts
610
+ Router.error(404, { layout: false }, ({ data }) => <ErrorScreen code={404} data={data} />);
611
+ ```
612
+
613
+ # Client Context And Controller Calls
614
+
615
+ Proteum generates a client context and controller tree.
616
+
617
+ Use:
618
+
619
+ ```ts
620
+ import useContext from '@/client/context';
621
+
622
+ const { Founder, user, Router, api } = useContext();
623
+ ```
624
+
625
+ Generated controller methods are promise-like fetchers:
626
+
627
+ - `then`
628
+ - `catch`
629
+ - `finally`
630
+ - `run()`
631
+
632
+ So all of these are valid:
633
+
634
+ ```ts
635
+ Founder.projects.getProjects().then(...);
636
+ await Founder.projects.updateProject(payload);
637
+ await Founder.projects.updateProject(payload).run();
638
+ ```
639
+
640
+ Modern usage in both apps is mostly direct `await` or `.then(...)`.
641
+
642
+ Use `api.reload(...)` and `api.set(...)` only when you intentionally want to refresh or mutate page setup data that already belongs to the active page response.
643
+
644
+ # Layouts
645
+
646
+ Layouts come from `client/pages/**/_layout/index.tsx`.
647
+
648
+ How Proteum resolves them:
649
+
650
+ - if `_layout: false`, no layout is used
651
+ - if `_layout: 'convert'`, a named generated layout with id `convert` is used
652
+ - otherwise Proteum picks the nearest matching `_layout` folder by file chunk identity
653
+ - if no generated layout matches, the internal root layout is used
654
+
655
+ Observed patterns:
656
+
657
+ - CrossPath has root, `convert`, and `employer` layouts
658
+ - Unique Domains mostly uses the internal/root layout and sets `_layout: false` for public landing pages
659
+
660
+ Practical rule:
661
+
662
+ - use `_layout: false` for standalone landing or embed-like pages
663
+ - use a named layout only when a matching `_layout` folder exists
664
+
665
+ # Manual Server Routes
666
+
667
+ Use `server/routes/**` for routes that should stay explicit HTTP endpoints rather than generated controller actions.
668
+
669
+ Typical uses from the reference apps:
670
+
671
+ - redirects
672
+ - webhook-like endpoints
673
+ - sitemap and RSS
674
+ - landing-page tracking
675
+ - public API endpoints with custom semantics
676
+ - OAuth callbacks
677
+
678
+ Example:
679
+
680
+ ```ts
681
+ import { Router, Navigation } from '@app';
682
+
683
+ Router.get('/sitemap.xml', async ({ response }) => {
684
+ return response.xml(await Navigation.Sitemap());
685
+ });
686
+ ```
687
+
688
+ Manual route rules:
689
+
690
+ - import server services from `@app`
691
+ - use route handler context for request/response and router-plugin services
692
+ - validate with `schema.validate(...)` when the schema router plugin is installed
693
+ - return `response.redirect(...)`, `response.json(...)`, `response.xml(...)`, `response.html(...)`, `response.file(...)`, or raw serializable data
694
+
695
+ Route handler context includes:
696
+
697
+ - `request`
698
+ - `response`
699
+ - `Router`
700
+ - app services
701
+ - generated controller tree
702
+ - router plugin request services such as `auth`, `schema`, `metrics`
703
+ - custom router context values from `Router.context(...)`
704
+
705
+ # Router Plugins
706
+
707
+ Router plugins are special services attached under `Router.config.plugins`.
708
+
709
+ They extend `RouterService`.
710
+
711
+ Use them for:
712
+
713
+ - authentication
714
+ - validation helpers
715
+ - metrics/tracking
716
+ - other request-scoped helpers
717
+
718
+ A router plugin usually has:
719
+
720
+ - `server/services/<Feature>/router/index.ts`
721
+ - optional `server/services/<Feature>/router/request.ts`
722
+ - `service.json` with `"parent": "router"`
723
+
724
+ Example `service.json`:
725
+
726
+ ```json
727
+ {
728
+ "id": "UniqueDomains/Users/Metrics/Router",
729
+ "name": "Metrics",
730
+ "parent": "router",
731
+ "dependences": []
732
+ }
733
+ ```
734
+
735
+ Router plugin rules:
736
+
737
+ - implement `requestService(request)` to expose a request-scoped helper to route/controller context
738
+ - use `this.parent.on('request' | 'resolved' | 'render', ...)` inside `ready()` when you need router lifecycle hooks
739
+
740
+ # Models And Prisma
741
+
742
+ For typings:
743
+
744
+ ```ts
745
+ import type * as Models from '@models/types';
746
+ ```
747
+
748
+ For runtime access:
749
+
750
+ - `this.models`
751
+ - `this.app.Models.client`
752
+
753
+ Rules:
754
+
755
+ - do not import runtime values from `@models`
756
+ - keep Prisma model access inside services
757
+ - prefer explicit `select` or narrow `include`
758
+ - do not edit generated Prisma client files
759
+
760
+ Both apps instantiate `Models` explicitly in `server/index.ts` with config imported from `server/config/*.ts`.
761
+
762
+ # Aliases
763
+
764
+ These aliases matter in real projects:
765
+
766
+ - `@/client/...`: app client code
767
+ - `@/server/...`: app server code
768
+ - `@/common/...`: app shared code
769
+ - `@client/...`, `@server/...`, `@common/...`: Proteum core modules
770
+ - `@app`: server-side application services for manual routes
771
+ - `@models/types`: Prisma typings only
772
+
773
+ Import rules:
774
+
775
+ - client pages: use `@/client/router`, `@/client/context`, app-local components, and generated controller tree from context
776
+ - controllers/services: use `@server/app/controller`, `@server/app/service`, app-local services, and `@models/types`
777
+ - manual server routes: use `@app` plus app-local utilities
778
+
779
+ # SEO And Static Output
780
+
781
+ Proteum is built for SSR and crawlable HTML.
782
+
783
+ Observed patterns in the apps:
784
+
785
+ - public landing pages use `Router.page(..., { _layout: false, ... })`
786
+ - sitemap is produced explicitly through a service, then exposed with `Router.get('/sitemap.xml', ...)`
787
+ - canonical behavior is available through `_canonicalParams`
788
+ - static caching exists through `_static`
789
+ - manual routes can opt into running even for static pages with `whenStatic: true`
790
+
791
+ Use these rules:
792
+
793
+ - prefer SSR page setup for crawlable content
794
+ - keep metadata and structured output on the server-rendered path
795
+ - use manual routes for sitemap, RSS, redirects, and resource endpoints
796
+
797
+ # Generated Code Mental Model
798
+
799
+ Proteum is not magic, but it is generation-heavy.
800
+
801
+ When you change source files, Proteum regenerates:
802
+
803
+ - route wrapper modules for client pages and server routes
804
+ - layout registries
805
+ - controller client tree
806
+ - typed server app shim
807
+
808
+ Source-to-generated mapping:
809
+
810
+ - `client/pages/**` -> generated route modules and layout modules
811
+ - `server/routes/**` -> generated server route modules
812
+ - `server/controllers/**/*.ts` -> `.proteum/common/controllers.ts` and server controller registry
813
+ - `server/services/**/service.json` + `server/index.ts` -> generated service typings and manifest service entries
814
+
815
+ LLM rule:
816
+
817
+ - edit source files only
818
+ - never patch generated output directly
819
+
820
+ # Maintenance Workflow For New Features
821
+
822
+ When adding a feature, follow this order:
823
+
824
+ 1. Add or extend a root service under `server/services/<Feature>`.
825
+ 2. Add or extend subservices if the feature has distinct concerns.
826
+ 3. Add `server/controllers/**/*.ts` entrypoints for callable app APIs.
827
+ 4. Add or extend typed config exports in `server/config/*.ts`, then instantiate the root service in `server/index.ts`.
828
+ 5. Add or update `client/pages/**` routes that consume the feature.
829
+ 6. Load SSR data in page `setup`.
830
+ 7. Use generated controller methods from page args or `useContext()` for interactions.
831
+ 8. Add manual `server/routes/**` only if you need explicit HTTP behavior that should not be a controller endpoint.
832
+
833
+ # Maintenance Checklist For Existing Projects
834
+
835
+ When maintaining a Proteum app:
836
+
837
+ - inspect `server/index.ts` and `server/config/*.ts` first to understand which services actually exist
838
+ - inspect `service.json` before moving or renaming services
839
+ - inspect `server/controllers/**/*.ts` to understand the public client API
840
+ - inspect `client/pages/**` for the real route table
841
+ - check `_layout` folders before changing page chrome
842
+ - check router plugins before assuming `auth`, `schema`, or `metrics` behavior
843
+ - trace generated controller calls back to controller files, not to ad-hoc fetch URLs
844
+
845
+ # Preferred Patterns For New Work
846
+
847
+ - `Router.page(path, setup, render)` over page-local fetch hacks
848
+ - controller-backed APIs over ad-hoc manual `/api/...` route files
849
+ - service classes over random server helpers with hidden dependencies
850
+ - `controllerPath` only when the file path would produce the wrong public API shape
851
+ - `useContext()` or page render args for controller access on the client
852
+ - one clear source of truth for catalogs and shared types
853
+ - when a project already includes a Shadcn-based `client/components/ui/**` layer, reuse those components for standard UI primitives before creating custom ones
854
+
855
+ # Legacy Or Discouraged Patterns
856
+
857
+ These exist in the codebase but should not be the default for new work:
858
+
859
+ - older pages that overuse `api.reload(...)` and `api.set(...)`
860
+ - older pages with deeply mixed UI and data responsibilities
861
+ - legacy code that leans on manual `/api/...` routes for app APIs
862
+ - any attempt to reintroduce `api.fetch(...)` for SSR page loading
863
+ - client-side `@app` imports
864
+ - runtime `@models` imports
865
+
866
+ # Testing And Verification
867
+
868
+ For app work, verify at the correct layer:
869
+
870
+ - route additions: boot the app and hit the real URL
871
+ - controller changes: exercise the generated client call or `/api/...` endpoint
872
+ - SSR changes: load the real page and inspect the rendered HTML and browser console
873
+ - router/plugin changes: verify request context behavior, auth, redirects, metrics, and validation on a running app
874
+
875
+ Use the real app commands already present in the reference projects when possible:
876
+
877
+ - `proteum dev`
878
+ - `npx proteum build prod`
879
+ - `npx proteum typecheck`
880
+ - `npx proteum lint`
881
+ - `npx proteum check`
882
+
883
+ # Minimal Recipes
884
+
885
+ ## Add a new app API
886
+
887
+ 1. Create or extend `server/services/Feature/index.ts`.
888
+ 2. Create `server/controllers/Feature.ts`.
889
+ 3. Validate input with `this.input(schema)`.
890
+ 4. Resolve auth or other request-derived values in the controller and pass them into the service method.
891
+ 5. Call it from the client as `Feature.MethodName(...)`.
892
+
893
+ ## Add a new SSR page
894
+
895
+ 1. Create `client/pages/.../index.tsx`.
896
+ 2. Register `Router.page('/real-url', setup, render)`.
897
+ 3. Return `_auth`, `_layout`, and SSR fetchers from `setup`.
898
+ 4. Read resolved data in `render`.
899
+ 5. Use `useContext()` only for interactive follow-up actions.
900
+
901
+ ## Add a new manual route
902
+
903
+ 1. Create `server/routes/.../file.ts`.
904
+ 2. Import `Router` and needed services from `@app`.
905
+ 3. Register `Router.get/post/...`.
906
+ 4. Use `schema.validate(...)` if the schema plugin is installed.
907
+ 5. Return a response helper or raw JSON-safe data.
908
+
909
+ # Summary Rule
910
+
911
+ If you are unsure where code belongs:
912
+
913
+ - page URL and SSR data: `client/pages`
914
+ - reusable business logic: `server/services`
915
+ - client-callable app API: `server/controllers/**/*.ts`
916
+ - custom HTTP endpoint: `server/routes`
917
+ - request-scoped cross-cutting concern: router plugin