proteum 1.0.3 → 2.0.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 +92 -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 +12 -17
- package/cli/app/index.ts +59 -99
- package/cli/bin.js +1 -1
- package/cli/commands/build.ts +23 -12
- 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 +185 -75
- 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 +6 -6
- package/cli/commands/typecheck.ts +18 -0
- package/cli/compiler/client/identite.ts +79 -49
- package/cli/compiler/client/index.ts +132 -214
- 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 +12 -25
- package/cli/compiler/common/generatedRouteModules.ts +368 -0
- package/cli/compiler/common/index.ts +29 -68
- 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 +764 -234
- package/cli/compiler/server/index.ts +52 -77
- package/cli/compiler/writeIfChanged.ts +21 -0
- package/cli/index.ts +65 -90
- 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/eslint.js +62 -0
- package/package.json +12 -45
- 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 +39 -69
- 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 +77 -72
- 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 -32
- 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 +1 -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 -173
- 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 -1170
- 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
|
@@ -5,13 +5,16 @@
|
|
|
5
5
|
// Core
|
|
6
6
|
import type ClientApplication from '@client/app';
|
|
7
7
|
import { fromJson as errorFromJson, NetworkError } from '@common/errors';
|
|
8
|
-
import ApiClientService, {
|
|
9
|
-
TPostData,
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
import ApiClientService, {
|
|
9
|
+
TPostData,
|
|
10
|
+
TPostDataWithoutFile,
|
|
11
|
+
TApiFetchOptions,
|
|
12
|
+
TFetcherList,
|
|
13
|
+
TFetcherArgs,
|
|
14
|
+
TFetcher,
|
|
15
|
+
TDataReturnedByFetchers,
|
|
12
16
|
} from '@common/router/request/api';
|
|
13
17
|
|
|
14
|
-
|
|
15
18
|
// Specific
|
|
16
19
|
import type { default as Router, Request } from '..';
|
|
17
20
|
import { toMultipart } from './multipart';
|
|
@@ -22,79 +25,71 @@ import { toMultipart } from './multipart';
|
|
|
22
25
|
|
|
23
26
|
const debug = false;
|
|
24
27
|
|
|
25
|
-
export type Config = {
|
|
28
|
+
export type Config = {};
|
|
26
29
|
|
|
27
|
-
|
|
30
|
+
const isFileValue = (value: unknown): value is File =>
|
|
31
|
+
typeof File !== 'undefined' && typeof value === 'object' && value instanceof File;
|
|
28
32
|
|
|
29
33
|
/*----------------------------------
|
|
30
34
|
- FUNCTION
|
|
31
35
|
----------------------------------*/
|
|
32
36
|
export default class ApiClient implements ApiClientService {
|
|
33
|
-
|
|
34
37
|
// APO Client needs to know the current request so we can monitor which api request is made from which page
|
|
35
|
-
public constructor(
|
|
36
|
-
public app: ClientApplication,
|
|
38
|
+
public constructor(
|
|
39
|
+
public app: ClientApplication,
|
|
37
40
|
public request: Request,
|
|
38
41
|
public router = request.router,
|
|
39
|
-
) {
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
+
) {}
|
|
42
43
|
|
|
43
44
|
/*----------------------------------
|
|
44
45
|
- HIGH LEVEL
|
|
45
46
|
----------------------------------*/
|
|
46
47
|
|
|
47
|
-
public fetch<FetchersList extends TFetcherList = TFetcherList>(
|
|
48
|
-
fetchers: FetchersList
|
|
48
|
+
public fetch<FetchersList extends TFetcherList = TFetcherList>(
|
|
49
|
+
fetchers: FetchersList,
|
|
49
50
|
): TDataReturnedByFetchers<FetchersList> {
|
|
50
51
|
throw new Error("api.fetch shouldn't be called here.");
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
public post = <TData extends unknown = unknown>(path: string, data?: TPostData, opts?: TApiFetchOptions) =>
|
|
54
|
+
public post = <TData extends unknown = unknown>(path: string, data?: TPostData, opts?: TApiFetchOptions) =>
|
|
54
55
|
this.createFetcher<TData>('POST', path, data, opts);
|
|
55
56
|
|
|
56
|
-
public set(
|
|
57
|
+
public set(newData: TObjetDonnees) {
|
|
58
|
+
if (!('context' in this.router)) throw new Error('api.set is not available on server side.');
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
else
|
|
64
|
-
throw new Error(`[api] this.router.context.page undefined`)
|
|
60
|
+
const currentPage = this.router.context.page;
|
|
61
|
+
if (currentPage && 'setAllData' in currentPage) {
|
|
62
|
+
const page = currentPage as { setAllData: (updater: (data: TObjetDonnees) => TObjetDonnees) => void };
|
|
63
|
+
page.setAllData((curData) => ({ ...curData, ...newData }));
|
|
64
|
+
}
|
|
65
|
+
else throw new Error(`[api] this.router.context.page undefined`);
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
public reload(
|
|
68
|
+
public reload(ids?: string | string[], params?: TObjetDonnees) {
|
|
69
|
+
if (!('context' in this.router)) throw new Error('api.reload is not available on server side.');
|
|
68
70
|
|
|
69
|
-
if (!('context' in this.router))
|
|
70
|
-
throw new Error("api.reload is not available on server side.");
|
|
71
|
-
|
|
72
71
|
const page = this.router.context.page;
|
|
72
|
+
if (!page) throw new Error('api.reload requires an active page context.');
|
|
73
73
|
|
|
74
|
-
if (ids === undefined)
|
|
75
|
-
|
|
76
|
-
else if (typeof ids === 'string')
|
|
77
|
-
ids = [ids];
|
|
78
|
-
|
|
79
|
-
debug && console.log("[api] Reload data", ids, params, page.fetchers);
|
|
80
|
-
|
|
81
|
-
for (const id of ids) {
|
|
74
|
+
if (ids === undefined) ids = Object.keys(page.fetchers);
|
|
75
|
+
else if (typeof ids === 'string') ids = [ids];
|
|
82
76
|
|
|
83
|
-
|
|
84
|
-
if (fetcher === undefined)
|
|
85
|
-
return console.error(`Unable to reload ${id}: Request not found in fetchers list.`);
|
|
77
|
+
if (params !== undefined) page.context.request.data = { ...page.context.request.data, ...params };
|
|
86
78
|
|
|
87
|
-
|
|
88
|
-
|
|
79
|
+
const nextData = { ...page.data };
|
|
80
|
+
for (const id of ids) delete nextData[id];
|
|
89
81
|
|
|
90
|
-
|
|
82
|
+
page.data = nextData;
|
|
91
83
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
this.set({ [id]: data });
|
|
84
|
+
debug && console.log('[api] Reload data', ids, params, page.fetchers);
|
|
95
85
|
|
|
86
|
+
page.fetchData()
|
|
87
|
+
.then((data: TObjetDonnees) => {
|
|
88
|
+
this.set(data);
|
|
96
89
|
})
|
|
97
|
-
|
|
90
|
+
.catch((error: Error) => {
|
|
91
|
+
this.app.handleError(error);
|
|
92
|
+
});
|
|
98
93
|
}
|
|
99
94
|
|
|
100
95
|
/*----------------------------------
|
|
@@ -106,36 +101,29 @@ export default class ApiClient implements ApiClientService {
|
|
|
106
101
|
// Lazily create (and cache) the underlying promise so the fetcher behaves like a real promise instance.
|
|
107
102
|
let promise: Promise<TData> | undefined;
|
|
108
103
|
|
|
109
|
-
const fetcher = {
|
|
110
|
-
method, path, data, options,
|
|
111
|
-
} as TFetcher<TData>;
|
|
104
|
+
const fetcher = { method, path, data, options } as TFetcher<TData>;
|
|
112
105
|
|
|
113
106
|
const getPromise = () => {
|
|
114
|
-
if (!promise)
|
|
115
|
-
promise = this.fetchAsync<TData>(fetcher.method, fetcher.path, fetcher.data, fetcher.options);
|
|
107
|
+
if (!promise) promise = this.fetchAsync<TData>(fetcher.method, fetcher.path, fetcher.data, fetcher.options);
|
|
116
108
|
|
|
117
109
|
return promise;
|
|
118
110
|
};
|
|
119
111
|
|
|
120
112
|
// For async calls: api.post(...).then((data) => ...)
|
|
121
|
-
fetcher.then = (onfulfilled?: any, onrejected?: any) =>
|
|
122
|
-
getPromise().then(onfulfilled, onrejected) as any;
|
|
113
|
+
fetcher.then = (onfulfilled?: any, onrejected?: any) => getPromise().then(onfulfilled, onrejected) as any;
|
|
123
114
|
|
|
124
|
-
fetcher.catch = (onrejected?: any) =>
|
|
125
|
-
getPromise().catch(onrejected) as any;
|
|
115
|
+
fetcher.catch = (onrejected?: any) => getPromise().catch(onrejected) as any;
|
|
126
116
|
|
|
127
|
-
fetcher.finally = (onfinally?: any) =>
|
|
128
|
-
getPromise().finally(onfinally) as any;
|
|
117
|
+
fetcher.finally = (onfinally?: any) => getPromise().finally(onfinally) as any;
|
|
129
118
|
|
|
130
119
|
fetcher.run = () => getPromise();
|
|
131
120
|
|
|
132
121
|
return fetcher;
|
|
133
122
|
}
|
|
134
123
|
|
|
135
|
-
public async fetchAsync<TData extends unknown = unknown>(
|
|
136
|
-
method, path, data, options
|
|
137
|
-
|
|
138
|
-
|
|
124
|
+
public async fetchAsync<TData extends unknown = unknown>(
|
|
125
|
+
...[method, path, data, options]: TFetcherArgs
|
|
126
|
+
): Promise<TData> {
|
|
139
127
|
/*if (options?.captcha !== undefined)
|
|
140
128
|
await this.gui.captcha.check(options?.captcha);*/
|
|
141
129
|
|
|
@@ -143,111 +131,102 @@ export default class ApiClient implements ApiClientService {
|
|
|
143
131
|
}
|
|
144
132
|
|
|
145
133
|
public async fetchSync(fetchers: TFetcherList, alreadyLoadedData: {}): Promise<TObjetDonnees> {
|
|
146
|
-
|
|
147
134
|
// Pick the fetchers where the data is needed
|
|
148
135
|
const fetchersToRun: TFetcherList = {};
|
|
149
136
|
let fetchersCount: number = 0;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (!(
|
|
153
|
-
fetchersToRun[
|
|
137
|
+
// The fetcher can be undefined
|
|
138
|
+
for (const fetcherId in fetchers)
|
|
139
|
+
if (!(fetcherId in alreadyLoadedData) && fetchers[fetcherId]) {
|
|
140
|
+
fetchersToRun[fetcherId] = fetchers[fetcherId];
|
|
154
141
|
fetchersCount++;
|
|
155
142
|
}
|
|
156
143
|
|
|
157
144
|
// Fetch all the api data thanks to one http request
|
|
158
|
-
const fetchedData =
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
145
|
+
const fetchedData =
|
|
146
|
+
fetchersCount === 0
|
|
147
|
+
? {}
|
|
148
|
+
: await this.execute<TObjetDonnees>(
|
|
149
|
+
'POST',
|
|
150
|
+
'/api',
|
|
151
|
+
({ fetchers: fetchersToRun } as unknown) as TPostData,
|
|
152
|
+
)
|
|
153
|
+
.then((res: TObjetDonnees) => {
|
|
154
|
+
const data: TObjetDonnees = {};
|
|
155
|
+
for (const id in res) data[id] = res[id];
|
|
156
|
+
|
|
157
|
+
return data;
|
|
158
|
+
})
|
|
159
|
+
.catch((e: Error) => {
|
|
160
|
+
// API Error hook
|
|
161
|
+
this.app.handleError(e);
|
|
162
|
+
|
|
163
|
+
throw e;
|
|
164
|
+
});
|
|
177
165
|
|
|
178
166
|
// Errors will be catched in the caller
|
|
179
167
|
|
|
180
|
-
return { ...alreadyLoadedData, ...fetchedData }
|
|
168
|
+
return { ...alreadyLoadedData, ...fetchedData };
|
|
181
169
|
}
|
|
182
170
|
|
|
183
171
|
public configure = (...[method, path, data, options = {}]: TFetcherArgs) => {
|
|
184
|
-
|
|
185
172
|
let url = this.router.url(path, {}, false);
|
|
186
|
-
|
|
173
|
+
|
|
187
174
|
debug && console.log(`[api] Sending request`, method, url, data);
|
|
188
|
-
|
|
175
|
+
|
|
189
176
|
// Create Fetch config
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
'Accept': "application/json",
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
|
|
177
|
+
const headers = new Headers({ Accept: 'application/json' });
|
|
178
|
+
const config: RequestInit = { method, headers };
|
|
179
|
+
|
|
197
180
|
// Update options depending on data
|
|
198
181
|
if (data) {
|
|
199
|
-
|
|
200
182
|
// If file included in data, need to use multipart
|
|
201
183
|
// TODO: deep check
|
|
202
|
-
|
|
184
|
+
const hasFile = Object.values(data).some((value) => isFileValue(value));
|
|
203
185
|
if (hasFile) {
|
|
204
186
|
// GET request = Can't send files
|
|
205
|
-
if (method ===
|
|
206
|
-
throw new Error("Cannot send file in GET request");
|
|
187
|
+
if (method === 'GET') throw new Error('Cannot send file in GET request');
|
|
207
188
|
// Auto switch to multiplart
|
|
208
|
-
else if (options.encoding === undefined)
|
|
209
|
-
options.encoding = 'multipart';
|
|
189
|
+
else if (options.encoding === undefined) options.encoding = 'multipart';
|
|
210
190
|
else if (options.encoding !== 'multipart')
|
|
211
|
-
|
|
212
|
-
throw new Error(
|
|
191
|
+
// Encoding set to JSON = Can't send files
|
|
192
|
+
throw new Error('Cannot send file in non-multipart request');
|
|
213
193
|
}
|
|
214
194
|
|
|
215
195
|
// Data encoding
|
|
216
|
-
if (method ===
|
|
217
|
-
|
|
218
|
-
const
|
|
196
|
+
if (method === 'GET') {
|
|
197
|
+
const params = new URLSearchParams();
|
|
198
|
+
for (const [key, value] of Object.entries(data as TPostDataWithoutFile)) {
|
|
199
|
+
if (value === undefined || value === null) continue;
|
|
200
|
+
params.set(key, String(value));
|
|
201
|
+
}
|
|
219
202
|
url = `${url}?${params}`;
|
|
220
|
-
|
|
221
203
|
} else if (options.encoding === 'multipart') {
|
|
222
|
-
|
|
223
|
-
debug && console.log("[api] Multipart request", data);
|
|
204
|
+
debug && console.log('[api] Multipart request', data);
|
|
224
205
|
// Browser will automatically choose the right headers
|
|
225
206
|
config.body = toMultipart(data);
|
|
226
|
-
|
|
227
207
|
} else {
|
|
228
|
-
|
|
208
|
+
headers.set('Content-Type', 'application/json');
|
|
229
209
|
config.body = JSON.stringify(data);
|
|
230
210
|
}
|
|
231
211
|
}
|
|
232
|
-
|
|
212
|
+
|
|
233
213
|
return { url, config };
|
|
234
|
-
}
|
|
235
|
-
|
|
214
|
+
};
|
|
215
|
+
|
|
236
216
|
public execute<TData = unknown>(...args: TFetcherArgs): Promise<TData> {
|
|
237
217
|
const { url, config } = this.configure(...args);
|
|
238
218
|
|
|
239
219
|
console.log(`[api] Fetching`, url, config);
|
|
240
|
-
|
|
220
|
+
|
|
241
221
|
return fetch(url, config)
|
|
242
222
|
.then(async (response) => {
|
|
243
223
|
if (!response.ok) {
|
|
244
|
-
|
|
245
224
|
const errorData = await response.json();
|
|
246
225
|
console.warn(`[api] Failure:`, response.status, errorData);
|
|
247
226
|
const error = errorFromJson(errorData);
|
|
248
227
|
throw error;
|
|
249
228
|
}
|
|
250
|
-
const json = await response.json() as TData;
|
|
229
|
+
const json = (await response.json()) as TData;
|
|
251
230
|
debug && console.log(`[api] Success:`, json);
|
|
252
231
|
return json;
|
|
253
232
|
})
|
|
@@ -263,5 +242,4 @@ export default class ApiClient implements ApiClientService {
|
|
|
263
242
|
}
|
|
264
243
|
});
|
|
265
244
|
}
|
|
266
|
-
|
|
267
245
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createBrowserHistory } from 'history';
|
|
2
2
|
export type { Update } from 'history';
|
|
3
3
|
|
|
4
|
-
export const history =
|
|
5
|
-
export const location = history?.location;
|
|
4
|
+
export const history = typeof window !== 'undefined' ? createBrowserHistory() : undefined;
|
|
5
|
+
export const location = history?.location;
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
----------------------------------*/
|
|
4
4
|
|
|
5
5
|
// Npm
|
|
6
|
-
import { Location } from 'history';
|
|
6
|
+
import { Location } from 'history';
|
|
7
7
|
|
|
8
|
-
// Core
|
|
8
|
+
// Core
|
|
9
9
|
import BaseRequest from '@common/router/request';
|
|
10
10
|
|
|
11
11
|
// Specific
|
|
@@ -17,24 +17,21 @@ import type ClientResponse from '../response';
|
|
|
17
17
|
- TYPES
|
|
18
18
|
----------------------------------*/
|
|
19
19
|
|
|
20
|
-
|
|
21
20
|
/*----------------------------------
|
|
22
21
|
- ROUTER
|
|
23
22
|
----------------------------------*/
|
|
24
23
|
// Since we do SSR, the server router can also be passed here
|
|
25
|
-
export default class ClientRequest<TRouter extends ClientRouter = ClientRouter
|
|
26
|
-
|
|
24
|
+
export default class ClientRequest<TRouter extends ClientRouter<any, any> = ClientRouter<any, any>> extends BaseRequest {
|
|
27
25
|
public api: ApiClient;
|
|
28
26
|
public response?: ClientResponse<TRouter>;
|
|
29
27
|
|
|
30
28
|
public hash?: string;
|
|
31
29
|
|
|
32
|
-
public constructor(
|
|
33
|
-
location: Location,
|
|
30
|
+
public constructor(
|
|
31
|
+
location: Location,
|
|
34
32
|
public router: TRouter,
|
|
35
|
-
public app = router.app
|
|
33
|
+
public app = router.app,
|
|
36
34
|
) {
|
|
37
|
-
|
|
38
35
|
super(location.pathname);
|
|
39
36
|
|
|
40
37
|
this.host = window.location.host;
|
|
@@ -44,10 +41,14 @@ export default class ClientRequest<TRouter extends ClientRouter = ClientRouter>
|
|
|
44
41
|
// Extract search params
|
|
45
42
|
if (location.search) {
|
|
46
43
|
this.url += location.search;
|
|
47
|
-
this.data = Object.fromEntries(
|
|
44
|
+
this.data = Object.fromEntries(new URLSearchParams(location.search));
|
|
48
45
|
}
|
|
49
|
-
|
|
46
|
+
|
|
50
47
|
// Request services
|
|
51
48
|
this.api = new ApiClient(this.app, this);
|
|
52
49
|
}
|
|
53
|
-
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const isClientRequest = <TRouter extends ClientRouter<any, any> = ClientRouter<any, any>>(
|
|
53
|
+
request: unknown,
|
|
54
|
+
): request is ClientRequest<TRouter> => request instanceof ClientRequest;
|
|
@@ -9,65 +9,94 @@ import { TPostData } from '@common/router/request/api';
|
|
|
9
9
|
- TYPES
|
|
10
10
|
----------------------------------*/
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
type TMultipartPrimitive = PrimitiveValue | null | undefined;
|
|
13
|
+
type TMultipartValue =
|
|
14
|
+
| TMultipartPrimitive
|
|
15
|
+
| Blob
|
|
16
|
+
| Date
|
|
17
|
+
| FileList
|
|
18
|
+
| TMultipartValue[]
|
|
19
|
+
| { [key: string]: TMultipartValue };
|
|
20
|
+
|
|
21
|
+
type TMultipartObject = { [key: string]: TMultipartValue };
|
|
22
|
+
|
|
23
|
+
function mergeObjects<TObject extends object>(object1: TObject, object2: Partial<TObject>): TObject {
|
|
24
|
+
return [object1, object2].reduce<TObject>((carry, objectToMerge) => {
|
|
25
|
+
Object.keys(objectToMerge).forEach((objectKey) => {
|
|
26
|
+
carry[objectKey as keyof TObject] = objectToMerge[objectKey as keyof TObject] as TObject[keyof TObject];
|
|
16
27
|
});
|
|
17
28
|
return carry;
|
|
18
|
-
}, {});
|
|
29
|
+
}, { ...object1 });
|
|
19
30
|
}
|
|
20
31
|
|
|
21
|
-
function isArray(val) {
|
|
22
|
-
|
|
23
|
-
return ({}).toString.call(val) === '[object Array]';
|
|
32
|
+
function isArray(val: unknown): val is TMultipartValue[] {
|
|
33
|
+
return Array.isArray(val);
|
|
24
34
|
}
|
|
25
35
|
|
|
26
|
-
function isJsonObject(val) {
|
|
27
|
-
|
|
36
|
+
function isJsonObject(val: unknown): val is TMultipartObject {
|
|
28
37
|
return !isArray(val) && typeof val === 'object' && !!val && !(val instanceof Blob) && !(val instanceof Date);
|
|
29
38
|
}
|
|
30
39
|
|
|
31
|
-
function isAppendFunctionPresent(formData) {
|
|
32
|
-
|
|
40
|
+
function isAppendFunctionPresent(formData: FormData) {
|
|
33
41
|
return typeof formData.append === 'function';
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
function isGlobalFormDataPresent() {
|
|
37
|
-
|
|
38
45
|
return typeof FormData === 'function';
|
|
39
46
|
}
|
|
40
47
|
|
|
41
|
-
function getDefaultFormData() {
|
|
48
|
+
function getDefaultFormData(): FormData {
|
|
49
|
+
if (isGlobalFormDataPresent()) return new FormData();
|
|
42
50
|
|
|
43
|
-
|
|
44
|
-
|
|
51
|
+
throw new Error('FormData is not available in the current environment.');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function appendValue(formData: FormData, key: string, value: TMultipartValue, options: TOptions) {
|
|
55
|
+
if (value instanceof FileList) {
|
|
56
|
+
for (let index = 0; index < value.length; index++) {
|
|
57
|
+
const file = value.item(index);
|
|
58
|
+
if (file) formData.append(`${key}[${index}]`, file, file.name);
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (value instanceof Blob) {
|
|
64
|
+
const filename = value instanceof File ? value.name : undefined;
|
|
65
|
+
if (filename) formData.append(key, value, filename);
|
|
66
|
+
else formData.append(key, value);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (value instanceof Date) {
|
|
71
|
+
formData.append(key, value.toISOString());
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (((value === null && options.includeNullValues) || value !== null) && value !== undefined) {
|
|
76
|
+
formData.append(key, String(value));
|
|
45
77
|
}
|
|
46
78
|
}
|
|
47
79
|
|
|
48
80
|
function convertRecursively(
|
|
49
|
-
jsonObject:
|
|
50
|
-
options: TOptions,
|
|
51
|
-
formData: FormData,
|
|
52
|
-
parentKey: string
|
|
81
|
+
jsonObject: TMultipartObject | TMultipartValue[],
|
|
82
|
+
options: TOptions,
|
|
83
|
+
formData: FormData,
|
|
84
|
+
parentKey: string,
|
|
53
85
|
) {
|
|
86
|
+
let index = 0;
|
|
54
87
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
for (var key in jsonObject) {
|
|
58
|
-
|
|
88
|
+
for (const key in jsonObject) {
|
|
59
89
|
if (jsonObject.hasOwnProperty(key)) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
90
|
+
let propName = parentKey || key;
|
|
91
|
+
const rawValue = isArray(jsonObject) ? jsonObject[Number(key)] : jsonObject[key];
|
|
92
|
+
const value = options.mapping(rawValue);
|
|
63
93
|
|
|
64
94
|
if (parentKey && isJsonObject(jsonObject)) {
|
|
65
95
|
propName = parentKey + '[' + key + ']';
|
|
66
96
|
}
|
|
67
97
|
|
|
68
98
|
if (parentKey && isArray(jsonObject)) {
|
|
69
|
-
|
|
70
|
-
if (isArray(value) || options.showLeafArrayIndexes ) {
|
|
99
|
+
if (isArray(value) || options.showLeafArrayIndexes) {
|
|
71
100
|
propName = parentKey + '[' + index + ']';
|
|
72
101
|
} else {
|
|
73
102
|
propName = parentKey + '[]';
|
|
@@ -75,25 +104,9 @@ function convertRecursively(
|
|
|
75
104
|
}
|
|
76
105
|
|
|
77
106
|
if (isArray(value) || isJsonObject(value)) {
|
|
78
|
-
|
|
79
107
|
convertRecursively(value, options, formData, propName);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
for (var j = 0; j < value.length; j++) {
|
|
84
|
-
formData.append(propName + '[' + j + ']', value.item(j));
|
|
85
|
-
}
|
|
86
|
-
} else if (value instanceof Blob) {
|
|
87
|
-
|
|
88
|
-
formData.append(propName, value, value.name);
|
|
89
|
-
|
|
90
|
-
} else if (value instanceof Date) {
|
|
91
|
-
|
|
92
|
-
formData.append(propName, value.toISOString());
|
|
93
|
-
|
|
94
|
-
} else if (((value === null && options.includeNullValues) || value !== null) && value !== undefined) {
|
|
95
|
-
|
|
96
|
-
formData.append(propName, value);
|
|
108
|
+
} else {
|
|
109
|
+
appendValue(formData, propName, value, options);
|
|
97
110
|
}
|
|
98
111
|
}
|
|
99
112
|
index++;
|
|
@@ -111,37 +124,34 @@ function convertRecursively(
|
|
|
111
124
|
|
|
112
125
|
// options type
|
|
113
126
|
type TOptions = {
|
|
114
|
-
initialFormData: FormData
|
|
115
|
-
showLeafArrayIndexes: boolean
|
|
116
|
-
includeNullValues: boolean
|
|
117
|
-
mapping: (value:
|
|
118
|
-
}
|
|
127
|
+
initialFormData: FormData;
|
|
128
|
+
showLeafArrayIndexes: boolean;
|
|
129
|
+
includeNullValues: boolean;
|
|
130
|
+
mapping: (value: TMultipartValue) => TMultipartValue;
|
|
131
|
+
};
|
|
119
132
|
|
|
120
133
|
export const toMultipart = (jsonObject: TPostData, options?: TOptions) => {
|
|
121
|
-
|
|
122
134
|
if (options && options.initialFormData) {
|
|
123
|
-
|
|
124
135
|
if (!isAppendFunctionPresent(options.initialFormData)) {
|
|
125
136
|
throw 'initialFormData must have an append function.';
|
|
126
137
|
}
|
|
127
138
|
} else if (!isGlobalFormDataPresent()) {
|
|
128
|
-
|
|
129
139
|
throw 'This environment does not have global form data. options.initialFormData must be specified.';
|
|
130
140
|
}
|
|
131
141
|
|
|
132
|
-
|
|
142
|
+
const defaultOptions: TOptions = {
|
|
133
143
|
initialFormData: getDefaultFormData(),
|
|
134
144
|
showLeafArrayIndexes: true,
|
|
135
145
|
includeNullValues: false,
|
|
136
|
-
mapping: function(value) {
|
|
146
|
+
mapping: function (value) {
|
|
137
147
|
if (typeof value === 'boolean') {
|
|
138
|
-
return
|
|
148
|
+
return value ? '1' : '0';
|
|
139
149
|
}
|
|
140
150
|
return value;
|
|
141
|
-
}
|
|
151
|
+
},
|
|
142
152
|
};
|
|
143
153
|
|
|
144
|
-
|
|
154
|
+
const mergedOptions = mergeObjects(defaultOptions, options || {});
|
|
145
155
|
|
|
146
|
-
return convertRecursively(jsonObject, mergedOptions, mergedOptions.initialFormData);
|
|
147
|
-
}
|
|
156
|
+
return convertRecursively(jsonObject as TMultipartObject, mergedOptions, mergedOptions.initialFormData, '');
|
|
157
|
+
};
|