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.
- 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
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
// Core
|
|
6
6
|
import type { Application } from '@server/app/index';
|
|
7
|
-
import Service, { AnyService,
|
|
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<
|
|
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<
|
|
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
|
|
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
|
|
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<
|
|
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
|
|
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('
|
|
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('
|
|
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 '
|
|
23
|
-
import type { TControllers } from '
|
|
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
|
|
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
|
-
|
|
75
|
+
TServerRouterCustomContext<TRouter>;
|
|
59
76
|
|
|
60
|
-
export type TRouterContextServices<
|
|
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> =
|
|
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 } });
|
package/agents/codex/AGENTS.md
DELETED
|
@@ -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
|