proteum 1.0.2 → 2.0.0-1
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 +101 -0
- package/agents/codex/AGENTS.md +95 -0
- package/agents/codex/CODING_STYLE.md +71 -0
- package/agents/codex/agents.md.zip +0 -0
- package/agents/codex/client/AGENTS.md +102 -0
- package/agents/codex/client/pages/AGENTS.md +35 -0
- package/agents/codex/server/routes/AGENTS.md +12 -0
- package/agents/codex/server/services/AGENTS.md +137 -0
- package/agents/codex/tests/AGENTS.md +8 -0
- package/cli/app/config.ts +13 -11
- package/cli/app/index.ts +74 -82
- package/cli/bin.js +1 -1
- package/cli/commands/build.ts +51 -14
- package/cli/commands/check.ts +19 -0
- package/cli/commands/deploy/app.ts +4 -8
- package/cli/commands/deploy/web.ts +16 -20
- package/cli/commands/dev.ts +189 -64
- package/cli/commands/devEvents.ts +106 -0
- package/cli/commands/init.ts +63 -57
- package/cli/commands/lint.ts +21 -0
- package/cli/commands/refresh.ts +18 -0
- package/cli/commands/typecheck.ts +18 -0
- package/cli/compiler/client/identite.ts +80 -53
- package/cli/compiler/client/index.ts +139 -213
- package/cli/compiler/common/bundleAnalysis.ts +94 -0
- package/cli/compiler/common/clientManifest.ts +67 -0
- package/cli/compiler/common/controllers.ts +288 -0
- package/cli/compiler/common/files/autres.ts +7 -18
- package/cli/compiler/common/files/images.ts +40 -37
- package/cli/compiler/common/files/style.ts +11 -22
- package/cli/compiler/common/generatedRouteModules.ts +368 -0
- package/cli/compiler/common/index.ts +31 -65
- package/cli/compiler/common/loaders/forbid-ssr-import.js +13 -0
- package/cli/compiler/common/rspackAliases.ts +13 -0
- package/cli/compiler/common/scripts.ts +37 -0
- package/cli/compiler/index.ts +781 -230
- package/cli/compiler/server/index.ts +59 -75
- package/cli/compiler/writeIfChanged.ts +21 -0
- package/cli/index.ts +71 -72
- package/cli/paths.ts +51 -57
- package/cli/print.ts +17 -11
- package/cli/tsconfig.json +5 -4
- package/cli/utils/agents.ts +100 -0
- package/cli/utils/check.ts +71 -0
- package/cli/utils/index.ts +1 -3
- package/cli/utils/keyboard.ts +8 -25
- package/cli/utils/runProcess.ts +30 -0
- package/client/app/component.tsx +29 -29
- package/client/app/index.ts +36 -57
- package/client/app/service.ts +7 -12
- package/client/app.tsconfig.json +2 -2
- package/client/components/Dialog/Manager.ssr.tsx +40 -0
- package/client/components/Dialog/Manager.tsx +119 -150
- package/client/components/Dialog/status.tsx +3 -3
- package/client/components/index.ts +1 -1
- package/client/components/types.d.ts +1 -3
- package/client/dev/hmr.ts +65 -0
- package/client/global.d.ts +2 -2
- package/client/hooks.ts +6 -9
- package/client/index.ts +2 -1
- package/client/islands/index.ts +7 -0
- package/client/islands/useDeferredModule.ts +199 -0
- package/client/pages/_layout/index.tsx +4 -12
- package/client/pages/useHeader.tsx +14 -21
- package/client/router.ts +27 -0
- package/client/services/router/components/Link.tsx +34 -27
- package/client/services/router/components/Page.tsx +6 -14
- package/client/services/router/components/router.ssr.tsx +36 -0
- package/client/services/router/components/router.tsx +63 -83
- package/client/services/router/index.tsx +185 -220
- package/client/services/router/request/api.ts +97 -119
- package/client/services/router/request/history.ts +2 -2
- package/client/services/router/request/index.ts +13 -12
- package/client/services/router/request/multipart.ts +72 -62
- package/client/services/router/response/index.tsx +68 -61
- package/client/services/router/response/page.ts +28 -32
- package/client/utils/dom.ts +17 -33
- package/common/app/index.ts +3 -3
- package/common/data/chaines/index.ts +22 -23
- package/common/data/dates.ts +35 -70
- package/common/data/markdown.ts +42 -39
- package/common/dev/serverHotReload.ts +26 -0
- package/common/errors/index.tsx +110 -142
- package/common/router/contracts.ts +29 -0
- package/common/router/index.ts +89 -108
- package/common/router/layouts.ts +34 -47
- package/common/router/pageSetup.ts +50 -0
- package/common/router/register.ts +53 -24
- package/common/router/request/api.ts +30 -36
- package/common/router/request/index.ts +2 -8
- package/common/router/response/index.ts +8 -15
- package/common/router/response/page.ts +70 -58
- package/common/utils.ts +1 -1
- package/doc/TODO.md +1 -1
- package/eslint.js +62 -0
- package/package.json +14 -49
- package/prettier.config.cjs +9 -0
- package/scripts/cleanup-generated-controllers.ts +62 -0
- package/scripts/fix-reference-app-typing.ts +490 -0
- package/scripts/refactor-client-app-imports.ts +244 -0
- package/scripts/refactor-client-pages.ts +587 -0
- package/scripts/refactor-server-controllers.ts +470 -0
- package/scripts/refactor-server-runtime-aliases.ts +360 -0
- package/scripts/restore-client-app-import-files.ts +41 -0
- package/scripts/restore-files-from-git-head.ts +20 -0
- package/scripts/update-codex-agents.ts +35 -0
- package/server/app/commands.ts +35 -64
- package/server/app/container/config.ts +48 -59
- package/server/app/container/console/index.ts +202 -248
- package/server/app/container/index.ts +33 -71
- package/server/app/controller/index.ts +61 -0
- package/server/app/index.ts +39 -105
- package/server/app/service/container.ts +41 -42
- package/server/app/service/index.ts +120 -147
- package/server/context.ts +1 -1
- package/server/index.ts +25 -1
- package/server/services/auth/index.ts +75 -115
- package/server/services/auth/router/index.ts +31 -32
- package/server/services/auth/router/request.ts +14 -16
- package/server/services/cron/CronTask.ts +13 -26
- package/server/services/cron/index.ts +14 -36
- package/server/services/disks/driver.ts +40 -58
- package/server/services/disks/drivers/local/index.ts +79 -90
- package/server/services/disks/drivers/s3/index.ts +116 -163
- package/server/services/disks/index.ts +23 -38
- package/server/services/email/index.ts +45 -104
- package/server/services/email/utils.ts +14 -27
- package/server/services/fetch/index.ts +53 -85
- package/server/services/prisma/Facet.ts +39 -91
- package/server/services/prisma/index.ts +74 -110
- package/server/services/router/generatedRuntime.ts +29 -0
- package/server/services/router/http/index.ts +78 -73
- package/server/services/router/http/multipart.ts +19 -42
- package/server/services/router/index.ts +378 -365
- package/server/services/router/request/api.ts +26 -25
- package/server/services/router/request/index.ts +44 -51
- package/server/services/router/request/service.ts +7 -11
- package/server/services/router/request/validation/zod.ts +111 -148
- package/server/services/router/response/index.ts +110 -125
- package/server/services/router/response/mask/Filter.ts +31 -72
- package/server/services/router/response/mask/index.ts +8 -15
- package/server/services/router/response/mask/selecteurs.ts +11 -25
- package/server/services/router/response/page/clientManifest.ts +25 -0
- package/server/services/router/response/page/document.tsx +199 -127
- package/server/services/router/response/page/index.tsx +89 -94
- package/server/services/router/service.ts +13 -15
- package/server/services/schema/index.ts +17 -26
- package/server/services/schema/request.ts +19 -33
- package/server/services/schema/router/index.ts +8 -11
- package/server/services/security/encrypt/aes/index.ts +15 -35
- package/server/utils/slug.ts +29 -35
- package/skills/clean-project-code/SKILL.md +63 -0
- package/skills/clean-project-code/agents/openai.yaml +4 -0
- package/tsconfig.common.json +4 -3
- package/tsconfig.json +4 -1
- package/types/aliases.d.ts +17 -21
- package/types/controller-input.test.ts +48 -0
- package/types/express-extra.d.ts +6 -0
- package/types/global/constants.d.ts +13 -0
- package/types/global/express-extra.d.ts +6 -0
- package/types/global/modules.d.ts +13 -16
- package/types/global/utils.d.ts +17 -49
- package/types/global/vendors.d.ts +62 -0
- package/types/icons.d.ts +65 -1
- package/types/uuid.d.ts +3 -0
- package/types/vendors.d.ts +62 -0
- package/cli/compiler/common/babel/index.ts +0 -170
- package/cli/compiler/common/babel/plugins/index.ts +0 -0
- package/cli/compiler/common/babel/plugins/services.ts +0 -586
- package/cli/compiler/common/babel/routes/imports.ts +0 -127
- package/cli/compiler/common/babel/routes/routes.ts +0 -1130
- package/client/services/captcha/index.ts +0 -67
- package/client/services/socket/index.ts +0 -147
- package/common/data/rte/nodes.ts +0 -83
- package/common/data/stats.ts +0 -90
- package/common/utils/rte.ts +0 -183
- package/server/services/auth/old.ts +0 -277
- package/server/services/cache/commands.ts +0 -41
- package/server/services/cache/index.ts +0 -297
- package/server/services/cache/service.json +0 -6
- package/server/services/socket/index.ts +0 -162
- package/server/services/socket/scope.ts +0 -226
- package/server/services/socket/service.json +0 -6
- package/server/services_old/SocketClient.ts +0 -92
- package/server/services_old/Token.old.ts +0 -97
|
@@ -6,27 +6,27 @@
|
|
|
6
6
|
|
|
7
7
|
import RequestService from './service';
|
|
8
8
|
|
|
9
|
-
import ApiClientService, {
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
import ApiClientService, {
|
|
10
|
+
TFetcherList,
|
|
11
|
+
TFetcherArgs,
|
|
12
|
+
TFetcher,
|
|
13
|
+
TDataReturnedByFetchers,
|
|
12
14
|
} from '@common/router/request/api';
|
|
13
15
|
|
|
14
16
|
/*----------------------------------
|
|
15
17
|
- TYPES
|
|
16
18
|
----------------------------------*/
|
|
17
19
|
|
|
18
|
-
|
|
19
20
|
/*----------------------------------
|
|
20
21
|
- SERVICE
|
|
21
22
|
----------------------------------*/
|
|
22
23
|
export default class ApiClientRequest extends RequestService implements ApiClientService {
|
|
23
|
-
|
|
24
24
|
/*----------------------------------
|
|
25
25
|
- HIGH LEVEL
|
|
26
26
|
----------------------------------*/
|
|
27
27
|
|
|
28
|
-
public fetch<TProvidedData extends TFetcherList = TFetcherList>(
|
|
29
|
-
|
|
28
|
+
public fetch<TProvidedData extends TFetcherList = TFetcherList>(
|
|
29
|
+
_fetchers: TFetcherList,
|
|
30
30
|
): TDataReturnedByFetchers<TProvidedData> {
|
|
31
31
|
throw new Error("api.fetch shouldn't be called here.");
|
|
32
32
|
}
|
|
@@ -35,34 +35,36 @@ export default class ApiClientRequest extends RequestService implements ApiClien
|
|
|
35
35
|
- PLACEHOLDERS
|
|
36
36
|
----------------------------------*/
|
|
37
37
|
|
|
38
|
-
public set(
|
|
39
|
-
throw new Error(
|
|
38
|
+
public set(_newData: TObjetDonnees) {
|
|
39
|
+
throw new Error('api.set is not available on server side.');
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
public reload(
|
|
43
|
-
throw new Error(
|
|
42
|
+
public reload(_ids?: string | string[], _params?: TObjetDonnees) {
|
|
43
|
+
throw new Error('api.set is not available on server side.');
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/*----------------------------------
|
|
47
47
|
- API CALLS FROM SERVER
|
|
48
48
|
----------------------------------*/
|
|
49
49
|
|
|
50
|
-
public createFetcher<TData extends unknown = unknown>(
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
public createFetcher<TData extends unknown = unknown>(
|
|
51
|
+
...[method, path, data, options]: TFetcherArgs
|
|
52
|
+
): TFetcher<TData> {
|
|
53
|
+
return {
|
|
54
|
+
method,
|
|
55
|
+
path,
|
|
56
|
+
data,
|
|
57
|
+
options,
|
|
53
58
|
// We don't put the then and catch methods so the api consumer on server side will know it's a fetcher and not a promize to wait
|
|
54
59
|
} as TFetcher<TData>;
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
public async fetchSync(fetchers: TFetcherList, alreadyLoadedData: {}): Promise<TObjetDonnees> {
|
|
58
|
-
|
|
59
63
|
const fetchedData: TObjetDonnees = { ...alreadyLoadedData };
|
|
60
64
|
|
|
61
65
|
for (const id in fetchers) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (!fetcher)
|
|
65
|
-
continue;
|
|
66
|
+
const fetcher = fetchers[id];
|
|
67
|
+
if (!fetcher) continue;
|
|
66
68
|
|
|
67
69
|
// Promise Fetcher (direct call from service method)
|
|
68
70
|
if ('then' in fetcher) {
|
|
@@ -70,18 +72,17 @@ export default class ApiClientRequest extends RequestService implements ApiClien
|
|
|
70
72
|
continue;
|
|
71
73
|
}
|
|
72
74
|
|
|
73
|
-
const { method, path, data
|
|
75
|
+
const { method, path, data } = fetcher;
|
|
74
76
|
//this.router.config.debug && console.log(`[api] Resolving from internal api`, method, path, data);
|
|
75
77
|
|
|
76
78
|
// We don't fetch the already given data
|
|
77
|
-
if (id in fetchedData)
|
|
78
|
-
continue;
|
|
79
|
+
if (id in fetchedData) continue;
|
|
79
80
|
|
|
80
81
|
// Create a children request to resolve the api data
|
|
81
82
|
const request = this.request.children(method, path, data);
|
|
82
|
-
fetchedData[id] = await request.router.resolve(request).then(res => res.data);
|
|
83
|
+
fetchedData[id] = await request.router.resolve(request).then((res) => res.data);
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
return fetchedData;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -6,16 +6,13 @@
|
|
|
6
6
|
import type express from 'express';
|
|
7
7
|
import ISO6391 from 'iso-639-1';
|
|
8
8
|
import accepts from 'accepts';
|
|
9
|
-
import Bowser from
|
|
9
|
+
import Bowser from 'bowser';
|
|
10
10
|
|
|
11
11
|
// Core
|
|
12
12
|
import BaseRequest from '@common/router/request';
|
|
13
13
|
|
|
14
14
|
// Specific
|
|
15
|
-
import type {
|
|
16
|
-
default as Router, Config as RouterConfig,
|
|
17
|
-
HttpMethod, HttpHeaders
|
|
18
|
-
} from '..';
|
|
15
|
+
import type { HttpMethod, HttpHeaders } from '..';
|
|
19
16
|
import ApiClient from './api';
|
|
20
17
|
import ServerResponse from '../response';
|
|
21
18
|
import type { TAnyRouter } from '..';
|
|
@@ -25,30 +22,24 @@ import type { TAnyRouter } from '..';
|
|
|
25
22
|
----------------------------------*/
|
|
26
23
|
|
|
27
24
|
const localeFilter = (input: any) => {
|
|
28
|
-
|
|
29
25
|
// Data type
|
|
30
|
-
if (typeof input !== 'string')
|
|
31
|
-
return;
|
|
26
|
+
if (typeof input !== 'string') return;
|
|
32
27
|
|
|
33
28
|
// Extract ISO code
|
|
34
29
|
let lang = input.trim().split(/[-_]/)[0].toLowerCase();
|
|
35
|
-
|
|
30
|
+
|
|
36
31
|
// Check size
|
|
37
|
-
if (!ISO6391.validate(lang))
|
|
38
|
-
return;
|
|
32
|
+
if (!ISO6391.validate(lang)) return;
|
|
39
33
|
|
|
40
34
|
return lang.toUpperCase();
|
|
41
|
-
}
|
|
35
|
+
};
|
|
42
36
|
|
|
43
|
-
export type UploadedFile = File
|
|
37
|
+
export type UploadedFile = File;
|
|
44
38
|
|
|
45
39
|
/*----------------------------------
|
|
46
40
|
- CONTEXTE
|
|
47
41
|
----------------------------------*/
|
|
48
|
-
export default class ServerRequest<
|
|
49
|
-
TRouter extends TAnyRouter = TAnyRouter
|
|
50
|
-
> extends BaseRequest {
|
|
51
|
-
|
|
42
|
+
export default class ServerRequest<TRouter extends TAnyRouter = TAnyRouter> extends BaseRequest {
|
|
52
43
|
/*----------------------------------
|
|
53
44
|
- PROPRIÉTÉS
|
|
54
45
|
----------------------------------*/
|
|
@@ -79,26 +70,24 @@ export default class ServerRequest<
|
|
|
79
70
|
/*----------------------------------
|
|
80
71
|
- INITIALISATION
|
|
81
72
|
----------------------------------*/
|
|
82
|
-
public constructor(
|
|
83
|
-
|
|
73
|
+
public constructor(
|
|
84
74
|
id: string,
|
|
85
|
-
method: HttpMethod,
|
|
86
|
-
path: string,
|
|
75
|
+
method: HttpMethod,
|
|
76
|
+
path: string,
|
|
87
77
|
data: TObjetDonnees | undefined,
|
|
88
78
|
headers: HttpHeaders | undefined,
|
|
89
79
|
|
|
90
|
-
res: express.Response,
|
|
80
|
+
res: express.Response,
|
|
91
81
|
router: TRouter,
|
|
92
|
-
isVirtual: boolean = false
|
|
82
|
+
isVirtual: boolean = false,
|
|
93
83
|
) {
|
|
94
|
-
|
|
95
84
|
super(path);
|
|
96
85
|
|
|
97
86
|
this.id = id;
|
|
98
87
|
this.isVirtual = isVirtual;
|
|
99
88
|
|
|
100
89
|
this.req = res.req;
|
|
101
|
-
this.res = res
|
|
90
|
+
this.res = res;
|
|
102
91
|
this.router = router;
|
|
103
92
|
this.api = new ApiClient(this);
|
|
104
93
|
|
|
@@ -116,48 +105,46 @@ export default class ServerRequest<
|
|
|
116
105
|
}
|
|
117
106
|
|
|
118
107
|
public children(method: HttpMethod, path: string, data: TObjetDonnees | undefined) {
|
|
119
|
-
const children = new ServerRequest(
|
|
120
|
-
this.id,
|
|
121
|
-
|
|
108
|
+
const children = new ServerRequest(
|
|
109
|
+
this.id,
|
|
110
|
+
method,
|
|
111
|
+
path,
|
|
112
|
+
data,
|
|
113
|
+
{ ...this.headers, accept: 'application/json' },
|
|
114
|
+
this.res,
|
|
115
|
+
this.router,
|
|
116
|
+
true,
|
|
122
117
|
);
|
|
123
118
|
children.user = this.user;
|
|
124
119
|
return children;
|
|
125
120
|
}
|
|
126
121
|
|
|
127
122
|
private getLocale() {
|
|
128
|
-
|
|
129
123
|
const fromQuery = localeFilter(this.req.query.lang);
|
|
130
124
|
if (fromQuery) {
|
|
131
125
|
this.res.cookie('lang', fromQuery);
|
|
132
126
|
return fromQuery;
|
|
133
|
-
}
|
|
127
|
+
}
|
|
134
128
|
|
|
135
|
-
const locale =
|
|
129
|
+
const locale =
|
|
136
130
|
// Member settings
|
|
137
|
-
this.user?.locale
|
|
138
|
-
||
|
|
131
|
+
this.user?.locale ||
|
|
139
132
|
// URL
|
|
140
|
-
localeFilter(
|
|
141
|
-
||
|
|
133
|
+
localeFilter(this.req.cookies.lang) ||
|
|
142
134
|
// Browser
|
|
143
|
-
localeFilter(
|
|
144
|
-
||
|
|
135
|
+
localeFilter(this.req.acceptsLanguages()[0]) ||
|
|
145
136
|
// Default
|
|
146
|
-
'EN'
|
|
147
|
-
)
|
|
137
|
+
'EN';
|
|
148
138
|
|
|
149
|
-
return locale ? locale.toUpperCase() : 'EN'
|
|
139
|
+
return locale ? locale.toUpperCase() : 'EN';
|
|
150
140
|
}
|
|
151
141
|
|
|
152
|
-
public cookie(
|
|
153
|
-
|
|
154
|
-
const value = this.req.cookies[ key ];
|
|
142
|
+
public cookie(key: string, consume: boolean = false) {
|
|
143
|
+
const value = this.req.cookies[key];
|
|
155
144
|
|
|
156
|
-
if (consume)
|
|
157
|
-
this.res.clearCookie(key);
|
|
145
|
+
if (consume) this.res.clearCookie(key);
|
|
158
146
|
|
|
159
147
|
return value;
|
|
160
|
-
|
|
161
148
|
}
|
|
162
149
|
|
|
163
150
|
/*----------------------------------
|
|
@@ -170,15 +157,21 @@ export default class ServerRequest<
|
|
|
170
157
|
}
|
|
171
158
|
|
|
172
159
|
public device(): Bowser.Parser.ParsedResult | undefined {
|
|
173
|
-
return this.headers['user-agent'] !== undefined
|
|
174
|
-
? Bowser.parse(this.headers['user-agent'])
|
|
175
|
-
: undefined;
|
|
160
|
+
return this.headers['user-agent'] !== undefined ? Bowser.parse(this.headers['user-agent']) : undefined;
|
|
176
161
|
}
|
|
177
162
|
|
|
178
163
|
public deviceString(): string | undefined {
|
|
179
164
|
const info = this.device();
|
|
180
165
|
if (info === undefined) return undefined;
|
|
181
166
|
const { os, browser } = info;
|
|
182
|
-
return (
|
|
167
|
+
return (
|
|
168
|
+
(os.name || 'Unknown OS') +
|
|
169
|
+
' ' +
|
|
170
|
+
(os.versionName || os.version || '') +
|
|
171
|
+
' / ' +
|
|
172
|
+
(browser.name || 'Unknown browser') +
|
|
173
|
+
' ' +
|
|
174
|
+
(browser.version || '')
|
|
175
|
+
);
|
|
183
176
|
}
|
|
184
|
-
}
|
|
177
|
+
}
|
|
@@ -2,20 +2,16 @@
|
|
|
2
2
|
- DEPENDANCES
|
|
3
3
|
----------------------------------*/
|
|
4
4
|
|
|
5
|
-
import type
|
|
5
|
+
import type { TAnyRouter } from '..';
|
|
6
6
|
import type ServerRequest from '.';
|
|
7
7
|
|
|
8
8
|
/*----------------------------------
|
|
9
9
|
- SERVICE
|
|
10
10
|
----------------------------------*/
|
|
11
|
-
export default abstract class RequestService {
|
|
12
|
-
|
|
11
|
+
export default abstract class RequestService<TRequest extends ServerRequest<TAnyRouter> = ServerRequest<TAnyRouter>> {
|
|
13
12
|
public constructor(
|
|
14
|
-
public request:
|
|
15
|
-
public router = request.router,
|
|
16
|
-
public app = router.app
|
|
17
|
-
) {
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
}
|
|
13
|
+
public request: TRequest,
|
|
14
|
+
public router: TRequest['router'] = request.router,
|
|
15
|
+
public app: TRequest['router']['app'] = router.app,
|
|
16
|
+
) {}
|
|
17
|
+
}
|
|
@@ -1,177 +1,140 @@
|
|
|
1
1
|
import { InputError } from '@common/errors';
|
|
2
|
-
import zod
|
|
3
|
-
|
|
4
|
-
export type TRichTextValidatorOptions = {
|
|
5
|
-
|
|
2
|
+
import zod from 'zod';
|
|
3
|
+
|
|
4
|
+
export type TRichTextValidatorOptions = { attachements?: boolean };
|
|
5
|
+
export type TValidationSchema = zod.ZodTypeAny;
|
|
6
|
+
export type TValidationShape = zod.ZodRawShape;
|
|
7
|
+
|
|
8
|
+
type TChoiceOption = { value: PrimitiveValue; label: string };
|
|
9
|
+
|
|
10
|
+
type TLexicalNode = {
|
|
11
|
+
type: string;
|
|
12
|
+
text?: string;
|
|
13
|
+
children?: TLexicalNode[];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type TLexicalRoot = {
|
|
17
|
+
root: {
|
|
18
|
+
type: 'root';
|
|
19
|
+
children: TLexicalNode[];
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === 'object' && value !== null;
|
|
24
|
+
|
|
25
|
+
const hasChoiceValue = (value: unknown): value is { value: PrimitiveValue } => isRecord(value) && 'value' in value;
|
|
26
|
+
|
|
27
|
+
const normalizeChoiceValue = (value: unknown): PrimitiveValue =>
|
|
28
|
+
(hasChoiceValue(value) ? value.value : value) as PrimitiveValue;
|
|
29
|
+
|
|
30
|
+
const isLexicalNode = (value: unknown): value is TLexicalNode =>
|
|
31
|
+
isRecord(value) && typeof value.type === 'string';
|
|
32
|
+
|
|
33
|
+
const isLexicalRoot = (value: unknown): value is TLexicalRoot => {
|
|
34
|
+
if (!isRecord(value)) return false;
|
|
35
|
+
if (!isRecord(value.root)) return false;
|
|
36
|
+
if (value.root.type !== 'root') return false;
|
|
37
|
+
return Array.isArray(value.root.children);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const isZodSchema = (fields: unknown): fields is TValidationSchema => {
|
|
41
|
+
return (
|
|
42
|
+
typeof fields === 'object' &&
|
|
43
|
+
fields !== null &&
|
|
44
|
+
'safeParse' in fields &&
|
|
45
|
+
typeof (fields as TValidationSchema).safeParse === 'function'
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export function toValidationSchema<TSchema extends TValidationSchema>(fields: TSchema): TSchema;
|
|
50
|
+
export function toValidationSchema<TShape extends TValidationShape>(fields: TShape): zod.ZodObject<TShape>;
|
|
51
|
+
export function toValidationSchema(
|
|
52
|
+
fields: TValidationSchema | TValidationShape,
|
|
53
|
+
): TValidationSchema | zod.ZodObject<TValidationShape>;
|
|
54
|
+
export function toValidationSchema(fields: TValidationSchema | TValidationShape) {
|
|
55
|
+
return isZodSchema(fields) ? fields : zod.object(fields);
|
|
6
56
|
}
|
|
7
57
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
// Not working, data is {}
|
|
11
|
-
return schema;
|
|
12
|
-
|
|
13
|
-
if (!(schema instanceof zod.ZodObject))
|
|
14
|
-
return schema;
|
|
15
|
-
|
|
16
|
-
if (schema.withPreprocessing)
|
|
17
|
-
return schema;
|
|
58
|
+
// Legacy hook kept as an identity to preserve the public surface without relying on removed Zod internals.
|
|
59
|
+
export const preprocessSchema = <TSchema extends zod.ZodObject<any>>(schema: TSchema): TSchema => schema;
|
|
18
60
|
|
|
19
|
-
|
|
20
|
-
|
|
61
|
+
const createChoiceValueSchema = (
|
|
62
|
+
choices: readonly string[] | readonly TChoiceOption[] | zod.ZodTypeAny,
|
|
63
|
+
): zod.ZodTypeAny => {
|
|
64
|
+
if (isZodSchema(choices)) return choices;
|
|
21
65
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (!['newEntity', 'email'].includes(key))
|
|
25
|
-
continue;
|
|
26
|
-
|
|
27
|
-
let current: zod.ZodTypeAny = shape[key];
|
|
28
|
-
while (current) {
|
|
29
|
-
|
|
30
|
-
const origType = current.type;
|
|
31
|
-
const preprocessor = toPreprocess[origType];
|
|
32
|
-
|
|
33
|
-
if (origType === 'object') {
|
|
34
|
-
newShape[key] = preprocessSchema(current as zod.ZodObject);
|
|
35
|
-
break;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (preprocessor) {
|
|
39
|
-
newShape[key] = preprocessor(current);
|
|
40
|
-
console.log('====newShape', key, newShape[key]);
|
|
41
|
-
break;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
current = current.def;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const newSchema = zod.object(newShape);
|
|
49
|
-
newSchema.withPreprocessing = true;
|
|
50
|
-
return newSchema;
|
|
51
|
-
}
|
|
66
|
+
const allowedValues = new Set(choices.map((choice) => normalizeChoiceValue(choice)));
|
|
52
67
|
|
|
53
|
-
|
|
68
|
+
return zod.custom<PrimitiveValue>((value) => allowedValues.has(normalizeChoiceValue(value)), {
|
|
69
|
+
message: 'Invalid choice.',
|
|
70
|
+
});
|
|
71
|
+
};
|
|
54
72
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}, zString),
|
|
58
|
-
|
|
59
|
-
int: (zInt: zod.ZodInt) => zod.preprocess( val => {
|
|
60
|
-
return typeof val === 'string' ? Number.parseInt(val) : val;
|
|
61
|
-
}, zInt),
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export const schema = {
|
|
66
|
-
...zod,
|
|
73
|
+
export const schema = {
|
|
74
|
+
...zod,
|
|
67
75
|
|
|
68
76
|
file: () => {
|
|
69
|
-
|
|
70
|
-
// Chaine = url ancien fichier = exclusion de la valeur pour conserver l'ancien fichier
|
|
71
|
-
// NOTE: Si la valeur est présente mais undefined, alors on supprimera le fichier
|
|
72
|
-
/*if (typeof val === 'string')
|
|
73
|
-
return true;*/
|
|
74
|
-
|
|
77
|
+
// String = existing file URL, so callers can omit replacing an uploaded file.
|
|
75
78
|
return zod.file();
|
|
76
79
|
},
|
|
77
80
|
|
|
78
|
-
choice: (
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const valueType
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
valueType
|
|
91
|
-
|
|
92
|
-
]);
|
|
93
|
-
|
|
94
|
-
const type = options.multiple ? zod.array( itemType ) : itemType;
|
|
95
|
-
|
|
96
|
-
return type.transform(v => {
|
|
97
|
-
if (options.multiple) {
|
|
98
|
-
return v.map(normalizeValue);
|
|
99
|
-
} else {
|
|
100
|
-
return normalizeValue(v);
|
|
101
|
-
}
|
|
81
|
+
choice: (
|
|
82
|
+
choices: readonly string[] | readonly TChoiceOption[] | zod.ZodTypeAny,
|
|
83
|
+
options: { multiple?: boolean } = {},
|
|
84
|
+
) => {
|
|
85
|
+
const valueType = createChoiceValueSchema(choices);
|
|
86
|
+
const itemType = zod.union([zod.object({ value: valueType, label: zod.string() }), valueType]);
|
|
87
|
+
const choiceType = options.multiple ? zod.array(itemType) : itemType;
|
|
88
|
+
|
|
89
|
+
return choiceType.transform((value: unknown) => {
|
|
90
|
+
if (options.multiple) return (value as unknown[]).map((entry: unknown) => normalizeChoiceValue(entry));
|
|
91
|
+
return normalizeChoiceValue(value);
|
|
102
92
|
});
|
|
103
93
|
},
|
|
104
94
|
|
|
105
|
-
richText: (opts: TRichTextValidatorOptions = {}) =>
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
// We get a stringified json as input since the editor workds with JSON string
|
|
113
|
-
try {
|
|
114
|
-
val = JSON.parse(val);
|
|
115
|
-
} catch (error) {
|
|
116
|
-
console.error("Failed to parse rich text json:", error, val);
|
|
117
|
-
return false;//throw new InputError("Invalid rich text format.");
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Check that the root exists and has a valid type
|
|
121
|
-
if (!val || typeof val !== 'object' || typeof val.root !== 'object' || val.root.type !== 'root') {
|
|
122
|
-
console.error("Invalid rich text value (1).", val);
|
|
123
|
-
return false;//throw new InputError("Invalid rich text value (1).");
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Check if root has children array
|
|
127
|
-
if (!Array.isArray(val.root.children)) {
|
|
128
|
-
console.error("Invalid rich text value (2).", val);
|
|
129
|
-
return false;
|
|
130
|
-
}
|
|
95
|
+
richText: (opts: TRichTextValidatorOptions = {}) =>
|
|
96
|
+
schema.custom((value) => {
|
|
97
|
+
if (typeof value !== 'string') {
|
|
98
|
+
console.error('Invalid rich text format.', value);
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
131
101
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
102
|
+
let parsed: unknown;
|
|
103
|
+
try {
|
|
104
|
+
parsed = JSON.parse(value);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error('Failed to parse rich text json:', error, value);
|
|
135
107
|
return false;
|
|
136
|
-
|
|
108
|
+
}
|
|
137
109
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
110
|
+
if (!isLexicalRoot(parsed)) {
|
|
111
|
+
console.error('Invalid rich text value.', parsed);
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
141
114
|
|
|
142
|
-
|
|
143
|
-
|
|
115
|
+
return parsed.root.children.every((child) => validateLexicalNode(child, opts));
|
|
116
|
+
}),
|
|
117
|
+
};
|
|
144
118
|
|
|
145
|
-
|
|
146
|
-
if (
|
|
147
|
-
throw new InputError("Invalid rich text value (3).");
|
|
119
|
+
function validateLexicalNode(node: unknown, opts: TRichTextValidatorOptions) {
|
|
120
|
+
if (!isLexicalNode(node)) throw new InputError('Invalid rich text value (3).');
|
|
148
121
|
|
|
149
|
-
// Validate text nodes
|
|
150
122
|
if (node.type === 'text') {
|
|
123
|
+
if (typeof node.text !== 'string') throw new InputError('Invalid rich text value (4).');
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
151
126
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
// Validate paragraph, heading, or other structural nodes that may contain children
|
|
156
|
-
} else if (['paragraph', 'heading', 'list', 'listitem'].includes(node.type)) {
|
|
157
|
-
|
|
158
|
-
if (!Array.isArray(node.children) || !node.children.every(children => validateLexicalNode(children, opts))) {
|
|
159
|
-
throw new InputError("Invalid rich text value (5).");
|
|
127
|
+
if (['paragraph', 'heading', 'list', 'listitem'].includes(node.type)) {
|
|
128
|
+
if (!Array.isArray(node.children) || !node.children.every((child) => validateLexicalNode(child, opts))) {
|
|
129
|
+
throw new InputError('Invalid rich text value (5).');
|
|
160
130
|
}
|
|
161
131
|
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Check if allowed
|
|
166
|
-
/*if (opts.attachements === undefined)
|
|
167
|
-
throw new InputError("Image attachments not allowed in this rich text field.");*/
|
|
168
|
-
|
|
169
|
-
// TODO: check mime
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
// Upload file
|
|
173
|
-
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
174
134
|
|
|
135
|
+
if (node.type === 'image') {
|
|
136
|
+
// Attachments are validated by the upload pipeline; rich-text validation only enforces node shape.
|
|
137
|
+
return true;
|
|
175
138
|
}
|
|
176
139
|
|
|
177
140
|
return true;
|