underpost 2.8.82 → 2.8.85

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 (115) hide show
  1. package/.env.development +1 -0
  2. package/.env.production +1 -0
  3. package/.env.test +1 -0
  4. package/.github/workflows/{ghpkg.yml → ghpkg.ci.yml} +5 -5
  5. package/.github/workflows/{npmpkg.yml → npmpkg.ci.yml} +5 -5
  6. package/.github/workflows/{publish.yml → publish.ci.yml} +1 -1
  7. package/.github/workflows/{pwa-microservices-template.page.yml → pwa-microservices-template-page.cd.yml} +1 -1
  8. package/.github/workflows/{pwa-microservices-template.test.yml → pwa-microservices-template-test.ci.yml} +1 -1
  9. package/.vscode/extensions.json +1 -1
  10. package/.vscode/settings.json +0 -44
  11. package/README.md +62 -2
  12. package/bin/build.js +15 -5
  13. package/bin/deploy.js +42 -92
  14. package/bin/file.js +33 -9
  15. package/bin/vs.js +12 -4
  16. package/cli.md +90 -42
  17. package/conf.js +1 -1
  18. package/docker-compose.yml +1 -1
  19. package/manifests/deployment/dd-template-development/deployment.yaml +2 -2
  20. package/manifests/deployment/tensorflow/tf-gpu-test.yaml +65 -0
  21. package/manifests/maas/device-scan.sh +3 -3
  22. package/manifests/maas/gpu-diag.sh +19 -0
  23. package/manifests/maas/maas-setup.sh +10 -10
  24. package/manifests/maas/snap-clean.sh +26 -0
  25. package/package.json +4 -6
  26. package/src/api/user/user.router.js +24 -1
  27. package/src/api/user/user.service.js +1 -4
  28. package/src/cli/baremetal.js +105 -73
  29. package/src/cli/cloud-init.js +21 -12
  30. package/src/cli/cluster.js +227 -133
  31. package/src/cli/deploy.js +34 -0
  32. package/src/cli/index.js +28 -1
  33. package/src/cli/monitor.js +8 -12
  34. package/src/cli/repository.js +7 -4
  35. package/src/cli/run.js +367 -0
  36. package/src/cli/ssh.js +32 -0
  37. package/src/cli/test.js +1 -1
  38. package/src/client/Default.index.js +7 -3
  39. package/src/client/components/core/Account.js +1 -1
  40. package/src/client/components/core/Chat.js +1 -1
  41. package/src/client/components/core/CommonJs.js +24 -22
  42. package/src/client/components/core/Content.js +1 -5
  43. package/src/client/components/core/Css.js +258 -18
  44. package/src/client/components/core/CssCore.js +8 -8
  45. package/src/client/components/core/Docs.js +14 -61
  46. package/src/client/components/core/DropDown.js +137 -82
  47. package/src/client/components/core/EventsUI.js +92 -5
  48. package/src/client/components/core/LoadingAnimation.js +8 -15
  49. package/src/client/components/core/Modal.js +597 -136
  50. package/src/client/components/core/NotificationManager.js +2 -2
  51. package/src/client/components/core/ObjectLayerEngine.js +638 -0
  52. package/src/client/components/core/Panel.js +158 -34
  53. package/src/client/components/core/PanelForm.js +12 -3
  54. package/src/client/components/core/Recover.js +1 -1
  55. package/src/client/components/core/Router.js +77 -17
  56. package/src/client/components/core/SocketIo.js +3 -3
  57. package/src/client/components/core/Translate.js +6 -2
  58. package/src/client/components/core/VanillaJs.js +0 -3
  59. package/src/client/components/core/Worker.js +3 -1
  60. package/src/client/components/default/CssDefault.js +17 -3
  61. package/src/client/components/default/MenuDefault.js +264 -45
  62. package/src/client/components/default/RoutesDefault.js +6 -12
  63. package/src/client/public/default/android-chrome-144x144.png +0 -0
  64. package/src/client/public/default/android-chrome-192x192.png +0 -0
  65. package/src/client/public/default/android-chrome-256x256.png +0 -0
  66. package/src/client/public/default/android-chrome-36x36.png +0 -0
  67. package/src/client/public/default/android-chrome-48x48.png +0 -0
  68. package/src/client/public/default/android-chrome-72x72.png +0 -0
  69. package/src/client/public/default/android-chrome-96x96.png +0 -0
  70. package/src/client/public/default/apple-touch-icon-114x114-precomposed.png +0 -0
  71. package/src/client/public/default/apple-touch-icon-114x114.png +0 -0
  72. package/src/client/public/default/apple-touch-icon-120x120-precomposed.png +0 -0
  73. package/src/client/public/default/apple-touch-icon-120x120.png +0 -0
  74. package/src/client/public/default/apple-touch-icon-144x144-precomposed.png +0 -0
  75. package/src/client/public/default/apple-touch-icon-144x144.png +0 -0
  76. package/src/client/public/default/apple-touch-icon-152x152-precomposed.png +0 -0
  77. package/src/client/public/default/apple-touch-icon-152x152.png +0 -0
  78. package/src/client/public/default/apple-touch-icon-180x180-precomposed.png +0 -0
  79. package/src/client/public/default/apple-touch-icon-180x180.png +0 -0
  80. package/src/client/public/default/apple-touch-icon-57x57-precomposed.png +0 -0
  81. package/src/client/public/default/apple-touch-icon-57x57.png +0 -0
  82. package/src/client/public/default/apple-touch-icon-60x60-precomposed.png +0 -0
  83. package/src/client/public/default/apple-touch-icon-60x60.png +0 -0
  84. package/src/client/public/default/apple-touch-icon-72x72-precomposed.png +0 -0
  85. package/src/client/public/default/apple-touch-icon-72x72.png +0 -0
  86. package/src/client/public/default/apple-touch-icon-76x76-precomposed.png +0 -0
  87. package/src/client/public/default/apple-touch-icon-76x76.png +0 -0
  88. package/src/client/public/default/apple-touch-icon-precomposed.png +0 -0
  89. package/src/client/public/default/apple-touch-icon.png +0 -0
  90. package/src/client/public/default/assets/background/dark.jpg +0 -0
  91. package/src/client/public/default/assets/background/dark.svg +557 -0
  92. package/src/client/public/default/assets/logo/base-icon.png +0 -0
  93. package/src/client/public/default/assets/logo/underpost.gif +0 -0
  94. package/src/client/public/default/assets/mailer/api-user-check.png +0 -0
  95. package/src/client/public/default/assets/mailer/api-user-invalid-token.png +0 -0
  96. package/src/client/public/default/assets/mailer/api-user-recover.png +0 -0
  97. package/src/client/public/default/favicon-16x16.png +0 -0
  98. package/src/client/public/default/favicon-32x32.png +0 -0
  99. package/src/client/public/default/favicon.ico +0 -0
  100. package/src/client/public/default/mstile-144x144.png +0 -0
  101. package/src/client/public/default/mstile-150x150.png +0 -0
  102. package/src/client/public/default/mstile-310x150.png +0 -0
  103. package/src/client/public/default/mstile-310x310.png +0 -0
  104. package/src/client/public/default/mstile-70x70.png +0 -0
  105. package/src/client/public/default/safari-pinned-tab.svg +24 -0
  106. package/src/client/ssr/body/DefaultSplashScreen.js +2 -2
  107. package/src/index.js +34 -17
  108. package/src/monitor.js +24 -0
  109. package/src/runtime/lampp/Dockerfile +30 -39
  110. package/src/runtime/lampp/Lampp.js +11 -2
  111. package/src/server/client-build-docs.js +205 -0
  112. package/src/server/client-build.js +16 -166
  113. package/src/server/conf.js +18 -8
  114. package/src/server/process.js +16 -19
  115. package/src/server/valkey.js +102 -41
