@zintrust/core 0.1.24 → 0.1.26

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 (152) hide show
  1. package/package.json +4 -3
  2. package/src/auth/Auth.d.ts.map +1 -0
  3. package/src/boot/Application.d.ts.map +1 -1
  4. package/src/boot/Application.js +8 -0
  5. package/src/boot/bootstrap.js +34 -15
  6. package/src/cache/drivers/RedisDriver.d.ts.map +1 -1
  7. package/src/cache/drivers/RedisDriver.js +10 -5
  8. package/src/cli/CLI.d.ts.map +1 -1
  9. package/src/cli/CLI.js +6 -0
  10. package/src/cli/commands/QueueCommand.d.ts.map +1 -1
  11. package/src/cli/commands/QueueCommand.js +89 -39
  12. package/src/cli/commands/QueueLockCommand.d.ts +7 -0
  13. package/src/cli/commands/QueueLockCommand.d.ts.map +1 -0
  14. package/src/cli/commands/QueueLockCommand.js +138 -0
  15. package/src/cli/commands/StartCommand.d.ts.map +1 -1
  16. package/src/cli/commands/StartCommand.js +15 -3
  17. package/src/cli/commands/TemplatesCommand.js +1 -1
  18. package/src/cli/commands/WorkerCommands.d.ts.map +1 -1
  19. package/src/cli/commands/WorkerCommands.js +46 -22
  20. package/src/cli/scaffolding/ProjectScaffolder.js +2 -2
  21. package/src/cli/scaffolding/RouteGenerator.d.ts.map +1 -1
  22. package/src/cli/scaffolding/RouteGenerator.js +27 -28
  23. package/src/cli/services/VersionChecker.d.ts +53 -0
  24. package/src/cli/services/VersionChecker.d.ts.map +1 -0
  25. package/src/cli/services/VersionChecker.js +180 -0
  26. package/src/cli/workers/QueueWorkRunner.d.ts.map +1 -1
  27. package/src/cli/workers/QueueWorkRunner.js +128 -7
  28. package/src/common/ExternalServiceUtils.d.ts +2 -2
  29. package/src/config/app.d.ts +4 -0
  30. package/src/config/app.d.ts.map +1 -1
  31. package/src/config/app.js +9 -0
  32. package/src/config/constants.d.ts +140 -10
  33. package/src/config/constants.d.ts.map +1 -1
  34. package/src/config/constants.js +86 -5
  35. package/src/config/index.d.ts +1 -0
  36. package/src/config/index.d.ts.map +1 -1
  37. package/src/config/middleware.d.ts +6 -6
  38. package/src/config/middleware.d.ts.map +1 -1
  39. package/src/config/middleware.js +6 -7
  40. package/src/config/queue.d.ts +4 -0
  41. package/src/config/queue.d.ts.map +1 -1
  42. package/src/config/queue.js +1 -1
  43. package/src/config/redis.d.ts +17 -0
  44. package/src/config/redis.d.ts.map +1 -0
  45. package/src/config/redis.js +54 -0
  46. package/src/config/type.d.ts +3 -0
  47. package/src/config/type.d.ts.map +1 -1
  48. package/src/http/Request.d.ts +10 -1
  49. package/src/http/Request.d.ts.map +1 -1
  50. package/src/http/Request.js +79 -7
  51. package/src/http/error-pages/ErrorPageRenderer.d.ts.map +1 -1
  52. package/src/http/error-pages/ErrorPageRenderer.js +4 -3
  53. package/src/index.d.ts +14 -11
  54. package/src/index.d.ts.map +1 -1
  55. package/src/index.js +18 -11
  56. package/src/lang/lang.d.ts +23 -0
  57. package/src/lang/lang.d.ts.map +1 -0
  58. package/src/lang/lang.js +22 -0
  59. package/src/middleware/ErrorHandlerMiddleware.d.ts.map +1 -1
  60. package/src/middleware/ErrorHandlerMiddleware.js +9 -1
  61. package/src/migrations/schema/SchemaCompiler.js +1 -1
  62. package/src/migrations/schema/types.d.ts +1 -1
  63. package/src/migrations/schema/types.d.ts.map +1 -1
  64. package/src/node.d.ts +1 -1
  65. package/src/node.d.ts.map +1 -1
  66. package/src/node.js +1 -1
  67. package/src/orm/Database.d.ts +1 -1
  68. package/src/orm/Database.d.ts.map +1 -1
  69. package/src/orm/Database.js +22 -3
  70. package/src/performance/Optimizer.js +1 -1
  71. package/src/routing/Router.d.ts +6 -2
  72. package/src/routing/Router.d.ts.map +1 -1
  73. package/src/routing/Router.js +19 -4
  74. package/src/runtime/PluginManager.js +1 -1
  75. package/src/runtime/PluginRegistry.js +2 -2
  76. package/src/start.d.ts.map +1 -1
  77. package/src/start.js +8 -7
  78. package/src/templates/TemplateRegistry.js +2 -2
  79. package/src/templates/TemplateRegistry.ts +2 -2
  80. package/src/templates/feature/Queue.ts.tpl +114 -0
  81. package/src/templates/project/basic/app/Controllers/UserController.ts.tpl +22 -0
  82. package/src/templates/project/basic/config/queue.ts.tpl +19 -0
  83. package/src/templates/project/basic/package.json.tpl +2 -1
  84. package/src/templates/project/basic/src/index.ts.tpl +0 -3
  85. package/src/tools/broadcast/drivers/Redis.d.ts.map +1 -1
  86. package/src/tools/broadcast/drivers/Redis.js +8 -56
  87. package/src/tools/mail/Mail.d.ts +1 -29
  88. package/src/tools/mail/Mail.d.ts.map +1 -1
  89. package/src/tools/mail/Mail.js +1 -111
  90. package/src/tools/mail/drivers/SendGrid.d.ts.map +1 -1
  91. package/src/tools/mail/drivers/SendGrid.js +4 -3
  92. package/src/tools/mail/drivers/Smtp.d.ts.map +1 -1
  93. package/src/tools/mail/drivers/Smtp.js +32 -10
  94. package/src/tools/mail/index.d.ts +40 -0
  95. package/src/tools/mail/index.d.ts.map +1 -0
  96. package/src/tools/mail/index.js +129 -0
  97. package/src/tools/mail/template-loader.d.ts +10 -0
  98. package/src/tools/mail/template-loader.d.ts.map +1 -0
  99. package/src/tools/mail/template-loader.js +101 -0
  100. package/src/tools/mail/template-utils.d.ts +10 -0
  101. package/src/tools/mail/template-utils.d.ts.map +1 -0
  102. package/src/tools/mail/template-utils.js +16 -0
  103. package/src/tools/mail/templates/index.d.ts +30 -0
  104. package/src/tools/mail/templates/index.d.ts.map +1 -1
  105. package/src/tools/mail/templates/index.js +69 -0
  106. package/src/tools/queue/AdvancedQueue.d.ts +19 -0
  107. package/src/tools/queue/AdvancedQueue.d.ts.map +1 -0
  108. package/src/tools/queue/AdvancedQueue.js +352 -0
  109. package/src/tools/queue/DeduplicationBuilder.d.ts +20 -0
  110. package/src/tools/queue/DeduplicationBuilder.d.ts.map +1 -0
  111. package/src/tools/queue/DeduplicationBuilder.js +77 -0
  112. package/src/tools/queue/LockProvider.d.ts +22 -0
  113. package/src/tools/queue/LockProvider.d.ts.map +1 -0
  114. package/src/tools/queue/LockProvider.js +282 -0
  115. package/src/tools/queue/Queue.d.ts.map +1 -1
  116. package/src/tools/queue/Queue.js +2 -1
  117. package/src/tools/queue/QueueExtensions.d.ts +46 -0
  118. package/src/tools/queue/QueueExtensions.d.ts.map +1 -0
  119. package/src/tools/queue/QueueExtensions.js +129 -0
  120. package/src/tools/queue/QueueRuntimeRegistration.d.ts.map +1 -1
  121. package/src/tools/queue/QueueRuntimeRegistration.js +2 -2
  122. package/src/tools/queue/drivers/Database.d.ts +23 -0
  123. package/src/tools/queue/drivers/Database.d.ts.map +1 -0
  124. package/src/tools/queue/drivers/Database.js +123 -0
  125. package/src/tools/queue/drivers/Redis.d.ts.map +1 -1
  126. package/src/tools/queue/drivers/Redis.js +11 -82
  127. package/src/tools/queue/index.d.ts +9 -0
  128. package/src/tools/queue/index.d.ts.map +1 -0
  129. package/src/tools/queue/index.js +7 -0
  130. package/src/tools/redis/RedisKeyManager.d.ts +64 -0
  131. package/src/tools/redis/RedisKeyManager.d.ts.map +1 -0
  132. package/src/tools/redis/RedisKeyManager.js +124 -0
  133. package/src/types/Queue.d.ts +62 -0
  134. package/src/types/Queue.d.ts.map +1 -0
  135. package/src/types/Queue.js +5 -0
  136. package/src/features/Auth.d.ts.map +0 -1
  137. package/src/features/Queue.d.ts +0 -21
  138. package/src/features/Queue.d.ts.map +0 -1
  139. package/src/features/Queue.js +0 -33
  140. package/src/templates/features/Queue.ts.tpl +0 -47
  141. package/src/tools/mail/templates/markdown/index.d.ts +0 -17
  142. package/src/tools/mail/templates/markdown/index.d.ts.map +0 -1
  143. package/src/tools/mail/templates/markdown/index.js +0 -49
  144. package/src/tools/mail/templates/markdown/registry.d.ts +0 -15
  145. package/src/tools/mail/templates/markdown/registry.d.ts.map +0 -1
  146. package/src/tools/mail/templates/markdown/registry.js +0 -34
  147. package/src/tools/mail/templates/markdown/validator.d.ts +0 -16
  148. package/src/tools/mail/templates/markdown/validator.d.ts.map +0 -1
  149. package/src/tools/mail/templates/markdown/validator.js +0 -24
  150. /package/src/{features → auth}/Auth.d.ts +0 -0
  151. /package/src/{features → auth}/Auth.js +0 -0
  152. /package/src/templates/{features → auth}/Auth.ts.tpl +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zintrust/core",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
