proteum 1.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.
Files changed (156) hide show
  1. package/.dockerignore +10 -0
  2. package/Rte.zip +0 -0
  3. package/cli/app/config.ts +54 -0
  4. package/cli/app/index.ts +195 -0
  5. package/cli/bin.js +11 -0
  6. package/cli/commands/build.ts +34 -0
  7. package/cli/commands/deploy/app.ts +29 -0
  8. package/cli/commands/deploy/web.ts +60 -0
  9. package/cli/commands/dev.ts +109 -0
  10. package/cli/commands/init.ts +85 -0
  11. package/cli/compiler/client/identite.ts +72 -0
  12. package/cli/compiler/client/index.ts +334 -0
  13. package/cli/compiler/common/babel/index.ts +170 -0
  14. package/cli/compiler/common/babel/plugins/index.ts +0 -0
  15. package/cli/compiler/common/babel/plugins/services.ts +579 -0
  16. package/cli/compiler/common/babel/routes/imports.ts +127 -0
  17. package/cli/compiler/common/babel/routes/routes.ts +1130 -0
  18. package/cli/compiler/common/files/autres.ts +39 -0
  19. package/cli/compiler/common/files/images.ts +35 -0
  20. package/cli/compiler/common/files/style.ts +78 -0
  21. package/cli/compiler/common/index.ts +154 -0
  22. package/cli/compiler/index.ts +532 -0
  23. package/cli/compiler/server/index.ts +211 -0
  24. package/cli/index.ts +189 -0
  25. package/cli/paths.ts +165 -0
  26. package/cli/print.ts +12 -0
  27. package/cli/tsconfig.json +38 -0
  28. package/cli/utils/index.ts +22 -0
  29. package/cli/utils/keyboard.ts +78 -0
  30. package/client/app/component.tsx +54 -0
  31. package/client/app/index.ts +142 -0
  32. package/client/app/service.ts +34 -0
  33. package/client/app.tsconfig.json +28 -0
  34. package/client/components/Button.tsx +298 -0
  35. package/client/components/Dialog/Manager.tsx +309 -0
  36. package/client/components/Dialog/card.tsx +208 -0
  37. package/client/components/Dialog/index.less +151 -0
  38. package/client/components/Dialog/status.less +176 -0
  39. package/client/components/Dialog/status.tsx +48 -0
  40. package/client/components/index.ts +2 -0
  41. package/client/components/types.d.ts +3 -0
  42. package/client/data/input.ts +32 -0
  43. package/client/global.d.ts +5 -0
  44. package/client/hooks.ts +22 -0
  45. package/client/index.ts +6 -0
  46. package/client/pages/_layout/index.less +6 -0
  47. package/client/pages/_layout/index.tsx +43 -0
  48. package/client/pages/bug.tsx.old +60 -0
  49. package/client/pages/useHeader.tsx +50 -0
  50. package/client/services/captcha/index.ts +67 -0
  51. package/client/services/router/components/Link.tsx +46 -0
  52. package/client/services/router/components/Page.tsx +55 -0
  53. package/client/services/router/components/router.tsx +218 -0
  54. package/client/services/router/index.tsx +521 -0
  55. package/client/services/router/request/api.ts +267 -0
  56. package/client/services/router/request/history.ts +5 -0
  57. package/client/services/router/request/index.ts +53 -0
  58. package/client/services/router/request/multipart.ts +147 -0
  59. package/client/services/router/response/index.tsx +128 -0
  60. package/client/services/router/response/page.ts +86 -0
  61. package/client/services/socket/index.ts +147 -0
  62. package/client/utils/dom.ts +77 -0
  63. package/common/app/index.ts +9 -0
  64. package/common/data/chaines/index.ts +54 -0
  65. package/common/data/dates.ts +179 -0
  66. package/common/data/markdown.ts +73 -0
  67. package/common/data/rte/nodes.ts +83 -0
  68. package/common/data/stats.ts +90 -0
  69. package/common/errors/index.tsx +326 -0
  70. package/common/router/index.ts +213 -0
  71. package/common/router/layouts.ts +93 -0
  72. package/common/router/register.ts +55 -0
  73. package/common/router/request/api.ts +77 -0
  74. package/common/router/request/index.ts +35 -0
  75. package/common/router/response/index.ts +45 -0
  76. package/common/router/response/page.ts +128 -0
  77. package/common/utils/rte.ts +183 -0
  78. package/common/utils.ts +7 -0
  79. package/doc/TODO.md +71 -0
  80. package/doc/front/router.md +27 -0
  81. package/doc/workspace/workspace.png +0 -0
  82. package/doc/workspace/workspace2.png +0 -0
  83. package/doc/workspace/workspace_26.01.22.png +0 -0
  84. package/package.json +171 -0
  85. package/server/app/commands.ts +141 -0
  86. package/server/app/container/config.ts +203 -0
  87. package/server/app/container/console/index.ts +550 -0
  88. package/server/app/container/index.ts +137 -0
  89. package/server/app/index.ts +273 -0
  90. package/server/app/service/container.ts +88 -0
  91. package/server/app/service/index.ts +235 -0
  92. package/server/app.tsconfig.json +28 -0
  93. package/server/context.ts +4 -0
  94. package/server/index.ts +4 -0
  95. package/server/services/auth/index.ts +250 -0
  96. package/server/services/auth/old.ts +277 -0
  97. package/server/services/auth/router/index.ts +95 -0
  98. package/server/services/auth/router/request.ts +54 -0
  99. package/server/services/auth/router/service.json +6 -0
  100. package/server/services/auth/service.json +6 -0
  101. package/server/services/cache/commands.ts +41 -0
  102. package/server/services/cache/index.ts +297 -0
  103. package/server/services/cache/service.json +6 -0
  104. package/server/services/cron/CronTask.ts +86 -0
  105. package/server/services/cron/index.ts +112 -0
  106. package/server/services/cron/service.json +6 -0
  107. package/server/services/disks/driver.ts +103 -0
  108. package/server/services/disks/drivers/local/index.ts +188 -0
  109. package/server/services/disks/drivers/local/service.json +6 -0
  110. package/server/services/disks/drivers/s3/index.ts +301 -0
  111. package/server/services/disks/drivers/s3/service.json +6 -0
  112. package/server/services/disks/index.ts +90 -0
  113. package/server/services/disks/service.json +6 -0
  114. package/server/services/email/index.ts +188 -0
  115. package/server/services/email/utils.ts +53 -0
  116. package/server/services/fetch/index.ts +201 -0
  117. package/server/services/fetch/service.json +7 -0
  118. package/server/services/models.7z +0 -0
  119. package/server/services/prisma/Facet.ts +142 -0
  120. package/server/services/prisma/index.ts +201 -0
  121. package/server/services/prisma/service.json +6 -0
  122. package/server/services/router/http/index.ts +217 -0
  123. package/server/services/router/http/multipart.ts +102 -0
  124. package/server/services/router/http/session.ts.old +40 -0
  125. package/server/services/router/index.ts +801 -0
  126. package/server/services/router/request/api.ts +87 -0
  127. package/server/services/router/request/index.ts +184 -0
  128. package/server/services/router/request/service.ts +21 -0
  129. package/server/services/router/request/validation/zod.ts +180 -0
  130. package/server/services/router/response/index.ts +338 -0
  131. package/server/services/router/response/mask/Filter.ts +323 -0
  132. package/server/services/router/response/mask/index.ts +60 -0
  133. package/server/services/router/response/mask/selecteurs.ts +92 -0
  134. package/server/services/router/response/page/document.tsx +160 -0
  135. package/server/services/router/response/page/index.tsx +196 -0
  136. package/server/services/router/service.json +6 -0
  137. package/server/services/router/service.ts +36 -0
  138. package/server/services/schema/index.ts +44 -0
  139. package/server/services/schema/request.ts +49 -0
  140. package/server/services/schema/router/index.ts +28 -0
  141. package/server/services/schema/router/service.json +6 -0
  142. package/server/services/schema/service.json +6 -0
  143. package/server/services/security/encrypt/aes/index.ts +85 -0
  144. package/server/services/security/encrypt/aes/service.json +6 -0
  145. package/server/services/socket/index.ts +162 -0
  146. package/server/services/socket/scope.ts +226 -0
  147. package/server/services/socket/service.json +6 -0
  148. package/server/services_old/SocketClient.ts +92 -0
  149. package/server/services_old/Token.old.ts +97 -0
  150. package/server/utils/slug.ts +79 -0
  151. package/tsconfig.common.json +45 -0
  152. package/tsconfig.json +3 -0
  153. package/types/aliases.d.ts +54 -0
  154. package/types/global/modules.d.ts +49 -0
  155. package/types/global/utils.d.ts +103 -0
  156. package/types/icons.d.ts +1 -0