@@ -17,11 +17,11 @@ import dotenv from 'dotenv';
17
17
  import AdmZip from 'adm-zip';
18
18
  import * as dir from 'path';
19
19
  import { shellExec } from './process.js';
20
- import swaggerAutoGen from 'swagger-autogen';
21
20
  import { SitemapStream, streamToPromise } from 'sitemap';
22
21
  import { Readable } from 'stream';
23
22
  import { buildIcons, buildTextImg, getBufferPngText } from './client-icons.js';
24
23
  import Underpost from '../index.js';
24
+ import { buildDocs } from './client-build-docs.js';
25
25
 
26
26
  dotenv.config();
27
27
 
@@ -333,14 +333,7 @@ const buildClient = async (options = { liveClientBuildPaths: [], instances: [] }
333
333
  minifyBuild || process.env.NODE_ENV === 'production' ? UglifyJS.minify(jsSrc).code : jsSrc,
334
334
  'utf8',
335
335
  );
336
-
337
- // const title = `${metadata && metadata.title ? metadata.title : cap(client)}${
338
- // view.title ? ` | ${view.title}` : view.path !== '/' ? ` | ${titleFormatted(view.path)}` : ''
339
- // }`;
340
-
341
- const title = `${
342
- view.title ? `${view.title} | ` : view.path !== '/' ? `${titleFormatted(view.path)} | ` : ''
343
- }${metadata && metadata.title ? metadata.title : cap(client)}`;
336
+ const title = metadata.title ? metadata.title : title;
344
337
 
