@agentuity/server 0.1.16 → 0.1.18

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 (93) hide show
  1. package/dist/api/api.d.ts +11 -6
  2. package/dist/api/api.d.ts.map +1 -1
  3. package/dist/api/api.js +19 -12
  4. package/dist/api/api.js.map +1 -1
  5. package/dist/api/index.d.ts +1 -0
  6. package/dist/api/index.d.ts.map +1 -1
  7. package/dist/api/index.js +1 -0
  8. package/dist/api/index.js.map +1 -1
  9. package/dist/api/org/env-delete.d.ts.map +1 -1
  10. package/dist/api/org/env-delete.js.map +1 -1
  11. package/dist/api/org/env-get.d.ts.map +1 -1
  12. package/dist/api/org/env-get.js.map +1 -1
  13. package/dist/api/org/env-update.d.ts.map +1 -1
  14. package/dist/api/org/env-update.js.map +1 -1
  15. package/dist/api/queue/analytics.d.ts +108 -0
  16. package/dist/api/queue/analytics.d.ts.map +1 -0
  17. package/dist/api/queue/analytics.js +245 -0
  18. package/dist/api/queue/analytics.js.map +1 -0
  19. package/dist/api/queue/destinations.d.ts +108 -0
  20. package/dist/api/queue/destinations.d.ts.map +1 -0
  21. package/dist/api/queue/destinations.js +238 -0
  22. package/dist/api/queue/destinations.js.map +1 -0
  23. package/dist/api/queue/dlq.d.ts +100 -0
  24. package/dist/api/queue/dlq.d.ts.map +1 -0
  25. package/dist/api/queue/dlq.js +204 -0
  26. package/dist/api/queue/dlq.js.map +1 -0
  27. package/dist/api/queue/index.d.ts +55 -0
  28. package/dist/api/queue/index.d.ts.map +1 -0
  29. package/dist/api/queue/index.js +86 -0
  30. package/dist/api/queue/index.js.map +1 -0
  31. package/dist/api/queue/messages.d.ts +332 -0
  32. package/dist/api/queue/messages.d.ts.map +1 -0
  33. package/dist/api/queue/messages.js +637 -0
  34. package/dist/api/queue/messages.js.map +1 -0
  35. package/dist/api/queue/queues.d.ts +153 -0
  36. package/dist/api/queue/queues.d.ts.map +1 -0
  37. package/dist/api/queue/queues.js +319 -0
  38. package/dist/api/queue/queues.js.map +1 -0
  39. package/dist/api/queue/sources.d.ts +132 -0
  40. package/dist/api/queue/sources.d.ts.map +1 -0
  41. package/dist/api/queue/sources.js +285 -0
  42. package/dist/api/queue/sources.js.map +1 -0
  43. package/dist/api/queue/types.d.ts +1129 -0
  44. package/dist/api/queue/types.d.ts.map +1 -0
  45. package/dist/api/queue/types.js +949 -0
  46. package/dist/api/queue/types.js.map +1 -0
  47. package/dist/api/queue/util.d.ts +262 -0
  48. package/dist/api/queue/util.d.ts.map +1 -0
  49. package/dist/api/queue/util.js +171 -0
  50. package/dist/api/queue/util.js.map +1 -0
  51. package/dist/api/queue/validation.d.ts +247 -0
  52. package/dist/api/queue/validation.d.ts.map +1 -0
  53. package/dist/api/queue/validation.js +513 -0
  54. package/dist/api/queue/validation.js.map +1 -0
  55. package/dist/api/sandbox/get.d.ts.map +1 -1
  56. package/dist/api/sandbox/get.js +5 -0
  57. package/dist/api/sandbox/get.js.map +1 -1
  58. package/dist/api/sandbox/index.d.ts +3 -3
  59. package/dist/api/sandbox/index.d.ts.map +1 -1
  60. package/dist/api/sandbox/index.js +1 -1
  61. package/dist/api/sandbox/index.js.map +1 -1
  62. package/dist/api/sandbox/run.d.ts.map +1 -1
  63. package/dist/api/sandbox/run.js +5 -2
  64. package/dist/api/sandbox/run.js.map +1 -1
  65. package/dist/api/sandbox/snapshot-build.d.ts +2 -0
  66. package/dist/api/sandbox/snapshot-build.d.ts.map +1 -1
  67. package/dist/api/sandbox/snapshot-build.js +4 -0
  68. package/dist/api/sandbox/snapshot-build.js.map +1 -1
  69. package/dist/api/sandbox/snapshot.d.ts +143 -1
  70. package/dist/api/sandbox/snapshot.d.ts.map +1 -1
  71. package/dist/api/sandbox/snapshot.js +183 -4
  72. package/dist/api/sandbox/snapshot.js.map +1 -1
  73. package/package.json +4 -4
  74. package/src/api/api.ts +62 -13
  75. package/src/api/index.ts +1 -0
  76. package/src/api/org/env-delete.ts +1 -4
  77. package/src/api/org/env-get.ts +1 -4
  78. package/src/api/org/env-update.ts +1 -4
  79. package/src/api/queue/analytics.ts +313 -0
  80. package/src/api/queue/destinations.ts +321 -0
  81. package/src/api/queue/dlq.ts +283 -0
  82. package/src/api/queue/index.ts +261 -0
  83. package/src/api/queue/messages.ts +875 -0
  84. package/src/api/queue/queues.ts +448 -0
  85. package/src/api/queue/sources.ts +384 -0
  86. package/src/api/queue/types.ts +1253 -0
  87. package/src/api/queue/util.ts +204 -0
  88. package/src/api/queue/validation.ts +560 -0
  89. package/src/api/sandbox/get.ts +5 -0
  90. package/src/api/sandbox/index.ts +13 -1
  91. package/src/api/sandbox/run.ts +5 -2
  92. package/src/api/sandbox/snapshot-build.ts +4 -0
  93. package/src/api/sandbox/snapshot.ts +223 -5
