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.
- package/AGENTS.md +13 -1
- package/README.md +375 -0
- package/agents/framework/AGENTS.md +917 -0
- package/agents/project/AGENTS.md +138 -0
- package/agents/{codex → project}/CODING_STYLE.md +3 -2
- package/agents/project/client/AGENTS.md +108 -0
- package/agents/{codex → project}/client/pages/AGENTS.md +8 -8
- package/agents/{codex → project}/server/routes/AGENTS.md +2 -1
- package/agents/project/server/services/AGENTS.md +170 -0
- package/agents/{codex → project}/tests/AGENTS.md +1 -0
- package/cli/app/config.ts +3 -2
- package/cli/app/index.ts +6 -66
- package/cli/bin.js +7 -2
- package/cli/commands/build.ts +94 -27
- package/cli/commands/check.ts +15 -1
- package/cli/commands/dev.ts +288 -132
- package/cli/commands/doctor.ts +108 -0
- package/cli/commands/explain.ts +226 -0
- package/cli/commands/init.ts +76 -70
- package/cli/commands/lint.ts +18 -1
- package/cli/commands/refresh.ts +16 -6
- package/cli/commands/typecheck.ts +14 -1
- package/cli/compiler/artifacts/controllers.ts +150 -0
- package/cli/compiler/artifacts/discovery.ts +132 -0
- package/cli/compiler/artifacts/manifest.ts +267 -0
- package/cli/compiler/artifacts/routing.ts +315 -0
- package/cli/compiler/artifacts/services.ts +480 -0
- package/cli/compiler/artifacts/shared.ts +12 -0
- package/cli/compiler/client/identite.ts +2 -1
- package/cli/compiler/client/index.ts +13 -3
- package/cli/compiler/common/controllers.ts +23 -28
- package/cli/compiler/common/files/style.ts +3 -4
- package/cli/compiler/common/generatedRouteModules.ts +333 -19
- package/cli/compiler/common/proteumManifest.ts +133 -0
- package/cli/compiler/index.ts +33 -896
- package/cli/compiler/server/index.ts +21 -4
- package/cli/context.ts +71 -0
- package/cli/index.ts +39 -181
- package/cli/presentation/commands.ts +208 -0
- package/cli/presentation/compileReporter.ts +65 -0
- package/cli/presentation/devSession.ts +70 -0
- package/cli/presentation/help.ts +193 -0
- package/cli/presentation/ink.ts +69 -0
- package/cli/presentation/layout.ts +83 -0
- package/cli/runtime/argv.ts +49 -0
- package/cli/runtime/command.ts +25 -0
- package/cli/runtime/commands.ts +221 -0
- package/cli/runtime/importEsm.ts +7 -0
- package/cli/runtime/verbose.ts +15 -0
- package/cli/utils/agents.ts +5 -4
- package/cli/utils/keyboard.ts +12 -6
- package/client/app/index.ts +0 -6
- package/client/services/router/index.tsx +1 -1
- package/client/services/router/response/index.tsx +2 -2
- package/common/dev/serverHotReload.ts +12 -0
- package/common/router/index.ts +3 -2
- package/common/router/layouts.ts +1 -1
- package/common/router/pageSetup.ts +1 -0
- package/package.json +10 -8
- package/prettier/router-registration-plugin.cjs +52 -0
- package/prettier.config.cjs +1 -0
- package/scripts/cleanup-generated-controllers.ts +2 -2
- package/scripts/fix-reference-app-typing.ts +2 -2
- package/scripts/format-router-registrations.ts +119 -0
- package/scripts/migrate-explicit-controllers-and-request.ts +423 -0
- package/scripts/refactor-server-controllers.ts +19 -18
- package/scripts/refactor-server-runtime-aliases.ts +1 -1
- package/server/app/commands.ts +309 -25
- package/server/app/container/config.ts +1 -1
- package/server/app/container/index.ts +2 -2
- package/server/app/controller/index.ts +13 -4
- package/server/app/index.ts +53 -37
- package/server/app/service/container.ts +26 -28
- package/server/app/service/index.ts +10 -20
- package/server/app.tsconfig.json +9 -2
- package/server/index.ts +32 -1
- package/server/services/auth/index.ts +234 -15
- package/server/services/auth/router/index.ts +39 -7
- package/server/services/auth/router/request.ts +40 -8
- package/server/services/disks/index.ts +1 -1
- package/server/services/prisma/Facet.ts +2 -2
- package/server/services/prisma/index.ts +22 -5
- package/server/services/prisma/mariadb.ts +47 -0
- package/server/services/router/http/index.ts +9 -1
- package/server/services/router/index.ts +10 -4
- package/server/services/router/response/index.ts +26 -6
- package/types/auth-check-rules.test.ts +51 -0
- package/types/controller-request-context.test.ts +55 -0
- package/types/service-config.test.ts +39 -0
- package/agents/codex/AGENTS.md +0 -95
- package/agents/codex/client/AGENTS.md +0 -102
- package/agents/codex/server/services/AGENTS.md +0 -137
- package/server/services/models.7z +0 -0
- /package/agents/{codex → project}/agents.md.zip +0 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
This is a full stack monolith project using Typescript, NodeJS, Preact, and Proteum.
|
|
4
|
+
|
|
5
|
+
`/client`: frontend
|
|
6
|
+
`/assets`: CSS, images and other frontend assets
|
|
7
|
+
`/catalogs`: client-only catalogs and registries
|
|
8
|
+
`/components`: reusable components
|
|
9
|
+
`/pages`: page route files and page-local UI
|
|
10
|
+
`/hooks`
|
|
11
|
+
`/common`: shared functions, constants, typings, and cross-runtime catalogs
|
|
12
|
+
`/server`: backend
|
|
13
|
+
`/catalogs`: server-only catalogs and registries
|
|
14
|
+
`/config`: service configuration
|
|
15
|
+
`/services`: backend services and controllers
|
|
16
|
+
`/routes`: explicit non-controller routes
|
|
17
|
+
`/lib`: helper functions
|
|
18
|
+
`/tests`
|
|
19
|
+
|
|
20
|
+
# Coding style
|
|
21
|
+
|
|
22
|
+
See `CODING_STYLE.md`.
|
|
23
|
+
This file is the source of truth for formatting, section-comment structure, and general coding style.
|
|
24
|
+
|
|
25
|
+
# Framework contract
|
|
26
|
+
|
|
27
|
+
For Proteum-wide project scaffolding, routing, SSR, controller, service, generated-code, and maintenance rules, use the
|
|
28
|
+
framework guide in the Proteum repository at `agents/framework/AGENTS.md`.
|
|
29
|
+
From a normal Proteum project install, read it at `./node_modules/proteum/agents/framework/AGENTS.md`.
|
|
30
|
+
|
|
31
|
+
Start framework inspection with:
|
|
32
|
+
|
|
33
|
+
- `./.proteum/manifest.json`
|
|
34
|
+
- `npx proteum explain`
|
|
35
|
+
- `npx proteum doctor`
|
|
36
|
+
|
|
37
|
+
Generated files live under `./.proteum` and should not be edited by hand.
|
|
38
|
+
Project code should use the generated aliases `@generated/client/*`, `@generated/common/*`, and `@generated/server/*`.
|
|
39
|
+
Client context is typically imported from `@/client/context`.
|
|
40
|
+
Prefer type inference from the explicit application class in `./server/index.ts` whenever possible. Treat it as the
|
|
41
|
+
canonical type root for app services, router services, router context, and models instead of duplicating manual type
|
|
42
|
+
declarations.
|
|
43
|
+
|
|
44
|
+
# Files organization
|
|
45
|
+
|
|
46
|
+
- Always keep one class / React component per file
|
|
47
|
+
- Prefer a deep tree structure that groups files by business concern instead of long file names
|
|
48
|
+
- The default `*.ts` / `*.tsx` file is the normal implementation; use `*.ssr.ts` / `*.ssr.tsx` only when the project
|
|
49
|
+
needs an SSR-specific variant
|
|
50
|
+
|
|
51
|
+
## Centralize feature catalogs (Single Source of Truth)
|
|
52
|
+
|
|
53
|
+
When implementing a feature that relies on a **curated list of items**, keep **one canonical catalog/registry file** and make all other code import it.
|
|
54
|
+
|
|
55
|
+
- Client-only catalogs live in `/client/catalogs/**`
|
|
56
|
+
- Server-only catalogs live in `/server/catalogs/**`
|
|
57
|
+
- Shared catalogs used by both runtimes live in `/common/catalogs/**`
|
|
58
|
+
- Organize those root catalog trees by business concern (mirror the feature path when useful)
|
|
59
|
+
- Do not create nested `catalogs/` folders inside `/client/pages/**`, `/client/components/**`, `/server/services/**`, or similar feature folders
|
|
60
|
+
|
|
61
|
+
## Runtime access rules
|
|
62
|
+
|
|
63
|
+
- `@models/types`: Prisma typings only. Can be imported anywhere.
|
|
64
|
+
- Never use runtime value imports from `@request` or `@models`.
|
|
65
|
+
- Never expose request-scoped state through imports.
|
|
66
|
+
- Keep app services, router services, router context, and model contracts inferred from `./server/index.ts` whenever
|
|
67
|
+
possible instead of recreating parallel type maps.
|
|
68
|
+
|
|
69
|
+
## Client runtime access
|
|
70
|
+
|
|
71
|
+
- Page route files use `Router.page(...)`.
|
|
72
|
+
- `Router.page(path, render)` for pages without SSR setup.
|
|
73
|
+
- `Router.page(path, setup, render)` for pages with SSR config/data.
|
|
74
|
+
- `setup` receives the normal page context plus the generated controller tree spread into it.
|
|
75
|
+
- `render` receives the normal page context plus the resolved setup data and the same controller tree spread into it.
|
|
76
|
+
- Components and hooks use the app client context hook, usually imported from `@/client/context`.
|
|
77
|
+
- For UI primitives, prefer the project's shared Shadcn-based components whenever they already exist and fit the need before creating bespoke buttons, inputs, dialogs, or similar building blocks.
|
|
78
|
+
|
|
79
|
+
## Server runtime access
|
|
80
|
+
|
|
81
|
+
- Normal business logic lives in `/server/services/**/index.ts` classes that extend `Service`.
|
|
82
|
+
- Route entrypoints live in `/server/controllers/**/*.ts` classes that extend `Controller`.
|
|
83
|
+
- Only controller files are indexed as callable API endpoints.
|
|
84
|
+
- Controller methods validate input with `this.input(schema)` and access request scope through `this.request`.
|
|
85
|
+
- Service classes access other services via `this.services` and prisma models via `this.models`.
|
|
86
|
+
- App service types should be inferred from the explicit application graph rooted at `./server/index.ts`.
|
|
87
|
+
- Router services and request/context values such as `user`, `auth`, and similar request-scoped contracts should come
|
|
88
|
+
from inferred request and app types, not ad hoc casts.
|
|
89
|
+
- Models should be inferred from the app/model registry rooted at `./server/index.ts` and exposed through the generated
|
|
90
|
+
server app shim, not from duplicated manual model maps.
|
|
91
|
+
- Never use request-scoped state directly inside normal service methods.
|
|
92
|
+
- Controllers should resolve auth and request-derived values, then pass plain typed arguments into services.
|
|
93
|
+
- When referencing an app service, a router service, or a model, expose it in the current block scope first by
|
|
94
|
+
destructuring from `this.request`, `this.app`, `this.models`, or the generated app/model accessors where applicable,
|
|
95
|
+
then call methods on that local binding. The service, router value, or model should be the first element of the callee
|
|
96
|
+
chain.
|
|
97
|
+
|
|
98
|
+
# Agent behavior
|
|
99
|
+
|
|
100
|
+
**Make sure the code you generate integrates perfectly with the current codebase by avoiding repetition and centralizing each purpose.**
|
|
101
|
+
|
|
102
|
+
## Typings
|
|
103
|
+
|
|
104
|
+
- Keep strong, consistent TypeScript typings across the whole project.
|
|
105
|
+
- Do not introduce `any` or `unknown`, including through casts, helper aliases, or fallback generic defaults.
|
|
106
|
+
- Fix typing issues only on the code you wrote.
|
|
107
|
+
- Never cast with `as any` or `as unknown`; fix the type contract or introduce an explicit typed adapter instead. If you find no other solution, tell me in the output.
|
|
108
|
+
|
|
109
|
+
## Workflow
|
|
110
|
+
|
|
111
|
+
- Every time I input error messages without any instructions, don't implement fixes.
|
|
112
|
+
Instead, investigate the potential causes of the errors, and for each:
|
|
113
|
+
1. Evaluate / quantify the probabilities
|
|
114
|
+
2. Give why and
|
|
115
|
+
3. Suggest how to fix it
|
|
116
|
+
- When you have finished your work, summarize in one top-level short sentence the changes you made since the beginning of the conversation. Output as "Commit message".
|
|
117
|
+
|
|
118
|
+
## High-impact files
|
|
119
|
+
|
|
120
|
+
- Do not edit generated files under `.proteum` by hand.
|
|
121
|
+
- Treat `tsconfig*.json`, `env*.yaml`, Prisma-generated files, and symbolic links as high-impact.
|
|
122
|
+
- Edit them only when the task actually requires it, and keep those changes minimal and explicit.
|
|
123
|
+
|
|
124
|
+
If a high-impact file change is not required for the task, leave it alone.
|
|
125
|
+
|
|
126
|
+
## Don't run any of these commands
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
git restore
|
|
130
|
+
git reset
|
|
131
|
+
prisma *
|
|
132
|
+
And any git command in the write mode.
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
# Copy and UX
|
|
136
|
+
|
|
137
|
+
Before making UX/copy decisions, read `docs/PERSONAS.md`, `docs/PRODUCT.md`, `docs/MARKETING.md`.
|
|
138
|
+
When implementing UI, prefer existing Shadcn components or local wrappers around them whenever they can satisfy the requirement cleanly.
|
|
@@ -7,7 +7,7 @@ This file is the source of truth for codex coding style instructions in Proteum-
|
|
|
7
7
|
- **The code should be at the highest level of industry, as the product will be used by GAFAMs and will be maintained by a team of 10 developers.**
|
|
8
8
|
- Write clean, consistent, readable code with a tab size of 4.
|
|
9
9
|
- Keep functions and methods short.
|
|
10
|
-
-
|
|
10
|
+
- Every time possible, create reusable functions and components instead of repeating.
|
|
11
11
|
|
|
12
12
|
## Formatting
|
|
13
13
|
|
|
@@ -16,10 +16,11 @@ This file is the source of truth for codex coding style instructions in Proteum-
|
|
|
16
16
|
- Keep short arrow functions and short returned object literals compact when they are easy to scan.
|
|
17
17
|
- Keep JSX multiline only when it is clearly more readable; otherwise keep short JSX compact.
|
|
18
18
|
- Avoid staircase formatting and unnecessary blank lines inside short callbacks.
|
|
19
|
+
- Keep `Router.page(...)` and `Router.error(...)` registrations in the compact inline-call shape when possible, for example `Router.page('/path', setup, render);`.
|
|
19
20
|
|
|
20
21
|
## File organization
|
|
21
22
|
|
|
22
|
-
- Always keep one class or
|
|
23
|
+
- Always keep one class or React component per file.
|
|
23
24
|
- Prefer a deep tree structure that groups files by business concern instead of long file names.
|
|
24
25
|
- The default `*.ts` / `*.tsx` file is the browser implementation; use `*.ssr.ts` / `*.ssr.tsx` only for SSR-safe fallbacks.
|
|
25
26
|
- When implementing a feature that relies on a curated list of items, keep one canonical catalog or registry file and make all other code import it.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Frontend
|
|
2
|
+
|
|
3
|
+
UI components are defined in `/client/pages` and `/client/components`.
|
|
4
|
+
Client-only catalogs and registries live in `/client/catalogs/**`.
|
|
5
|
+
|
|
6
|
+
## Stack
|
|
7
|
+
|
|
8
|
+
- Typescript strict
|
|
9
|
+
- Preact with SSR
|
|
10
|
+
- Follow the UI stack already used in the touched area.
|
|
11
|
+
- Many Proteum apps use Tailwind and `@/client/components/Motion`, but those are app conventions, not framework guarantees.
|
|
12
|
+
- When the project already exposes Shadcn components or `client/components/ui/**` primitives derived from them, prefer those components for standard UI instead of rebuilding the same primitives locally.
|
|
13
|
+
|
|
14
|
+
Don't add `React` imports just for JSX.
|
|
15
|
+
Use `React` only when a React or Preact API is actually needed.
|
|
16
|
+
Don't use `React.useCallback` unless strictly necessary or already common in the touched area.
|
|
17
|
+
|
|
18
|
+
## Communicate with the server
|
|
19
|
+
|
|
20
|
+
### Pages
|
|
21
|
+
|
|
22
|
+
Pages use `Router.page(...)`.
|
|
23
|
+
|
|
24
|
+
Use `Router.page(path, render)` when there is no SSR setup.
|
|
25
|
+
|
|
26
|
+
Use `Router.page(path, setup, render)` when the page needs SSR config or SSR data:
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
Router.page('/dashboard/example', ({ Feature }) => ({
|
|
30
|
+
_auth: 'USER',
|
|
31
|
+
dataKey: Feature.loadExample(),
|
|
32
|
+
}), ({ dataKey }) => <Page data={dataKey} />);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- Keep the route registration compact instead of exploding the whole call into a staircase layout.
|
|
36
|
+
- `setup` returns one flat object
|
|
37
|
+
- keys like `_auth`, `_layout`, `_priority` are route config
|
|
38
|
+
- all other keys are SSR data fetchers
|
|
39
|
+
- never use `api.fetch(...)` in page files
|
|
40
|
+
|
|
41
|
+
### Components and hooks
|
|
42
|
+
|
|
43
|
+
Components and hooks access controllers through the app client context hook, typically `useContext()` from `@/client/context`:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
const { Auth, Domains } = useContext();
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Then call controller methods directly:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
Auth.signUp(data).then((result) => {
|
|
53
|
+
...
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Async calls
|
|
58
|
+
|
|
59
|
+
- Prefer direct controller calls from the context or page render args
|
|
60
|
+
- Follow the controller naming and hierarchy already used in the touched feature instead of inventing a new one
|
|
61
|
+
- The thrown errors will automatically be displayed to the user, so don't silence them
|
|
62
|
+
- Never depend on legacy `@app` imports on the client
|
|
63
|
+
|
|
64
|
+
## Errors handling
|
|
65
|
+
|
|
66
|
+
Errors caught from controller calls should never be silenced.
|
|
67
|
+
If a catch is needed, rethrow or surface the failure clearly.
|
|
68
|
+
|
|
69
|
+
## Design
|
|
70
|
+
|
|
71
|
+
- Follow the existing design language of the app or feature area.
|
|
72
|
+
- Keep layouts responsive and accessible.
|
|
73
|
+
- Add motion only when the area already uses it or when it materially improves UX.
|
|
74
|
+
- Reuse Shadcn-based shared primitives first for common controls like buttons, inputs, dialogs, dropdowns, tabs, tables, and forms when they cover the requirement.
|
|
75
|
+
- Create custom UI primitives only when the existing Shadcn layer cannot express the interaction or visual requirement cleanly.
|
|
76
|
+
|
|
77
|
+
## Rules
|
|
78
|
+
|
|
79
|
+
- Don't add `React` imports unless the file actually uses a React or Preact API.
|
|
80
|
+
- Don't use any component from `@client/components` unless the codebase already does in that area, except for established shared primitives such as the project's Shadcn-based `client/components/ui/**` layer
|
|
81
|
+
- To create a link / button, always use the `Link` component when the codebase expects navigation links
|
|
82
|
+
|
|
83
|
+
## Keep the code organized
|
|
84
|
+
|
|
85
|
+
- Split big components (more than 1000 lines) into smaller components
|
|
86
|
+
- Always use one component per file
|
|
87
|
+
- Every time possible, load data and define action handlers in the directly concerned component instead of passing everything from the parent
|
|
88
|
+
- Put curated lists, option registries, UI copy catalogs, and similar SSOT data under `/client/catalogs/**`, not inside page or component folders
|
|
89
|
+
|
|
90
|
+
## Split the page by sections via comments
|
|
91
|
+
|
|
92
|
+
Use:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
/*----------------------------------
|
|
96
|
+
- SECTION NAME
|
|
97
|
+
----------------------------------*/
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
File sections:
|
|
101
|
+
- DEPENDENCIES
|
|
102
|
+
- TYPES
|
|
103
|
+
- COMPONENT / PAGE
|
|
104
|
+
|
|
105
|
+
Component / page sections:
|
|
106
|
+
- INIT
|
|
107
|
+
- ACTIONS
|
|
108
|
+
- RENDER
|
|
@@ -7,26 +7,26 @@ Page files are located in `/client/pages/**/*.tsx`.
|
|
|
7
7
|
Use one of these forms:
|
|
8
8
|
|
|
9
9
|
```typescript
|
|
10
|
-
Router.page('/path', ({ request,
|
|
11
|
-
return <Page />;
|
|
12
|
-
});
|
|
10
|
+
Router.page('/path', ({ request, Feature }) => <Page />);
|
|
13
11
|
```
|
|
14
12
|
|
|
15
13
|
```typescript
|
|
16
|
-
Router.page('/path', ({
|
|
14
|
+
Router.page('/path', ({ Feature }) => ({
|
|
17
15
|
_auth: 'USER',
|
|
18
|
-
dataKey:
|
|
19
|
-
}), ({ request, dataKey,
|
|
20
|
-
return <Page data={dataKey} />;
|
|
21
|
-
});
|
|
16
|
+
dataKey: Feature.loadData({ param1: 'value' }),
|
|
17
|
+
}), ({ request, dataKey, Feature }) => <Page data={dataKey} />);
|
|
22
18
|
```
|
|
23
19
|
|
|
20
|
+
- Keep the `Router.page(...)` call itself compact instead of expanding each argument onto its own outer line.
|
|
24
21
|
- `setup` is the second `Router.page` argument
|
|
25
22
|
- `render` is the last `Router.page` argument
|
|
26
23
|
- `setup` receives the page context plus generated controller instances
|
|
27
24
|
- `render` receives the page context, resolved setup data, and generated controller instances
|
|
25
|
+
- Prefer the generated page arguments or the app context hook; do not import `.proteum` files directly.
|
|
28
26
|
- Never use `api.fetch(...)` in page files
|
|
29
27
|
- Never import client service values from `@app`
|
|
28
|
+
- Compose page UI from shared Shadcn-based components when the project already provides them instead of redefining common controls inline in the page file
|
|
29
|
+
- Keep page-local curated copy, option sets, and registries in `/client/catalogs/**`; do not create `catalogs/` folders under `client/pages/**`
|
|
30
30
|
|
|
31
31
|
## Typings
|
|
32
32
|
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Use `/server/routes/**` only for explicit custom routes that should not be generated from controllers.
|
|
4
4
|
|
|
5
|
-
- Callable app APIs belong in
|
|
5
|
+
- Callable app APIs belong in `/server/controllers/**/*.ts`
|
|
6
6
|
- `/server/routes/**` is for manual `Router.get/post/...` routes, redirects, resources, OAuth callbacks, etc.
|
|
7
|
+
- If a route needs a curated list or registry, keep server-only data in `/server/catalogs/**` and shared data in `/common/catalogs/**`
|
|
7
8
|
|
|
8
9
|
## Generate absolute urls
|
|
9
10
|
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Server Services
|
|
2
|
+
|
|
3
|
+
Stack:
|
|
4
|
+
- Typescript with strict mode
|
|
5
|
+
- NodeJS
|
|
6
|
+
- Prisma 7 ORM
|
|
7
|
+
|
|
8
|
+
## Catalog placement
|
|
9
|
+
|
|
10
|
+
- Server-only catalogs and registries live in `/server/catalogs/**`
|
|
11
|
+
- Shared cross-runtime catalogs live in `/common/catalogs/**`
|
|
12
|
+
- Do not create nested `catalogs/` folders under `/server/services/**`
|
|
13
|
+
- Services should import curated lists from those root catalog locations instead of redefining them locally
|
|
14
|
+
|
|
15
|
+
## 1. Create the service file in `/server/services/<service name>/index.ts`
|
|
16
|
+
|
|
17
|
+
Template:
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
/*----------------------------------
|
|
21
|
+
- DEPENDANCE
|
|
22
|
+
----------------------------------*/
|
|
23
|
+
|
|
24
|
+
// Core libs
|
|
25
|
+
import Service from '@server/app/service';
|
|
26
|
+
|
|
27
|
+
/*----------------------------------
|
|
28
|
+
- TYPES
|
|
29
|
+
----------------------------------*/
|
|
30
|
+
|
|
31
|
+
export type Config = Record<string, never>;
|
|
32
|
+
|
|
33
|
+
/*----------------------------------
|
|
34
|
+
- SERVICE
|
|
35
|
+
----------------------------------*/
|
|
36
|
+
|
|
37
|
+
export default class ServiceName extends Service<Config, {}, AppType, ParentServiceType> {
|
|
38
|
+
|
|
39
|
+
public async methodName(data: { param1: string }) {
|
|
40
|
+
return this.services.OtherService.otherMethod(data);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Replace `AppType` and `ParentServiceType` with the local app types used by neighboring services.
|
|
46
|
+
If the service receives config from a project config file, replace `Config` with the real config shape and expose a typed
|
|
47
|
+
config export with `Services.config(ServiceName, { ... })` from `server/config/*.ts`.
|
|
48
|
+
|
|
49
|
+
## 2. Create the controller file in `/server/controllers/...`
|
|
50
|
+
|
|
51
|
+
Template:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import Controller, { schema } from '@server/app/controller';
|
|
55
|
+
|
|
56
|
+
const MethodInput = schema.object({
|
|
57
|
+
param1: schema.string(),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export default class ServiceNameController extends Controller<AppType> {
|
|
61
|
+
|
|
62
|
+
public async methodName() {
|
|
63
|
+
const input = this.input(MethodInput);
|
|
64
|
+
const currentUser = this.request.auth.check('USER', null);
|
|
65
|
+
|
|
66
|
+
return this.services.ServiceName.methodName({
|
|
67
|
+
...input,
|
|
68
|
+
currentUser,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Replace `AppType` with the local app type if the surrounding controllers use a generic.
|
|
75
|
+
Place the controller under the path that should drive the public API shape, for example `/server/controllers/ServiceName.ts` or `/server/controllers/ServiceName/subFeature.ts`.
|
|
76
|
+
|
|
77
|
+
Rules:
|
|
78
|
+
- Only files under `/server/controllers/**/*.ts` are indexed as callable API endpoints
|
|
79
|
+
- Route path is derived from the controller file path and the method name
|
|
80
|
+
- `this.input(schema)` is the only validation entrypoint
|
|
81
|
+
- Call `this.input(...)` at most once per controller method
|
|
82
|
+
- Request-scoped state exists only on `this.request`
|
|
83
|
+
- Keep controllers thin and push business logic into services
|
|
84
|
+
- Extract auth and request-derived values in the controller and pass explicit typed arguments into services
|
|
85
|
+
|
|
86
|
+
## 3. Create the service metas file in `/server/services/<service name>/service.json`
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"id": "<AppIdentifier>/ServiceName",
|
|
91
|
+
"name": "ServiceName",
|
|
92
|
+
"parent": "app",
|
|
93
|
+
"dependences": []
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Use the same id namespace and naming convention as neighboring services in the project.
|
|
98
|
+
|
|
99
|
+
## 4. Add a typed config export in `/server/config/*.ts` and instantiate the service in `/server/index.ts`
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// server/config/feature.ts
|
|
103
|
+
import { Services } from '@server/app';
|
|
104
|
+
import ServiceName from '@/server/services/ServiceName';
|
|
105
|
+
|
|
106
|
+
export const serviceNameConfig = Services.config(ServiceName, {});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
// server/index.ts
|
|
111
|
+
import { Application } from '@server/app';
|
|
112
|
+
import ServiceName from '@/server/services/ServiceName';
|
|
113
|
+
import * as featureConfig from '@/server/config/feature';
|
|
114
|
+
|
|
115
|
+
export default class MyApp extends Application {
|
|
116
|
+
public ServiceName = new ServiceName(this, featureConfig.serviceNameConfig, this);
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Match the existing config-grouping and namespace-import convention in the project instead of inventing a new bootstrap shape.
|
|
121
|
+
|
|
122
|
+
## 5. Keep classes clean
|
|
123
|
+
|
|
124
|
+
If the class grows too large, split business concerns into subservices.
|
|
125
|
+
|
|
126
|
+
## 6. Use request-aware features only in controllers
|
|
127
|
+
|
|
128
|
+
Use:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
const { auth, request, user, response } = this.request;
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
- Never import runtime request state from `@request`
|
|
135
|
+
- Never access request-scoped state inside normal service methods
|
|
136
|
+
- If a service needs user identity, locale, cookies, or another request-derived value, compute it in the controller and pass only that value
|
|
137
|
+
|
|
138
|
+
## 7. Fetch and return data from the database
|
|
139
|
+
|
|
140
|
+
Use runtime models through `this.models`:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
const users = await this.models.user.findMany({
|
|
144
|
+
select: {
|
|
145
|
+
id: true,
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Use prisma typings through `@models/types` only:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
import type * as Models from '@models/types';
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Rules:
|
|
157
|
+
- Never edit prisma files, except the schema
|
|
158
|
+
- Never use runtime `@models` imports
|
|
159
|
+
- If you need generated runtime Prisma enums or helpers already emitted by Proteum, follow the local `@generated/server/models` import pattern
|
|
160
|
+
- In all queries and joins, always specify what fields to select
|
|
161
|
+
|
|
162
|
+
## DTO and typing rules
|
|
163
|
+
|
|
164
|
+
- Prefer inferred return types:
|
|
165
|
+
`export type TResult = Awaited<ReturnType<MyService["MethodName"]>>;`
|
|
166
|
+
- Never create manual DTO types when the exact return type can be inferred
|
|
167
|
+
|
|
168
|
+
## Errors handling
|
|
169
|
+
|
|
170
|
+
Never silence caught errors. Throw `Anomaly` with enough detail and the original error when needed.
|
|
@@ -6,3 +6,4 @@
|
|
|
6
6
|
- Add `data-testid` where needed
|
|
7
7
|
- Keep test files clean, organized and structured
|
|
8
8
|
- Test the current controller/page runtime model, not legacy `@Route` or `api.fetch` behavior
|
|
9
|
+
- Reuse root catalog files from `/client/catalogs/**`, `/server/catalogs/**`, or `/common/catalogs/**` instead of duplicating catalog constants inside tests
|
package/cli/app/config.ts
CHANGED
|
@@ -16,6 +16,7 @@ import yaml from 'yaml';
|
|
|
16
16
|
|
|
17
17
|
// Types
|
|
18
18
|
import type { TEnvConfig } from '../../server/app/container/config';
|
|
19
|
+
import { logVerbose } from '../runtime/verbose';
|
|
19
20
|
|
|
20
21
|
/*----------------------------------
|
|
21
22
|
- LOADE
|
|
@@ -28,7 +29,7 @@ export default class ConfigParser {
|
|
|
28
29
|
) {}
|
|
29
30
|
|
|
30
31
|
private loadYaml(filepath: string) {
|
|
31
|
-
|
|
32
|
+
logVerbose(`Loading config ${filepath}`);
|
|
32
33
|
const rawConfig = fs.readFileSync(filepath, 'utf-8');
|
|
33
34
|
return yaml.parse(rawConfig);
|
|
34
35
|
}
|
|
@@ -36,7 +37,7 @@ export default class ConfigParser {
|
|
|
36
37
|
public env(): TEnvConfig {
|
|
37
38
|
// We assume that when we run 5htp dev, we're in local
|
|
38
39
|
// Otherwise, we're in production environment (docker)
|
|
39
|
-
|
|
40
|
+
logVerbose('[app] Using environment:', process.env.NODE_ENV);
|
|
40
41
|
const envFileName = this.appDir + '/env.yaml';
|
|
41
42
|
const envFile = this.loadYaml(envFileName);
|
|
42
43
|
return {
|
package/cli/app/index.ts
CHANGED
|
@@ -20,12 +20,6 @@ import type { TEnvConfig } from '../../server/app/container/config';
|
|
|
20
20
|
|
|
21
21
|
export type TAppSide = 'server' | 'client';
|
|
22
22
|
|
|
23
|
-
type TServiceSetup = { id: string; name: string; config: {}; subservices: TServiceSubservices; type: 'service.setup' };
|
|
24
|
-
|
|
25
|
-
type TServiceRef = { refTo: string; type: 'service.ref' };
|
|
26
|
-
|
|
27
|
-
type TServiceSubservices = { [key: string]: TServiceSetup | TServiceRef };
|
|
28
|
-
|
|
29
23
|
const parseRouterPortOverride = (rawPort: string | boolean | string[] | undefined): number | undefined => {
|
|
30
24
|
if (rawPort === undefined || rawPort === '') return undefined;
|
|
31
25
|
|
|
@@ -65,13 +59,14 @@ export class App {
|
|
|
65
59
|
public: path.join(cli.paths.appRoot, 'public'),
|
|
66
60
|
pages: path.join(cli.paths.appRoot, 'client', 'pages'),
|
|
67
61
|
cache: path.join(cli.paths.appRoot, '.cache'),
|
|
62
|
+
proteum: path.join(cli.paths.appRoot, '.proteum'),
|
|
68
63
|
|
|
69
|
-
client: { generated: path.join(cli.paths.appRoot, '
|
|
64
|
+
client: { generated: path.join(cli.paths.appRoot, '.proteum', 'client') },
|
|
70
65
|
server: {
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
entry: path.join(cli.paths.appRoot, 'server', 'index.ts'),
|
|
67
|
+
generated: path.join(cli.paths.appRoot, '.proteum', 'server'),
|
|
73
68
|
},
|
|
74
|
-
common: { generated: path.join(cli.paths.appRoot, '
|
|
69
|
+
common: { generated: path.join(cli.paths.appRoot, '.proteum', 'common') },
|
|
75
70
|
|
|
76
71
|
withAlias: (filename: string, side: TAppSide) => this.aliases[side].apply(filename),
|
|
77
72
|
|
|
@@ -122,63 +117,8 @@ export class App {
|
|
|
122
117
|
return fs.readJSONSync(this.paths.root + '/package.json');
|
|
123
118
|
}
|
|
124
119
|
|
|
125
|
-
/*----------------------------------
|
|
126
|
-
- WARMUP (Services awareness)
|
|
127
|
-
----------------------------------*/
|
|
128
|
-
|
|
129
|
-
public registered = {};
|
|
130
|
-
|
|
131
|
-
public use(referenceName: string): TServiceRef {
|
|
132
|
-
// We don't check because all service are not regstered when we register subservices
|
|
133
|
-
/*if (this.registered[referenceName] === undefined) {
|
|
134
|
-
throw new Error(`Service ${referenceName} is not registered`);
|
|
135
|
-
}*/
|
|
136
|
-
|
|
137
|
-
return { refTo: referenceName, type: 'service.ref' };
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
public setup(
|
|
141
|
-
...args:
|
|
142
|
-
| [
|
|
143
|
-
// { user: app.setup('Core/User') }
|
|
144
|
-
servicePath: string,
|
|
145
|
-
serviceConfig?: {},
|
|
146
|
-
]
|
|
147
|
-
| [
|
|
148
|
-
// app.setup('User', 'Core/User')
|
|
149
|
-
serviceName: string,
|
|
150
|
-
servicePath: string,
|
|
151
|
-
serviceConfig?: {},
|
|
152
|
-
]
|
|
153
|
-
): TServiceSetup {
|
|
154
|
-
// Registration to app root
|
|
155
|
-
if (typeof args[1] === 'string') {
|
|
156
|
-
const [name, id, config] = args;
|
|
157
|
-
|
|
158
|
-
const service = { id, name, config, type: 'service.setup' } as TServiceSetup;
|
|
159
|
-
|
|
160
|
-
this.registered[name] = service;
|
|
161
|
-
|
|
162
|
-
return service;
|
|
163
|
-
|
|
164
|
-
// Scoped to a parent service
|
|
165
|
-
} else {
|
|
166
|
-
const [id, config] = args;
|
|
167
|
-
|
|
168
|
-
const service = { id, config, type: 'service.setup' } as TServiceSetup;
|
|
169
|
-
|
|
170
|
-
return service;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
120
|
public async warmup() {
|
|
175
|
-
|
|
176
|
-
const configDir = path.resolve(cli.paths.appRoot, 'server', 'config');
|
|
177
|
-
const configFiles = fs.readdirSync(configDir);
|
|
178
|
-
for (const configFile of configFiles) {
|
|
179
|
-
console.log('Loading config file:', configFile);
|
|
180
|
-
require(path.resolve(configDir, configFile));
|
|
181
|
-
}
|
|
121
|
+
return Promise.resolve();
|
|
182
122
|
}
|
|
183
123
|
}
|
|
184
124
|
|
package/cli/bin.js
CHANGED
|
@@ -21,7 +21,7 @@ const path = require('path');
|
|
|
21
21
|
if (!process.env.TS_NODE_IGNORE) {
|
|
22
22
|
process.env.TS_NODE_IGNORE = [
|
|
23
23
|
// Default ts-node ignore rule (works when deps are nested under `../node_modules/...`)
|
|
24
|
-
'(node_modules\/(?!proteum\/))|(\.generated\/)|(\.cache\/)',
|
|
24
|
+
'(node_modules\/(?!proteum\/))|(\.generated\/)|(\.cache\/)|(\.proteum\/)',
|
|
25
25
|
// Extra rule for deps hoisted next to Proteum (ex: `../../tailwindcss/...`)
|
|
26
26
|
'^\\.\\./\\.\\./(?!\\./|\\.\\./)[^/]+/',
|
|
27
27
|
].join(',');
|
|
@@ -32,4 +32,9 @@ process.env.TS_NODE_TRANSPILE_ONLY = '1';
|
|
|
32
32
|
|
|
33
33
|
require('ts-node/register/transpile-only');
|
|
34
34
|
|
|
35
|
-
require('./index.ts');
|
|
35
|
+
const { runCli } = require('./index.ts');
|
|
36
|
+
|
|
37
|
+
Promise.resolve(runCli()).catch((error) => {
|
|
38
|
+
console.error(error);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
});
|