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.
Files changed (49) hide show
  1. package/.env.development +35 -3
  2. package/.env.production +40 -3
  3. package/.env.test +35 -3
  4. package/.github/workflows/release.cd.yml +2 -1
  5. package/README.md +44 -36
  6. package/bin/deploy.js +40 -0
  7. package/cli.md +88 -86
  8. package/conf.js +28 -3
  9. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  10. package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
  11. package/package.json +1 -2
  12. package/src/api/document/document.controller.js +66 -0
  13. package/src/api/document/document.model.js +51 -0
  14. package/src/api/document/document.router.js +24 -0
  15. package/src/api/document/document.service.js +125 -0
  16. package/src/cli/deploy.js +1 -1
  17. package/src/cli/index.js +2 -0
  18. package/src/cli/run.js +27 -1
  19. package/src/client/Default.index.js +46 -1
  20. package/src/client/components/core/Account.js +8 -1
  21. package/src/client/components/core/AgGrid.js +18 -9
  22. package/src/client/components/core/BtnIcon.js +3 -2
  23. package/src/client/components/core/Content.js +2 -1
  24. package/src/client/components/core/CssCore.js +4 -0
  25. package/src/client/components/core/Docs.js +0 -3
  26. package/src/client/components/core/Input.js +34 -19
  27. package/src/client/components/core/Modal.js +29 -7
  28. package/src/client/components/core/ObjectLayerEngine.js +370 -0
  29. package/src/client/components/core/ObjectLayerEngineModal.js +1 -0
  30. package/src/client/components/core/Panel.js +7 -2
  31. package/src/client/components/core/PanelForm.js +187 -63
  32. package/src/client/components/core/VanillaJs.js +3 -0
  33. package/src/client/components/default/MenuDefault.js +94 -41
  34. package/src/client/components/default/RoutesDefault.js +2 -0
  35. package/src/client/services/default/default.management.js +1 -0
  36. package/src/client/services/document/document.service.js +97 -0
  37. package/src/client/services/file/file.service.js +2 -0
  38. package/src/client/ssr/Render.js +1 -1
  39. package/src/client/ssr/head/DefaultScripts.js +2 -0
  40. package/src/client/ssr/head/Seo.js +1 -0
  41. package/src/index.js +1 -1
  42. package/src/mailer/EmailRender.js +1 -1
  43. package/src/server/client-build.js +2 -3
  44. package/src/server/client-formatted.js +40 -12
  45. package/src/server/conf.js +5 -1
  46. package/src/server/object-layer.js +196 -0
  47. package/src/server/runtime.js +10 -13
  48. package/src/server/ssr.js +52 -10
  49. package/src/server/valkey.js +89 -1
@@ -1,4 +1,4 @@
1
- import { ssrFactory } from '../server/client-formatted.js';
1
+ import { ssrFactory } from '../server/ssr.js';
2
2
 
3
3
  const EmailRender = {
4
4
  style: {
@@ -1,14 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  import fs from 'fs-extra';
4
- import { srcFormatted, componentFormatted, viewFormatted, ssrFactory, JSONweb } from './client-formatted.js';
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
- 'use strict';
1
+ /**
2
+ * Module for formatting client-side code
3
+ * @module src/server/client-formatted.js
4
+ * @namespace clientFormatted
5
+ */
2
6
 
3
- import fs from 'fs-extra';
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
- const ssrFactory = async (componentPath = `./src/client/ssr/Render.js`) => {
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 };
@@ -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 = Object.keys(confClient).concat(['core', 'test', 'default', 'user']);
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
+ };
@@ -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 { ssrFactory, JSONweb } from './client-formatted.js';
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 };
@@ -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
- // Backward-compatible overall flag: true if any instance is connected
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,