@vercube/queue 1.3.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.
- package/LICENSE +21 -0
- package/README.md +60 -0
- package/dist/QueueStrategy-DwLPpfFM.mjs +237 -0
- package/dist/QueueStrategy-saWSBeU6.d.mts +715 -0
- package/dist/Strategies/BullMQStrategy.d.mts +161 -0
- package/dist/Strategies/BullMQStrategy.mjs +328 -0
- package/dist/Strategies/KafkaStrategy.d.mts +152 -0
- package/dist/Strategies/KafkaStrategy.mjs +280 -0
- package/dist/Strategies/MemoryStrategy.d.mts +139 -0
- package/dist/Strategies/MemoryStrategy.mjs +305 -0
- package/dist/Strategies/RabbitMQStrategy.d.mts +205 -0
- package/dist/Strategies/RabbitMQStrategy.mjs +388 -0
- package/dist/decorate-C0p0FnUM.mjs +25 -0
- package/dist/index.d.mts +953 -0
- package/dist/index.mjs +1719 -0
- package/package.json +54 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { f as encodePayload, g as readNumericHeader, m as normalizeHeaders, p as generateJobId, r as ATTEMPT_HEADER, t as QueueStrategy, u as decodePayload, v as QueueError } from "../QueueStrategy-DwLPpfFM.mjs";
|
|
2
|
+
import { n as toQueueError, t as __decorate } from "../decorate-C0p0FnUM.mjs";
|
|
3
|
+
import { InjectOptional } from "@vercube/di";
|
|
4
|
+
import { Logger } from "@vercube/logger";
|
|
5
|
+
import { connect } from "amqplib";
|
|
6
|
+
//#region src/Strategies/RabbitMQStrategy.ts
|
|
7
|
+
/**
|
|
8
|
+
* RabbitMQ backed queue implementation.
|
|
9
|
+
*
|
|
10
|
+
* Jobs are plain AMQP messages: the job name travels in the message `type`
|
|
11
|
+
* property, the module's bookkeeping in the headers, and the payload as JSON.
|
|
12
|
+
* RabbitMQ has no notion of attempts or delays, so the manager owns them - which
|
|
13
|
+
* also means a job that has run out of attempts is nacked without requeue, and
|
|
14
|
+
* ends up wherever the queue's dead letter exchange points.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* await queueManager.mount({
|
|
19
|
+
* strategy: RabbitMQStrategy,
|
|
20
|
+
* initOptions: { url: 'amqp://localhost' },
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* // a queue that dead-letters what it cannot process
|
|
27
|
+
* await queueManager.mount({
|
|
28
|
+
* strategy: RabbitMQStrategy,
|
|
29
|
+
* initOptions: {
|
|
30
|
+
* url: 'amqp://localhost',
|
|
31
|
+
* queueOptions: { durable: true, deadLetterExchange: 'failed' },
|
|
32
|
+
* },
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
var RabbitMQStrategy = class extends QueueStrategy {
|
|
37
|
+
/** Transport this strategy talks to. */
|
|
38
|
+
transport = "rabbitmq";
|
|
39
|
+
/** Logger instance */
|
|
40
|
+
gLogger;
|
|
41
|
+
/** Options the strategy was initialized with */
|
|
42
|
+
fOptions = null;
|
|
43
|
+
/** The connection, recovering on its own unless recovery was turned off */
|
|
44
|
+
fConnection = null;
|
|
45
|
+
/** Channel every publish goes through */
|
|
46
|
+
fPublishChannel = null;
|
|
47
|
+
/** Running consumers, indexed by queue name */
|
|
48
|
+
fConsumers = /* @__PURE__ */ new Map();
|
|
49
|
+
/**
|
|
50
|
+
* Queues the application asked to consume, indexed by queue name.
|
|
51
|
+
*
|
|
52
|
+
* Kept apart from {@link RabbitMQStrategy.fConsumers}, which holds the live
|
|
53
|
+
* channels: those belong to one connection and are thrown away when it is
|
|
54
|
+
* replaced, while what should be consumed does not change with a reconnect.
|
|
55
|
+
*/
|
|
56
|
+
fRequests = /* @__PURE__ */ new Map();
|
|
57
|
+
/**
|
|
58
|
+
* Bumped on every connection. A consumer that finishes starting after its
|
|
59
|
+
* connection was replaced belongs to a dead one and is discarded.
|
|
60
|
+
*/
|
|
61
|
+
fGeneration = 0;
|
|
62
|
+
/** Queues already asserted on the current connection */
|
|
63
|
+
fAsserted = /* @__PURE__ */ new Set();
|
|
64
|
+
/**
|
|
65
|
+
* RabbitMQ carries priorities and message counts, while attempts and delays
|
|
66
|
+
* are left to the manager.
|
|
67
|
+
*
|
|
68
|
+
* @returns {QueueTypes.Capabilities} What this strategy supports
|
|
69
|
+
*/
|
|
70
|
+
get capabilities() {
|
|
71
|
+
return {
|
|
72
|
+
retries: false,
|
|
73
|
+
delay: false,
|
|
74
|
+
priority: true,
|
|
75
|
+
progress: false,
|
|
76
|
+
stats: true,
|
|
77
|
+
peek: false
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Opens the connection to the broker.
|
|
82
|
+
*
|
|
83
|
+
* @param {RabbitMQStrategyOptions} options - Broker to connect to and the defaults to use
|
|
84
|
+
* @returns {Promise<void>} Resolves once the connection is open
|
|
85
|
+
* @throws {QueueError} When no broker is given, or the connection cannot be opened
|
|
86
|
+
*/
|
|
87
|
+
async initialize(options) {
|
|
88
|
+
if (!options?.url) throw new QueueError("RabbitMQ needs a broker url", "initialize", void 0, void 0, false);
|
|
89
|
+
this.fOptions = options;
|
|
90
|
+
const recovery = options.recovery ?? true;
|
|
91
|
+
try {
|
|
92
|
+
this.fConnection = await (recovery ? connect(options.url, {
|
|
93
|
+
...options.socketOptions,
|
|
94
|
+
recovery: recovery === true ? {} : recovery
|
|
95
|
+
}) : connect(options.url, options.socketOptions));
|
|
96
|
+
} catch (error) {
|
|
97
|
+
throw toQueueError(error, "Failed to connect to RabbitMQ", "initialize");
|
|
98
|
+
}
|
|
99
|
+
this.fConnection.on("error", (error) => {
|
|
100
|
+
this.gLogger?.error("Vercube/RabbitMQStrategy::Connection failed", error);
|
|
101
|
+
});
|
|
102
|
+
this.fConnection.on("connect", () => {
|
|
103
|
+
this.fAsserted.clear();
|
|
104
|
+
this.fPublishChannel = null;
|
|
105
|
+
this.fConsumers.clear();
|
|
106
|
+
this.fGeneration++;
|
|
107
|
+
for (const request of this.fRequests.values()) this.resume(request, this.fGeneration);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Sends a job to a queue.
|
|
112
|
+
*
|
|
113
|
+
* @param {QueueTypes.PublishRequest} request - Job to publish
|
|
114
|
+
* @returns {Promise<QueueTypes.JobRef>} Reference to the published job
|
|
115
|
+
* @throws {QueueError} When the job cannot be sent
|
|
116
|
+
*/
|
|
117
|
+
async publish(request) {
|
|
118
|
+
const options = this.requireOptions("publish");
|
|
119
|
+
try {
|
|
120
|
+
const channel = await this.publishChannel();
|
|
121
|
+
await this.assertQueue(channel, request.queue);
|
|
122
|
+
const id = request.options.jobId ?? generateJobId();
|
|
123
|
+
if (!channel.sendToQueue(request.queue, encodePayload(request.payload), {
|
|
124
|
+
persistent: true,
|
|
125
|
+
...options.publishOptions,
|
|
126
|
+
headers: request.headers,
|
|
127
|
+
type: request.job,
|
|
128
|
+
messageId: id,
|
|
129
|
+
priority: request.options.priority,
|
|
130
|
+
correlationId: request.options.key
|
|
131
|
+
})) await new Promise((resolve, reject) => {
|
|
132
|
+
const drained = () => settle();
|
|
133
|
+
const failed = (channelError) => settle(channelError);
|
|
134
|
+
const closed = () => settle(/* @__PURE__ */ new Error("The channel closed before the job was flushed"));
|
|
135
|
+
const settle = (error) => {
|
|
136
|
+
channel.removeListener("drain", drained);
|
|
137
|
+
channel.removeListener("error", failed);
|
|
138
|
+
channel.removeListener("close", closed);
|
|
139
|
+
if (error) reject(error);
|
|
140
|
+
else resolve();
|
|
141
|
+
};
|
|
142
|
+
channel.once("drain", drained);
|
|
143
|
+
channel.once("error", failed);
|
|
144
|
+
channel.once("close", closed);
|
|
145
|
+
});
|
|
146
|
+
return {
|
|
147
|
+
id,
|
|
148
|
+
queue: request.queue,
|
|
149
|
+
job: request.job,
|
|
150
|
+
strategy: this.transport
|
|
151
|
+
};
|
|
152
|
+
} catch (error) {
|
|
153
|
+
throw toQueueError(error, "Failed to send job to RabbitMQ", "publish", {
|
|
154
|
+
queue: request.queue,
|
|
155
|
+
job: request.job
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Starts consuming a queue on its own channel, so its prefetch is its own.
|
|
161
|
+
*
|
|
162
|
+
* A job whose dispatch rejects is nacked without requeue: the manager has
|
|
163
|
+
* already decided it is not worth another attempt.
|
|
164
|
+
*
|
|
165
|
+
* @param {QueueTypes.ConsumeRequest} request - Queue to consume, its concurrency and the dispatch callback
|
|
166
|
+
* @returns {Promise<QueueTypes.ConsumerHandle>} Handle used to stop the consumer again
|
|
167
|
+
* @throws {QueueError} When the consumer cannot be started
|
|
168
|
+
*/
|
|
169
|
+
async consume(request) {
|
|
170
|
+
const generation = this.fGeneration;
|
|
171
|
+
this.fRequests.set(request.queue, request);
|
|
172
|
+
try {
|
|
173
|
+
await this.startConsumer(request, generation);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (generation !== this.fGeneration) return {
|
|
176
|
+
queue: request.queue,
|
|
177
|
+
stop: () => this.stopConsumer(request.queue)
|
|
178
|
+
};
|
|
179
|
+
this.fRequests.delete(request.queue);
|
|
180
|
+
throw toQueueError(error, "Failed to consume RabbitMQ queue", "consume", { queue: request.queue });
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
queue: request.queue,
|
|
184
|
+
stop: () => this.stopConsumer(request.queue)
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Opens a channel for a queue and starts delivering its messages.
|
|
189
|
+
*
|
|
190
|
+
* @param {QueueTypes.ConsumeRequest} request - Queue to consume, its concurrency and the dispatch callback
|
|
191
|
+
* @param {number} generation - Connection this consumer is being started for
|
|
192
|
+
* @returns {Promise<boolean>} Whether the consumer was installed, false when it was no longer wanted
|
|
193
|
+
* @throws {Error} When the channel or the consumer cannot be created
|
|
194
|
+
*/
|
|
195
|
+
async startConsumer(request, generation) {
|
|
196
|
+
const options = this.requireOptions("consume");
|
|
197
|
+
const channel = await this.requireConnection("consume").createChannel();
|
|
198
|
+
try {
|
|
199
|
+
await this.assertQueue(channel, request.queue);
|
|
200
|
+
await channel.prefetch(options.prefetch ?? Math.max(1, request.concurrency));
|
|
201
|
+
const reply = await channel.consume(request.queue, (message) => {
|
|
202
|
+
if (!message) {
|
|
203
|
+
this.gLogger?.warn(`Vercube/RabbitMQStrategy::Consumer of "${request.queue}" was cancelled by the broker`);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
this.handle(request, message);
|
|
207
|
+
});
|
|
208
|
+
if (generation !== this.fGeneration || !this.fRequests.has(request.queue)) {
|
|
209
|
+
await channel.close().catch(() => void 0);
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
this.fConsumers.set(request.queue, {
|
|
213
|
+
channel,
|
|
214
|
+
tag: reply.consumerTag,
|
|
215
|
+
active: 0,
|
|
216
|
+
stopping: false
|
|
217
|
+
});
|
|
218
|
+
return true;
|
|
219
|
+
} catch (error) {
|
|
220
|
+
await channel.close().catch(() => void 0);
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Starts a consumer again on a recovered connection.
|
|
226
|
+
*
|
|
227
|
+
* Failing here is reported rather than thrown: nothing is waiting on a
|
|
228
|
+
* recovery, and a queue that cannot be consumed again has to be visible.
|
|
229
|
+
*
|
|
230
|
+
* @param {QueueTypes.ConsumeRequest} request - What the consumer was started with
|
|
231
|
+
* @param {number} generation - Connection being recovered onto
|
|
232
|
+
* @returns {Promise<void>} Resolves once the consumer is running again, or once the failure was reported
|
|
233
|
+
*/
|
|
234
|
+
async resume(request, generation) {
|
|
235
|
+
try {
|
|
236
|
+
await this.startConsumer(request, generation);
|
|
237
|
+
} catch (error) {
|
|
238
|
+
this.gLogger?.error(`Vercube/RabbitMQStrategy::Failed to consume "${request.queue}" again after a recovery`, error);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Reads how many messages are waiting on a queue.
|
|
243
|
+
*
|
|
244
|
+
* @param {string} queue - Queue to read
|
|
245
|
+
* @returns {Promise<QueueTypes.QueueStats>} Counters of that queue
|
|
246
|
+
* @throws {QueueError} When the queue cannot be inspected
|
|
247
|
+
*/
|
|
248
|
+
async stats(queue) {
|
|
249
|
+
const connection = this.requireConnection("stats");
|
|
250
|
+
let channel = null;
|
|
251
|
+
try {
|
|
252
|
+
channel = await connection.createChannel();
|
|
253
|
+
const info = await channel.checkQueue(queue);
|
|
254
|
+
const consumer = this.fConsumers.get(queue);
|
|
255
|
+
return {
|
|
256
|
+
waiting: info.messageCount,
|
|
257
|
+
active: consumer?.active ?? 0
|
|
258
|
+
};
|
|
259
|
+
} catch (error) {
|
|
260
|
+
throw toQueueError(error, "Failed to inspect RabbitMQ queue", "stats", { queue });
|
|
261
|
+
} finally {
|
|
262
|
+
await channel?.close().catch(() => void 0);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Stops every consumer and closes the connection.
|
|
267
|
+
*
|
|
268
|
+
* @returns {Promise<void>} Resolves once the connection is closed
|
|
269
|
+
*/
|
|
270
|
+
async close() {
|
|
271
|
+
const consuming = /* @__PURE__ */ new Set([...this.fConsumers.keys(), ...this.fRequests.keys()]);
|
|
272
|
+
for (const queue of consuming) await this.stopConsumer(queue);
|
|
273
|
+
const channel = this.fPublishChannel;
|
|
274
|
+
const connection = this.fConnection;
|
|
275
|
+
this.fPublishChannel = null;
|
|
276
|
+
this.fConnection = null;
|
|
277
|
+
this.fAsserted.clear();
|
|
278
|
+
try {
|
|
279
|
+
await channel?.close();
|
|
280
|
+
await connection?.close();
|
|
281
|
+
} catch (error) {
|
|
282
|
+
this.gLogger?.warn("Vercube/RabbitMQStrategy::Failed to close the connection", error);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Runs a single delivery and acknowledges it according to the outcome.
|
|
287
|
+
*
|
|
288
|
+
* @param {QueueTypes.ConsumeRequest} request - The consumer the message belongs to
|
|
289
|
+
* @param {ConsumeMessage} message - The delivery
|
|
290
|
+
* @returns {Promise<void>} Resolves once the message has been settled
|
|
291
|
+
*/
|
|
292
|
+
async handle(request, message) {
|
|
293
|
+
const consumer = this.fConsumers.get(request.queue);
|
|
294
|
+
const headers = normalizeHeaders(message.properties.headers);
|
|
295
|
+
if (consumer) consumer.active++;
|
|
296
|
+
try {
|
|
297
|
+
await request.dispatch({
|
|
298
|
+
id: message.properties.messageId ?? generateJobId(),
|
|
299
|
+
job: message.properties.type ?? headers["x-job"] ?? "unknown",
|
|
300
|
+
payload: decodePayload(message.content),
|
|
301
|
+
headers,
|
|
302
|
+
attempt: readNumericHeader(headers[ATTEMPT_HEADER], 1),
|
|
303
|
+
raw: message
|
|
304
|
+
});
|
|
305
|
+
consumer?.channel.ack(message);
|
|
306
|
+
} catch {
|
|
307
|
+
consumer?.channel.nack(message, false, false);
|
|
308
|
+
} finally {
|
|
309
|
+
if (consumer) consumer.active--;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Cancels a consumer, waits for its in-flight messages and closes its channel.
|
|
314
|
+
*
|
|
315
|
+
* @param {string} queue - Queue whose consumer is stopped
|
|
316
|
+
* @returns {Promise<void>} Resolves once the channel is closed
|
|
317
|
+
*/
|
|
318
|
+
async stopConsumer(queue) {
|
|
319
|
+
this.fRequests.delete(queue);
|
|
320
|
+
const consumer = this.fConsumers.get(queue);
|
|
321
|
+
if (!consumer || consumer.stopping) return;
|
|
322
|
+
consumer.stopping = true;
|
|
323
|
+
this.fConsumers.delete(queue);
|
|
324
|
+
try {
|
|
325
|
+
await consumer.channel.cancel(consumer.tag);
|
|
326
|
+
while (consumer.active > 0) await new Promise((resolve) => setTimeout(resolve, 10));
|
|
327
|
+
await consumer.channel.close();
|
|
328
|
+
} catch (error) {
|
|
329
|
+
this.gLogger?.warn(`Vercube/RabbitMQStrategy::Failed to stop the consumer of "${queue}"`, error);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Returns the channel every publish goes through, opening it on first use and
|
|
334
|
+
* after a reconnection.
|
|
335
|
+
*
|
|
336
|
+
* @returns {Promise<Channel>} The publish channel
|
|
337
|
+
* @throws {QueueError} When the strategy is not connected
|
|
338
|
+
*/
|
|
339
|
+
async publishChannel() {
|
|
340
|
+
if (!this.fPublishChannel) {
|
|
341
|
+
this.fPublishChannel = await this.requireConnection("publish").createChannel();
|
|
342
|
+
this.fPublishChannel.on("error", (error) => {
|
|
343
|
+
this.gLogger?.error("Vercube/RabbitMQStrategy::Publish channel failed", error);
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
return this.fPublishChannel;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Declares a queue once per connection.
|
|
350
|
+
*
|
|
351
|
+
* @param {Channel} channel - Channel to declare it on
|
|
352
|
+
* @param {string} queue - Queue to declare
|
|
353
|
+
* @returns {Promise<void>} Resolves once the queue exists
|
|
354
|
+
*/
|
|
355
|
+
async assertQueue(channel, queue) {
|
|
356
|
+
if (this.fAsserted.has(queue)) return;
|
|
357
|
+
await channel.assertQueue(queue, {
|
|
358
|
+
durable: true,
|
|
359
|
+
...this.fOptions?.queueOptions
|
|
360
|
+
});
|
|
361
|
+
this.fAsserted.add(queue);
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Returns the options the strategy was initialized with.
|
|
365
|
+
*
|
|
366
|
+
* @param {string} operation - Operation asking for them, reported in the error
|
|
367
|
+
* @returns {RabbitMQStrategyOptions} The options
|
|
368
|
+
* @throws {QueueError} When the strategy has not been initialized
|
|
369
|
+
*/
|
|
370
|
+
requireOptions(operation) {
|
|
371
|
+
if (!this.fOptions) throw new QueueError("RabbitMQ strategy is not initialized", operation, void 0, void 0, false);
|
|
372
|
+
return this.fOptions;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Returns the open connection.
|
|
376
|
+
*
|
|
377
|
+
* @param {string} operation - Operation asking for it, reported in the error
|
|
378
|
+
* @returns {ChannelModel | RecoveringChannelModel} The connection
|
|
379
|
+
* @throws {QueueError} When the strategy is not connected
|
|
380
|
+
*/
|
|
381
|
+
requireConnection(operation) {
|
|
382
|
+
if (!this.fConnection) throw new QueueError("RabbitMQ strategy is not connected", operation, void 0, void 0, false);
|
|
383
|
+
return this.fConnection;
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
__decorate([InjectOptional(Logger)], RabbitMQStrategy.prototype, "gLogger", void 0);
|
|
387
|
+
//#endregion
|
|
388
|
+
export { RabbitMQStrategy };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { v as QueueError } from "./QueueStrategy-DwLPpfFM.mjs";
|
|
2
|
+
//#region src/Utils/Errors.ts
|
|
3
|
+
/**
|
|
4
|
+
* Turns any thrown value into a {@link QueueError}, leaving queue errors alone so
|
|
5
|
+
* their operation and `retryable` flag survive.
|
|
6
|
+
*
|
|
7
|
+
* @param error - The value that was thrown.
|
|
8
|
+
* @param message - Message of the resulting error.
|
|
9
|
+
* @param operation - Queue operation that failed.
|
|
10
|
+
* @param metadata - Additional non-sensitive context.
|
|
11
|
+
* @returns The error to throw.
|
|
12
|
+
*/
|
|
13
|
+
function toQueueError(error, message, operation, metadata) {
|
|
14
|
+
return error instanceof QueueError ? error : new QueueError(message, operation, error, metadata);
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region \0@oxc-project+runtime@0.150.0/helpers/esm/decorate.js
|
|
18
|
+
function __decorate(decorators, target, key, desc) {
|
|
19
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
20
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
21
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
22
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { toQueueError as n, __decorate as t };
|