4
4
  "description": "Production-grade TypeScript backend framework for JavaScript",
5
5
  "homepage": "https://zintrust.com",
6
6
  "repository": {
@@ -36,13 +36,14 @@
36
36
  "bcrypt": "^6.0.0",
37
37
  "chalk": "^5.6.2",
38
38
  "commander": "^14.0.2",
39
- "inquirer": "^13.2.0",
39
+ "inquirer": "^13.2.1",
40
40
  "jsonwebtoken": "^9.0.3"
41
41
  },
42
42
  "overrides": {
43
43
  "node-forge": "1.3.3",
44
44
  "cross-spawn": "^7.0.5",
45
- "glob": "^11.1.0"
45
+ "glob": "^11.1.0",
46
+ "undici": "^6.23.0"
46
47
  },
47
48
  "bin": {
48
49
  "zintrust": "bin/zintrust.js",
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Auth.d.ts","sourceRoot":"","sources":["../../../src/auth/Auth.ts"],"names":[],"mappings":"AAEA,OAAY,EAAE,KAAK,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAIlE,eAAO,MAAM,IAAI;IACf;;OAEG;mBACkB,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAK7C;;OAEG;sBACqB,MAAM,QAAQ,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI/D;;OAEG;2BAEQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,UACxB,MAAM,cACH,WAAW,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,GAC/C,MAAM;IAKT;;OAEG;gBACS,CAAC,SAAS,MAAM,UAAU,MAAM,GAAG,CAAC;EAGhD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"Application.d.ts","sourceRoot":"","sources":["../../../src/boot/Application.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAGtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAErE,OAAO,EAAE,KAAK,OAAO,EAAU,MAAM,kBAAkB,CAAC;AAUxD,MAAM,WAAW,YAAY;IAC3B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,QAAQ,IAAI,OAAO,CAAC;IACpB,aAAa,IAAI,OAAO,CAAC;IACzB,YAAY,IAAI,OAAO,CAAC;IACxB,SAAS,IAAI,OAAO,CAAC;IACrB,cAAc,IAAI,MAAM,CAAC;IACzB,SAAS,IAAI,OAAO,CAAC;IACrB,YAAY,IAAI,iBAAiB,CAAC;IAClC,kBAAkB,IAAI,gBAAgB,CAAC;IACvC,WAAW,IAAI,MAAM,CAAC;CACvB;AA2YD;;GAEG;AACH,eAAO,MAAM,WAAW;IACtB;;OAEG;sBACe,MAAM,GAAG,YAAY;EA0CvC,CAAC;AAEH,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"Application.d.ts","sourceRoot":"","sources":["../../../src/boot/Application.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAGtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAErE,OAAO,EAAE,KAAK,OAAO,EAAU,MAAM,kBAAkB,CAAC;AAUxD,MAAM,WAAW,YAAY;IAC3B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,QAAQ,IAAI,OAAO,CAAC;IACpB,aAAa,IAAI,OAAO,CAAC;IACzB,YAAY,IAAI,OAAO,CAAC;IACxB,SAAS,IAAI,OAAO,CAAC;IACrB,cAAc,IAAI,MAAM,CAAC;IACzB,SAAS,IAAI,OAAO,CAAC;IACrB,YAAY,IAAI,iBAAiB,CAAC;IAClC,kBAAkB,IAAI,gBAAgB,CAAC;IACvC,WAAW,IAAI,MAAM,CAAC;CACvB;AAqZD;;GAEG;AACH,eAAO,MAAM,WAAW;IACtB;;OAEG;sBACe,MAAM,GAAG,YAAY;EA0CvC,CAAC;AAEH,eAAe,WAAW,CAAC"}
@@ -291,6 +291,14 @@ const createLifecycle = (params) => {
291
291
  catch (error) {
292
292
  Logger.error('Shutdown hook failed:', error);
293
293
  }
294
+ // Ensure FileLogWriter.flush is attempted even if dynamic registration failed.
295
+ try {
296
+ const fileLogWriter = await tryImportOptional('@config/FileLogWriter');
297
+ fileLogWriter?.FileLogWriter?.flush?.();
298
+ }
299
+ catch {
300
+ /* best-effort */
301
+ }
294
302
  params.setBooted(false);
295
303
  Logger.info('✅ Application shut down successfully');
296
304
  };
@@ -87,8 +87,11 @@ const gracefulShutdown = async (signal) => {
87
87
  if (isShuttingDown)
88
88
  return;
89
89
  isShuttingDown = true;
90
- const timeoutMs = Env.getInt('SHUTDOWN_TIMEOUT', 1500);
91
- const forceExitMs = Env.getInt('SHUTDOWN_FORCE_EXIT_MS', 2500);
90
+ const shutdownBudgetMs = Env.getInt('SHUTDOWN_TIMEOUT', 1500);
91
+ const minForceExitMs = shutdownBudgetMs + 250;
92
+ const forceExitMs = Math.max(Env.getInt('SHUTDOWN_FORCE_EXIT_MS', 10000), minForceExitMs);
93
+ const deadlineMs = Date.now() + shutdownBudgetMs;
94
+ const remainingMs = () => Math.max(0, deadlineMs - Date.now());
92
95
  Logger.info(`${signal} received, shutting down gracefully...`);
93
96
  try {
94
97
  const forceExitTimer = globalThis.setTimeout(() => {
@@ -97,24 +100,30 @@ const gracefulShutdown = async (signal) => {
97
100
  // Best-effort: don't keep the process alive just for this timer
98
101
  forceExitTimer.unref?.();
99
102
  await withTimeout((async () => {
103
+ // Shutdown worker management system FIRST (before database closes)
104
+ if (appConfig.detectRuntime() === 'nodejs' || appConfig.detectRuntime() === 'lambda') {
105
+ try {
106
+ const { WorkerShutdown } = await import('../../packages/workers/src/index.js');
107
+ const workerBudgetMs = Math.min(15000, remainingMs());
108
+ await withTimeout(WorkerShutdown.shutdown({ signal, timeout: workerBudgetMs, forceExit: false }), workerBudgetMs, 'Worker shutdown timed out');
109
+ }
110
+ catch (error) {
111
+ Logger.warn('Worker shutdown failed (continuing with app shutdown)', error);
112
+ }
113
+ }
100
114
  if (serverInstance !== undefined) {
101
115
  await serverInstance.close();
102
116
  }
103
117
  if (appInstance !== undefined) {
104
- await appInstance.shutdown();
105
- }
106
- })(), timeoutMs, 'Graceful shutdown timed out');
107
- // Shutdown worker management system first
108
- if (appConfig.detectRuntime() === 'nodejs' || appConfig.detectRuntime() === 'lambda') {
109
- try {
110
- const { WorkerShutdown } = await import('../../packages/workers/src/index.js');
111
- await withTimeout(WorkerShutdown.shutdown({ signal, timeout: 30000, forceExit: false }), timeoutMs, 'Worker shutdown timed out');
112
- Logger.info('Worker management system shutdown complete');
113
- }
114
- catch (error) {
115
- Logger.warn('Worker shutdown failed (continuing with app shutdown)', error);
118
+ try {
119
+ const appBudgetMs = Math.min(5000, remainingMs());
120
+ await withTimeout(appInstance.shutdown(), appBudgetMs, 'App shutdown timed out');
121
+ }
122
+ catch (error) {
123
+ Logger.warn('App shutdown failed or timed out, forcing exit', error);
124
+ }
116
125
  }
117
- }
126
+ })(), shutdownBudgetMs, 'Graceful shutdown timed out');
118
127
  globalThis.clearTimeout(forceExitTimer);
119
128
  process.exit(0);
120
129
  }
@@ -155,6 +164,16 @@ const BootstrapFunctions = Object.freeze({
155
164
  */
156
165
  async start() {
157
166
  try {
167
+ // Ensure project-installed adapters/drivers are registered for web server.
168
+ // (This is driven by src/zintrust.plugins.ts generated by `zin plugin install`.)
169
+ try {
170
+ const { PluginAutoImports } = await import('../runtime/PluginAutoImports.js');
171
+ await PluginAutoImports.tryImportProjectAutoImports();
172
+ }
173
+ catch (error) {
174
+ // best-effort; run without plugins if loader fails (e.g. non-Node runtime)
175
+ Logger.debug('Plugin auto-imports loader skipped:', error);
176
+ }
158
177
  // Create application instance
159
178
  // if (Env.ZINTRUST_PROJECT_ROOT) {
160
179
  // }
@@ -1 +1 @@
1
- {"version":3,"file":"RedisDriver.d.ts","sourceRoot":"","sources":["../../../../src/cache/drivers/RedisDriver.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AA+EtD;;GAEG;AACH,eAAO,MAAM,WAAW;kBA1EL,WAAW;EA4E5B,CAAC"}
1
+ {"version":3,"file":"RedisDriver.d.ts","sourceRoot":"","sources":["../../../../src/cache/drivers/RedisDriver.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAoFtD;;GAEG;AACH,eAAO,MAAM,WAAW;kBA9EL,WAAW;EAgF5B,CAAC"}
@@ -5,6 +5,7 @@
5
5
  import { Env } from '../../config/env.js';
6
6
  import { Logger } from '../../config/logger.js';
7
7
  import * as net from '../../node-singletons/net.js';
8
+ import { createCacheKey } from '../../tools/redis/RedisKeyManager.js';
8
9
  /**
9
10
  * Create a new Redis driver instance
10
11
  */
@@ -38,7 +39,8 @@ const create = () => {
38
39
  return {
39
40
  async get(key) {
40
41
  try {
41
- const response = await sendCommand(`GET ${key}\r\n`);
42
+ const prefixedKey = createCacheKey(key);
43
+ const response = await sendCommand(`GET ${prefixedKey}\r\n`);
42
44
  if (response.startsWith('$-1'))
43
45
  return null;
44
46
  // Basic RESP parsing
@@ -52,21 +54,24 @@ const create = () => {
52
54
  }
53
55
  },
54
56
  async set(key, value, ttl) {
57
+ const prefixedKey = createCacheKey(key);
55
58
  const jsonValue = JSON.stringify(value);
56
- let command = `SET ${key} ${jsonValue}\r\n`;
59
+ let command = `SET ${prefixedKey} ${jsonValue}\r\n`;
57
60
  if (ttl !== undefined) {
58
- command = `SETEX ${key} ${ttl} ${jsonValue}\r\n`;
61
+ command = `SETEX ${prefixedKey} ${ttl} ${jsonValue}\r\n`;
59
62
  }
60
63
  await sendCommand(command);
61
64
  },
62
65
  async delete(key) {
63
- await sendCommand(`DEL ${key}\r\n`);
66
+ const prefixedKey = createCacheKey(key);
67
+ await sendCommand(`DEL ${prefixedKey}\r\n`);
64
68
  },
65
69
  async clear() {
66
70
  await sendCommand(`FLUSHDB\r\n`);
67
71
  },
68
72
  async has(key) {
69
- const response = await sendCommand(`EXISTS ${key}\r\n`);
73
+ const prefixedKey = createCacheKey(key);
74
+ const response = await sendCommand(`EXISTS ${prefixedKey}\r\n`);
70
75
  return response.includes(':1');
71
76
  },
72
77
  };
@@ -1 +1 @@
1
- {"version":3,"file":"CLI.d.ts","sourceRoot":"","sources":["../../../src/cli/CLI.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAyCH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,MAAM,WAAW,IAAI;IACnB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,UAAU,IAAI,OAAO,CAAC;CACvB;AAqMD;;;;;;;GAOG;AACH,eAAO,MAAM,GAAG;cACJ,IAAI;EAed,CAAC"}
1
+ {"version":3,"file":"CLI.d.ts","sourceRoot":"","sources":["../../../src/cli/CLI.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA0CH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,MAAM,WAAW,IAAI;IACnB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,UAAU,IAAI,OAAO,CAAC;CACvB;AA2MD;;;;;;;GAOG;AACH,eAAO,MAAM,GAAG;cACJ,IAAI;EAed,CAAC"}
package/src/cli/CLI.js CHANGED
@@ -32,6 +32,7 @@ import { TemplatesCommand } from './commands/TemplatesCommand.js';
32
32
  import { UpgradeCommand } from './commands/UpgradeCommand.js';
33
33
  import { WorkerCommands } from './commands/WorkerCommands.js';
34
34
  import { ErrorHandler } from './ErrorHandler.js';
35
+ import { VersionChecker } from './services/VersionChecker.js';
35
36
  import { esmDirname } from '../common/index.js';
36
37
  import { Logger } from '../config/logger.js';
37
38
  import { ErrorFactory } from '../exceptions/ZintrustError.js';
@@ -194,6 +195,11 @@ const runCLI = async (program, version, args) => {
194
195
  }
195
196
  // Always show banner for normal commands
196
197
  ErrorHandler.banner(version);
198
+ // Run version check in background (non-blocking)
199
+ VersionChecker.runVersionCheck().catch((error) => {
200
+ // Version check should never crash the CLI
201
+ Logger.debug('Version check encountered an error', error);
202
+ });
197
203
  // Show help if no arguments provided
198
204
  if (args.length === 0) {
199
205
  program.help();
@@ -1 +1 @@
1
- {"version":3,"file":"QueueCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/QueueCommand.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAYvF,eAAO,MAAM,YAAY;cACb,YAAY;EA0EtB,CAAC;AAEH,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"QueueCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/QueueCommand.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAqHvF,eAAO,MAAM,YAAY;cACb,YAAY;EAmBtB,CAAC;AAEH,eAAe,YAAY,CAAC"}
@@ -3,8 +3,93 @@
3
3
  * Run queued jobs via the framework CLI.
4
4
  */
5
5
  import { BaseCommand } from '../BaseCommand.js';
6
+ import { setupQueueLockCommands } from '../commands/QueueLockCommand.js';
6
7
  import { QueueWorkCommandUtils } from '../commands/QueueWorkCommandUtils.js';
7
8
  import { QueueWorkRunner } from '../workers/QueueWorkRunner.js';
9
+ /**
10
+ * Setup work subcommand with explicit kind
11
+ */
12
+ const setupWorkCommand = (command) => {
13
+ command
14
+ .command('work <kind> <queueName>')
15
+ .alias('w')
16
+ .description('Work a queue with explicit kind (broadcast|notification)')
17
+ .option('--timeout <seconds>', 'Stop after this many seconds (default: 10)')
18
+ .option('--retry <count>', 'Retries after first attempt (default: 3)')
19
+ .option('--max-items <count>', 'Max items to process in one run (default: 1000)')
20
+ .option('--driver <name>', 'Queue driver name (default: from QUEUE_DRIVER)')
21
+ .action(async (kindRaw, queueName, subOptions) => {
22
+ const kind = QueueWorkRunner.parseKind(kindRaw);
23
+ const timeoutSeconds = QueueWorkCommandUtils.parsePositiveInt(subOptions['timeout'], '--timeout');
24
+ const retry = QueueWorkCommandUtils.parseNonNegativeInt(subOptions['retry'], '--retry');
25
+ const maxItems = QueueWorkCommandUtils.parsePositiveInt(subOptions['maxItems'], '--max-items');
26
+ const driverName = QueueWorkCommandUtils.normalizeDriverName(subOptions['driver']);
27
+ const result = await QueueWorkRunner.run({
28
+ kind,
29
+ queueName,
30
+ timeoutSeconds,
31
+ retry,
32
+ maxItems,
33
+ driverName,
34
+ });
35
+ QueueWorkCommandUtils.logSummary(queueName, kind, result);
36
+ });
37
+ };
38
+ /**
39
+ * Setup prune subcommand
40
+ */
41
+ const setupPruneCommand = (command) => {
42
+ command
43
+ .command('prune')
44
+ .description('Prune failed jobs from the database queue')
45
+ .option('--hours <count>', 'Prune jobs older than this many hours (default: 168)', '168')
46
+ .action(async (options) => {
47
+ const { Logger } = await import('../../config/logger.js');
48
+ const { QueryBuilder } = await import('../../orm/QueryBuilder.js');
49
+ const { useEnsureDbConnected } = await import('../../orm/Database.js');
50
+ const { databaseConfig } = await import('../../config/database.js');
51
+ const hours = Number.parseInt(options.hours ?? '168', 10);
52
+ const cutoff = new Date(Date.now() - hours * 60 * 60 * 1000);
53
+ Logger.info(`[Queue] Pruning failed jobs older than ${cutoff.toISOString()}...`);
54
+ try {
55
+ // Resolved connection config (Queue prune usually runs on default DB)
56
+ const config = databaseConfig.getConnection();
57
+ const db = await useEnsureDbConnected(config);
58
+ const deleted = await QueryBuilder.create('queue_jobs_failed', db)
59
+ .where('failed_at', '<', cutoff)
60
+ .delete();
61
+ Logger.info(`[Queue] Pruned ${deleted} failed jobs.`);
62
+ }
63
+ catch (err) {
64
+ const msg = err instanceof Error ? err.message : String(err);
65
+ if (msg.includes('no such table')) {
66
+ Logger.warn('[Queue] Table queue_jobs_failed not found. Skipping prune.');
67
+ }
68
+ else {
69
+ Logger.error('[Queue] Prune failed', err);
70
+ process.exit(1);
71
+ }
72
+ }
73
+ });
74
+ };
75
+ /**
76
+ * Parse command options and run queue work
77
+ */
78
+ const executeQueueWork = async (options) => {
79
+ const queueName = QueueWorkCommandUtils.requireQueueNameFromArgs(options.args, 'zin queue --help');
80
+ const timeoutSeconds = QueueWorkCommandUtils.parsePositiveInt(options.timeout, '--timeout');
81
+ const retry = QueueWorkCommandUtils.parseNonNegativeInt(options.retry, '--retry');
82
+ const maxItems = QueueWorkCommandUtils.parsePositiveInt(options.maxItems, '--max-items');
83
+ const driverName = QueueWorkCommandUtils.normalizeDriverName(options.driver);
84
+ const result = await QueueWorkRunner.run({
85
+ queueName,
86
+ timeoutSeconds,
87
+ retry,
88
+ maxItems,
89
+ driverName,
90
+ });
91
+ QueueWorkCommandUtils.logSummary(queueName, 'auto', result);
92
+ };
8
93
  export const QueueCommand = Object.freeze({
9
94
  create() {
10
95
  return BaseCommand.create({
@@ -17,46 +102,11 @@ export const QueueCommand = Object.freeze({
17
102
  .option('--retry <count>', 'Retries after first attempt (default: 3)')
18
103
  .option('--max-items <count>', 'Max items to process in one run (default: 1000)')
19
104
  .option('--driver <name>', 'Queue driver name (default: from QUEUE_DRIVER)');
20
- command
21
- .command('work <kind> <queueName>')
22
- .alias('w')
23
- .description('Work a queue with explicit kind (broadcast|notification)')
24
- .option('--timeout <seconds>', 'Stop after this many seconds (default: 10)')
25
- .option('--retry <count>', 'Retries after first attempt (default: 3)')
26
- .option('--max-items <count>', 'Max items to process in one run (default: 1000)')
27
- .option('--driver <name>', 'Queue driver name (default: from QUEUE_DRIVER)')
28
- .action(async (kindRaw, queueName, subOptions) => {
29
- const kind = QueueWorkRunner.parseKind(kindRaw);
30
- const timeoutSeconds = QueueWorkCommandUtils.parsePositiveInt(subOptions['timeout'], '--timeout');
31
- const retry = QueueWorkCommandUtils.parseNonNegativeInt(subOptions['retry'], '--retry');
32
- const maxItems = QueueWorkCommandUtils.parsePositiveInt(subOptions['maxItems'], '--max-items');
33
- const driverName = QueueWorkCommandUtils.normalizeDriverName(subOptions['driver']);
34
- const result = await QueueWorkRunner.run({
35
- kind,
36
- queueName,
37
- timeoutSeconds,
38
- retry,
39
- maxItems,
40
- driverName,
41
- });
42
- QueueWorkCommandUtils.logSummary(queueName, kind, result);
43
- });
44
- },
45
- execute: async (options) => {
46
- const queueName = QueueWorkCommandUtils.requireQueueNameFromArgs(options.args, 'zin queue --help');
47
- const timeoutSeconds = QueueWorkCommandUtils.parsePositiveInt(options.timeout, '--timeout');
48
- const retry = QueueWorkCommandUtils.parseNonNegativeInt(options.retry, '--retry');
49
- const maxItems = QueueWorkCommandUtils.parsePositiveInt(options.maxItems, '--max-items');
50
- const driverName = QueueWorkCommandUtils.normalizeDriverName(options.driver);
51
- const result = await QueueWorkRunner.run({
52
- queueName,
53
- timeoutSeconds,
54
- retry,
55
- maxItems,
56
- driverName,
57
- });
58
- QueueWorkCommandUtils.logSummary(queueName, 'auto', result);
105
+ setupWorkCommand(command);
106
+ setupPruneCommand(command);
107
+ setupQueueLockCommands(command);
59
108
  },
109
+ execute: executeQueueWork,
60
110
  });
61
111
  },
62
112
  });
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Queue Lock Command Extensions
3
+ * Implements CLI commands for managing queue locks and deduplication
4
+ */
5
+ import type { Command } from 'commander';
6
+ export declare function setupQueueLockCommands(command: Command): void;
7
+ //# sourceMappingURL=QueueLockCommand.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"QueueLockCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/QueueLockCommand.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA2IzC,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAK7D"}
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Queue Lock Command Extensions
3
+ * Implements CLI commands for managing queue locks and deduplication
4
+ */
5
+ import { ErrorFactory } from '../../exceptions/ZintrustError.js';
6
+ import { ZintrustLang } from '../../lang/lang.js';
7
+ import { Logger } from '../../config/logger.js';
8
+ import { createAdvancedQueue } from '../../tools/queue/AdvancedQueue.js';
9
+ import { getLockProvider } from '../../tools/queue/LockProvider.js';
10
+ // Reusing initialization logic from AdvancedQueue
11
+ // But we need direct access to lock provider for maintenance
12
+ // AdvancedQueue only exposes limited interface
13
+ function getProvider(providerName = 'redis') {
14
+ // Create proper QueueConfig object that matches the expected interface
15
+ const advancedQueueConfig = {
16
+ name: ZintrustLang.CLI_LOCKS,
17
+ connection: undefined, // No specific connection needed for CLI operations
18
+ defaultDedupTtl: ZintrustLang.ZINTRUST_LOCKS_TTL,
19
+ lockProvider: providerName,
20
+ };
21
+ // Need to ensure provider is registered. creating AdvancedQueue triggers registration.
22
+ createAdvancedQueue(advancedQueueConfig);
23
+ const provider = getLockProvider(providerName);
24
+ if (!provider) {
25
+ throw ErrorFactory.createCliError(`Lock provider '${providerName}' not found.`);
26
+ }
27
+ return provider;
28
+ }
29
+ const setupLockListCommand = (command) => {
30
+ command
31
+ .command('lock:list')
32
+ .description('List active deduplication locks')
33
+ .option('--pattern <pattern>', 'Key pattern to match (default: *)', '*')
34
+ .option('--provider <name>', 'Lock provider name (default: redis)', 'redis')
35
+ .action(async (options) => {
36
+ try {
37
+ const provider = getProvider(options.provider);
38
+ const locks = await provider.list(options?.pattern ?? '*');
39
+ if (locks.length === 0) {
40
+ Logger.info('No locks found.');
41
+ return;
42
+ }
43
+ Logger.info(`Found ${locks.length} locks:`);
44
+ // Batch all status checks for better performance
45
+ const statusPromises = locks.map(async (key) => {
46
+ const status = await provider.status(key);
47
+ const expires = status.expires ? status.expires.toISOString() : 'never';
48
+ const ttl = typeof status.ttl === 'number' ? `${Math.round(status.ttl / 1000)}s` : 'unknown';
49
+ return { key, ttl, expires };
50
+ });
51
+ const lockStatuses = await Promise.all(statusPromises);
52
+ for (const { key, ttl, expires } of lockStatuses) {
53
+ Logger.info(`- [${key}] (TTL: ${ttl}, Expires: ${expires})`);
54
+ }
55
+ }
56
+ catch (error) {
57
+ Logger.error('Failed to list locks', error);
58
+ }
59
+ });
60
+ };
61
+ const setupLockReleaseCommand = (command) => {
62
+ command
63
+ .command('lock:release <key>')
64
+ .description('Manually release a deduplication lock')
65
+ .option('--provider <name>', 'Lock provider name (default: redis)', 'redis')
66
+ .action(async (key, options) => {
67
+ try {
68
+ const provider = getProvider(options.provider);
69
+ const status = await provider.status(key);
70
+ if (!status.exists) {
71
+ Logger.info(`Lock '${key}' does not exist.`);
72
+ return;
73
+ }
74
+ await provider.release({ key, ttl: 0, acquired: true, expires: new Date() });
75
+ Logger.info(`Lock '${key}' released successfully.`);
76
+ }
77
+ catch (error) {
78
+ Logger.error(`Failed to release lock ${key}`, error);
79
+ }
80
+ });
81
+ };
82
+ const setupLockExtendCommand = (command) => {
83
+ command
84
+ .command('lock:extend <key> <seconds>')
85
+ .description('Extend TTL of an existing lock')
86
+ .option('--provider <name>', 'Lock provider name (default: redis)', 'redis')
87
+ .action(async (key, seconds, options) => {
88
+ try {
89
+ const ttl = Number.parseInt(seconds, 10) * 1000;
90
+ const provider = getProvider(options.provider);
91
+ // Mock lock object for extension
92
+ const lock = { key, ttl: 0, acquired: true, expires: new Date() };
93
+ const extended = await provider.extend(lock, ttl);
94
+ if (extended) {
95
+ Logger.info(`Lock '${key}' extended by ${seconds} seconds.`);
96
+ }
97
+ else {
98
+ Logger.info(`Failed to extend lock '${key}' (may not exist).`);
99
+ }
100
+ }
101
+ catch (error) {
102
+ Logger.error(`Failed to extend lock ${key}`, error);
103
+ }
104
+ });
105
+ };
106
+ const setupDedupeStatusCommand = (command) => {
107
+ command
108
+ .command('dedupe:status <id>')
109
+ .description('Check deduplication status of a job ID')
110
+ .option('--provider <name>', 'Lock provider name (default: redis)', 'redis')
111
+ .action(async (id, options) => {
112
+ try {
113
+ const provider = getProvider(options.provider);
114
+ const status = await provider.status(id);
115
+ if (status.exists) {
116
+ Logger.info(`Job ID '${id}' is currently LOCKED (Deduplicated).`);
117
+ const ttlMs = status.ttl;
118
+ const ttlSeconds = ttlMs !== null && ttlMs !== undefined && !Number.isNaN(ttlMs) && ttlMs > 0
119
+ ? Math.round(ttlMs / 1000) + 's'
120
+ : 'unknown';
121
+ Logger.info(`TTL Remaining: ${ttlSeconds}`);
122
+ Logger.info(`Expires At: ${status.expires ? status.expires.toISOString() : 'unknown'}`);
123
+ }
124
+ else {
125
+ Logger.info(`Job ID '${id}' is NOT locked (Ready for processing or expired).`);
126
+ }
127
+ }
128
+ catch (error) {
129
+ Logger.error(`Failed to check status for ${id}`, error);
130
+ }
131
+ });
132
+ };
133
+ export function setupQueueLockCommands(command) {
134
+ setupLockListCommand(command);
135
+ setupLockReleaseCommand(command);
136
+ setupLockExtendCommand(command);
137
+ setupDedupeStatusCommand(command);
138
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"StartCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/StartCommand.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAyZvF,eAAO,MAAM,YAAY;cACb,YAAY;EAyBtB,CAAC"}
1
+ {"version":3,"file":"StartCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/StartCommand.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAqavF,eAAO,MAAM,YAAY;cACb,YAAY;EAyBtB,CAAC"}
@@ -246,18 +246,30 @@ const executeNodeStart = async (cmd, cwd, mode, watchEnabled) => {
246
246
  cmd.warn('Watch mode disabled; starting once.');
247
247
  const bootstrap = resolveBootstrapEntryTs(cwd);
248
248
  const args = bootstrap === undefined ? ['src/index.ts'] : [bootstrap];
249
- const exitCode = await SpawnUtil.spawnAndWait({ command: 'tsx', args });
249
+ const exitCode = await SpawnUtil.spawnAndWait({
250
+ command: 'tsx',
251
+ args,
252
+ forwardSignals: false,
253
+ });
250
254
  process.exit(exitCode);
251
255
  }
252
256
  const packageJson = readPackageJson(cwd);
253
257
  const dev = resolveNodeDevCommand(cwd, packageJson);
254
258
  cmd.info('Starting in development mode (watch enabled)...');
255
- const exitCode = await SpawnUtil.spawnAndWait({ command: dev.command, args: dev.args });
259
+ const exitCode = await SpawnUtil.spawnAndWait({
260
+ command: dev.command,
261
+ args: dev.args,
262
+ forwardSignals: false,
263
+ });
256
264
  process.exit(exitCode);
257
265
  }
258
266
  const prod = resolveNodeProdCommand(cwd);
259
267
  cmd.info('Starting in production mode...');
260
- const exitCode = await SpawnUtil.spawnAndWait({ command: prod.command, args: prod.args });
268
+ const exitCode = await SpawnUtil.spawnAndWait({
269
+ command: prod.command,
270
+ args: prod.args,
271
+ forwardSignals: false,
272
+ });
261
273
  process.exit(exitCode);
262
274
  };
263
275
  const executeStart = async (options, cmd) => {
@@ -1,7 +1,7 @@
1
1
  import { BaseCommand } from '../BaseCommand.js';
2
2
  import { ErrorHandler } from '../ErrorHandler.js';
3
3
  import { ErrorFactory } from '../../exceptions/ZintrustError.js';
4
- import { listTemplates as listMail, renderTemplate as renderMail } from '../../tools/mail/templates/markdown/index.js';
4
+ import { listTemplates as listMail, renderTemplate as renderMail } from '../../tools/mail/templates/index.js';
5
5
  import { listTemplates as listNotification, renderTemplate as renderNotification, } from '../../tools/notification/templates/markdown/index.js';
6
6
  const listForScope = (scope) => {
7
7
  let items = [];
@@ -1 +1 @@
1
- {"version":3,"file":"WorkerCommands.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/WorkerCommands.ts"],"names":[],"mappings":"AACA;;;GAGG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AA0UrD;;GAEG;AACH,eAAO,MAAM,cAAc;mCAhPS,YAAY;qCA2CV,YAAY;oCAiDb,YAAY;mCA+Bb,YAAY;sCAgCT,YAAY;sCAgCZ,YAAY;CA4DlD,CAAC"}
1
+ {"version":3,"file":"WorkerCommands.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/WorkerCommands.ts"],"names":[],"mappings":"AACA;;;GAGG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAmWrD;;GAEG;AACH,eAAO,MAAM,cAAc;mCAjPS,YAAY;qCA4CV,YAAY;oCAiDb,YAAY;mCA+Bb,YAAY;sCAgCT,YAAY;sCAgCZ,YAAY;CA4DlD,CAAC"}