5htp-core 0.6.2-97 → 0.6.2-99

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.6.2-97",
4
+ "version": "0.6.2-99",
5
5
  "author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
6
6
  "repository": "git://github.com/gaetanlegac/5htp-core.git",
7
7
  "license": "MIT",
@@ -17,6 +17,7 @@ import ServicesContainer, {
17
17
  // Built-in
18
18
  import type { default as Router, Request as ServerRequest, TRoute } from '@server/services/router';
19
19
  import { Anomaly } from '@common/errors';
20
+ import { preprocessSchema } from '@server/services/router/request/validation/zod';
20
21
 
21
22
  export { default as Services } from './service/container';
22
23
  export type { TEnvConfig as Environment } from './container/config';
@@ -150,7 +151,9 @@ export abstract class Application<
150
151
  console.log('----------------------------------');
151
152
  console.log('- SERVICES');
152
153
  console.log('----------------------------------');
153
- await this.ready();
154
+ const startingServices = await this.ready();
155
+ await Promise.all(startingServices);
156
+ console.log('All services are ready');
154
157
  await this.runHook('ready');
155
158
 
156
159
  const startedTime = (Date.now() - startTime) / 1000;
@@ -179,12 +182,14 @@ export abstract class Application<
179
182
 
180
183
  public register( service: AnyService ) {
181
184
 
182
- service.ready();
185
+ return service.ready();
183
186
 
184
187
  }
185
188
 
186
189
  protected async ready() {
187
190
 
191
+ const startingServices: Promise<any>[] = [];
192
+
188
193
  // Print services
189
194
  const processService = async (propKey: string, service: AnyService, level: number = 0) => {
190
195
 
@@ -193,7 +198,8 @@ export abstract class Application<
193
198
 
194
199
  // Services start shouldn't block app boot
195
200
  // use await ServiceName.started to make services depends on each other
196
- this.starting = service.ready();
201
+ service.starting = service.ready();
202
+ startingServices.push(service.starting);
197
203
  service.status = 'running';
198
204
  console.log('-' + '-'.repeat(level * 1), propKey + ': ' + service.constructor.name);
199
205
 
@@ -203,12 +209,14 @@ export abstract class Application<
203
209
 
204
210
  console.log('Attached service', service.constructor.name, 'to route', route.path);
205
211
 
212
+ const preprocessedSchema = route.schema ? preprocessSchema(route.schema) : undefined;
213
+
206
214
  const origController = route.controller;
207
215
  route.controller = (context: RouterContext) => {
208
216
 
209
217
  // Filter data
210
- const data = route.schema
211
- ? route.schema.parse( context.request.data )
218
+ const data = preprocessedSchema
219
+ ? preprocessedSchema.parse( context.request.data )
212
220
  : {};
213
221
 
214
222
  // Run controller
@@ -253,6 +261,8 @@ export abstract class Application<
253
261
  // Services start shouldn't block app boot
254
262
  processService(serviceId, service);
255
263
  }
264
+
265
+ return startingServices;
256
266
  }
257
267
 
258
268
  }
@@ -50,8 +50,6 @@ export default class DisksManager<
50
50
  super(...args);
51
51
 
52
52
  const drivers = this.config.drivers;
53
-
54
- console.log('drivers', args);
55
53
 
56
54
  if (Object.keys( drivers ).length === 0)
57
55
  throw new Error("At least one disk driver should be mounted.");
@@ -168,11 +168,6 @@ export default class ServerRouter<
168
168
 
169
169
  public async ready() {
170
170
 
171
- // Every hours
172
- setInterval(() => {
173
- this.refreshStaticPages();
174
- }, 1000 * 60 * 60);
175
-
176
171
  // Detect router services
177
172
  for (const serviceName in this.config.plugins) {
178
173
  const service = this.config.plugins[serviceName];
@@ -216,6 +211,12 @@ export default class ServerRouter<
216
211
  );
217
212
  };
218
213
 
214
+
215
+ // When all the services are ready, initialize static routes
216
+ this.app.on('ready', () => {
217
+ this.initStaticRoutes();
218
+ });
219
+
219
220
  }
220
221
 
