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.
Files changed (184) hide show
  1. package/AGENTS.md +92 -0
  2. package/agents/codex/AGENTS.md +95 -0
  3. package/agents/codex/CODING_STYLE.md +71 -0
  4. package/agents/codex/agents.md.zip +0 -0
  5. package/agents/codex/client/AGENTS.md +102 -0
  6. package/agents/codex/client/pages/AGENTS.md +35 -0
  7. package/agents/codex/server/routes/AGENTS.md +12 -0
  8. package/agents/codex/server/services/AGENTS.md +137 -0
  9. package/agents/codex/tests/AGENTS.md +8 -0
  10. package/cli/app/config.ts +12 -17
  11. package/cli/app/index.ts +59 -99
  12. package/cli/bin.js +1 -1
  13. package/cli/commands/build.ts +23 -12
  14. package/cli/commands/check.ts +19 -0
  15. package/cli/commands/deploy/app.ts +4 -8
  16. package/cli/commands/deploy/web.ts +16 -20
  17. package/cli/commands/dev.ts +185 -75
  18. package/cli/commands/devEvents.ts +106 -0
  19. package/cli/commands/init.ts +63 -57
  20. package/cli/commands/lint.ts +21 -0
  21. package/cli/commands/refresh.ts +6 -6
  22. package/cli/commands/typecheck.ts +18 -0
  23. package/cli/compiler/client/identite.ts +79 -49
  24. package/cli/compiler/client/index.ts +132 -214
  25. package/cli/compiler/common/bundleAnalysis.ts +94 -0
  26. package/cli/compiler/common/clientManifest.ts +67 -0
  27. package/cli/compiler/common/controllers.ts +288 -0
  28. package/cli/compiler/common/files/autres.ts +7 -18
  29. package/cli/compiler/common/files/images.ts +40 -37
  30. package/cli/compiler/common/files/style.ts +12 -25
  31. package/cli/compiler/common/generatedRouteModules.ts +368 -0
  32. package/cli/compiler/common/index.ts +29 -68
  33. package/cli/compiler/common/loaders/forbid-ssr-import.js +13 -0
  34. package/cli/compiler/common/rspackAliases.ts +13 -0
  35. package/cli/compiler/common/scripts.ts +37 -0
  36. package/cli/compiler/index.ts +764 -234
  37. package/cli/compiler/server/index.ts +52 -77
  38. package/cli/compiler/writeIfChanged.ts +21 -0
  39. package/cli/index.ts +65 -90
  40. package/cli/paths.ts +51 -57
  41. package/cli/print.ts +17 -11
  42. package/cli/tsconfig.json +5 -4
  43. package/cli/utils/agents.ts +100 -0
  44. package/cli/utils/check.ts +71 -0
  45. package/cli/utils/index.ts +1 -3
  46. package/cli/utils/keyboard.ts +8 -25
  47. package/cli/utils/runProcess.ts +30 -0
  48. package/client/app/component.tsx +29 -29
  49. package/client/app/index.ts +36 -57
  50. package/client/app/service.ts +7 -12
  51. package/client/app.tsconfig.json +2 -2
  52. package/client/components/Dialog/Manager.ssr.tsx +40 -0
  53. package/client/components/Dialog/Manager.tsx +119 -150
  54. package/client/components/Dialog/status.tsx +3 -3
  55. package/client/components/index.ts +1 -1
  56. package/client/components/types.d.ts +1 -3
  57. package/client/dev/hmr.ts +65 -0
  58. package/client/global.d.ts +2 -2
  59. package/client/hooks.ts +6 -9
  60. package/client/index.ts +2 -1
  61. package/client/islands/index.ts +7 -0
  62. package/client/islands/useDeferredModule.ts +199 -0
  63. package/client/pages/_layout/index.tsx +4 -12
  64. package/client/pages/useHeader.tsx +14 -21
  65. package/client/router.ts +27 -0
  66. package/client/services/router/components/Link.tsx +34 -27
  67. package/client/services/router/components/Page.tsx +6 -14
  68. package/client/services/router/components/router.ssr.tsx +36 -0
  69. package/client/services/router/components/router.tsx +63 -83
  70. package/client/services/router/index.tsx +185 -220
  71. package/client/services/router/request/api.ts +97 -119
  72. package/client/services/router/request/history.ts +2 -2
  73. package/client/services/router/request/index.ts +13 -12
  74. package/client/services/router/request/multipart.ts +72 -62
  75. package/client/services/router/response/index.tsx +68 -61
  76. package/client/services/router/response/page.ts +28 -32
  77. package/client/utils/dom.ts +17 -33
  78. package/common/app/index.ts +3 -3
  79. package/common/data/chaines/index.ts +22 -23
  80. package/common/data/dates.ts +35 -70
  81. package/common/data/markdown.ts +42 -39
  82. package/common/dev/serverHotReload.ts +26 -0
  83. package/common/errors/index.tsx +110 -142
  84. package/common/router/contracts.ts +29 -0
  85. package/common/router/index.ts +89 -108
  86. package/common/router/layouts.ts +34 -47
  87. package/common/router/pageSetup.ts +50 -0
  88. package/common/router/register.ts +53 -24
  89. package/common/router/request/api.ts +30 -36
  90. package/common/router/request/index.ts +2 -8
  91. package/common/router/response/index.ts +8 -15
  92. package/common/router/response/page.ts +70 -58
  93. package/common/utils.ts +1 -1
  94. package/eslint.js +62 -0
  95. package/package.json +12 -45
  96. package/prettier.config.cjs +9 -0
  97. package/scripts/cleanup-generated-controllers.ts +62 -0
  98. package/scripts/fix-reference-app-typing.ts +490 -0
  99. package/scripts/refactor-client-app-imports.ts +244 -0
  100. package/scripts/refactor-client-pages.ts +587 -0
  101. package/scripts/refactor-server-controllers.ts +470 -0
  102. package/scripts/refactor-server-runtime-aliases.ts +360 -0
  103. package/scripts/restore-client-app-import-files.ts +41 -0
  104. package/scripts/restore-files-from-git-head.ts +20 -0
  105. package/scripts/update-codex-agents.ts +35 -0
  106. package/server/app/commands.ts +35 -64
  107. package/server/app/container/config.ts +39 -69
  108. package/server/app/container/console/index.ts +202 -248
  109. package/server/app/container/index.ts +33 -71
  110. package/server/app/controller/index.ts +61 -0
  111. package/server/app/index.ts +39 -105
  112. package/server/app/service/container.ts +41 -42
  113. package/server/app/service/index.ts +120 -147
  114. package/server/context.ts +1 -1
  115. package/server/index.ts +25 -1
  116. package/server/services/auth/index.ts +75 -115
  117. package/server/services/auth/router/index.ts +31 -32
  118. package/server/services/auth/router/request.ts +14 -16
  119. package/server/services/cron/CronTask.ts +13 -26
  120. package/server/services/cron/index.ts +14 -36
  121. package/server/services/disks/driver.ts +40 -58
  122. package/server/services/disks/drivers/local/index.ts +79 -90
  123. package/server/services/disks/drivers/s3/index.ts +116 -163
  124. package/server/services/disks/index.ts +23 -38
  125. package/server/services/email/index.ts +45 -104
  126. package/server/services/email/utils.ts +14 -27
  127. package/server/services/fetch/index.ts +53 -85
  128. package/server/services/prisma/Facet.ts +39 -91
  129. package/server/services/prisma/index.ts +74 -110
  130. package/server/services/router/generatedRuntime.ts +29 -0
  131. package/server/services/router/http/index.ts +77 -72
  132. package/server/services/router/http/multipart.ts +19 -42
  133. package/server/services/router/index.ts +378 -365
  134. package/server/services/router/request/api.ts +26 -25
  135. package/server/services/router/request/index.ts +44 -51
  136. package/server/services/router/request/service.ts +7 -11
  137. package/server/services/router/request/validation/zod.ts +111 -148
  138. package/server/services/router/response/index.ts +110 -125
  139. package/server/services/router/response/mask/Filter.ts +31 -72
  140. package/server/services/router/response/mask/index.ts +8 -15
  141. package/server/services/router/response/mask/selecteurs.ts +11 -25
  142. package/server/services/router/response/page/clientManifest.ts +25 -0
  143. package/server/services/router/response/page/document.tsx +199 -127
  144. package/server/services/router/response/page/index.tsx +89 -94
  145. package/server/services/router/service.ts +13 -15
  146. package/server/services/schema/index.ts +17 -26
  147. package/server/services/schema/request.ts +19 -33
  148. package/server/services/schema/router/index.ts +8 -11
  149. package/server/services/security/encrypt/aes/index.ts +15 -35
  150. package/server/utils/slug.ts +29 -32
  151. package/skills/clean-project-code/SKILL.md +63 -0
  152. package/skills/clean-project-code/agents/openai.yaml +4 -0
  153. package/tsconfig.common.json +4 -3
  154. package/tsconfig.json +4 -1
  155. package/types/aliases.d.ts +17 -21
  156. package/types/controller-input.test.ts +48 -0
  157. package/types/express-extra.d.ts +6 -0
  158. package/types/global/constants.d.ts +1 -0
  159. package/types/global/express-extra.d.ts +6 -0
  160. package/types/global/modules.d.ts +13 -16
  161. package/types/global/utils.d.ts +17 -49
  162. package/types/global/vendors.d.ts +62 -0
  163. package/types/icons.d.ts +65 -1
  164. package/types/uuid.d.ts +3 -0
  165. package/types/vendors.d.ts +62 -0
  166. package/cli/compiler/common/babel/index.ts +0 -173
  167. package/cli/compiler/common/babel/plugins/index.ts +0 -0
  168. package/cli/compiler/common/babel/plugins/services.ts +0 -586
  169. package/cli/compiler/common/babel/routes/imports.ts +0 -127
  170. package/cli/compiler/common/babel/routes/routes.ts +0 -1170
  171. package/client/services/captcha/index.ts +0 -67
  172. package/client/services/socket/index.ts +0 -147
  173. package/common/data/rte/nodes.ts +0 -83
  174. package/common/data/stats.ts +0 -90
  175. package/common/utils/rte.ts +0 -183
  176. package/server/services/auth/old.ts +0 -277
  177. package/server/services/cache/commands.ts +0 -41
  178. package/server/services/cache/index.ts +0 -297
  179. package/server/services/cache/service.json +0 -6
  180. package/server/services/socket/index.ts +0 -162
  181. package/server/services/socket/scope.ts +0 -226
  182. package/server/services/socket/service.json +0 -6
  183. package/server/services_old/SocketClient.ts +0 -92
  184. 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, TPostDataWithoutFile,