345
338
  const canonicalURL = `https://${host}${path}${
346
339
  view.path === '/' ? (path === '/' ? '' : '/') : path === '/' ? `${view.path.slice(1)}/` : `${view.path}/`
@@ -441,27 +434,13 @@ const buildClient = async (options = { liveClientBuildPaths: [], instances: [] }
441
434
  case 'CyberiaDefaultSplashScreen':
442
435
  case 'NexodevSplashScreen':
443
436
  case 'DefaultSplashScreen':
444
- if (backgroundImage) {
437
+ if (backgroundImage)
445
438
  ssrHeadComponents += SrrComponent({
439
+ ...metadata,
446
440
  backgroundImage: (path === '/' ? path : `${path}/`) + backgroundImage,
447
441
  });
448
- // `data:image/${backgroundImage.split('.').pop()};base64,${fs
449
- // .readFileSync()
450
- // .toString('base64')}`,
451
- break;
452
- } else {
453
- ssrHeadComponents += SrrComponent({ metadata });
454
- break;
455
- const bufferBackgroundImage = await getBufferPngText({
456
- text: ' ',
457
- textColor: metadata?.themeColor ? metadata.themeColor : '#ececec',
458
- size: '100x100',
459
- bgColor: metadata?.themeColor ? metadata.themeColor : '#ececec',
460
- });
461
- ssrHeadComponents += SrrComponent({
462
- backgroundImage: `data:image/png;base64,${bufferBackgroundImage.toString('base64')}`,
463
- });
464
- }
442
+ else ssrHeadComponents += SrrComponent({ metadata });
443
+ break;
465
444
 
