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.
- package/.github/workflows/release.cd.yml +1 -2
- package/CHANGELOG.md +251 -1
- package/CLI-HELP.md +26 -13
- package/Dockerfile +0 -4
- package/README.md +3 -3
- package/bin/build.js +13 -3
- package/bin/deploy.js +570 -1
- package/bin/file.js +5 -0
- package/conf.js +11 -2
- package/jsconfig.json +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -6
- package/manifests/deployment/dd-test-development/deployment.yaml +136 -66
- package/manifests/deployment/dd-test-development/proxy.yaml +41 -5
- package/package.json +20 -11
- package/src/api/core/core.controller.js +10 -10
- package/src/api/core/core.service.js +10 -10
- package/src/api/default/default.controller.js +10 -10
- package/src/api/default/default.service.js +10 -10
- package/src/api/document/document.controller.js +12 -12
- package/src/api/document/document.model.js +10 -16
- package/src/api/file/file.controller.js +8 -8
- package/src/api/file/file.model.js +10 -10
- package/src/api/file/file.service.js +36 -36
- package/src/api/test/test.controller.js +8 -8
- package/src/api/test/test.service.js +8 -8
- package/src/api/user/guest.service.js +99 -0
- package/src/api/user/user.controller.js +6 -6
- package/src/api/user/user.model.js +8 -13
- package/src/api/user/user.service.js +3 -20
- package/src/cli/deploy.js +33 -30
- package/src/cli/fs.js +62 -5
- package/src/cli/image.js +43 -1
- package/src/cli/index.js +5 -1
- package/src/cli/release.js +57 -1
- package/src/cli/repository.js +35 -3
- package/src/cli/run.js +300 -35
- package/src/cli/ssh.js +1 -1
- package/src/cli/static.js +43 -115
- package/src/client/Default.index.js +21 -33
- package/src/client/components/core/404.js +4 -4
- package/src/client/components/core/500.js +4 -4
- package/src/client/components/core/Account.js +73 -60
- package/src/client/components/core/AgGrid.js +23 -33
- package/src/client/components/core/Alert.js +12 -13
- package/src/client/components/core/AppStore.js +1 -1
- package/src/client/components/core/Auth.js +20 -32
- package/src/client/components/core/Badge.js +7 -13
- package/src/client/components/core/BtnIcon.js +15 -17
- package/src/client/components/core/CalendarCore.js +42 -63
- package/src/client/components/core/Chat.js +13 -15
- package/src/client/components/core/ClientEvents.js +87 -0
- package/src/client/components/core/ColorPaletteElement.js +309 -0
- package/src/client/components/core/Content.js +17 -14
- package/src/client/components/core/Css.js +15 -71
- package/src/client/components/core/CssCore.js +12 -16
- package/src/client/components/core/D3Chart.js +4 -4
- package/src/client/components/core/Docs.js +60 -59
- package/src/client/components/core/DropDown.js +69 -91
- package/src/client/components/core/EventBus.js +92 -0
- package/src/client/components/core/EventsUI.js +14 -17
- package/src/client/components/core/FileExplorer.js +102 -234
- package/src/client/components/core/FullScreen.js +47 -75
- package/src/client/components/core/Input.js +24 -69
- package/src/client/components/core/Keyboard.js +25 -18
- package/src/client/components/core/KeyboardAvoidance.js +145 -0
- package/src/client/components/core/LoadingAnimation.js +25 -31
- package/src/client/components/core/LogIn.js +41 -41
- package/src/client/components/core/LogOut.js +23 -14
- package/src/client/components/core/Modal.js +397 -176
- package/src/client/components/core/NotificationManager.js +14 -18
- package/src/client/components/core/Panel.js +54 -50
- package/src/client/components/core/PanelForm.js +25 -125
- package/src/client/components/core/Polyhedron.js +110 -214
- package/src/client/components/core/PublicProfile.js +39 -32
- package/src/client/components/core/Recover.js +52 -48
- package/src/client/components/core/Responsive.js +88 -32
- package/src/client/components/core/RichText.js +9 -18
- package/src/client/components/core/Router.js +24 -3
- package/src/client/components/core/SearchBox.js +37 -37
- package/src/client/components/core/SignUp.js +39 -30
- package/src/client/components/core/SocketIo.js +31 -2
- package/src/client/components/core/SocketIoHandler.js +6 -6
- package/src/client/components/core/ToggleSwitch.js +8 -20
- package/src/client/components/core/ToolTip.js +5 -17
- package/src/client/components/core/Translate.js +56 -59
- package/src/client/components/core/Validator.js +26 -16
- package/src/client/components/core/Wallet.js +15 -26
- package/src/client/components/core/Worker.js +140 -25
- package/src/client/components/core/windowGetDimensions.js +7 -7
- package/src/client/components/default/{MenuDefault.js → AppShellDefault.js} +87 -87
- package/src/client/components/default/CssDefault.js +12 -12
- package/src/client/components/default/LogInDefault.js +6 -4
- package/src/client/components/default/LogOutDefault.js +6 -4
- package/src/client/components/default/RouterDefault.js +47 -0
- package/src/client/components/default/SettingsDefault.js +4 -4
- package/src/client/components/default/SignUpDefault.js +6 -4
- package/src/client/components/default/TranslateDefault.js +3 -3
- package/src/client/services/core/core.service.js +17 -49
- package/src/client/services/default/default.management.js +139 -242
- package/src/client/services/default/default.service.js +10 -16
- package/src/client/services/document/document.service.js +14 -19
- package/src/client/services/file/file.service.js +8 -13
- package/src/client/services/test/test.service.js +8 -13
- package/src/client/services/user/guest.service.js +79 -0
- package/src/client/services/user/user.management.js +5 -5
- package/src/client/services/user/user.service.js +14 -20
- package/src/client/ssr/body/404.js +3 -3
- package/src/client/ssr/body/500.js +3 -3
- package/src/client/ssr/body/CacheControl.js +5 -2
- package/src/client/ssr/body/DefaultSplashScreen.js +19 -12
- package/src/client/ssr/mailer/DefaultRecoverEmail.js +19 -20
- package/src/client/ssr/mailer/DefaultVerifyEmail.js +15 -16
- package/src/client/ssr/offline/Maintenance.js +12 -11
- package/src/client/ssr/offline/NoNetworkConnection.js +3 -3
- package/src/client/ssr/pages/Test.js +2 -2
- package/src/client/sw/core.sw.js +212 -0
- package/src/index.js +1 -1
- package/src/runtime/express/Dockerfile +4 -4
- package/src/runtime/lampp/Dockerfile +8 -7
- package/src/runtime/wp/Dockerfile +11 -17
- package/src/server/client-build-docs.js +45 -46
- package/src/server/client-build.js +334 -60
- package/src/server/client-formatted.js +47 -16
- package/src/server/conf.js +5 -4
- package/src/server/ipfs-client.js +232 -91
- package/src/server/process.js +13 -27
- package/src/server/start.js +6 -3
- package/src/server/valkey.js +134 -235
- package/tsconfig.docs.json +15 -0
- package/typedoc.json +20 -0
- package/jsdoc.json +0 -52
- package/src/client/components/core/ColorPalette.js +0 -5267
- package/src/client/components/core/JoyStick.js +0 -80
- package/src/client/components/default/RoutesDefault.js +0 -49
- package/src/client/sw/default.sw.js +0 -127
- package/src/client/sw/template.sw.js +0 -84
package/src/server/process.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
57
|
-
return
|
|
50
|
+
static onSigListen() {
|
|
51
|
+
return ProcessController.SIG.map((sig) =>
|
|
58
52
|
process.on(sig, (...args) => {
|
|
59
|
-
|
|
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
|
|
79
|
-
|
|
70
|
+
static init(logger) {
|
|
71
|
+
ProcessController.logger = logger;
|
|
80
72
|
process.on('exit', (...args) => {
|
|
81
|
-
|
|
73
|
+
ProcessController.logger.info(`process on exit`, args);
|
|
82
74
|
});
|
|
83
|
-
|
|
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 };
|
package/src/server/start.js
CHANGED
|
@@ -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.
|
package/src/server/valkey.js
CHANGED
|
@@ -1,293 +1,192 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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
|
-
//
|
|
20
|
+
// ─── Instance registry ────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/** @type {Record<string, import('iovalkey').default>} */
|
|
15
23
|
const ValkeyInstances = {};
|
|
16
|
-
|
|
17
|
-
|
|
24
|
+
|
|
25
|
+
/** @type {Record<string, 'connected' | 'error'>} */
|
|
26
|
+
const ValkeyStatus = {};
|
|
18
27
|
|
|
19
28
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* @returns {
|
|
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
|
|
33
|
+
const _instanceKey = (opts = {}) => `${opts.host || ''}${opts.path || ''}`;
|
|
34
|
+
|
|
35
|
+
// ─── Connection ───────────────────────────────────────────────────────────────
|
|
26
36
|
|
|
27
37
|
/**
|
|
28
|
-
*
|
|
29
|
-
* @
|
|
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
|
|
42
|
+
const isValkeyEnable = () => Object.values(ValkeyStatus).some((s) => s === 'connected');
|
|
37
43
|
|
|
38
44
|
/**
|
|
39
|
-
* Creates
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* @param {string}
|
|
44
|
-
* @param {
|
|
45
|
-
* @returns {Promise<
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
await client.
|
|
84
|
-
await client.
|
|
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
|
|
87
|
-
ValkeyStatus[key] = '
|
|
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
|
-
|
|
93
|
-
await Promise.race([probe(), new Promise((resolve) => setTimeout(resolve, 1000))]);
|
|
88
|
+
})(),
|
|
89
|
+
new Promise((resolve) => setTimeout(resolve, 1500)),
|
|
90
|
+
]);
|
|
94
91
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* @
|
|
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
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
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
|
|
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
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
* @param {
|
|
155
|
-
* @param {string}
|
|
156
|
-
* @returns {Promise<object|string|null>}
|
|
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
|
|
160
|
-
const
|
|
161
|
-
|
|
124
|
+
const get = async (options, key) => {
|
|
125
|
+
const raw = await _client(options).get(key);
|
|
126
|
+
if (raw == null) return null;
|
|
162
127
|
try {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
* @param {
|
|
185
|
-
* @param {string}
|
|
186
|
-
* @param {object|string}
|
|
187
|
-
* @
|
|
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
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
* @param {
|
|
212
|
-
* @
|
|
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
|
|
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
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
* @param {
|
|
229
|
-
* @param {string}
|
|
230
|
-
* @param {object}
|
|
231
|
-
* @
|
|
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
|
|
236
|
-
const
|
|
237
|
-
options
|
|
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
|
-
*
|
|
270
|
-
* @
|
|
271
|
-
* @memberof ValkeyServiceService
|
|
179
|
+
* Namespace grouping all Valkey operations.
|
|
180
|
+
* @memberof ValkeyService
|
|
272
181
|
*/
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
+
}
|