proteum 2.0.0 → 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
@@ -4,7 +4,7 @@
4
4
 
5
5
  // Core
6
6
  import type { Application } from '@server/app/index';
7
- import Service, { AnyService, TRegisteredServicesIndex, TServiceArgs } from '@server/app/service';
7
+ import Service, { AnyService, TServiceArgs } from '@server/app/service';
8
8
 
9
9
  // Specific
10
10
  import type Driver from './driver';
@@ -9,7 +9,7 @@ export type TWithStats = { $table: string; $key: string } & Record<string, strin
9
9
 
10
10
  export type TSubset = (...args: any[]) => Record<string, unknown> & { withStats?: TWithStats };
11
11
 
12
- export type Transform<S extends TSubset, R, RT> = (row: R) => RT;
12
+ export type Transform<R, RT> = (row: R) => RT;
13
13
 
14
14
  export default class Facet<
15
15
  D extends TDelegate<R>,
@@ -21,7 +21,7 @@ export default class Facet<
21
21
  private readonly prisma: PrismaClient,
22
22
  private readonly delegate: D,
23
23
  private readonly subset: S,
24
- private readonly transform?: Transform<S, R, RT>,
24
+ private readonly transform?: Transform<R, RT>,
25
25
  ) {}
26
26
 
27
27
  public async findMany(...args: Parameters<S>): Promise<RT[]> {
@@ -3,17 +3,18 @@
3
3
  ----------------------------------*/
4
4
 
5
5
  // Npm
6
- import { PrismaClient } from '@/var/prisma';
6
+ import dotenv from 'dotenv';
7
+ import { PrismaClient } from '@generated/server/models';
7
8
  import mysql from 'mysql2/promise';
8
- import type PrismaClientType from '@prisma/client';
9
9
  const safeStringify = require('fast-safe-stringify'); // remplace les références circulairs par un [Circular]
10
10
 
11
11
  // Core
12
12
  import type { Application } from '@server/app/index';
13
- import Service from '@server/app/service';
13
+ import Service, { TServiceArgs } from '@server/app/service';
14
14
 
15
15
  // Specific
16
16
  import Facet, { TDelegate, TSubset, Transform } from './Facet';
17
+ import { createMariaDbAdapter } from './mariadb';
17
18
  import { NotFound } from '@common/errors';
18
19
 
19
20
  /*----------------------------------
@@ -37,7 +38,23 @@ export type Services = {};
37
38
  ----------------------------------*/
38
39
 
39
40
  export default class ModelsManager extends Service<Config, Hooks, Application, Application> {
40
- public client = new PrismaClient() as PrismaClientType.PrismaClient;
41
+ public client: PrismaClient;
42
+
43
+ public constructor(...args: TServiceArgs<ModelsManager>) {
44
+ super(...args);
45
+
46
+ dotenv.config();
47
+
48
+ const databaseUrl = process.env.DATABASE_URL;
49
+ if (!databaseUrl)
50
+ throw new Error(
51
+ 'DATABASE_URL is required before starting the Models service. Prisma 7 no longer auto-loads runtime env files.',
52
+ );
53
+
54
+ this.client = new PrismaClient({
55
+ adapter: createMariaDbAdapter(databaseUrl),
56
+ });
57
+ }
41
58
 
42
59
  public async ready() {
43
60
  await this.client.$executeRaw`SET time_zone = '+00:00'`;
@@ -47,7 +64,7 @@ export default class ModelsManager extends Service<Config, Hooks, Application, A
47
64
  await this.client.$disconnect();
48
65
  }
49
66
 
50
- public Facet<D extends TDelegate<R>, S extends TSubset, R, RT>(...args: [D, S, Transform<S, R, RT>]) {
67
+ public Facet<D extends TDelegate<R>, S extends TSubset, R, RT = R>(...args: [D, S, Transform<R, RT>?]) {
51
68
  return new Facet(this.client, ...args);
52
69
  }
53
70
 
@@ -0,0 +1,47 @@
1
+ import { PrismaMariaDb } from '@prisma/adapter-mariadb';
2
+
3
+ const defaultConnectTimeout = 5_000;
4
+ const defaultIdleTimeout = 300;
5
+ const defaultPort = 3306;
6
+
7
+ const parseInteger = (value: string | null | undefined) => {
8
+ if (!value) return undefined;
9
+
10
+ const parsed = Number(value);
11
+ if (!Number.isFinite(parsed) || parsed <= 0) return undefined;
12
+
13
+ return parsed;
14
+ };
15
+
16
+ const decodeUrlSegment = (value: string) => {
17
+ if (!value) return value;
18
+
19
+ return decodeURIComponent(value);
20
+ };
21
+
22
+ export const createMariaDbAdapter = (databaseUrl: string) => {
23
+ const url = new URL(databaseUrl);
24
+
25
+ if (url.protocol !== 'mysql:' && url.protocol !== 'mariadb:')
26
+ throw new Error(
27
+ `Unsupported DATABASE_URL protocol "${url.protocol}". Prisma 7 Proteum support expects mysql:// or mariadb://.`,
28
+ );
29
+
30
+ const database = url.pathname.replace(/^\/+/, '');
31
+ if (!database) throw new Error('DATABASE_URL must include a database name.');
32
+
33
+ const connectionLimit = parseInteger(url.searchParams.get('connection_limit'));
34
+ const connectTimeoutSeconds = parseInteger(url.searchParams.get('connect_timeout'));
35
+ const idleTimeoutSeconds = parseInteger(url.searchParams.get('max_idle_connection_lifetime'));
36
+
37
+ return new PrismaMariaDb({
38
+ host: url.hostname,
39
+ port: parseInteger(url.port) ?? defaultPort,
40
+ user: decodeUrlSegment(url.username),
41
+ password: decodeUrlSegment(url.password),
42
+ database: decodeUrlSegment(database),
43
+ connectTimeout: connectTimeoutSeconds ? connectTimeoutSeconds * 1_000 : defaultConnectTimeout,
44
+ idleTimeout: idleTimeoutSeconds ?? defaultIdleTimeout,
45
+ ...(connectionLimit !== undefined ? { connectionLimit } : {}),
46
+ });
47
+ };
@@ -22,6 +22,7 @@ import * as csp from 'express-csp-header';
22
22
  // Core
23
23
  import Container from '@server/app/container';
24
24
  import type { TServerRouter } from '..';
25
+ import { serverHotReloadMessageType } from '@common/dev/serverHotReload';
25
26
 
26
27
  // Middlewaees (core)
27
28
  import { isMutipart, MiddlewareFormData } from './multipart';
@@ -210,8 +211,15 @@ export default class HttpServer<TRouter extends TServerRouter = TServerRouter> {
210
211
  /*----------------------------------
211
212
  - BOOT SERVICES
212
213
  ----------------------------------*/
213
- console.info('Lancement du serveur web');
214
214
  this.http.listen(this.config.port, () => {
215
+ if (__DEV__ && typeof process.send === 'function') {
216
+ process.send({
217
+ type: serverHotReloadMessageType.ready,
218
+ publicUrl: this.publicUrl,
219
+ });
220
+ return;
221
+ }
222
+
215
223
  console.info(`Web server ready on ${this.publicUrl}`);
216
224
  });
217
225
  }
@@ -56,7 +56,7 @@ import { loadGeneratedRuntimeBundle } from './generatedRuntime';
56
56
  export { type AnyRouterService, default as RouterService } from './service';
57
57
  export { default as RequestService } from './request/service';
58
58
  export type { default as Request, UploadedFile } from './request';
59
- export type { default as Response, TRouterContext } from './response';
59
+ export type { default as Response, TRouterContext, TRouterContextServices } from './response';
60
60
  export type { TRoute, TAnyRoute } from '@common/router';
61
61
 
62
62
  export type TApiRegisterArgs<TRouter extends TServerRouter> =
@@ -145,7 +145,13 @@ export default class ServerRouter<
145
145
  extends Service<TConfig, Hooks, TApplication, TApplication>
146
146
  implements BaseRouter
147
147
  {
148
- public disks = this.use<DisksManager<any, any, TApplication>>('Core/Disks', { optional: true });
148
+ public get disks() {
149
+ const { Disks } = this.app as TApplication & {
150
+ Disks?: DisksManager<any, any, TApplication>;
151
+ };
152
+
153
+ return Disks;
154
+ }
149
155
 
150
156
  // Services
151
157
  public http: HTTP;
@@ -490,7 +496,7 @@ export default class ServerRouter<
490
496
  private loadGeneratedControllerDefinitions() {
491
497
  return (
492
498
  loadGeneratedRuntimeBundle<TGeneratedControllerDefinition[]>('controllers') ||
493
- require('@/server/.generated/controllers').default ||
499
+ require('@generated/server/controllers').default ||
494
500
  []
495
501
  );
496
502
  }
@@ -498,7 +504,7 @@ export default class ServerRouter<
498
504
  private loadGeneratedRouteModules() {
499
505
  return (
500
506
  loadGeneratedRuntimeBundle<TGeneratedRouteModule[]>('routes') ||
501
- require('@/server/.generated/routes').default ||
507
+ require('@generated/server/routes').default ||
502
508
  []
503
509
  );
504
510
  }
@@ -19,8 +19,8 @@ import { NotFound, Forbidden, Anomaly } from '@common/errors';
19
19
  import BaseResponse, { TResponseData } from '@common/router/response';
20
20
  import { splitRouteSetupResult } from '@common/router/pageSetup';
21
21
  import Page from './page';
22
- import createControllers from '@/common/.generated/controllers';
23
- import type { TControllers } from '@/common/.generated/controllers';
22
+ import createControllers from '@generated/common/controllers';
23
+ import type { TControllers } from '@generated/common/controllers';
24
24
 
25
25
  // To move into a new npm module: json-mask
26
26
  import jsonMask from './mask';
@@ -41,10 +41,27 @@ export type TBasicSSrData = {
41
41
  domains: TDomainsList;
42
42
  };
43
43
 
44
+ type TServerRouterApplication<TRouter extends TServerRouter> =
45
+ TRouter extends ServerRouter<infer TApplication, any, any> ? TApplication : never;
46
+
47
+ type TServerRouterPlugins<TRouter extends TServerRouter> =
48
+ TRouter extends ServerRouter<any, any, infer TConfig>
49
+ ? TConfig extends { plugins: infer TPlugins }
50
+ ? TPlugins
51
+ : {}
52
+ : {};
53
+
54
+ type TServerRouterCustomContext<TRouter extends TServerRouter> =
55
+ TRouter extends ServerRouter<any, any, infer TConfig>
56
+ ? TConfig extends { context: (...args: any[]) => infer TContext }
57
+ ? TContext
58
+ : {}
59
+ : {};
60
+
44
61
  export type TRouterContext<TRouter extends TServerRouter> =
45
62
  // Request context
46
63
  {
47
- app: TRouter['app'];
64
+ app: TServerRouterApplication<TRouter>;
48
65
  context: TRouterContext<TRouter>; // = this
49
66
  request: ServerRequest<TRouter>;
50
67
  api: ServerRequest<TRouter>['api'];
@@ -55,9 +72,12 @@ export type TRouterContext<TRouter extends TServerRouter> =
55
72
  Router: TRouter;
56
73
  } & TRouterContextServices<TRouter> &
57
74
  TControllers &
58
- TRouterRequestContext<TRouter>;
75
+ TServerRouterCustomContext<TRouter>;
59
76
 
60
- export type TRouterContextServices<TRouter extends TServerRouter, TPlugins = TRouter['config']['plugins']> =
77
+ export type TRouterContextServices<
78
+ TRouter extends TServerRouter,
79
+ TPlugins extends object = TServerRouterPlugins<TRouter>,
80
+ > =
61
81
  // Custom context via servuces
62
82
  // For each roiuter service, return the request service (returned by roiuterService.requestService() )
63
83
  {
@@ -66,7 +86,7 @@ export type TRouterContextServices<TRouter extends TServerRouter, TPlugins = TRo
66
86
  : TPlugins[serviceName];
67
87
  };
68
88
 
69
- export type TRouterRequestContext<TRouter extends TServerRouter> = ReturnType<TRouter['config']['context']>;
89
+ export type TRouterRequestContext<TRouter extends TServerRouter> = TServerRouterCustomContext<TRouter>;
70
90
 
71
91
  /*----------------------------------
72
92
  - CLASSE
@@ -0,0 +1,51 @@
1
+ import type { TRouteOptions } from '@common/router';
2
+ import { AuthRequired, UpgradeRequired } from '@common/errors';
3
+ import type {
4
+ TAuthCheckConditions,
5
+ TAuthRulesFactory,
6
+ TAuthRuleNoInput,
7
+ TAuthTrackingContext,
8
+ TBasicUser,
9
+ TUserRole,
10
+ } from '@server/services/auth';
11
+ import type { Request as ServerRequest, TAnyRouter } from '@server/services/router';
12
+
13
+ type Assert<T extends true> = T;
14
+
15
+ type Equals<TLeft, TRight> = (<T>() => T extends TLeft ? 1 : 2) extends <T>() => T extends TRight ? 1 : 2
16
+ ? (<T>() => T extends TRight ? 1 : 2) extends <T>() => T extends TLeft ? 1 : 2
17
+ ? true
18
+ : false
19
+ : false;
20
+
21
+ declare global {
22
+ interface ProteumAuthRuleCatalog {
23
+ isPaid: TAuthRuleNoInput;
24
+ hasFeature: 'radar' | 'api';
25
+ matchesPlanWindow: [string, 'modal' | 'page'];
26
+ }
27
+ }
28
+
29
+ type TResolvedConditions = TAuthCheckConditions;
30
+
31
+ type _AssertRoleCondition = Assert<Equals<TResolvedConditions['role'], TUserRole | undefined>>;
32
+ type _AssertZeroArgCondition = Assert<Equals<TResolvedConditions['isPaid'], true | undefined>>;
33
+ type _AssertSingleArgCondition = Assert<Equals<TResolvedConditions['hasFeature'], 'radar' | 'api' | undefined>>;
34
+ type _AssertTupleArgCondition = Assert<
35
+ Equals<TResolvedConditions['matchesPlanWindow'], [string, 'modal' | 'page'] | undefined>
36
+ >;
37
+ type _AssertRouteAuthOptions = Assert<
38
+ Equals<TRouteOptions['auth'], TAuthCheckConditions | TUserRole | boolean | null | undefined>
39
+ >;
40
+ type _AssertRouteAuthTracking = Assert<Equals<TRouteOptions['authTracking'], TAuthTrackingContext | undefined>>;
41
+
42
+ type TExampleRulesFactory = TAuthRulesFactory<TBasicUser, ServerRequest<TAnyRouter>>;
43
+ type _AssertRulesFactoryArgs = Assert<
44
+ Equals<Parameters<TExampleRulesFactory>, [user: TBasicUser, tracking: TAuthTrackingContext, request: ServerRequest<TAnyRouter>]>
45
+ >;
46
+
47
+ const authRequiredFromRule = new AuthRequired('Please login to continue', 'auth', 'view');
48
+ const upgradeRequiredFromRule = new UpgradeRequired('Please upgrade to continue', 'radar', 'view');
49
+
50
+ type _AssertInjectedAuthRequired = Assert<typeof authRequiredFromRule extends Error ? true : false>;
51
+ type _AssertInjectedUpgradeRequired = Assert<typeof upgradeRequiredFromRule extends Error ? true : false>;
@@ -0,0 +1,55 @@
1
+ import type { Application } from '@server/app';
2
+ import Controller from '@server/app/controller';
3
+ import type { TServerRouter } from '@server/services/router';
4
+ import type { TServiceModelsClient, TServiceRequestContext } from '@server/app/service';
5
+
6
+ type Assert<T extends true> = T;
7
+
8
+ type Equals<TLeft, TRight> = (<T>() => T extends TLeft ? 1 : 2) extends <T>() => T extends TRight ? 1 : 2
9
+ ? (<T>() => T extends TRight ? 1 : 2) extends <T>() => T extends TLeft ? 1 : 2
10
+ ? true
11
+ : false
12
+ : false;
13
+
14
+ type TTypedAuthRequestService = {
15
+ check(): 'typed-user';
16
+ };
17
+
18
+ type TTestRouter = TServerRouter & {
19
+ app: TTestApp;
20
+ config: TServerRouter['config'] & {
21
+ plugins: TServerRouter['config']['plugins'] & {
22
+ auth: {
23
+ requestService(request: any): TTypedAuthRequestService;
24
+ };
25
+ };
26
+ context: () => {};
27
+ };
28
+ };
29
+
30
+ interface TTestApp extends Application {
31
+ Router: TTestRouter;
32
+ Models: {
33
+ client: {
34
+ post: {
35
+ findMany(): 'posts';
36
+ };
37
+ };
38
+ };
39
+ }
40
+
41
+ class TypedRequestController extends Controller<TTestApp> {
42
+ public useAuth() {
43
+ return this.request.auth.check();
44
+ }
45
+ }
46
+
47
+ type TRequestAuth = TypedRequestController['request']['auth'];
48
+ type TAuthCheckResult = ReturnType<TypedRequestController['useAuth']>;
49
+ type TServiceContextAuth = TServiceRequestContext<TTestApp>['auth'];
50
+ type TModelsClient = TServiceModelsClient<TTestApp>;
51
+
52
+ type _AssertTypedRequestService = Assert<Equals<TRequestAuth['check'], TTypedAuthRequestService['check']>>;
53
+ type _AssertTypedAuthCheckResult = Assert<Equals<TAuthCheckResult, 'typed-user'>>;
54
+ type _AssertTypedServiceRequestContext = Assert<Equals<TServiceContextAuth['check'], TTypedAuthRequestService['check']>>;
55
+ type _AssertTypedModelsClient = Assert<Equals<ReturnType<TModelsClient['post']['findMany']>, 'posts'>>;
@@ -0,0 +1,39 @@
1
+ import { Services, type ServiceConfig } from '@server/app';
2
+ import Service from '@server/app/service';
3
+ import Router from '@server/services/router';
4
+
5
+ type Assert<T extends true> = T;
6
+
7
+ type Equals<TLeft, TRight> = (<T>() => T extends TLeft ? 1 : 2) extends <T>() => T extends TRight ? 1 : 2
8
+ ? (<T>() => T extends TRight ? 1 : 2) extends <T>() => T extends TLeft ? 1 : 2
9
+ ? true
10
+ : false
11
+ : false;
12
+
13
+ class ExampleService extends Service<
14
+ {
15
+ enabled: boolean;
16
+ nested: { count: number };
17
+ },
18
+ {}
19
+ > {}
20
+
21
+ const exampleConfig = Services.config(ExampleService, {
22
+ enabled: true,
23
+ nested: { count: 1 },
24
+ });
25
+
26
+ type RouterBaseConfig = Omit<ServiceConfig<typeof Router>, 'plugins'>;
27
+
28
+ type _AssertLiteralEnabled = Assert<Equals<typeof exampleConfig.enabled, true>>;
29
+ type _AssertLiteralNestedCount = Assert<Equals<typeof exampleConfig.nested.count, 1>>;
30
+ type _AssertRouterBaseConfigExtendsObject = Assert<RouterBaseConfig extends object ? true : false>;
31
+
32
+ // @ts-expect-error Services.config should reject unknown top-level config keys.
33
+ Services.config(ExampleService, { enabled: true, nested: { count: 1 }, extra: true });
34
+
35
+ // @ts-expect-error Services.config should reject unknown nested config keys.
36
+ Services.config(ExampleService, { enabled: true, nested: { count: 1, extra: true } });
37
+
38
+ // @ts-expect-error Services.config should reject invalid property types.
39
+ Services.config(ExampleService, { enabled: 'yes', nested: { count: 1 } });
@@ -1,95 +0,0 @@
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
- `/components`: reusable components
8
- `/pages`: page route files and page-local UI
9
- `/hooks`
10
- `/common`: shared functions, constants and typings
11
- `/server`: backend
12
- `/config`: service configuration
13
- `/services`: backend services and controllers
14
- `/routes`: explicit non-controller routes
15
- `/lib`: helper functions
16
- `/tests`
17
-
18
- # Coding style
19
-
20
- See `CODING_STYLE.md`.
21
- This file is the source of truth for formatting, section-comment structure, and general coding style.
22
-
23
- # Files organization
24
-
25
- - Always keep one class / react component per file
26
- - Prefer a deep tree structure that groups files by business concern instead of long file names
27
- - The default `*.ts` / `*.tsx` file is the browser implementation; use `*.ssr.ts` / `*.ssr.tsx` only for SSR-safe fallbacks
28
-
29
- ## Centralize feature catalogs (Single Source of Truth)
30
-
31
- 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.
32
-
33
- ## Runtime access rules
34
-
35
- - `@models/types`: Prisma typings only. Can be imported anywhere.
36
- - Never use runtime value imports from `@request` or `@models`.
37
- - Never expose request-scoped state through imports.
38
-
39
- ## Client runtime access
40
-
41
- - Page route files use `Router.page(...)`.
42
- - `Router.page(path, render)` for pages without SSR setup.
43
- - `Router.page(path, setup, render)` for pages with SSR config/data.
44
- - `setup` receives the normal page context plus the generated controller tree spread into it.
45
- - `render` receives the normal page context plus the resolved setup data and the same controller tree spread into it.
46
- - Components and hooks use `useContext()` to access controller instances and client runtime services.
47
-
48
- ## Server runtime access
49
-
50
- - Normal business logic lives in `/server/services/**/index.ts` classes that extend `Service`.
51
- - Route entrypoints live in `*.controller.ts` classes that extend `Controller`.
52
- - Only controller files are indexed as callable API endpoints.
53
- - Controller methods validate input with `this.input(schema)` and access request scope through `this.request`.
54
- - Service classes access other services via `this.services` and prisma models via `this.models`.
55
- - Never use request-scoped state directly inside normal service methods.
56
-
57
- # Agent behavior
58
-
59
- **Make sure the code you generate integrates perfectly with the current codebase by avoiding repetition and centralizing each purpose.**
60
-
61
- ## Typings
62
-
63
- - Fix typing issues only on the code you wrote.
64
- - 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.
65
-
66
- ## Workflow
67
-
68
- - Everytime I input error messages without any instructions, don't implement fixes.
69
- Instead, ivestigate the potential causes of the errors, and for each:
70
- 1. Evaluate / quantify the probabiliies
71
- 2. Give why and
72
- 3. Suggest how to fix it
73
- - 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".
74
-
75
- ## Never edit the following files
76
-
77
- - Prisma files (except schema.prisma)
78
- - tsconfigs
79
- - env
80
- - Any file / folder that is a symbolic link
81
-
82
- If you need to edit them, just suggest it in the chat.
83
-
84
- ## Don't run any of these commands
85
-
86
- ```
87
- git restore
88
- git reset
89
- prisma *
90
- And any git command in the write mode.
91
- ```
92
-
93
- # Copy and UX
94
-
95
- Before making UX/copy decisions, read `docs/PERSONAS.md`, `docs/PRODUCT.md`, `docs/MARKETING.md`.
@@ -1,102 +0,0 @@
1
- # Frontend designing
2
-
3
- UI components are defined in `/client/pages` and `/client/components`.
4
-
5
- ## Stack
6
-
7
- - Typescript strict
8
- - Preact with SSR
9
- - Base UI
10
- - `@/client/components/Motion`
11
- - Tailwind CSS 4
12
-
13
- Don't use React.useCallback unless strictly necessary.
14
-
15
- ## Communicate with the server
16
-
17
- ### Pages
18
-
19
- Pages use `Router.page(...)`.
20
-
21
- Use `Router.page(path, render)` when there is no SSR setup.
22
-
23
- Use `Router.page(path, setup, render)` when the page needs SSR config or SSR data:
24
-
25
- ```typescript
26
- Router.page('/dashboard/example', ({ Missions }) => ({
27
- _auth: 'USER',
28
- missions: Missions.Get(),
29
- }), ({ request, missions, Missions }) => {
30
- return <Page missions={missions} />;
31
- });
32
- ```
33
-
34
- - `setup` returns one flat object
35
- - keys like `_auth`, `_layout`, `_priority` are route config
36
- - all other keys are SSR data fetchers
37
- - never use `api.fetch(...)` in page files
38
-
39
- ### Components and hooks
40
-
41
- Components and hooks access controllers through `useContext()`:
42
-
43
- ```typescript
44
- const { Auth, Domains } = useContext();
45
- ```
46
-
47
- Then call controller methods directly:
48
-
49
- ```typescript
50
- Auth.Signup(data).then((result) => {
51
- ...
52
- });
53
- ```
54
-
55
- ### Async calls
56
-
57
- - Prefer direct controller calls from the context or page render args
58
- - The thrown errors will automatically be displayed to the user, so don't silent them
59
- - Never depend on legacy `@app` imports on the client
60
-
61
- ## Errors handling
62
-
63
- Errors catched from controller calls should never be silented.
64
- If a catch is needed, rethrow or surface the failure clearly.
65
-
66
- ## Design
67
-
68
- - Beautiful, modern, minimalist and intuitive design
69
- - Responsive layout
70
- - Enhance the UX with meaningful animations
71
-
72
- ## Rules
73
-
74
- - Always import React in react files (`.tsx`)
75
- - Don't use any component from `@client/components` unless the codebase already does in that area
76
- - To create a link / button, always use the `Link` component when the codebase expects navigation links
77
-
78
- ## Keep the code organized
79
-
80
- - Split big components (more than 1000 lines) into smaller components
81
- - Always use one component per file
82
- - Everytime possible, load data and define action handlers in the directly concerned component instead of passing everything from the parent
83
-
84
- ## Split the page by sections via comments
85
-
86
- Use:
87
-
88
- ```typescript
89
- /*----------------------------------
90
- - SECTION NAME
91
- ----------------------------------*/
92
- ```
93
-
94
- File sections:
95
- - DEPENDENCIES
96
- - TYPES
97
- - COMPONENT / PAGE
98
-
99
- Component / page sections:
100
- - INIT
101
- - ACTIONS
102
- - RENDER