466
445
  case 'CyberiaSplashScreenLore': {
467
446
  ssrBodyComponents += SrrComponent({
@@ -557,145 +536,16 @@ Sitemap: https://${host}${path === '/' ? '' : path}/sitemap.xml`,
557
536
  }
558
537
 
559
538
  if (!enableLiveRebuild && !process.argv.includes('l') && !process.argv.includes('deploy') && docsBuild) {
560
- // https://www.pullrequest.com/blog/leveraging-jsdoc-for-better-code-documentation-in-javascript/
561
- // https://jsdoc.app/about-configuring-jsdoc
562
- // https://jsdoc.app/ Block tags
563
-
564
- const jsDocsConfig = JSON.parse(fs.readFileSync(`./jsdoc.json`, 'utf8'));
565
- jsDocsConfig.opts.destination = `./public/${host}${path === '/' ? path : `${path}/`}docs/`;
566
- jsDocsConfig.opts.theme_opts.title = metadata && metadata.title ? metadata.title : undefined;
567
- jsDocsConfig.opts.theme_opts.favicon = `./public/${host}${path === '/' ? path : `${path}/favicon.ico`}`;
568
- fs.writeFileSync(`./jsdoc.json`, JSON.stringify(jsDocsConfig, null, 4), 'utf8');
569
- logger.warn('build jsdoc view', jsDocsConfig.opts.destination);
570
- shellExec(`npm run docs`, { silent: true });
571
-
572
- // coverage
573
- if (!fs.existsSync(`./coverage`)) {
574
- shellExec(`npm test`);
575
- }
576
- const coverageBuildPath = `${jsDocsConfig.opts.destination}/coverage`;
577
- fs.mkdirSync(coverageBuildPath, { recursive: true });
578
- fs.copySync(`./coverage`, coverageBuildPath);
579
-
580
- // uml
581
- // shellExec(`node bin/deploy uml ${host} ${path}`);
582
-
583
- // https://swagger-autogen.github.io/docs/
584
-
585
- const basePath = path === '/' ? `${process.env.BASE_API}` : `/${process.env.BASE_API}`;
586
-
587
- const doc = {
588
- info: {
589
- version: packageData.version, // by default: '1.0.0'
590
- title: metadata?.title ? `${metadata.title}` : 'REST API', // by default: 'REST API'
591
- description: metadata?.description ? metadata.description : '', // by default: ''
592
- },
593
- servers: [
594
- {
595
- url:
596
- process.env.NODE_ENV === 'development'
597
- ? `http://localhost:${port}${path}${basePath}`
598
- : `https://${host}${path}${basePath}`, // by default: 'http://localhost:3000'
599
- description: `${process.env.NODE_ENV} server`, // by default: ''
600
- },
601
- ],
602
- tags: [
603
- // by default: empty Array
604
- {
605
- name: 'user', // Tag name
606
- description: 'User API operations', // Tag description
607
- },
608
- ],
609
- components: {
610
- schemas: {
611
- userRequest: {
612
- username: 'user123',
613
- password: 'Password123',
614
- email: 'user@example.com',
615
- },
616
- userResponse: {
617
- status: 'success',
618
- data: {
619
- token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7Il9pZCI6IjY2YzM3N2Y1N2Y5OWU1OTY5YjgxZG...',
620
- user: {
621
- _id: '66c377f57f99e5969b81de89',
622
- email: 'user@example.com',
623
- emailConfirmed: false,
624
- username: 'user123',
625
- role: 'user',
626
- profileImageId: '66c377f57f99e5969b81de87',
627
- },
628
- },
629
- },
630
- userUpdateResponse: {
631
- status: 'success',
632
- data: {
633
- _id: '66c377f57f99e5969b81de89',
634
- email: 'user@example.com',
635
- emailConfirmed: false,
636
- username: 'user123222',
637
- role: 'user',
638
- profileImageId: '66c377f57f99e5969b81de87',
639
- },
640
- },
641
- userGetResponse: {
642
- status: 'success',
643
- data: {
644
- _id: '66c377f57f99e5969b81de89',
645
- email: 'user@example.com',
646
- emailConfirmed: false,
647
- username: 'user123222',
648
- role: 'user',
649
- profileImageId: '66c377f57f99e5969b81de87',
650
- },
651
- },
652
- userLogInRequest: {
653
- email: 'user@example.com',
654
- password: 'Password123',
655
- },
656
- userBadRequestResponse: {
657
- status: 'error',
658
- message: 'Bad request. Please check your inputs, and try again',
659
- },
660
- },
661
- securitySchemes: {
662
- bearerAuth: {
663
- type: 'http',
664
- scheme: 'bearer',
665
- },
666
- },
667
- },
668
- };
669
-
670
- // plantuml
671
- logger.info('copy plantuml', `${rootClientPath}/docs/plantuml`);
672
- fs.copySync(`./src/client/public/default/plantuml`, `${rootClientPath}/docs/plantuml`);
673
-
674
- logger.warn('build swagger api docs', doc.info);
675
-
676
- const outputFile = `./public/${host}${path === '/' ? path : `${path}/`}swagger-output.json`;
677
- const routes = [];
678
- for (const api of apis) {
679
- if (['user'].includes(api)) routes.push(`./src/api/${api}/${api}.router.js`);
680
- }
681
-
682
- /* NOTE: If you are using the express Router, you must pass in the 'routes' only the
683
- root file where the route starts, such as index.js, app.js, routes.js, etc ... */
684
-
685
- await swaggerAutoGen({ openapi: '3.0.0' })(outputFile, routes, doc);
686
-
687
- const htmlFiles = await fs.readdir(`./public/${host}/docs/engine/${Underpost.version.replace('v', '')}`);
688
- for (const htmlFile of htmlFiles) {
689
- if (htmlFile.match('.html')) {
690
- fs.writeFileSync(
691
- `./public/${host}/docs/engine/${Underpost.version.replace('v', '')}/${htmlFile}`,
692
- fs
693
- .readFileSync(`./public/${host}/docs/engine/${Underpost.version.replace('v', '')}/${htmlFile}`, 'utf8')
694
- .replaceAll('Tutorials', 'References'),
695
- 'utf8',
696
- );
697
- }
698
- }
539
+ await buildDocs({
540
+ host,
541
+ path,
542
+ port,
543
+ metadata,
544
+ apis,
545
+ publicClientId,
546
+ rootClientPath,
547
+ packageData,
548
+ });
699
549
  }
700
550
 
701
551
  if (client) {
@@ -89,12 +89,19 @@ const Config = {
89
89
  };
90
90
 
91
91
  const loadConf = (deployId, envInput, subConf) => {
92
+ if (deployId === 'current') {
93
+ console.log(process.env.DEPLOY_ID);
94
+ return;
95
+ }
92
96
  if (deployId === 'clean') {
93
- shellExec(`git checkout package.json`);
94
- shellExec(`git checkout .env.production`);
95
- shellExec(`git checkout .env.development`);
96
- shellExec(`git checkout .env.test`);
97
- shellExec(`git checkout jsdoc.json`);
97
+ const path = envInput ?? '.';
98
+ fs.removeSync(`${path}/.env`);
99
+ shellExec(`git checkout ${path}/.env.production`);
100
+ shellExec(`git checkout ${path}/.env.development`);
101
+ shellExec(`git checkout ${path}/.env.test`);
102
+ if (fs.existsSync(`${path}/jsdoc.json`)) shellExec(`git checkout ${path}/jsdoc.json`);
103
+ shellExec(`git checkout ${path}/package.json`);
104
+ shellExec(`git checkout ${path}/package-lock.json`);
98
105
  return;
99
106
  }
100
107
  const folder = fs.existsSync(`./engine-private/replica/${deployId}`)
@@ -1171,7 +1178,7 @@ const writeEnv = (envPath, envObj) =>
1171
1178
  'utf8',
1172
1179
  );
1173
1180
 
1174
- const buildCliDoc = (program) => {
1181
+ const buildCliDoc = (program, oldVersion, newVersion) => {
1175
1182
  let md = shellExec(`node bin help`, { silent: true, stdout: true }).split('Options:');
1176
1183
  const baseOptions =
1177
1184
  `## ${md[0].split(`\n`)[2]}
@@ -1207,13 +1214,15 @@ const buildCliDoc = (program) => {
1207
1214
  `
1208
1215
  `;
1209
1216
  });
1217
+ md = md.replaceAll(oldVersion, newVersion);
1210
1218
  fs.writeFileSync(`./src/client/public/nexodev/docs/references/Command Line Interface.md`, md, 'utf8');
1211
1219
  fs.writeFileSync(`./cli.md`, md, 'utf8');
1212
1220
  const readmeSplit = `pwa-microservices-template</a>`;
1213
1221
  const readme = fs.readFileSync(`./README.md`, 'utf8').split(readmeSplit);
1214
1222
  fs.writeFileSync(
1215
1223
  './README.md',
1216
- readme[0] +
1224
+ (
1225
+ readme[0] +
1217
1226
  readmeSplit +
1218
1227
  `
1219
1228
 
@@ -1223,7 +1232,8 @@ const buildCliDoc = (program) => {
1223
1232
 
1224
1233
  <a target="_top" href="https://github.com/underpostnet/pwa-microservices-template/blob/master/cli.md">See complete CLI Docs here.</a>
1225
1234
 
1226
- `,
1235
+ `
1236
+ ).replaceAll(oldVersion, newVersion),
1227
1237
  'utf8',
1228
1238
  );
1229
1239
  };
@@ -10,8 +10,6 @@ dotenv.config();
10
10
 
11
11
  const logger = loggerFactory(import.meta);
12
12
 
13
- // process.exit();
14
-
15
13
  const getRootDirectory = () => process.cwd().replace(/\\/g, '/');
16
14
 
17
15
  const ProcessController = {
@@ -63,27 +61,26 @@ const shellCd = (cd, options = { disableLog: false }) => {
63
61
  return shell.cd(cd);
64
62
  };
65
63
 
66
- function pbcopy(data) {
67
- switch (process.platform) {
68
- case 'linux':
69
- {
70
- // sudo dnf install xclip
71
- // sudo apt update
72
- // sudo apt install xclip
73
- // paste: xclip -o
74
- // copy:
75
- // shellExec(`echo "${data}" | xclip -sel clip`, { async: true });
76
- }
64
+ const openTerminal = (cmd, options = { single: false }) => {
65
+ if (options.single === true) {
66
+ shellExec(`setsid gnome-terminal -- bash -ic "${cmd}; exec bash" >/dev/null 2>&1 &`);
67
+ return;
68
+ }
69
+ shellExec(`gnome-terminal -- bash -c "${cmd}; exec bash" & disown`, {
70
+ async: true,
71
+ stdout: true,
72
+ });
73
+ };
77
74
 
78
- break;
75
+ const daemonProcess = (cmd) => `exec bash -c '${cmd}; exec tail -f /dev/null'`;
79
76
 
80
- default:
81
- break;
82
- }
77
+ // list all terminals: pgrep gnome-terminal
78
+ // list last terminal: pgrep -n gnome-terminal
79
+ const getTerminalPid = () => JSON.parse(shellExec(`pgrep -n gnome-terminal`, { stdout: true, silent: true }));
83
80
 
81
+ function pbcopy(data) {
84
82
  clipboard.writeSync(data || '🦄');
85
-
86
83
  logger.info(`copied to clipboard`, clipboard.readSync());
87
84
  }
88
85
 
89
- export { ProcessController, getRootDirectory, shellExec, shellCd, pbcopy };
86
+ export { ProcessController, getRootDirectory, shellExec, shellCd, pbcopy, openTerminal, getTerminalPid, daemonProcess };
@@ -5,22 +5,71 @@ import { loggerFactory } from './logger.js';
5
5
 
6
6
  const logger = loggerFactory(import.meta);
7
7
 
8
+ // Per-instance registries keyed by `${host}${path}`
8
9
  const ValkeyInstances = {};
10
+ const DummyStores = {}; // in-memory Maps per instance
11
+ const ValkeyStatus = {}; // 'connected' | 'dummy' | 'error' | undefined
9
12
 
10
- let valkeyEnabled = true;
13
+ // Backward-compatible overall flag: true if any instance is connected
14
+ const isValkeyEnable = () => Object.values(ValkeyStatus).some((s) => s === 'connected');
11
15
 
12
- const disableValkeyErrorMessage = 'valkey is not enabled';
13
-
14
- const isValkeyEnable = () => valkeyEnabled;
16
+ const _instanceKey = (opts = { host: '', path: '' }) => `${opts.host || ''}${opts.path || ''}`;
15
17
 
16
18
  const createValkeyConnection = async (
17
- instance = { host: '', port: 0 },
18
- valkeyServerConnectionOptions = { host: '', port: 0 },
19
+ instance = { host: '', path: '' },
20
+ valkeyServerConnectionOptions = { host: '', path: '' },
19
21
  ) => {
20
- ValkeyInstances[`${instance.host}${instance.path}`] = await ValkeyAPI.valkeyClientFactory(
21
- valkeyServerConnectionOptions,
22
- );
23
- return ValkeyInstances[`${instance.host}${instance.path}`];
22
+ const key = _instanceKey(instance);
23
+ // Initialize dummy store for the instance
24
+ if (!DummyStores[key]) DummyStores[key] = new Map();
25
+
26
+ try {
27
+ const client = await ValkeyAPI.valkeyClientFactory(valkeyServerConnectionOptions);
28
+
29
+ // Attach listeners for visibility
30
+ client.on?.('ready', () => {
31
+ ValkeyStatus[key] = 'connected';
32
+ logger.info('Valkey connected', { instance, status: ValkeyStatus[key] });
33
+ });
34
+ client.on?.('error', (err) => {
35
+ // Switch to dummy if not yet connected
36
+ if (ValkeyStatus[key] !== 'connected') {
37
+ ValkeyStatus[key] = 'dummy';
38
+ } else {
39
+ ValkeyStatus[key] = 'error';
40
+ }
41
+ logger.warn('Valkey error', { err: err?.message, instance, status: ValkeyStatus[key] });
42
+ });
43
+ client.on?.('end', () => {
44
+ if (ValkeyStatus[key] !== 'dummy') ValkeyStatus[key] = 'error';
45
+ logger.warn('Valkey connection ended', { instance, status: ValkeyStatus[key] });
46
+ });
47
+
48
+ // Probe connectivity with a short timeout
49
+ const probe = async () => {
50
+ try {
51
+ // basic ping via SET/GET roundtrip
52
+ const probeKey = `__vk_probe_${Date.now()}`;
53
+ await client.set(probeKey, '1');
54
+ await client.get(probeKey);
55
+ ValkeyStatus[key] = 'connected';
56
+ } catch (e) {
57
+ ValkeyStatus[key] = 'dummy';
58
+ logger.warn('Valkey probe failed, falling back to dummy', { instance, error: e?.message });
59
+ }
60
+ };
61
+
62
+ // Race with timeout to avoid hanging
63
+ await Promise.race([probe(), new Promise((resolve) => setTimeout(resolve, 1000))]);
64
+
65
+ ValkeyInstances[key] = client;
66
+ if (!ValkeyStatus[key]) ValkeyStatus[key] = 'dummy';
67
+ } catch (err) {
68
+ ValkeyStatus[key] = 'dummy';
69
+ logger.warn('Valkey client creation failed, using dummy', { instance, error: err?.message });
70
+ }
71
+
72
+ return ValkeyInstances[key];
24
73
  };
25
74
 
26
75
  const selectDtoFactory = (payload, select) => {
@@ -33,18 +82,12 @@ const selectDtoFactory = (payload, select) => {
33
82
 
34
83
  const valkeyClientFactory = async (options) => {
35
84
  const valkey = new Valkey({
36
- // port: 6379,
37
- // host: 'valkey-service.default.svc.cluster.local',
38
85
  port: options?.port ? options.port : undefined,
39
86
  host: options?.host ? options.host : undefined,
87
+ // Keep retry strategy minimal; state handled in createValkeyConnection
40
88
  retryStrategy: (attempt) => {
41
- if (attempt === 1) {
42
- valkey.disconnect();
43
- valkeyEnabled = false;
44
- logger.warn('Valkey service not enabled', { ...options, valkeyEnabled });
45
- return;
46
- }
47
- return 1000; // 1 second interval attempt
89
+ if (attempt === 1) return undefined; // stop aggressive retries early
90
+ return 1000; // retry interval if library continues
48
91
  },
49
92
  }); // Connect to 127.0.0.1:6379
50
93
  // new Valkey(6380); // 127.0.0.1:6380
@@ -60,34 +103,52 @@ const valkeyClientFactory = async (options) => {
60
103
  return valkey;
61
104
  };
62
105
 
63
- const getValkeyObject = async (options = { host: '', port: 0 }, key = '') => {
64
- if (!valkeyEnabled) {
65
- logger.warn(disableValkeyErrorMessage + ' get', key);
66
- return null;
67
- }
68
- const object = await ValkeyInstances[`${options.host}${options.path}`].get(key);
106
+ const getValkeyObject = async (options = { host: '', path: '' }, key = '') => {
107
+ const k = _instanceKey(options);
108
+ const status = ValkeyStatus[k];
69
109
  try {
70
- return JSON.parse(object);
71
- } catch (error) {
72
- logger.error(error);
73
- return object;
110
+ if (status === 'connected' && ValkeyInstances[k]) {
111
+ const value = await ValkeyInstances[k].get(key);
112
+ if (value == null) return null;
113
+ try {
114
+ return JSON.parse(value);
115
+ } catch {
116
+ // not JSON, return raw string
117
+ return value;
118
+ }
119
+ }
120
+ } catch (err) {
121
+ logger.warn('Valkey get failed, using dummy', { key, err: err?.message });
74
122
  }
123
+ // Dummy fallback returns stored value as-is (string or object)
124
+ return DummyStores[k]?.get(key) ?? null;
75
125
  };
76
126
 
77
- const setValkeyObject = async (options = { host: '', port: 0 }, key = '', payload = {}) => {
78
- if (!valkeyEnabled) throw new Error(disableValkeyErrorMessage);
79
- return await ValkeyInstances[`${options.host}${options.path}`].set(key, JSON.stringify(payload));
127
+ const setValkeyObject = async (options = { host: '', path: '' }, key = '', payload = {}) => {
128
+ const k = _instanceKey(options);
129
+ const isString = typeof payload === 'string';
130
+ const value = isString ? payload : JSON.stringify(payload);
131
+ try {
132
+ if (ValkeyStatus[k] === 'connected' && ValkeyInstances[k]) {
133
+ return await ValkeyInstances[k].set(key, value);
134
+ }
135
+ } catch (err) {
136
+ logger.warn('Valkey set failed, writing to dummy', { key, err: err?.message });
137
+ }
138
+ if (!DummyStores[k]) DummyStores[k] = new Map();
139
+ // Store raw string or object accordingly
140
+ DummyStores[k].set(key, isString ? payload : payload);
141
+ return 'OK';
80
142
  };
81
143
 
82
- const updateValkeyObject = async (options = { host: '', port: 0 }, key = '', payload = {}) => {
83
- if (!valkeyEnabled) throw new Error(disableValkeyErrorMessage);
84
- const object = await getValkeyObject(key);
85
- object.updatedAt = new Date().toISOString();
86
- return await ValkeyInstances[`${options.host}${options.path}`].set(key, JSON.stringify({ ...object, ...payload }));
144
+ const updateValkeyObject = async (options = { host: '', path: '' }, key = '', payload = {}) => {
145
+ let base = await getValkeyObject(options, key);
146
+ if (typeof base !== 'object' || base === null) base = {};
147
+ base.updatedAt = new Date().toISOString();
148
+ return await setValkeyObject(options, key, { ...base, ...payload });
87
149
  };
88
150
 
89
- const valkeyObjectFactory = async (options = { host: 'localhost', object: {} }, module = '') => {
90
- if (!valkeyEnabled) throw new Error(disableValkeyErrorMessage);
151
+ const valkeyObjectFactory = async (options = { host: 'localhost', object: {} }, model = '') => {
91
152
  const idoDate = new Date().toISOString();
92
153
  options.object = options.object || {};
93
154
  const { object } = options;
@@ -95,7 +156,7 @@ const valkeyObjectFactory = async (options = { host: 'localhost', object: {} },
95
156
  object._id = _id;
96
157
  object.createdAt = idoDate;
97
158
  object.updatedAt = idoDate;
98
- switch (module) {
159
+ switch (model) {
99
160
  case 'user': {
100
161
  const role = 'guest';
101
162
  object._id = `${role}${_id}`;
@@ -115,7 +176,7 @@ const valkeyObjectFactory = async (options = { host: 'localhost', object: {} },
115
176
  };
116
177
  }
117
178
  default:
118
- throw new Error(`module schema not found: ${module}`);
179
+ throw new Error(`model schema not found: ${model}`);
119
180
  }
120
181
  };
121
182