5htp-core 0.3.2 → 0.3.3

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "5htp-core",
3
3
  "description": "Convenient TypeScript framework designed for Performance and Productivity.",
4
- "version": "0.3.2",
4
+ "version": "0.3.3",
5
5
  "author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
6
6
  "repository": "git://github.com/gaetanlegac/5htp-core.git",
7
7
  "license": "MIT",
@@ -61,7 +61,6 @@
61
61
  "mysql2": "^2.3.0",
62
62
  "nodemailer": "^6.6.3",
63
63
  "object-sizeof": "^1.6.3",
64
- "ololog": "^1.1.175",
65
64
  "path-to-regexp": "^6.2.0",
66
65
  "picomatch": "^2.3.1",
67
66
  "preact": "^10.5.15",
@@ -75,6 +74,7 @@
75
74
  "request": "^2.88.2",
76
75
  "sharp": "^0.29.1",
77
76
  "sql-formatter": "^4.0.2",
77
+ "tslog": "^4.9.1",
78
78
  "uuid": "^8.3.2",
79
79
  "uuid-by-string": "^3.0.4",
80
80
  "validator": "^13.7.0",
@@ -65,6 +65,7 @@
65
65
  gap: @spacing / 2;
66
66
  min-width: 4em;
67
67
  font-size: 1rem;
68
+ z-index: 1; // Make the label on top of ::before for example
68
69
 
69
70
  li > & {
70
71
  flex: 1;
@@ -111,7 +112,7 @@
111
112
  }
112
113
 
113
114
  > i {
114
- color: inherit !important;
115
+ color: inherit;
115
116
  }
116
117
 
117
118
  }
@@ -185,7 +185,7 @@ export const createDialog = (app: Application, isToast: boolean): DialogActions
185
185
  </div>
186
186
  )),
187
187
 