221
222
  public async shutdown() {
@@ -238,6 +239,8 @@ export default class ServerRouter<
238
239
 
239
240
  if (!rendered) {
240
241
 
242
+ console.log('[router] renderStatic: url', url);
243
+
241
244
  const fullUrl = this.url(url, {}, true);
242
245
  const response = await got( fullUrl, {
243
246
  method: 'GET',
@@ -266,6 +269,26 @@ export default class ServerRouter<
266
269
 
267
270
  }
268
271
 
272
+ private initStaticRoutes() {
273
+
274
+ for (const route of this.routes) {
275
+
276
+ if (!route.options.static)
277
+ continue;
278
+
279
+ // Add to static pages
280
+ // Should be a GET oage that don't take any parameter
281
+ for (const url of route.options.static.urls) {
282
+ this.renderStatic(url, route.options.static);
283
+ }
284
+ }
285
+
286
+ // Every hours, refresh static pages
287
+ setInterval(() => {
288
+ this.refreshStaticPages();
289
+ }, 1000 * 60 * 60);
290
+ }
291
+
269
292
  private refreshStaticPages() {
270
293
 
271
294
  console.log('[router] refreshStaticPages');
@@ -275,15 +298,10 @@ export default class ServerRouter<
275
298
  if (page.expire && page.expire < Date.now()) {
276
299
 
277
300
  this.renderStatic(pageUrl, page.options);
278
-
279
301
  }
280
302
  }
281
303
  }
282
304
 
283
-
284
-
285
-
286
-
287
305
  private registerRoutes(defModules: GlobImportedWithMetas<TRouteModule>) {
288
306
  for (const routeModule of defModules) {
289
307
 
@@ -332,14 +350,6 @@ export default class ServerRouter<
332
350
 
333
351
  this.routes.push(route);
334
352
 
335
- // Add to static pages
336
- // Should be a GET oage that don't take any parameter
337
- if (options.static) {
338
- for (const url of options.static.urls) {
339
- this.renderStatic(url, options.static);
340
- }
341
- }
342
-
343
353
  return this;
344
354
 
345
355
  }
@@ -5,42 +5,61 @@ export type TRichTextValidatorOptions = {
5
5
  attachements?: boolean
6
6
  }
7
7
 
8
- // Recursive function to validate each node
9
- function validateLexicalNode(node: any, opts: TRichTextValidatorOptions ) {
8
+ export const preprocessSchema = (schema: zod.ZodObject): zod.ZodObject => {
10
9
 
11
- // Each node should be an object with a `type` property
12
- if (typeof node !== 'object' || !node.type || typeof node.type !== 'string')
13
- throw new InputError("Invalid rich text value (3).");
10
+ // Not working, data is {}
11
+ return schema;
14
12
 
15
- // Validate text nodes
16
- if (node.type === 'text') {
13
+ if (!(schema instanceof zod.ZodObject))
14
+ return schema;
17
15
 
18
- if (typeof node.text !== 'string')
19
- throw new InputError("Invalid rich text value (4).");
16
+ if (schema.withPreprocessing)
17
+ return schema;
20
18
 
21
- // Validate paragraph, heading, or other structural nodes that may contain children
22
- } else if (['paragraph', 'heading', 'list', 'listitem'].includes(node.type)) {
19
+ const shape = schema.def.shape;
20
+ const newShape: Record<string, zod.ZodTypeAny> = {};
23
21
 
24
- if (!Array.isArray(node.children) || !node.children.every(children => validateLexicalNode(children, opts))) {
25
- throw new InputError("Invalid rich text value (5).");
26
- }
22
+ for (const key in shape) {
27
23
 
28
- // Files upload
29
- } else if (node.type === 'image') {
24
+ if (!['newEntity', 'email'].includes(key))
25
+ continue;
30
26
 
31
- // Check if allowed
32
- /*if (opts.attachements === undefined)
33
- throw new InputError("Image attachments not allowed in this rich text field.");*/
27
+ let current: zod.ZodTypeAny = shape[key];
28
+ while (current) {
29
+
30
+ const origType = current.type;
31
+ const preprocessor = toPreprocess[origType];
34
32
 
35
- // TODO: check mime
33
+ if (origType === 'object') {
34
+ newShape[key] = preprocessSchema(current as zod.ZodObject);
35
+ break;
36
+ }
36
37
 
38
+ if (preprocessor) {
39
+ newShape[key] = preprocessor(current);
40
+ console.log('====newShape', key, newShape[key]);
41
+ break;
42
+ }
37
43
 
38
- // Upload file
44
+ current = current.def;
45
+ }
46
+ }
39
47
 
48
+ const newSchema = zod.object(newShape);
49
+ newSchema.withPreprocessing = true;
50
+ return newSchema;
51
+ }
40
52
 
41
- }
53
+ const toPreprocess = {
54
+
55
+ string: (zString: zod.ZodString) => zod.preprocess( val => {
56
+ return val === '' ? undefined : val;
57
+ }, zString),
58
+
59
+ int: (zInt: zod.ZodInt) => zod.preprocess( val => {
60
+ return typeof val === 'string' ? Number.parseInt(val) : val;
61
+ }, zInt),
42
62
 
43
- return true;
44
63
  }
45
64
 
46
65
  export const schema = {
@@ -56,15 +75,6 @@ export const schema = {
56
75
  return zod.file();
57
76
  },
58
77
 
59
- int: () => zod.preprocess( val => {
60
-
61
- if (typeof val === "string")
62
- return Number.parseInt(val);
63
-
64
- return val;
65
-
66
- }, zod.int()),
67
-
68
78
  choice: ( choices: string[] | { value: any, label: string }[] | _ZodType, options: { multiple?: boolean } = {} ) => {
69
79
 
70
80
  const normalizeValue = (value: any) => typeof value === 'object' ? value.value : value;
@@ -129,4 +139,42 @@ export const schema = {
129
139
  })
130
140
  }
131
141
 
132
- export type { default as z } from 'zod';
142
+ // Recursive function to validate each node
143
+ function validateLexicalNode(node: any, opts: TRichTextValidatorOptions ) {
144
+
145
+ // Each node should be an object with a `type` property
146
+ if (typeof node !== 'object' || !node.type || typeof node.type !== 'string')
147
+ throw new InputError("Invalid rich text value (3).");
148
+
149
+ // Validate text nodes
150
+ if (node.type === 'text') {
151
+
152
+ if (typeof node.text !== 'string')
153
+ throw new InputError("Invalid rich text value (4).");
154
+
155
+ // Validate paragraph, heading, or other structural nodes that may contain children
156
+ } else if (['paragraph', 'heading', 'list', 'listitem'].includes(node.type)) {
157
+
158
+ if (!Array.isArray(node.children) || !node.children.every(children => validateLexicalNode(children, opts))) {
159
+ throw new InputError("Invalid rich text value (5).");
160
+ }
161
+
162
+ // Files upload
163
+ } else if (node.type === 'image') {
164
+
165
+ // Check if allowed
166
+ /*if (opts.attachements === undefined)
167
+ throw new InputError("Image attachments not allowed in this rich text field.");*/
168
+
169
+ // TODO: check mime
170
+
171
+
172
+ // Upload file
173
+
174
+
175
+ }
176
+
177
+ return true;
178
+ }
179
+
180
+ export type { default as z } from 'zod';
@@ -313,11 +313,11 @@ export default class ServerResponse<
313
313
  return this.end();
314
314
  }
315
315
 
316
- public redirect(url: string, code: number = 302) {
316
+ public redirect(url: string, code: number = 302, absolute: boolean = false) {
317
317
 
318
318
  debug && console.log("[routeur][response] Redirect", url);
319
319
  this.statusCode = code;
320
- this.headers['Location'] = this.router.url( url );
320
+ this.headers['Location'] = this.router.url( url, {}, absolute );
321
321
  return this.end();
322
322
  }
323
323
 
@@ -12,7 +12,7 @@ import {
12
12
  } from '@server/services/router';
13
13
 
14
14
  // Ap
15
- import { schema } from '@server/services/router/request/validation/zod';
15
+ import { preprocessSchema, schema } from '@server/services/router/request/validation/zod';
16
16
 
17
17
  /*----------------------------------
18
18
  - SERVICE CONFIG
@@ -42,6 +42,8 @@ export default(
42
42
 
43
43
  const schema = typeof fields === 'object' ? zod.object(fields) : fields;
44
44
 
45
- return schema.parse(request.data);
45
+ const preprocessedSchema = preprocessSchema(schema);
46
+
47
+ return preprocessedSchema.parse(request.data);
46
48
  },
47
49
  })
package/types/icons.d.ts CHANGED
@@ -1 +1 @@
1
- export type TIcones = "solid/spinner-third"|"rocket"|"user-circle"|"times"|"at"|"star"|"plus"|"minus"|"trash"|"magnet"|"search"|"paper-plane"|"brands/linkedin"|"play"|"stop"|"check"|"plus-circle"|"clock"|"cog"|"ellipsis-h"|"long-arrow-right"|"lightbulb"|"long-arrow-left"|"phone"|"arrow-right"|"regular/shield-check"|"angle-down"|"plane-departure"|"comments-alt"|"user-shield"|"shield-alt"|"chart-line"|"money-bill-wave"|"link"|"file-alt"|"envelope"|"solid/crown"|"eye"|"pen"|"file"|"angle-up"|"user-plus"|"sack-dollar"|"info-circle"|"mouse-pointer"|"thumbs-up"|"dollar-sign"|"download"|"brands/google"|"brands/whatsapp"|"crown"|"check-circle"|"exclamation-circle"|"times-circle"|"arrow-left"|"broom"|"solid/check-circle"|"solid/exclamation-triangle"|"solid/times-circle"|"hourglass"|"key"|"building"|"briefcase"|"map-marker-alt"|"graduation-cap"|"angle-left"|"angle-right"|"plug"|"coin"|"coins"|"minus-circle"|"external-link"|"question-circle"|"arrow-to-bottom"|"magic"|"user"|"meh-rolling-eyes"|"unlink"|"bold"|"italic"|"underline"|"strikethrough"|"subscript"|"superscript"|"code"|"font"|"empty-set"|"horizontal-rule"|"page-break"|"image"|"table"|"poll"|"columns"|"sticky-note"|"caret-right"|"list-ul"|"check-square"|"h1"|"h2"|"h3"|"h4"|"list-ol"|"paragraph"|"quote-left"|"align-left"|"align-center"|"align-right"|"align-justify"|"indent"|"outdent"
1
+ export type TIcones = "solid/spinner-third"|"rocket"|"user-circle"|"brands/linkedin"|"play"|"stop"|"trash"|"times"|"at"|"star"|"plus"|"minus"|"magnet"|"paper-plane"|"search"|"check"|"plus-circle"|"regular/shield-check"|"angle-down"|"clock"|"cog"|"ellipsis-h"|"long-arrow-right"|"lightbulb"|"long-arrow-left"|"phone"|"arrow-right"|"plane-departure"|"comments-alt"|"user-shield"|"shield-alt"|"chart-line"|"money-bill-wave"|"link"|"file-alt"|"solid/crown"|"eye"|"pen"|"file"|"envelope"|"angle-up"|"user-plus"|"sack-dollar"|"info-circle"|"mouse-pointer"|"thumbs-up"|"dollar-sign"|"download"|"brands/google"|"brands/whatsapp"|"crown"|"check-circle"|"exclamation-circle"|"times-circle"|"arrow-left"|"key"|"building"|"briefcase"|"map-marker-alt"|"graduation-cap"|"solid/check-circle"|"solid/exclamation-triangle"|"solid/times-circle"|"hourglass"|"angle-left"|"angle-right"|"broom"|"question-circle"|"coin"|"coins"|"plug"|"arrow-to-bottom"|"external-link"|"magic"|"minus-circle"|"user"|"meh-rolling-eyes"|"bold"|"italic"|"underline"|"strikethrough"|"subscript"|"superscript"|"code"|"unlink"|"font"|"empty-set"|"horizontal-rule"|"page-break"|"image"|"table"|"poll"|"columns"|"sticky-note"|"caret-right"|"align-left"|"align-center"|"align-right"|"align-justify"|"indent"|"outdent"|"list-ul"|"check-square"|"h1"|"h2"|"h3"|"h4"|"list-ol"|"paragraph"|"quote-left"