@adobe-commerce/aio-toolkit 1.2.3 → 1.2.4

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/dist/index.mjs CHANGED
@@ -5978,13 +5978,31 @@ var _AbdbRepository = class _AbdbRepository {
5978
5978
  * Returns all documents matching `filter` as an array.
5979
5979
  * Passing no filter (or an empty object) returns every document in the collection.
5980
5980
  *
5981
+ * Pagination and sorting are applied in the following MongoDB cursor order:
5982
+ * `.find(filter)` → `.sort(...)` → `.skip(...)` → `.limit(...)` → `.toArray()`
5983
+ *
5981
5984
  * @param filter - Optional query filter (default: `{}`)
5985
+ * @param options - Optional pagination and sort configuration
5986
+ * @param options.sort - Column and direction to sort by (`'asc'` maps to `1`, `'desc'` to `-1`)
5987
+ * @param options.page_size - Maximum number of documents to return
5988
+ * @param options.current_page - 1-based page index; combined with `page_size` to compute `.skip()`
5982
5989
  * @returns Array of matched documents (may be empty)
5983
5990
  */
5984
- async find(filter = {}) {
5991
+ async find(filter = {}, options = {}) {
5985
5992
  return this._collection.run(
5986
5993
  async (collection) => {
5987
- return collection.find(filter).toArray();
5994
+ const { current_page, page_size, sort } = options;
5995
+ let cursor = collection.find(filter);
5996
+ if (sort?.column) {
5997
+ cursor = cursor.sort({ [sort.column]: sort.direction === "desc" ? -1 : 1 });
5998
+ }
5999
+ if (page_size !== void 0 && page_size > 0) {
6000
+ if (current_page !== void 0 && current_page > 1) {
6001
+ cursor = cursor.skip((current_page - 1) * page_size);
6002
+ }
6003
+ cursor = cursor.limit(page_size);
6004
+ }
6005
+ return cursor.toArray();
5988
6006
  },
5989
6007
  this._token,
5990
6008
  this._region
@@ -6405,7 +6423,7 @@ var _WebhookActionResponse = class _WebhookActionResponse {
6405
6423
  * processing the webhook. This helps with debugging and error tracking.
6406
6424
  *
6407
6425
  * @param message - Optional error message describing what went wrong
6408
- * @param exceptionClass - Optional exception class name for categorization (e.g., 'Magento\\Framework\\Exception\\LocalizedException')
6426
+ * @param exceptionType - Optional exception type name for categorization (e.g., 'Magento\\Framework\\Exception\\LocalizedException')
6409
6427
  * @returns An exception response object
6410
6428
  *
6411
6429
  * @example
@@ -6427,15 +6445,15 @@ var _WebhookActionResponse = class _WebhookActionResponse {
6427
6445
  * });
6428
6446
  * ```
6429
6447
  */
6430
- static exception(message, exceptionClass) {
6448
+ static exception(message, exceptionType) {
6431
6449
  const response = {
6432
6450
  op: "exception" /* EXCEPTION */
6433
6451
  };
6434
6452
  if (message !== void 0) {
6435
6453
  response.message = message;
6436
6454
  }
6437
- if (exceptionClass !== void 0) {
6438
- response.class = exceptionClass;
6455
+ if (exceptionType !== void 0) {
6456
+ response.type = exceptionType;
6439
6457
  }
6440
6458
  return response;
6441
6459
  }
@@ -11708,11 +11726,11 @@ var _RabbitMQClient = class _RabbitMQClient {
11708
11726
  this.credentials = credentials;
11709
11727
  }
11710
11728
  /**
11711
- * Opens an AMQP connection, checks queue depth, sets prefetch for concurrency control,
11712
- * and starts a push consumer. The broker delivers at most `maxParallel` unacked messages
11713
- * at a time, so processing concurrency is naturally bounded without a separate semaphore.
11714
- * The consumer is cancelled once `effectiveBatch = min(batchSize, messageCount)` messages
11715
- * are received and processed. The connection is always closed in a `finally` block.
11729
+ * Opens an AMQP connection, checks queue depth, and pulls up to
11730
+ * `effectiveBatch = min(batchSize, messageCount)` messages via channel.get.
11731
+ * Messages are processed in parallel windows of `maxParallel`. channel.get returns
11732
+ * false when the queue is empty, so the method exits deterministically without any
11733
+ * cancellation logic. The connection is always closed in a `finally` block.
11716
11734
  *
11717
11735
  * @param queueName - Name of the queue to consume from.
11718
11736
  * @param options - Consume configuration (batchSize, maxParallel, nackRequeue, exchange).
@@ -11734,16 +11752,20 @@ var _RabbitMQClient = class _RabbitMQClient {
11734
11752
  }
11735
11753
  return await this.processBatch(channel, queueName, options, handler);
11736
11754
  } finally {
11737
- await channel?.close();
11755
+ try {
11756
+ await channel?.close();
11757
+ } catch {
11758
+ }
11738
11759
  await connection.close();
11739
11760
  }
11740
11761
  }
11741
11762
  /**
11742
11763
  * Opens an AMQP connection, asserts the queue, and enqueues each payload in `payloads`.
11743
11764
  * Each payload is sent as a persistent message via `sendToQueue`. If the write buffer is
11744
- * full (`sendToQueue` returns `false`) or the call throws, the payload is recorded in
11745
- * `stats.errors` and counted as failed. The connection is always closed in a `finally`
11746
- * block regardless of outcome.
11765
+ * full (`sendToQueue` returns `false`), the message is still counted as published and the
11766
+ * method waits for the channel's `drain` event before continuing, to respect backpressure
11767
+ * without losing messages. Only genuine send errors (thrown exceptions) are counted as
11768
+ * failures. The connection is always closed in a `finally` block regardless of outcome.
11747
11769
  *
11748
11770
  * @param queueName - Name of the queue to publish to.
11749
11771
  * @param payloads - Array of string payloads to enqueue.
@@ -11756,27 +11778,30 @@ var _RabbitMQClient = class _RabbitMQClient {
11756
11778
  try {
11757
11779
  channel = await connection.createChannel();
11758
11780
  await channel.assertQueue(queueName, { durable: true });
11759
- return this.publishBatch(channel, queueName, payloads);
11781
+ return await this.publishBatch(channel, queueName, payloads);
11760
11782
  } finally {
11761
- await channel?.close();
11783
+ try {
11784
+ await channel?.close();
11785
+ } catch {
11786
+ }
11762
11787
  await connection.close();
11763
11788
  }
11764
11789
  }
11765
11790
  /**
11766
11791
  * Sends each payload to the queue via `sendToQueue`. Tracks published and failed counts.
11767
- * A payload is considered failed when `sendToQueue` returns `false` (write buffer full)
11768
- * or throws an error.
11792
+ * When `sendToQueue` returns `false` (write buffer full / backpressure), the message is
11793
+ * still counted as published — it is already accepted by the channel — and the loop
11794
+ * pauses until the channel emits `drain` before continuing, preventing unbounded memory
11795
+ * pressure. A payload is only counted as failed when `sendToQueue` throws an error.
11769
11796
  */
11770
- publishBatch(channel, queueName, payloads) {
11797
+ async publishBatch(channel, queueName, payloads) {
11771
11798
  const stats = { published: 0, failed: 0, errors: [] };
11772
11799
  for (const payload of payloads) {
11773
11800
  try {
11774
11801
  const sent = channel.sendToQueue(queueName, Buffer.from(payload), { persistent: true });
11775
- if (sent) {
11776
- stats.published++;
11777
- } else {
11778
- stats.failed++;
11779
- stats.errors.push({ payload, error: new Error("Write buffer full") });
11802
+ stats.published++;
11803
+ if (!sent) {
11804
+ await new Promise((resolve) => channel.once("drain", resolve));
11780
11805
  }
11781
11806
  } catch (error) {
11782
11807
  stats.failed++;
@@ -11796,63 +11821,37 @@ var _RabbitMQClient = class _RabbitMQClient {
11796
11821
  return `${protocol}://${encodeURIComponent(username)}:${encodeURIComponent(password)}@${host}:${port}/${encodeURIComponent(vhost)}`;
11797
11822
  }
11798
11823
  /**
11799
- * Checks queue depth via `channel.checkQueue`, then sets `channel.prefetch(maxParallel)`
11800
- * for broker-side concurrency control. Registers a push consumer that receives
11801
- * `effectiveBatch = min(batchSize, messageCount)` messages. The Promise resolves after
11802
- * `channel.consume` setup completes and all in-flight handlers have settled.
11824
+ * Checks queue depth via channel.checkQueue, then pulls up to
11825
+ * `effectiveBatch = min(batchSize, messageCount)` messages using channel.get
11826
+ * (AMQP basic.get). Messages are processed in parallel windows of `maxParallel`:
11827
+ * each window pulls up to `maxParallel` messages sequentially, then processes them
11828
+ * concurrently via Promise.all before moving to the next window.
11803
11829
  *
11804
- * Because the broker delivers at most `maxParallel` unacked messages at a time, processing
11805
- * concurrency is bounded without a separate semaphore each ack/nack releases a slot.
11830
+ * channel.get returns false when the queue is empty, so the loop exits immediately
11831
+ * if siblings have drained the queueno consumer registration, no cancel, no hang.
11806
11832
  */
11807
11833
  async processBatch(channel, queueName, options, handler) {
11808
11834
  const stats = { consumed: 0, acked: 0, nacked: 0, errors: [] };
11809
- const maxParallel = Math.floor(options.maxParallel);
11810
- const batchSize = Math.floor(options.batchSize);
11835
+ const { batchSize, maxParallel } = options;
11811
11836
  const { messageCount } = await channel.checkQueue(queueName);
11812
11837
  if (messageCount === 0) return stats;
11813
11838
  const effectiveBatch = Math.min(batchSize, messageCount);
11814
- await channel.prefetch(maxParallel);
11815
- return new Promise((resolve, reject) => {
11816
- let consumerTag = "";
11817
- let cancelRequested = false;
11818
- let received = 0;
11819
- let pending = 0;
11820
- let setupDone = false;
11821
- const maybeResolve = /* @__PURE__ */ __name(() => {
11822
- if ((received >= effectiveBatch || cancelRequested) && pending === 0 && setupDone) {
11823
- resolve(stats);
11824
- }
11825
- }, "maybeResolve");
11826
- const msgCallback = /* @__PURE__ */ __name(async (msg) => {
11827
- if (!msg || received >= effectiveBatch) return;
11828
- received++;
11839
+ while (stats.consumed < effectiveBatch) {
11840
+ const windowSize = Math.min(maxParallel, effectiveBatch - stats.consumed);
11841
+ const window = [];
11842
+ for (let i = 0; i < windowSize; i++) {
11843
+ const msg = await channel.get(queueName, { noAck: false });
11844
+ if (!msg) break;
11845
+ window.push(msg);
11829
11846
  stats.consumed++;
11830
- pending++;
11831
- if (received >= effectiveBatch) {
11832
- cancelRequested = true;
11833
- if (consumerTag) {
11834
- channel.cancel(consumerTag).catch(reject);
11835
- }
11836
- }
11837
- await this.processMessage(channel, queueName, msg, stats, options, handler);
11838
- pending--;
11839
- maybeResolve();
11840
- }, "msgCallback");
11841
- channel.consume(queueName, msgCallback, { noAck: false }).then(async ({ consumerTag: tag }) => {
11842
- consumerTag = tag;
11843
- setupDone = true;
11844
- if (cancelRequested) {
11845
- channel.cancel(tag).catch(reject);
11846
- } else if (received === 0) {
11847
- const { messageCount: remaining } = await channel.checkQueue(queueName);
11848
- if (remaining === 0) {
11849
- cancelRequested = true;
11850
- channel.cancel(tag).catch(reject);
11851
- }
11852
- }
11853
- maybeResolve();
11854
- }).catch(reject);
11855
- });
11847
+ }
11848
+ if (window.length === 0) break;
11849
+ await Promise.all(
11850
+ window.map((msg) => this.processMessage(channel, queueName, msg, stats, options, handler))
11851
+ );
11852
+ if (window.length < windowSize) break;
11853
+ }
11854
+ return stats;
11856
11855
  }
11857
11856
  /**
11858
11857
  * Invokes `handler` with the queue name and decoded message content. Acks on success.