@@ -0,0 +1,188 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ // Core
6
+ import type { Application } from '@server/app';
7
+ import Service from '@server/app/service';
8
+ import markdown from '@common/data/markdown';
9
+
10
+ // Speciic
11
+ import { jsonToHtml } from './utils';
12
+
13
+ /*----------------------------------
14
+ - SERVICE CONFIG
15
+ ----------------------------------*/
16
+
17
+ const LogPrefix = `[services][email]`
18
+
19
+ export type Config = {
20
+ debug: boolean,
21
+ simulateWhenLocal: boolean,
22
+ default: {
23
+ from: TPerson
24
+ },
25
+ bugReport: {
26
+ from: TPerson,
27
+ to: TPerson
28
+ },
29
+ }
30
+
31
+ export type Hooks = {
32
+
33
+ }
34
+
35
+ export type Services = {
36
+
37
+ }
38
+
39
+ /*----------------------------------
40
+ - TYPES: EMAILS
41
+ ----------------------------------*/
42
+
43
+ export type TEmail = THtmlEmail | TMarkdownEmail;
44
+
45
+ type TPerson = {
46
+ name?: string,
47
+ email: string
48
+ }
49
+
50
+ type TBaseEmail = {
51
+ to: TPerson | TPerson[],
52
+ cc?: TPerson | TPerson[]
53
+ from?: TPerson,
54
+ };
55
+
56
+ export type THtmlEmail = TBaseEmail & {
57
+ subject: string,
58
+ html: string | { [label: string]: any },
59
+ }
60
+
61
+ export type TMarkdownEmail = TBaseEmail & {
62
+ subject: string,
63
+ markdown: string,
64
+ }
65
+
66
+ export type TCompleteEmail = With<THtmlEmail, {
67
+ to: TPerson[],
68
+ from: TPerson,
69
+ cc: TPerson[]
70
+ }>;
71
+
72
+ type TShortEmailSendArgs = [
73
+ to: string,
74
+ subject: string,
75
+ markdown: string,
76
+ options?: TOptions
77
+ ]
78
+
79
+ type TCompleteEmailSendArgs = [
80
+ emails: TEmail | TEmail[],
81
+ options?: TOptions
82
+ ]
83
+
84
+ type TEmailSendArgs = TShortEmailSendArgs | TCompleteEmailSendArgs;
85
+
86
+ /*----------------------------------
87
+ - TYPES: OPTIONS
88
+ ----------------------------------*/
89
+ type TOptions = {
90
+ transporter?: string
91
+ }
92
+
93
+ /*----------------------------------
94
+ - FONCTIONS
95
+ ----------------------------------*/
96
+ export default abstract class Email<TConfig extends Config>
97
+ extends Service<TConfig, Hooks, Application, Application> {
98
+
99
+ /*----------------------------------
100
+ - ACTIONS
101
+ ----------------------------------*/
102
+
103
+ protected abstract sendNow( emails: TCompleteEmail[] ): Promise<void>;
104
+
105
+ public async send( to: string, subject: string, markdown: string, options?: TOptions );
106
+ public async send( emails: TEmail | TEmail[], options?: TOptions ): Promise<void>;
107
+ public async send( ...args: TEmailSendArgs ): Promise<void> {
108
+
109
+ let emails: TEmail[] | TEmail;
110
+ let options: TOptions | undefined;
111
+ if (typeof args[0] === 'string') {
112
+ const [to, subject, markdown, opts] = args as TShortEmailSendArgs;
113
+ emails = [{
114
+ to: { email: to },
115
+ subject,
116
+ markdown
117
+ }]
118
+ options = opts;
119
+ } else {
120
+
121
+ ([ emails, options ] = args as TCompleteEmailSendArgs);
122
+ if (!Array.isArray( emails ))
123
+ emails = [emails];
124
+ else if (emails.length === 0)
125
+ return console.warn(LogPrefix, `No email to send.`);
126
+ }
127
+
128
+ options = options || {}
129
+
130
+ this.config.debug && console.log(`Preparing to send ${emails.length} emails ...`);
131
+
132
+ const htmlWarning = this.app.env.profile === 'dev'
133
+ ? `⚠️ This email has been sent for testing purposes. Please ignore it if you're not a developer.<br /><br />`
134
+ : '';
135
+
136
+ const emailsToSend: TCompleteEmail[] = emails.map(email => {
137
+
138
+ const from: TPerson = email.from === undefined
139
+ ? this.config.default.from
140
+ : email.from;
141
+
142
+ const cc: TPerson[] = email.cc === undefined ? [] : Array.isArray(email.cc)
143
+ ? email.cc
144
+ : [email.cc];
145
+
146
+ const to: TPerson[] = Array.isArray(email.to)
147
+ ? email.to
148
+ : [email.to];
149
+
150
+ if ('markdown' in email) {
151
+
152
+ return {
153
+ ...email,
154
+ html: htmlWarning + markdown.render(email.markdown),
155
+ from,
156
+ to,
157
+ cc
158
+ }
159
+
160
+ } else {
161
+ return {
162
+ ...email,
163
+ html: htmlWarning + (typeof email.html === "string"
164
+ ? email.html
165
+ : jsonToHtml(email.html)),
166
+ from,
167
+ to,
168
+ cc
169
+ }
170
+ }
171
+
172
+ });
173
+
174
+ console.info(LogPrefix, `Sending ${emailsToSend.length} emails:`, emailsToSend[0].subject);
175
+
176
+ // Pas d'envoi d'email quand local
177
+ if (this.app.env.profile !== 'prod' && this.config.simulateWhenLocal === true) {
178
+ console.log(LogPrefix, `Simulate email sending:\n`, emailsToSend[0].html);
179
+ return;
180
+ } else if (emailsToSend.length === 0) {
181
+ console.warn(LogPrefix, `No email to send.`);
182
+ return;
183
+ }
184
+
185
+ await this.sendNow(emailsToSend);
186
+
187
+ }
188
+ }
@@ -0,0 +1,53 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+ // Npm
5
+ import dayjs from 'dayjs';
6
+ import escapehtml from 'escape-html';
7
+
8
+ /*----------------------------------
9
+ - FONCTIONS
10
+ ----------------------------------*/
11
+ export const espacesVersHtml = (txt: string) => txt
12
+ .replace(/(\n|\r)/g, '<br>') // Sauts de ligne
13
+ .replace(/(\t)/g, '&nbsp;'.repeat(8)) // Tabulations
14
+ .replace(/ /g, '&nbsp;') // Espaces
15
+ /*.replace(/\"/g, '"') // Doubles quotes
16
+ .replace(/\'/g, "'") // Doubles quotes*/
17
+
18
+ export const jsonToHtml = (objet: {[cle: string]: any}, complet: boolean = false): string => {
19
+
20
+ let html: string[] = [];
21
+
22
+ for (const label in objet) {
23
+
24
+ let valeur = objet[label];
25
+
26
+ if (valeur === undefined || valeur === null) {
27
+
28
+ if (complet === true)
29
+ // quand undefined, JSON.stringify retourne aussi undefined
30
+ valeur = valeur === undefined
31
+ ? 'undefined'
32
+ : JSON.stringify(valeur, null, 4);
33
+ else
34
+ continue;
35
+
36
+ } else if (typeof valeur === 'object') {
37
+
38
+ if (valeur instanceof Date)
39
+ valeur = dayjs(valeur).format('DD/MM/YYYY HH:mm:ss');
40
+
41
+ }
42
+
43
+ if (!valeur || typeof valeur !== 'string')
44
+ valeur = JSON.stringify(valeur, null, 4);
45
+
46
+ html.push(
47
+ '<b>' + label + ':</b> ' + espacesVersHtml(escapehtml(valeur))
48
+ );
49
+ }
50
+
51
+ return html.join('<br>');
52
+
53
+ }
@@ -0,0 +1,201 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ // Npm
6
+ import type { default as sharp, Sharp } from 'sharp';
7
+ import fs from 'fs-extra';
8
+ import got, { Method, Options } from 'got';
9
+
10
+ // Node
11
+ import request from 'request';
12
+
13
+ // Core: general
14
+ import type { Application } from '@server/app';
15
+ import Service, { AnyService } from '@server/app/service';
16
+ import { viaHttpCode } from '@common/errors';
17
+
18
+ // Local
19
+ import type { TAnyRouter } from '../router';
20
+ import type DisksManager from '../disks';
21
+ import type FsDriver from '../disks/driver';
22
+
23
+ /*----------------------------------
24
+ - SERVICE TYPES
25
+ ----------------------------------*/
26
+
27
+ export type Config = {
28
+ debug?: boolean,
29
+ disk?: string,
30
+
31
+ disks: DisksManager,
32
+ router?: TAnyRouter
33
+ }
34
+
35
+ export type Hooks = {
36
+
37
+ }
38
+
39
+ /*----------------------------------
40
+ - TYPES
41
+ ----------------------------------*/
42
+
43
+ export type TImageConfig = {
44
+ sharp: typeof sharp,
45
+ width: number,
46
+ height: number,
47
+ fit: keyof sharp.FitEnum,
48
+ quality: number
49
+ }
50
+
51
+ /*----------------------------------
52
+ - CONST
53
+ ----------------------------------*/
54
+
55
+ const LogPrefix = `[services][fetch]`
56
+
57
+ /*----------------------------------
58
+ - SERVICE
59
+ - Tools that helps to consume external resources (including apis, ..)
60
+ -----------------------------------*/
61
+ export default class FetchService extends Service<Config, Hooks, Application, Application> {
62
+
63
+ private disk?: FsDriver;
64
+
65
+ public async ready() {
66
+
67
+ if (this.config.disks)
68
+ this.disk = this.config.disks.get( this.config.disk );
69
+
70
+ }
71
+
72
+ public async shutdown() {
73
+
74
+ }
75
+
76
+ /*----------------------------------
77
+ - EXTERNAL API REQUESTS
78
+ ----------------------------------*/
79
+
80
+ public post(
81
+ url: string,
82
+ data: {[k: string]: any},
83
+ options: {} = {}
84
+ ) {
85
+
86
+ return this.request('POST', url, data, options);
87
+
88
+ }
89
+
90
+ public async request(
91
+ method: Method,
92
+ url: string,
93
+ data: {[k: string]: any},
94
+ options: Options = {}
95
+ ) {
96
+
97
+ // Parse url if router service is provided
98
+ if (this.config.router === undefined)
99
+ throw new Error(`Please bind the Router service to the Fetch service in order to contact APIs.`);
100
+
101
+ url = this.config.router.url(url);
102
+
103
+ // Send request
104
+ const res = await got(url, {
105
+ throwHttpErrors: false,
106
+ headers: {
107
+ 'Accept': 'application/json',
108
+ },
109
+ method,
110
+ ...(method === 'GET' ? {
111
+ searchParams: data
112
+ } : {
113
+ json: data
114
+ })
115
+ })
116
+
117
+ // Handle errors
118
+ if (res.statusCode !== 200) {
119
+
120
+ // Instanciate error from HTTP code
121
+ const error = viaHttpCode( res.statusCode, res.body );
122
+ if (error)
123
+ throw error;
124
+
125
+ // Not catched via viaHttpCode
126
+ console.log("RESPONSE", res.body);
127
+ throw new Error("Error while contacting the API");
128
+ }
129
+
130
+ // Format & return response
131
+ return JSON.parse( res.body );
132
+ }
133
+
134
+ /*----------------------------------
135
+ - IMAGES
136
+ ----------------------------------*/
137
+
138
+ public toBuffer( uri: string ): Promise<Buffer> {
139
+ return new Promise<Buffer>((resolve, reject) => {
140
+ request(uri, { encoding: null }, (err, res, body) => {
141
+
142
+ if (err)
143
+ return reject(err);
144
+
145
+ if (!body)
146
+ return reject(`Body is empty for ${uri}.`);
147
+
148
+ resolve(body);
149
+ })
150
+ })
151
+ }
152
+
153
+ public async image(
154
+ imageFileUrl: string,
155
+ imageMod: TImageConfig,
156
+ saveToBucket: string,
157
+ saveToPath?: string,
158
+ disk?: string
159
+ ): Promise<Buffer | null> {
160
+
161
+ // Define target disk
162
+ if (this.disk === undefined)
163
+ throw new Error(`Please provide a Disks service in order to download files.`);
164
+
165
+ // Download
166
+ let imageBuffer: Buffer | null;
167
+ try {
168
+ imageBuffer = await this.toBuffer( imageFileUrl );
169
+ } catch (error) {
170
+ console.error(LogPrefix, `Error while fetching image at ${imageFileUrl}:`, error);
171
+ return null;
172
+ }
173
+
174
+ if (imageMod) {
175
+
176
+ const { sharp, width, height, fit, quality } = imageMod;
177
+
178
+ // Resize
179
+ const processing = sharp( imageBuffer )
180
+ // Max dimensions (save space)
181
+ .resize(width, height, { fit })
182
+
183
+ // Convert to webp and finalize
184
+ imageBuffer = await processing.webp({ quality }).toBuffer().catch(e => {
185
+ console.error(LogPrefix, `Error while processing image at ${imageFileUrl}:`, e);
186
+ return null;
187
+ })
188
+
189
+ }
190
+
191
+ // Save file
192
+ if (saveToPath !== undefined && imageBuffer !== null) {
193
+ console.log(LogPrefix, `Saving ${imageFileUrl} logo to ${saveToPath}`);
194
+ await this.disk.outputFile(saveToBucket, saveToPath, imageBuffer);
195
+ }
196
+
197
+ // We return the original, because Vibrant.js doesn't support webp
198
+ return imageBuffer;
199
+ }
200
+
201
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "id": "Core/Fetch",
3
+ "name": "Fetch",
4
+ "parent": "app",
5
+ "dependences": [],
6
+ "priority": 0
7
+ }
Binary file
@@ -0,0 +1,142 @@
1
+ import type { Prisma, PrismaClient } from '@models/types';
2
+ import * as runtime from '@/var/prisma/runtime/library.js';
3
+
4
+ /*export type TDelegate<R> = {
5
+ findMany(args?: any): Promise<R[]>
6
+ findFirst(args?: any): Promise<R | null>
7
+ }*/
8
+
9
+ /*
10
+
11
+ */
12
+
13
+
14
+ export type TDelegate<R> = PrismaClient[string];
15
+
16
+ /*export type TExtractPayload<D extends TDelegate<never>> =
17
+ D extends { [K in symbol]: { types: { payload: infer P } } } ? P : never;
18
+
19
+ export type TExtractPayload2<D> =
20
+ D extends { [K: symbol]: { types: Prisma.TypeMap<infer E>['model'][infer M] } }
21
+ ? Prisma.TypeMap<E>['model'][M & keyof Prisma.TypeMap<E>['model']]['payload']
22
+ : never;*/
23
+
24
+ export type Transform<S extends TSubset, R, RT> = (
25
+ row: runtime.Types.Result.GetResult<
26
+ Prisma.$ProspectContactLeadPayload,
27
+ ReturnType<S>,
28
+ 'findMany'
29
+ >[number]
30
+ ) => RT
31
+
32
+ export type TWithStats = {
33
+ $table: string,
34
+ $key: string
35
+ } & {
36
+ [key: string]: string // key => SQL
37
+ }
38
+
39
+ export type TSubset = (...a: any[]) => Prisma.ProspectContactLeadFindFirstArgs & {
40
+ withStats?: TWithStats
41
+ }
42
+
43
+ export default class Facet<
44
+ D extends TDelegate<R>,
45
+ S extends TSubset,
46
+ R, // Result type
47
+ RT // Transformed result type
48
+ > {
49
+ constructor(
50
+
51
+ private readonly prisma: PrismaClient,
52
+
53
+ private readonly delegate: D,
54
+ private readonly subset: S,
55
+
56
+ /* the **ONLY** line that changed ↓↓↓ */
57
+ private readonly transform?: Transform<S, R, RT>,
58
+ ) { }
59
+
60
+ public async findMany(
61
+ ...args: Parameters<S>
62
+ ): Promise<RT[]> {
63
+
64
+ const { withStats, ...subset } = this.subset(...args);
65
+
66
+ const results = await this.delegate.findMany(subset);
67
+ if (results.length === 0)
68
+ return [];
69
+
70
+ // Load stats
71
+ const stats = withStats
72
+ ? await this.fetchStats( withStats, results )
73
+ : [];
74
+
75
+ return results.map(row => this.transformResult(row, stats, withStats));
76
+ }
77
+
78
+ public async findFirst(
79
+ ...args: Parameters<S>
80
+ ): Promise<RT | null> {
81
+
82
+ const { withStats, ...subset } = this.subset(...args);
83
+
84
+ const result = await this.delegate.findFirst(subset);
85
+ if (!result)
86
+ return null;
87
+
88
+ const stats = withStats
89
+ ? await this.fetchStats( withStats, [result] )
90
+ : [];
91
+
92
+ return this.transformResult(result, stats, withStats);
93
+ }
94
+
95
+ private async fetchStats(
96
+ { $table, $key, ...withStats }: TWithStats,
97
+ results: any[]
98
+ ): Promise<any[]> {
99
+
100
+ const select = Object.entries(withStats).map(([key, sql]) =>
101
+ `(COALESCE((
102
+ ${sql}
103
+ ), 0)) as ${key}`
104
+ );
105
+
106
+ const stats = await this.prisma.$queryRawUnsafe(`
107
+ SELECT ${$key}, ${select.join(', ')}
108
+ FROM ${$table}
109
+ WHERE ${$key} IN (
110
+ ${results.map(r => "'" + r[ $key ] + "'").join(',')}
111
+ )
112
+ `);
113
+
114
+ for (const stat of stats) {
115
+ for (const key in stat) {
116
+
117
+ if (key === $key)
118
+ continue;
119
+
120
+ stat[key] = stat[key] ? parseInt(stat[key]) : 0;
121
+ }
122
+ }
123
+
124
+ return stats;
125
+ }
126
+
127
+ private transformResult( result: any, stats: any[], withStats?: TWithStats ) {
128
+
129
+ // Transform stats
130
+ const resultStats = withStats
131
+ ? stats.find(stat => stat[withStats.$key] === result[withStats.$key]) || {}
132
+ : {};
133
+
134
+ if (this.transform)
135
+ result = this.transform(result);
136
+
137
+ return {
138
+ ...result,
139
+ ...resultStats
140
+ }
141
+ }
142
+ }