proteum 2.1.0 → 2.1.2
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 +44 -98
- package/README.md +143 -10
- package/agents/framework/AGENTS.md +146 -886
- package/agents/project/AGENTS.md +73 -127
- package/agents/project/client/AGENTS.md +22 -93
- package/agents/project/client/pages/AGENTS.md +24 -26
- package/agents/project/server/routes/AGENTS.md +10 -8
- package/agents/project/server/services/AGENTS.md +22 -159
- package/agents/project/tests/AGENTS.md +11 -8
- package/cli/app/config.ts +7 -20
- package/cli/bin.js +8 -0
- package/cli/commands/command.ts +243 -0
- package/cli/commands/commandLocalRunner.js +198 -0
- package/cli/commands/create.ts +5 -0
- package/cli/commands/deploy/web.ts +1 -2
- package/cli/commands/dev.ts +98 -2
- package/cli/commands/doctor.ts +8 -74
- package/cli/commands/explain.ts +8 -186
- package/cli/commands/init.ts +2 -94
- package/cli/commands/trace.ts +228 -0
- package/cli/compiler/artifacts/commands.ts +217 -0
- package/cli/compiler/artifacts/manifest.ts +35 -21
- package/cli/compiler/artifacts/services.ts +300 -1
- package/cli/compiler/client/index.ts +43 -8
- package/cli/compiler/common/commands.ts +175 -0
- package/cli/compiler/common/index.ts +1 -1
- package/cli/compiler/common/proteumManifest.ts +15 -114
- package/cli/compiler/index.ts +25 -2
- package/cli/compiler/server/index.ts +31 -6
- package/cli/index.ts +1 -4
- package/cli/paths.ts +16 -1
- package/cli/presentation/commands.ts +104 -14
- package/cli/presentation/devSession.ts +22 -3
- package/cli/presentation/proteum_logo_400x400_square_icon.txt +400 -0
- package/cli/runtime/commands.ts +121 -4
- package/cli/scaffold/index.ts +720 -0
- package/cli/scaffold/templates.ts +344 -0
- package/cli/scaffold/types.ts +26 -0
- package/cli/tsconfig.json +4 -1
- package/cli/utils/check.ts +1 -1
- package/client/app/component.tsx +13 -9
- package/client/dev/profiler/index.tsx +2511 -0
- package/client/dev/profiler/noop.tsx +5 -0
- package/client/dev/profiler/runtime.noop.ts +116 -0
- package/client/dev/profiler/runtime.ts +840 -0
- package/client/services/router/components/router.tsx +30 -2
- package/client/services/router/index.tsx +27 -3
- package/client/services/router/request/api.ts +133 -17
- package/commands/proteum/diagnostics.ts +11 -0
- package/common/dev/commands.ts +50 -0
- package/common/dev/diagnostics.ts +298 -0
- package/common/dev/profiler.ts +92 -0
- package/common/dev/proteumManifest.ts +135 -0
- package/common/dev/requestTrace.ts +115 -0
- package/common/env/proteumEnv.ts +284 -0
- package/common/router/index.ts +4 -22
- package/docs/dev-commands.md +93 -0
- package/docs/diagnostics.md +88 -0
- package/docs/request-tracing.md +132 -0
- package/eslint.js +11 -6
- package/package.json +3 -3
- package/server/app/commands.ts +35 -370
- package/server/app/commandsManager.ts +393 -0
- package/server/app/container/config.ts +11 -49
- package/server/app/container/console/index.ts +2 -3
- package/server/app/container/index.ts +5 -2
- package/server/app/container/trace/index.ts +364 -0
- package/server/app/devCommands.ts +192 -0
- package/server/app/devDiagnostics.ts +53 -0
- package/server/app/index.ts +29 -6
- package/server/index.ts +0 -1
- package/server/services/auth/index.ts +525 -61
- package/server/services/auth/router/index.ts +106 -7
- package/server/services/cron/CronTask.ts +73 -5
- package/server/services/cron/index.ts +34 -11
- package/server/services/fetch/index.ts +3 -10
- package/server/services/prisma/index.ts +66 -4
- package/server/services/router/http/index.ts +173 -6
- package/server/services/router/index.ts +200 -12
- package/server/services/router/request/api.ts +30 -1
- package/server/services/router/response/index.ts +83 -10
- package/server/services/router/response/page/document.tsx +16 -0
- package/server/services/router/response/page/index.tsx +27 -1
- package/skills/clean-project-code/SKILL.md +7 -2
- package/test-results/.last-run.json +4 -0
- package/types/aliases.d.ts +6 -0
- package/types/global/utils.d.ts +7 -14
- package/Rte.zip +0 -0
- package/agents/project/agents.md.zip +0 -0
- package/doc/TODO.md +0 -71
- package/doc/front/router.md +0 -27
- package/doc/workspace/workspace.png +0 -0
- package/doc/workspace/workspace2.png +0 -0
- package/doc/workspace/workspace_26.01.22.png +0 -0
- package/server/services/router/http/session.ts.old +0 -40
|
@@ -25,12 +25,6 @@ import UsersRequestService from './request';
|
|
|
25
25
|
- TYPES
|
|
26
26
|
----------------------------------*/
|
|
27
27
|
|
|
28
|
-
/*----------------------------------
|
|
29
|
-
- CONFIG
|
|
30
|
-
----------------------------------*/
|
|
31
|
-
|
|
32
|
-
const LogPrefix = '[router][auth]';
|
|
33
|
-
|
|
34
28
|
/*----------------------------------
|
|
35
29
|
- SERVICE
|
|
36
30
|
----------------------------------*/
|
|
@@ -59,11 +53,32 @@ export default class AuthenticationRouterService<
|
|
|
59
53
|
this.users = this.config.users;
|
|
60
54
|
}
|
|
61
55
|
|
|
56
|
+
private traceRouteAuth(
|
|
57
|
+
request: TRequest,
|
|
58
|
+
route: TAnyRoute,
|
|
59
|
+
details: Record<string, any>,
|
|
60
|
+
minimumCapture: 'summary' | 'resolve' | 'deep' = 'resolve',
|
|
61
|
+
) {
|
|
62
|
+
this.app.container.Trace.record(
|
|
63
|
+
request.id,
|
|
64
|
+
'auth.route',
|
|
65
|
+
{
|
|
66
|
+
routePath: route.path || '',
|
|
67
|
+
routeId: route.options.id || '',
|
|
68
|
+
authInput: route.options.auth ?? null,
|
|
69
|
+
tracking: route.options.authTracking ?? null,
|
|
70
|
+
redirectLogged: route.options.redirectLogged ?? null,
|
|
71
|
+
...details,
|
|
72
|
+
},
|
|
73
|
+
minimumCapture,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
62
77
|
public async ready() {
|
|
63
78
|
// Decode current user
|
|
64
79
|
this.parent.on('request', async (request: TRequest) => {
|
|
65
80
|
// TODO: Typings. (context.user ?)
|
|
66
|
-
const decoded = await this.users.decode(request.req, true);
|
|
81
|
+
const decoded = await this.users.decode(request.req, true, request.id);
|
|
67
82
|
|
|
68
83
|
request.user = decoded || null;
|
|
69
84
|
});
|
|
@@ -72,10 +87,48 @@ export default class AuthenticationRouterService<
|
|
|
72
87
|
this.parent.on('resolved', async (route: TAnyRoute, request: TRequest, response: ServerResponse<TRouter>) => {
|
|
73
88
|
if (route.options.auth !== undefined) {
|
|
74
89
|
const tracking = route.options.authTracking ?? null;
|
|
90
|
+
const strategy =
|
|
91
|
+
route.options.auth === false
|
|
92
|
+
? 'guest-only'
|
|
93
|
+
: route.options.auth === null
|
|
94
|
+
? 'authenticated'
|
|
95
|
+
: typeof route.options.auth === 'object'
|
|
96
|
+
? 'conditions'
|
|
97
|
+
: route.options.auth === true
|
|
98
|
+
? tracking !== null && this.users.config.rules
|
|
99
|
+
? 'user-via-rules'
|
|
100
|
+
: 'user'
|
|
101
|
+
: tracking !== null && this.users.config.rules
|
|
102
|
+
? 'role-via-rules'
|
|
103
|
+
: 'role';
|
|
104
|
+
|
|
105
|
+
this.traceRouteAuth(
|
|
106
|
+
request,
|
|
107
|
+
route,
|
|
108
|
+
{
|
|
109
|
+
phase: 'start',
|
|
110
|
+
strategy,
|
|
111
|
+
},
|
|
112
|
+
'resolve',
|
|
113
|
+
);
|
|
75
114
|
|
|
76
115
|
// Guest-only routes can still redirect authenticated users away.
|
|
77
116
|
if (route.options.auth === false) {
|
|
78
117
|
const currentUser = this.users.check(request, false, tracking);
|
|
118
|
+
const redirected = Boolean(route.options.redirectLogged && currentUser);
|
|
119
|
+
|
|
120
|
+
this.traceRouteAuth(
|
|
121
|
+
request,
|
|
122
|
+
route,
|
|
123
|
+
{
|
|
124
|
+
phase: 'result',
|
|
125
|
+
strategy,
|
|
126
|
+
outcome: redirected ? 'redirected' : 'allowed',
|
|
127
|
+
userPresent: currentUser !== null,
|
|
128
|
+
redirectTo: redirected ? route.options.redirectLogged : null,
|
|
129
|
+
},
|
|
130
|
+
'resolve',
|
|
131
|
+
);
|
|
79
132
|
|
|
80
133
|
if (route.options.redirectLogged && currentUser) response.redirect(route.options.redirectLogged);
|
|
81
134
|
return;
|
|
@@ -83,11 +136,13 @@ export default class AuthenticationRouterService<
|
|
|
83
136
|
|
|
84
137
|
if (route.options.auth === null) {
|
|
85
138
|
this.users.check(request, null, tracking);
|
|
139
|
+
this.traceRouteAuth(request, route, { phase: 'result', strategy, outcome: 'allowed' }, 'resolve');
|
|
86
140
|
return;
|
|
87
141
|
}
|
|
88
142
|
|
|
89
143
|
if (typeof route.options.auth === 'object') {
|
|
90
144
|
this.users.check(request, route.options.auth as TAuthCheckConditions, tracking);
|
|
145
|
+
this.traceRouteAuth(request, route, { phase: 'result', strategy, outcome: 'allowed' }, 'resolve');
|
|
91
146
|
return;
|
|
92
147
|
}
|
|
93
148
|
|
|
@@ -95,10 +150,32 @@ export default class AuthenticationRouterService<
|
|
|
95
150
|
if (route.options.auth === true) {
|
|
96
151
|
if (tracking !== null && this.users.config.rules) {
|
|
97
152
|
this.users.check(request, { role: 'USER' }, tracking);
|
|
153
|
+
this.traceRouteAuth(
|
|
154
|
+
request,
|
|
155
|
+
route,
|
|
156
|
+
{
|
|
157
|
+
phase: 'result',
|
|
158
|
+
strategy,
|
|
159
|
+
outcome: 'allowed',
|
|
160
|
+
requiredRole: 'USER',
|
|
161
|
+
},
|
|
162
|
+
'resolve',
|
|
163
|
+
);
|
|
98
164
|
return;
|
|
99
165
|
}
|
|
100
166
|
|
|
101
167
|
this.users.check(request, true);
|
|
168
|
+
this.traceRouteAuth(
|
|
169
|
+
request,
|
|
170
|
+
route,
|
|
171
|
+
{
|
|
172
|
+
phase: 'result',
|
|
173
|
+
strategy,
|
|
174
|
+
outcome: 'allowed',
|
|
175
|
+
requiredRole: 'USER',
|
|
176
|
+
},
|
|
177
|
+
'resolve',
|
|
178
|
+
);
|
|
102
179
|
return;
|
|
103
180
|
}
|
|
104
181
|
|
|
@@ -106,10 +183,32 @@ export default class AuthenticationRouterService<
|
|
|
106
183
|
|
|
107
184
|
if (tracking !== null && this.users.config.rules) {
|
|
108
185
|
this.users.check(request, { role: requiredRole }, tracking);
|
|
186
|
+
this.traceRouteAuth(
|
|
187
|
+
request,
|
|
188
|
+
route,
|
|
189
|
+
{
|
|
190
|
+
phase: 'result',
|
|
191
|
+
strategy,
|
|
192
|
+
outcome: 'allowed',
|
|
193
|
+
requiredRole,
|
|
194
|
+
},
|
|
195
|
+
'resolve',
|
|
196
|
+
);
|
|
109
197
|
return;
|
|
110
198
|
}
|
|
111
199
|
|
|
112
200
|
this.users.check(request, requiredRole);
|
|
201
|
+
this.traceRouteAuth(
|
|
202
|
+
request,
|
|
203
|
+
route,
|
|
204
|
+
{
|
|
205
|
+
phase: 'result',
|
|
206
|
+
strategy,
|
|
207
|
+
outcome: 'allowed',
|
|
208
|
+
requiredRole,
|
|
209
|
+
},
|
|
210
|
+
'resolve',
|
|
211
|
+
);
|
|
113
212
|
}
|
|
114
213
|
});
|
|
115
214
|
}
|
|
@@ -10,16 +10,33 @@ import cronParser, { CronExpression } from 'cron-parser';
|
|
|
10
10
|
----------------------------------*/
|
|
11
11
|
|
|
12
12
|
import type CronManager from '.';
|
|
13
|
+
import type {
|
|
14
|
+
TProfilerCronTask,
|
|
15
|
+
TProfilerCronTaskFrequency,
|
|
16
|
+
TProfilerCronTaskRunStatus,
|
|
17
|
+
TProfilerCronTaskTrigger,
|
|
18
|
+
} from '@common/dev/profiler';
|
|
13
19
|
|
|
14
20
|
export type TFrequence = string | Date;
|
|
15
21
|
export type TRunner = () => Promise<any>;
|
|
22
|
+
const nowIso = () => new Date().toISOString();
|
|
16
23
|
|
|
17
24
|
/*----------------------------------
|
|
18
25
|
- CLASS
|
|
19
26
|
----------------------------------*/
|
|
20
27
|
export default class CronTask {
|
|
21
28
|
public cron?: CronExpression;
|
|
29
|
+
public frequency!: TProfilerCronTaskFrequency;
|
|
22
30
|
public nextInvocation?: Date;
|
|
31
|
+
public registeredAt = nowIso();
|
|
32
|
+
public running = false;
|
|
33
|
+
public lastTrigger?: TProfilerCronTaskTrigger;
|
|
34
|
+
public lastRunStartedAt?: string;
|
|
35
|
+
public lastRunFinishedAt?: string;
|
|
36
|
+
public lastRunDurationMs?: number;
|
|
37
|
+
public lastRunStatus?: TProfilerCronTaskRunStatus;
|
|
38
|
+
public lastErrorMessage?: string;
|
|
39
|
+
public runCount = 0;
|
|
23
40
|
|
|
24
41
|
public constructor(
|
|
25
42
|
private manager: CronManager,
|
|
@@ -35,6 +52,10 @@ export default class CronTask {
|
|
|
35
52
|
|
|
36
53
|
public schedule(next: TFrequence) {
|
|
37
54
|
this.cron = undefined;
|
|
55
|
+
this.frequency =
|
|
56
|
+
typeof next === 'string'
|
|
57
|
+
? { kind: 'cron', value: next }
|
|
58
|
+
: { kind: 'date', value: next.toISOString() };
|
|
38
59
|
|
|
39
60
|
// Cron expression
|
|
40
61
|
if (typeof next === 'string') {
|
|
@@ -58,16 +79,63 @@ export default class CronTask {
|
|
|
58
79
|
else this.nextInvocation = undefined;
|
|
59
80
|
}
|
|
60
81
|
|
|
61
|
-
public
|
|
82
|
+
public toProfilerTask(): TProfilerCronTask {
|
|
83
|
+
return {
|
|
84
|
+
name: this.nom,
|
|
85
|
+
registeredAt: this.registeredAt,
|
|
86
|
+
frequency: this.frequency,
|
|
87
|
+
autoexec: Boolean(this.autoexec),
|
|
88
|
+
automaticExecution: this.manager.isAutomaticExecutionEnabled(),
|
|
89
|
+
nextInvocation: this.nextInvocation?.toISOString(),
|
|
90
|
+
running: this.running,
|
|
91
|
+
lastTrigger: this.lastTrigger,
|
|
92
|
+
lastRunStartedAt: this.lastRunStartedAt,
|
|
93
|
+
lastRunFinishedAt: this.lastRunFinishedAt,
|
|
94
|
+
lastRunDurationMs: this.lastRunDurationMs,
|
|
95
|
+
lastRunStatus: this.lastRunStatus,
|
|
96
|
+
lastErrorMessage: this.lastErrorMessage,
|
|
97
|
+
runCount: this.runCount,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public async run(now: boolean = false, trigger: TProfilerCronTaskTrigger = 'scheduler') {
|
|
62
102
|
// Update invocation date
|
|
63
103
|
const maintenant = new Date();
|
|
64
104
|
const runnable = this.nextInvocation !== undefined && this.nextInvocation.valueOf() <= maintenant.valueOf();
|
|
65
105
|
if (runnable) this.scheduleNext();
|
|
66
|
-
else if (now === false) return;
|
|
106
|
+
else if (now === false) return false;
|
|
107
|
+
|
|
108
|
+
if (this.running) return false;
|
|
109
|
+
|
|
110
|
+
this.running = true;
|
|
111
|
+
this.lastTrigger = trigger;
|
|
112
|
+
const startedAt = nowIso();
|
|
113
|
+
this.lastRunStartedAt = startedAt;
|
|
114
|
+
this.lastRunFinishedAt = undefined;
|
|
115
|
+
this.lastRunDurationMs = undefined;
|
|
116
|
+
this.lastRunStatus = undefined;
|
|
117
|
+
this.lastErrorMessage = undefined;
|
|
67
118
|
|
|
68
119
|
// Execution
|
|
69
|
-
|
|
70
|
-
this.
|
|
71
|
-
|
|
120
|
+
try {
|
|
121
|
+
await this.runner();
|
|
122
|
+
this.runCount += 1;
|
|
123
|
+
const finishedAt = nowIso();
|
|
124
|
+
this.lastRunFinishedAt = finishedAt;
|
|
125
|
+
this.lastRunDurationMs = Math.max(0, Date.parse(finishedAt) - Date.parse(startedAt));
|
|
126
|
+
this.lastRunStatus = 'completed';
|
|
127
|
+
this.manager.config.debug && console.info(`[cron][${this.nom}] Task completed.`);
|
|
128
|
+
return true;
|
|
129
|
+
} catch (error) {
|
|
130
|
+
this.runCount += 1;
|
|
131
|
+
const finishedAt = nowIso();
|
|
132
|
+
this.lastRunFinishedAt = finishedAt;
|
|
133
|
+
this.lastRunDurationMs = Math.max(0, Date.parse(finishedAt) - Date.parse(startedAt));
|
|
134
|
+
this.lastRunStatus = 'error';
|
|
135
|
+
this.lastErrorMessage = error instanceof Error ? error.message : String(error);
|
|
136
|
+
throw error;
|
|
137
|
+
} finally {
|
|
138
|
+
this.running = false;
|
|
139
|
+
}
|
|
72
140
|
}
|
|
73
141
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import type { Application } from '@server/app/index';
|
|
7
7
|
import Service from '@server/app/service';
|
|
8
8
|
import { NotFound } from '@common/errors';
|
|
9
|
+
import type { TProfilerCronTaskTrigger } from '@common/dev/profiler';
|
|
9
10
|
import context from '@server/context';
|
|
10
11
|
|
|
11
12
|
/*----------------------------------
|
|
@@ -32,7 +33,7 @@ export type Services = {};
|
|
|
32
33
|
|
|
33
34
|
export default class CronManager extends Service<Config, Hooks, Application, Application> {
|
|
34
35
|
public static taches: { [nom: string]: CronTask } = {};
|
|
35
|
-
public static timer
|
|
36
|
+
public static timer?: NodeJS.Timeout;
|
|
36
37
|
|
|
37
38
|
/*----------------------------------
|
|
38
39
|
- LIFECICLE
|
|
@@ -40,8 +41,17 @@ export default class CronManager extends Service<Config, Hooks, Application, App
|
|
|
40
41
|
|
|
41
42
|
public async ready() {
|
|
42
43
|
clearInterval(CronManager.timer);
|
|
44
|
+
if (!this.isAutomaticExecutionEnabled()) {
|
|
45
|
+
this.config.debug && console.info('[cron] Automatic execution disabled in dev mode.');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
43
49
|
CronManager.timer = setInterval(() => {
|
|
44
|
-
for (const id in CronManager.taches)
|
|
50
|
+
for (const id in CronManager.taches) {
|
|
51
|
+
void this.runTask(id, false, 'scheduler').catch((error) => {
|
|
52
|
+
console.error(`[cron][${id}] Task failed.`, error);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
45
55
|
}, 10000);
|
|
46
56
|
}
|
|
47
57
|
|
|
@@ -59,16 +69,12 @@ export default class CronManager extends Service<Config, Hooks, Application, App
|
|
|
59
69
|
* @param autoexec true to execute the task immediatly
|
|
60
70
|
* @returns The CronTask that just have been created
|
|
61
71
|
*/
|
|
62
|
-
public task(nom: string, frequence: TFrequence, run: TRunner, autoexec?: boolean) {
|
|
63
|
-
|
|
64
|
-
context.run({ channelType: 'cron', channelId: nom }, async () => {
|
|
65
|
-
CronManager.taches[nom] = new CronTask(this, nom, frequence, run, autoexec);
|
|
72
|
+
public async task(nom: string, frequence: TFrequence, run: TRunner, autoexec?: boolean) {
|
|
73
|
+
CronManager.taches[nom] = new CronTask(this, nom, frequence, run, autoexec);
|
|
66
74
|
|
|
67
|
-
|
|
75
|
+
if (autoexec && this.isAutomaticExecutionEnabled()) await this.runTask(nom, true, 'autoexec');
|
|
68
76
|
|
|
69
|
-
|
|
70
|
-
});
|
|
71
|
-
});
|
|
77
|
+
return CronManager.taches[nom];
|
|
72
78
|
}
|
|
73
79
|
|
|
74
80
|
public async exec(nom: string) {
|
|
@@ -76,7 +82,8 @@ export default class CronManager extends Service<Config, Hooks, Application, App
|
|
|
76
82
|
|
|
77
83
|
if (tache === undefined) throw new NotFound('Tâche NotFound: ' + nom);
|
|
78
84
|
|
|
79
|
-
await
|
|
85
|
+
await this.runTask(nom, true, 'manual');
|
|
86
|
+
return tache;
|
|
80
87
|
}
|
|
81
88
|
public get(): typeof CronManager.taches;
|
|
82
89
|
public get(name: string): CronTask;
|
|
@@ -87,4 +94,20 @@ export default class CronManager extends Service<Config, Hooks, Application, App
|
|
|
87
94
|
if (cron === undefined) throw new Error(`L'instance de la tâche cron ${name} n'a pas été trouvée`);
|
|
88
95
|
return cron;
|
|
89
96
|
}
|
|
97
|
+
|
|
98
|
+
public isAutomaticExecutionEnabled() {
|
|
99
|
+
return !__DEV__;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public listTasks() {
|
|
103
|
+
return Object.values(CronManager.taches).map((task) => task.toProfilerTask());
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private async runTask(name: string, now: boolean, trigger: TProfilerCronTaskTrigger) {
|
|
107
|
+
const task = this.get(name);
|
|
108
|
+
|
|
109
|
+
return context.run({ channelType: 'cron', channelId: name }, async () => {
|
|
110
|
+
return task.run(now, trigger);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
90
113
|
}
|
|
@@ -7,9 +7,6 @@ import type { default as sharp, Sharp } from 'sharp';
|
|
|
7
7
|
import fs from 'fs-extra';
|
|
8
8
|
import got, { Method, Options, Response as GotResponse } from 'got';
|
|
9
9
|
|
|
10
|
-
// Node
|
|
11
|
-
import request from 'request';
|
|
12
|
-
|
|
13
10
|
// Core: general
|
|
14
11
|
import type { Application } from '@server/app/index';
|
|
15
12
|
import Service from '@server/app/service';
|
|
@@ -109,14 +106,10 @@ export default class FetchService extends Service<Config, Hooks, Application, Ap
|
|
|
109
106
|
----------------------------------*/
|
|
110
107
|
|
|
111
108
|
public toBuffer(uri: string): Promise<Buffer> {
|
|
112
|
-
return
|
|
113
|
-
|
|
114
|
-
if (err) return reject(err);
|
|
115
|
-
|
|
116
|
-
if (!body) return reject(`Body is empty for ${uri}.`);
|
|
109
|
+
return got(uri, { responseType: 'buffer', throwHttpErrors: false }).then((response) => {
|
|
110
|
+
if (!response.body || response.body.length === 0) throw new Error(`Body is empty for ${uri}.`);
|
|
117
111
|
|
|
118
|
-
|
|
119
|
-
});
|
|
112
|
+
return response.body;
|
|
120
113
|
});
|
|
121
114
|
}
|
|
122
115
|
|
|
@@ -23,6 +23,62 @@ import { NotFound } from '@common/errors';
|
|
|
23
23
|
|
|
24
24
|
export type SqlQuery = ReturnType<ModelsManager['SQL']>;
|
|
25
25
|
|
|
26
|
+
type DecimalLike = {
|
|
27
|
+
constructor?: { name?: string };
|
|
28
|
+
equals: (value: number) => boolean;
|
|
29
|
+
toNumber: () => number;
|
|
30
|
+
toString: () => string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/*----------------------------------
|
|
34
|
+
- HELPERS
|
|
35
|
+
----------------------------------*/
|
|
36
|
+
|
|
37
|
+
const isDecimalLike = (value: object): value is DecimalLike =>
|
|
38
|
+
'constructor' in value &&
|
|
39
|
+
'equals' in value &&
|
|
40
|
+
'toNumber' in value &&
|
|
41
|
+
'toString' in value &&
|
|
42
|
+
typeof value.constructor === 'function' &&
|
|
43
|
+
value.constructor.name === 'Decimal' &&
|
|
44
|
+
typeof value.equals === 'function' &&
|
|
45
|
+
typeof value.toNumber === 'function' &&
|
|
46
|
+
typeof value.toString === 'function';
|
|
47
|
+
|
|
48
|
+
const isPlainObject = (value: object) => {
|
|
49
|
+
const prototype = Object.getPrototypeOf(value);
|
|
50
|
+
return prototype === Object.prototype || prototype === null;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const normalizeBigInt = (value: bigint) => {
|
|
54
|
+
const number = Number(value);
|
|
55
|
+
return Number.isSafeInteger(number) ? number : value.toString();
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const normalizeDecimal = (value: DecimalLike) => {
|
|
59
|
+
const number = value.toNumber();
|
|
60
|
+
return Number.isFinite(number) && value.equals(number) ? number : value.toString();
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const normalizeSqlScalar = (value: bigint | DecimalLike) =>
|
|
64
|
+
typeof value === 'bigint' ? normalizeBigInt(value) : normalizeDecimal(value);
|
|
65
|
+
|
|
66
|
+
const normalizeSqlResult = <T>(value: T): T => {
|
|
67
|
+
if (typeof value === 'bigint') return normalizeSqlScalar(value) as T;
|
|
68
|
+
|
|
69
|
+
if (Array.isArray(value)) return value.map((item) => normalizeSqlResult(item)) as T;
|
|
70
|
+
|
|
71
|
+
if (value === null || value === undefined || typeof value !== 'object' || value instanceof Date) return value;
|
|
72
|
+
|
|
73
|
+
if (isDecimalLike(value)) return normalizeSqlScalar(value) as T;
|
|
74
|
+
|
|
75
|
+
if (!isPlainObject(value)) return value;
|
|
76
|
+
|
|
77
|
+
return Object.fromEntries(
|
|
78
|
+
Object.entries(value).map(([key, nestedValue]) => [key, normalizeSqlResult(nestedValue)]),
|
|
79
|
+
) as T;
|
|
80
|
+
};
|
|
81
|
+
|
|
26
82
|
/*----------------------------------
|
|
27
83
|
- SERVICE CONFIG
|
|
28
84
|
----------------------------------*/
|
|
@@ -33,6 +89,11 @@ export type Hooks = {};
|
|
|
33
89
|
|
|
34
90
|
export type Services = {};
|
|
35
91
|
|
|
92
|
+
// Fix: Do not know how to serialize a BigInt
|
|
93
|
+
BigInt.prototype.toJSON = function () {
|
|
94
|
+
return normalizeBigInt(this.valueOf());
|
|
95
|
+
};
|
|
96
|
+
|
|
36
97
|
/*----------------------------------
|
|
37
98
|
- CLASSE
|
|
38
99
|
----------------------------------*/
|
|
@@ -43,7 +104,7 @@ export default class ModelsManager extends Service<Config, Hooks, Application, A
|
|
|
43
104
|
public constructor(...args: TServiceArgs<ModelsManager>) {
|
|
44
105
|
super(...args);
|
|
45
106
|
|
|
46
|
-
dotenv.config();
|
|
107
|
+
dotenv.config({ quiet: true });
|
|
47
108
|
|
|
48
109
|
const databaseUrl = process.env.DATABASE_URL;
|
|
49
110
|
if (!databaseUrl)
|
|
@@ -74,9 +135,10 @@ export default class ModelsManager extends Service<Config, Hooks, Application, A
|
|
|
74
135
|
public SQL<TRowData extends {} | number | string>(strings: TemplateStringsArray, ...data: any[]) {
|
|
75
136
|
const string = this.string(strings, ...data);
|
|
76
137
|
|
|
77
|
-
const query = () =>
|
|
78
|
-
|
|
79
|
-
|
|
138
|
+
const query = () =>
|
|
139
|
+
this.client.$queryRawUnsafe(string).then((resultatRequetes) => normalizeSqlResult(resultatRequetes)) as Promise<
|
|
140
|
+
TRowData[]
|
|
141
|
+
>;
|
|
80
142
|
|
|
81
143
|
query.all = query;
|
|
82
144
|
query.value = <TValue extends any = number>() =>
|