@chaaskit/server 0.1.0

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 (189) hide show
  1. package/dist/api/admin.js +438 -0
  2. package/dist/api/admin.js.map +1 -0
  3. package/dist/api/agents.js +21 -0
  4. package/dist/api/agents.js.map +1 -0
  5. package/dist/api/api-keys.js +122 -0
  6. package/dist/api/api-keys.js.map +1 -0
  7. package/dist/api/auth.js +399 -0
  8. package/dist/api/auth.js.map +1 -0
  9. package/dist/api/chat.js +900 -0
  10. package/dist/api/chat.js.map +1 -0
  11. package/dist/api/config.js +91 -0
  12. package/dist/api/config.js.map +1 -0
  13. package/dist/api/documents.js +237 -0
  14. package/dist/api/documents.js.map +1 -0
  15. package/dist/api/export.js +107 -0
  16. package/dist/api/export.js.map +1 -0
  17. package/dist/api/health.js +25 -0
  18. package/dist/api/health.js.map +1 -0
  19. package/dist/api/mcp-server.js +84 -0
  20. package/dist/api/mcp-server.js.map +1 -0
  21. package/dist/api/mcp.js +400 -0
  22. package/dist/api/mcp.js.map +1 -0
  23. package/dist/api/mentions.js +94 -0
  24. package/dist/api/mentions.js.map +1 -0
  25. package/dist/api/oauth.js +366 -0
  26. package/dist/api/oauth.js.map +1 -0
  27. package/dist/api/payments.js +473 -0
  28. package/dist/api/payments.js.map +1 -0
  29. package/dist/api/projects.js +301 -0
  30. package/dist/api/projects.js.map +1 -0
  31. package/dist/api/scheduled-prompts.js +617 -0
  32. package/dist/api/scheduled-prompts.js.map +1 -0
  33. package/dist/api/search.js +85 -0
  34. package/dist/api/search.js.map +1 -0
  35. package/dist/api/share.js +188 -0
  36. package/dist/api/share.js.map +1 -0
  37. package/dist/api/slack.js +468 -0
  38. package/dist/api/slack.js.map +1 -0
  39. package/dist/api/teams.js +693 -0
  40. package/dist/api/teams.js.map +1 -0
  41. package/dist/api/templates.js +134 -0
  42. package/dist/api/templates.js.map +1 -0
  43. package/dist/api/threads.js +323 -0
  44. package/dist/api/threads.js.map +1 -0
  45. package/dist/api/upload.js +57 -0
  46. package/dist/api/upload.js.map +1 -0
  47. package/dist/api/user.js +111 -0
  48. package/dist/api/user.js.map +1 -0
  49. package/dist/api/v1/openai.js +245 -0
  50. package/dist/api/v1/openai.js.map +1 -0
  51. package/dist/app.js +168 -0
  52. package/dist/app.js.map +1 -0
  53. package/dist/bin/cli.js +57 -0
  54. package/dist/bin/cli.js.map +1 -0
  55. package/dist/commands/db-sync.js +108 -0
  56. package/dist/commands/db-sync.js.map +1 -0
  57. package/dist/config/loader.js +374 -0
  58. package/dist/config/loader.js.map +1 -0
  59. package/dist/documents/extractors.js +136 -0
  60. package/dist/documents/extractors.js.map +1 -0
  61. package/dist/extensions/glob.js +53 -0
  62. package/dist/extensions/glob.js.map +1 -0
  63. package/dist/extensions/loader.js +72 -0
  64. package/dist/extensions/loader.js.map +1 -0
  65. package/dist/index.js +25 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/loaders/index.js +75 -0
  68. package/dist/loaders/index.js.map +1 -0
  69. package/dist/mcp/client.js +551 -0
  70. package/dist/mcp/client.js.map +1 -0
  71. package/dist/mcp/server.js +335 -0
  72. package/dist/mcp/server.js.map +1 -0
  73. package/dist/middleware/apiKeyAuth.js +136 -0
  74. package/dist/middleware/apiKeyAuth.js.map +1 -0
  75. package/dist/middleware/auth.js +192 -0
  76. package/dist/middleware/auth.js.map +1 -0
  77. package/dist/middleware/errorHandler.js +41 -0
  78. package/dist/middleware/errorHandler.js.map +1 -0
  79. package/dist/middleware/mcpServerAuth.js +164 -0
  80. package/dist/middleware/mcpServerAuth.js.map +1 -0
  81. package/dist/middleware/requestLogger.js +9 -0
  82. package/dist/middleware/requestLogger.js.map +1 -0
  83. package/dist/middleware/team.js +132 -0
  84. package/dist/middleware/team.js.map +1 -0
  85. package/dist/oauth/server.js +410 -0
  86. package/dist/oauth/server.js.map +1 -0
  87. package/dist/queue/cli.js +93 -0
  88. package/dist/queue/cli.js.map +1 -0
  89. package/dist/queue/handlers/index.js +91 -0
  90. package/dist/queue/handlers/index.js.map +1 -0
  91. package/dist/queue/handlers/scheduled-prompt.js +270 -0
  92. package/dist/queue/handlers/scheduled-prompt.js.map +1 -0
  93. package/dist/queue/index.js +91 -0
  94. package/dist/queue/index.js.map +1 -0
  95. package/dist/queue/providers/memory.js +296 -0
  96. package/dist/queue/providers/memory.js.map +1 -0
  97. package/dist/queue/providers/sqs.js +275 -0
  98. package/dist/queue/providers/sqs.js.map +1 -0
  99. package/dist/queue/scheduler.js +355 -0
  100. package/dist/queue/scheduler.js.map +1 -0
  101. package/dist/queue/types.js +5 -0
  102. package/dist/queue/types.js.map +1 -0
  103. package/dist/queue/worker.js +230 -0
  104. package/dist/queue/worker.js.map +1 -0
  105. package/dist/registry/index.js +40 -0
  106. package/dist/registry/index.js.map +1 -0
  107. package/dist/server.js +207 -0
  108. package/dist/server.js.map +1 -0
  109. package/dist/services/agent.js +530 -0
  110. package/dist/services/agent.js.map +1 -0
  111. package/dist/services/agents.js +194 -0
  112. package/dist/services/agents.js.map +1 -0
  113. package/dist/services/documents.js +507 -0
  114. package/dist/services/documents.js.map +1 -0
  115. package/dist/services/email/index.js +91 -0
  116. package/dist/services/email/index.js.map +1 -0
  117. package/dist/services/email/providers/ses.js +97 -0
  118. package/dist/services/email/providers/ses.js.map +1 -0
  119. package/dist/services/email/templates.js +194 -0
  120. package/dist/services/email/templates.js.map +1 -0
  121. package/dist/services/email/types.js +5 -0
  122. package/dist/services/email/types.js.map +1 -0
  123. package/dist/services/encryption.js +69 -0
  124. package/dist/services/encryption.js.map +1 -0
  125. package/dist/services/oauth-discovery.js +226 -0
  126. package/dist/services/oauth-discovery.js.map +1 -0
  127. package/dist/services/pendingConfirmation.js +105 -0
  128. package/dist/services/pendingConfirmation.js.map +1 -0
  129. package/dist/services/scheduledPrompts.js +70 -0
  130. package/dist/services/scheduledPrompts.js.map +1 -0
  131. package/dist/services/slack/client.js +174 -0
  132. package/dist/services/slack/client.js.map +1 -0
  133. package/dist/services/slack/events.js +189 -0
  134. package/dist/services/slack/events.js.map +1 -0
  135. package/dist/services/slack/index.js +6 -0
  136. package/dist/services/slack/index.js.map +1 -0
  137. package/dist/services/slack/notifications.js +124 -0
  138. package/dist/services/slack/notifications.js.map +1 -0
  139. package/dist/services/slack/signature.js +74 -0
  140. package/dist/services/slack/signature.js.map +1 -0
  141. package/dist/services/slack/thread-context.js +191 -0
  142. package/dist/services/slack/thread-context.js.map +1 -0
  143. package/dist/services/toolConfirmation.js +55 -0
  144. package/dist/services/toolConfirmation.js.map +1 -0
  145. package/dist/services/usage.js +241 -0
  146. package/dist/services/usage.js.map +1 -0
  147. package/dist/ssr/build.js +90 -0
  148. package/dist/ssr/build.js.map +1 -0
  149. package/dist/ssr/components/SSRMessageList.js +120 -0
  150. package/dist/ssr/components/SSRMessageList.js.map +1 -0
  151. package/dist/ssr/entry.client.js +8 -0
  152. package/dist/ssr/entry.client.js.map +1 -0
  153. package/dist/ssr/entry.server.js +71 -0
  154. package/dist/ssr/entry.server.js.map +1 -0
  155. package/dist/ssr/handler.js +51 -0
  156. package/dist/ssr/handler.js.map +1 -0
  157. package/dist/ssr/root.js +184 -0
  158. package/dist/ssr/root.js.map +1 -0
  159. package/dist/ssr/routes/login.js +140 -0
  160. package/dist/ssr/routes/login.js.map +1 -0
  161. package/dist/ssr/routes/pricing.js +195 -0
  162. package/dist/ssr/routes/pricing.js.map +1 -0
  163. package/dist/ssr/routes/privacy.js +39 -0
  164. package/dist/ssr/routes/privacy.js.map +1 -0
  165. package/dist/ssr/routes/register.js +148 -0
  166. package/dist/ssr/routes/register.js.map +1 -0
  167. package/dist/ssr/routes/shared.$shareId.js +153 -0
  168. package/dist/ssr/routes/shared.$shareId.js.map +1 -0
  169. package/dist/ssr/routes/terms.js +39 -0
  170. package/dist/ssr/routes/terms.js.map +1 -0
  171. package/dist/storage/index.js +43 -0
  172. package/dist/storage/index.js.map +1 -0
  173. package/dist/storage/providers/database.js +38 -0
  174. package/dist/storage/providers/database.js.map +1 -0
  175. package/dist/storage/providers/filesystem.js +51 -0
  176. package/dist/storage/providers/filesystem.js.map +1 -0
  177. package/dist/storage/types.js +2 -0
  178. package/dist/storage/types.js.map +1 -0
  179. package/dist/tools/documents.js +336 -0
  180. package/dist/tools/documents.js.map +1 -0
  181. package/dist/tools/get-plan-usage.js +82 -0
  182. package/dist/tools/get-plan-usage.js.map +1 -0
  183. package/dist/tools/index.js +106 -0
  184. package/dist/tools/index.js.map +1 -0
  185. package/dist/tools/types.js +2 -0
  186. package/dist/tools/types.js.map +1 -0
  187. package/dist/tools/web-scrape.js +145 -0
  188. package/dist/tools/web-scrape.js.map +1 -0
  189. package/package.json +93 -0
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Scheduled Prompt Job Handlers
3
+ *
4
+ * Handles execution and notification of scheduled prompts (automations).
5
+ */
6
+ import { db } from '@chaaskit/db';
7
+ import { registerJobHandler } from '../index.js';
8
+ import { createAgentService } from '../../services/agent.js';
9
+ import { getAgentById, getDefaultAgent, toAgentConfig, isBuiltInAgent } from '../../services/agents.js';
10
+ import { sendEmail, isEmailEnabled } from '../../services/email/index.js';
11
+ import { getConfig } from '../../config/loader.js';
12
+ import { getQueueProvider } from '../index.js';
13
+ /**
14
+ * scheduled-prompt:execute
15
+ *
16
+ * Executes a scheduled prompt:
17
+ * 1. Loads the ScheduledPrompt with user/team context
18
+ * 2. Gets or creates the dedicated thread
19
+ * 3. Creates user message with the prompt
20
+ * 4. Runs the agent and collects the response
21
+ * 5. Saves the assistant message
22
+ * 6. Updates tracking (lastRunAt, runCount)
23
+ * 7. Enqueues notification job
24
+ */
25
+ registerJobHandler('scheduled-prompt:execute', async (job, ctx) => {
26
+ const { scheduledPromptId } = job.payload;
27
+ const startTime = Date.now();
28
+ ctx.log(`Executing scheduled prompt: ${scheduledPromptId}`);
29
+ // 1. Load the scheduled prompt
30
+ const prompt = await db.scheduledPrompt.findUnique({
31
+ where: { id: scheduledPromptId },
32
+ include: {
33
+ thread: true,
34
+ user: true,
35
+ team: {
36
+ include: {
37
+ slackIntegration: true,
38
+ },
39
+ },
40
+ },
41
+ });
42
+ if (!prompt) {
43
+ throw new Error(`Scheduled prompt not found: ${scheduledPromptId}`);
44
+ }
45
+ if (!prompt.enabled) {
46
+ ctx.log('Scheduled prompt is disabled, skipping execution');
47
+ return;
48
+ }
49
+ ctx.log(`Running prompt: "${prompt.name}"`);
50
+ try {
51
+ // 2. Get or create dedicated thread
52
+ let thread = prompt.thread;
53
+ if (!thread) {
54
+ thread = await db.thread.create({
55
+ data: {
56
+ title: `[Auto] ${prompt.name}`,
57
+ userId: prompt.userId,
58
+ teamId: prompt.teamId,
59
+ agentId: prompt.agentId,
60
+ },
61
+ });
62
+ // Update the scheduled prompt with the new thread
63
+ await db.scheduledPrompt.update({
64
+ where: { id: scheduledPromptId },
65
+ data: { threadId: thread.id },
66
+ });
67
+ ctx.log(`Created new thread: ${thread.id}`);
68
+ }
69
+ // 3. Create user message
70
+ await db.message.create({
71
+ data: {
72
+ threadId: thread.id,
73
+ role: 'user',
74
+ content: prompt.prompt,
75
+ },
76
+ });
77
+ // 4. Get agent and run
78
+ const agent = prompt.agentId
79
+ ? getAgentById(prompt.agentId)
80
+ : getDefaultAgent();
81
+ if (!agent) {
82
+ throw new Error(`Agent not found: ${prompt.agentId || 'default'}`);
83
+ }
84
+ const agentConfig = toAgentConfig(agent);
85
+ const agentService = createAgentService(agentConfig);
86
+ // Get system prompt
87
+ let systemPrompt = 'You are a helpful assistant.';
88
+ if (isBuiltInAgent(agent)) {
89
+ systemPrompt = agent.systemPrompt;
90
+ }
91
+ // Add team context if applicable
92
+ let teamContext = null;
93
+ if (prompt.team?.context) {
94
+ teamContext = prompt.team.context;
95
+ }
96
+ // Load thread messages for context
97
+ const messages = await db.message.findMany({
98
+ where: { threadId: thread.id },
99
+ orderBy: { createdAt: 'asc' },
100
+ });
101
+ const chatMessages = messages.map((m) => ({
102
+ role: m.role,
103
+ content: m.content,
104
+ }));
105
+ // Run agent
106
+ let result = '';
107
+ let inputTokens = 0;
108
+ let outputTokens = 0;
109
+ ctx.log('Running agent...');
110
+ for await (const chunk of agentService.chat(chatMessages, {
111
+ systemPrompt,
112
+ teamContext,
113
+ })) {
114
+ if (chunk.type === 'text' && chunk.content) {
115
+ result += chunk.content;
116
+ }
117
+ else if (chunk.type === 'usage' && chunk.usage) {
118
+ inputTokens = chunk.usage.inputTokens;
119
+ outputTokens = chunk.usage.outputTokens;
120
+ }
121
+ }
122
+ ctx.log(`Agent response: ${result.length} chars, ${inputTokens} in / ${outputTokens} out`);
123
+ // 5. Save assistant message
124
+ await db.message.create({
125
+ data: {
126
+ threadId: thread.id,
127
+ role: 'assistant',
128
+ content: result,
129
+ inputTokens,
130
+ outputTokens,
131
+ },
132
+ });
133
+ const runDuration = Date.now() - startTime;
134
+ // 6. Update tracking
135
+ await db.scheduledPrompt.update({
136
+ where: { id: scheduledPromptId },
137
+ data: {
138
+ lastRunAt: new Date(),
139
+ lastRunStatus: 'success',
140
+ lastError: null,
141
+ runCount: { increment: 1 },
142
+ },
143
+ });
144
+ // 7. Enqueue notification
145
+ if (prompt.notifySlack || prompt.notifyEmail) {
146
+ const queue = getQueueProvider();
147
+ await queue.enqueue('scheduled-prompt:notify', {
148
+ scheduledPromptId,
149
+ threadId: thread.id,
150
+ result,
151
+ runDuration,
152
+ });
153
+ ctx.log('Notification job enqueued');
154
+ }
155
+ ctx.log(`Completed in ${runDuration}ms`);
156
+ }
157
+ catch (error) {
158
+ const errorMessage = error instanceof Error ? error.message : String(error);
159
+ // Update tracking with failure
160
+ await db.scheduledPrompt.update({
161
+ where: { id: scheduledPromptId },
162
+ data: {
163
+ lastRunAt: new Date(),
164
+ lastRunStatus: 'failed',
165
+ lastError: errorMessage,
166
+ },
167
+ });
168
+ throw error;
169
+ }
170
+ }, 'Execute scheduled prompt and store result');
171
+ /**
172
+ * scheduled-prompt:notify
173
+ *
174
+ * Sends notifications after a scheduled prompt completes:
175
+ * 1. Slack notification (if enabled and team has integration)
176
+ * 2. Email notification (if enabled and recipients configured)
177
+ */
178
+ registerJobHandler('scheduled-prompt:notify', async (job, ctx) => {
179
+ const { scheduledPromptId, threadId, result, runDuration } = job.payload;
180
+ ctx.log(`Sending notifications for scheduled prompt: ${scheduledPromptId}`);
181
+ // Load prompt with team and slack integration
182
+ const prompt = await db.scheduledPrompt.findUnique({
183
+ where: { id: scheduledPromptId },
184
+ include: {
185
+ user: true,
186
+ team: {
187
+ include: {
188
+ slackIntegration: true,
189
+ },
190
+ },
191
+ },
192
+ });
193
+ if (!prompt) {
194
+ throw new Error(`Scheduled prompt not found: ${scheduledPromptId}`);
195
+ }
196
+ const config = getConfig();
197
+ const appUrl = config.app.url;
198
+ const appName = config.app.name;
199
+ const threadUrl = `${appUrl}/chat/${threadId}`;
200
+ // Helper to truncate text
201
+ const truncate = (text, maxLength) => {
202
+ if (text.length <= maxLength)
203
+ return text;
204
+ return text.slice(0, maxLength - 3) + '...';
205
+ };
206
+ // Slack notification
207
+ if (prompt.notifySlack && prompt.team?.slackIntegration) {
208
+ try {
209
+ const { SlackClient } = await import('../../services/slack/client.js');
210
+ const integration = prompt.team.slackIntegration;
211
+ if (integration.status === 'active' && integration.notificationChannel) {
212
+ const client = SlackClient.fromEncrypted(integration.encryptedTokens);
213
+ const message = [
214
+ `*${prompt.name}* completed in ${(runDuration / 1000).toFixed(1)}s`,
215
+ '',
216
+ truncate(result, 500),
217
+ '',
218
+ `<${threadUrl}|View full thread>`,
219
+ ].join('\n');
220
+ await client.postMessage(integration.notificationChannel, message);
221
+ ctx.log('Slack notification sent');
222
+ }
223
+ }
224
+ catch (error) {
225
+ ctx.log(`Slack notification failed: ${error instanceof Error ? error.message : error}`);
226
+ // Don't throw - continue with email
227
+ }
228
+ }
229
+ // Email notification
230
+ if (prompt.notifyEmail && prompt.emailRecipients && isEmailEnabled()) {
231
+ try {
232
+ const emails = JSON.parse(prompt.emailRecipients);
233
+ for (const email of emails) {
234
+ await sendEmail({
235
+ to: email,
236
+ subject: `[${appName}] ${prompt.name} completed`,
237
+ html: `
238
+ <div style="font-family: sans-serif; max-width: 600px; margin: 0 auto;">
239
+ <h2>${prompt.name}</h2>
240
+ <p style="color: #666;">Completed in ${(runDuration / 1000).toFixed(1)} seconds</p>
241
+
242
+ <div style="background: #f5f5f5; padding: 16px; border-radius: 8px; margin: 16px 0;">
243
+ <pre style="white-space: pre-wrap; word-break: break-word; margin: 0;">${truncate(result, 1000)}</pre>
244
+ </div>
245
+
246
+ <p>
247
+ <a href="${threadUrl}" style="display: inline-block; background: #4f46e5; color: white; padding: 8px 16px; text-decoration: none; border-radius: 4px;">
248
+ View Full Thread
249
+ </a>
250
+ </p>
251
+
252
+ <p style="color: #999; font-size: 12px; margin-top: 32px;">
253
+ This email was sent by ${appName}.
254
+ <a href="${appUrl}/automations">Manage automations</a>
255
+ </p>
256
+ </div>
257
+ `,
258
+ text: `${prompt.name} completed in ${(runDuration / 1000).toFixed(1)} seconds\n\n${truncate(result, 1000)}\n\nView thread: ${threadUrl}`,
259
+ });
260
+ ctx.log(`Email sent to ${email}`);
261
+ }
262
+ }
263
+ catch (error) {
264
+ ctx.log(`Email notification failed: ${error instanceof Error ? error.message : error}`);
265
+ throw error;
266
+ }
267
+ }
268
+ ctx.log('Notifications complete');
269
+ }, 'Send notifications for completed scheduled prompt');
270
+ //# sourceMappingURL=scheduled-prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduled-prompt.js","sourceRoot":"","sources":["../../../src/queue/handlers/scheduled-prompt.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ import { MemoryQueueProvider } from './providers/memory.js';
2
+ export * from './types.js';
3
+ // Re-export handlers
4
+ export { registerJobHandler, getJobHandler, hasJobHandler, getRegisteredJobTypes, unregisterJobHandler, clearJobHandlers, executeJob, } from './handlers/index.js';
5
+ // Re-export worker
6
+ export { Worker, startWorker, stopWorker, getWorker, resetWorker, } from './worker.js';
7
+ // Re-export scheduler
8
+ export { Scheduler, startScheduler, stopScheduler, getScheduler, resetScheduler, } from './scheduler.js';
9
+ let queueProvider = null;
10
+ /**
11
+ * Get default queue provider config for development
12
+ */
13
+ export function getDefaultProviderConfig() {
14
+ return {
15
+ type: 'memory',
16
+ maxHistorySize: 1000,
17
+ };
18
+ }
19
+ /**
20
+ * Create a queue provider based on configuration.
21
+ * Uses dynamic imports for optional dependencies (SQS SDK only loaded if needed).
22
+ */
23
+ export async function createQueueProvider(config) {
24
+ switch (config.type) {
25
+ case 'memory':
26
+ return new MemoryQueueProvider(config);
27
+ case 'sqs':
28
+ // Dynamic import - @aws-sdk/client-sqs is optional peer dependency
29
+ try {
30
+ const { SQSQueueProvider } = await import('./providers/sqs.js');
31
+ return new SQSQueueProvider(config);
32
+ }
33
+ catch (e) {
34
+ if (e.code === 'ERR_MODULE_NOT_FOUND') {
35
+ throw new Error('SQS provider requires @aws-sdk/client-sqs. Install it with: pnpm add @aws-sdk/client-sqs');
36
+ }
37
+ throw e;
38
+ }
39
+ default:
40
+ throw new Error(`Unknown queue provider type: ${config.type}`);
41
+ }
42
+ }
43
+ /**
44
+ * Get or create the queue provider singleton.
45
+ * Must be initialized with initializeQueueProvider() first, or returns a memory provider.
46
+ */
47
+ export function getQueueProvider() {
48
+ if (!queueProvider) {
49
+ // Default to memory provider if not initialized
50
+ console.warn('[Queue] Provider not initialized, using default memory provider');
51
+ queueProvider = new MemoryQueueProvider({ type: 'memory' });
52
+ }
53
+ return queueProvider;
54
+ }
55
+ /**
56
+ * Initialize the queue provider singleton.
57
+ * Call this during server startup with the app's queue config.
58
+ */
59
+ export async function initializeQueueProvider(config) {
60
+ if (queueProvider) {
61
+ console.warn('[Queue] Provider already initialized');
62
+ return queueProvider;
63
+ }
64
+ if (!config.enabled) {
65
+ console.log('[Queue] Queue system is disabled');
66
+ // Return a no-op provider that just logs
67
+ queueProvider = new MemoryQueueProvider({ type: 'memory' });
68
+ return queueProvider;
69
+ }
70
+ console.log(`[Queue] Initializing ${config.providerConfig.type} provider...`);
71
+ queueProvider = await createQueueProvider(config.providerConfig);
72
+ console.log(`[Queue] ${queueProvider.name} provider initialized`);
73
+ return queueProvider;
74
+ }
75
+ /**
76
+ * Close and reset the queue provider (for testing or shutdown)
77
+ */
78
+ export async function closeQueueProvider() {
79
+ if (queueProvider) {
80
+ await queueProvider.close();
81
+ queueProvider = null;
82
+ console.log('[Queue] Provider closed');
83
+ }
84
+ }
85
+ /**
86
+ * Reset the queue provider singleton (for testing)
87
+ */
88
+ export function resetQueueProvider() {
89
+ queueProvider = null;
90
+ }
91
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/queue/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,296 @@
1
+ import { EventEmitter } from 'events';
2
+ import { randomUUID } from 'crypto';
3
+ /**
4
+ * In-memory queue provider for development and testing.
5
+ * Uses EventEmitter for instant notification when jobs are enqueued.
6
+ */
7
+ export class MemoryQueueProvider {
8
+ name = 'memory';
9
+ jobs = new Map();
10
+ pendingQueue = []; // Job IDs in order
11
+ completedHistory = [];
12
+ maxHistorySize;
13
+ emitter;
14
+ deduplicationKeys = new Map(); // dedupKey -> jobId
15
+ closed = false;
16
+ constructor(config) {
17
+ this.maxHistorySize = config.maxHistorySize ?? 1000;
18
+ this.emitter = new EventEmitter();
19
+ this.emitter.setMaxListeners(100); // Allow many concurrent receivers
20
+ }
21
+ async enqueue(type, payload, options) {
22
+ if (this.closed) {
23
+ throw new Error('Queue provider is closed');
24
+ }
25
+ // Check deduplication
26
+ if (options?.deduplicationKey) {
27
+ const existingJobId = this.deduplicationKeys.get(options.deduplicationKey);
28
+ if (existingJobId) {
29
+ const existingJob = this.jobs.get(existingJobId);
30
+ if (existingJob && !['completed', 'failed', 'dead'].includes(existingJob.status)) {
31
+ // Return existing job instead of creating duplicate
32
+ return existingJob;
33
+ }
34
+ // Clean up old dedup key if job is done
35
+ this.deduplicationKeys.delete(options.deduplicationKey);
36
+ }
37
+ }
38
+ const now = new Date();
39
+ const jobOptions = {
40
+ maxRetries: options?.maxRetries ?? 3,
41
+ timeout: options?.timeout ?? 30000,
42
+ priority: options?.priority ?? 0,
43
+ deduplicationKey: options?.deduplicationKey,
44
+ };
45
+ // Calculate when job becomes visible
46
+ let visibleAt = now;
47
+ let status = 'pending';
48
+ let scheduledFor;
49
+ if (options?.scheduledFor) {
50
+ visibleAt = options.scheduledFor;
51
+ scheduledFor = options.scheduledFor;
52
+ status = 'scheduled';
53
+ }
54
+ else if (options?.delay) {
55
+ visibleAt = new Date(now.getTime() + options.delay);
56
+ scheduledFor = visibleAt;
57
+ status = options.delay > 0 ? 'scheduled' : 'pending';
58
+ }
59
+ const job = {
60
+ id: randomUUID(),
61
+ type,
62
+ payload,
63
+ options: jobOptions,
64
+ status,
65
+ attempts: 0,
66
+ createdAt: now,
67
+ scheduledFor,
68
+ visibleAt,
69
+ };
70
+ this.jobs.set(job.id, job);
71
+ if (options?.deduplicationKey) {
72
+ this.deduplicationKeys.set(options.deduplicationKey, job.id);
73
+ }
74
+ // Add to pending queue based on visibility
75
+ if (status === 'pending') {
76
+ this.insertIntoQueue(job.id);
77
+ // Emit event to wake up any waiting receivers
78
+ this.emitter.emit('job');
79
+ }
80
+ else {
81
+ // Schedule for later
82
+ const delay = visibleAt.getTime() - now.getTime();
83
+ setTimeout(() => {
84
+ const j = this.jobs.get(job.id);
85
+ if (j && j.status === 'scheduled') {
86
+ j.status = 'pending';
87
+ this.insertIntoQueue(job.id);
88
+ this.emitter.emit('job');
89
+ }
90
+ }, delay);
91
+ }
92
+ // Return job without internal fields
93
+ return this.toPublicJob(job);
94
+ }
95
+ async receive(maxMessages = 1, waitTimeSeconds = 20) {
96
+ if (this.closed) {
97
+ return [];
98
+ }
99
+ const jobs = this.getVisibleJobs(maxMessages);
100
+ if (jobs.length > 0) {
101
+ return jobs;
102
+ }
103
+ // Wait for jobs to arrive
104
+ return new Promise((resolve) => {
105
+ const timeout = setTimeout(() => {
106
+ this.emitter.removeListener('job', onJob);
107
+ resolve([]);
108
+ }, waitTimeSeconds * 1000);
109
+ const onJob = () => {
110
+ const jobs = this.getVisibleJobs(maxMessages);
111
+ if (jobs.length > 0) {
112
+ clearTimeout(timeout);
113
+ this.emitter.removeListener('job', onJob);
114
+ resolve(jobs);
115
+ }
116
+ };
117
+ this.emitter.on('job', onJob);
118
+ // Check if closed while waiting
119
+ const onClose = () => {
120
+ clearTimeout(timeout);
121
+ this.emitter.removeListener('job', onJob);
122
+ this.emitter.removeListener('close', onClose);
123
+ resolve([]);
124
+ };
125
+ this.emitter.once('close', onClose);
126
+ });
127
+ }
128
+ getVisibleJobs(maxMessages) {
129
+ const now = new Date();
130
+ const results = [];
131
+ const toRemove = [];
132
+ for (let i = 0; i < this.pendingQueue.length && results.length < maxMessages; i++) {
133
+ const jobId = this.pendingQueue[i];
134
+ const job = this.jobs.get(jobId);
135
+ if (!job) {
136
+ toRemove.push(i);
137
+ continue;
138
+ }
139
+ if (job.status !== 'pending' || job.visibleAt > now) {
140
+ continue;
141
+ }
142
+ // Mark as processing and create receipt handle
143
+ const receiptHandle = randomUUID();
144
+ job.status = 'processing';
145
+ job.receiptHandle = receiptHandle;
146
+ job.startedAt = now;
147
+ job.attempts += 1;
148
+ toRemove.push(i);
149
+ results.push({
150
+ ...this.toPublicJob(job),
151
+ receiptHandle,
152
+ });
153
+ }
154
+ // Remove processed jobs from queue (in reverse to maintain indices)
155
+ for (let i = toRemove.length - 1; i >= 0; i--) {
156
+ this.pendingQueue.splice(toRemove[i], 1);
157
+ }
158
+ return results;
159
+ }
160
+ async acknowledge(receiptHandle) {
161
+ const job = this.findByReceiptHandle(receiptHandle);
162
+ if (!job) {
163
+ throw new Error(`Job not found for receipt handle: ${receiptHandle}`);
164
+ }
165
+ job.status = 'completed';
166
+ job.completedAt = new Date();
167
+ delete job.receiptHandle;
168
+ // Add to completed history
169
+ this.completedHistory.push(job.id);
170
+ this.trimHistory();
171
+ // Clean up deduplication key
172
+ if (job.options.deduplicationKey) {
173
+ this.deduplicationKeys.delete(job.options.deduplicationKey);
174
+ }
175
+ }
176
+ async fail(receiptHandle, error) {
177
+ const job = this.findByReceiptHandle(receiptHandle);
178
+ if (!job) {
179
+ throw new Error(`Job not found for receipt handle: ${receiptHandle}`);
180
+ }
181
+ job.lastError = error.message;
182
+ delete job.receiptHandle;
183
+ if (job.attempts >= job.options.maxRetries) {
184
+ // No more retries, mark as dead
185
+ job.status = 'dead';
186
+ job.completedAt = new Date();
187
+ // Clean up deduplication key
188
+ if (job.options.deduplicationKey) {
189
+ this.deduplicationKeys.delete(job.options.deduplicationKey);
190
+ }
191
+ }
192
+ else {
193
+ // Requeue with exponential backoff
194
+ const backoffMs = Math.min(1000 * Math.pow(2, job.attempts), 60000);
195
+ job.status = 'pending';
196
+ job.visibleAt = new Date(Date.now() + backoffMs);
197
+ setTimeout(() => {
198
+ if (job.status === 'pending') {
199
+ this.insertIntoQueue(job.id);
200
+ this.emitter.emit('job');
201
+ }
202
+ }, backoffMs);
203
+ }
204
+ }
205
+ async getJob(jobId) {
206
+ const job = this.jobs.get(jobId);
207
+ return job ? this.toPublicJob(job) : null;
208
+ }
209
+ async getStats() {
210
+ let pending = 0;
211
+ let processing = 0;
212
+ let completed = 0;
213
+ let failed = 0;
214
+ let dead = 0;
215
+ let scheduled = 0;
216
+ for (const job of this.jobs.values()) {
217
+ switch (job.status) {
218
+ case 'pending':
219
+ pending++;
220
+ break;
221
+ case 'scheduled':
222
+ scheduled++;
223
+ break;
224
+ case 'processing':
225
+ processing++;
226
+ break;
227
+ case 'completed':
228
+ completed++;
229
+ break;
230
+ case 'failed':
231
+ failed++;
232
+ break;
233
+ case 'dead':
234
+ dead++;
235
+ break;
236
+ }
237
+ }
238
+ return { pending, processing, completed, failed, dead, scheduled };
239
+ }
240
+ async close() {
241
+ this.closed = true;
242
+ this.emitter.emit('close');
243
+ this.emitter.removeAllListeners();
244
+ }
245
+ findByReceiptHandle(receiptHandle) {
246
+ for (const job of this.jobs.values()) {
247
+ if (job.receiptHandle === receiptHandle) {
248
+ return job;
249
+ }
250
+ }
251
+ return undefined;
252
+ }
253
+ insertIntoQueue(jobId) {
254
+ const job = this.jobs.get(jobId);
255
+ if (!job)
256
+ return;
257
+ // Insert by priority (lower = higher priority)
258
+ let insertIndex = this.pendingQueue.length;
259
+ for (let i = 0; i < this.pendingQueue.length; i++) {
260
+ const existingJob = this.jobs.get(this.pendingQueue[i]);
261
+ if (existingJob && job.options.priority < existingJob.options.priority) {
262
+ insertIndex = i;
263
+ break;
264
+ }
265
+ }
266
+ this.pendingQueue.splice(insertIndex, 0, jobId);
267
+ }
268
+ trimHistory() {
269
+ // Remove oldest completed jobs if we exceed maxHistorySize
270
+ while (this.completedHistory.length > this.maxHistorySize) {
271
+ const oldJobId = this.completedHistory.shift();
272
+ if (oldJobId) {
273
+ const job = this.jobs.get(oldJobId);
274
+ if (job && job.status === 'completed') {
275
+ this.jobs.delete(oldJobId);
276
+ }
277
+ }
278
+ }
279
+ }
280
+ toPublicJob(job) {
281
+ return {
282
+ id: job.id,
283
+ type: job.type,
284
+ payload: job.payload,
285
+ options: job.options,
286
+ status: job.status,
287
+ attempts: job.attempts,
288
+ createdAt: job.createdAt,
289
+ scheduledFor: job.scheduledFor,
290
+ startedAt: job.startedAt,
291
+ completedAt: job.completedAt,
292
+ lastError: job.lastError,
293
+ };
294
+ }
295
+ }
296
+ //# sourceMappingURL=memory.js.map