underpost 2.8.878 → 2.8.881
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/.env.development +35 -3
- package/.env.production +40 -3
- package/.env.test +35 -3
- package/.github/workflows/release.cd.yml +2 -1
- package/README.md +44 -36
- package/bin/deploy.js +40 -0
- package/cli.md +88 -86
- package/conf.js +28 -3
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/package.json +1 -2
- package/src/api/document/document.controller.js +66 -0
- package/src/api/document/document.model.js +51 -0
- package/src/api/document/document.router.js +24 -0
- package/src/api/document/document.service.js +125 -0
- package/src/cli/deploy.js +1 -1
- package/src/cli/index.js +2 -0
- package/src/cli/run.js +27 -1
- package/src/client/Default.index.js +46 -1
- package/src/client/components/core/Account.js +8 -1
- package/src/client/components/core/AgGrid.js +18 -9
- package/src/client/components/core/BtnIcon.js +3 -2
- package/src/client/components/core/Content.js +2 -1
- package/src/client/components/core/CssCore.js +4 -0
- package/src/client/components/core/Docs.js +0 -3
- package/src/client/components/core/Input.js +34 -19
- package/src/client/components/core/Modal.js +29 -7
- package/src/client/components/core/ObjectLayerEngine.js +370 -0
- package/src/client/components/core/ObjectLayerEngineModal.js +1 -0
- package/src/client/components/core/Panel.js +7 -2
- package/src/client/components/core/PanelForm.js +187 -63
- package/src/client/components/core/VanillaJs.js +3 -0
- package/src/client/components/default/MenuDefault.js +94 -41
- package/src/client/components/default/RoutesDefault.js +2 -0
- package/src/client/services/default/default.management.js +1 -0
- package/src/client/services/document/document.service.js +97 -0
- package/src/client/services/file/file.service.js +2 -0
- package/src/client/ssr/Render.js +1 -1
- package/src/client/ssr/head/DefaultScripts.js +2 -0
- package/src/client/ssr/head/Seo.js +1 -0
- package/src/index.js +1 -1
- package/src/mailer/EmailRender.js +1 -1
- package/src/server/client-build.js +2 -3
- package/src/server/client-formatted.js +40 -12
- package/src/server/conf.js +5 -1
- package/src/server/object-layer.js +196 -0
- package/src/server/runtime.js +10 -13
- package/src/server/ssr.js +52 -10
- package/src/server/valkey.js +89 -1
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
import fs from 'fs-extra';
|
|
4
|
-
import { srcFormatted, componentFormatted, viewFormatted,
|
|
4
|
+
import { srcFormatted, componentFormatted, viewFormatted, JSONweb } from './client-formatted.js';
|
|
5
5
|
import { loggerFactory } from './logger.js';
|
|
6
6
|
import {
|
|
7
|
-
cap,
|
|
8
7
|
getCapVariableName,
|
|
9
8
|
newInstance,
|
|
10
9
|
orderArrayFromAttrInt,
|
|
11
|
-
titleFormatted,
|
|
12
10
|
uniqueArray,
|
|
13
11
|
} from '../client/components/core/CommonJs.js';
|
|
14
12
|
import UglifyJS from 'uglify-js';
|
|
@@ -22,6 +20,7 @@ import { Readable } from 'stream';
|
|
|
22
20
|
import { buildIcons } from './client-icons.js';
|
|
23
21
|
import Underpost from '../index.js';
|
|
24
22
|
import { buildDocs } from './client-build-docs.js';
|
|
23
|
+
import { ssrFactory } from './ssr.js';
|
|
25
24
|
|
|
26
25
|
dotenv.config();
|
|
27
26
|
|
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Module for formatting client-side code
|
|
3
|
+
* @module src/server/client-formatted.js
|
|
4
|
+
* @namespace clientFormatted
|
|
5
|
+
*/
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
import vm from 'node:vm';
|
|
5
|
-
import Underpost from '../index.js';
|
|
7
|
+
'use strict';
|
|
6
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Formats a source code string by removing 'html`' and 'css`' tags from template literals.
|
|
11
|
+
* @param {string} src - The source code string.
|
|
12
|
+
* @returns {string} The formatted source code.
|
|
13
|
+
* @memberof clientFormatted
|
|
14
|
+
*/
|
|
7
15
|
const srcFormatted = (src) =>
|
|
8
16
|
src
|
|
9
17
|
.replaceAll(' html`', '`')
|
|
@@ -15,8 +23,26 @@ const srcFormatted = (src) =>
|
|
|
15
23
|
.replaceAll('[html`', '[`')
|
|
16
24
|
.replaceAll('[css`', '[`');
|
|
17
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Converts a JavaScript object into a string that can be embedded in client-side code
|
|
28
|
+
* and parsed back into an object (e.g., 'JSON.parse(`{...}`)').
|
|
29
|
+
* @param {*} data - The data to be stringified.
|
|
30
|
+
* @returns {string} A string representing the code to parse the JSON data.
|
|
31
|
+
* @memberof clientFormatted
|
|
32
|
+
*/
|
|
18
33
|
const JSONweb = (data) => 'JSON.parse(`' + JSON.stringify(data) + '`)';
|
|
19
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Formats a component's source code by rewriting its import paths to be absolute for browser consumption.
|
|
37
|
+
* @param {string} src - The source code of the component.
|
|
38
|
+
* @param {string} module - The name of the module/component.
|
|
39
|
+
* @param {Array<object>} dists - An array of distribution objects with import names.
|
|
40
|
+
* @param {string} proxyPath - The proxy path for the application.
|
|
41
|
+
* @param {string} [componentBasePath=''] - The base path for components.
|
|
42
|
+
* @param {string} [baseHost=''] - The base host URL.
|
|
43
|
+
* @returns {string} The formatted source code with updated import paths.
|
|
44
|
+
* @memberof clientFormatted
|
|
45
|
+
*/
|
|
20
46
|
const componentFormatted = (src, module, dists, proxyPath, componentBasePath = '', baseHost = '') => {
|
|
21
47
|
dists.map(
|
|
22
48
|
(dist) =>
|
|
@@ -40,6 +66,15 @@ const componentFormatted = (src, module, dists, proxyPath, componentBasePath = '
|
|
|
40
66
|
);
|
|
41
67
|
};
|
|
42
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Formats a view's source code by rewriting its import paths.
|
|
71
|
+
* @param {string} src - The source code of the view.
|
|
72
|
+
* @param {Array<object>} dists - An array of distribution objects with import names.
|
|
73
|
+
* @param {string} proxyPath - The proxy path for the application.
|
|
74
|
+
* @param {string} [baseHost=''] - The base host URL.
|
|
75
|
+
* @returns {string} The formatted source code with updated import paths.
|
|
76
|
+
* @memberof clientFormatted
|
|
77
|
+
*/
|
|
43
78
|
const viewFormatted = (src, dists, proxyPath, baseHost = '') => {
|
|
44
79
|
dists.map(
|
|
45
80
|
(dist) =>
|
|
@@ -49,11 +84,4 @@ const viewFormatted = (src, dists, proxyPath, baseHost = '') => {
|
|
|
49
84
|
return src.replaceAll(`from './`, componentFromFormatted).replaceAll(`from '../`, componentFromFormatted);
|
|
50
85
|
};
|
|
51
86
|
|
|
52
|
-
|
|
53
|
-
const context = { SrrComponent: () => {}, npm_package_version: Underpost.version };
|
|
54
|
-
vm.createContext(context);
|
|
55
|
-
vm.runInContext(await srcFormatted(fs.readFileSync(componentPath, 'utf8')), context);
|
|
56
|
-
return context.SrrComponent;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
export { srcFormatted, JSONweb, componentFormatted, viewFormatted, ssrFactory };
|
|
87
|
+
export { srcFormatted, JSONweb, componentFormatted, viewFormatted };
|
package/src/server/conf.js
CHANGED
|
@@ -77,6 +77,10 @@ const Config = {
|
|
|
77
77
|
'utf8',
|
|
78
78
|
);
|
|
79
79
|
shellExec(`node bin/deploy update-default-conf ${deployId}`);
|
|
80
|
+
|
|
81
|
+
if (!fs.existsSync(`./engine-private/deploy/dd.router`))
|
|
82
|
+
fs.writeFileSync(`./engine-private/deploy/dd.router`, '', 'utf8');
|
|
83
|
+
|
|
80
84
|
fs.writeFileSync(
|
|
81
85
|
`./engine-private/deploy/dd.router`,
|
|
82
86
|
fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8').trim() + `,${deployId}`,
|
|
@@ -692,7 +696,7 @@ const validateTemplatePath = (absolutePath = '') => {
|
|
|
692
696
|
const confServer = DefaultConf.server[host][path];
|
|
693
697
|
const confClient = DefaultConf.client[client];
|
|
694
698
|
const confSsr = DefaultConf.ssr[ssr];
|
|
695
|
-
const clients =
|
|
699
|
+
const clients = DefaultConf.client.default.services;
|
|
696
700
|
|
|
697
701
|
if (absolutePath.match('src/api') && !confServer.apis.find((p) => absolutePath.match(`src/api/${p}/`))) {
|
|
698
702
|
return false;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import dotenv from 'dotenv';
|
|
2
|
+
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import { PNG } from 'pngjs';
|
|
5
|
+
import sharp from 'sharp';
|
|
6
|
+
import Jimp from 'jimp';
|
|
7
|
+
|
|
8
|
+
import { range } from '../client/components/core/CommonJs.js';
|
|
9
|
+
import { random } from '../client/components/core/CommonJs.js';
|
|
10
|
+
|
|
11
|
+
dotenv.config({ path: `./engine-private/conf/dd-cyberia/.env.production`, override: true });
|
|
12
|
+
|
|
13
|
+
const pngDirectoryIteratorByObjectLayerType = async (
|
|
14
|
+
objectLayerType = 'skin',
|
|
15
|
+
callback = ({ path, objectLayerType, objectLayerId, direction, frame }) => {},
|
|
16
|
+
) => {
|
|
17
|
+
for (const objectLayerId of await fs.readdir(`./src/client/public/cyberia/assets/${objectLayerType}`)) {
|
|
18
|
+
for (const direction of await fs.readdir(
|
|
19
|
+
`./src/client/public/cyberia/assets/${objectLayerType}/${objectLayerId}`,
|
|
20
|
+
)) {
|
|
21
|
+
const dirFolder = `./src/client/public/cyberia/assets/${objectLayerType}/${objectLayerId}/${direction}`;
|
|
22
|
+
if (!fs.statSync(dirFolder).isDirectory()) continue;
|
|
23
|
+
for (const frame of await fs.readdir(dirFolder)) {
|
|
24
|
+
const imageFilePath = `./src/client/public/cyberia/assets/${objectLayerType}/${objectLayerId}/${direction}/${frame}`;
|
|
25
|
+
await callback({ path: imageFilePath, objectLayerType, objectLayerId, direction, frame });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const readPngAsync = (filePath) => {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
fs.createReadStream(filePath)
|
|
34
|
+
.pipe(new PNG())
|
|
35
|
+
.on('parsed', function () {
|
|
36
|
+
resolve({
|
|
37
|
+
width: this.width,
|
|
38
|
+
height: this.height,
|
|
39
|
+
data: Buffer.from(this.data),
|
|
40
|
+
});
|
|
41
|
+
})
|
|
42
|
+
.on('error', (err) => {
|
|
43
|
+
reject(err);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const frameFactory = async (path, colors = []) => {
|
|
49
|
+
const frame = [];
|
|
50
|
+
try {
|
|
51
|
+
let image;
|
|
52
|
+
|
|
53
|
+
if (path.endsWith('.gif')) {
|
|
54
|
+
image = await Jimp.read(path);
|
|
55
|
+
// remove gif file
|
|
56
|
+
fs.removeSync(path);
|
|
57
|
+
// save image replacing gif for png
|
|
58
|
+
const pngPath = path.replace('.gif', '.png');
|
|
59
|
+
await image.writeAsync(pngPath);
|
|
60
|
+
} else {
|
|
61
|
+
const png = await readPngAsync(path);
|
|
62
|
+
image = new Jimp(png.width, png.height);
|
|
63
|
+
image.bitmap = png;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const mazeFactor = parseInt(image.bitmap.height / 24);
|
|
67
|
+
let _y = -1;
|
|
68
|
+
for (const y of range(0, image.bitmap.height - 1)) {
|
|
69
|
+
if (y % mazeFactor === 0) {
|
|
70
|
+
_y++;
|
|
71
|
+
if (!frame[_y]) frame[_y] = [];
|
|
72
|
+
}
|
|
73
|
+
let _x = -1;
|
|
74
|
+
for (const x of range(0, image.bitmap.width - 1)) {
|
|
75
|
+
const rgba = Object.values(Jimp.intToRGBA(image.getPixelColor(x, y)));
|
|
76
|
+
if (y % mazeFactor === 0 && x % mazeFactor === 0) {
|
|
77
|
+
_x++;
|
|
78
|
+
const indexColor = colors.findIndex(
|
|
79
|
+
(c) => c[0] === rgba[0] && c[1] === rgba[1] && c[2] === rgba[2] && c[3] === rgba[3],
|
|
80
|
+
);
|
|
81
|
+
if (indexColor === -1) {
|
|
82
|
+
colors.push(rgba);
|
|
83
|
+
frame[_y][_x] = colors.length - 1;
|
|
84
|
+
} else {
|
|
85
|
+
frame[_y][_x] = indexColor;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} catch (error) {
|
|
91
|
+
logger.error(`Failed to process image ${path}:`, error);
|
|
92
|
+
}
|
|
93
|
+
return { frame, colors };
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const getKeyFramesDirectionsFromNumberFolderDirection = (direction) => {
|
|
97
|
+
let objectLayerFrameDirections = [];
|
|
98
|
+
|
|
99
|
+
switch (direction) {
|
|
100
|
+
case '08':
|
|
101
|
+
objectLayerFrameDirections = ['down_idle', 'none_idle', 'default_idle'];
|
|
102
|
+
break;
|
|
103
|
+
case '18':
|
|
104
|
+
objectLayerFrameDirections = ['down_walking'];
|
|
105
|
+
break;
|
|
106
|
+
case '02':
|
|
107
|
+
objectLayerFrameDirections = ['up_idle'];
|
|
108
|
+
break;
|
|
109
|
+
case '12':
|
|
110
|
+
objectLayerFrameDirections = ['up_walking'];
|
|
111
|
+
break;
|
|
112
|
+
case '04':
|
|
113
|
+
objectLayerFrameDirections = ['left_idle', 'up_left_idle', 'down_left_idle'];
|
|
114
|
+
break;
|
|
115
|
+
case '14':
|
|
116
|
+
objectLayerFrameDirections = ['left_walking', 'up_left_walking', 'down_left_walking'];
|
|
117
|
+
break;
|
|
118
|
+
case '06':
|
|
119
|
+
objectLayerFrameDirections = ['right_idle', 'up_right_idle', 'down_right_idle'];
|
|
120
|
+
break;
|
|
121
|
+
case '16':
|
|
122
|
+
objectLayerFrameDirections = ['right_walking', 'up_right_walking', 'down_right_walking'];
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return objectLayerFrameDirections;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const buildImgFromTile = async (
|
|
130
|
+
options = {
|
|
131
|
+
tile: { map_color: null, frame_matrix: null },
|
|
132
|
+
imagePath: '',
|
|
133
|
+
cellPixelDim: 20,
|
|
134
|
+
opacityFilter: (x, y, color) => 255,
|
|
135
|
+
},
|
|
136
|
+
) => {
|
|
137
|
+
const { tile, imagePath, cellPixelDim, opacityFilter } = options;
|
|
138
|
+
const mainMatrix = tile.frame_matrix;
|
|
139
|
+
const sharpOptions = {
|
|
140
|
+
create: {
|
|
141
|
+
width: cellPixelDim * mainMatrix[0].length,
|
|
142
|
+
height: cellPixelDim * mainMatrix.length,
|
|
143
|
+
channels: 4,
|
|
144
|
+
background: { r: 0, g: 0, b: 0, alpha: 0 }, // transparent background
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
let image = await sharp(sharpOptions).png().toBuffer();
|
|
149
|
+
fs.writeFileSync(imagePath, image);
|
|
150
|
+
image = await Jimp.read(imagePath);
|
|
151
|
+
|
|
152
|
+
for (let y = 0; y < mainMatrix.length; y++) {
|
|
153
|
+
for (let x = 0; x < mainMatrix[y].length; x++) {
|
|
154
|
+
const colorIndex = mainMatrix[y][x];
|
|
155
|
+
if (colorIndex === null || colorIndex === undefined) continue;
|
|
156
|
+
|
|
157
|
+
const color = tile.map_color[colorIndex];
|
|
158
|
+
if (!color) continue;
|
|
159
|
+
|
|
160
|
+
const rgbaColor = color.length === 4 ? color : [...color, 255]; // Ensure alpha channel
|
|
161
|
+
|
|
162
|
+
for (let dy = 0; dy < cellPixelDim; dy++) {
|
|
163
|
+
for (let dx = 0; dx < cellPixelDim; dx++) {
|
|
164
|
+
const pixelX = x * cellPixelDim + dx;
|
|
165
|
+
const pixelY = y * cellPixelDim + dy;
|
|
166
|
+
image.setPixelColor(Jimp.rgbaToInt(...rgbaColor), pixelX, pixelY);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
await image.writeAsync(imagePath);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const generateRandomStats = () => {
|
|
176
|
+
return {
|
|
177
|
+
effect: random(0, 10),
|
|
178
|
+
resistance: random(0, 10),
|
|
179
|
+
agility: random(0, 10),
|
|
180
|
+
range: random(0, 10),
|
|
181
|
+
intelligence: random(0, 10),
|
|
182
|
+
utility: random(0, 10),
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const zIndexPriority = { floor: 0, skin: 1, weapon: 2, skill: 3, coin: 4 };
|
|
187
|
+
|
|
188
|
+
export {
|
|
189
|
+
pngDirectoryIteratorByObjectLayerType,
|
|
190
|
+
readPngAsync,
|
|
191
|
+
frameFactory,
|
|
192
|
+
getKeyFramesDirectionsFromNumberFolderDirection,
|
|
193
|
+
buildImgFromTile,
|
|
194
|
+
generateRandomStats,
|
|
195
|
+
zIndexPriority,
|
|
196
|
+
};
|
package/src/server/runtime.js
CHANGED
|
@@ -129,17 +129,6 @@ const buildRuntime = async () => {
|
|
|
129
129
|
return compression.filter(req, res);
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
if (process.argv.includes('static')) {
|
|
133
|
-
logger.info('Build static server runtime', `${host}${path}`);
|
|
134
|
-
currentPort += 2;
|
|
135
|
-
const staticPort = newInstance(currentPort);
|
|
136
|
-
|
|
137
|
-
await UnderpostStartUp.API.listenPortController(app, staticPort, runningData);
|
|
138
|
-
currentPort++;
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
logger.info('Build api server runtime', `${host}${path}`);
|
|
142
|
-
|
|
143
132
|
// parse requests of content-type - application/json
|
|
144
133
|
app.use(express.json({ limit: '100MB' }));
|
|
145
134
|
|
|
@@ -163,6 +152,14 @@ const buildRuntime = async () => {
|
|
|
163
152
|
|
|
164
153
|
// instance public static
|
|
165
154
|
app.use('/', express.static(directory ? directory : `.${rootHostPath}`));
|
|
155
|
+
if (process.argv.includes('static')) {
|
|
156
|
+
logger.info('Build static server runtime', `${host}${path}`);
|
|
157
|
+
currentPort += 2;
|
|
158
|
+
const staticPort = newInstance(currentPort);
|
|
159
|
+
await UnderpostStartUp.API.listenPortController(app, staticPort, runningData);
|
|
160
|
+
currentPort++;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
166
163
|
|
|
167
164
|
// security
|
|
168
165
|
applySecurity(app, {
|
|
@@ -228,12 +225,12 @@ const buildRuntime = async () => {
|
|
|
228
225
|
templates: mailerSsrConf ? mailerSsrConf.mailer : {},
|
|
229
226
|
});
|
|
230
227
|
}
|
|
231
|
-
if (apis) {
|
|
228
|
+
if (apis && apis.length > 0) {
|
|
232
229
|
const authMiddleware = authMiddlewareFactory({ host, path });
|
|
233
|
-
|
|
234
230
|
const apiPath = `${path === '/' ? '' : path}/${process.env.BASE_API}`;
|
|
235
231
|
for (const api of apis)
|
|
236
232
|
await (async () => {
|
|
233
|
+
logger.info(`Build api server`, `${host}${apiPath}/${api}`);
|
|
237
234
|
const { ApiRouter } = await import(`../api/${api}/${api}.router.js`);
|
|
238
235
|
const router = ApiRouter({ host, path, apiPath, mailer, db, authMiddleware, origins });
|
|
239
236
|
// router.use(cors({ origin: origins }));
|
package/src/server/ssr.js
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module for managing server side rendering
|
|
3
|
+
* @module src/server/ssr.js
|
|
4
|
+
* @namespace SSR
|
|
5
|
+
*/
|
|
6
|
+
|
|
1
7
|
import fs from 'fs-extra';
|
|
2
8
|
import dotenv from 'dotenv';
|
|
9
|
+
import vm from 'node:vm';
|
|
10
|
+
|
|
3
11
|
import Underpost from '../index.js';
|
|
4
12
|
|
|
5
|
-
import {
|
|
13
|
+
import { srcFormatted, JSONweb } from './client-formatted.js';
|
|
6
14
|
import { loggerFactory } from './logger.js';
|
|
7
15
|
import { getRootDirectory } from './process.js';
|
|
8
16
|
|
|
@@ -10,6 +18,48 @@ dotenv.config();
|
|
|
10
18
|
|
|
11
19
|
const logger = loggerFactory(import.meta);
|
|
12
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Creates a server-side rendering component function from a given file path.
|
|
23
|
+
* It reads the component file, formats it, and executes it in a sandboxed Node.js VM context to extract the component.
|
|
24
|
+
* @param {string} [componentPath='./src/client/ssr/Render.js'] - The path to the SSR component file.
|
|
25
|
+
* @returns {Promise<Function>} A promise that resolves to the SSR component function.
|
|
26
|
+
* @memberof SSR
|
|
27
|
+
*/
|
|
28
|
+
const ssrFactory = async (componentPath = `./src/client/ssr/Render.js`) => {
|
|
29
|
+
const context = { SrrComponent: () => {}, npm_package_version: Underpost.version };
|
|
30
|
+
vm.createContext(context);
|
|
31
|
+
vm.runInContext(await srcFormatted(fs.readFileSync(componentPath, 'utf8')), context);
|
|
32
|
+
return context.SrrComponent;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Sanitizes an HTML string by adding a nonce to all script and style tags for Content Security Policy (CSP).
|
|
37
|
+
* The nonce is retrieved from `res.locals.nonce`.
|
|
38
|
+
* @param {object} res - The Express response object.
|
|
39
|
+
* @param {object} req - The Express request object.
|
|
40
|
+
* @param {string} html - The HTML string to sanitize.
|
|
41
|
+
* @returns {string} The sanitized HTML string with nonces.
|
|
42
|
+
* @memberof SSR
|
|
43
|
+
*/
|
|
44
|
+
const sanitizeHtml = (res, req, html) => {
|
|
45
|
+
const nonce = res.locals.nonce;
|
|
46
|
+
|
|
47
|
+
return html
|
|
48
|
+
.replace(/<script(?=\s|>)/gi, `<script nonce="${nonce}"`)
|
|
49
|
+
.replace(/<style(?=\s|>)/gi, `<style nonce="${nonce}"`);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Factory function to create Express middleware for handling 404 and 500 errors.
|
|
54
|
+
* It generates server-side rendered HTML for these error pages. If static error pages exist, it redirects to them.
|
|
55
|
+
* @param {object} options - The options for creating the middleware.
|
|
56
|
+
* @param {object} options.app - The Express app instance.
|
|
57
|
+
* @param {string} options.directory - The directory for the instance's static files.
|
|
58
|
+
* @param {string} options.rootHostPath - The root path for the host's public files.
|
|
59
|
+
* @param {string} options.path - The base path for the instance.
|
|
60
|
+
* @returns {Promise<{error500: Function, error400: Function}>} A promise that resolves to an object containing the 500 and 404 error handling middleware.
|
|
61
|
+
* @memberof SSR
|
|
62
|
+
*/
|
|
13
63
|
const ssrMiddlewareFactory = async ({ app, directory, rootHostPath, path }) => {
|
|
14
64
|
const Render = await ssrFactory();
|
|
15
65
|
const ssrPath = path === '/' ? path : `${path}/`;
|
|
@@ -48,14 +98,6 @@ const ssrMiddlewareFactory = async ({ app, directory, rootHostPath, path }) => {
|
|
|
48
98
|
const path500 = `${directory ? directory : `${getRootDirectory()}${rootHostPath}`}/500/index.html`;
|
|
49
99
|
const page500 = fs.existsSync(path500) ? `${path === '/' ? '' : path}/500` : undefined;
|
|
50
100
|
|
|
51
|
-
const sanitizeHtml = (res, req, html) => {
|
|
52
|
-
const nonce = res.locals.nonce;
|
|
53
|
-
|
|
54
|
-
return html
|
|
55
|
-
.replace(/<script(?=\s|>)/gi, `<script nonce="${nonce}"`)
|
|
56
|
-
.replace(/<style(?=\s|>)/gi, `<style nonce="${nonce}"`);
|
|
57
|
-
};
|
|
58
|
-
|
|
59
101
|
return {
|
|
60
102
|
error500: function (err, req, res, next) {
|
|
61
103
|
logger.error(err, err.stack);
|
|
@@ -82,4 +124,4 @@ const ssrMiddlewareFactory = async ({ app, directory, rootHostPath, path }) => {
|
|
|
82
124
|
};
|
|
83
125
|
};
|
|
84
126
|
|
|
85
|
-
export { ssrMiddlewareFactory };
|
|
127
|
+
export { ssrMiddlewareFactory, ssrFactory, sanitizeHtml };
|
package/src/server/valkey.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module for managing Valkey
|
|
3
|
+
* @module src/server/valkey.js
|
|
4
|
+
* @namespace Valkey
|
|
5
|
+
*/
|
|
6
|
+
|
|
1
7
|
import Valkey from 'iovalkey';
|
|
2
8
|
import mongoose from 'mongoose';
|
|
3
9
|
import { hashPassword } from './auth.js';
|
|
@@ -10,11 +16,35 @@ const ValkeyInstances = {};
|
|
|
10
16
|
const DummyStores = {}; // in-memory Maps per instance
|
|
11
17
|
const ValkeyStatus = {}; // 'connected' | 'dummy' | 'error' | undefined
|
|
12
18
|
|
|
13
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Checks if any Valkey instance is connected.
|
|
21
|
+
* This is a backward-compatible overall flag.
|
|
22
|
+
* @returns {boolean} True if any instance has a 'connected' status.
|
|
23
|
+
* @memberof Valkey
|
|
24
|
+
*/
|
|
14
25
|
const isValkeyEnable = () => Object.values(ValkeyStatus).some((s) => s === 'connected');
|
|
15
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Generates a unique key for a Valkey instance based on its host and path.
|
|
29
|
+
* @param {object} [opts={ host: '', path: '' }] - The instance options.
|
|
30
|
+
* @param {string} [opts.host=''] - The host of the instance.
|
|
31
|
+
* @param {string} [opts.path=''] - The path of the instance.
|
|
32
|
+
* @returns {string} The instance key.
|
|
33
|
+
* @private
|
|
34
|
+
* @memberof Valkey
|
|
35
|
+
*/
|
|
16
36
|
const _instanceKey = (opts = { host: '', path: '' }) => `${opts.host || ''}${opts.path || ''}`;
|
|
17
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Creates and manages a connection to a Valkey server for a given instance.
|
|
40
|
+
* It sets up a client, attaches event listeners for connection status, and implements a fallback to an in-memory dummy store if the connection fails.
|
|
41
|
+
* @param {object} [instance={ host: '', path: '' }] - The instance identifier.
|
|
42
|
+
* @param {string} [instance.host=''] - The host of the instance.
|
|
43
|
+
* @param {string} [instance.path=''] - The path of the instance.
|
|
44
|
+
* @param {object} [valkeyServerConnectionOptions={ host: '', path: '' }] - Connection options for the iovalkey client.
|
|
45
|
+
* @returns {Promise<Valkey|undefined>} A promise that resolves to the Valkey client instance, or undefined if creation fails.
|
|
46
|
+
* @memberof Valkey
|
|
47
|
+
*/
|
|
18
48
|
const createValkeyConnection = async (
|
|
19
49
|
instance = { host: '', path: '' },
|
|
20
50
|
valkeyServerConnectionOptions = { host: '', path: '' },
|
|
@@ -72,6 +102,14 @@ const createValkeyConnection = async (
|
|
|
72
102
|
return ValkeyInstances[key];
|
|
73
103
|
};
|
|
74
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Factory function to create a Data Transfer Object (DTO) from a payload.
|
|
107
|
+
* It filters the payload to include only the keys specified in the `select` object.
|
|
108
|
+
* @param {object} payload - The source object.
|
|
109
|
+
* @param {object} select - An object where keys are field names and values are 1 to include them.
|
|
110
|
+
* @returns {object} A new object containing only the selected fields from the payload.
|
|
111
|
+
* @memberof Valkey
|
|
112
|
+
*/
|
|
75
113
|
const selectDtoFactory = (payload, select) => {
|
|
76
114
|
const result = {};
|
|
77
115
|
for (const key of Object.keys(select)) {
|
|
@@ -80,6 +118,12 @@ const selectDtoFactory = (payload, select) => {
|
|
|
80
118
|
return result;
|
|
81
119
|
};
|
|
82
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Factory function to create a new Valkey client instance.
|
|
123
|
+
* @param {object} options - Connection options for the iovalkey client.
|
|
124
|
+
* @returns {Promise<Valkey>} A promise that resolves to a new Valkey client.
|
|
125
|
+
* @memberof Valkey
|
|
126
|
+
*/
|
|
83
127
|
const valkeyClientFactory = async (options) => {
|
|
84
128
|
const valkey = new Valkey({
|
|
85
129
|
port: options?.port ? options.port : undefined,
|
|
@@ -103,6 +147,15 @@ const valkeyClientFactory = async (options) => {
|
|
|
103
147
|
return valkey;
|
|
104
148
|
};
|
|
105
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Retrieves an object from Valkey by key for a specific instance.
|
|
152
|
+
* If the Valkey client is not connected or an error occurs, it falls back to the dummy in-memory store.
|
|
153
|
+
* It automatically parses JSON strings.
|
|
154
|
+
* @param {object} [options={ host: '', path: '' }] - The instance identifier.
|
|
155
|
+
* @param {string} [key=''] - The key of the object to retrieve.
|
|
156
|
+
* @returns {Promise<object|string|null>} A promise that resolves to the retrieved object, string, or null if not found.
|
|
157
|
+
* @memberof Valkey
|
|
158
|
+
*/
|
|
106
159
|
const getValkeyObject = async (options = { host: '', path: '' }, key = '') => {
|
|
107
160
|
const k = _instanceKey(options);
|
|
108
161
|
const status = ValkeyStatus[k];
|
|
@@ -124,6 +177,16 @@ const getValkeyObject = async (options = { host: '', path: '' }, key = '') => {
|
|
|
124
177
|
return DummyStores[k]?.get(key) ?? null;
|
|
125
178
|
};
|
|
126
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Sets an object or string in Valkey for a specific instance.
|
|
182
|
+
* If the Valkey client is not connected, it writes to the in-memory dummy store instead.
|
|
183
|
+
* Objects are automatically stringified.
|
|
184
|
+
* @param {object} [options={ host: '', path: '' }] - The instance identifier.
|
|
185
|
+
* @param {string} [key=''] - The key under which to store the payload.
|
|
186
|
+
* @param {object|string} [payload={}] - The data to store.
|
|
187
|
+
* @returns {Promise<string>} A promise that resolves to 'OK' on success.
|
|
188
|
+
* @memberof Valkey
|
|
189
|
+
*/
|
|
127
190
|
const setValkeyObject = async (options = { host: '', path: '' }, key = '', payload = {}) => {
|
|
128
191
|
const k = _instanceKey(options);
|
|
129
192
|
const isString = typeof payload === 'string';
|
|
@@ -141,6 +204,16 @@ const setValkeyObject = async (options = { host: '', path: '' }, key = '', paylo
|
|
|
141
204
|
return 'OK';
|
|
142
205
|
};
|
|
143
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Updates an existing object in Valkey by merging it with a new payload.
|
|
209
|
+
* It retrieves the current object, merges it with the new payload, and sets the updated object back.
|
|
210
|
+
* It also updates the `updatedAt` timestamp.
|
|
211
|
+
* @param {object} [options={ host: '', path: '' }] - The instance identifier.
|
|
212
|
+
* @param {string} [key=''] - The key of the object to update.
|
|
213
|
+
* @param {object} [payload={}] - The new data to merge into the object.
|
|
214
|
+
* @returns {Promise<string>} A promise that resolves to the result of the set operation.
|
|
215
|
+
* @memberof Valkey
|
|
216
|
+
*/
|
|
144
217
|
const updateValkeyObject = async (options = { host: '', path: '' }, key = '', payload = {}) => {
|
|
145
218
|
let base = await getValkeyObject(options, key);
|
|
146
219
|
if (typeof base !== 'object' || base === null) base = {};
|
|
@@ -148,6 +221,17 @@ const updateValkeyObject = async (options = { host: '', path: '' }, key = '', pa
|
|
|
148
221
|
return await setValkeyObject(options, key, { ...base, ...payload });
|
|
149
222
|
};
|
|
150
223
|
|
|
224
|
+
/**
|
|
225
|
+
* Factory function to create a new object based on a model schema.
|
|
226
|
+
* It generates a new object with default properties like `_id`, `createdAt`, and `updatedAt`,
|
|
227
|
+
* and model-specific properties.
|
|
228
|
+
* @param {object} [options={ host: 'localhost', path: '', object: {} }] - Options for object creation.
|
|
229
|
+
* @param {string} [options.host='localhost'] - The host context for the object.
|
|
230
|
+
* @param {object} [options.object={}] - An initial object to extend.
|
|
231
|
+
* @param {string} [model=''] - The name of the model schema to use (e.g., 'user').
|
|
232
|
+
* @returns {Promise<object>} A promise that resolves to the newly created object.
|
|
233
|
+
* @memberof Valkey
|
|
234
|
+
*/
|
|
151
235
|
const valkeyObjectFactory = async (options = { host: 'localhost', path: '', object: {} }, model = '') => {
|
|
152
236
|
const idoDate = new Date().toISOString();
|
|
153
237
|
options.object = options.object || {};
|
|
@@ -181,6 +265,10 @@ const valkeyObjectFactory = async (options = { host: 'localhost', path: '', obje
|
|
|
181
265
|
}
|
|
182
266
|
};
|
|
183
267
|
|
|
268
|
+
/**
|
|
269
|
+
* A collection of Valkey-related API functions.
|
|
270
|
+
* @type {object}
|
|
271
|
+
*/
|
|
184
272
|
const ValkeyAPI = {
|
|
185
273
|
valkeyClientFactory,
|
|
186
274
|
selectDtoFactory,
|