underpost 3.2.5 → 3.2.8

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 (138) hide show
  1. package/.github/workflows/release.cd.yml +1 -2
  2. package/CHANGELOG.md +251 -1
  3. package/CLI-HELP.md +26 -13
  4. package/Dockerfile +0 -4
  5. package/README.md +3 -3
  6. package/bin/build.js +13 -3
  7. package/bin/deploy.js +570 -1
  8. package/bin/file.js +5 -0
  9. package/conf.js +11 -2
  10. package/jsconfig.json +1 -1
  11. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
  12. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
  13. package/manifests/deployment/dd-default-development/deployment.yaml +2 -6
  14. package/manifests/deployment/dd-test-development/deployment.yaml +136 -66
  15. package/manifests/deployment/dd-test-development/proxy.yaml +41 -5
  16. package/package.json +20 -11
  17. package/src/api/core/core.controller.js +10 -10
  18. package/src/api/core/core.service.js +10 -10
  19. package/src/api/default/default.controller.js +10 -10
  20. package/src/api/default/default.service.js +10 -10
  21. package/src/api/document/document.controller.js +12 -12
  22. package/src/api/document/document.model.js +10 -16
  23. package/src/api/file/file.controller.js +8 -8
  24. package/src/api/file/file.model.js +10 -10
  25. package/src/api/file/file.service.js +36 -36
  26. package/src/api/test/test.controller.js +8 -8
  27. package/src/api/test/test.service.js +8 -8
  28. package/src/api/user/guest.service.js +99 -0
  29. package/src/api/user/user.controller.js +6 -6
  30. package/src/api/user/user.model.js +8 -13
  31. package/src/api/user/user.service.js +3 -20
  32. package/src/cli/deploy.js +33 -30
  33. package/src/cli/fs.js +62 -5
  34. package/src/cli/image.js +43 -1
  35. package/src/cli/index.js +5 -1
  36. package/src/cli/release.js +57 -1
  37. package/src/cli/repository.js +35 -3
  38. package/src/cli/run.js +300 -35
  39. package/src/cli/ssh.js +1 -1
  40. package/src/cli/static.js +43 -115
  41. package/src/client/Default.index.js +21 -33
  42. package/src/client/components/core/404.js +4 -4
  43. package/src/client/components/core/500.js +4 -4
  44. package/src/client/components/core/Account.js +73 -60
  45. package/src/client/components/core/AgGrid.js +23 -33
  46. package/src/client/components/core/Alert.js +12 -13
  47. package/src/client/components/core/AppStore.js +1 -1
  48. package/src/client/components/core/Auth.js +20 -32
  49. package/src/client/components/core/Badge.js +7 -13
  50. package/src/client/components/core/BtnIcon.js +15 -17
  51. package/src/client/components/core/CalendarCore.js +42 -63
  52. package/src/client/components/core/Chat.js +13 -15
  53. package/src/client/components/core/ClientEvents.js +87 -0
  54. package/src/client/components/core/ColorPaletteElement.js +309 -0
  55. package/src/client/components/core/Content.js +17 -14
  56. package/src/client/components/core/Css.js +15 -71
  57. package/src/client/components/core/CssCore.js +12 -16
  58. package/src/client/components/core/D3Chart.js +4 -4
  59. package/src/client/components/core/Docs.js +60 -59
  60. package/src/client/components/core/DropDown.js +69 -91
  61. package/src/client/components/core/EventBus.js +92 -0
  62. package/src/client/components/core/EventsUI.js +14 -17
  63. package/src/client/components/core/FileExplorer.js +102 -234
  64. package/src/client/components/core/FullScreen.js +47 -75
  65. package/src/client/components/core/Input.js +24 -69
  66. package/src/client/components/core/Keyboard.js +25 -18
  67. package/src/client/components/core/KeyboardAvoidance.js +145 -0
  68. package/src/client/components/core/LoadingAnimation.js +25 -31
  69. package/src/client/components/core/LogIn.js +41 -41
  70. package/src/client/components/core/LogOut.js +23 -14
  71. package/src/client/components/core/Modal.js +397 -176
  72. package/src/client/components/core/NotificationManager.js +14 -18
  73. package/src/client/components/core/Panel.js +54 -50
  74. package/src/client/components/core/PanelForm.js +25 -125
  75. package/src/client/components/core/Polyhedron.js +110 -214
  76. package/src/client/components/core/PublicProfile.js +39 -32
  77. package/src/client/components/core/Recover.js +52 -48
  78. package/src/client/components/core/Responsive.js +88 -32
  79. package/src/client/components/core/RichText.js +9 -18
  80. package/src/client/components/core/Router.js +24 -3
  81. package/src/client/components/core/SearchBox.js +37 -37
  82. package/src/client/components/core/SignUp.js +39 -30
  83. package/src/client/components/core/SocketIo.js +31 -2
  84. package/src/client/components/core/SocketIoHandler.js +6 -6
  85. package/src/client/components/core/ToggleSwitch.js +8 -20
  86. package/src/client/components/core/ToolTip.js +5 -17
  87. package/src/client/components/core/Translate.js +56 -59
  88. package/src/client/components/core/Validator.js +26 -16
  89. package/src/client/components/core/Wallet.js +15 -26
  90. package/src/client/components/core/Worker.js +140 -25
  91. package/src/client/components/core/windowGetDimensions.js +7 -7
  92. package/src/client/components/default/{MenuDefault.js → AppShellDefault.js} +87 -87
  93. package/src/client/components/default/CssDefault.js +12 -12
  94. package/src/client/components/default/LogInDefault.js +6 -4
  95. package/src/client/components/default/LogOutDefault.js +6 -4
  96. package/src/client/components/default/RouterDefault.js +47 -0
  97. package/src/client/components/default/SettingsDefault.js +4 -4
  98. package/src/client/components/default/SignUpDefault.js +6 -4
  99. package/src/client/components/default/TranslateDefault.js +3 -3
  100. package/src/client/services/core/core.service.js +17 -49
  101. package/src/client/services/default/default.management.js +139 -242
  102. package/src/client/services/default/default.service.js +10 -16
  103. package/src/client/services/document/document.service.js +14 -19
  104. package/src/client/services/file/file.service.js +8 -13
  105. package/src/client/services/test/test.service.js +8 -13
  106. package/src/client/services/user/guest.service.js +79 -0
  107. package/src/client/services/user/user.management.js +5 -5
  108. package/src/client/services/user/user.service.js +14 -20
  109. package/src/client/ssr/body/404.js +3 -3
  110. package/src/client/ssr/body/500.js +3 -3
  111. package/src/client/ssr/body/CacheControl.js +5 -2
  112. package/src/client/ssr/body/DefaultSplashScreen.js +19 -12
  113. package/src/client/ssr/mailer/DefaultRecoverEmail.js +19 -20
  114. package/src/client/ssr/mailer/DefaultVerifyEmail.js +15 -16
  115. package/src/client/ssr/offline/Maintenance.js +12 -11
  116. package/src/client/ssr/offline/NoNetworkConnection.js +3 -3
  117. package/src/client/ssr/pages/Test.js +2 -2
  118. package/src/client/sw/core.sw.js +212 -0
  119. package/src/index.js +1 -1
  120. package/src/runtime/express/Dockerfile +4 -4
  121. package/src/runtime/lampp/Dockerfile +8 -7
  122. package/src/runtime/wp/Dockerfile +11 -17
  123. package/src/server/client-build-docs.js +45 -46
  124. package/src/server/client-build.js +334 -60
  125. package/src/server/client-formatted.js +47 -16
  126. package/src/server/conf.js +5 -4
  127. package/src/server/ipfs-client.js +232 -91
  128. package/src/server/process.js +13 -27
  129. package/src/server/start.js +6 -3
  130. package/src/server/valkey.js +134 -235
  131. package/tsconfig.docs.json +15 -0
  132. package/typedoc.json +20 -0
  133. package/jsdoc.json +0 -52
  134. package/src/client/components/core/ColorPalette.js +0 -5267
  135. package/src/client/components/core/JoyStick.js +0 -80
  136. package/src/client/components/default/RoutesDefault.js +0 -49
  137. package/src/client/sw/default.sw.js +0 -127
  138. package/src/client/sw/template.sw.js +0 -84
