create-moost 0.4.22 → 0.5.0

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.
@@ -0,0 +1,233 @@
1
+ /**
2
+ * @file Workflow to HTML Page Interceptor for Moost Workflow Template
3
+ *
4
+ * This module defines the `Wf2HtmlPageInterceptor` class, which serves as an interceptor
5
+ * for MoostHttp handler. The interceptor is responsible for
6
+ * converting workflow outputs into HTML pages with forms, enabling workflows to execute,
7
+ * pause for user inputs, and maintain context in an encrypted state property.
8
+ *
9
+ * **Important:** This example is highly simplified and intended solely for demonstration
10
+ * purposes. A production implementation would require more robust error handling,
11
+ * secure key management, and comprehensive validation mechanisms.
12
+ *
13
+ * The interceptor performs the following key functions:
14
+ * - Decrypts the workflow state received from the frontend.
15
+ * - Processes the workflow response to render appropriate HTML pages for user interaction.
16
+ * - Encrypts the updated workflow state before sending it back to the frontend.
17
+ *
18
+ * For more information on Moost Workflows, visit https://moost.org/wf/
19
+ */
20
+
21
+ import { Body } from "@moostjs/event-http";
22
+ import type { TFlowOutput } from "@moostjs/event-wf";
23
+ import type { TInterceptorClass, TInterceptorFn } from "moost";
24
+ import { Injectable, Intercept } from "moost";
25
+
26
+ import { decryptState, encryptState } from "./wf.encrypt";
27
+ import type {
28
+ TWfExampleContext,
29
+ TWfExampleInput,
30
+ TWfExampleInputSchema,
31
+ TWfState,
32
+ } from "./wf.types";
33
+
34
+ /**
35
+ * `Wf2HtmlPageInterceptor` intercepts http handlers to transform workflow outputs
36
+ * into HTML pages, facilitating user interactions through forms.
37
+ *
38
+ * This interceptor handles the encryption and decryption of the workflow state to ensure
39
+ * secure transmission between the backend and frontend.
40
+ */
41
+ @Injectable("FOR_EVENT")
42
+ class Wf2HtmlPageInterceptor implements TInterceptorClass {
43
+ /**
44
+ * The input payload containing user inputs and the workflow state.
45
+ *
46
+ * @type {TWfExampleInput & { wfState: string | TWfState | undefined }}
47
+ */
48
+ @Body()
49
+ input?: TWfExampleInput & { wfState: string | TWfState | undefined };
50
+
51
+ /**
52
+ * The interceptor handler function that processes workflow events.
53
+ *
54
+ * @type {TInterceptorFn}
55
+ */
56
+ handler: TInterceptorFn = (before, after) => {
57
+ /**
58
+ * Pre-processing step executed before the main workflow logic.
59
+ *
60
+ * - Decrypts the workflow state if it is a string.
61
+ * - Clears the workflow state if it is not a string.
62
+ *
63
+ * @function before
64
+ */
65
+ before(() => {
66
+ if (typeof this.input?.wfState === "string") {
67
+ this.input.wfState = decryptState(this.input.wfState);
68
+ } else if (this.input) {
69
+ this.input.wfState = undefined;
70
+ }
71
+ });
72
+
73
+ /**
74
+ * Post-processing step executed after the main workflow logic.
75
+ *
76
+ * - Renders an input form if additional inputs are required.
77
+ * - Renders the final output page if the workflow has finished.
78
+ *
79
+ * @function after
80
+ * @param {TFlowOutput<TWfExampleContext, TWfExampleInput, TWfExampleInputSchema>} response - The workflow response.
81
+ * @param {Function} reply - The function to send the HTTP response.
82
+ */
83
+ after((response, reply) => {
84
+ const wfOutput = response as TFlowOutput<
85
+ TWfExampleContext,
86
+ TWfExampleInput,
87
+ TWfExampleInputSchema
88
+ >;
89
+
90
+ if (wfOutput.inputRequired) {
91
+ reply(
92
+ renderInputPage(wfOutput.inputRequired, encryptState(wfOutput.state))
93
+ );
94
+ }
95
+ if (wfOutput.finished) {
96
+ reply(renderOutputPage(wfOutput.state.context));
97
+ }
98
+ });
99
+ };
100
+ }
101
+
102
+ /**
103
+ * Factory function to create an interceptor decorator for the `Wf2HtmlPageInterceptor`.
104
+ *
105
+ * @returns {Function} The interceptor decorator.
106
+ */
107
+ export const Wf2HtmlPage = () => Intercept(Wf2HtmlPageInterceptor);
108
+
109
+ /**
110
+ * SVG path data for the Moost logo used in the HTML pages.
111
+ *
112
+ * @type {string}
113
+ */
114
+ const moostLogo =
115
+ '<path fill-rule="evenodd" clip-rule="evenodd" d="M126 71H127.5C137.717 71 146 79.2827 146 89.5C146 99.7173 137.717 108 127.5 108H126C120.942 117.021 113.089 122 103.012 122H127.5C145.449 122 160 107.449 160 89.5C160 71.5507 145.449 57 127.5 57H103.012C113.089 57 120.942 61.9792 126 71ZM103.012 71L89 57H87.5C69.5507 57 55 71.5507 55 89.5C55 107.449 69.5507 122 87.5 122H89L103.012 108H87.5C77.2827 108 69 99.7173 69 89.5C69 79.2827 77.2827 71 87.5 71H103.012Z" fill="#FF269B"/>';
116
+
117
+ /**
118
+ * Renders the HTML page for user inputs.
119
+ *
120
+ * This function generates an HTML form based on the required inputs from the workflow.
121
+ * It includes the necessary fields, messages, error messages, and the encrypted workflow state.
122
+ *
123
+ * **Note:** This implementation is simplified for demonstration purposes.
124
+ *
125
+ * @param {TWfExampleInputSchema} inputRequired - The schema defining required inputs.
126
+ * @param {string} [state=''] - The encrypted workflow state to be included as a hidden field.
127
+ * @returns {string} The rendered HTML string for the input page.
128
+ */
129
+ function renderInputPage(
130
+ inputRequired: TWfExampleInputSchema,
131
+ state = ""
132
+ ): string {
133
+ return `
134
+ <!DOCTYPE html>
135
+ <html>
136
+ <head>
137
+ <title>Moost Workflows Example</title>
138
+ <link rel="preload stylesheet" href="/static/wf.css" as="style">
139
+ </head>
140
+ <body>
141
+ <section>
142
+ <h1>
143
+ <svg viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg">
144
+ ${moostLogo}
145
+ <rect x="27" y="64" width="91" height="51" rx="25.5" stroke="#FF269B" stroke-width="14"/>
146
+ </svg>
147
+ Workflows Example
148
+ </h1>
149
+ <form method="POST" action="/wf">
150
+ ${renderMessage(inputRequired.message)}
151
+ <input type="hidden" name="wfState" value="${state}" />
152
+ ${inputRequired.inputs.map((i) => renderInput(i)).join("\n")}
153
+ ${renderError(inputRequired.errorMessage)}
154
+ <button type="submit">Submit</button>
155
+ <a href="https://moost.org/wf/" target="_blank">Moost WF Documentation</a>
156
+ </form>
157
+ </section>
158
+ </body>
159
+ </html>
160
+ `;
161
+ }
162
+
163
+ /**
164
+ * Renders an optional message within the HTML page.
165
+ *
166
+ * @param {string} [message] - The message to be displayed.
167
+ * @returns {string} The rendered HTML string for the message.
168
+ */
169
+ function renderMessage(message?: string): string {
170
+ return message ? `<p>${message}</p>` : "";
171
+ }
172
+
173
+ /**
174
+ * Renders an optional error message within the HTML page.
175
+ *
176
+ * @param {string} [message] - The error message to be displayed.
177
+ * @returns {string} The rendered HTML string for the error message.
178
+ */
179
+ function renderError(message?: string): string {
180
+ return message ? `<p class="error">${message}</p>` : "";
181
+ }
182
+
183
+ /**
184
+ * Renders the final output HTML page after workflow completion.
185
+ *
186
+ * This function generates an HTML page displaying the user's greeting, email, and age.
187
+ *
188
+ * **Note:** This implementation is simplified for demonstration purposes.
189
+ *
190
+ * @param {TWfExampleContext} ctx - The workflow context containing user data.
191
+ * @returns {string} The rendered HTML string for the output page.
192
+ */
193
+ function renderOutputPage(ctx: TWfExampleContext): string {
194
+ return `
195
+ <!DOCTYPE html>
196
+ <html>
197
+ <head>
198
+ <title>Moost Workflows Example</title>
199
+ <link rel="preload stylesheet" href="/static/wf.css" as="style">
200
+ </head>
201
+ <body>
202
+ <section>
203
+ <h1>
204
+ <svg viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg">
205
+ ${moostLogo}
206
+ <rect x="27" y="64" width="91" height="51" rx="25.5" stroke="#FF269B" stroke-width="14"/>
207
+ </svg>
208
+ Workflows Example
209
+ </h1>
210
+ <h2>${ctx.greeting}</h2>
211
+ <p>Your email is ${ctx.email}</p>
212
+ <p>Your age is ${ctx.age}</p>
213
+
214
+ <a href="https://moost.org/wf/" target="_blank">Moost WF Documentation</a>
215
+ </section>
216
+ </body>
217
+ </html>
218
+ `;
219
+ }
220
+
221
+ /**
222
+ * Renders an individual input field within the HTML form.
223
+ *
224
+ * This function generates the HTML string for a single input field based on its schema.
225
+ *
226
+ * @param {TWfExampleInputSchema['inputs'][number]} input - The input schema item.
227
+ * @returns {string} The rendered HTML string for the input field.
228
+ */
229
+ function renderInput(input: TWfExampleInputSchema["inputs"][number]): string {
230
+ return `<label>${input.label}<input type="${input.type}" name="${
231
+ input.name
232
+ }" value="${input.value || ""}" /></label>`;
233
+ }
@@ -1,52 +0,0 @@
1
- {
2
- "parser": "@typescript-eslint/parser",
3
- "parserOptions": {
4
- "project": ["./tsconfig.json"],
5
- "tsconfigRootDir": "./",
6
- "sourceType": "module"
7
- },
8
- "ignorePatterns": [
9
- //=IF (bundler === 'rollup')
10
- "rollup.config.js",
11
- //=END IF
12
- //=IF (type === 'cli')
13
- "bin.js",
14
- //=END IF
15
- ".eslintrc.js",
16
- "dist"
17
- ],
18
- "plugins": ["@typescript-eslint/eslint-plugin"],
19
- "extends": [
20
- "plugin:@typescript-eslint/eslint-recommended",
21
- "plugin:@typescript-eslint/recommended",
22
- "plugin:@typescript-eslint/recommended-requiring-type-checking",
23
- //=IF (prettier)
24
- "prettier"
25
- //=END IF
26
- ],
27
- "root": true,
28
- "env": {
29
- "node": true,
30
- "jest": true
31
- },
32
- "rules": {
33
- "indent": ["error", 4, { "SwitchCase": 1 }],
34
- "comma-dangle": ["error", "always-multiline"],
35
- "no-multiple-empty-lines": ["error", { "max": 1 }],
36
- "lines-between-class-members": ["error", "always"],
37
- "padded-blocks": ["error", "never"],
38
- "eol-last": ["error", "always"],
39
- "quotes": ["error", "single"],
40
- "semi": ["error", "never"],
41
- "@typescript-eslint/explicit-function-return-type": "off",
42
- "@typescript-eslint/no-empty-interface": "off",
43
- "@typescript-eslint/no-empty-function": "off",
44
- "@typescript-eslint/no-explicit-any": "warn",
45
- "@typescript-eslint/no-inferrable-types": "off",
46
- "@typescript-eslint/explicit-module-boundary-types": "off",
47
- "@typescript-eslint/no-implied-eval": "off",
48
-
49
- "@typescript-eslint/no-this-alias": "warn",
50
- "no-debugger": "error"
51
- }
52
- }
@@ -1,4 +0,0 @@
1
- # Ignore artifacts:
2
- build
3
- coverage
4
- dist
@@ -1,8 +0,0 @@
1
- {
2
- "trailingComma": "es5",
3
- "tabWidth": 4,
4
- "semi": false,
5
- "singleQuote": true,
6
- "quoteProps": "as-needed",
7
- "bracketSpacing": true
8
- }
@@ -1,28 +0,0 @@
1
- const esbuild = require('esbuild')
2
- const esbuildPluginTsc = require('esbuild-plugin-tsc')
3
- //=IF (type === 'http')
4
- const { spawn } = require('child_process')
5
- //=END IF
6
-
7
- const watch = process.argv[2] === 'watch'
8
-
9
- esbuild[watch ? 'context' : 'build']({
10
- entryPoints: [
11
- './src/main.ts',
12
- ],
13
- logLevel: 'info',
14
- bundle: true,
15
- outdir: './dist',
16
- platform: 'node',
17
- packages: 'external',
18
- sourcemap: watch,
19
- plugins: [esbuildPluginTsc()],
20
- }).then(async ctx => {
21
- if (watch) {
22
- //=IF (type === 'http')
23
- const nodemon = spawn('nodemon', { stdio: 'inherit' })
24
- process.on('SIGTERM', async () => { nodemon.kill(); await ctx.dispose() })
25
- //=END IF
26
- await ctx.watch()
27
- }
28
- })
@@ -1,78 +0,0 @@
1
- {
2
- "name": "{{ packageName }}",
3
- "version": "1.0.0",
4
- "description": "",
5
- "main": "dist/main.js",
6
- //=IF (type === 'cli')
7
- "bin": {
8
- "{{ packageName }}": "./bin.js"
9
- },
10
- "files": [
11
- "dist",
12
- "bin.js"
13
- ],
14
- //=END IF
15
- "scripts": {
16
- //=IF (bundler === 'rollup')
17
- //=IF (type === 'http')
18
- "dev": "NODE_OPTIONS=--enable-source-maps rollup -c=./rollup.config.js --watch",
19
- //=END IF
20
- //=IF (type === 'cli')
21
- "dev": "npm run build && ./bin.js",
22
- //=END IF
23
- "build": "rollup -c=./rollup.config.js",
24
- //=END IF
25
- //=IF (bundler === 'esbuild')
26
- //=IF (type === 'http')
27
- "dev": "npm run build:watch",
28
- "nodemon": "NODE_OPTIONS=--enable-source-maps nodemon ./dist/main",
29
- //=END IF
30
- //=IF (type === 'cli')
31
- "dev": "npm run build && ./bin.js",
32
- //=END IF
33
- "build": "node ./build",
34
- "build:watch": "node ./build watch",
35
- //=END IF
36
- //=IF (prettier)
37
- "prettify": "prettier . --write && lint --fix",
38
- //=END IF
39
- "test": "echo \"Error: no test specified\" && exit 1"
40
- },
41
- "author": "",
42
- "license": "ISC",
43
- "dependencies": {
44
- //=IF (type === 'http')
45
- "@moostjs/event-http": "^{{ version }}",
46
- //=END IF
47
- //=IF (type === 'cli')
48
- "@moostjs/event-cli": "^{{ version }}",
49
- //=END IF
50
- "moost": "^{{ version }}"
51
- },
52
- "devDependencies": {
53
- //=IF (bundler === 'rollup')
54
- //=IF (type === 'http')
55
- "@rollup/plugin-run": "^3.0.1",
56
- //=END IF
57
- "@rollup/plugin-typescript": "^11.1.1",
58
- "rollup": "^3.23.0",
59
- "tslib": "^2.5.2",
60
- //=END IF
61
- //=IF (bundler === 'esbuild')
62
- "esbuild": "^0.18.17",
63
- "esbuild-plugin-tsc": "^0.4.0",
64
- //=IF (type === 'http')
65
- "nodemon": "^2.0.22",
66
- //=END IF
67
- //=END IF
68
- //=IF (eslint)
69
- "@typescript-eslint/eslint-plugin": "^5.59.7",
70
- "eslint": "^8.41.0",
71
- "eslint-config-prettier": "^8.8.0",
72
- //=END IF
73
- //=IF (prettier)
74
- "prettier": "^2.8.8",
75
- //=END IF
76
- "typescript": "^5.0.4"
77
- }
78
- }
@@ -1,23 +0,0 @@
1
- //=IF (type === 'http')
2
- const run = require('@rollup/plugin-run')
3
- //=END IF
4
- const ts = require('@rollup/plugin-typescript')
5
- //=IF (type === 'http')
6
- const dev = process.env.NODE_OPTIONS === '--enable-source-maps'
7
- //=END IF
8
- module.exports = {
9
- input: './src/main.ts',
10
- output: {
11
- file: 'dist/main.js',
12
- format: 'cjs',
13
- //=IF (type === 'http')
14
- sourcemap: dev,
15
- //=END IF
16
- },
17
- plugins: [
18
- ts(),
19
- //=IF (type === 'http')
20
- dev && run() || null,
21
- //=END IF
22
- ],
23
- }