@@ -0,0 +1,875 @@
1
+ import { z } from 'zod';
2
+ import { APIClient, APIResponseSchema, APIResponseSchemaNoData } from '../api';
3
+ import {
4
+ MessageSchema,
5
+ type Message,
6
+ type PublishMessageRequest,
7
+ type BatchPublishMessagesRequest,
8
+ type ListMessagesRequest,
9
+ type ConsumeMessagesRequest,
10
+ type QueueApiOptions,
11
+ PublishMessageRequestSchema,
12
+ BatchPublishMessagesRequestSchema,
13
+ } from './types';
14
+ import {
15
+ QueueError,
16
+ QueueNotFoundError,
17
+ MessageNotFoundError,
18
+ queueApiPath,
19
+ queueApiPathWithQuery,
20
+ buildQueueHeaders,
21
+ } from './util';
22
+ import {
23
+ validateQueueName,
24
+ validatePayload,
25
+ validateMessageId,
26
+ validatePartitionKey,
27
+ validateIdempotencyKey,
28
+ validateTTL,
29
+ validateOffset,
30
+ validateLimit,
31
+ validateBatchSize,
32
+ } from './validation';
33
+
34
+ const MessageResponseSchema = APIResponseSchema(z.object({ message: MessageSchema }));
35
+ const MessagesListResponseSchema = APIResponseSchema(
36
+ z.object({
37
+ messages: z.array(MessageSchema),
38
+ total: z.number().optional(),
39
+ })
40
+ );
41
+ const BatchPublishResponseSchema = APIResponseSchema(
42
+ z.object({
43
+ messages: z.array(MessageSchema),
44
+ failed: z.array(z.number()).optional(),
45
+ })
46
+ );
47
+ const DeleteMessageResponseSchema = APIResponseSchemaNoData();
48
+ const AckNackResponseSchema = APIResponseSchemaNoData();
49
+ const OffsetResponseSchema = APIResponseSchema(z.object({ offset: z.number() }));
50
+ const ReceiveResponseSchema = APIResponseSchema(
51
+ z.object({
52
+ message: MessageSchema.nullable(),
53
+ })
54
+ );
55
+
56
+ /**
57
+ * Publish a message to a queue.
58
+ *
59
+ * Publishes a single message to the specified queue. The message will be assigned
60
+ * a unique ID and offset.
61
+ *
62
+ * @param client - The API client instance
63
+ * @param queueName - The name of the queue to publish to
64
+ * @param params - Message parameters including payload
65
+ * @returns The published message with assigned ID and offset
66
+ * @throws {QueueValidationError} If validation fails
67
+ * @throws {QueueNotFoundError} If the queue does not exist
68
+ * @throws {QueueError} If the API request fails
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * const message = await publishMessage(client, 'order-queue', {
73
+ * payload: { orderId: 123, action: 'process' },
74
+ * metadata: { priority: 'high' },
75
+ * idempotency_key: 'order-123-process',
76
+ * });
77
+ * console.log(`Published message ${message.id} at offset ${message.offset}`);
78
+ * ```
79
+ */
80
+ export async function publishMessage(
81
+ client: APIClient,
82
+ queueName: string,
83
+ params: PublishMessageRequest,
84
+ options?: QueueApiOptions
85
+ ): Promise<Message> {
86
+ // Validate before sending to API
87
+ validateQueueName(queueName);
88
+ validatePayload(params.payload);
89
+ if (params.partition_key) {
90
+ validatePartitionKey(params.partition_key);
91
+ }
92
+ if (params.idempotency_key) {
93
+ validateIdempotencyKey(params.idempotency_key);
94
+ }
95
+ if (params.ttl_seconds !== undefined) {
96
+ validateTTL(params.ttl_seconds);
97
+ }
98
+
99
+ const url = queueApiPath('messages/publish', queueName);
100
+ const resp = await client.post(
101
+ url,
102
+ params,
103
+ MessageResponseSchema,
104
+ PublishMessageRequestSchema,
105
+ undefined,
106
+ buildQueueHeaders(options?.orgId)
107
+ );
108
+
109
+ if (resp.success) {
110
+ return resp.data.message;
111
+ }
112
+
113
+ if (resp.message?.includes('not found')) {
114
+ throw new QueueNotFoundError({
115
+ queueName,
116
+ message: resp.message,
117
+ });
118
+ }
119
+
120
+ throw new QueueError({
121
+ queueName,
122
+ message: resp.message || 'Failed to publish message',
123
+ });
124
+ }
125
+
126
+ /**
127
+ * Batch publish multiple messages to a queue.
128
+ *
129
+ * Publishes up to 1000 messages in a single API call. This is more efficient
130
+ * than publishing messages individually.
131
+ *
132
+ * @param client - The API client instance
133
+ * @param queueName - The name of the queue to publish to
134
+ * @param messages - Array of message parameters (max 1000)
135
+ * @returns Object containing the published messages and optionally failed indices
136
+ * @throws {QueueValidationError} If validation fails
137
+ * @throws {QueueNotFoundError} If the queue does not exist
138
+ * @throws {QueueError} If the API request fails
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * const result = await batchPublishMessages(client, 'order-queue', [
143
+ * { payload: { orderId: 1 } },
144
+ * { payload: { orderId: 2 } },
145
+ * { payload: { orderId: 3 } },
146
+ * ]);
147
+ * console.log(`Published ${result.messages.length} messages`);
148
+ * ```
149
+ */
150
+ export async function batchPublishMessages(
151
+ client: APIClient,
152
+ queueName: string,
153
+ messages: BatchPublishMessagesRequest['messages'],
154
+ options?: QueueApiOptions
155
+ ): Promise<{ messages: Message[]; failed?: number[] }> {
156
+ // Validate before sending to API
157
+ validateQueueName(queueName);
158
+ validateBatchSize(messages.length);
159
+ for (const msg of messages) {
160
+ validatePayload(msg.payload);
161
+ if (msg.partition_key) {
162
+ validatePartitionKey(msg.partition_key);
163
+ }
164
+ if (msg.idempotency_key) {
165
+ validateIdempotencyKey(msg.idempotency_key);
166
+ }
167
+ if (msg.ttl_seconds !== undefined) {
168
+ validateTTL(msg.ttl_seconds);
169
+ }
170
+ }
171
+
172
+ const url = queueApiPath('messages/batch', queueName);
173
+ const resp = await client.post(
174
+ url,
175
+ { messages },
176
+ BatchPublishResponseSchema,
177
+ BatchPublishMessagesRequestSchema,
178
+ undefined,
179
+ buildQueueHeaders(options?.orgId)
180
+ );
181
+
182
+ if (resp.success) {
183
+ return { messages: resp.data.messages, failed: resp.data.failed };
184
+ }
185
+
186
+ if (resp.message?.includes('not found')) {
187
+ throw new QueueNotFoundError({
188
+ queueName,
189
+ message: resp.message,
190
+ });
191
+ }
192
+
193
+ throw new QueueError({
194
+ queueName,
195
+ message: resp.message || 'Failed to batch publish messages',
196
+ });
197
+ }
198
+
199
+ /**
200
+ * Get a message by ID.
201
+ *
202
+ * Retrieves a specific message from a queue by its message ID.
203
+ *
204
+ * @param client - The API client instance
205
+ * @param queueName - The name of the queue
206
+ * @param messageId - The message ID (prefixed with msg_)
207
+ * @returns The message details
208
+ * @throws {QueueValidationError} If validation fails
209
+ * @throws {MessageNotFoundError} If the message does not exist
210
+ * @throws {QueueNotFoundError} If the queue does not exist
211
+ * @throws {QueueError} If the API request fails
212
+ *
213
+ * @example
214
+ * ```typescript
215
+ * const message = await getMessage(client, 'order-queue', 'msg_abc123');
216
+ * console.log(`Message state: ${message.state}`);
217
+ * ```
218
+ */
219
+ export async function getMessage(
220
+ client: APIClient,
221
+ queueName: string,
222
+ messageId: string,
223
+ options?: QueueApiOptions
224
+ ): Promise<Message> {
225
+ validateQueueName(queueName);
226
+ validateMessageId(messageId);
227
+
228
+ const url = queueApiPath('messages/get', queueName, messageId);
229
+ const resp = await client.get(
230
+ url,
231
+ MessageResponseSchema,
232
+ undefined,
233
+ buildQueueHeaders(options?.orgId)
234
+ );
235
+
236
+ if (resp.success) {
237
+ return resp.data.message;
238
+ }
239
+
240
+ if (resp.message?.includes('message') && resp.message?.includes('not found')) {
241
+ throw new MessageNotFoundError({
242
+ queueName,
243
+ messageId,
244
+ message: resp.message,
245
+ });
246
+ }
247
+
248
+ if (resp.message?.includes('queue') && resp.message?.includes('not found')) {
249
+ throw new QueueNotFoundError({
250
+ queueName,
251
+ message: resp.message,
252
+ });
253
+ }
254
+
255
+ throw new QueueError({
256
+ queueName,
257
+ message: resp.message || 'Failed to get message',
258
+ });
259
+ }
260
+
261
+ /**
262
+ * Get a message by its offset position.
263
+ *
264
+ * Retrieves a specific message from a queue by its offset (sequential position).
265
+ * Useful for log-style consumption where you track position by offset.
266
+ *
267
+ * @param client - The API client instance
268
+ * @param queueName - The name of the queue
269
+ * @param offset - The message offset (0-based sequential position)
270
+ * @returns The message at the specified offset
271
+ * @throws {QueueValidationError} If validation fails
272
+ * @throws {MessageNotFoundError} If no message exists at the offset
273
+ * @throws {QueueError} If the API request fails
274
+ *
275
+ * @example
276
+ * ```typescript
277
+ * const message = await getMessageByOffset(client, 'events', 42);
278
+ * console.log(`Message at offset 42: ${message.id}`);
279
+ * ```
280
+ */
281
+ export async function getMessageByOffset(
282
+ client: APIClient,
283
+ queueName: string,
284
+ offset: number,
285
+ options?: QueueApiOptions
286
+ ): Promise<Message> {
287
+ validateQueueName(queueName);
288
+ validateOffset(offset);
289
+
290
+ const url = queueApiPath('messages/offset', queueName, String(offset));
291
+ const resp = await client.get(
292
+ url,
293
+ MessageResponseSchema,
294
+ undefined,
295
+ buildQueueHeaders(options?.orgId)
296
+ );
297
+
298
+ if (resp.success) {
299
+ return resp.data.message;
300
+ }
301
+
302
+ if (resp.message?.includes('not found')) {
303
+ throw new MessageNotFoundError({
304
+ queueName,
305
+ messageId: `offset:${offset}`,
306
+ message: resp.message,
307
+ });
308
+ }
309
+
310
+ throw new QueueError({
311
+ queueName,
312
+ message: resp.message || 'Failed to get message by offset',
313
+ });
314
+ }
315
+
316
+ /**
317
+ * List messages in a queue.
318
+ *
319
+ * Retrieves messages from a queue with optional filtering and pagination.
320
+ * Supports filtering by state and pagination via limit/offset.
321
+ *
322
+ * @param client - The API client instance
323
+ * @param queueName - The name of the queue
324
+ * @param params - Optional filtering and pagination parameters
325
+ * @param params.limit - Maximum number of messages to return (1-1000)
326
+ * @param params.offset - Starting offset for pagination
327
+ * @param params.state - Filter by message state (pending, processing, completed, failed, dead)
328
+ * @returns Object containing messages array and optional total count
329
+ * @throws {QueueValidationError} If validation fails
330
+ * @throws {QueueNotFoundError} If the queue does not exist
331
+ * @throws {QueueError} If the API request fails
332
+ *
333
+ * @example
334
+ * ```typescript
335
+ * // List first 10 pending messages
336
+ * const result = await listMessages(client, 'order-queue', {
337
+ * limit: 10,
338
+ * state: 'pending',
339
+ * });
340
+ * console.log(`Found ${result.messages.length} pending messages`);
341
+ * ```
342
+ */
343
+ export async function listMessages(
344
+ client: APIClient,
345
+ queueName: string,
346
+ params?: ListMessagesRequest,
347
+ options?: QueueApiOptions
348
+ ): Promise<{ messages: Message[]; total?: number }> {
349
+ validateQueueName(queueName);
350
+ if (params?.limit !== undefined) {
351
+ validateLimit(params.limit);
352
+ }
353
+ if (params?.offset !== undefined) {
354
+ validateOffset(params.offset);
355
+ }
356
+
357
+ const searchParams = new URLSearchParams();
358
+ if (params?.limit !== undefined) {
359
+ searchParams.set('limit', String(params.limit));
360
+ }
361
+ if (params?.offset !== undefined) {
362
+ searchParams.set('offset', String(params.offset));
363
+ }
364
+ if (params?.state !== undefined) {
365
+ searchParams.set('state', params.state);
366
+ }
367
+
368
+ const queryString = searchParams.toString();
369
+ const url = queueApiPathWithQuery('messages/list', queryString || undefined, queueName);
370
+ const resp = await client.get(
371
+ url,
372
+ MessagesListResponseSchema,
373
+ undefined,
374
+ buildQueueHeaders(options?.orgId)
375
+ );
376
+
377
+ if (resp.success) {
378
+ return { messages: resp.data.messages, total: resp.data.total };
379
+ }
380
+
381
+ if (resp.message?.includes('not found')) {
382
+ throw new QueueNotFoundError({
383
+ queueName,
384
+ message: resp.message,
385
+ });
386
+ }
387
+
388
+ throw new QueueError({
389
+ queueName,
390
+ message: resp.message || 'Failed to list messages',
391
+ });
392
+ }
393
+
394
+ /**
395
+ * Delete a message from a queue.
396
+ *
397
+ * Permanently removes a message from the queue. This operation cannot be undone.
398
+ *
399
+ * @param client - The API client instance
400
+ * @param queueName - The name of the queue
401
+ * @param messageId - The message ID to delete (prefixed with msg_)
402
+ * @returns void
403
+ * @throws {QueueValidationError} If validation fails
404
+ * @throws {MessageNotFoundError} If the message does not exist
405
+ * @throws {QueueError} If the API request fails
406
+ *
407
+ * @example
408
+ * ```typescript
409
+ * await deleteMessage(client, 'order-queue', 'msg_abc123');
410
+ * console.log('Message deleted');
411
+ * ```
412
+ */
413
+ export async function deleteMessage(
414
+ client: APIClient,
415
+ queueName: string,
416
+ messageId: string,
417
+ options?: QueueApiOptions
418
+ ): Promise<void> {
419
+ validateQueueName(queueName);
420
+ validateMessageId(messageId);
421
+
422
+ const url = queueApiPath('messages/delete', queueName, messageId);
423
+ const resp = await client.delete(
424
+ url,
425
+ DeleteMessageResponseSchema,
426
+ undefined,
427
+ buildQueueHeaders(options?.orgId)
428
+ );
429
+
430
+ if (resp.success) {
431
+ return;
432
+ }
433
+
434
+ if (resp.message?.includes('message') && resp.message?.includes('not found')) {
435
+ throw new MessageNotFoundError({
436
+ queueName,
437
+ messageId,
438
+ message: resp.message,
439
+ });
440
+ }
441
+
442
+ throw new QueueError({
443
+ queueName,
444
+ message: resp.message || 'Failed to delete message',
445
+ });
446
+ }
447
+
448
+ /**
449
+ * Replay a message.
450
+ *
451
+ * Re-queues a previously processed message for reprocessing. The message
452
+ * is reset to pending state and will be delivered again to consumers.
453
+ * Useful for retrying failed messages or reprocessing historical data.
454
+ *
455
+ * @param client - The API client instance
456
+ * @param queueName - The name of the queue
457
+ * @param messageId - The message ID to replay (prefixed with msg_)
458
+ * @returns The replayed message with updated state
459
+ * @throws {QueueValidationError} If validation fails
460
+ * @throws {MessageNotFoundError} If the message does not exist
461
+ * @throws {QueueError} If the API request fails
462
+ *
463
+ * @example
464
+ * ```typescript
465
+ * const message = await replayMessage(client, 'order-queue', 'msg_abc123');
466
+ * console.log(`Message replayed, new state: ${message.state}`);
467
+ * ```
468
+ */
469
+ export async function replayMessage(
470
+ client: APIClient,
471
+ queueName: string,
472
+ messageId: string,
473
+ options?: QueueApiOptions
474
+ ): Promise<Message> {
475
+ validateQueueName(queueName);
476
+ validateMessageId(messageId);
477
+
478
+ const url = queueApiPath('messages/replay', queueName, messageId);
479
+ const resp = await client.post(
480
+ url,
481
+ undefined,
482
+ MessageResponseSchema,
483
+ undefined,
484
+ undefined,
485
+ buildQueueHeaders(options?.orgId)
486
+ );
487
+
488
+ if (resp.success) {
489
+ return resp.data.message;
490
+ }
491
+
492
+ if (resp.message?.includes('message') && resp.message?.includes('not found')) {
493
+ throw new MessageNotFoundError({
494
+ queueName,
495
+ messageId,
496
+ message: resp.message,
497
+ });
498
+ }
499
+
500
+ throw new QueueError({
501
+ queueName,
502
+ message: resp.message || 'Failed to replay message',
503
+ });
504
+ }
505
+
506
+ /**
507
+ * Consume messages from a queue starting at an offset.
508
+ *
509
+ * Retrieves messages for log-style consumption, starting from the specified
510
+ * offset. Unlike receive/ack flow, this does not mark messages as processing.
511
+ * Ideal for event sourcing or fan-out patterns where multiple consumers
512
+ * read the same messages.
513
+ *
514
+ * @param client - The API client instance
515
+ * @param queueName - The name of the queue
516
+ * @param params - Consume parameters
517
+ * @param params.offset - Starting offset (0-based)
518
+ * @param params.limit - Maximum messages to return (optional, 1-1000)
519
+ * @returns Object containing the messages array
520
+ * @throws {QueueValidationError} If validation fails
521
+ * @throws {QueueNotFoundError} If the queue does not exist
522
+ * @throws {QueueError} If the API request fails
523
+ *
524
+ * @example
525
+ * ```typescript
526
+ * // Consume 100 messages starting at offset 500
527
+ * const result = await consumeMessages(client, 'events', {
528
+ * offset: 500,
529
+ * limit: 100,
530
+ * });
531
+ * for (const msg of result.messages) {
532
+ * console.log(`Processing event at offset ${msg.offset}`);
533
+ * }
534
+ * ```
535
+ */
536
+ export async function consumeMessages(
537
+ client: APIClient,
538
+ queueName: string,
539
+ params: ConsumeMessagesRequest,
540
+ options?: QueueApiOptions
541
+ ): Promise<{ messages: Message[] }> {
542
+ validateQueueName(queueName);
543
+ validateOffset(params.offset);
544
+ if (params.limit !== undefined) {
545
+ validateLimit(params.limit);
546
+ }
547
+
548
+ const searchParams = new URLSearchParams();
549
+ searchParams.set('offset', String(params.offset));
550
+ if (params.limit !== undefined) {
551
+ searchParams.set('limit', String(params.limit));
552
+ }
553
+
554
+ const url = queueApiPathWithQuery('consume', searchParams.toString(), queueName);
555
+ const resp = await client.get(
556
+ url,
557
+ MessagesListResponseSchema,
558
+ undefined,
559
+ buildQueueHeaders(options?.orgId)
560
+ );
561
+
562
+ if (resp.success) {
563
+ return { messages: resp.data.messages };
564
+ }
565
+
566
+ if (resp.message?.includes('not found')) {
567
+ throw new QueueNotFoundError({
568
+ queueName,
569
+ message: resp.message,
570
+ });
571
+ }
572
+
573
+ throw new QueueError({
574
+ queueName,
575
+ message: resp.message || 'Failed to consume messages',
576
+ });
577
+ }
578
+
579
+ /**
580
+ * Get the head offset of a queue.
581
+ *
582
+ * Returns the offset of the oldest (first) message in the queue.
583
+ * Useful for determining the starting point for log-style consumption.
584
+ *
585
+ * @param client - The API client instance
586
+ * @param queueName - The name of the queue
587
+ * @returns The head offset (oldest message position)
588
+ * @throws {QueueValidationError} If validation fails
589
+ * @throws {QueueNotFoundError} If the queue does not exist
590
+ * @throws {QueueError} If the API request fails
591
+ *
592
+ * @example
593
+ * ```typescript
594
+ * const head = await getQueueHead(client, 'events');
595
+ * console.log(`Queue starts at offset ${head}`);
596
+ * ```
597
+ */
598
+ export async function getQueueHead(
599
+ client: APIClient,
600
+ queueName: string,
601
+ options?: QueueApiOptions
602
+ ): Promise<number> {
603
+ validateQueueName(queueName);
604
+ const url = queueApiPath('head', queueName);
605
+ const resp = await client.get(
606
+ url,
607
+ OffsetResponseSchema,
608
+ undefined,
609
+ buildQueueHeaders(options?.orgId)
610
+ );
611
+
612
+ if (resp.success) {
613
+ return resp.data.offset;
614
+ }
615
+
616
+ if (resp.message?.includes('not found')) {
617
+ throw new QueueNotFoundError({
618
+ queueName,
619
+ message: resp.message,
620
+ });
621
+ }
622
+
623
+ throw new QueueError({
624
+ queueName,
625
+ message: resp.message || 'Failed to get queue head',
626
+ });
627
+ }
628
+
629
+ /**
630
+ * Get the tail offset of a queue.
631
+ *
632
+ * Returns the offset of the newest (last) message in the queue.
633
+ * The next published message will have offset = tail + 1.
634
+ *
635
+ * @param client - The API client instance
636
+ * @param queueName - The name of the queue
637
+ * @returns The tail offset (newest message position)
638
+ * @throws {QueueValidationError} If validation fails
639
+ * @throws {QueueNotFoundError} If the queue does not exist
640
+ * @throws {QueueError} If the API request fails
641
+ *
642
+ * @example
643
+ * ```typescript
644
+ * const tail = await getQueueTail(client, 'events');
645
+ * console.log(`Queue ends at offset ${tail}`);
646
+ * ```
647
+ */
648
+ export async function getQueueTail(
649
+ client: APIClient,
650
+ queueName: string,
651
+ options?: QueueApiOptions
652
+ ): Promise<number> {
653
+ validateQueueName(queueName);
654
+ const url = queueApiPath('tail', queueName);
655
+ const resp = await client.get(
656
+ url,
657
+ OffsetResponseSchema,
658
+ undefined,
659
+ buildQueueHeaders(options?.orgId)
660
+ );
661
+
662
+ if (resp.success) {
663
+ return resp.data.offset;
664
+ }
665
+
666
+ if (resp.message?.includes('not found')) {
667
+ throw new QueueNotFoundError({
668
+ queueName,
669
+ message: resp.message,
670
+ });
671
+ }
672
+
673
+ throw new QueueError({
674
+ queueName,
675
+ message: resp.message || 'Failed to get queue tail',
676
+ });
677
+ }
678
+
679
+ /**
680
+ * Receive the next available message from a queue.
681
+ *
682
+ * Atomically retrieves and locks the next pending message for processing.
683
+ * The message state transitions to "processing" and must be acknowledged
684
+ * (ack) or negative-acknowledged (nack) when done. Supports long polling
685
+ * with an optional timeout.
686
+ *
687
+ * @param client - The API client instance
688
+ * @param queueName - The name of the queue
689
+ * @param timeout - Optional timeout in seconds for long polling (0-30)
690
+ * @returns The received message, or null if no message is available
691
+ * @throws {QueueValidationError} If validation fails
692
+ * @throws {QueueNotFoundError} If the queue does not exist
693
+ * @throws {QueueError} If the API request fails
694
+ *
695
+ * @example
696
+ * ```typescript
697
+ * // Receive with 10 second long poll
698
+ * const message = await receiveMessage(client, 'tasks', 10);
699
+ * if (message) {
700
+ * console.log(`Received: ${message.id}`);
701
+ * // Process message...
702
+ * await ackMessage(client, 'tasks', message.id);
703
+ * }
704
+ * ```
705
+ */
706
+ export async function receiveMessage(
707
+ client: APIClient,
708
+ queueName: string,
709
+ timeout?: number,
710
+ options?: QueueApiOptions
711
+ ): Promise<Message | null> {
712
+ validateQueueName(queueName);
713
+
714
+ const searchParams = new URLSearchParams();
715
+ if (timeout !== undefined) {
716
+ searchParams.set('timeout', String(timeout));
717
+ }
718
+
719
+ const queryString = searchParams.toString();
720
+ const url = queueApiPathWithQuery('receive', queryString || undefined, queueName);
721
+ const resp = await client.get(
722
+ url,
723
+ ReceiveResponseSchema,
724
+ undefined,
725
+ buildQueueHeaders(options?.orgId)
726
+ );
727
+
728
+ if (resp.success) {
729
+ return resp.data.message;
730
+ }
731
+
732
+ if (resp.message?.includes('not found')) {
733
+ throw new QueueNotFoundError({
734
+ queueName,
735
+ message: resp.message,
736
+ });
737
+ }
738
+
739
+ throw new QueueError({
740
+ queueName,
741
+ message: resp.message || 'Failed to receive message',
742
+ });
743
+ }
744
+
745
+ /**
746
+ * Acknowledge successful processing of a message.
747
+ *
748
+ * Marks a message as successfully processed (completed state).
749
+ * Should be called after successfully processing a message received
750
+ * via receiveMessage. The message will not be redelivered.
751
+ *
752
+ * @param client - The API client instance
753
+ * @param queueName - The name of the queue
754
+ * @param messageId - The message ID to acknowledge (prefixed with msg_)
755
+ * @returns void
756
+ * @throws {QueueValidationError} If validation fails
757
+ * @throws {MessageNotFoundError} If the message does not exist
758
+ * @throws {QueueError} If the API request fails
759
+ *
760
+ * @example
761
+ * ```typescript
762
+ * const message = await receiveMessage(client, 'tasks');
763
+ * if (message) {
764
+ * try {
765
+ * await processTask(message.payload);
766
+ * await ackMessage(client, 'tasks', message.id);
767
+ * } catch (error) {
768
+ * await nackMessage(client, 'tasks', message.id);
769
+ * }
770
+ * }
771
+ * ```
772
+ */
773
+ export async function ackMessage(
774
+ client: APIClient,
775
+ queueName: string,
776
+ messageId: string,
777
+ options?: QueueApiOptions
778
+ ): Promise<void> {
779
+ validateQueueName(queueName);
780
+ validateMessageId(messageId);
781
+
782
+ const url = queueApiPath('ack', queueName, messageId);
783
+ const resp = await client.post(
784
+ url,
785
+ undefined,
786
+ AckNackResponseSchema,
787
+ undefined,
788
+ undefined,
789
+ buildQueueHeaders(options?.orgId)
790
+ );
791
+
792
+ if (resp.success) {
793
+ return;
794
+ }
795
+
796
+ if (resp.message?.includes('message') && resp.message?.includes('not found')) {
797
+ throw new MessageNotFoundError({
798
+ queueName,
799
+ messageId,
800
+ message: resp.message,
801
+ });
802
+ }
803
+
804
+ throw new QueueError({
805
+ queueName,
806
+ message: resp.message || 'Failed to acknowledge message',
807
+ });
808
+ }
809
+
810
+ /**
811
+ * Negative acknowledge a message (mark as failed).
812
+ *
813
+ * Returns a message to the queue for retry. The message state returns
814
+ * to pending and will be redelivered. Use when processing fails and
815
+ * the message should be retried. After max retries, the message moves
816
+ * to the dead letter queue.
817
+ *
818
+ * @param client - The API client instance
819
+ * @param queueName - The name of the queue
820
+ * @param messageId - The message ID to nack (prefixed with msg_)
821
+ * @returns void
822
+ * @throws {QueueValidationError} If validation fails
823
+ * @throws {MessageNotFoundError} If the message does not exist
824
+ * @throws {QueueError} If the API request fails
825
+ *
826
+ * @example
827
+ * ```typescript
828
+ * const message = await receiveMessage(client, 'tasks');
829
+ * if (message) {
830
+ * try {
831
+ * await processTask(message.payload);
832
+ * await ackMessage(client, 'tasks', message.id);
833
+ * } catch (error) {
834
+ * // Processing failed, return to queue for retry
835
+ * await nackMessage(client, 'tasks', message.id);
836
+ * }
837
+ * }
838
+ * ```
839
+ */
840
+ export async function nackMessage(
841
+ client: APIClient,
842
+ queueName: string,
843
+ messageId: string,
844
+ options?: QueueApiOptions
845
+ ): Promise<void> {
846
+ validateQueueName(queueName);
847
+ validateMessageId(messageId);
848
+
849
+ const url = queueApiPath('nack', queueName, messageId);
850
+ const resp = await client.post(
851
+ url,
852
+ undefined,
853
+ AckNackResponseSchema,
854
+ undefined,
855
+ undefined,
856
+ buildQueueHeaders(options?.orgId)
857
+ );
858
+
859
+ if (resp.success) {
860
+ return;
861
+ }
862
+
863
+ if (resp.message?.includes('message') && resp.message?.includes('not found')) {
864
+ throw new MessageNotFoundError({
865
+ queueName,
866
+ messageId,
867
+ message: resp.message,
868
+ });
869
+ }
870
+
871
+ throw new QueueError({
872
+ queueName,
873
+ message: resp.message || 'Failed to negative acknowledge message',
874
+ });
875
+ }