From e4b3f865e20165cc084d3c2683acc7dfbca13462 Mon Sep 17 00:00:00 2001
From: Rafael Cardenas <rafael@rafaelcr.com>
Date: Wed, 1 Nov 2023 16:17:30 -0600
Subject: [PATCH] feat: add iterator and enum value handling helpers

---
 src/helpers/index.ts     |  1 +
 src/helpers/iterators.ts | 78 ++++++++++++++++++++++++++++++++++++++++
 src/helpers/values.ts    | 12 +++++++
 3 files changed, 91 insertions(+)
 create mode 100644 src/helpers/iterators.ts

diff --git a/src/helpers/index.ts b/src/helpers/index.ts
index 430f360..4fa4ee7 100644
--- a/src/helpers/index.ts
+++ b/src/helpers/index.ts
@@ -1,2 +1,3 @@
+export * from './iterators';
 export * from './time';
 export * from './values';
diff --git a/src/helpers/iterators.ts b/src/helpers/iterators.ts
new file mode 100644
index 0000000..0bbc61d
--- /dev/null
+++ b/src/helpers/iterators.ts
@@ -0,0 +1,78 @@
+import { logger } from '../logger';
+import { isDevEnv } from './values';
+
+/**
+ * Iterate over an array, yielding multiple items at a time. If the size of the given array
+ * is not divisible by the given batch size, then the length of the last items returned will
+ * be smaller than the given batch size, i.e.:
+ * ```typescript
+ * items.length % batchSize
+ * ```
+ * @param items - The array to iterate over.
+ * @param batchSize - Maximum number of items to return at a time.
+ * @param printBenchmark - If we should print benchmark of items per second
+ */
+export function* batchIterate<T>(
+  items: T[],
+  batchSize: number,
+  printBenchmark = isDevEnv
+): Generator<T[]> {
+  if (items.length === 0) return;
+  const startTime = Date.now();
+  for (let i = 0; i < items.length; ) {
+    const itemsRemaining = items.length - i;
+    const sliceSize = Math.min(batchSize, itemsRemaining);
+    yield items.slice(i, i + sliceSize);
+    i += sliceSize;
+  }
+  if (printBenchmark) {
+    const itemsPerSecond = Math.round((items.length / (Date.now() - startTime)) * 1000);
+    const caller = new Error().stack?.split('at ')[3].trim();
+    logger.debug(`Iterated ${itemsPerSecond} items/second at ${caller}`);
+  }
+}
+
+/**
+ * Iterate over an `AsyncIterable`, yielding multiple items at a time. If the size of the given
+ * array is not divisible by the given batch size, then the length of the last items returned will
+ * be smaller than the given batch size.
+ *
+ * @param items - AsyncIterable
+ * @param batchSize - Batch size
+ * @param printBenchmark - If we should print benchmark of items per second
+ */
+export async function* asyncBatchIterate<T>(
+  items: AsyncIterable<T>,
+  batchSize: number,
+  printBenchmark = isDevEnv
+): AsyncGenerator<T[], void, unknown> {
+  const startTime = Date.now();
+  let itemCount = 0;
+  let itemBatch: T[] = [];
+  for await (const item of items) {
+    itemBatch.push(item);
+    itemCount++;
+    if (itemBatch.length >= batchSize) {
+      yield itemBatch;
+      itemBatch = [];
+      if (printBenchmark) {
+        const itemsPerSecond = Math.round((itemCount / (Date.now() - startTime)) * 1000);
+        const caller = new Error().stack?.split('at ')[3].trim();
+        logger.debug(`Iterated ${itemsPerSecond} items/second at ${caller}`);
+      }
+    }
+  }
+  if (itemBatch.length > 0) {
+    yield itemBatch;
+  }
+}
+
+/**
+ * Convert an `AsyncIterable` to a generator
+ * @param iter - AsyncIterable
+ */
+export async function* asyncIterableToGenerator<T>(iter: AsyncIterable<T>) {
+  for await (const entry of iter) {
+    yield entry;
+  }
+}
diff --git a/src/helpers/values.ts b/src/helpers/values.ts
index 6f7dd98..cc1a34e 100644
--- a/src/helpers/values.ts
+++ b/src/helpers/values.ts
@@ -130,3 +130,15 @@ export function numberToHex(number: number, paddingBytes: number = 4): string {
  * @returns Boolean
  */
 export const has0xPrefix = (val: string) => val.substring(0, 2).toLowerCase() === '0x';
+
+/**
+ * Converts a string to an enum value.
+ * @param enumType - The enum type
+ * @param value - The string value to convert
+ * @returns Enum item or undefined
+ */
+export function toEnumValue<T>(enm: { [s: string]: T }, value: string): T | undefined {
+  return (Object.values(enm) as unknown as string[]).includes(value)
+    ? (value as unknown as T)
+    : undefined;
+}