@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
@@ -33,3 +33,72 @@ export const MailTemplates = Object.freeze({
33
33
  }),
34
34
  }),
35
35
  });
36
+ import { ErrorFactory } from '../../../exceptions/ZintrustError.js';
37
+ /**
38
+ * Markdown template compatibility exports
39
+ * Mail templates are now pure HTML files.
40
+ * These exports provide backward compatibility for CLI and node.ts exports.
41
+ */
42
+ import { readdirSync, readFileSync } from 'node:fs';
43
+ import { join } from 'node:path';
44
+ /**
45
+ * List all available HTML mail templates.
46
+ * Returns template names without the .html extension.
47
+ */
48
+ export function listTemplates() {
49
+ try {
50
+ const templatesDir = join(process.cwd(), 'src/tools/mail/templates');
51
+ const files = readdirSync(templatesDir);
52
+ return files
53
+ .filter((file) => file.endsWith('.html'))
54
+ .map((file) => file.replace('.html', ''))
55
+ .sort((a, b) => a.localeCompare(b));
56
+ }
57
+ catch {
58
+ return [];
59
+ }
60
+ }
61
+ /**
62
+ * Load a mail template by name.
63
+ * Note: HTML templates don't have embedded metadata like markdown templates did.
64
+ * This function returns the raw HTML content.
65
+ */
66
+ export function loadTemplate(name) {
67
+ try {
68
+ const templatesDir = join(process.cwd(), 'src/tools/mail/templates');
69
+ const filePath = join(templatesDir, `${name}.html`);
70
+ const content = readFileSync(filePath, 'utf8');
71
+ return {
72
+ subject: undefined,
73
+ preheader: undefined,
74
+ variables: undefined,
75
+ content,
76
+ };
77
+ }
78
+ catch (error) {
79
+ throw ErrorFactory.createTryCatchError(`Template "${name}" not found: ${error.message}`);
80
+ }
81
+ }
82
+ /**
83
+ * Render a mail template with variables.
84
+ * Note: This is a compatibility function. For production use, use MailTemplateRenderer instead.
85
+ */
86
+ export function renderTemplate(name, variables) {
87
+ const template = loadTemplate(name);
88
+ let html = template.content;
89
+ // Simple variable interpolation
90
+ if (variables) {
91
+ for (const [key, value] of Object.entries(variables)) {
92
+ const regex = new RegExp(String.raw `{{\s*${key}\s*}}`, 'g');
93
+ html = html.replace(regex, String(value ?? ''));
94
+ }
95
+ }
96
+ return {
97
+ html,
98
+ meta: {
99
+ subject: template.subject,
100
+ content: template.content,
101
+ variables: template.variables,
102
+ },
103
+ };
104
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Advanced Queue Implementation
3
+ * Extends ZinTrust queue functionality with deduplication and lock management
4
+ */
5
+ import type { AdvancedJobOptions, JobResult, QueueConfig } from '../../types/Queue';
6
+ import { type DeduplicationBuilder } from './DeduplicationBuilder';
7
+ export interface AdvancedQueue {
8
+ enqueue(name: string, payload: unknown, options: AdvancedJobOptions): Promise<string>;
9
+ deduplicate(id: string, builder: DeduplicationBuilder): Promise<JobResult>;
10
+ releaseLock(key: string): Promise<void>;
11
+ extendLock(key: string, ttl: number): Promise<boolean>;
12
+ }
13
+ /**
14
+ * Creates an advanced queue instance with deduplication capabilities
15
+ * @param config - Queue configuration
16
+ * @returns {AdvancedQueue} Advanced queue instance
17
+ */
18
+ export declare function createAdvancedQueue(config: QueueConfig): AdvancedQueue;
19
+ //# sourceMappingURL=AdvancedQueue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AdvancedQueue.d.ts","sourceRoot":"","sources":["../../../../src/tools/queue/AdvancedQueue.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAElB,SAAS,EAGT,WAAW,EACZ,MAAM,eAAe,CAAC;AAKvB,OAAO,EAAE,KAAK,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAIxE,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACtF,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAC3E,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACxD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,WAAW,GAAG,aAAa,CAiBtE"}
@@ -0,0 +1,352 @@
1
+ /**
2
+ * Advanced Queue Implementation
3
+ * Extends ZinTrust queue functionality with deduplication and lock management
4
+ */
5
+ import { Env } from '../../config/env.js';
6
+ import { Logger } from '../../config/logger.js';
7
+ import { createValidationError } from '../../exceptions/ZintrustError.js';
8
+ import { ZintrustLang } from '../../lang/lang.js';
9
+ import { createLockProvider, getLockProvider, registerLockProvider } from './LockProvider.js';
10
+ import { Queue } from './Queue.js';
11
+ /**
12
+ * Creates an advanced queue instance with deduplication capabilities
13
+ * @param config - Queue configuration
14
+ * @returns {AdvancedQueue} Advanced queue instance
15
+ */
16
+ export function createAdvancedQueue(config) {
17
+ const lockProviderName = resolveLockProviderName(config);
18
+ const lockProvider = initializeLockProvider(lockProviderName, config);
19
+ return {
20
+ enqueue: async (name, payload, options) => enqueueWithDeduplication(name, payload, options, lockProvider),
21
+ // eslint-disable-next-line @typescript-eslint/require-await
22
+ deduplicate: async (_id, _builder) => {
23
+ throw createValidationError('deduplicate() method should be used via enqueue() with deduplication options');
24
+ },
25
+ releaseLock: async (key) => releaseLock(key, lockProvider),
26
+ extendLock: async (key, ttl) => extendLock(key, ttl, lockProvider),
27
+ };
28
+ }
29
+ /**
30
+ * Initialize lock provider for the advanced queue
31
+ */
32
+ function initializeLockProvider(lockProviderName, config) {
33
+ // Initialize default lock provider if not exists
34
+ const getLock = getLockProvider(lockProviderName);
35
+ if (getLock === undefined) {
36
+ const lockConfig = {
37
+ type: lockProviderName === ZintrustLang.REDIS ? ZintrustLang.REDIS : ZintrustLang.MEMORY,
38
+ prefix: resolveLockPrefix(),
39
+ defaultTtl: resolveDefaultLockTtl(config),
40
+ };
41
+ registerLockProvider(lockProviderName, createLockProvider(lockConfig));
42
+ }
43
+ const provider = getLockProvider(lockProviderName);
44
+ if (provider === undefined) {
45
+ throw createValidationError(`Failed to initialize lock provider: ${lockProviderName}`);
46
+ }
47
+ return provider;
48
+ }
49
+ function resolveLockProviderName(config) {
50
+ const envProvider = Env.get('QUEUE_LOCK_PROVIDER', '').trim();
51
+ if (config.lockProvider !== undefined &&
52
+ config.lockProvider !== null &&
53
+ config.lockProvider.length > 0)
54
+ return config.lockProvider;
55
+ if (envProvider.length > 0)
56
+ return envProvider;
57
+ return ZintrustLang.MEMORY;
58
+ }
59
+ function resolveLockPrefix() {
60
+ const fromEnv = Env.get('QUEUE_LOCK_PREFIX', '').trim();
61
+ return fromEnv.length > 0 ? fromEnv : ZintrustLang.ZINTRUST_LOCKS_PREFIX;
62
+ }
63
+ function resolveDefaultLockTtl(config) {
64
+ return Env.getInt('QUEUE_DEFAULT_DEDUP_TTL', config.defaultDedupTtl ?? ZintrustLang.ZINTRUST_LOCKS_TTL);
65
+ }
66
+ function resolveMaxLockTtl() {
67
+ const max = Env.getInt('QUEUE_MAX_LOCK_TTL', 0);
68
+ if (max <= 0)
69
+ return undefined;
70
+ return max;
71
+ }
72
+ /**
73
+ * Handle uniqueId validation
74
+ */
75
+ function validateUniqueIdOptions(options) {
76
+ if (options.uniqueId !== null && options.uniqueId !== undefined && options.uniqueId !== '') {
77
+ const validation = validateUniqueId(options.uniqueId);
78
+ if (!validation.valid) {
79
+ throw createValidationError(`Invalid uniqueId: ${validation.reason}`);
80
+ }
81
+ }
82
+ }
83
+ function validateDeduplicationOptions(options) {
84
+ if (!options.deduplication)
85
+ return;
86
+ const maxTtl = resolveMaxLockTtl();
87
+ if (maxTtl !== undefined && options.deduplication.ttl !== undefined) {
88
+ if (options.deduplication.ttl > maxTtl) {
89
+ throw createValidationError(`Deduplication TTL exceeds QUEUE_MAX_LOCK_TTL (${options.deduplication.ttl} > ${maxTtl})`);
90
+ }
91
+ }
92
+ }
93
+ const QUEUE_META_KEY = '__zintrustQueueMeta';
94
+ function shouldAttachReleaseAfterMeta(options) {
95
+ if (options.deduplication?.releaseAfter === undefined)
96
+ return false;
97
+ return typeof options.deduplication.releaseAfter !== 'number';
98
+ }
99
+ function attachQueueMeta(payload, options) {
100
+ if (!shouldAttachReleaseAfterMeta(options)) {
101
+ return { payload, metaAttached: false };
102
+ }
103
+ if (payload === null || payload === undefined || typeof payload !== 'object') {
104
+ return { payload, metaAttached: false };
105
+ }
106
+ const meta = {
107
+ deduplicationId: options.deduplication?.id,
108
+ releaseAfter: options.deduplication?.releaseAfter,
109
+ uniqueId: options.uniqueId,
110
+ };
111
+ return {
112
+ payload: {
113
+ ...payload,
114
+ [QUEUE_META_KEY]: meta,
115
+ },
116
+ metaAttached: true,
117
+ };
118
+ }
119
+ /**
120
+ * Handle deduplication logic
121
+ */
122
+ async function handleDeduplicationLogic(options, lockProvider, name, startTime) {
123
+ if (!options.deduplication) {
124
+ return null;
125
+ }
126
+ const deduplicationResult = await handleDeduplication(options.deduplication, lockProvider);
127
+ if (deduplicationResult.deduplicated) {
128
+ Logger.info('Job deduplicated', {
129
+ queueName: name,
130
+ deduplicationId: options.deduplication.id,
131
+ duration: Date.now() - startTime,
132
+ });
133
+ return deduplicationResult.lockId ?? ZintrustLang.DEDUPLICATED;
134
+ }
135
+ // Handle releaseAfter (numeric delay)
136
+ if (options.deduplication?.releaseAfter !== undefined &&
137
+ typeof options.deduplication.releaseAfter === 'number' &&
138
+ deduplicationResult.lockId !== null &&
139
+ deduplicationResult.lockId !== undefined &&
140
+ deduplicationResult.lockId !== '') {
141
+ const delay = options.deduplication.releaseAfter;
142
+ // lockId from handleDeduplication (via acquire) already has prefix?
143
+ // acquire returns lock.key which INCLUDES prefix.
144
+ // releaseLock() calls lockProvider.status(key) which ADDS prefix.
145
+ // So if we pass lockId (with prefix) to releaseLock, lockProvider.status will double prefix?
146
+ // Let's check LockProvider.ts.
147
+ // acquire: lockKey = `${prefix}${key}`. Returns lock.key = lockKey.
148
+ // releaseLock (AdvancedQueue): calls lockProvider.status(key).
149
+ // lockProvider.status: lockKey = `${prefix}${key}`.
150
+ // So releaseLock expects 'id' (without prefix).
151
+ // deduplicationResult.lockId is lock.key (WITH prefix).
152
+ // deduplicationResult.id is the original ID.
153
+ const lockId = options.deduplication.id; // Use the original ID
154
+ // Create timeout with proper cleanup tracking
155
+ // Using unref() to prevent blocking process exit
156
+ // eslint-disable-next-line no-restricted-syntax
157
+ const timeoutId = setTimeout(async () => {
158
+ try {
159
+ await releaseLock(lockId, lockProvider);
160
+ Logger.debug('Released lock after delay', { lockId, delay });
161
+ }
162
+ catch (error) {
163
+ Logger.error('Failed to release lock after delay', { lockId, error });
164
+ }
165
+ }, delay);
166
+ // Prevent Node.js from keeping the event loop alive
167
+ timeoutId.unref();
168
+ }
169
+ // Store lock reference for manual release info
170
+ if (deduplicationResult.lockId !== null &&
171
+ deduplicationResult.lockId !== undefined &&
172
+ deduplicationResult.lockId !== '') {
173
+ // In real implementation, this would be stored for later reference
174
+ Logger.debug('Lock created for job', {
175
+ lockId: deduplicationResult.lockId,
176
+ deduplicationId: options.deduplication.id,
177
+ });
178
+ }
179
+ return null;
180
+ }
181
+ /**
182
+ * Enqueue a job with advanced deduplication options
183
+ */
184
+ async function enqueueWithDeduplication(name, payload, options, defaultLockProvider) {
185
+ const startTime = Date.now();
186
+ try {
187
+ // Handle uniqueId validation
188
+ validateUniqueIdOptions(options);
189
+ validateDeduplicationOptions(options);
190
+ // Determine lock provider (uniqueVia override)
191
+ let lockProvider = defaultLockProvider;
192
+ if (options.uniqueVia !== null && options.uniqueVia !== undefined && options.uniqueVia !== '') {
193
+ const customProvider = getLockProvider(options.uniqueVia);
194
+ if (!customProvider) {
195
+ throw createValidationError(`Lock provider not found: ${options.uniqueVia}`);
196
+ }
197
+ lockProvider = customProvider;
198
+ }
199
+ // Handle deduplication
200
+ const deduplicationResult = await handleDeduplicationLogic(options, lockProvider, name, startTime);
201
+ if (deduplicationResult !== null) {
202
+ return deduplicationResult;
203
+ }
204
+ const { payload: payloadToSend, metaAttached } = attachQueueMeta(payload, options);
205
+ if (!metaAttached && shouldAttachReleaseAfterMeta(options)) {
206
+ Logger.warn('releaseAfter condition metadata could not be attached; payload is not an object', {
207
+ queueName: name,
208
+ releaseAfter: options.deduplication?.releaseAfter,
209
+ });
210
+ }
211
+ // Enqueue the job using existing queue system
212
+ const jobId = await Queue.enqueue(name, payloadToSend);
213
+ Logger.info('Job enqueued successfully', {
214
+ queueName: name,
215
+ jobId,
216
+ uniqueId: options.uniqueId,
217
+ duration: Date.now() - startTime,
218
+ });
219
+ return jobId;
220
+ }
221
+ catch (error) {
222
+ Logger.error('Failed to enqueue job', {
223
+ queueName: name,
224
+ uniqueId: options.uniqueId,
225
+ error,
226
+ });
227
+ throw error;
228
+ }
229
+ }
230
+ /**
231
+ * Release a lock by key
232
+ */
233
+ async function releaseLock(key, lockProvider) {
234
+ try {
235
+ const lockStatus = await lockProvider.status(key);
236
+ if (!lockStatus.exists) {
237
+ Logger.warn('Attempted to release non-existent lock', { key });
238
+ return;
239
+ }
240
+ // Create lock object for release
241
+ const lock = {
242
+ key,
243
+ ttl: lockStatus.ttl ?? 0,
244
+ acquired: true,
245
+ expires: lockStatus.expires ?? new Date(),
246
+ };
247
+ await lockProvider.release(lock);
248
+ Logger.info('Lock released successfully', { key });
249
+ }
250
+ catch (error) {
251
+ Logger.error('Failed to release lock', { key, error });
252
+ throw error;
253
+ }
254
+ }
255
+ /**
256
+ * Extend a lock's TTL
257
+ */
258
+ async function extendLock(key, ttl, lockProvider) {
259
+ try {
260
+ const lockStatus = await lockProvider.status(key);
261
+ if (!lockStatus.exists) {
262
+ Logger.warn('Attempted to extend non-existent lock', { key });
263
+ return false;
264
+ }
265
+ // Create lock object for extension
266
+ const lock = {
267
+ key,
268
+ ttl: lockStatus.ttl ?? 0,
269
+ acquired: true,
270
+ expires: lockStatus.expires ?? new Date(),
271
+ };
272
+ const extended = await lockProvider.extend(lock, ttl);
273
+ if (extended) {
274
+ Logger.info('Lock extended successfully', { key, ttl });
275
+ }
276
+ else {
277
+ Logger.warn('Failed to extend lock', { key, ttl });
278
+ }
279
+ return extended;
280
+ }
281
+ catch (error) {
282
+ Logger.error('Error extending lock', { key, ttl, error });
283
+ return false;
284
+ }
285
+ }
286
+ /**
287
+ * Validate unique ID format and check for potential issues
288
+ */
289
+ function validateUniqueId(uniqueId) {
290
+ if (!uniqueId || typeof uniqueId !== 'string') {
291
+ return { valid: false, reason: 'uniqueId must be a non-empty string' };
292
+ }
293
+ if (uniqueId.length > 255) {
294
+ return { valid: false, reason: 'uniqueId must be less than 255 characters' };
295
+ }
296
+ if (uniqueId.includes(' ') || uniqueId.includes('\n') || uniqueId.includes('\r')) {
297
+ return { valid: false, reason: 'uniqueId cannot contain whitespace characters' };
298
+ }
299
+ // Check for invalid characters
300
+ const invalidChars = /[<>:"\\|?*]/;
301
+ if (invalidChars.test(uniqueId)) {
302
+ return { valid: false, reason: 'uniqueId contains invalid characters' };
303
+ }
304
+ return { valid: true };
305
+ }
306
+ /**
307
+ * Handle deduplication logic
308
+ */
309
+ async function handleDeduplication(deduplicationOptions, // DeduplicationOptions - using any to avoid circular import
310
+ lockProvider) {
311
+ const { id, ttl, replace } = deduplicationOptions;
312
+ try {
313
+ // Check if lock already exists
314
+ const lockStatus = await lockProvider.status(id);
315
+ if (lockStatus.exists) {
316
+ if (replace === true) {
317
+ // Replace existing lock
318
+ const newLock = await lockProvider.acquire(id, { ttl });
319
+ return {
320
+ id,
321
+ deduplicated: false,
322
+ lockId: newLock.key,
323
+ status: ZintrustLang.QUEUED,
324
+ };
325
+ }
326
+ else {
327
+ // Job is deduplicated
328
+ return {
329
+ id,
330
+ deduplicated: true,
331
+ status: ZintrustLang.DEDUPLICATED,
332
+ };
333
+ }
334
+ }
335
+ // Acquire new lock
336
+ const lock = await lockProvider.acquire(id, { ttl });
337
+ return {
338
+ id,
339
+ deduplicated: false,
340
+ lockId: lock.key,
341
+ status: ZintrustLang.QUEUED,
342
+ };
343
+ }
344
+ catch (error) {
345
+ Logger.error(`Deduplication handling failed`, { id, error });
346
+ return {
347
+ id,
348
+ deduplicated: false,
349
+ status: ZintrustLang.FAILED,
350
+ };
351
+ }
352
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Deduplication Builder - Plain function implementation for BullMQ job deduplication
3
+ * Follows ZinTrust's preference for functions over classes
4
+ */
5
+ import type { DeduplicationOptions, ReleaseCondition } from '../../types/Queue';
6
+ export type ReleaseStrategy = string | number | ReleaseCondition;
7
+ export interface DeduplicationBuilder {
8
+ id(id: string): DeduplicationBuilder;
9
+ expireAfter(ms: number): DeduplicationBuilder;
10
+ dontRelease(): DeduplicationBuilder;
11
+ replace(): DeduplicationBuilder;
12
+ releaseAfter(strategy: ReleaseStrategy): DeduplicationBuilder;
13
+ build(): DeduplicationOptions;
14
+ }
15
+ /**
16
+ * Creates a deduplication builder for configuring job deduplication options
17
+ * @returns {DeduplicationBuilder} Builder instance with fluent interface
18
+ */
19
+ export declare function createDeduplicationBuilder(): DeduplicationBuilder;
20
+ //# sourceMappingURL=DeduplicationBuilder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeduplicationBuilder.d.ts","sourceRoot":"","sources":["../../../../src/tools/queue/DeduplicationBuilder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAG5E,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,gBAAgB,CAAC;AAEjE,MAAM,WAAW,oBAAoB;IACnC,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,oBAAoB,CAAC;IACrC,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,oBAAoB,CAAC;IAC9C,WAAW,IAAI,oBAAoB,CAAC;IACpC,OAAO,IAAI,oBAAoB,CAAC;IAChC,YAAY,CAAC,QAAQ,EAAE,eAAe,GAAG,oBAAoB,CAAC;IAC9D,KAAK,IAAI,oBAAoB,CAAC;CAC/B;AAUD;;;GAGG;AACH,wBAAgB,0BAA0B,IAAI,oBAAoB,CA+EjE"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Deduplication Builder - Plain function implementation for BullMQ job deduplication
3
+ * Follows ZinTrust's preference for functions over classes
4
+ */
5
+ import { createValidationError } from '../../exceptions/ZintrustError.js';
6
+ /**
7
+ * Creates a deduplication builder for configuring job deduplication options
8
+ * @returns {DeduplicationBuilder} Builder instance with fluent interface
9
+ */
10
+ export function createDeduplicationBuilder() {
11
+ const state = {};
12
+ return {
13
+ /**
14
+ * Set the unique identifier for deduplication
15
+ * @param id - Unique identifier string
16
+ */
17
+ id(id) {
18
+ state.id = id;
19
+ return this;
20
+ },
21
+ /**
22
+ * Set time-to-live for deduplication lock in milliseconds
23
+ * @param ms - TTL in milliseconds
24
+ */
25
+ expireAfter(ms) {
26
+ state.ttl = ms;
27
+ return this;
28
+ },
29
+ /**
30
+ * Prevent automatic lock release - requires manual release
31
+ */
32
+ dontRelease() {
33
+ state.dontRelease = true;
34
+ return this;
35
+ },
36
+ /**
37
+ * Replace existing job with same ID instead of ignoring
38
+ */
39
+ replace() {
40
+ state.replace = true;
41
+ return this;
42
+ },
43
+ /**
44
+ * Set release strategy for the deduplication lock
45
+ * @param strategy - Release strategy (delay, 'success', or condition object)
46
+ */
47
+ releaseAfter(strategy) {
48
+ state.releaseAfter = strategy;
49
+ return this;
50
+ },
51
+ /**
52
+ * Build the final deduplication options
53
+ * @returns {DeduplicationOptions} Configured deduplication options
54
+ */
55
+ build() {
56
+ if (state.id === null || state.id === undefined) {
57
+ throw createValidationError('Deduplication ID is required. Call .id() before .build()');
58
+ }
59
+ const options = {
60
+ id: state.id,
61
+ };
62
+ if (state.ttl !== undefined) {
63
+ options.ttl = state.ttl;
64
+ }
65
+ if (state.dontRelease === true) {
66
+ options.dontRelease = true;
67
+ }
68
+ if (state.replace === true) {
69
+ options.replace = true;
70
+ }
71
+ if (state.releaseAfter !== undefined) {
72
+ options.releaseAfter = state.releaseAfter;
73
+ }
74
+ return options;
75
+ },
76
+ };
77
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Lock Provider Implementation
3
+ * Provides distributed lock management for job deduplication
4
+ */
5
+ import type { LockProvider, LockProviderConfig } from '../../types/Queue';
6
+ /**
7
+ * Redis-based Lock Provider Implementation
8
+ */
9
+ export declare function createRedisLockProvider(config: LockProviderConfig): LockProvider;
10
+ export declare function createMemoryLockProvider(config: LockProviderConfig): LockProvider;
11
+ export declare function registerLockProvider(name: string, provider: LockProvider): void;
12
+ export declare function getLockProvider(name: string): LockProvider | undefined;
13
+ /**
14
+ * Clear all registered lock providers (for testing purposes)
15
+ * @internal
16
+ */
17
+ export declare function clearLockProviders(): void;
18
+ /**
19
+ * Create lock provider based on configuration
20
+ */
21
+ export declare function createLockProvider(config: LockProviderConfig): LockProvider;
22
+ //# sourceMappingURL=LockProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LockProvider.d.ts","sourceRoot":"","sources":["../../../../src/tools/queue/LockProvider.ts"],"names":[],"mappings":"AACA;;;GAGG;AAGH,OAAO,KAAK,EAGV,YAAY,EACZ,kBAAkB,EAEnB,MAAM,eAAe,CAAC;AAwKvB;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,kBAAkB,GAAG,YAAY,CAWhF;AAYD,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,kBAAkB,GAAG,YAAY,CA4FjF;AAOD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,IAAI,CAG/E;AACD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAEtE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,YAAY,CAS3E"}