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,93 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ // Npm
6
+ import type { ComponentChild } from 'preact';
7
+
8
+ // Core
9
+ import type { ClientContext } from '@/client/context';
10
+ import type { TRouteOptions } from '.';
11
+ import type { TDataProvider } from './response/page';
12
+
13
+ // App
14
+ import internalLayout from '@client/pages/_layout';
15
+
16
+ import * as layouts from '@/client/pages/**/_layout/index.tsx';
17
+
18
+ /*----------------------------------
19
+ - CONST
20
+ ----------------------------------*/
21
+
22
+ export const layoutsList = layouts as ImportedLayouts;
23
+
24
+ /*----------------------------------
25
+ - TYPES
26
+ ----------------------------------*/
27
+ type LayoutComponent = (attributes: { context: ClientContext }) => ComponentChild;
28
+
29
+ export type Layout = {
30
+ path: string,
31
+ Component: LayoutComponent,
32
+ data?: TDataProvider
33
+ }
34
+
35
+ export type ImportedLayouts = {
36
+ [chunkId: string]: Layout["Component"]
37
+ }
38
+
39
+ /*----------------------------------
40
+ - UTILS
41
+ ----------------------------------*/
42
+ // TODO: getLayot only on server side, and pass the layout chunk id
43
+ export const getLayout = (routePath: string, routeOptions?: TRouteOptions): Layout | undefined => {
44
+
45
+ if (routeOptions === undefined)
46
+ return undefined;
47
+ // W don't want a layout on this page
48
+ if (routeOptions.layout === false)
49
+ return undefined;
50
+
51
+ // options.id has been injected via the babel plugon
52
+ const chunkId = routeOptions["id"];
53
+ if (chunkId === undefined) {
54
+ console.error("Route informations where ID cas not injected:", routeOptions);
55
+ throw new Error(`ID has not injected for the following page route: ${routePath}`);
56
+ }
57
+
58
+ // Layout via name
59
+ if (routeOptions.layout !== undefined) {
60
+
61
+ const { default: LayoutComponent, data } = layouts[routeOptions.layout];
62
+ if (LayoutComponent === undefined)
63
+ throw new Error(`No layout found with ID: ${routeOptions.layout}. registered layouts: ${Object.keys(layouts)}`);
64
+
65
+ return {
66
+ path: routeOptions.layout,
67
+ Component: layouts[routeOptions.layout].default,
68
+ data
69
+ }
70
+ }
71
+
72
+ // Automatic layout via the nearest _layout folder
73
+ for (const layoutPath in layouts)
74
+ if (
75
+ // The layout is nammed '' when it's at the root (@/client/pages/_layout)
76
+ layoutPath === '' // Root layout
77
+ // Exact match
78
+ || chunkId === layoutPath
79
+ // Parent
80
+ || chunkId.startsWith( layoutPath + '_' )
81
+ )
82
+ return {
83
+ path: layoutPath,
84
+ Component: layouts[layoutPath].default,
85
+ data: layouts[layoutPath].data,
86
+ };
87
+
88
+ // Internal layout
89
+ return {
90
+ path: '/',
91
+ Component: internalLayout
92
+ }
93
+ }
@@ -0,0 +1,55 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ // Npm
6
+ import { pathToRegexp, Key } from 'path-to-regexp';
7
+
8
+ // Core
9
+ import { getLayout } from './layouts';
10
+
11
+ // types
12
+ import type { TRouteOptions } from '.';
13
+ import type { TFrontRenderer } from './response/page';
14
+ import type { TRegisterPageArgs } from '@client/services/router';
15
+
16
+ /*----------------------------------
17
+ - UTILS
18
+ ----------------------------------*/
19
+
20
+ export const getRegisterPageArgs = (...args: TRegisterPageArgs) => {
21
+
22
+ let path: string;
23
+ let options: Partial<TRouteOptions> = {};
24
+ let renderer: TFrontRenderer;
25
+
26
+ if (args.length === 2)
27
+ ([path, renderer] = args)
28
+ else
29
+ ([path, options, renderer] = args)
30
+
31
+ // Automatic layout form the nearest _layout folder
32
+ const layout = getLayout(path, options);
33
+
34
+ return { path, options, renderer, layout }
35
+
36
+ }
37
+
38
+ export const buildRegex = ( path: string ) => {
39
+
40
+ // pathToRegexp ne supporte plus les wildcards depuis 4.0
41
+ if (path.endsWith('*'))
42
+ path = path.substring(0, path.length - 1) + '(.*)';
43
+
44
+ // path => regex
45
+ const keys: Key[] = []
46
+ const regex = pathToRegexp(path, keys, {
47
+ sensitive: true
48
+ });
49
+
50
+ return {
51
+ keys: keys.map(k => k.name),
52
+ regex
53
+ }
54
+
55
+ }
@@ -0,0 +1,77 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ import type { HttpMethod } from '@server/services/router';
6
+
7
+ /*----------------------------------
8
+ - TYPES
9
+ ----------------------------------*/
10
+
11
+ // The fetcher can be undefined if we put a condition on it
12
+ // By example if we want to fetch an api endpoint only if the url contains a certain url parameter
13
+ export type TFetcherList = { [id: string]: /*TFetcher | */Promise<any> | undefined }
14
+
15
+ export type TFetcher<TData extends any = unknown> = {
16
+
17
+ // For async calls: api.post(...).then((data) => ...)
18
+ then: (callback: (data: TData) => void) => Promise<TData>,
19
+ catch: (callback: (data: any) => false | void) => Promise<TData>,
20
+ finally: (callback: () => void) => Promise<TData>,
21
+ run: () => Promise<TData>,
22
+
23
+ method: HttpMethod,
24
+ path: string,
25
+ data?: TPostDataWithFile,
26
+ options?: TApiFetchOptions
27
+ }
28
+
29
+ export type TFetcherArgs = [
30
+ method: HttpMethod,
31
+ path: string,
32
+ data?: TPostDataWithFile,
33
+ options?: TApiFetchOptions
34
+ ]
35
+
36
+ export type TApiFetchOptions = {
37
+ captcha?: string, // Action id (required by recaptcha)
38
+ onProgress?: (percent: number) => void,
39
+ // Default: json
40
+ encoding?: 'json' | 'multipart'
41
+ }
42
+
43
+ export type TPostData = TPostDataWithFile
44
+
45
+ export type TPostDataWithFile = { [key: string]: PrimitiveValue }
46
+
47
+ export type TPostDataWithoutFile = { [key: string]: PrimitiveValue }
48
+
49
+ // https://stackoverflow.com/questions/44851268/typescript-how-to-extract-the-generic-parameter-from-a-type
50
+ type TypeWithGeneric<T> = TFetcher<T>
51
+ type extractGeneric<Type> = Type extends TypeWithGeneric<infer X> ? X : never
52
+
53
+ export type TDataReturnedByFetchers<TProvidedData extends TFetcherList = {}> = {
54
+ [Property in keyof TProvidedData]: ThenArg< TProvidedData[Property] >
55
+ }
56
+
57
+ /*----------------------------------
58
+ - CLASS
59
+ ----------------------------------*/
60
+ export default abstract class ApiClient {
61
+
62
+ /*----------------------------------
63
+ - TOP LEVEL
64
+ ----------------------------------*/
65
+
66
+ public abstract set( newData: TObjetDonnees );
67
+
68
+ public abstract reload( ids?: string | string[], params?: TObjetDonnees );
69
+
70
+ /*----------------------------------
71
+ - LOW LEVEL
72
+ ----------------------------------*/
73
+
74
+ public abstract createFetcher<TData extends unknown = unknown>(...args: TFetcherArgs): TFetcher<TData>;
75
+
76
+ public abstract fetchSync(fetchers: TFetcherList, alreadyLoadedData: {}): Promise<TObjetDonnees>;
77
+ }
@@ -0,0 +1,35 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ // Core
6
+ import Response from '../response';
7
+
8
+ // Types
9
+ import type { TBasicUser } from '@server/services/auth';
10
+
11
+ /*----------------------------------
12
+ - TYPES
13
+ ----------------------------------*/
14
+
15
+
16
+ /*----------------------------------
17
+ - CONTEXT
18
+ ----------------------------------*/
19
+ export default abstract class BaseRequest {
20
+
21
+ // Permet d'accèder à l'instance complète via spread
22
+ public request: this = this;
23
+ public url!: string;
24
+ public host!: string;
25
+
26
+ public data: TObjetDonnees = {};
27
+ public abstract response?: Response;
28
+ public user: TBasicUser | null = null;
29
+
30
+ public constructor(
31
+ public path: string,
32
+ ) {
33
+
34
+ }
35
+ }
@@ -0,0 +1,45 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ // Npm
6
+ import { FunctionalComponent } from "preact";
7
+
8
+ // Core
9
+ import { TAnyRoute } from "..";
10
+ import type ClientRequest from '@client/services/router/request';
11
+ import Page from '@client/services/router/response/page'
12
+
13
+ /*----------------------------------
14
+ - TYPES
15
+ ----------------------------------*/
16
+
17
+ export type TResponseData = Page
18
+
19
+ /*----------------------------------
20
+ - CONTEXT
21
+ ----------------------------------*/
22
+ export default abstract class BaseResponse<
23
+ TData extends TResponseData = Page,
24
+ TRequest extends ClientRequest = ClientRequest
25
+ > {
26
+
27
+ public data?: TData;
28
+ public request: TRequest;
29
+ public route?: TAnyRoute;
30
+
31
+ public constructor(
32
+ request: TRequest,
33
+ ) {
34
+ // ServerResponse et ClientResponse assignent request.response
35
+ request.response = this;
36
+ this.request = request as TRequest;
37
+ }
38
+
39
+ public setRoute(route: TAnyRoute) {
40
+ this.route = route;
41
+ return this;
42
+ }
43
+
44
+ public abstract redirect(url: string, code: number);
45
+ }
@@ -0,0 +1,128 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ // Npm
6
+ import type { VNode } from 'preact';
7
+ import type { Thing } from 'schema-dts';
8
+
9
+ // Core libs
10
+ import { ClientOrServerRouter, TClientOrServerContextForPage, TRoute, TErrorRoute } from '@common/router';
11
+ import { TFetcherList, TDataReturnedByFetchers } from '@common/router/request/api';
12
+
13
+ /*----------------------------------
14
+ - TYPES
15
+ ----------------------------------*/
16
+
17
+ // The function that fetch data from the api before to pass them as context to the renderer
18
+ export type TDataProvider<TProvidedData extends TFetcherList = TFetcherList> = (
19
+ context: TClientOrServerContextForPage & {
20
+ // URL query parameters
21
+ // TODO: typings
22
+ data: {[key: string]: string | number}
23
+ }
24
+ ) => TProvidedData
25
+
26
+ // The function that renders routes
27
+ export type TFrontRenderer<
28
+ TProvidedData extends TFetcherList = TFetcherList,
29
+ TAdditionnalData extends {} = {},
30
+ TRouter = ClientOrServerRouter,
31
+ > = (
32
+ context: (
33
+ TClientOrServerContextForPage
34
+ &
35
+ TAdditionnalData
36
+ &
37
+ {
38
+ context: TClientOrServerContextForPage,
39
+ data: {[key: string]: PrimitiveValue}
40
+ }
41
+ )
42
+ ) => VNode<any> | null
43
+
44
+ // Script or CSS resource
45
+ export type TPageResource = {
46
+ id: string,
47
+ attrs?: TObjetDonnees
48
+ } & ({
49
+ inline: string
50
+ } | {
51
+ url: string,
52
+ preload?: boolean
53
+ })
54
+
55
+ type TMetasDict = {
56
+ [key: string]: string | Date | undefined | null
57
+ }
58
+
59
+ type TMetasList = ({ $: string } & TMetasDict)[]
60
+
61
+ const debug = false;
62
+
63
+ /*----------------------------------
64
+ - CLASS
65
+ ----------------------------------*/
66
+ export default abstract class PageResponse<TRouter extends ClientOrServerRouter = ClientOrServerRouter> {
67
+
68
+ // Metadata
69
+ public chunkId: string;
70
+ public title?: string;
71
+ public description?: string;
72
+ public bodyClass: Set<string> = new Set<string>();
73
+ public bodyId?: string;
74
+ public url: string;
75
+
76
+ // Resources
77
+ public head: TMetasList = [];
78
+ public metas: TMetasDict = {};
79
+ public jsonld: Thing[] = [];
80
+ public scripts: TPageResource[] = [];
81
+ public style: TPageResource[] = [];
82
+
83
+ // Data
84
+ public fetchers: TFetcherList = {};
85
+ public data: TObjetDonnees = {};
86
+
87
+ public constructor(
88
+ public route: TRoute | TErrorRoute,
89
+ public renderer: TFrontRenderer,
90
+ public context: TClientOrServerContextForPage
91
+ ) {
92
+
93
+ this.chunkId = context.route.options["id"];
94
+
95
+ this.url = context.request.url;
96
+
97
+ this.fetchers = this.createFetchers(route.options.data);
98
+
99
+ }
100
+
101
+ private createFetchers( dataProvider?: TDataProvider ) {
102
+
103
+ // Load the fetchers list to load data if needed
104
+ if (dataProvider)
105
+ return dataProvider({
106
+ ...this.context,
107
+ data: this.context.request.data
108
+ });
109
+ else
110
+ return {}
111
+
112
+ }
113
+
114
+ public async fetchData() {
115
+
116
+ // Fetch layout data
117
+ if (this.layout?.data) {
118
+ const fetchers = this.createFetchers(this.layout.data);
119
+ this.fetchers = { ...this.fetchers, ...fetchers };
120
+ }
121
+
122
+ // Fetch page data
123
+ debug && console.log(`[router][page] Fetching api data:` + Object.keys(this.fetchers));
124
+ this.data = await this.context.request.api.fetchSync( this.fetchers, this.data );
125
+
126
+ return this.data;
127
+ }
128
+ }
@@ -0,0 +1,183 @@
1
+
2
+ import type Driver from '@server/services/disks/driver';
3
+ import { Anomaly } from '@common/errors';
4
+
5
+ export type LexicalNode = {
6
+ version: number,
7
+ type: string,
8
+ children?: LexicalNode[],
9
+ // Attachement
10
+ src?: string;
11
+ // Headhing
12
+ text?: string;
13
+ anchor?: string;
14
+ tag?: string;
15
+ }
16
+
17
+ export type LexicalState = {
18
+ root: LexicalNode
19
+ }
20
+
21
+ export type TRenderOptions = {
22
+
23
+ format?: 'html' | 'text', // Default = html
24
+ transform?: RteUtils["transformNode"],
25
+
26
+ render?: (
27
+ node: LexicalNode,
28
+ parent: LexicalNode | null,
29
+ options: TRenderOptions
30
+ ) => Promise<LexicalNode>,
31
+
32
+ attachements?: {
33
+ disk: Driver,
34
+ directory: string,
35
+ prevVersion?: string | LexicalState | null,
36
+ }
37
+ }
38
+
39
+ export type TSkeleton = {
40
+ id: string,
41
+ title: string,
42
+ level: number,
43
+ childrens: TSkeleton
44
+ }[];
45
+
46
+ export type TContentAssets = {
47
+ attachements: string[],
48
+ skeleton: TSkeleton
49
+ }
50
+
51
+ export default abstract class RteUtils {
52
+
53
+ public async render(
54
+ content: string | LexicalState,
55
+ options: TRenderOptions = {}
56
+ ): Promise<TContentAssets & {
57
+ html: string | null,
58
+ json: string | LexicalState,
59
+ }> {
60
+
61
+ // Transform content
62
+ const assets: TContentAssets = {
63
+ attachements: [],
64
+ skeleton: []
65
+ }
66
+
67
+ // Parse content if string
68
+ let json = this.parseState(content);
69
+ if (json === false)
70
+ return { html: '', json: content, ...assets }
71
+
72
+ // Parse prev version if string
73
+ if (typeof options?.attachements?.prevVersion === 'string') {
74
+ try {
75
+ options.attachements.prevVersion = JSON.parse(options.attachements.prevVersion) as LexicalState;
76
+ } catch (error) {
77
+ throw new Anomaly("Invalid JSON format for the given JSON RTE prev version.");
78
+ }
79
+ }
80
+
81
+ const root = await this.processContent(json.root, null, async (node, parent) => {
82
+ return await this.transformNode(node, parent, assets, options);
83
+ });
84
+
85
+ json = { ...json, root };
86
+
87
+ // Delete unused attachements
88
+ const attachementOptions = options?.attachements;
89
+ if (attachementOptions && attachementOptions.prevVersion !== undefined) {
90
+
91
+ await this.processContent(root, null, async (node) => {
92
+ return await this.deleteUnusedFile(node, assets, attachementOptions);
93
+ });
94
+ }
95
+
96
+ // Convert json to HTML
97
+ let html: string | null;
98
+ if (options.format === 'text')
99
+ html = await this.jsonToText( json.root );
100
+ else
101
+ html = await this.jsonToHtml( json, options );
102
+
103
+ return { html, json: content, ...assets };
104
+ }
105
+
106
+ private parseState( content: string | LexicalState ): LexicalState | false {
107
+
108
+ if (typeof content === 'string' && content.trim().startsWith('{')) {
109
+ try {
110
+ return JSON.parse(content) as LexicalState;
111
+ } catch (error) {
112
+ throw new Anomaly("Invalid JSON format for the given JSON RTE content.");
113
+ }
114
+ } else if (content && typeof content === 'object' && content.root)
115
+ return content;
116
+ else
117
+ return false;
118
+
119
+ }
120
+
121
+ protected jsonToText(root: LexicalNode): string {
122
+ let result = '';
123
+
124
+ function traverse(node: LexicalNode) {
125
+ switch (node.type) {
126
+ case 'text':
127
+ // Leaf text node
128
+ result += node.text ?? '';
129
+ break;
130
+ case 'linebreak':
131
+ // Explicit line break node
132
+ result += '\n';
133
+ break;
134
+ default:
135
+ // Container or block node: dive into children if any
136
+ if (node.children) {
137
+ node.children.forEach(traverse);
138
+ }
139
+ // After finishing a block-level node, append newline
140
+ if (isBlockNode(node.type)) {
141
+ result += '\n';
142
+ }
143
+ break;
144
+ }
145
+ }
146
+
147
+ // Heuristic: treat these as blocks
148
+ function isBlockNode(type: string): boolean {
149
+ return [
150
+ 'root',
151
+ 'paragraph',
152
+ 'heading',
153
+ 'listitem',
154
+ 'unorderedlist',
155
+ 'orderedlist',
156
+ 'quote',
157
+ 'codeblock',
158
+ 'table',
159
+ ].includes(type);
160
+ }
161
+
162
+ traverse(root);
163
+
164
+ // Trim trailing whitespace/newlines
165
+ return result.replace(/\s+$/, '');
166
+ }
167
+
168
+ public abstract jsonToHtml( json: LexicalState, options: TRenderOptions ): Promise<string | null>;
169
+
170
+ protected abstract processContent(
171
+ node: LexicalNode,
172
+ parent: LexicalNode | null,
173
+ callback: (node: LexicalNode, parent: LexicalNode | null) => Promise<LexicalNode>
174
+ ): Promise<LexicalNode>;
175
+
176
+ protected abstract transformNode( node: LexicalNode, parent: LexicalNode | null, assets: TContentAssets, options: TRenderOptions ): Promise<LexicalNode>;
177
+
178
+ protected abstract deleteUnusedFile(
179
+ node: LexicalNode,
180
+ assets: TContentAssets,
181
+ options: NonNullable<TRenderOptions["attachements"]>
182
+ ): Promise<LexicalNode>;
183
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Helper to wait x seconds with promises
3
+ * Usage: await seconds(10); // Wait 10 seconds
4
+ * @param seconds The number of seconds to wait
5
+ * @returns A void promise
6
+ */
7
+ export const seconds = (seconds: number) => new Promise((resolve) => setTimeout(resolve, seconds * 1000));
package/doc/TODO.md ADDED
@@ -0,0 +1,71 @@
1
+ * Fix erreurs type Client / Server context
2
+ * Server side: ServerContext
3
+ * Client side: ClientContext | ServerContext
4
+ * PageResponse extends Response
5
+ * Toast service
6
+ * ClientApplication hooks
7
+ app.on('bug')
8
+ app.on('error')
9
+
10
+
11
+ # Dependancies injection
12
+
13
+ # Full stack Pages
14
+
15
+ ```typescript
16
+ import Router from '@server/services/router';
17
+ import { TRouterContext as ServerServices } from '@server/services/router/response';
18
+ import { TRouterContext as ClientServices } from '@client/services/router/response';
19
+
20
+ abstract class Controller<
21
+ TRouter extends Router,
22
+ TData extends any = any,
23
+ TUserAccess extends string = string
24
+ > {
25
+
26
+ abstract auth: TUserAccess;
27
+
28
+ abstract get( services: ServerServices<TRouter> ): Promise<TData>;
29
+
30
+ abstract render( context: TData, services: ClientServices<TRouter> ): ComponentChild;
31
+
32
+ }
33
+ ```
34
+
35
+ ```typescript
36
+ //? /headhunter/missions/suggested'
37
+ class Missions extends Controller<CrossPath["router"]> {
38
+
39
+ auth = 'USER';
40
+
41
+ async get({ headhunting, response, auth }) {
42
+
43
+ const user = await auth.check('USER');
44
+
45
+ const suggested = await headhunting.missions.Suggest( user );
46
+
47
+ return { suggested }
48
+ }
49
+
50
+ render({ page, api, suggested }) {
51
+ return (
52
+ <Page title="App title here" subtitle="SEO description here">{page.loading || <>
53
+
54
+ <section class="col">
55
+
56
+ <header class="row">
57
+ <h2 class="col-1">Suggested Missions</h2>
58
+ </header>
59
+
60
+ <div class="grid xa3">
61
+ {suggested.map( mission => (
62
+ <MissionCard mission={mission} />
63
+ ))}
64
+ </div>
65
+ </section>
66
+
67
+ </>}</Page>
68
+ )
69
+ }
70
+ }
71
+ ```