@zintrust/core 0.1.23 → 0.1.25

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 (172) 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/DbSeedCommand.d.ts.map +1 -1
  11. package/src/cli/commands/DbSeedCommand.js +6 -38
  12. package/src/cli/commands/MigrateCommand.d.ts.map +1 -1
  13. package/src/cli/commands/MigrateCommand.js +12 -55
  14. package/src/cli/commands/MigrateWorkerCommand.d.ts.map +1 -1
  15. package/src/cli/commands/MigrateWorkerCommand.js +8 -54
  16. package/src/cli/commands/NewCommand.d.ts.map +1 -1
  17. package/src/cli/commands/NewCommand.js +1 -13
  18. package/src/cli/commands/QueueCommand.d.ts.map +1 -1
  19. package/src/cli/commands/QueueCommand.js +89 -39
  20. package/src/cli/commands/QueueLockCommand.d.ts +7 -0
  21. package/src/cli/commands/QueueLockCommand.d.ts.map +1 -0
  22. package/src/cli/commands/QueueLockCommand.js +138 -0
  23. package/src/cli/commands/StartCommand.d.ts.map +1 -1
  24. package/src/cli/commands/StartCommand.js +16 -16
  25. package/src/cli/commands/TemplatesCommand.js +1 -1
  26. package/src/cli/commands/WorkerCommands.d.ts.map +1 -1
  27. package/src/cli/commands/WorkerCommands.js +46 -22
  28. package/src/cli/scaffolding/ProjectScaffolder.js +2 -2
  29. package/src/cli/scaffolding/RouteGenerator.d.ts.map +1 -1
  30. package/src/cli/scaffolding/RouteGenerator.js +27 -28
  31. package/src/cli/services/VersionChecker.d.ts +53 -0
  32. package/src/cli/services/VersionChecker.d.ts.map +1 -0
  33. package/src/cli/services/VersionChecker.js +176 -0
  34. package/src/cli/utils/DatabaseCliUtils.d.ts +20 -0
  35. package/src/cli/utils/DatabaseCliUtils.d.ts.map +1 -0
  36. package/src/cli/utils/DatabaseCliUtils.js +54 -0
  37. package/src/cli/workers/QueueWorkRunner.d.ts.map +1 -1
  38. package/src/cli/workers/QueueWorkRunner.js +128 -7
  39. package/src/common/ExternalServiceUtils.d.ts +2 -2
  40. package/src/config/app.d.ts +4 -0
  41. package/src/config/app.d.ts.map +1 -1
  42. package/src/config/app.js +9 -0
  43. package/src/config/constants.d.ts +140 -10
  44. package/src/config/constants.d.ts.map +1 -1
  45. package/src/config/constants.js +86 -5
  46. package/src/config/index.d.ts +1 -0
  47. package/src/config/index.d.ts.map +1 -1
  48. package/src/config/middleware.d.ts +6 -6
  49. package/src/config/middleware.d.ts.map +1 -1
  50. package/src/config/middleware.js +6 -7
  51. package/src/config/queue.d.ts +4 -0
  52. package/src/config/queue.d.ts.map +1 -1
  53. package/src/config/queue.js +1 -1
  54. package/src/config/redis.d.ts +17 -0
  55. package/src/config/redis.d.ts.map +1 -0
  56. package/src/config/redis.js +54 -0
  57. package/src/config/type.d.ts +3 -0
  58. package/src/config/type.d.ts.map +1 -1
  59. package/src/http/Request.d.ts +10 -1
  60. package/src/http/Request.d.ts.map +1 -1
  61. package/src/http/Request.js +79 -7
  62. package/src/http/error-pages/ErrorPageRenderer.d.ts.map +1 -1
  63. package/src/http/error-pages/ErrorPageRenderer.js +4 -3
  64. package/src/index.d.ts +14 -11
  65. package/src/index.d.ts.map +1 -1
  66. package/src/index.js +18 -11
  67. package/src/lang/lang.d.ts +23 -0
  68. package/src/lang/lang.d.ts.map +1 -0
  69. package/src/lang/lang.js +22 -0
  70. package/src/middleware/ErrorHandlerMiddleware.d.ts.map +1 -1
  71. package/src/middleware/ErrorHandlerMiddleware.js +9 -1
  72. package/src/migrations/schema/SchemaCompiler.js +1 -1
  73. package/src/migrations/schema/types.d.ts +1 -1
  74. package/src/migrations/schema/types.d.ts.map +1 -1
  75. package/src/node.d.ts +1 -1
  76. package/src/node.d.ts.map +1 -1
  77. package/src/node.js +1 -1
  78. package/src/orm/Database.d.ts +1 -1
  79. package/src/orm/Database.d.ts.map +1 -1
  80. package/src/orm/Database.js +22 -3
  81. package/src/performance/Optimizer.js +1 -1
  82. package/src/routing/Router.d.ts +6 -2
  83. package/src/routing/Router.d.ts.map +1 -1
  84. package/src/routing/Router.js +19 -4
  85. package/src/runtime/PluginAutoImports.d.ts.map +1 -1
  86. package/src/runtime/PluginAutoImports.js +1 -13
  87. package/src/runtime/PluginManager.d.ts.map +1 -1
  88. package/src/runtime/PluginManager.js +2 -14
  89. package/src/runtime/PluginRegistry.js +2 -2
  90. package/src/start.d.ts.map +1 -1
  91. package/src/start.js +8 -7
  92. package/src/templates/TemplateRegistry.js +2 -2
  93. package/src/templates/TemplateRegistry.ts +2 -2
  94. package/src/templates/feature/Queue.ts.tpl +114 -0
  95. package/src/templates/project/basic/app/Controllers/UserController.ts.tpl +22 -0
  96. package/src/templates/project/basic/config/queue.ts.tpl +19 -0
  97. package/src/templates/project/basic/package.json.tpl +2 -1
  98. package/src/templates/project/basic/src/index.ts.tpl +0 -3
  99. package/src/toolkit/Secrets/providers/AwsSecretsManager.d.ts.map +1 -1
  100. package/src/toolkit/Secrets/providers/AwsSecretsManager.js +1 -13
  101. package/src/toolkit/Secrets/providers/CloudflareKv.d.ts.map +1 -1
  102. package/src/toolkit/Secrets/providers/CloudflareKv.js +4 -16
  103. package/src/tools/broadcast/drivers/Redis.d.ts.map +1 -1
  104. package/src/tools/broadcast/drivers/Redis.js +8 -56
  105. package/src/tools/mail/Mail.d.ts +1 -29
  106. package/src/tools/mail/Mail.d.ts.map +1 -1
  107. package/src/tools/mail/Mail.js +1 -111
  108. package/src/tools/mail/drivers/SendGrid.d.ts.map +1 -1
  109. package/src/tools/mail/drivers/SendGrid.js +4 -3
  110. package/src/tools/mail/drivers/Smtp.d.ts.map +1 -1
  111. package/src/tools/mail/drivers/Smtp.js +32 -10
  112. package/src/tools/mail/index.d.ts +40 -0
  113. package/src/tools/mail/index.d.ts.map +1 -0
  114. package/src/tools/mail/index.js +129 -0
  115. package/src/tools/mail/template-loader.d.ts +10 -0
  116. package/src/tools/mail/template-loader.d.ts.map +1 -0
  117. package/src/tools/mail/template-loader.js +101 -0
  118. package/src/tools/mail/template-utils.d.ts +10 -0
  119. package/src/tools/mail/template-utils.d.ts.map +1 -0
  120. package/src/tools/mail/template-utils.js +16 -0
  121. package/src/tools/mail/templates/index.d.ts +30 -0
  122. package/src/tools/mail/templates/index.d.ts.map +1 -1
  123. package/src/tools/mail/templates/index.js +69 -0
  124. package/src/tools/queue/AdvancedQueue.d.ts +19 -0
  125. package/src/tools/queue/AdvancedQueue.d.ts.map +1 -0
  126. package/src/tools/queue/AdvancedQueue.js +352 -0
  127. package/src/tools/queue/DeduplicationBuilder.d.ts +20 -0
  128. package/src/tools/queue/DeduplicationBuilder.d.ts.map +1 -0
  129. package/src/tools/queue/DeduplicationBuilder.js +77 -0
  130. package/src/tools/queue/LockProvider.d.ts +25 -0
  131. package/src/tools/queue/LockProvider.d.ts.map +1 -0
  132. package/src/tools/queue/LockProvider.js +276 -0
  133. package/src/tools/queue/Queue.d.ts.map +1 -1
  134. package/src/tools/queue/Queue.js +2 -1
  135. package/src/tools/queue/QueueExtensions.d.ts +46 -0
  136. package/src/tools/queue/QueueExtensions.d.ts.map +1 -0
  137. package/src/tools/queue/QueueExtensions.js +129 -0
  138. package/src/tools/queue/QueueRuntimeRegistration.d.ts.map +1 -1
  139. package/src/tools/queue/QueueRuntimeRegistration.js +2 -2
  140. package/src/tools/queue/drivers/Database.d.ts +23 -0
  141. package/src/tools/queue/drivers/Database.d.ts.map +1 -0
  142. package/src/tools/queue/drivers/Database.js +123 -0
  143. package/src/tools/queue/drivers/Redis.d.ts.map +1 -1
  144. package/src/tools/queue/drivers/Redis.js +11 -82
  145. package/src/tools/queue/index.d.ts +9 -0
  146. package/src/tools/queue/index.d.ts.map +1 -0
  147. package/src/tools/queue/index.js +7 -0
  148. package/src/tools/redis/RedisKeyManager.d.ts +64 -0
  149. package/src/tools/redis/RedisKeyManager.d.ts.map +1 -0
  150. package/src/tools/redis/RedisKeyManager.js +124 -0
  151. package/src/tools/storage/drivers/S3.d.ts.map +1 -1
  152. package/src/tools/storage/drivers/S3.js +4 -16
  153. package/src/types/Queue.d.ts +62 -0
  154. package/src/types/Queue.d.ts.map +1 -0
  155. package/src/types/Queue.js +5 -0
  156. package/src/features/Auth.d.ts.map +0 -1
  157. package/src/features/Queue.d.ts +0 -21
  158. package/src/features/Queue.d.ts.map +0 -1
  159. package/src/features/Queue.js +0 -33
  160. package/src/templates/features/Queue.ts.tpl +0 -47
  161. package/src/tools/mail/templates/markdown/index.d.ts +0 -17
  162. package/src/tools/mail/templates/markdown/index.d.ts.map +0 -1
  163. package/src/tools/mail/templates/markdown/index.js +0 -49
  164. package/src/tools/mail/templates/markdown/registry.d.ts +0 -15
  165. package/src/tools/mail/templates/markdown/registry.d.ts.map +0 -1
  166. package/src/tools/mail/templates/markdown/registry.js +0 -34
  167. package/src/tools/mail/templates/markdown/validator.d.ts +0 -16
  168. package/src/tools/mail/templates/markdown/validator.d.ts.map +0 -1
  169. package/src/tools/mail/templates/markdown/validator.js +0 -24
  170. /package/src/{features → auth}/Auth.d.ts +0 -0
  171. /package/src/{features → auth}/Auth.js +0 -0
  172. /package/src/templates/{features → auth}/Auth.ts.tpl +0 -0