188
- loading: (title: string) => app.loading = show({
188
+ loading: (title: string) => show({
189
189
  title: title,
190
190
  type: 'loading'
191
191
  }),
@@ -14,45 +14,34 @@ import type Page from '../response/page';
14
14
  - PAGE STATE
15
15
  ----------------------------------*/
16
16
 
17
- export default ({ page, isCurrent }: { page: Page, isCurrent?: boolean }) => {
17
+ export default ({ page }: { page: Page }) => {
18
18
 
19
+ /*----------------------------------
20
+ - CONTEXT
21
+ ----------------------------------*/
19
22
  const context = useContext();
20
23
 
21
24
  const [apiData, setApiData] = React.useState<{[k: string]: any} | null>(
22
- page.loading ? null : page.data
25
+ page.data || {}
23
26
  );
24
27
  page.setAllData = setApiData;
25
28
  context.data = apiData;
26
-
27
- React.useEffect(() => {
28
-
29
- // Fetch the data asynchronously for the first time
30
- if (/*apiData === null && */isCurrent)
31
- page.fetchData().then( loadedData => {
32
- page.loading = false;
33
- setApiData(loadedData);
34
- })
35
-
36
- }, [page]);
37
-
38
- /*
39
- <div
40
- class={"page" + (isCurrent ? ' current' : '')}
41
- id={page.chunkId === undefined ? undefined : 'page_' + page.chunkId}
42
- >
43
- */
44
29
 
30
+ /*----------------------------------
31
+ - RENDER
32
+ ----------------------------------*/
45
33
  // Make request parameters and api data accessible from the page component
46
34
  return page.renderer ? (
47
35
 
48
36
  <page.renderer
49
37
  // Services
50
38
  {...context}
51
- // URL params
52
- {...context.request.data}
53
- // API data
54
- data={apiData}
39
+ // API data & URL params
40
+ data={{
41
+ ...apiData,
42
+ ...context.request.data
43
+ }}
55
44
  />
56
45
 
57
- ) : null
46
+ ) : 'Renderer missing'
58
47
  }
@@ -38,16 +38,22 @@ export default ({ service: router }: { service: Router }) => {
38
38
 
39
39
  const [pages, setPages] = React.useState<{
40
40
  current: undefined | Page,
41
- //previous: undefined | Page
41
+ loading: boolean
42
42
  }>({
43
43
  current: context.page,
44
- //previous: undefined
44
+ loading: false
45
45
  });
46
46
 
47
47
  const resolvePage = async (request: ClientRequest, locationUpdate?: Update) => {
48
48
 
49
49
  // WARNING: Don"t try to play with pages here, since the object will not be updated
50
50
  // If needed to play with pages, do it in the setPages callback below
51
+
52
+ // Set.loading state
53
+ setPages( oldState => ({
54
+ ...oldState,
55
+ loading: true,
56
+ }));
51
57
 
52
58
  // Load the route chunks
53
59
  context.request = request;
@@ -63,9 +69,8 @@ export default ({ service: router }: { service: Router }) => {
63
69
  return;
64
70
  }
65
71
 
66
- // Set.loading state
67
- newpage.isLoading = true;
68
- newpage.loading = <i src="spin" />
72
+ const data = context.data = await newpage.fetchData();
73
+
69
74
  // Add page container
70
75
  setPages( pages => {
71
76
 
@@ -74,7 +79,7 @@ export default ({ service: router }: { service: Router }) => {
74
79
  // Check if the page changed
75
80
  if (currentRoute?.path === request.path) {
76
81
  console.warn(LogPrefix, "Canceling navigation to the same page:", {...request});
77
- return pages;
82
+ return { ...pages, loading: false }
78
83
  }
79
84
 
80
85
  // If if the layout changed
@@ -88,7 +93,7 @@ export default ({ service: router }: { service: Router }) => {
88
93
  // Find a way to unload the previous layout / page resources before to load the new one
89
94
  console.log(LogPrefix, `Changing layout. Before:`, curLayout, 'New layout:', newLayout);
90
95
  window.location.replace(request.url);
91
- return pages;
96
+ return { ...pages, loading: false }
92
97
 
93
98
  context.app.setLayout(newLayout);
94
99
  }
@@ -98,13 +103,13 @@ export default ({ service: router }: { service: Router }) => {
98
103
  if (oldPage !== undefined) {
99
104
  setTimeout(() => setPages({
100
105
  current: newpage,
101
- //previous: undefined
106
+ loading: false
102
107
  }), 500);
103
108
  }
104
109
 
105
110
  return {
106
111
  current: newpage,
107
- //previous: oldPage
112
+ loading: false
108
113
  }
109
114
  });
110
115
  }
@@ -116,7 +121,7 @@ export default ({ service: router }: { service: Router }) => {
116
121
  inline: "nearest"
117
122
  })
118
123
 
119
- // First load
124
+ // First render
120
125
  React.useEffect(() => {
121
126
 
122
127
  // Resolve page if it wasn't done via SSR
@@ -158,9 +163,16 @@ export default ({ service: router }: { service: Router }) => {
158
163
 
159
164
  {pages.current && (
160
165
  <PageComponent page={pages.current}
161
- isCurrent
162
- key={pages.current.id === undefined ? undefined : 'page_' + pages.current.id}
166
+ /* Create a new instance of the Page component every time the page change
167
+ Otherwise the page will memorise the data of the previous page */
168
+ key={pages.current.chunkId === undefined ? undefined : 'page_' + pages.current.chunkId}
163
169
  />
164
170
  )}
171
+
172
+ {pages.loading && (
173
+ <div id="loading">
174
+ <i src="spin" />
175
+ </div>
176
+ )}
165
177
  </>
166
178
  }
@@ -48,9 +48,9 @@ export default class ApiClient implements ApiClientService {
48
48
  - HIGH LEVEL
49
49
  ----------------------------------*/
50
50
 
51
- public fetch<TProvidedData extends TFetcherList = TFetcherList>(
52
- fetchers: TFetcherList
53
- ): TDataReturnedByFetchers<TProvidedData> {
51
+ public fetch<FetchersList extends TFetcherList = TFetcherList>(
52
+ fetchers: FetchersList
53
+ ): TDataReturnedByFetchers<FetchersList> {
54
54
  throw new Error("api.fetch shouldn't be called here.");
55
55
  }
56
56
 
@@ -24,8 +24,6 @@ import type ClientRouter from '..';
24
24
 
25
25
  export default class ClientPage<TRouter = ClientRouter> extends PageResponse<TRouter> {
26
26
 
27
- public isLoading: boolean = false;
28
- public loading: false | ComponentChild;
29
27
  public scrollToId: string;
30
28
 
31
29
  public constructor(
@@ -45,11 +43,9 @@ export default class ClientPage<TRouter = ClientRouter> extends PageResponse<TRo
45
43
 
46
44
  // Add the page to the context
47
45
  this.context.page = this;
48
- this.isLoading = true;
49
46
 
50
47
  // Data succesfully loaded
51
48
  this.data = data || await this.fetchData();
52
- this.isLoading = false;
53
49
 
54
50
  return this;
55
51
  }
@@ -91,15 +91,15 @@ export type TRouteOptions = {
91
91
  // Resolving
92
92
  domain?: string,
93
93
  accept?: string,
94
-
95
- // Access Restriction
96
94
  auth?: TUserRole | boolean,
97
- //form?: TSchema,
95
+
96
+ // Rendering
97
+ static?: boolean,
98
98
  layout?: false | string, // The nale of the layout
99
99
 
100
+ // To cleanup
100
101
  TESTING?: boolean,
101
102
  logging?: boolean,
102
-
103
103
  }
104
104
 
105
105
  export type TRouteModule<TRegisteredRoute = any> = {
@@ -14,11 +14,7 @@ import type { FileToUpload } from '@client/components/inputv3/file';
14
14
  // By example if we want to fetch an api endpoint only if the url contains a certain url parameter
15
15
  export type TFetcherList = { [id: string]: TFetcher | undefined }
16
16
 
17
- export type TPostData = {[key: string]: PrimitiveValue}
18
-
19
- export type TPostDataWithFile = {[key: string]: PrimitiveValue | FileToUpload}
20
-
21
- export type TFetcher<TData extends unknown = unknown> = {
17
+ export type TFetcher<TData extends any = unknown> = {
22
18
 
23
19
  // For async calls: api.post(...).then((data) => ...)
24
20
  then: (callback: (data: TData) => void) => Promise<TData>,
@@ -44,12 +40,16 @@ export type TApiFetchOptions = {
44
40
  encoding?: 'json' | 'multipart'
45
41
  }
46
42
 
43
+ export type TPostData = {[key: string]: PrimitiveValue}
44
+
45
+ export type TPostDataWithFile = {[key: string]: PrimitiveValue | FileToUpload}
46
+
47
47
  // https://stackoverflow.com/questions/44851268/typescript-how-to-extract-the-generic-parameter-from-a-type
48
48
  type TypeWithGeneric<T> = TFetcher<T>
49
49
  type extractGeneric<Type> = Type extends TypeWithGeneric<infer X> ? X : never
50
50
 
51
51
  export type TDataReturnedByFetchers<TProvidedData extends TFetcherList = {}> = {
52
- [Property in keyof TProvidedData]: undefined | (extractGeneric<TProvidedData[Property]> extends ((...args: any[]) => any)
52
+ [Property in keyof TProvidedData]: (extractGeneric<TProvidedData[Property]> extends ((...args: any[]) => any)
53
53
  ? ThenArg<ReturnType< extractGeneric<TProvidedData[Property]> >>
54
54
  : extractGeneric<TProvidedData[Property]>
55
55
  )
@@ -35,7 +35,7 @@ export type TFrontRenderer<
35
35
  TAdditionnalData
36
36
  &
37
37
  {
38
- data: TDataReturnedByFetchers<TProvidedData>
38
+ data: {[key: string]: PrimitiveValue}
39
39
  }
40
40
  )
41
41
  ) => VNode<any> | null
@@ -106,7 +106,8 @@ export class ServicesContainer<
106
106
  ...Object.getOwnPropertyNames( Object.getPrototypeOf( instance )),
107
107
  ...Object.getOwnPropertyNames( instance ),
108
108
  // service.launch() isn't included, maybe because parent abstract class
109
- 'launch'
109
+ 'launch',
110
+ 'bindServices'
110
111
  ];
111
112
 
112
113
  for (const method of methods)
@@ -83,8 +83,7 @@ export default abstract class Service<
83
83
  : app
84
84
 
85
85
  // Instanciate subservices
86
- for (const localName in services)
87
- this.bindService( localName, services[localName] as unknown as TRegisteredService );
86
+ this.bindServices( services );
88
87
 
89
88
  }
90
89
 
@@ -155,7 +154,7 @@ export default abstract class Service<
155
154
  ReturnType< TServiceClass["getServiceInstance"] >
156
155
  &
157
156
  {
158
- new (...args: any[]): TServiceClass & { services: TSubServices },
157
+ new (...args: any[]): TServiceClass["getServiceInstance"] & { services: TSubServices },
159
158
  services: TSubServices
160
159
  }
161
160
  /*Omit<TServiceClass, 'services'> & {
@@ -169,12 +168,17 @@ export default abstract class Service<
169
168
  throw new Error(`Unable to use service "${serviceId}": This one hasn't been setup.`);
170
169
 
171
170
  // Bind subservices
172
- registered.subServices = subServices || {};
171
+ if (subServices !== undefined)
172
+ registered.subServices = {
173
+ ...registered.subServices,
174
+ ...subServices
175
+ };
173
176
 
174
177
  // Check if not already instanciated
175
178
  const existing = ServicesContainer.allServices[ serviceId ];
176
179
  if (existing !== undefined) {
177
180
  console.info("Service", serviceId, "already instanciated through another service.");
181
+ existing.bindServices( registered.subServices );
178
182
  return existing;
179
183
  }
180
184
 
@@ -183,14 +187,25 @@ export default abstract class Service<
183
187
  const ServiceClass = registered.metas.class().default;
184
188
 
185
189
  // Create class instance
186
- const service = new ServiceClass(this, registered.config, registered.subServices, this.app || this)
187
- .getServiceInstance()
190
+ const service = new ServiceClass(
191
+ this,
192
+ registered.config,
193
+ registered.subServices,
194
+ this.app || this
195
+ )
196
+ const serviceInstance = service.getServiceInstance();
188
197
 
189
198
  // Bind his own metas
190
199
  service.metas = registered.metas;
191
- ServicesContainer.allServices[ registered.metas.id ] = service;
200
+ ServicesContainer.allServices[ registered.metas.id ] = serviceInstance;
201
+
202
+ return serviceInstance;
203
+ }
192
204
 
193
- return service;
205
+ public bindServices( services: TServicesIndex ) {
206
+
207
+ for (const localName in services)
208
+ this.bindService( localName, services[localName] as unknown as TRegisteredService );
194
209
  }
195
210
 
196
211
  public bindService( localName: string, service: AnyService ) {
@@ -5,7 +5,7 @@
5
5
  "baseUrl": "..",
6
6
  "paths": {
7
7
 
8
- "@/server/models": ["./server/.generated/models"],
8
+ "@/server/models": ["./server/.generated/models.d.ts"],
9
9
 
10
10
  "@client/*": ["../node_modules/5htp-core/src/client/*"],
11
11
  "@common/*": ["../node_modules/5htp-core/src/common/*"],
@@ -3,8 +3,8 @@
3
3
  ----------------------------------*/
4
4
  // Npm
5
5
  import Ansi2Html from 'ansi-to-html';
6
- import { Logger, ILogObject } from "tslog"
7
- import type { Console } from '.';;
6
+ import { formatWithOptions } from 'util';
7
+ import type { default as Console, TJsonLog } from '.';;
8
8
 
9
9
  var ansi2Html = new Ansi2Html({
10
10
  newline: true,
@@ -35,30 +35,31 @@ var ansi2Html = new Ansi2Html({
35
35
  /*----------------------------------
36
36
  - METHOD
37
37
  ----------------------------------*/
38
- export default (log: ILogObject, c: Console) => {
38
+ export default (log: TJsonLog, c: Console) => {
39
39
 
40
- if (log.logLevel === 'error') {
40
+ // Print metas as ANSI
41
+ const logMetaMarkup = c.logger._prettyFormatLogObjMeta({
42
+ date: log.time,
43
+ logLevelId: c.getLogLevelId( log.level ),
44
+ logLevelName: log.level,
45
+ // We consider that having the path is useless in this case
46
+ path: undefined,
47
+ });
41
48
 
42
- // Enrichissement erreurs
43
- for (const arg of log.argumentsArray) {
44
-
45
- // Chemin complet pour pouvoir l'ouvrir dans un éditeyr via un clic
46
- if (typeof arg === 'object' && arg.stack !== undefined) for (const stack of arg.stack)
47
- stack.filePath = stack.fullFilePath;
48
-
49
- }
50
- }
51
-
52
- // BUG: log.date pas pris encompte, affiche la date actuelle
53
- // https://github.com/fullstack-build/tslog/blob/master/src/LoggerWithoutCallSite.ts#L509
54
-
55
- let ansi: string = '';
56
- const myStd = { write: (message: string) => ansi += message }
57
- c.logger.printPrettyLog(myStd, log);
49
+ // Print args as ANSI
50
+ const logArgsAndErrorsMarkup = c.logger.runtime.prettyFormatLogObj( log.args, c.logger.settings);
51
+ const logErrors = logArgsAndErrorsMarkup.errors;
52
+ const logArgs = logArgsAndErrorsMarkup.args;
53
+ const logErrorsStr = (logErrors.length > 0 && logArgs.length > 0 ? "\n" : "") + logErrors.join("\n");
54
+ c.logger.settings.prettyInspectOptions.colors = c.logger.settings.stylePrettyLogs;
55
+ let ansi = logMetaMarkup + formatWithOptions(c.logger.settings.prettyInspectOptions, ...logArgs) + logErrorsStr;
58
56
 
57
+ // Use HTML spaces
59
58
  ansi = ansi.replace(/ {2}/g, '&nbsp;');
60
59
  ansi = ansi.replace(/\t/g, '&nbsp;'.repeat(8));
60
+ ansi = ansi.replace(/\n/g, '<br/>');
61
61
 
62
+ // Convert ANSI to HTML
62
63
  const html = ansi2Html.toHtml(ansi)
63
64
 
64
65
  return html;
@@ -1,9 +1,14 @@
1
1
  /*----------------------------------
2
2
  - DEPENDANCES
3
3
  ----------------------------------*/
4
+
5
+ // Node
6
+ import { serialize } from 'v8';
7
+ import { formatWithOptions } from 'util';
8
+
4
9
  // Npm
5
10
  import { v4 as uuid } from 'uuid';
6
- import log from 'ololog';
11
+ import { Logger, IMeta, ILogObj, ISettings } from 'tslog';
7
12
  import { format as formatSql } from 'sql-formatter';
8
13
  import highlight from 'cli-highlight';
9
14
 
@@ -85,8 +90,14 @@ export type TDbQueryLog = ChannelInfos & {
85
90
  time: number,
86
91
  }
87
92
 
88
- export type TLog = ILogObject & ChannelInfos
93
+ export type TLogLevel = keyof typeof logLevels
89
94
 
95
+ export type TJsonLog = {
96
+ time: Date,
97
+ level: TLogLevel,
98
+ args: unknown[],
99
+ channel: ChannelInfos
100
+ }
90
101
 
91
102
  /*----------------------------------
92
103
  - CONST
@@ -96,21 +107,12 @@ const LogPrefix = '[console]'
96
107
 
97
108
  const errorMailInterval = (1 * 60 * 60 * 1000); // 1 hour
98
109
 
99
- const logFields = [
100
- 'date',
101
- 'logLevelId',
102
- 'logLevel',
103
-
104
- 'isConstructor',
105
- 'methodName',
106
- 'functionName',
107
- 'typeName',
108
-
109
- 'filePath',
110
- 'lineNumber',
111
- 'argumentsArray',
112
- 'stack',
113
- ] as const
110
+ const logLevels = {
111
+ 'log': 0,
112
+ 'info': 3,
113
+ 'warn': 4,
114
+ 'error': 5
115
+ } as const
114
116
 
115
117
  /*----------------------------------
116
118
  - LOGGER
@@ -118,21 +120,16 @@ const logFields = [
118
120
  export default class Console extends Service<Config, Hooks, Application, Services> {
119
121
 
120
122
  // Services
121
- public logger!: Logger;
122
-
123
+ public logger!: Logger<ILogObj>;
123
124
  // Buffers
124
- public logs: TLog[] = [];
125
- public clients: TGuestLogs[] = [];
126
- public requests: TRequestLogs[] = [];
127
- public sqlQueries: TDbQueryLog[] = [];
125
+ public logs: TJsonLog[] = [];
128
126
  // Bug ID => Timestamp latest send
129
127
  private sentBugs: {[bugId: string]: number} = {};
130
128
 
131
- // Adapters
132
- public log = console.log;
133
- public warn = console.warn;
134
- public info = console.info;
135
- public error = console.error;
129
+ // Old (still useful???)
130
+ /*public clients: TGuestLogs[] = [];
131
+ public requests: TRequestLogs[] = [];
132
+ public sqlQueries: TDbQueryLog[] = [];*/
136
133
 
137
134
  /*----------------------------------
138
135
  - LIFECYCLE
@@ -140,34 +137,62 @@ export default class Console extends Service<Config, Hooks, Application, Service
140
137
 
141
138
  protected async start() {
142
139
 
143
- const envConfig = this.config[ this.app.env.profile ];
144
-
145
- /*const origConsole = console;
146
- console.log = (...args: unknown[]) => log(...args)*/
140
+ const origLog = console.log
147
141
 
148
- /*this.logger = new Logger({
149
- overwriteConsole: true,
150
- //type: this.app.env.profile === 'dev' ? 'pretty' : 'hidden',
151
- requestId: (): string => {
152
- const { channelType, channelId } = this.getChannel();
153
- return channelId === undefined ? channelType : channelType + ':' + channelId;
154
- },
155
- displayRequestId: false,
142
+ this.logger = new Logger({
143
+ // Use to improve performance in production
156
144
  hideLogPositionForProduction: this.app.env.profile === 'prod',
145
+ type: 'pretty',
157
146
  prettyInspectOptions: {
158
147
  depth: 2
148
+ },
149
+ overwrite: {
150
+ formatMeta: (meta?: IMeta) => {
151
+
152
+ // Shorten file paths
153
+ if (meta?.path !== undefined) {
154
+ meta.path.filePathWithLine = this.shortenFilePath( meta.path.filePathWithLine );
155
+ }
156
+
157
+ return this.logger._prettyFormatLogObjMeta( meta );
158
+ },
159
+ transportFormatted: (
160
+ logMetaMarkup: string,
161
+ logArgs: unknown[],
162
+ logErrors: string[],
163
+ settings: ISettings<ILogObj>
164
+ ) => {
165
+ const logErrorsStr = (logErrors.length > 0 && logArgs.length > 0 ? "\n" : "") + logErrors.join("\n");
166
+ settings.prettyInspectOptions.colors = settings.stylePrettyLogs;
167
+ origLog(logMetaMarkup + formatWithOptions(settings.prettyInspectOptions, ...logArgs) + logErrorsStr);
168
+ },
159
169
  }
160
170
  });
161
171
 
162
- this.logger.attachTransport({
163
- silly: this.logEntry.bind(this),
164
- debug: this.logEntry.bind(this),
165
- trace: this.logEntry.bind(this),
166
- info: this.logEntry.bind(this),
167
- warn: this.logEntry.bind(this),
168
- error: this.logEntry.bind(this),
169
- fatal: this.logEntry.bind(this),
170
- }, envConfig.level);*/
172
+ if (console["_wrapped"] !== undefined)
173
+ return;
174
+
175
+ for (const logLevel in logLevels) {
176
+ console[ logLevel ] = (...args: any[]) => {
177
+
178
+ // Dev mode = no care about performance = rich logging
179
+ if (this.app.env.profile === 'dev')
180
+ //this.logger[ logLevel ](...args);
181
+ origLog(...args);
182
+ // Prod mode = minimal logging
183
+
184
+ const channel = this.getChannel();
185
+
186
+ this.logs.push({
187
+ time: new Date,
188
+ level: logLevel,
189
+ args,
190
+ channel
191
+ });
192
+ }
193
+ }
194
+
195
+ console["_wrapped"] = true;
171
196
 
172
197
  setInterval(() => this.clean(), 10000);
173
198
  }
@@ -180,15 +205,50 @@ export default class Console extends Service<Config, Hooks, Application, Service
180
205
 
181
206
  }
182
207
 
208
+ /*----------------------------------
209
+ - LOGS FORMATTING
210
+ ----------------------------------*/
211
+
212
+ public shortenFilePath( filepath?: string ) {
213
+
214
+ if (filepath === undefined)
215
+ return undefined;
216
+
217
+ const projectRoot = this.app.container.path.root;
218
+ if (filepath.startsWith( projectRoot ))
219
+ filepath = filepath.substring( projectRoot.length )
220
+
221
+ const frameworkRoot = '/node_modules/5htp-core/src/';
222
+ if (filepath.startsWith( frameworkRoot ))
223
+ filepath = '@' + filepath.substring( frameworkRoot.length )
224
+
225
+ return filepath;
226
+
227
+ }
228
+
229
+
183
230
  /*----------------------------------
184
231
  - ACTIONS
185
232
  ----------------------------------*/
186
233
 
234
+ public getLogLevelId( logLevelName: TLogLevel ) {
235
+ return logLevels[ logLevelName ]
236
+ }
237
+
187
238
  private clean() {
188
- /*this.config.debug && console.log(LogPrefix, `Clean logs buffer. Current size:`, this.logs.length, '/', this.config.bufferLimit);
239
+
240
+ if (this.config.debug) {
241
+ console.log(
242
+ LogPrefix,
243
+ `Clean logs buffer. Current size:`,
244
+ this.logs.length, '/', this.config.bufferLimit,
245
+ 'Memory Size:', serialize(this.logs).byteLength
246
+ );
247
+ }
248
+
189
249
  const bufferOverflow = this.logs.length - this.config.bufferLimit;
190
250
  if (bufferOverflow > 0)
191
- this.logs = this.logs.slice(bufferOverflow);*/
251
+ this.logs = this.logs.slice(bufferOverflow);
192
252
  }
193
253
 
194
254
  public async createBugReport( error: Error, request?: ServerRequest ) {
@@ -223,7 +283,7 @@ export default class Console extends Service<Config, Hooks, Application, Service
223
283
  // On envoi l'email avant l'insertion dans bla bdd
224
284
  // Car cette denrière a plus de chances de provoquer une erreur
225
285
  const logsHtml = this.printHtml(
226
- this.logs/*.filter(e => e.channelId === channelId)*/.slice(-100),
286
+ this.logs.filter( e => e.channel.channelId === channelId).slice(-100),
227
287
  true
228
288
  );
229
289
 
@@ -252,95 +312,10 @@ export default class Console extends Service<Config, Hooks, Application, Service
252
312
  }
253
313
  }
254
314
 
255
- private logEntry(entry: ILogObject) {
256
-
257
- // Don't keep logs from the admin sashboard
258
- const [channelType, channelId] = entry.requestId?.split(':') || ['master'];
259
- if (entry.requestId === 'admin')
260
- return;
261
-
262
- // Only keep data required by printPrettyLog
263
- // https://github.com/fullstack-build/tslog/blob/4f045d61333230bd0f9db0e0d59cb1e81fc03aa6/src/LoggerWithoutCallSite.ts#L509
264
- const miniLog: TObjetDonnees = { channelType, channelId };
265
- for (const k of logFields)
266
- miniLog[k] = entry[k];
267
-
268
- this.logs.push(miniLog as TLog);
269
- }
270
-
271
- public client( client: TGuestLogs ) {
272
- this.clients.push(client);
273
- }
274
-
275
- public request( request: TRequestLogs ) {
276
-
277
- if (request.id === 'admin')
278
- return;
279
-
280
- this.requests.push( request );
281
- }
282
-
283
- public database( dbQuery: TDbQueryLog ) {
284
-
285
- this.requests.push( dbQuery );
286
- }
287
-
288
315
  /*----------------------------------
289
316
  - READ
290
317
  ----------------------------------*/
291
318
 
292
- /*public getClients() {
293
- return sql`
294
- SELECT * FROM logs.Clients
295
- ORDER BY activity DESC
296
- LIMIT 100
297
- `.all();
298
- }
299
-
300
- public async getClient(clientId: string) {
301
- return (
302
- this.clients.find(c => c.id === clientId)
303
- ||
304
- await sql`
305
- SELECT * FROM logs.Clients
306
- WHERE id = ${clientId}
307
- `.first()
308
- )
309
- }
310
-
311
- public getRequests(clientId?: string) {
312
- return sql`
313
- SELECT * FROM logs.Requests
314
- ORDER BY date DESC
315
- LIMIT 100
316
- `.all();
317
- }
318
-
319
- public async getRequest(requestId: string) {
320
- return (
321
- this.requests.find(r => r.id === requestId)
322
- ||
323
- await sql`
324
- SELECT * FROM logs.Requests
325
- WHERE id = ${requestId}
326
- `.first()
327
- )
328
- }
329
-
330
- public getQueries( channelType: ChannelInfos["channelType"], channelId?: string ) {
331
-
332
- const filters: Partial<TDbQueryLog> = { channelType };
333
- if (channelId !== undefined)
334
- filters.channelId = channelId;
335
-
336
- return sql`
337
- SELECT * FROM logs.Queries
338
- WHERE :${filters}
339
- ORDER BY date DESC
340
- LIMIT 100
341
- `.all();
342
- }*/
343
-
344
319
  public async getLogs( channelType: ChannelInfos["channelType"], channelId?: string ) {
345
320
 
346
321
  const filters: Partial<TDbQueryLog> = { channelType };
@@ -373,7 +348,7 @@ export default class Console extends Service<Config, Hooks, Application, Service
373
348
  return this.printHtml( entries );
374
349
  }
375
350
 
376
- public printHtml(logs: TLog[], full: boolean = false): string {
351
+ public printHtml( logs: TJsonLog[], full: boolean = false ): string {
377
352
 
378
353
  let html = logs.map( logEntry => logToHTML( logEntry, this )).join('\n');
379
354
 
@@ -9,7 +9,7 @@ import cronParser, { CronExpression } from 'cron-parser';
9
9
  - TYPES
10
10
  ----------------------------------*/
11
11
 
12
- import type { CronManager } from '.';
12
+ import type CronManager from '.';
13
13
 
14
14
  export type TFrequence = string | Date;
15
15
  export type TRunner = () => Promise<any>
@@ -22,17 +22,15 @@ export default class CronTask {
22
22
  public cron?: CronExpression
23
23
  public nextInvocation?: Date;
24
24
 
25
- private debug?: boolean;
26
-
27
25
  public constructor(
28
- manager: CronManager,
26
+ private manager: CronManager,
29
27
  public nom: string,
30
28
  next: TFrequence,
31
29
  public runner: TRunner,
32
30
  public autoexec?: boolean
33
31
  ) {
34
32
 
35
- console.info(`[cron][${this.nom}] Enregistrement de la tâche`);
33
+ this.manager.config.debug && console.info(`[cron][${this.nom}] Enregistrement de la tâche`);
36
34
 
37
35
  this.schedule(next);
38
36
 
@@ -48,13 +46,15 @@ export default class CronTask {
48
46
  this.cron = cronParser.parseExpression(next);
49
47
  this.nextInvocation = this.cron.next().toDate();
50
48
 
51
- console.info(`[cron][${this.nom}] Planifié pour ${this.nextInvocation.toISOString()} via cron ${next}`);
49
+ this.manager.config.debug &&
50
+ console.info(`[cron][${this.nom}] Planifié pour ${this.nextInvocation.toISOString()} via cron ${next}`);
52
51
 
53
52
  // Date
54
53
  } else {
55
54
 
56
55
  this.nextInvocation = next;
57
- console.info(`[cron][${this.nom}] Planifié pour ${this.nextInvocation.toISOString()} via date`);
56
+ this.manager.config.debug &&
57
+ console.info(`[cron][${this.nom}] Planifié pour ${this.nextInvocation.toISOString()} via date`);
58
58
 
59
59
  }
60
60
  }
@@ -80,7 +80,7 @@ export default class CronTask {
80
80
 
81
81
  // Execution
82
82
  this.runner().then(() => {
83
- console.info(`Tâche executée.`);
83
+ this.manager.config.debug && console.info(`Task runned.`);
84
84
  })
85
85
  }
86
86
  }
@@ -21,7 +21,7 @@ export { default as CronTask } from './CronTask';
21
21
  ----------------------------------*/
22
22
 
23
23
  export type Config = {
24
-
24
+ debug?: boolean
25
25
  }
26
26
 
27
27
  export type Hooks = {
@@ -46,8 +46,6 @@ export default class CronManager extends Service<Config, Hooks, Application, Ser
46
46
  ----------------------------------*/
47
47
 
48
48
  protected async start() {
49
-
50
- this.app.on('cleanup', () => this.cleanup());
51
49
 
52
50
  clearInterval(CronManager.timer);
53
51
  CronManager.timer = setInterval(() => {
@@ -111,8 +111,6 @@ const LogPrefix = '[database][meta]';
111
111
  const sqlTypeParamsReg = /\'([^\']+)\'\,?/gi;
112
112
  const typeViaCommentReg = /\[type=([a-z]+)\]/g;
113
113
 
114
- const modelsTypesPath = process.cwd() + '/src/server/models.ts';
115
-
116
114
  /*----------------------------------
117
115
  - FUNCTIONS
118
116
  ----------------------------------*/
@@ -368,11 +366,11 @@ export default class MySQLMetasParser {
368
366
  }
369
367
  }
370
368
 
369
+ // Given that this file is updated during run time,
370
+ // We output a typescript ambient file, so the file change doest trigger infinite app reload
371
371
  fs.outputFileSync(
372
- path.join( Container.path.server.generated, 'models.ts'),
372
+ path.join( Container.path.server.generated, 'models.d.ts'),
373
373
  types.join('\n')
374
374
  );
375
- this.debug && console.log(LogPrefix, `Wrote database types to ${modelsTypesPath}`);
376
-
377
375
  }
378
376
  }
@@ -126,6 +126,9 @@ export default class ServerRouter<
126
126
  public errors: { [code: number]: TErrorRoute } = {};
127
127
  public ssrRoutes: TSsrUnresolvedRoute[] = [];
128
128
 
129
+ // Cache (ex: for static pages)
130
+ public cache: {[pageId: string]: string} = {}
131
+
129
132
  /*----------------------------------
130
133
  - SERVICE
131
134
  ----------------------------------*/
@@ -97,25 +97,40 @@ export default class ServerResponse<
97
97
  // Create response context for controllers
98
98
  const context = await this.createContext(route);
99
99
 
100
+ // Static rendering
101
+ const chunkId = route.options["id"];
102
+ if (route.options.static &&
103
+ chunkId !== undefined
104
+ &&
105
+ this.router.cache[ chunkId ] !== undefined
106
+ ) {
107
+ await this.html( this.router.cache[ chunkId ] );
108
+ return;
109
+ }
110
+
100
111
  // Run controller
101
- const response = await this.route.controller( context );
112
+ const content = await this.route.controller( context );
102
113
 
103
- // Handle response type
104
- if (response === undefined)
114
+ // Handle content type
115
+ if (content === undefined)
105
116
  return;
106
117
 
107
- // No need to process the response
108
- if (response instanceof ServerResponse)
118
+ // No need to process the content
119
+ if (content instanceof ServerResponse)
109
120
  return;
110
121
  // Render react page to html
111
- else if (response instanceof Page)
112
- await this.render(response, context, additionnalData);
122
+ else if (content instanceof Page)
123
+ await this.render(content, context, additionnalData);
113
124
  // Return HTML
114
- else if (typeof response === 'string' && this.route.options.accept === 'html')
115
- await this.html(response);
125
+ else if (typeof content === 'string' && this.route.options.accept === 'html')
126
+ await this.html(content);
116
127
  // Return JSON
117
128
  else
118
- await this.json(response);
129
+ await this.json(content);
130
+
131
+ // Cache
132
+ if (route.options.static)
133
+ this.router.cache[ chunkId ] = this.data;
119
134
  }
120
135
 
121
136
  /*----------------------------------
@@ -36,7 +36,7 @@ declare module "*.svg" {
36
36
  export = value;
37
37
  }
38
38
 
39
- declare module "*.(png|webp)" {
39
+ declare module "*.(png|webp|jpg|jpeg|gif|bmp)" {
40
40
  const value: string;
41
41
  export = value;
42
42
  }
File without changes