@panorama-industries/effect-vercel-queue 0.0.0-stage → 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 (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +13 -2
  3. package/dist/contracts/message-handling.d.ts +128 -0
  4. package/dist/contracts/message-handling.d.ts.map +1 -0
  5. package/dist/contracts/message-handling.js +4 -0
  6. package/dist/contracts/message-handling.js.map +1 -0
  7. package/dist/contracts/queue-api.d.ts +255 -0
  8. package/dist/contracts/queue-api.d.ts.map +1 -0
  9. package/dist/contracts/queue-api.js +18 -0
  10. package/dist/contracts/queue-api.js.map +1 -0
  11. package/dist/contracts/queue-config.d.ts +87 -0
  12. package/dist/contracts/queue-config.d.ts.map +1 -0
  13. package/dist/contracts/queue-config.js +7 -0
  14. package/dist/contracts/queue-config.js.map +1 -0
  15. package/dist/contracts/queue-consumer.d.ts +30 -0
  16. package/dist/contracts/queue-consumer.d.ts.map +1 -0
  17. package/dist/contracts/queue-consumer.js +15 -0
  18. package/dist/contracts/queue-consumer.js.map +1 -0
  19. package/dist/contracts/queue-domain.d.ts +171 -0
  20. package/dist/contracts/queue-domain.d.ts.map +1 -0
  21. package/dist/contracts/queue-domain.js +137 -0
  22. package/dist/contracts/queue-domain.js.map +1 -0
  23. package/dist/contracts/queue-poller.d.ts +169 -0
  24. package/dist/contracts/queue-poller.d.ts.map +1 -0
  25. package/dist/contracts/queue-poller.js +10 -0
  26. package/dist/contracts/queue-poller.js.map +1 -0
  27. package/dist/contracts/queue-producer.d.ts +23 -0
  28. package/dist/contracts/queue-producer.d.ts.map +1 -0
  29. package/dist/contracts/queue-producer.js +5 -0
  30. package/dist/contracts/queue-producer.js.map +1 -0
  31. package/dist/contracts/topic.d.ts +47 -0
  32. package/dist/contracts/topic.d.ts.map +1 -0
  33. package/dist/contracts/topic.js +34 -0
  34. package/dist/contracts/topic.js.map +1 -0
  35. package/dist/implementation/queue-api-live.d.ts +16 -0
  36. package/dist/implementation/queue-api-live.d.ts.map +1 -0
  37. package/dist/implementation/queue-api-live.js +326 -0
  38. package/dist/implementation/queue-api-live.js.map +1 -0
  39. package/dist/implementation/queue-config-live.d.ts +37 -0
  40. package/dist/implementation/queue-config-live.d.ts.map +1 -0
  41. package/dist/implementation/queue-config-live.js +99 -0
  42. package/dist/implementation/queue-config-live.js.map +1 -0
  43. package/dist/implementation/queue-consumer-live.d.ts +14 -0
  44. package/dist/implementation/queue-consumer-live.d.ts.map +1 -0
  45. package/dist/implementation/queue-consumer-live.js +56 -0
  46. package/dist/implementation/queue-consumer-live.js.map +1 -0
  47. package/dist/implementation/queue-live.d.ts +15 -0
  48. package/dist/implementation/queue-live.d.ts.map +1 -0
  49. package/dist/implementation/queue-live.js +16 -0
  50. package/dist/implementation/queue-live.js.map +1 -0
  51. package/dist/implementation/queue-poller-live.d.ts +14 -0
  52. package/dist/implementation/queue-poller-live.d.ts.map +1 -0
  53. package/dist/implementation/queue-poller-live.js +66 -0
  54. package/dist/implementation/queue-poller-live.js.map +1 -0
  55. package/dist/implementation/queue-producer-live.d.ts +9 -0
  56. package/dist/implementation/queue-producer-live.d.ts.map +1 -0
  57. package/dist/implementation/queue-producer-live.js +54 -0
  58. package/dist/implementation/queue-producer-live.js.map +1 -0
  59. package/dist/index.d.ts +16 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +16 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/internal/cloud-event.d.ts +46 -0
  64. package/dist/internal/cloud-event.d.ts.map +1 -0
  65. package/dist/internal/cloud-event.js +140 -0
  66. package/dist/internal/cloud-event.js.map +1 -0
  67. package/dist/internal/lease.d.ts +23 -0
  68. package/dist/internal/lease.d.ts.map +1 -0
  69. package/dist/internal/lease.js +44 -0
  70. package/dist/internal/lease.js.map +1 -0
  71. package/dist/internal/multipart.d.ts +23 -0
  72. package/dist/internal/multipart.d.ts.map +1 -0
  73. package/dist/internal/multipart.js +205 -0
  74. package/dist/internal/multipart.js.map +1 -0
  75. package/dist/internal/process.d.ts +25 -0
  76. package/dist/internal/process.d.ts.map +1 -0
  77. package/dist/internal/process.js +95 -0
  78. package/dist/internal/process.js.map +1 -0
  79. package/dist/internal/queue-protocol.d.ts +38 -0
  80. package/dist/internal/queue-protocol.d.ts.map +1 -0
  81. package/dist/internal/queue-protocol.js +119 -0
  82. package/dist/internal/queue-protocol.js.map +1 -0
  83. package/dist/internal/version.d.ts +4 -0
  84. package/dist/internal/version.d.ts.map +1 -0
  85. package/dist/internal/version.js +4 -0
  86. package/dist/internal/version.js.map +1 -0
  87. package/dist/retry-policy.d.ts +26 -0
  88. package/dist/retry-policy.d.ts.map +1 -0
  89. package/dist/retry-policy.js +34 -0
  90. package/dist/retry-policy.js.map +1 -0
  91. package/dist/testing/index.d.ts +2 -0
  92. package/dist/testing/index.d.ts.map +1 -0
  93. package/dist/testing/index.js +2 -0
  94. package/dist/testing/index.js.map +1 -0
  95. package/dist/testing/test-queue.d.ts +74 -0
  96. package/dist/testing/test-queue.d.ts.map +1 -0
  97. package/dist/testing/test-queue.js +253 -0
  98. package/dist/testing/test-queue.js.map +1 -0
  99. package/package.json +59 -3
  100. package/src/contracts/message-handling.ts +68 -0
  101. package/src/contracts/queue-api.ts +199 -0
  102. package/src/contracts/queue-config.ts +47 -0
  103. package/src/contracts/queue-consumer.ts +40 -0
  104. package/src/contracts/queue-domain.ts +202 -0
  105. package/src/contracts/queue-poller.ts +76 -0
  106. package/src/contracts/queue-producer.ts +40 -0
  107. package/src/contracts/topic.ts +76 -0
  108. package/src/implementation/queue-api-live.ts +637 -0
  109. package/src/implementation/queue-config-live.ts +178 -0
  110. package/src/implementation/queue-consumer-live.ts +112 -0
  111. package/src/implementation/queue-live.ts +32 -0
  112. package/src/implementation/queue-poller-live.ts +130 -0
  113. package/src/implementation/queue-producer-live.ts +89 -0
  114. package/src/index.ts +19 -0
  115. package/src/internal/cloud-event.ts +244 -0
  116. package/src/internal/lease.ts +84 -0
  117. package/src/internal/multipart.ts +240 -0
  118. package/src/internal/process.ts +194 -0
  119. package/src/internal/queue-protocol.ts +169 -0
  120. package/src/internal/version.ts +5 -0
  121. package/src/retry-policy.ts +48 -0
  122. package/src/testing/index.ts +1 -0
  123. package/src/testing/test-queue.ts +469 -0
@@ -0,0 +1,2 @@
1
+ export * from "./test-queue.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1,74 @@
1
+ import { VercelRegion } from "@panorama-industries/effect-vercel-core";
2
+ import { Context, Duration, Effect, Layer, Option } from "effect";
3
+ import type { QueueLeaseError, QueueReceiveByIdError, QueueRequestError, QueueSendError } from "../contracts/queue-api.ts";
4
+ import { QueueApi } from "../contracts/queue-api.ts";
5
+ import { ConsumerGroupName, CursorFallback, MessageCorrupted, MessageId, ReceiptHandle, TopicName } from "../contracts/queue-domain.ts";
6
+ /** A `QueueApi` operation, as recorded by {@link TestQueue}. */
7
+ export type TestQueueOperation = "send" | "sendBatch" | "receive" | "receiveById" | "acknowledge" | "changeVisibility";
8
+ /** Errors each operation can be scripted to fail with. */
9
+ export interface TestQueueOperationError {
10
+ readonly send: QueueSendError;
11
+ readonly sendBatch: QueueSendError;
12
+ readonly receive: QueueRequestError | MessageCorrupted;
13
+ readonly receiveById: QueueReceiveByIdError;
14
+ readonly acknowledge: QueueLeaseError;
15
+ readonly changeVisibility: QueueLeaseError;
16
+ }
17
+ /** One call made to the test `QueueApi`, in call order. */
18
+ export interface TestQueueCall {
19
+ readonly operation: TestQueueOperation;
20
+ /** When the call was made, in epoch milliseconds. */
21
+ readonly at: number;
22
+ readonly topic: TopicName;
23
+ /** Region the call was routed to: the handle's region for lease calls. */
24
+ readonly region: VercelRegion;
25
+ readonly consumerGroup?: ConsumerGroupName;
26
+ readonly messageId?: MessageId;
27
+ readonly receiptHandle?: ReceiptHandle;
28
+ readonly visibilityTimeout?: Duration.Duration;
29
+ readonly limit?: number;
30
+ readonly cursorFallback?: CursorFallback;
31
+ readonly experimentalHandoff?: boolean;
32
+ readonly experimentalIfCoalesced?: number;
33
+ }
34
+ /** Where one message stands for one consumer group. */
35
+ export interface TestQueueDelivery {
36
+ readonly status: "pending" | "leased" | "acknowledged";
37
+ readonly deliveryCount: number;
38
+ readonly handoffCount: number;
39
+ /** When the message becomes receivable again, in epoch milliseconds. */
40
+ readonly visibleAt: number;
41
+ readonly receiptHandle: Option.Option<ReceiptHandle>;
42
+ readonly coalescedCount: number;
43
+ }
44
+ declare const TestQueue_base: Context.ServiceClass<TestQueue, "@effect-vercel/queue/TestQueue", {
45
+ /** Every `QueueApi` call so far, in order, including calls that failed. */
46
+ readonly calls: Effect.Effect<ReadonlyArray<TestQueueCall>>;
47
+ /** The state of one message for one consumer group; none before it was first leased. */
48
+ readonly delivery: (topic: TopicName, consumerGroup: ConsumerGroupName, messageId: MessageId) => Effect.Effect<Option.Option<TestQueueDelivery>>;
49
+ /** Make the next call to `operation` fail with `error` instead of running. */
50
+ readonly failNext: <Op extends TestQueueOperation>(operation: Op, error: TestQueueOperationError[Op]) => Effect.Effect<void>;
51
+ /**
52
+ * **Experimental.** Coalesce `count` messages into the current delivery, as a concurrency key
53
+ * does: acknowledgments are refused with `ConcurrencyCoalescedPending` until they cover it.
54
+ */
55
+ readonly coalesce: (topic: TopicName, consumerGroup: ConsumerGroupName, messageId: MessageId, count: number) => Effect.Effect<void>;
56
+ }>;
57
+ /**
58
+ * Inspects and scripts the in-memory queue behind {@link TestQueue.layer}.
59
+ *
60
+ * Time follows `Clock`, so `TestClock` drives lease expiry and delays.
61
+ */
62
+ export declare class TestQueue extends TestQueue_base {
63
+ /**
64
+ * An in-memory `QueueApi` with real lease semantics, plus the {@link TestQueue} controls.
65
+ *
66
+ * Each consumer group sees every message of a topic. Leases expire after their visibility
67
+ * timeout; acknowledging or changing visibility needs the current receipt handle (409 otherwise,
68
+ * 404 once acknowledged). Leasing by identifier answers 404, 409 or 410 like the service.
69
+ * Calls without a region go to `iad1`.
70
+ */
71
+ static readonly layer: Layer.Layer<QueueApi | TestQueue>;
72
+ }
73
+ export {};
74
+ //# sourceMappingURL=test-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-queue.d.ts","sourceRoot":"","sources":["../../src/testing/test-queue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,253 @@
1
+ import { VercelRegion } from "@panorama-industries/effect-vercel-core";
2
+ import { Clock, Context, DateTime, Duration, Effect, Layer, Option } from "effect";
3
+ import { BatchSendResult, QueueApi, SendResult } from "../contracts/queue-api.js";
4
+ import { ConcurrencyCoalescedPending, ConsumerGroupName, CursorFallback, MessageAlreadyProcessed, MessageCorrupted, MessageHandle, MessageId, MessageLocked, MessageMetadata, MessageNotAvailable, MessageNotFound, MessageUnavailable, ReceiptHandle, ReceivedMessage, TopicName, } from "../contracts/queue-domain.js";
5
+ const DEFAULT_RETENTION = Duration.hours(24);
6
+ const DEFAULT_VISIBILITY_TIMEOUT = Duration.seconds(30);
7
+ /**
8
+ * Inspects and scripts the in-memory queue behind {@link TestQueue.layer}.
9
+ *
10
+ * Time follows `Clock`, so `TestClock` drives lease expiry and delays.
11
+ */
12
+ export class TestQueue extends Context.Service()("@effect-vercel/queue/TestQueue") {
13
+ /**
14
+ * An in-memory `QueueApi` with real lease semantics, plus the {@link TestQueue} controls.
15
+ *
16
+ * Each consumer group sees every message of a topic. Leases expire after their visibility
17
+ * timeout; acknowledging or changing visibility needs the current receipt handle (409 otherwise,
18
+ * 404 once acknowledged). Leasing by identifier answers 404, 409 or 410 like the service.
19
+ * Calls without a region go to `iad1`.
20
+ */
21
+ static layer = Layer.effectContext(Effect.sync(() => {
22
+ const defaultRegion = VercelRegion.make("iad1");
23
+ const topics = new Map();
24
+ const deliveries = new Map();
25
+ const recorded = [];
26
+ const scripted = new Map();
27
+ let sequence = 0;
28
+ const key = (topic, group, id) => `${topic}\u0000${group}\u0000${id}`;
29
+ const nextId = (prefix) => `${prefix}_${++sequence}`;
30
+ const call = (entry, run) => Effect.flatMap(Clock.currentTimeMillis, (now) => {
31
+ recorded.push({ ...entry, at: now });
32
+ const failure = scripted.get(entry.operation)?.shift();
33
+ if (failure !== undefined) {
34
+ return Effect.fail(failure);
35
+ }
36
+ return run(now);
37
+ });
38
+ const findMessage = (topic, id, now) => Option.fromNullishOr(topics.get(topic)?.find((message) => message.id === id && message.expiresAt > now));
39
+ const deliveryOf = (topic, group, id) => {
40
+ const k = key(topic, group, id);
41
+ let state = deliveries.get(k);
42
+ if (state === undefined) {
43
+ state = {
44
+ status: "pending",
45
+ deliveryCount: 0,
46
+ handoffCount: 0,
47
+ visibleAt: 0,
48
+ receiptHandle: Option.none(),
49
+ coalescedCount: 0,
50
+ };
51
+ deliveries.set(k, state);
52
+ }
53
+ return state;
54
+ };
55
+ const isLeased = (state, now) => state.status === "leased" && state.visibleAt > now;
56
+ const lease = (topic, group, message, state, now, timeout, region) => {
57
+ const receiptHandle = ReceiptHandle.make(nextId("rh"));
58
+ state.status = "leased";
59
+ state.deliveryCount += 1;
60
+ state.visibleAt = now + Duration.toMillis(timeout);
61
+ state.receiptHandle = Option.some(receiptHandle);
62
+ return new ReceivedMessage({
63
+ handle: new MessageHandle({
64
+ topic,
65
+ consumerGroup: group,
66
+ messageId: message.id,
67
+ receiptHandle,
68
+ region,
69
+ }),
70
+ metadata: new MessageMetadata({
71
+ messageId: message.id,
72
+ deliveryCount: state.deliveryCount,
73
+ createdAt: DateTime.makeUnsafe(message.createdAt),
74
+ expiresAt: DateTime.makeUnsafe(message.expiresAt),
75
+ topic,
76
+ consumerGroup: group,
77
+ region,
78
+ ...(state.handoffCount > 0 ? { experimentalHandoffCount: state.handoffCount } : {}),
79
+ }),
80
+ contentType: message.contentType,
81
+ body: message.body,
82
+ });
83
+ };
84
+ const publish = (topic, message, now) => {
85
+ const stored = topics.get(topic) ?? [];
86
+ topics.set(topic, stored);
87
+ if (message.idempotencyKey !== undefined) {
88
+ const duplicate = stored.find((existing) => existing.idempotencyKey === message.idempotencyKey && existing.expiresAt > now);
89
+ if (duplicate !== undefined)
90
+ return duplicate.id;
91
+ }
92
+ const id = MessageId.make(nextId("msg"));
93
+ const retention = message.retention === undefined
94
+ ? DEFAULT_RETENTION
95
+ : Duration.fromInputUnsafe(message.retention);
96
+ stored.push({
97
+ id,
98
+ body: message.body,
99
+ contentType: message.contentType,
100
+ createdAt: now,
101
+ expiresAt: now + Duration.toMillis(retention),
102
+ availableAt: now + (message.delay === undefined ? 0 : Duration.toMillis(message.delay)),
103
+ idempotencyKey: message.idempotencyKey,
104
+ });
105
+ return id;
106
+ };
107
+ const validateLease = (handle, now) => {
108
+ const message = findMessage(handle.topic, handle.messageId, now);
109
+ const state = deliveries.get(key(handle.topic, handle.consumerGroup, handle.messageId));
110
+ if (Option.isNone(message) || state === undefined || state.status === "acknowledged") {
111
+ return Effect.fail(new MessageNotFound({ messageId: handle.messageId }));
112
+ }
113
+ const held = isLeased(state, now) &&
114
+ Option.getOrUndefined(state.receiptHandle) === handle.receiptHandle;
115
+ if (!held) {
116
+ return Effect.fail(new MessageNotAvailable({
117
+ messageId: handle.messageId,
118
+ reason: new MessageUnavailable({ message: "The lease is no longer held" }),
119
+ }));
120
+ }
121
+ return Effect.succeed(state);
122
+ };
123
+ const api = QueueApi.of({
124
+ send: (topic, message) => call({ operation: "send", topic, region: defaultRegion }, (now) => Effect.succeed(SendResult.Sent({ messageId: publish(topic, message, now) }))),
125
+ sendBatch: (topic, messages) => call({ operation: "sendBatch", topic, region: defaultRegion }, (now) => Effect.succeed(messages.map((message) => BatchSendResult.Sent({ messageId: publish(topic, message, now) })))),
126
+ receive: (topic, consumerGroup, options) => {
127
+ const region = options?.region ?? defaultRegion;
128
+ const timeout = Duration.fromInputUnsafe(options?.visibilityTimeout ?? DEFAULT_VISIBILITY_TIMEOUT);
129
+ const limit = options?.limit ?? 1;
130
+ return call({
131
+ operation: "receive",
132
+ topic,
133
+ region,
134
+ consumerGroup,
135
+ visibilityTimeout: timeout,
136
+ limit,
137
+ ...(options?.cursorFallback === undefined
138
+ ? {}
139
+ : { cursorFallback: options.cursorFallback }),
140
+ }, (now) => Effect.succeed((topics.get(topic) ?? [])
141
+ .filter((message) => message.expiresAt > now && message.availableAt <= now)
142
+ .map((message) => ({
143
+ message,
144
+ state: deliveryOf(topic, consumerGroup, message.id),
145
+ }))
146
+ .filter(({ state }) => state.status !== "acknowledged" && state.visibleAt <= now)
147
+ .slice(0, limit)
148
+ .map(({ message, state }) => lease(topic, consumerGroup, message, state, now, timeout, region))));
149
+ },
150
+ receiveById: (topic, consumerGroup, messageId, options) => {
151
+ const region = options?.region ?? defaultRegion;
152
+ const timeout = Duration.fromInputUnsafe(options?.visibilityTimeout ?? DEFAULT_VISIBILITY_TIMEOUT);
153
+ return call({
154
+ operation: "receiveById",
155
+ topic,
156
+ region,
157
+ consumerGroup,
158
+ messageId,
159
+ visibilityTimeout: timeout,
160
+ }, (now) => {
161
+ const message = findMessage(topic, messageId, now);
162
+ if (Option.isNone(message)) {
163
+ return Effect.fail(new MessageNotFound({ messageId }));
164
+ }
165
+ const state = deliveryOf(topic, consumerGroup, messageId);
166
+ if (state.status === "acknowledged") {
167
+ return Effect.fail(new MessageAlreadyProcessed({ messageId }));
168
+ }
169
+ if (isLeased(state, now)) {
170
+ return Effect.fail(new MessageNotAvailable({
171
+ messageId,
172
+ reason: new MessageLocked({
173
+ retryAfter: Option.some(Duration.millis(state.visibleAt - now)),
174
+ }),
175
+ }));
176
+ }
177
+ if (message.value.availableAt > now || state.visibleAt > now) {
178
+ return Effect.fail(new MessageNotAvailable({
179
+ messageId,
180
+ reason: new MessageUnavailable({ message: "The message is not visible yet" }),
181
+ }));
182
+ }
183
+ return Effect.succeed(lease(topic, consumerGroup, message.value, state, now, timeout, region));
184
+ });
185
+ },
186
+ acknowledge: (handle, options) => call({
187
+ operation: "acknowledge",
188
+ topic: handle.topic,
189
+ region: handle.region,
190
+ consumerGroup: handle.consumerGroup,
191
+ messageId: handle.messageId,
192
+ receiptHandle: handle.receiptHandle,
193
+ ...(options?.experimentalIfCoalesced === undefined
194
+ ? {}
195
+ : { experimentalIfCoalesced: options.experimentalIfCoalesced }),
196
+ }, (now) => Effect.flatMap(validateLease(handle, now), (state) => {
197
+ if (state.coalescedCount > (options?.experimentalIfCoalesced ?? 0)) {
198
+ return Effect.fail(new MessageNotAvailable({
199
+ messageId: handle.messageId,
200
+ reason: new ConcurrencyCoalescedPending({
201
+ coalescedCount: state.coalescedCount,
202
+ }),
203
+ }));
204
+ }
205
+ state.status = "acknowledged";
206
+ state.receiptHandle = Option.none();
207
+ state.coalescedCount = 0;
208
+ return Effect.void;
209
+ })),
210
+ changeVisibility: (handle, visibilityTimeout, options) => {
211
+ const timeout = Duration.fromInputUnsafe(visibilityTimeout);
212
+ const handoff = options?.experimentalHandoff === true;
213
+ return call({
214
+ operation: "changeVisibility",
215
+ topic: handle.topic,
216
+ region: handle.region,
217
+ consumerGroup: handle.consumerGroup,
218
+ messageId: handle.messageId,
219
+ receiptHandle: handle.receiptHandle,
220
+ visibilityTimeout: timeout,
221
+ ...(handoff ? { experimentalHandoff: true } : {}),
222
+ }, (now) => Effect.map(validateLease(handle, now), (state) => {
223
+ const releases = Duration.isZero(timeout);
224
+ state.visibleAt = now + Duration.toMillis(timeout);
225
+ if (releases) {
226
+ state.status = "pending";
227
+ state.receiptHandle = Option.none();
228
+ }
229
+ if (!handoff)
230
+ return Option.none();
231
+ if (!releases)
232
+ return Option.some("not_immediate");
233
+ state.handoffCount += 1;
234
+ return Option.some("accepted");
235
+ }));
236
+ },
237
+ });
238
+ const control = TestQueue.of({
239
+ calls: Effect.sync(() => [...recorded]),
240
+ delivery: (topic, consumerGroup, messageId) => Effect.sync(() => Option.map(Option.fromNullishOr(deliveries.get(key(topic, consumerGroup, messageId))), (state) => ({ ...state }))),
241
+ failNext: (operation, error) => Effect.sync(() => {
242
+ const queue = scripted.get(operation) ?? [];
243
+ queue.push(error);
244
+ scripted.set(operation, queue);
245
+ }),
246
+ coalesce: (topic, consumerGroup, messageId, count) => Effect.sync(() => {
247
+ deliveryOf(topic, consumerGroup, messageId).coalescedCount = count;
248
+ }),
249
+ });
250
+ return Context.make(QueueApi, api).pipe(Context.add(TestQueue, control));
251
+ }));
252
+ }
253
+ //# sourceMappingURL=test-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-queue.js","sourceRoot":"","sources":["../../src/testing/test-queue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,yCAAyC,CAAC;AACvE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAYnF,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAClF,OAAO,EACL,2BAA2B,EAC3B,iBAAiB,EACjB,cAAc,EACd,uBAAuB,EACvB,gBAAgB,EAChB,aAAa,EACb,SAAS,EACT,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,eAAe,EACf,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,SAAS,GACV,MAAM,8BAA8B,CAAC;AAqEtC,MAAM,iBAAiB,GAAG,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AAC7C,MAAM,0BAA0B,GAAG,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;AAExD;;;;GAIG;AACH,MAAM,OAAO,SAAU,SAAQ,OAAO,CAAC,OAAO,EA8B3C,CAAC,gCAAgC,CAAC;IACnC;;;;;;;OAOG;IACH,MAAM,CAAU,KAAK,GAAsC,KAAK,CAAC,aAAa,CAC5E,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;QACf,MAAM,aAAa,GAAG,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAChD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAmC,CAAC;QAC1D,MAAM,UAAU,GAAG,IAAI,GAAG,EAA2B,CAAC;QACtD,MAAM,QAAQ,GAAyB,EAAE,CAAC;QAC1C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAsC,CAAC;QAC/D,IAAI,QAAQ,GAAG,CAAC,CAAC;QAEjB,MAAM,GAAG,GAAG,CAAC,KAAa,EAAE,KAAa,EAAE,EAAU,EAAE,EAAE,CAAC,GAAG,KAAK,SAAS,KAAK,SAAS,EAAE,EAAE,CAAC;QAC9F,MAAM,MAAM,GAAG,CAAC,MAAc,EAAE,EAAE,CAAC,GAAG,MAAM,IAAI,EAAE,QAAQ,EAAE,CAAC;QAE7D,MAAM,IAAI,GAAG,CACX,KAA6D,EAC7D,GAAmE,EACpB,EAAE,CACjD,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAC,GAAG,EAAE,EAAE;YAC9C,QAAQ,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;YACrC,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC;YACvD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;gBAC1B,OAAO,MAAM,CAAC,IAAI,CAAC,OAAsC,CAAC,CAAC;YAC7D,CAAC;YACD,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;QAEL,MAAM,WAAW,GAAG,CAAC,KAAgB,EAAE,EAAU,EAAE,GAAW,EAAE,EAAE,CAChE,MAAM,CAAC,aAAa,CAClB,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,IAAI,OAAO,CAAC,SAAS,GAAG,GAAG,CAAC,CACnF,CAAC;QAEJ,MAAM,UAAU,GAAG,CAAC,KAAgB,EAAE,KAAwB,EAAE,EAAa,EAAE,EAAE;YAC/E,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;YAChC,IAAI,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC9B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxB,KAAK,GAAG;oBACN,MAAM,EAAE,SAAS;oBACjB,aAAa,EAAE,CAAC;oBAChB,YAAY,EAAE,CAAC;oBACf,SAAS,EAAE,CAAC;oBACZ,aAAa,EAAE,MAAM,CAAC,IAAI,EAAE;oBAC5B,cAAc,EAAE,CAAC;iBAClB,CAAC;gBACF,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;YAC3B,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC,CAAC;QAEF,MAAM,QAAQ,GAAG,CAAC,KAAsB,EAAE,GAAW,EAAE,EAAE,CACvD,KAAK,CAAC,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,SAAS,GAAG,GAAG,CAAC;QAErD,MAAM,KAAK,GAAG,CACZ,KAAgB,EAChB,KAAwB,EACxB,OAAsB,EACtB,KAAsB,EACtB,GAAW,EACX,OAA0B,EAC1B,MAAoB,EACpB,EAAE;YACF,MAAM,aAAa,GAAG,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;YACvD,KAAK,CAAC,MAAM,GAAG,QAAQ,CAAC;YACxB,KAAK,CAAC,aAAa,IAAI,CAAC,CAAC;YACzB,KAAK,CAAC,SAAS,GAAG,GAAG,GAAG,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YACnD,KAAK,CAAC,aAAa,GAAG,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YACjD,OAAO,IAAI,eAAe,CAAC;gBACzB,MAAM,EAAE,IAAI,aAAa,CAAC;oBACxB,KAAK;oBACL,aAAa,EAAE,KAAK;oBACpB,SAAS,EAAE,OAAO,CAAC,EAAE;oBACrB,aAAa;oBACb,MAAM;iBACP,CAAC;gBACF,QAAQ,EAAE,IAAI,eAAe,CAAC;oBAC5B,SAAS,EAAE,OAAO,CAAC,EAAE;oBACrB,aAAa,EAAE,KAAK,CAAC,aAAa;oBAClC,SAAS,EAAE,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,SAAS,CAAC;oBACjD,SAAS,EAAE,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,SAAS,CAAC;oBACjD,KAAK;oBACL,aAAa,EAAE,KAAK;oBACpB,MAAM;oBACN,GAAG,CAAC,KAAK,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,wBAAwB,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBACpF,CAAC;gBACF,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,IAAI,EAAE,OAAO,CAAC,IAAI;aACnB,CAAC,CAAC;QACL,CAAC,CAAC;QAEF,MAAM,OAAO,GAAG,CAAC,KAAgB,EAAE,OAAwB,EAAE,GAAW,EAAE,EAAE;YAC1E,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;YACvC,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YAC1B,IAAI,OAAO,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;gBACzC,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAC3B,CAAC,QAAQ,EAAE,EAAE,CACX,QAAQ,CAAC,cAAc,KAAK,OAAO,CAAC,cAAc,IAAI,QAAQ,CAAC,SAAS,GAAG,GAAG,CACjF,CAAC;gBACF,IAAI,SAAS,KAAK,SAAS;oBAAE,OAAO,SAAS,CAAC,EAAE,CAAC;YACnD,CAAC;YACD,MAAM,EAAE,GAAG,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YACzC,MAAM,SAAS,GACb,OAAO,CAAC,SAAS,KAAK,SAAS;gBAC7B,CAAC,CAAC,iBAAiB;gBACnB,CAAC,CAAC,QAAQ,CAAC,eAAe,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YAClD,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE;gBACF,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,WAAW,EAAE,OAAO,CAAC,WAAW;gBAChC,SAAS,EAAE,GAAG;gBACd,SAAS,EAAE,GAAG,GAAG,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC;gBAC7C,WAAW,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;gBACvF,cAAc,EAAE,OAAO,CAAC,cAAc;aACvC,CAAC,CAAC;YACH,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC;QAEF,MAAM,aAAa,GAAG,CACpB,MAAqB,EACrB,GAAW,EAC4D,EAAE;YACzE,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;YACjE,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;YACxF,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,cAAc,EAAE,CAAC;gBACrF,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,eAAe,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;YAC3E,CAAC;YACD,MAAM,IAAI,GACR,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC;gBACpB,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,MAAM,CAAC,aAAa,CAAC;YACtE,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,OAAO,MAAM,CAAC,IAAI,CAChB,IAAI,mBAAmB,CAAC;oBACtB,SAAS,EAAE,MAAM,CAAC,SAAS;oBAC3B,MAAM,EAAE,IAAI,kBAAkB,CAAC,EAAE,OAAO,EAAE,6BAA6B,EAAE,CAAC;iBAC3E,CAAC,CACH,CAAC;YACJ,CAAC;YACD,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC,CAAC;QAEF,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAE,CAAC;YACtB,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CACvB,IAAI,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE,CAChE,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAC7E;YAEH,SAAS,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,CAC7B,IAAI,CAAC,EAAE,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE,CACrE,MAAM,CAAC,OAAO,CACZ,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CACvB,eAAe,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC,CAClE,CACF,CACF;YAEH,OAAO,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,OAAwB,EAAE,EAAE;gBAC1D,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,IAAI,aAAa,CAAC;gBAChD,MAAM,OAAO,GAAG,QAAQ,CAAC,eAAe,CACtC,OAAO,EAAE,iBAAiB,IAAI,0BAA0B,CACzD,CAAC;gBACF,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,IAAI,CAAC,CAAC;gBAClC,OAAO,IAAI,CACT;oBACE,SAAS,EAAE,SAAS;oBACpB,KAAK;oBACL,MAAM;oBACN,aAAa;oBACb,iBAAiB,EAAE,OAAO;oBAC1B,KAAK;oBACL,GAAG,CAAC,OAAO,EAAE,cAAc,KAAK,SAAS;wBACvC,CAAC,CAAC,EAAE;wBACJ,CAAC,CAAC,EAAE,cAAc,EAAE,OAAO,CAAC,cAAc,EAAE,CAAC;iBAChD,EACD,CAAC,GAAG,EAAE,EAAE,CACN,MAAM,CAAC,OAAO,CACZ,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;qBACtB,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,SAAS,GAAG,GAAG,IAAI,OAAO,CAAC,WAAW,IAAI,GAAG,CAAC;qBAC1E,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;oBACjB,OAAO;oBACP,KAAK,EAAE,UAAU,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,CAAC;iBACpD,CAAC,CAAC;qBACF,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,cAAc,IAAI,KAAK,CAAC,SAAS,IAAI,GAAG,CAAC;qBAChF,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC;qBACf,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,CAC1B,KAAK,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,CAClE,CACJ,CACJ,CAAC;YACJ,CAAC;YAED,WAAW,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,OAA4B,EAAE,EAAE;gBAC7E,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,IAAI,aAAa,CAAC;gBAChD,MAAM,OAAO,GAAG,QAAQ,CAAC,eAAe,CACtC,OAAO,EAAE,iBAAiB,IAAI,0BAA0B,CACzD,CAAC;gBACF,OAAO,IAAI,CACT;oBACE,SAAS,EAAE,aAAa;oBACxB,KAAK;oBACL,MAAM;oBACN,aAAa;oBACb,SAAS;oBACT,iBAAiB,EAAE,OAAO;iBAC3B,EACD,CAAC,GAAG,EAAE,EAAE;oBACN,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;oBACnD,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;wBAC3B,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,eAAe,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;oBACzD,CAAC;oBACD,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC;oBAC1D,IAAI,KAAK,CAAC,MAAM,KAAK,cAAc,EAAE,CAAC;wBACpC,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,uBAAuB,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;oBACjE,CAAC;oBACD,IAAI,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC;wBACzB,OAAO,MAAM,CAAC,IAAI,CAChB,IAAI,mBAAmB,CAAC;4BACtB,SAAS;4BACT,MAAM,EAAE,IAAI,aAAa,CAAC;gCACxB,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC;6BAChE,CAAC;yBACH,CAAC,CACH,CAAC;oBACJ,CAAC;oBACD,IAAI,OAAO,CAAC,KAAK,CAAC,WAAW,GAAG,GAAG,IAAI,KAAK,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC;wBAC7D,OAAO,MAAM,CAAC,IAAI,CAChB,IAAI,mBAAmB,CAAC;4BACtB,SAAS;4BACT,MAAM,EAAE,IAAI,kBAAkB,CAAC,EAAE,OAAO,EAAE,gCAAgC,EAAE,CAAC;yBAC9E,CAAC,CACH,CAAC;oBACJ,CAAC;oBACD,OAAO,MAAM,CAAC,OAAO,CACnB,KAAK,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,CACxE,CAAC;gBACJ,CAAC,CACF,CAAC;YACJ,CAAC;YAED,WAAW,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CAC/B,IAAI,CACF;gBACE,SAAS,EAAE,aAAa;gBACxB,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,aAAa,EAAE,MAAM,CAAC,aAAa;gBACnC,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,aAAa,EAAE,MAAM,CAAC,aAAa;gBACnC,GAAG,CAAC,OAAO,EAAE,uBAAuB,KAAK,SAAS;oBAChD,CAAC,CAAC,EAAE;oBACJ,CAAC,CAAC,EAAE,uBAAuB,EAAE,OAAO,CAAC,uBAAuB,EAAE,CAAC;aAClE,EACD,CAAC,GAAG,EAAE,EAAE,CACN,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE;gBACnD,IAAI,KAAK,CAAC,cAAc,GAAG,CAAC,OAAO,EAAE,uBAAuB,IAAI,CAAC,CAAC,EAAE,CAAC;oBACnE,OAAO,MAAM,CAAC,IAAI,CAChB,IAAI,mBAAmB,CAAC;wBACtB,SAAS,EAAE,MAAM,CAAC,SAAS;wBAC3B,MAAM,EAAE,IAAI,2BAA2B,CAAC;4BACtC,cAAc,EAAE,KAAK,CAAC,cAAc;yBACrC,CAAC;qBACH,CAAC,CACH,CAAC;gBACJ,CAAC;gBACD,KAAK,CAAC,MAAM,GAAG,cAAc,CAAC;gBAC9B,KAAK,CAAC,aAAa,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;gBACpC,KAAK,CAAC,cAAc,GAAG,CAAC,CAAC;gBACzB,OAAO,MAAM,CAAC,IAAI,CAAC;YACrB,CAAC,CAAC,CACL;YAEH,gBAAgB,EAAE,CAAC,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,EAAE;gBACvD,MAAM,OAAO,GAAG,QAAQ,CAAC,eAAe,CAAC,iBAAiB,CAAC,CAAC;gBAC5D,MAAM,OAAO,GAAG,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAC;gBACtD,OAAO,IAAI,CACT;oBACE,SAAS,EAAE,kBAAkB;oBAC7B,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,MAAM,EAAE,MAAM,CAAC,MAAM;oBACrB,aAAa,EAAE,MAAM,CAAC,aAAa;oBACnC,SAAS,EAAE,MAAM,CAAC,SAAS;oBAC3B,aAAa,EAAE,MAAM,CAAC,aAAa;oBACnC,iBAAiB,EAAE,OAAO;oBAC1B,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,mBAAmB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAClD,EACD,CAAC,GAAG,EAAE,EAAE,CACN,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,EAAE;oBAC/C,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;oBAC1C,KAAK,CAAC,SAAS,GAAG,GAAG,GAAG,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;oBACnD,IAAI,QAAQ,EAAE,CAAC;wBACb,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC;wBACzB,KAAK,CAAC,aAAa,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;oBACtC,CAAC;oBACD,IAAI,CAAC,OAAO;wBAAE,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;oBACnC,IAAI,CAAC,QAAQ;wBAAE,OAAO,MAAM,CAAC,IAAI,CAAC,eAAwB,CAAC,CAAC;oBAC5D,KAAK,CAAC,YAAY,IAAI,CAAC,CAAC;oBACxB,OAAO,MAAM,CAAC,IAAI,CAAC,UAAmB,CAAC,CAAC;gBAC1C,CAAC,CAAC,CACL,CAAC;YACJ,CAAC;SACF,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,SAAS,CAAC,EAAE,CAAC;YAC3B,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC;YACvC,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,EAAE,CAC5C,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CACf,MAAM,CAAC,GAAG,CACR,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC,EAC1E,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC,CAC1B,CACF;YACH,QAAQ,EAAE,CAAC,SAAS,EAAE,KAAK,EAAE,EAAE,CAC7B,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;gBACf,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;gBAC5C,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBAClB,QAAQ,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;YACjC,CAAC,CAAC;YACJ,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,CACnD,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;gBACf,UAAU,CAAC,KAAK,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC,cAAc,GAAG,KAAK,CAAC;YACrE,CAAC,CAAC;SACL,CAAC,CAAC;QAEH,OAAO,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;IAC3E,CAAC,CAAC,CACH,CAAC;CACH"}
package/package.json CHANGED
@@ -1,6 +1,62 @@
1
1
  {
2
+ "$schema": "../../.vscode/schemas/package.schema.json",
2
3
  "name": "@panorama-industries/effect-vercel-queue",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
4
+ "version": "0.1.0",
5
+ "description": "Effect-native client for Vercel Queues: typed topics, producers, push and poll consumers.",
6
+ "keywords": [
7
+ "effect",
8
+ "queue",
9
+ "sdk",
10
+ "typescript",
11
+ "vercel",
12
+ "vercel-queues"
13
+ ],
14
+ "license": "MIT",
15
+ "author": {
16
+ "name": "Santiago Galán",
17
+ "email": "santiago@panorama.industries",
18
+ "url": "https://sgalanb.com"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/panorama-industries/effect-vercel.git",
23
+ "directory": "packages/queue"
24
+ },
25
+ "files": [
26
+ "src/**/*.ts",
27
+ "!src/**/*.test.ts",
28
+ "dist/**/*.js",
29
+ "dist/**/*.js.map",
30
+ "dist/**/*.d.ts",
31
+ "dist/**/*.d.ts.map"
32
+ ],
33
+ "type": "module",
34
+ "sideEffects": false,
35
+ "exports": {
36
+ "./package.json": "./package.json",
37
+ ".": {
38
+ "effect-vercel-source": "./src/index.ts",
39
+ "types": "./dist/index.d.ts",
40
+ "default": "./dist/index.js"
41
+ },
42
+ "./testing": {
43
+ "effect-vercel-source": "./src/testing/index.ts",
44
+ "types": "./dist/testing/index.d.ts",
45
+ "default": "./dist/testing/index.js"
46
+ }
47
+ },
48
+ "publishConfig": {
49
+ "access": "public",
50
+ "provenance": true
51
+ },
52
+ "dependencies": {
53
+ "@panorama-industries/effect-vercel-core": "^0.1.0"
54
+ },
55
+ "devDependencies": {
56
+ "@vercel/queue": "0.10.0",
57
+ "effect": "4.0.6"
58
+ },
59
+ "peerDependencies": {
60
+ "effect": "^4.0.6"
61
+ }
6
62
  }
@@ -0,0 +1,68 @@
1
+ import type { Duration, Effect } from "effect";
2
+
3
+ import { Data } from "effect";
4
+
5
+ import type {
6
+ ConcurrencyCoalescedPending,
7
+ MessageMetadata,
8
+ PayloadDecodeFailed,
9
+ } from "./queue-domain.ts";
10
+
11
+ /** What to do with a message whose handler failed. */
12
+ export type RetryDirective = Data.TaggedEnum<{
13
+ /** Acknowledge the message so it is never delivered again. */
14
+ Acknowledge: {};
15
+ /** Release the lease and redeliver the message after `delay`. */
16
+ RetryAfter: { readonly delay: Duration.Duration };
17
+ /**
18
+ * **Experimental.** End the lease now and redeliver immediately. A handoff counts toward neither
19
+ * the queue's retry backoff nor its maximum attempts; past the queue's bound it is an ordinary
20
+ * retry.
21
+ */
22
+ ExperimentalHandoff: {};
23
+ /**
24
+ * Leave the message to the queue's own redelivery. Push consumers answer HTTP 500; pollers return
25
+ * the handler's error and let the lease expire.
26
+ */
27
+ Fail: {};
28
+ }>;
29
+
30
+ /** Constructors and matchers for {@link RetryDirective}. */
31
+ export const RetryDirective = Data.taggedEnum<RetryDirective>();
32
+
33
+ /**
34
+ * Processes one delivered payload. Succeeding acknowledges the message; failing hands the error to
35
+ * {@link ConsumeOptions.onError}.
36
+ */
37
+ export type MessageHandler<A, E, R> = (
38
+ payload: A,
39
+ metadata: MessageMetadata,
40
+ ) => Effect.Effect<void, E, R>;
41
+
42
+ /** Failures a consumer's error policy can receive besides the handler's own errors. */
43
+ export type ConsumeFailure = PayloadDecodeFailed | ConcurrencyCoalescedPending;
44
+
45
+ /** How a consumer holds leases and reacts to failures. */
46
+ export interface ConsumeOptions<E, R = never> {
47
+ /**
48
+ * How long a lease lasts before it must be renewed. The consumer renews it in the background
49
+ * while the handler runs. Default: 5 minutes.
50
+ */
51
+ readonly visibilityTimeout?: Duration.Input;
52
+
53
+ /**
54
+ * Choose a {@link RetryDirective} for a failed handler, an undecodable payload, or an
55
+ * acknowledgment refused after the coalesced reruns are used up. Default: `Fail`. Defects always
56
+ * mean `Fail`.
57
+ */
58
+ readonly onError?: (
59
+ error: E | ConsumeFailure,
60
+ metadata: MessageMetadata,
61
+ ) => Effect.Effect<RetryDirective, never, R>;
62
+
63
+ /**
64
+ * **Experimental.** Rerun the handler, with the lease still held, up to this many times when its
65
+ * acknowledgment is refused because messages were coalesced into the delivery. Default: 0.
66
+ */
67
+ readonly experimentalMaxCoalescedReruns?: number;
68
+ }