@@ -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;AAoavF,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"}
@@ -2,8 +2,8 @@ import { BaseCommand } from '../BaseCommand.js';
2
2
  import { DENO_RUNNER_SOURCE, LAMBDA_RUNNER_SOURCE } from '../commands/runner/index.js';
3
3
  import { EnvFileLoader } from '../utils/EnvFileLoader.js';
4
4
  import { SpawnUtil } from '../utils/spawn.js';
5
+ import { readEnvString } from '../../common/ExternalServiceUtils.js';
5
6
  import { resolveNpmPath } from '../../common/index.js';
6
- import { Env } from '../../config/env.js';
7
7
  import { ErrorFactory } from '../../exceptions/ZintrustError.js';
8
8
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from '../../node-singletons/fs.js';
9
9
  import * as path from '../../node-singletons/path.js';
@@ -20,18 +20,6 @@ const normalizeMode = (value) => {
20
20
  return 'testing';
21
21
  return 'development';
22
22
  };
23
- const readEnvString = (key) => {
24
- const anyEnv = Env;
25
- const fromEnv = typeof anyEnv.get === 'function' ? anyEnv.get(key, '') : '';
26
- if (typeof fromEnv === 'string' && fromEnv.trim() !== '')
27
- return fromEnv;
28
- if (typeof process !== 'undefined') {
29
- const raw = process.env?.[key];
30
- if (typeof raw === 'string')
31
- return raw;
32
- }
33
- return fromEnv ?? '';
34
- };
35
23
  const resolveModeFromAppMode = () => {
36
24
  const raw = readEnvString('APP_MODE').trim();
37
25
  const normalized = raw.toLowerCase();
@@ -258,18 +246,30 @@ const executeNodeStart = async (cmd, cwd, mode, watchEnabled) => {
258
246
  cmd.warn('Watch mode disabled; starting once.');
259
247
  const bootstrap = resolveBootstrapEntryTs(cwd);
260
248
  const args = bootstrap === undefined ? ['src/index.ts'] : [bootstrap];
261
- const exitCode = await SpawnUtil.spawnAndWait({ command: 'tsx', args });
249
+ const exitCode = await SpawnUtil.spawnAndWait({
250
+ command: 'tsx',
251
+ args,
252
+ forwardSignals: false,
253
+ });
262
254
  process.exit(exitCode);
263
255
  }
264
256
  const packageJson = readPackageJson(cwd);
265
257
  const dev = resolveNodeDevCommand(cwd, packageJson);
266
258
  cmd.info('Starting in development mode (watch enabled)...');
267
- 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
+ });
268
264
  process.exit(exitCode);