10
- TApiFetchOptions, TFetcherList, TFetcherArgs, TFetcher,
11
- TDataReturnedByFetchers
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( newData: TObjetDonnees ) {
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
- if (!('context' in this.router))
59
- throw new Error("api.set is not available on server side.");
60
-
61
- if (this.router.context.page)
62
- this.router.context.page.setAllData(curData => ({ ...curData, ...newData }));
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( ids?: string | string[], params?: TObjetDonnees ) {
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
- ids = Object.keys(page.fetchers);
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
- const fetcher = page.fetchers[id];
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
- if (params !== undefined)
88
- fetcher.data = { ...(fetcher.data || {}), ...params };
79
+ const nextData = { ...page.data };
80
+ for (const id of ids) delete nextData[id];
89
81
 
90
- debug && console.log("[api][reload]", id, fetcher.method, fetcher.path, fetcher.data);
82
+ page.data = nextData;
91
83
 
92
- this.fetchAsync(fetcher.method, fetcher.path, fetcher.data).then((data) => {
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
- ]: TFetcherArgs): Promise<TData> {
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
- for (const fetcherId in fetchers)
151
- // The fetcher can be undefined
152
- if (!( fetcherId in alreadyLoadedData ) && fetchers[ fetcherId ]) {
153
- fetchersToRun[ fetcherId ] = fetchers[ fetcherId ]
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 = fetchersCount === 0
159
- ? 0
160
- : await this.execute("POST", "/api", {
161
- fetchers: fetchersToRun
162
- }).then((res) => {
163
-
164
- const data: TObjetDonnees = {};
165
- for (const id in res)
166
- data[id] = res[id];
167
-
168
- return data;
169
-
170
- }).catch(e => {
171
-
172
- // API Error hook
173
- this.app.handleError(e);
174
-
175
- throw e;
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 config: With<RequestInit, 'headers'> = {
191
- method: method,
192
- headers: {
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
- const hasFile = Object.values(data).some((value) => value instanceof File);
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 === "GET")
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
- // Encoding set to JSON = Can't send files
212
- throw new Error("Cannot send file in non-multipart request");
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 === "GET") {
217
-
218
- const params = new URLSearchParams( data as unknown as TPostDataWithoutFile ).toString();
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
- config.headers["Content-Type"] = "application/json";
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 = (typeof window !== 'undefined') ? createBrowserHistory() : undefined;
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> extends BaseRequest {
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( new URLSearchParams( location.search ));
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
- function mergeObjects(object1, object2) {
13
- return [object1, object2].reduce(function (carry, objectToMerge) {
14
- Object.keys(objectToMerge).forEach(function (objectKey) {
15
- carry[objectKey] = objectToMerge[objectKey];
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
- if (isGlobalFormDataPresent()) {
44
- return new FormData();
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
- var index = 0;
56
-
57
- for (var key in jsonObject) {
58
-
88
+ for (const key in jsonObject) {
59
89
  if (jsonObject.hasOwnProperty(key)) {
60
-
61
- var propName = parentKey || key;
62
- var value = options.mapping(jsonObject[key]);
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
- } else if (value instanceof FileList) {
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: any) => any
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
- var defaultOptions = {
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 +value ? '1': '0';
148
+ return value ? '1' : '0';
139
149
  }
140
150
  return value;
141
- }
151
+ },
142
152
  };
143
153
 
144
- var mergedOptions = mergeObjects(defaultOptions, options || {});
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
+ };