@@ -4,35 +4,30 @@
4
4
  * @module src/server/process.js
5
5
  * @namespace Process
6
6
  */
7
-
8
7
  // https://nodejs.org/api/process
9
-
10
8
  import shell from 'shelljs';
11
9
  import { loggerFactory } from './logger.js';
12
10
  import clipboard from 'clipboardy';
13
11
  import Underpost from '../index.js';
14
12
  import { getNpmRootPath } from './conf.js';
15
-
16
13
  const logger = loggerFactory(import.meta);
17
-
18
14
  /**
19
15
  * Gets the current working directory, replacing backslashes with forward slashes for consistency.
20
16
  * @memberof Process
21
17
  * @returns {string} The root directory path.
22
18
  */
23
19
  const getRootDirectory = () => process.cwd().replace(/\\/g, '/');
24
-
25
20
  /**
26
21
  * Controls and manages process-level events and signals.
27
22
  * @namespace ProcessController
28
23
  */
29
- const ProcessController = {
24
+ class ProcessController {
30
25
  /**
31
26
  * List of signals to listen for for graceful shutdown/handling.
32
27
  * @memberof ProcessController
33
28
  * @type {string[]}
34
29
  */
35
- SIG: [
30
+ static SIG = [
36
31
  'SIGPIPE',
37
32
  'SIGHUP',
38
33
  'SIGTERM',
@@ -45,29 +40,26 @@ const ProcessController = {
45
40
  'SIGFPE',
46
41
  'SIGSEGV',
47
42
  'SIGILL',
48
- ],
49
-
43
+ ];
50
44
  /**
51
45
  * Sets up listeners for various process signals defined in {@link ProcessController.SIG}.
52
46
  * Handles graceful exit on 'SIGINT' (Ctrl+C).
53
47
  * @memberof ProcessController
54
48
  * @returns {Array<process.Process>} An array of process listener handles.
55
49
  */
56
- onSigListen: function () {
57
- return this.SIG.map((sig) =>
50
+ static onSigListen() {
51
+ return ProcessController.SIG.map((sig) =>
58
52
  process.on(sig, (...args) => {
59
- this.logger.info(`process on ${sig}`, args);
53
+ ProcessController.logger.info(`process on ${sig}`, args);
60
54
  switch (sig) {
61
55
  case 'SIGINT':
62
56
  return process.exit();
63
-
64
57
  default:
65
58
  break;
66
59
  }
67
60
  }),
68
61
  );
69
- },
70
-
62
+ }
71
63
  /**
72
64
  * Initializes the ProcessController.
73
65
  * Sets up signal listeners, registers a listener for the 'exit' event, and cleans up temporary deployment environment variables.
@@ -75,16 +67,15 @@ const ProcessController = {
75
67
  * @param {Object} logger - The logger instance to use for internal logging.
76
68
  * @returns {void}
77
69
  */
78
- init: function (logger) {
79
- this.logger = logger;
70
+ static init(logger) {
71
+ ProcessController.logger = logger;
80
72
  process.on('exit', (...args) => {
81
- this.logger.info(`process on exit`, args);
73
+ ProcessController.logger.info(`process on exit`, args);
82
74
  });
83
- this.onSigListen();
75
+ ProcessController.onSigListen();
84
76
  Underpost.env.delete('await-deploy');
85
- },
86
- };
87
-
77
+ }
78
+ }
88
79
  /**
89
80
  * Executes a shell command using shelljs.
90
81
  * @memberof Process
@@ -105,7 +96,6 @@ const shellExec = (
105
96
  if (options.callback) return shell.exec(cmd, options, options.callback);
106
97
  return options.stdout ? shell.exec(cmd, options).stdout : shell.exec(cmd, options);
107
98
  };
108
-
109
99
  /**
110
100
  * Changes the current working directory using shelljs.
111
101
  * @memberof Process
@@ -118,7 +108,6 @@ const shellCd = (cd, options = { disableLog: false }) => {
118
108
  if (!options.disableLog) logger.info(`cd`, cd);
119
109
  return shell.cd(cd);
120
110
  };
121
-
122
111
  /**
123
112
  * Wraps a command to run it as a daemon process in a shell (keeping the process alive/terminal open).
124
113
  * @memberof Process
@@ -126,7 +115,6 @@ const shellCd = (cd, options = { disableLog: false }) => {
126
115
  * @returns {string} The shell command string for the daemon process.
127
116
  */
128
117
  const daemonProcess = (cmd) => `exec bash -c '${cmd}; exec tail -f /dev/null'`;
129
-
130
118
  /**
131
119
  * Retrieves the process ID (PID) of the most recently created gnome-terminal instance.
132
120
  * Note: This function is environment-specific (GNOME/Linux) and uses `pgrep -n`.
@@ -136,7 +124,6 @@ const daemonProcess = (cmd) => `exec bash -c '${cmd}; exec tail -f /dev/null'`;
136
124
  // list all terminals: pgrep gnome-terminal
137
125
  // list last terminal: pgrep -n gnome-terminal
138
126
  const getTerminalPid = () => JSON.parse(shellExec(`pgrep -n gnome-terminal`, { stdout: true, silent: true }));
139
-
140
127
  /**
141
128
  * Copies text content to the system clipboard using clipboardy.
142
129
  * Logs the copied content for confirmation.
@@ -148,5 +135,4 @@ function pbcopy(data) {
148
135
  clipboard.writeSync(data || '🦄');
149
136
  logger.info(`copied to clipboard`, clipboard.readSync());
150
137
  }
151
-
152
138
  export { ProcessController, getRootDirectory, shellExec, shellCd, pbcopy, getTerminalPid, daemonProcess };
@@ -131,11 +131,13 @@ class UnderpostStartUp {
131
131
  * @param {boolean} options.build - Whether to build the deployment.
132
132
  * @param {boolean} options.run - Whether to run the deployment.
133
133
  * @param {boolean} options.underpostQuicklyInstall - Whether to use underpost quickly install.
134
+ * @param {boolean} options.skipPullBase - Whether to skip pulling the base code.
135
+ * @param {boolean} options.skipFullBuild - Whether to skip building the full client bundle.
134
136
  */
135
137
  async callback(
136
138
  deployId = 'dd-default',
137
139
  env = 'development',
138
- options = { build: false, run: false, underpostQuicklyInstall: false },
140
+ options = { build: false, run: false, underpostQuicklyInstall: false, skipPullBase: false, skipFullBuild: false },
139
141
  ) {
140
142
  Underpost.env.set('container-status', `${deployId}-${env}-build-deployment`);
141
143
  if (options.build === true) await Underpost.start.build(deployId, env, options);
@@ -149,12 +151,13 @@ class UnderpostStartUp {
149
151
  * @param {Object} options - Options for the build.
150
152
  * @param {boolean} options.skipPullBase - Whether to skip pulling the base code and use the current workspace code directly.
151
153
  * @param {boolean} options.underpostQuicklyInstall - Whether to use underpost quickly install.
154
+ * @param {boolean} options.skipFullBuild - Whether to skip building the full client bundle.
152
155
  * @memberof UnderpostStartUp
153
156
  */
154
157
  async build(
155
158
  deployId = 'dd-default',
156
159
  env = 'development',
157
- options = { underpostQuicklyInstall: false, skipPullBase: false },
160
+ options = { underpostQuicklyInstall: false, skipPullBase: false, skipFullBuild: false },
158
161
  ) {
159
162
  const buildBasePath = `/home/dd`;
160
163
  const repoName = `engine-${deployId.split('-')[1]}`;
@@ -173,7 +176,7 @@ class UnderpostStartUp {
173
176
  for (const itcScript of itcScripts)
174
177
  if (itcScript.match(deployId)) shellExec(`node ./engine-private/itc-scripts/${itcScript}`);
175
178
  }
176
- shellExec(`node bin client ${deployId}`);
179
+ if (!options.skipFullBuild) shellExec(`node bin client ${deployId}`);
177
180
  },
178
181
  /**
179
182
  * Runs a deployment.
@@ -1,293 +1,192 @@
1
1
  /**
2
- * Module for managing Valkey
2
+ * Valkey connection and key-value store module.
3
+ *
4
+ * Responsibilities:
5
+ * - Manage per-instance Valkey connections keyed by `${host}${path}`.
6
+ * - Provide a thin, typed CRUD surface: get / set / del / update.
7
+ * - Expose connection status helpers.
8
+ *
9
+ * Out of scope: domain model factories, DTO projection — those belong in
10
+ * their respective service modules (e.g. guest.service.js).
11
+ *
3
12
  * @module src/server/valkey.js
4
13
  * @namespace ValkeyService
5
14
  */
6
-
7
15
  import Valkey from 'iovalkey';
8
- import mongoose from 'mongoose';
9
- import { hashPassword } from './auth.js';
10
16
  import { loggerFactory } from './logger.js';
11
17
 
12
18
  const logger = loggerFactory(import.meta);
13
19
 
14
- // Per-instance registries keyed by `${host}${path}`
20
+ // ─── Instance registry ────────────────────────────────────────────────────────
21
+
22
+ /** @type {Record<string, import('iovalkey').default>} */
15
23
  const ValkeyInstances = {};
16
- const DummyStores = {}; // in-memory Maps per instance
17
- const ValkeyStatus = {}; // 'connected' | 'dummy' | 'error' | undefined
24
+
25
+ /** @type {Record<string, 'connected' | 'error'>} */
26
+ const ValkeyStatus = {};
18
27
 
19
28
  /**
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 ValkeyService
29
+ * Derives the registry key from an instance descriptor.
30
+ * @param {{ host?: string, path?: string }} opts
31
+ * @returns {string}
24
32
  */
25
- const isValkeyEnable = () => Object.values(ValkeyStatus).some((s) => s === 'connected');
33
+ const _instanceKey = (opts = {}) => `${opts.host || ''}${opts.path || ''}`;
34
+
35
+ // ─── Connection ───────────────────────────────────────────────────────────────
26
36
 
27
37
  /**
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
38
+ * Returns true when at least one Valkey instance is connected.
39
+ * @returns {boolean}
34
40
  * @memberof ValkeyService
35
41
  */
36
- const _instanceKey = (opts = { host: '', path: '' }) => `${opts.host || ''}${opts.path || ''}`;
42
+ const isValkeyEnable = () => Object.values(ValkeyStatus).some((s) => s === 'connected');
37
43
 
38
44
  /**
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.
45
+ * Creates a Valkey client for the given instance and waits for connectivity.
46
+ * Throws on connection failure callers decide whether to abort or continue
47
+ * without Valkey.
48
+ *
49
+ * @param {{ host?: string, path?: string }} instance - Registry key descriptor.
50
+ * @param {{ host?: string, port?: number }} connectionOptions - iovalkey connection options.
51
+ * @returns {Promise<import('iovalkey').default>}
46
52
  * @memberof ValkeyService
47
53
  */
48
- const createValkeyConnection = async (
49
- instance = { host: '', path: '' },
50
- valkeyServerConnectionOptions = { host: '', path: '' },
51
- ) => {
54
+ const createValkeyConnection = async (instance = {}, connectionOptions = {}) => {
52
55
  const key = _instanceKey(instance);
53
- // Initialize dummy store for the instance
54
- if (!DummyStores[key]) DummyStores[key] = new Map();
55
56
 
56
- try {
57
- const client = await ValkeyAPI.valkeyClientFactory(valkeyServerConnectionOptions);
58
-
59
- // Attach listeners for visibility
60
- client.on?.('ready', () => {
61
- ValkeyStatus[key] = 'connected';
62
- logger.info('Valkey connected', { instance, status: ValkeyStatus[key] });
63
- });
64
- client.on?.('error', (err) => {
65
- // Switch to dummy if not yet connected
66
- if (ValkeyStatus[key] !== 'connected') {
67
- ValkeyStatus[key] = 'dummy';
68
- } else {
69
- ValkeyStatus[key] = 'error';
70
- }
71
- logger.warn('Valkey error', { err: err?.message, instance, status: ValkeyStatus[key] });
72
- });
73
- client.on?.('end', () => {
74
- if (ValkeyStatus[key] !== 'dummy') ValkeyStatus[key] = 'error';
75
- logger.warn('Valkey connection ended', { instance, status: ValkeyStatus[key] });
76
- });
77
-
78
- // Probe connectivity with a short timeout
79
- const probe = async () => {
57
+ const client = new Valkey({
58
+ port: connectionOptions.port ?? undefined,
59
+ host: connectionOptions.host ?? undefined,
60
+ retryStrategy: (attempt) => (attempt === 1 ? undefined : 1000),
61
+ });
62
+
63
+ client.on('ready', () => {
64
+ ValkeyStatus[key] = 'connected';
65
+ logger.info('Valkey connected', { instance });
66
+ });
67
+ client.on('error', (err) => {
68
+ ValkeyStatus[key] = 'error';
69
+ logger.error('Valkey error', { err: err?.message, instance });
70
+ });
71
+ client.on('end', () => {
72
+ ValkeyStatus[key] = 'error';
73
+ logger.warn('Valkey connection ended', { instance });
74
+ });
75
+
76
+ // Verify connectivity with a probe before marking ready
77
+ await Promise.race([
78
+ (async () => {
80
79
  try {
81
- // basic ping via SET/GET roundtrip
82
- const probeKey = `__vk_probe_${Date.now()}`;
83
- await client.set(probeKey, '1');
84
- await client.get(probeKey);
80
+ const probe = `__vk_probe_${Date.now()}`;
81
+ await client.set(probe, '1');
82
+ await client.get(probe);
83
+ await client.del(probe);
85
84
  ValkeyStatus[key] = 'connected';
86
- } catch (e) {
87
- ValkeyStatus[key] = 'dummy';
88
- logger.warn('Valkey probe failed, falling back to dummy', { instance, error: e?.message });
85
+ } catch {
86
+ ValkeyStatus[key] = 'error';
89
87
  }
90
- };
91
-
92
- // Race with timeout to avoid hanging
93
- await Promise.race([probe(), new Promise((resolve) => setTimeout(resolve, 1000))]);
88
+ })(),
89
+ new Promise((resolve) => setTimeout(resolve, 1500)),
90
+ ]);
94
91
 
95
- ValkeyInstances[key] = client;
96
- if (!ValkeyStatus[key]) ValkeyStatus[key] = 'dummy';
97
- } catch (err) {
98
- ValkeyStatus[key] = 'dummy';
99
- logger.warn('Valkey client creation failed, using dummy', { instance, error: err?.message });
100
- }
101
-
102
- return ValkeyInstances[key];
92
+ ValkeyInstances[key] = client;
93
+ logger.info('Valkey instance registered', { key, status: ValkeyStatus[key] });
94
+ return client;
103
95
  };
104
96
 
97
+ // ─── Internal client resolver ─────────────────────────────────────────────────
98
+
105
99
  /**
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 ValkeyService
100
+ * Resolves the connected client for an instance or throws.
101
+ * @param {{ host?: string, path?: string }} options
102
+ * @returns {import('iovalkey').default}
112
103
  */
113
- const selectDtoFactory = (payload, select) => {
114
- const result = {};
115
- for (const key of Object.keys(select)) {
116
- if (select[key] === 1 && key in payload) result[key] = payload[key];
104
+ const _client = (options) => {
105
+ const k = _instanceKey(options);
106
+ const client = ValkeyInstances[k];
107
+ if (!client || ValkeyStatus[k] !== 'connected') {
108
+ throw new Error(`Valkey instance not connected: ${k}`);
117
109
  }
118
- return result;
110
+ return client;
119
111
  };
120
112
 
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 ValkeyService
126
- */
127
- const valkeyClientFactory = async (options) => {
128
- const valkey = new Valkey({
129
- port: options?.port ? options.port : undefined,
130
- host: options?.host ? options.host : undefined,
131
- // Keep retry strategy minimal; state handled in createValkeyConnection
132
- retryStrategy: (attempt) => {
133
- if (attempt === 1) return undefined; // stop aggressive retries early
134
- return 1000; // retry interval if library continues
135
- },
136
- }); // Connect to 127.0.0.1:6379
137
- // new Valkey(6380); // 127.0.0.1:6380
138
- // new Valkey(6379, '192.168.1.1'); // 192.168.1.1:6379
139
- // new Valkey('/tmp/redis.sock');
140
- // new Valkey({
141
- // port: 6379, // Valkey port
142
- // host: '127.0.0.1', // Valkey host
143
- // username: 'default', // needs Valkey >= 6
144
- // password: 'my-top-secret',
145
- // db: 0, // Defaults to 0
146
- // });
147
- return valkey;
148
- };
113
+ // ─── CRUD ─────────────────────────────────────────────────────────────────────
149
114
 
150
115
  /**
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.
116
+ * Retrieves and JSON-parses a value by key.
117
+ * Returns null when the key does not exist.
118
+ *
119
+ * @param {{ host?: string, path?: string }} options
120
+ * @param {string} key
121
+ * @returns {Promise<object | string | null>}
157
122
  * @memberof ValkeyService
158
123
  */
159
- const getValkeyObject = async (options = { host: '', path: '' }, key = '') => {
160
- const k = _instanceKey(options);
161
- const status = ValkeyStatus[k];
124
+ const get = async (options, key) => {
125
+ const raw = await _client(options).get(key);
126
+ if (raw == null) return null;
162
127
  try {
163
- if (status === 'connected' && ValkeyInstances[k]) {
164
- const value = await ValkeyInstances[k].get(key);
165
- if (value == null) return null;
166
- try {
167
- return JSON.parse(value);
168
- } catch {
169
- // not JSON, return raw string
170
- return value;
171
- }
172
- }
173
- } catch (err) {
174
- logger.warn('Valkey get failed, using dummy', { key, err: err?.message });
128
+ return JSON.parse(raw);
129
+ } catch {
130
+ return raw;
175
131
  }
176
- // Dummy fallback returns stored value as-is (string or object)
177
- return DummyStores[k]?.get(key) ?? null;
178
132
  };
179
133
 
180
134
  /**
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.
135
+ * Serialises and stores a value by key.
136
+ * Pass `ttlMs` to set an expiry in milliseconds.
137
+ *
138
+ * @param {{ host?: string, path?: string }} options
139
+ * @param {string} key
140
+ * @param {object | string} payload
141
+ * @param {number} [ttlMs]
142
+ * @returns {Promise<string>} Resolves to 'OK'.
188
143
  * @memberof ValkeyService
189
144
  */
190
- const setValkeyObject = async (options = { host: '', path: '' }, key = '', payload = {}) => {
191
- const k = _instanceKey(options);
192
- const isString = typeof payload === 'string';
193
- const value = isString ? payload : JSON.stringify(payload);
194
- try {
195
- if (ValkeyStatus[k] === 'connected' && ValkeyInstances[k]) {
196
- return await ValkeyInstances[k].set(key, value);
197
- }
198
- } catch (err) {
199
- logger.warn('Valkey set failed, writing to dummy', { key, err: err?.message });
200
- }
201
- if (!DummyStores[k]) DummyStores[k] = new Map();
202
- // Store raw string or object accordingly
203
- DummyStores[k].set(key, isString ? payload : payload);
204
- return 'OK';
145
+ const set = async (options, key, payload, ttlMs) => {
146
+ const value = typeof payload === 'string' ? payload : JSON.stringify(payload);
147
+ if (ttlMs) return _client(options).set(key, value, 'PX', ttlMs);
148
+ return _client(options).set(key, value);
205
149
  };
206
150
 
207
151
  /**
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.
152
+ * Deletes a key.
153
+ *
154
+ * @param {{ host?: string, path?: string }} options
155
+ * @param {string} key
156
+ * @returns {Promise<number>}
215
157
  * @memberof ValkeyService
216
158
  */
217
- const updateValkeyObject = async (options = { host: '', path: '' }, key = '', payload = {}) => {
218
- let base = await getValkeyObject(options, key);
219
- if (typeof base !== 'object' || base === null) base = {};
220
- base.updatedAt = new Date().toISOString();
221
- return await setValkeyObject(options, key, { ...base, ...payload });
222
- };
159
+ const del = async (options, key) => _client(options).del(key);
223
160
 
224
161
  /**
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.
162
+ * Shallow-merges `payload` into the existing object stored at `key`
163
+ * and persists the result. The `updatedAt` timestamp is refreshed automatically.
164
+ *
165
+ * @param {{ host?: string, path?: string }} options
166
+ * @param {string} key
167
+ * @param {object} payload
168
+ * @returns {Promise<string>} Resolves to 'OK'.
233
169
  * @memberof ValkeyService
234
170
  */
235
- const valkeyObjectFactory = async (options = { host: 'localhost', path: '', object: {} }, model = '') => {
236
- const idoDate = new Date().toISOString();
237
- options.object = options.object || {};
238
- const { object } = options;
239
- const _id = new mongoose.Types.ObjectId().toString();
240
- object._id = _id;
241
- object.createdAt = idoDate;
242
- object.updatedAt = idoDate;
243
- switch (model) {
244
- case 'user': {
245
- const role = 'guest';
246
- object._id = `${role}${_id}`;
247
- return {
248
- ...object,
249
- username: `${role}${_id.slice(-5)}`,
250
- email: `${_id}@${options.host}`,
251
- password: hashPassword(process.env.JWT_SECRET),
252
- role,
253
- failedLoginAttempts: 0,
254
- phoneNumbers: [],
255
- publicKey: [],
256
- profileImageId: null,
257
- emailConfirmed: false,
258
- recoverTimeOut: null,
259
- lastLoginDate: null,
260
- activeSessions: [],
261
- };
262
- }
263
- default:
264
- throw new Error(`model schema not found: ${model}`);
265
- }
171
+ const update = async (options, key, payload) => {
172
+ const base = (await get(options, key)) ?? {};
173
+ return set(options, key, { ...base, ...payload, updatedAt: new Date().toISOString() });
266
174
  };
267
175
 
176
+ // ─── Public API class ─────────────────────────────────────────────────────────
177
+
268
178
  /**
269
- * A collection of Valkey-related API functions.
270
- * @type {object}
271
- * @memberof ValkeyServiceService
179
+ * Namespace grouping all Valkey operations.
180
+ * @memberof ValkeyService
272
181
  */
273
- const ValkeyAPI = {
274
- valkeyClientFactory,
275
- selectDtoFactory,
276
- getValkeyObject,
277
- setValkeyObject,
278
- valkeyObjectFactory,
279
- updateValkeyObject,
280
- createValkeyConnection,
281
- };
282
-
283
- export {
284
- valkeyClientFactory,
285
- selectDtoFactory,
286
- getValkeyObject,
287
- setValkeyObject,
288
- valkeyObjectFactory,
289
- updateValkeyObject,
290
- isValkeyEnable,
291
- createValkeyConnection,
292
- ValkeyAPI,
293
- };
182
+ class ValkeyAPI {
183
+ /** @param {{ host?: string, path?: string }} options */
184
+ static isConnected = (options) => ValkeyStatus[_instanceKey(options)] === 'connected';
185
+ static get = get;
186
+ static set = set;
187
+ static del = del;
188
+ static update = update;
189
+ static createValkeyConnection = createValkeyConnection;
190
+ }
191
+
192
+ export { isValkeyEnable, createValkeyConnection, get, set, del, update, ValkeyAPI };
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true,
4
+ "checkJs": false,
5
+ "maxNodeModuleJsDepth": 0,
6
+ "target": "ES2022",
7
+ "module": "ESNext",
8
+ "moduleResolution": "bundler",
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "noEmit": true
12
+ },
13
+ "include": ["src/**/*.js"],
14
+ "exclude": ["node_modules"]
15
+ }
package/typedoc.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "Nexodev - ERP, CRM Development & Cloud DevOps Services",
3
+ "entryPoints": ["./src/server", "./src/api", "./src/db", "./src/ws", "./src/grpc", "./src/mailer", "./src/runtime"],
4
+ "entryPointStrategy": "expand",
5
+ "exclude": ["**/node_modules/**", "**/docs/**", "**/client/**"],
6
+ "out": "./public/www.nexodev.org/docs/",
7
+ "readme": "./README.md",
8
+ "tsconfig": "./tsconfig.docs.json",
9
+ "skipErrorChecking": true,
10
+ "logLevel": "Error",
11
+ "includeVersion": true,
12
+ "hideGenerator": true,
13
+ "searchInComments": true,
14
+ "navigation": {
15
+ "includeCategories": true,
16
+ "includeGroups": true
17
+ },
18
+ "categorizeByGroup": true,
19
+ "sort": ["source-order"]
20
+ }