269
265
  }
270
266
  const prod = resolveNodeProdCommand(cwd);
271
267
  cmd.info('Starting in production mode...');
272
- 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
+ });
273
273
  process.exit(exitCode);
274
274
  };
275
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"}
@@ -3,28 +3,51 @@
3
3
  * Worker Management CLI Commands
4
4
  * Command-line interface for managing workers
5
5
  */
6
+ import { ErrorFactory } from '../../exceptions/ZintrustError.js';
6
7
  import { BaseCommand } from '../BaseCommand.js';
7
8
  import { Logger } from '../../config/logger.js';
8
- import { HealthMonitor as HealthMonitorAny, ResourceMonitor as ResourceMonitorAny, WorkerFactory as WorkerFactoryAny, WorkerRegistry as WorkerRegistryAny, } from '../../../packages/workers/src/index.js';
9
9
  // Lazy initialization to prevent temporal dead zone issues
10
10
  let WorkerFactory;
11
11
  let WorkerRegistry;
12
12
  let HealthMonitor;
13
13
  let ResourceMonitor;
14
- const getWorkerFactory = () => {
15
- WorkerFactory ??= WorkerFactoryAny;
14
+ let workersModulePromise;
15
+ const loadWorkersModule = async () => {
16
+ workersModulePromise ??= import('../../../packages/workers/src/index.js');
17
+ try {
18
+ return await workersModulePromise;
19
+ }
20
+ catch (error) {
21
+ Logger.error('Failed to load optional package "@zintrust/workers"; worker commands require this package.', error);
22
+ throw ErrorFactory.createCliError('Optional package "@zintrust/workers" is required for worker commands. Install it to use worker:* commands.');
23
+ }
24
+ };
25
+ const getWorkerFactory = async () => {
26
+ if (!WorkerFactory) {
27
+ const mod = await loadWorkersModule();
28
+ WorkerFactory = mod.WorkerFactory;
29
+ }
16
30
  return WorkerFactory;
17
31
  };
18
- const getWorkerRegistry = () => {
19
- WorkerRegistry ??= WorkerRegistryAny;
32
+ const getWorkerRegistry = async () => {
33
+ if (!WorkerRegistry) {
34
+ const mod = await loadWorkersModule();
35
+ WorkerRegistry = mod.WorkerRegistry;
36
+ }
20
37
  return WorkerRegistry;
21
38
  };
22
- const getHealthMonitor = () => {
23
- HealthMonitor ??= HealthMonitorAny;
39
+ const getHealthMonitor = async () => {
40
+ if (!HealthMonitor) {
41
+ const mod = await loadWorkersModule();
42
+ HealthMonitor = mod.HealthMonitor;
43
+ }
24
44
  return HealthMonitor;
25
45
  };
26
- const getResourceMonitor = () => {
27
- ResourceMonitor ??= ResourceMonitorAny;
46
+ const getResourceMonitor = async () => {
47
+ if (!ResourceMonitor) {
48
+ const mod = await loadWorkersModule();
49
+ ResourceMonitor = mod.ResourceMonitor;
50
+ }
28
51
  return ResourceMonitor;
29
52
  };
30
53
  /**
@@ -46,14 +69,15 @@ const formatTable = (headers, rows) => {
46
69
  const createWorkerListCommand = () => {
47
70
  const ext = async () => {
48
71
  try {
49
- const workers = await getWorkerFactory().listPersisted();
72
+ const workers = await (await getWorkerFactory()).listPersisted();
50
73
  console.log(`\nTotal Workers: ${workers.length}\n`);
51
74
  if (workers.length === 0) {
52
75
  console.log('No workers found.');
53
76
  return;
54
77
  }
78
+ const registry = await getWorkerRegistry();
55
79
  const rows = workers.map((name) => {
56
- const status = getWorkerRegistry().status(name);
80
+ const status = registry.status(name);
57
81
  return [
58
82
  name,
59
83
  status?.status ?? 'unknown',
@@ -87,12 +111,12 @@ const createWorkerStatusCommand = () => {
87
111
  Logger.error('Error: Worker name is required');
88
112
  process.exit(1);
89
113
  }
90
- const status = getWorkerRegistry().status(name);
91
- const health = await getWorkerFactory().getHealth(name);
114
+ const status = (await getWorkerRegistry()).status(name);
115
+ const health = await (await getWorkerFactory()).getHealth(name);
92
116
  const healthData = typeof health === 'object' && health !== null
93
117
  ? health
94
118
  : {};
95
- const metrics = await getWorkerFactory().getMetrics(name);
119
+ const metrics = await (await getWorkerFactory()).getMetrics(name);
96
120
  console.log(`\n=== Worker: ${name} ===\n`);
97
121
  console.log(`Status: ${status?.status}`);
98
122
  console.log(`Version: ${status?.version}`);
@@ -131,7 +155,7 @@ const createWorkerStartCommand = () => {
131
155
  Logger.error('Error: Worker name is required');
132
156
  process.exit(1);
133
157
  }
134
- await getWorkerFactory().start(name);
158
+ await (await getWorkerFactory()).start(name);
135
159
  Logger.info(`✓ Worker "${name}" started successfully`);
136
160
  }
137
161
  catch (error) {
@@ -159,7 +183,7 @@ const createWorkerStopCommand = () => {
159
183
  console.error('Error: Worker name is required');
160
184
  process.exit(1);
161
185
  }
162
- await getWorkerFactory().stop(name);
186
+ await (await getWorkerFactory()).stop(name);
163
187
  console.log(`✓ Worker "${name}" stopped successfully`);
164
188
  }
165
189
  catch (error) {
@@ -188,7 +212,7 @@ const createWorkerRestartCommand = () => {
188
212
  console.error('Error: Worker name is required');
189
213
  process.exit(1);
190
214
  }
191
- await getWorkerFactory().restart(name);
215
+ await (await getWorkerFactory()).restart(name);
192
216
  console.log(`✓ Worker "${name}" restarted successfully`);
193
217
  }
194
218
  catch (error) {
@@ -211,11 +235,11 @@ const createWorkerRestartCommand = () => {
211
235
  * Worker Summary Command
212
236
  */
213
237
  const createWorkerSummaryCommand = () => {
214
- const ext = () => {
238
+ const ext = async () => {
215
239
  try {
216
- const workers = getWorkerFactory().list();
217
- const monitoringSummary = getHealthMonitor().getSummary();
218
- const resourceUsage = getResourceMonitor().getCurrentUsage('system');
240
+ const workers = (await getWorkerFactory()).list();
241
+ const monitoringSummary = (await getHealthMonitor()).getSummary();
242
+ const resourceUsage = (await getResourceMonitor()).getCurrentUsage('system');
219
243
  console.log(`\n=== Worker System Summary ===\n`);
220
244
  console.log(`Total Workers: ${workers.length}`);
221
245
  console.log(`\nHealth Overview:`);
@@ -247,7 +271,7 @@ const createWorkerSummaryCommand = () => {
247
271
  const cmd = BaseCommand.create({
248
272
  name: 'worker:summary',
249
273
  description: 'Get system-wide worker summary',
250
- execute: () => ext(),
274
+ execute: async () => ext(),
251
275
  });
252
276
  return cmd;
253
277
  };
@@ -199,8 +199,8 @@ const createEnvFile = (projectPath, variables) => {
199
199
  '',
200
200
  '# Logging',
201
201
  'LOG_LEVEL=debug',
202
- 'LOG_CHANNEL=console',
203
- 'LOG_FORMAT=json',
202
+ 'LOG_CHANNEL=file',
203
+ 'LOG_FORMAT=text',
204
204
  '',
205
205
  '# Auth / Security',
206
206
  'JWT_SECRET=',
@@ -1 +1 @@
1
- {"version":3,"file":"RouteGenerator.d.ts","sourceRoot":"","sources":["../../../../src/cli/scaffolding/RouteGenerator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEnF,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAuBD,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAqB3F;AAED;;GAEG;AAEH,wBAAgB,cAAc,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA4CnF;AA+LD;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,eAAe,EAAE,CAsCpD;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,eAAe,EAAE,CA6BjD;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,eAAe,EAAE,CA+BlD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,WAAW,EAAE,CAEhD;AAED;;GAEG;AACH,eAAO,MAAM,cAAc;;;;;;;EAOzB,CAAC"}
1
+ {"version":3,"file":"RouteGenerator.d.ts","sourceRoot":"","sources":["../../../../src/cli/scaffolding/RouteGenerator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEnF,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,WAAW,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAuBD,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAqB3F;AAED;;GAEG;AAEH,wBAAgB,cAAc,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA4CnF;AA4LD;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,eAAe,EAAE,CAsCpD;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,eAAe,EAAE,CA6BjD;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,eAAe,EAAE,CA+BlD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,WAAW,EAAE,CAEhD;AAED;;GAEG;AACH,eAAO,MAAM,cAAc;;;;;;;EAOzB,CAAC"}
@@ -8,21 +8,21 @@ import * as path from '../../node-singletons/path.js';
8
8
  // Escape characters that can cause issues when embedding JSON.stringify output
9
9
  // into generated JavaScript source (e.g., inside <script> tags).
10
10
  const unsafeCharMap = {
11
- '<': '\\u003C',
12
- '>': '\\u003E',
13
- '/': '\\u002F',
11
+ '<': String.raw `\u003C`,
12
+ '>': String.raw `\u003E`,
13
+ '/': String.raw `\u002F`,
14
14
  '\\': '\\\\',
15
- '\b': '\\b',
16
- '\f': '\\f',
17
- '\n': '\\n',
18
- '\r': '\\r',
19
- '\t': '\\t',
20
- '\0': '\\0',
21
- '\u2028': '\\u2028',
22
- '\u2029': '\\u2029',
15
+ '\b': String.raw `\b`,
16
+ '\f': String.raw `\f`,
17
+ '\n': String.raw `\n`,
18
+ '\r': String.raw `\r`,
19
+ '\t': String.raw `\t`,
20
+ '\0': String.raw `\0`,
21
+ '\u2028': String.raw `\u2028`,
22
+ '\u2029': String.raw `\u2029`,
23
23
  };
24
24
  function escapeUnsafeChars(str) {
25
- return str.replace(/[<>/\\\b\f\n\r\t\0\u2028\u2029]/g, (ch) => unsafeCharMap[ch] ?? ch);
25
+ return str.replaceAll(/[<>/\\\b\f\n\r\t\0\u2028\u2029]/g, (ch) => unsafeCharMap[ch] ?? ch);
26
26
  }
27
27
  /**
28
28
  * Validate route options
@@ -181,6 +181,19 @@ function buildRouteCode(route, router, groupMiddlewareList) {
181
181
  return buildMethodRoute(route, router, groupMiddlewareList);
182
182
  }
183
183
  }
184
+ /**
185
+ * Build middleware property string for routes
186
+ */
187
+ function buildMiddlewareProp(route, groupMiddlewareList) {
188
+ const localMiddlewareList = (route.middleware ?? []).map((m) => `'${m}'`).join(', ');
189
+ const hasGroup = groupMiddlewareList.trim() !== '';
190
+ const hasLocal = localMiddlewareList.trim() !== '';
191
+ return hasGroup || hasLocal
192
+ ? `middleware: [${[hasGroup ? groupMiddlewareList : '', hasLocal ? localMiddlewareList : '']
193
+ .filter((v) => v.trim() !== '')
194
+ .join(', ')}]`
195
+ : '';
196
+ }
184
197
  /**
185
198
  * Build standard method route (GET, POST, etc.)
186
199
  */
@@ -192,14 +205,7 @@ function buildMethodRoute(route, router, groupMiddlewareList) {
192
205
  const tag = toTag(controller);
193
206
  const summary = `${method.toUpperCase()} ${routePath}`;
194
207
  const controllerVar = toControllerVar(controller);
195
- const localMiddlewareList = (route.middleware ?? []).map((m) => `'${m}'`).join(', ');
196
- const hasGroup = groupMiddlewareList.trim() !== '';
197
- const hasLocal = localMiddlewareList.trim() !== '';
198
- const middlewareProp = hasGroup || hasLocal
199
- ? `middleware: [${[hasGroup ? groupMiddlewareList : '', hasLocal ? localMiddlewareList : '']
200
- .filter((v) => v.trim() !== '')
201
- .join(', ')}]`
202
- : '';
208
+ const middlewareProp = buildMiddlewareProp(route, groupMiddlewareList);
203
209
  const metaProp = `meta: { summary: ${escapeUnsafeChars(JSON.stringify(summary))}, tags: [${escapeUnsafeChars(JSON.stringify(tag))}] }`;
204
210
  const options = `{ ${[middlewareProp, metaProp].filter((v) => v !== '').join(', ')} }`;
205
211
  return ` Router.${method}(${router}, '${routePath}', (req, res) => ${controllerVar}.${action}(req, res), ${options});\n`;
@@ -212,14 +218,7 @@ function buildResourceRoute(route, router, groupMiddlewareList) {
212
218
  const controller = route.controller;
213
219
  const tag = toTag(controller);
214
220
  const controllerVar = toControllerVar(controller);
215
- const localMiddlewareList = (route.middleware ?? []).map((m) => `'${m}'`).join(', ');
216
- const hasGroup = groupMiddlewareList.trim() !== '';
217
- const hasLocal = localMiddlewareList.trim() !== '';
218
- const middlewareProp = hasGroup || hasLocal
219
- ? `middleware: [${[hasGroup ? groupMiddlewareList : '', hasLocal ? localMiddlewareList : '']
220
- .filter((v) => v.trim() !== '')
221
- .join(', ')}]`
222
- : '';
221
+ const middlewareProp = buildMiddlewareProp(route, groupMiddlewareList);
223
222
  const resourceMeta = (action, routePattern) => `meta: { summary: ${escapeUnsafeChars(JSON.stringify(action.toUpperCase() + ' ' + routePattern))}, tags: [${escapeUnsafeChars(JSON.stringify(tag))}] }`;
224
223
  const pathId = routePath + '/:id';
225
224
  const optsParts = [