@airtop/plugin-batch-operate 1.0.0-alpha2.0 → 1.0.0-alpha2.2

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/README.md ADDED
@@ -0,0 +1,22 @@
1
+ # Airtop Batch Operate Plugin
2
+
3
+ This plugin adds batch operation support to the Airtop SDK.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @airtop/sdk @airtop/plugin-batch-operate
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { registerAirtopPlugin } from "@airtop/sdk";
15
+ import { batchOperatePlugin } from "@airtop/plugin-batch-operate";
16
+
17
+ registerAirtopPlugin(batchOperatePlugin);
18
+ ```
19
+
20
+ ## Docs
21
+
22
+ Visit the docs [here](https://docs-v2.airtop.ai/sdk/typescript/@airtop/plugin-batch-operate/interfaces/BatchOperateMethods/).
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/theo/projects/monarch/sdk-v2/wrapper-sdks/typescript/packages/plugins/batch-operate/dist/index.cjs","../src/batch-operate.plugin.ts","../src/lib/batch-util.ts","../src/lib/SessionQueue.ts","../src/lib/WindowQueue.ts","../src/lib/helpers.ts"],"names":["data"],"mappings":"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","file":"/Users/theo/projects/monarch/sdk-v2/wrapper-sdks/typescript/packages/plugins/batch-operate/dist/index.cjs","sourcesContent":[null,"import { AirtopPluginAugmentationType } from \"@airtop/sdk\";\nimport type { AirtopPluginRegistration } from \"@airtop/sdk\";\nimport type { AirtopClientPlugin } from \"@airtop/sdk\";\nimport type { AirtopClient } from \"@airtop/sdk\";\nimport { batchOperate } from \"./lib/batch-util.js\";\nimport type {\n BatchOperateConfig,\n BatchOperationInput,\n BatchOperationResponse,\n BatchOperationUrl,\n} from \"./lib/types.js\";\n\n// This is exported so the docs can expose it as we can't generate docs against the\n// module declaration\n/**\n * The methods provided by the Batch Operate plugin.\n */\nexport interface BatchOperateMethods {\n batchOperate: <T>(\n urls: BatchOperationUrl[],\n operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>,\n config?: BatchOperateConfig,\n ) => Promise<T[]>;\n}\n\ndeclare module \"@airtop/sdk\" {\n interface AirtopClient {\n batchOperate<T>(\n urls: BatchOperationUrl[],\n operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>,\n config?: BatchOperateConfig,\n ): Promise<T[]>;\n }\n}\n\nconst plugin: AirtopClientPlugin = {\n augmentationType: AirtopPluginAugmentationType.AirtopClient,\n augment: (prototype: typeof AirtopClient.prototype) => {\n prototype.batchOperate = async function <T>(\n urls: BatchOperationUrl[],\n operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>,\n config?: BatchOperateConfig,\n ): Promise<T[]> {\n return await batchOperate(urls, operation, this, config);\n };\n },\n};\n\n/**\n * Use with registerAirtopPlugin() from the Airtop SDK to add this plugin.\n */\nexport function batchOperatePlugin(): AirtopPluginRegistration {\n return {\n pluginsToAdd: [plugin],\n };\n}\n","import type { AirtopClient } from \"@airtop/sdk\";\nimport { EventEmitter } from \"eventemitter3\";\nimport { SessionQueue } from \"./SessionQueue.js\";\nimport { distributeUrlsToBatches } from \"./helpers.js\";\nimport type { BatchOperateConfig, BatchOperationInput, BatchOperationResponse, BatchOperationUrl } from \"./types.js\";\n\nconst DEFAULT_MAX_WINDOWS_PER_SESSION = 1;\nconst DEFAULT_MAX_CONCURRENT_SESSIONS = 30;\n\nexport const batchOperate = async <T>(\n urls: BatchOperationUrl[],\n operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>, // operation to invoke on each url\n client: AirtopClient,\n config?: BatchOperateConfig,\n): Promise<T[]> => {\n // Validate the urls before proceeding\n if (!Array.isArray(urls)) {\n throw new Error(\"Please provide a valid list of urls\");\n }\n\n for (const url of urls) {\n if (!url || typeof url !== \"object\" || !(\"url\" in url)) {\n throw new Error(\"Please provide a valid list of urls\");\n }\n }\n\n const runEmitter = new EventEmitter();\n\n const {\n maxConcurrentSessions = DEFAULT_MAX_CONCURRENT_SESSIONS,\n maxWindowsPerSession = DEFAULT_MAX_WINDOWS_PER_SESSION,\n sessionConfig,\n onError,\n } = config ?? {};\n\n // Split the urls into batches\n const initialBatches = distributeUrlsToBatches(urls, maxConcurrentSessions);\n const sessionQueue = new SessionQueue({\n maxConcurrentSessions,\n runEmitter,\n maxWindowsPerSession,\n initialBatches,\n operation,\n client,\n sessionConfig,\n onError,\n });\n\n runEmitter.on(\"addUrls\", (additionalUrls: BatchOperationUrl[]) => {\n sessionQueue.addUrlsToBatchQueue(additionalUrls);\n });\n\n await sessionQueue.processInitialBatches();\n\n return await sessionQueue.waitForProcessingToComplete();\n};\n","import type { Issue } from \"@airtop/sdk\";\nimport type { AirtopClient, ILogLayer } from \"@airtop/sdk\";\nimport type { CreateSessionConfig } from \"@airtop/sdk\";\nimport { Mutex } from \"async-mutex\";\nimport type { EventEmitter } from \"eventemitter3\";\nimport { WindowQueue } from \"./WindowQueue.js\";\nimport { distributeUrlsToBatches } from \"./helpers.js\";\nimport type { BatchOperationError, BatchOperationInput, BatchOperationResponse, BatchOperationUrl } from \"./types.js\";\n\nexport class SessionQueue<T> {\n private activePromises: Promise<void>[] = [];\n private activePromisesMutex = new Mutex();\n\n private maxConcurrentSessions: number;\n private runEmitter: EventEmitter;\n private maxWindowsPerSession: number;\n private sessionConfig?: CreateSessionConfig;\n private initialBatches: BatchOperationUrl[][] = [];\n private operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>;\n private onError?: (error: BatchOperationError) => Promise<void>;\n private isHalted: boolean;\n\n private batchQueue: BatchOperationUrl[][] = [];\n private batchQueueMutex = new Mutex();\n private latestProcessingPromise: Promise<void> | null = null;\n private processingPromisesCount = 0;\n\n private client: AirtopClient;\n private sessionPool: string[] = [];\n private sessionPoolMutex = new Mutex();\n\n private results: T[];\n private logger: ILogLayer;\n\n constructor({\n maxConcurrentSessions,\n runEmitter,\n maxWindowsPerSession,\n initialBatches,\n operation,\n client,\n sessionConfig,\n onError,\n }: {\n maxConcurrentSessions: number;\n runEmitter: EventEmitter;\n maxWindowsPerSession: number;\n initialBatches: BatchOperationUrl[][];\n operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>;\n client: AirtopClient;\n sessionConfig?: CreateSessionConfig;\n onError?: (error: BatchOperationError) => Promise<void>;\n }) {\n if (!Number.isInteger(maxConcurrentSessions) || maxConcurrentSessions <= 0) {\n throw new Error(\"maxConcurrentSessions must be a positive integer\");\n }\n\n this.maxConcurrentSessions = maxConcurrentSessions;\n this.runEmitter = runEmitter;\n this.maxWindowsPerSession = maxWindowsPerSession;\n this.sessionConfig = sessionConfig;\n this.initialBatches = initialBatches;\n this.operation = operation;\n this.onError = onError;\n this.latestProcessingPromise = null;\n this.results = [];\n this.client = client;\n this.isHalted = false;\n this.logger = client.getLogger();\n }\n\n public handleHaltEvent(): void {\n this.logger.info(\"Halt event received\");\n this.isHalted = true;\n }\n\n public async addUrlsToBatchQueue(newBatch: BatchOperationUrl[]): Promise<void> {\n // Distribute new URLs across batches\n const newBatches = distributeUrlsToBatches(newBatch, this.maxConcurrentSessions);\n\n this.logger\n .withMetadata({\n newBatches,\n })\n .info(\"Adding new batches to queue\");\n\n // Add new batches to the queue\n await this.batchQueueMutex.runExclusive(() => {\n this.batchQueue.push(...newBatches);\n });\n\n // Update existing processing promise\n this.processingPromisesCount++;\n this.latestProcessingPromise = this.processPendingBatches();\n }\n\n public async processInitialBatches(): Promise<void> {\n await this.batchQueueMutex.runExclusive(() => {\n this.batchQueue = [...this.initialBatches];\n });\n this.processingPromisesCount++;\n this.runEmitter.on(\"halt\", this.handleHaltEvent.bind(this));\n this.latestProcessingPromise = this.processPendingBatches();\n await this.latestProcessingPromise;\n }\n\n public async waitForProcessingToComplete(): Promise<T[]> {\n while (this.processingPromisesCount > 0) {\n await this.latestProcessingPromise;\n }\n\n await this.terminateAllSessions();\n\n this.runEmitter.removeListener(\"halt\", this.handleHaltEvent);\n\n return this.results;\n }\n\n private async terminateAllSessions(): Promise<void> {\n for (const sessionId of this.sessionPool) {\n this.safelyTerminateSession(sessionId);\n }\n }\n\n private async processPendingBatches(): Promise<void> {\n try {\n while (this.batchQueue.length > 0) {\n // Wait for any session to complete before starting a new one\n let shouldContinue = false;\n await this.activePromisesMutex.runExclusive(async () => {\n if (this.activePromises.length >= this.maxConcurrentSessions) {\n await Promise.race(this.activePromises);\n shouldContinue = true;\n }\n });\n\n if (shouldContinue) continue;\n\n let batch: BatchOperationUrl[] | undefined;\n await this.batchQueueMutex.runExclusive(() => {\n batch = this.batchQueue.shift();\n });\n\n if (!batch || batch.length === 0) break;\n\n const promise = (async () => {\n if (this.isHalted) {\n this.logger.info(\"Halt event received, skipping batch\");\n return;\n }\n\n let sessionId: string | undefined;\n try {\n // Check if there's an available session in the pool\n await this.sessionPoolMutex.runExclusive(() => {\n if (this.sessionPool.length > 0) {\n sessionId = this.sessionPool.pop();\n }\n });\n\n // Otherwise, create a new session\n if (!sessionId) {\n const { data: session, warnings, errors } = await this.client.createSession(this.sessionConfig);\n sessionId = session.id;\n\n this.handleErrorAndWarningResponses({ warnings, errors, sessionId, batch });\n }\n\n const queue = new WindowQueue(\n this.maxWindowsPerSession,\n this.runEmitter,\n sessionId,\n this.client,\n this.operation,\n this.onError,\n this.isHalted,\n );\n const windowResults = await queue.processInBatches(batch);\n this.results.push(...windowResults);\n\n // Return the session to the pool\n await this.sessionPoolMutex.runExclusive(() => {\n if (!sessionId) {\n throw new Error(\"Missing sessionId, cannot return to pool\");\n }\n\n this.sessionPool.push(sessionId);\n });\n } catch (error) {\n if (this.onError) {\n await this.handleErrorWithCallback({ originalError: error, batch, sessionId, callback: this.onError });\n } else {\n // By default, log the error and continue\n const urls = batch.map((url) => url.url);\n this.logErrorForUrls(urls, error);\n }\n\n // Clean up the session in case of error\n if (sessionId) {\n this.safelyTerminateSession(sessionId);\n }\n }\n })();\n\n await this.activePromisesMutex.runExclusive(() => {\n this.activePromises.push(promise);\n });\n\n // Remove the promise when it completes\n promise.finally(async () => {\n await this.activePromisesMutex.runExclusive(() => {\n const index = this.activePromises.indexOf(promise);\n if (index > -1) {\n this.activePromises.splice(index, 1);\n }\n });\n });\n }\n\n // Wait for all remaining sessions to complete\n await Promise.allSettled(this.activePromises);\n } finally {\n this.processingPromisesCount--;\n }\n }\n\n private async handleErrorWithCallback({\n originalError,\n batch,\n sessionId,\n callback,\n }: {\n originalError: unknown;\n batch: BatchOperationUrl[];\n sessionId?: string;\n callback: (error: BatchOperationError) => Promise<void>;\n }): Promise<void> {\n // Catch any errors in the onError callback to avoid halting the entire process\n try {\n await callback({\n error: this.formatError(originalError),\n operationUrls: batch,\n sessionId,\n });\n } catch (newError) {\n this.logger.error(\n `Error in onError callback: ${this.formatError(newError)}. Original error: ${this.formatError(originalError)}`,\n );\n }\n }\n\n private logErrorForUrls(urls: string[], error: unknown): void {\n this.logger\n .withError(error)\n .withMetadata({\n urls,\n })\n .error(\"Error for URLs\");\n }\n\n private safelyTerminateSession(sessionId: string): void {\n // Do not await since we don't want to block the main thread\n this.client\n .withSessionId(sessionId)\n .terminateSession()\n .catch((error) => {\n this.logger\n .withMetadata({\n sessionId,\n })\n .withError(error)\n .error(`Error terminating session ${sessionId}`);\n });\n }\n\n private formatError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n }\n\n private handleErrorAndWarningResponses({\n warnings,\n errors,\n sessionId,\n batch,\n }: { warnings?: Issue[]; errors?: Issue[]; sessionId: string; batch: BatchOperationUrl[] }): void {\n if (!warnings && !errors) return;\n\n const details: { sessionId: string; urls: BatchOperationUrl[]; warnings?: Issue[]; errors?: Issue[] } = {\n sessionId,\n urls: batch,\n };\n\n if (warnings) {\n details.warnings = warnings;\n this.logger\n .withMetadata({\n details,\n })\n .warn(\"Received warnings creating session\");\n }\n\n // Log an object with the errors and the URL\n if (errors) {\n details.errors = errors;\n this.logger.withMetadata({ details }).error(\"Received errors creating session\");\n }\n }\n}\n","import type { Issue } from \"@airtop/sdk\";\nimport type { AirtopClient } from \"@airtop/sdk\";\nimport type { ILogLayer } from \"@airtop/sdk\";\nimport { Mutex } from \"async-mutex\";\nimport type { EventEmitter } from \"eventemitter3\";\nimport type { BatchOperationError, BatchOperationInput, BatchOperationResponse, BatchOperationUrl } from \"./types.js\";\n\nexport class WindowQueue<T> {\n private activePromises: Promise<void>[] = [];\n private urlQueue: BatchOperationUrl[] = [];\n private activePromisesMutex = new Mutex();\n private urlQueueMutex = new Mutex();\n\n private maxWindowsPerSession: number;\n private runEmitter: EventEmitter;\n private sessionId: string;\n private client: AirtopClient;\n private operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>;\n private onError?: (error: BatchOperationError) => Promise<void>;\n private isHalted;\n private logger: ILogLayer;\n\n constructor(\n maxWindowsPerSession: number,\n runEmitter: EventEmitter,\n sessionId: string,\n client: AirtopClient,\n operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>,\n onError?: (error: BatchOperationError) => Promise<void>,\n isHalted = false,\n ) {\n if (!Number.isInteger(maxWindowsPerSession) || maxWindowsPerSession <= 0) {\n throw new Error(\"maxWindowsPerSession must be a positive integer\");\n }\n\n this.maxWindowsPerSession = maxWindowsPerSession;\n this.runEmitter = runEmitter;\n this.sessionId = sessionId;\n this.client = client;\n this.operation = operation;\n this.onError = onError;\n this.isHalted = isHalted;\n this.logger = this.client.getLogger();\n }\n\n public async addUrlToQueue(url: BatchOperationUrl): Promise<void> {\n await this.urlQueueMutex.runExclusive(() => {\n this.urlQueue.push(url);\n });\n }\n\n private handleHaltEvent(): void {\n this.logger.info(\"Halt event received\");\n this.isHalted = true;\n }\n\n async processInBatches(urls: BatchOperationUrl[]): Promise<T[]> {\n const results: T[] = [];\n this.runEmitter.on(\"halt\", this.handleHaltEvent.bind(this));\n\n await this.urlQueueMutex.runExclusive(() => {\n this.urlQueue = [...urls];\n });\n this.logger\n .withMetadata({\n urls,\n sessionId: this.sessionId,\n })\n .info(`Processing batch for session ${this.sessionId}`);\n\n while (this.urlQueue.length > 0) {\n // Wait for any window to complete before starting a new one\n let shouldContinue = false;\n await this.activePromisesMutex.runExclusive(async () => {\n if (this.activePromises.length >= this.maxWindowsPerSession) {\n await Promise.race(this.activePromises);\n shouldContinue = true;\n }\n });\n\n if (shouldContinue) continue;\n\n let urlData: BatchOperationUrl | undefined;\n await this.urlQueueMutex.runExclusive(() => {\n urlData = this.urlQueue.shift(); // Take the next url from the queue\n });\n if (!urlData) break; // No more urls to process\n\n // If we have less than the max concurrent operations, start a new one\n const promise = (async () => {\n // Do not process any more urls if the processing has been halted\n if (this.isHalted) {\n this.logger.info(`Processing halted, skipping window creation for ${urlData.url}`);\n return;\n }\n\n let windowId: string | undefined;\n let liveViewUrl: string | undefined;\n try {\n // Create a new window pointed to the url\n this.logger.info(`Creating window for ${urlData.url} in session ${this.sessionId}`);\n const { data, errors, warnings } = await this.client.withSessionId(this.sessionId).createWindow(urlData.url);\n\n windowId = data.windowId;\n\n this.handleErrorAndWarningResponses({\n warnings,\n errors,\n sessionId: this.sessionId,\n url: urlData,\n operation: \"window creation\",\n });\n\n if (!windowId) {\n throw new Error(`WindowId not found, errors: ${JSON.stringify(errors)}`);\n }\n\n const {\n data: windowInfo,\n warnings: windowWarnings,\n errors: windowErrors,\n } = await this.client.withSessionId(this.sessionId).withWindowId(windowId).getWindowInfo();\n liveViewUrl = windowInfo.liveViewUrl;\n\n this.handleErrorAndWarningResponses({\n warnings: windowWarnings,\n errors: windowErrors,\n sessionId: this.sessionId,\n url: urlData,\n operation: \"window info retrieval\",\n });\n\n // Run the operation on the window\n const result = await this.operation({\n windowId,\n sessionId: this.sessionId,\n liveViewUrl,\n operationUrl: urlData,\n });\n\n if (result) {\n const { shouldHaltBatch, additionalUrls, data } = result;\n\n if (data) {\n results.push(data);\n }\n\n if (shouldHaltBatch) {\n this.logger.info(\"Emitting halt event\");\n this.runEmitter.emit(\"halt\");\n }\n\n if (additionalUrls && additionalUrls.length > 0) {\n this.logger\n .withMetadata({\n additionalUrls,\n })\n .info(\"Emitting addUrls event\");\n this.runEmitter.emit(\"addUrls\", additionalUrls);\n }\n }\n } catch (error) {\n if (this.onError) {\n await this.handleErrorWithCallback({\n originalError: error,\n url: urlData,\n callback: this.onError,\n windowId,\n liveViewUrl,\n });\n } else {\n // By default, log the error and continue\n const message = `Error for URL ${urlData.url}: ${this.formatError(error)}`;\n this.logger.error(message);\n }\n } finally {\n if (windowId) {\n await this.safelyTerminateWindow(windowId);\n }\n }\n })();\n\n await this.activePromisesMutex.runExclusive(() => {\n this.activePromises.push(promise);\n });\n\n // Remove the promise from the active list when it resolves\n promise.finally(async () => {\n await this.activePromisesMutex.runExclusive(() => {\n const index = this.activePromises.indexOf(promise);\n if (index > -1) {\n this.activePromises.splice(index, 1);\n }\n });\n });\n }\n\n // Wait for all processes to complete\n await Promise.allSettled(this.activePromises);\n\n // Remove the halt listener\n this.runEmitter.removeListener(\"halt\", this.handleHaltEvent);\n\n return results;\n }\n\n private async handleErrorWithCallback({\n originalError,\n url,\n windowId,\n liveViewUrl,\n callback,\n }: {\n originalError: unknown;\n url: BatchOperationUrl;\n windowId?: string;\n liveViewUrl?: string;\n callback: (error: BatchOperationError) => Promise<void>;\n }): Promise<void> {\n // Catch any errors in the onError callback to avoid halting the entire process\n try {\n await callback({\n error: this.formatError(originalError),\n operationUrls: [url],\n sessionId: this.sessionId,\n windowId,\n liveViewUrl,\n });\n } catch (newError) {\n this.logger.error(\n `Error in onError callback: ${this.formatError(newError)}. Original error: ${this.formatError(originalError)}`,\n );\n }\n }\n\n private async safelyTerminateWindow(windowId: string): Promise<void> {\n try {\n await this.client.withSessionId(this.sessionId).withWindowId(windowId).close();\n } catch (error) {\n this.logger.withError(error).error(`Error closing window ${windowId}`);\n }\n }\n\n private formatError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n }\n\n private handleErrorAndWarningResponses({\n warnings,\n errors,\n sessionId,\n url,\n operation,\n }: { warnings?: Issue[]; errors?: Issue[]; sessionId: string; url: BatchOperationUrl; operation: string }): void {\n if (!warnings && !errors) return;\n\n const details: { sessionId: string; url: BatchOperationUrl; warnings?: Issue[]; errors?: Issue[] } = {\n sessionId,\n url,\n };\n\n if (warnings) {\n details.warnings = warnings;\n this.logger\n .withMetadata({\n details,\n })\n .warn(`Received warnings for ${operation}`);\n }\n\n if (errors) {\n details.errors = errors;\n this.logger\n .withMetadata({\n details,\n })\n .error(`Received errors for ${operation}`);\n }\n }\n}\n","import type { BatchOperationUrl } from \"./types.js\";\n\nexport const distributeUrlsToBatches = (\n urls: BatchOperationUrl[],\n maxConcurrentSessions: number,\n): BatchOperationUrl[][] => {\n if (urls.length === 0) return [];\n\n // Calculate optimal number of batches\n const batchCount = Math.min(maxConcurrentSessions, urls.length);\n const batches: BatchOperationUrl[][] = Array.from({ length: batchCount }, () => []);\n\n urls.forEach((url, index) => {\n const batchIndex = index % batchCount;\n if (!batches[batchIndex]) {\n batches[batchIndex] = [];\n }\n batches[batchIndex].push(url);\n });\n\n return batches;\n};\n"]}
1
+ {"version":3,"sources":["/home/runner/work/airtop-sdk/airtop-sdk/typescript/packages/plugins/batch-operate/dist/index.cjs","../src/batch-operate.plugin.ts","../src/lib/batch-util.ts","../src/lib/SessionQueue.ts","../src/lib/WindowQueue.ts","../src/lib/helpers.ts"],"names":["data"],"mappings":"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","file":"/home/runner/work/airtop-sdk/airtop-sdk/typescript/packages/plugins/batch-operate/dist/index.cjs","sourcesContent":[null,"import { AirtopPluginAugmentationType } from \"@airtop/sdk\";\nimport type { AirtopPluginRegistration } from \"@airtop/sdk\";\nimport type { AirtopClientPlugin } from \"@airtop/sdk\";\nimport type { AirtopClient } from \"@airtop/sdk\";\nimport { batchOperate } from \"./lib/batch-util.js\";\nimport type {\n BatchOperateConfig,\n BatchOperationInput,\n BatchOperationResponse,\n BatchOperationUrl,\n} from \"./lib/types.js\";\n\n// This is exported so the docs can expose it as we can't generate docs against the\n// module declaration\n/**\n * The methods provided by the Batch Operate plugin.\n */\nexport interface BatchOperateMethods {\n /**\n * Perform a batch operation on a list of URLs.\n * @param urls The list of URLs to perform the operation on.\n * @param operation The operation to perform on each URL.\n * @param config Configuration options for the batch operation.\n *\n * @template T The type of the response from the operation.\n *\n * @example\n * // AirtopClient instance\n * client.batchOperate(urls, operation, config);\n */\n batchOperate<T>(\n urls: BatchOperationUrl[],\n operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>,\n config?: BatchOperateConfig,\n ): Promise<T[]>;\n}\n\ndeclare module \"@airtop/sdk\" {\n interface AirtopClient {\n /**\n * Perform a batch operation on a list of URLs.\n * @param urls The list of URLs to perform the operation on.\n * @param operation The operation to perform on each URL.\n * @param config Configuration options for the batch operation.\n */\n batchOperate<T>(\n urls: BatchOperationUrl[],\n operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>,\n config?: BatchOperateConfig,\n ): Promise<T[]>;\n }\n}\n\nconst plugin: AirtopClientPlugin = {\n augmentationType: AirtopPluginAugmentationType.AirtopClient,\n augment: (prototype: typeof AirtopClient.prototype) => {\n prototype.batchOperate = async function <T>(\n urls: BatchOperationUrl[],\n operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>,\n config?: BatchOperateConfig,\n ): Promise<T[]> {\n return await batchOperate(urls, operation, this, config);\n };\n },\n};\n\n/**\n * Use with registerAirtopPlugin() from the Airtop SDK to add this plugin.\n */\nexport function batchOperatePlugin(): AirtopPluginRegistration {\n return {\n pluginsToAdd: [plugin],\n };\n}\n","import type { AirtopClient } from \"@airtop/sdk\";\nimport { EventEmitter } from \"eventemitter3\";\nimport { SessionQueue } from \"./SessionQueue.js\";\nimport { distributeUrlsToBatches } from \"./helpers.js\";\nimport type { BatchOperateConfig, BatchOperationInput, BatchOperationResponse, BatchOperationUrl } from \"./types.js\";\n\nconst DEFAULT_MAX_WINDOWS_PER_SESSION = 1;\nconst DEFAULT_MAX_CONCURRENT_SESSIONS = 30;\n\nexport const batchOperate = async <T>(\n urls: BatchOperationUrl[],\n operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>, // operation to invoke on each url\n client: AirtopClient,\n config?: BatchOperateConfig,\n): Promise<T[]> => {\n // Validate the urls before proceeding\n if (!Array.isArray(urls)) {\n throw new Error(\"Please provide a valid list of urls\");\n }\n\n for (const url of urls) {\n if (!url || typeof url !== \"object\" || !(\"url\" in url)) {\n throw new Error(\"Please provide a valid list of urls\");\n }\n }\n\n const runEmitter = new EventEmitter();\n\n const {\n maxConcurrentSessions = DEFAULT_MAX_CONCURRENT_SESSIONS,\n maxWindowsPerSession = DEFAULT_MAX_WINDOWS_PER_SESSION,\n sessionConfig,\n onError,\n } = config ?? {};\n\n // Split the urls into batches\n const initialBatches = distributeUrlsToBatches(urls, maxConcurrentSessions);\n const sessionQueue = new SessionQueue({\n maxConcurrentSessions,\n runEmitter,\n maxWindowsPerSession,\n initialBatches,\n operation,\n client,\n sessionConfig,\n onError,\n });\n\n runEmitter.on(\"addUrls\", (additionalUrls: BatchOperationUrl[]) => {\n sessionQueue.addUrlsToBatchQueue(additionalUrls);\n });\n\n await sessionQueue.processInitialBatches();\n\n return await sessionQueue.waitForProcessingToComplete();\n};\n","import type { Issue } from \"@airtop/sdk\";\nimport type { AirtopClient, ILogLayer } from \"@airtop/sdk\";\nimport type { CreateSessionConfig } from \"@airtop/sdk\";\nimport { Mutex } from \"async-mutex\";\nimport type { EventEmitter } from \"eventemitter3\";\nimport { WindowQueue } from \"./WindowQueue.js\";\nimport { distributeUrlsToBatches } from \"./helpers.js\";\nimport type { BatchOperationError, BatchOperationInput, BatchOperationResponse, BatchOperationUrl } from \"./types.js\";\n\nexport class SessionQueue<T> {\n private activePromises: Promise<void>[] = [];\n private activePromisesMutex = new Mutex();\n\n private maxConcurrentSessions: number;\n private runEmitter: EventEmitter;\n private maxWindowsPerSession: number;\n private sessionConfig?: CreateSessionConfig;\n private initialBatches: BatchOperationUrl[][] = [];\n private operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>;\n private onError?: (error: BatchOperationError) => Promise<void>;\n private isHalted: boolean;\n\n private batchQueue: BatchOperationUrl[][] = [];\n private batchQueueMutex = new Mutex();\n private latestProcessingPromise: Promise<void> | null = null;\n private processingPromisesCount = 0;\n\n private client: AirtopClient;\n private sessionPool: string[] = [];\n private sessionPoolMutex = new Mutex();\n\n private results: T[];\n private logger: ILogLayer;\n\n constructor({\n maxConcurrentSessions,\n runEmitter,\n maxWindowsPerSession,\n initialBatches,\n operation,\n client,\n sessionConfig,\n onError,\n }: {\n maxConcurrentSessions: number;\n runEmitter: EventEmitter;\n maxWindowsPerSession: number;\n initialBatches: BatchOperationUrl[][];\n operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>;\n client: AirtopClient;\n sessionConfig?: CreateSessionConfig;\n onError?: (error: BatchOperationError) => Promise<void>;\n }) {\n if (!Number.isInteger(maxConcurrentSessions) || maxConcurrentSessions <= 0) {\n throw new Error(\"maxConcurrentSessions must be a positive integer\");\n }\n\n this.maxConcurrentSessions = maxConcurrentSessions;\n this.runEmitter = runEmitter;\n this.maxWindowsPerSession = maxWindowsPerSession;\n this.sessionConfig = sessionConfig;\n this.initialBatches = initialBatches;\n this.operation = operation;\n this.onError = onError;\n this.latestProcessingPromise = null;\n this.results = [];\n this.client = client;\n this.isHalted = false;\n this.logger = client.getLogger();\n }\n\n public handleHaltEvent(): void {\n this.logger.info(\"Halt event received\");\n this.isHalted = true;\n }\n\n public async addUrlsToBatchQueue(newBatch: BatchOperationUrl[]): Promise<void> {\n // Distribute new URLs across batches\n const newBatches = distributeUrlsToBatches(newBatch, this.maxConcurrentSessions);\n\n this.logger\n .withMetadata({\n newBatches,\n })\n .info(\"Adding new batches to queue\");\n\n // Add new batches to the queue\n await this.batchQueueMutex.runExclusive(() => {\n this.batchQueue.push(...newBatches);\n });\n\n // Update existing processing promise\n this.processingPromisesCount++;\n this.latestProcessingPromise = this.processPendingBatches();\n }\n\n public async processInitialBatches(): Promise<void> {\n await this.batchQueueMutex.runExclusive(() => {\n this.batchQueue = [...this.initialBatches];\n });\n this.processingPromisesCount++;\n this.runEmitter.on(\"halt\", this.handleHaltEvent.bind(this));\n this.latestProcessingPromise = this.processPendingBatches();\n await this.latestProcessingPromise;\n }\n\n public async waitForProcessingToComplete(): Promise<T[]> {\n while (this.processingPromisesCount > 0) {\n await this.latestProcessingPromise;\n }\n\n await this.terminateAllSessions();\n\n this.runEmitter.removeListener(\"halt\", this.handleHaltEvent);\n\n return this.results;\n }\n\n private async terminateAllSessions(): Promise<void> {\n for (const sessionId of this.sessionPool) {\n this.safelyTerminateSession(sessionId);\n }\n }\n\n private async processPendingBatches(): Promise<void> {\n try {\n while (this.batchQueue.length > 0) {\n // Wait for any session to complete before starting a new one\n let shouldContinue = false;\n await this.activePromisesMutex.runExclusive(async () => {\n if (this.activePromises.length >= this.maxConcurrentSessions) {\n await Promise.race(this.activePromises);\n shouldContinue = true;\n }\n });\n\n if (shouldContinue) continue;\n\n let batch: BatchOperationUrl[] | undefined;\n await this.batchQueueMutex.runExclusive(() => {\n batch = this.batchQueue.shift();\n });\n\n if (!batch || batch.length === 0) break;\n\n const promise = (async () => {\n if (this.isHalted) {\n this.logger.info(\"Halt event received, skipping batch\");\n return;\n }\n\n let sessionId: string | undefined;\n try {\n // Check if there's an available session in the pool\n await this.sessionPoolMutex.runExclusive(() => {\n if (this.sessionPool.length > 0) {\n sessionId = this.sessionPool.pop();\n }\n });\n\n // Otherwise, create a new session\n if (!sessionId) {\n const { data: session, warnings, errors } = await this.client.createSession(this.sessionConfig);\n sessionId = session.id;\n\n this.handleErrorAndWarningResponses({ warnings, errors, sessionId, batch });\n }\n\n const queue = new WindowQueue(\n this.maxWindowsPerSession,\n this.runEmitter,\n sessionId,\n this.client,\n this.operation,\n this.onError,\n this.isHalted,\n );\n const windowResults = await queue.processInBatches(batch);\n this.results.push(...windowResults);\n\n // Return the session to the pool\n await this.sessionPoolMutex.runExclusive(() => {\n if (!sessionId) {\n throw new Error(\"Missing sessionId, cannot return to pool\");\n }\n\n this.sessionPool.push(sessionId);\n });\n } catch (error) {\n if (this.onError) {\n await this.handleErrorWithCallback({ originalError: error, batch, sessionId, callback: this.onError });\n } else {\n // By default, log the error and continue\n const urls = batch.map((url) => url.url);\n this.logErrorForUrls(urls, error);\n }\n\n // Clean up the session in case of error\n if (sessionId) {\n this.safelyTerminateSession(sessionId);\n }\n }\n })();\n\n await this.activePromisesMutex.runExclusive(() => {\n this.activePromises.push(promise);\n });\n\n // Remove the promise when it completes\n promise.finally(async () => {\n await this.activePromisesMutex.runExclusive(() => {\n const index = this.activePromises.indexOf(promise);\n if (index > -1) {\n this.activePromises.splice(index, 1);\n }\n });\n });\n }\n\n // Wait for all remaining sessions to complete\n await Promise.allSettled(this.activePromises);\n } finally {\n this.processingPromisesCount--;\n }\n }\n\n private async handleErrorWithCallback({\n originalError,\n batch,\n sessionId,\n callback,\n }: {\n originalError: unknown;\n batch: BatchOperationUrl[];\n sessionId?: string;\n callback: (error: BatchOperationError) => Promise<void>;\n }): Promise<void> {\n // Catch any errors in the onError callback to avoid halting the entire process\n try {\n await callback({\n error: this.formatError(originalError),\n operationUrls: batch,\n sessionId,\n });\n } catch (newError) {\n this.logger.error(\n `Error in onError callback: ${this.formatError(newError)}. Original error: ${this.formatError(originalError)}`,\n );\n }\n }\n\n private logErrorForUrls(urls: string[], error: unknown): void {\n this.logger\n .withError(error)\n .withMetadata({\n urls,\n })\n .error(\"Error for URLs\");\n }\n\n private safelyTerminateSession(sessionId: string): void {\n // Do not await since we don't want to block the main thread\n this.client\n .withSessionId(sessionId)\n .terminateSession()\n .catch((error) => {\n this.logger\n .withMetadata({\n sessionId,\n })\n .withError(error)\n .error(`Error terminating session ${sessionId}`);\n });\n }\n\n private formatError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n }\n\n private handleErrorAndWarningResponses({\n warnings,\n errors,\n sessionId,\n batch,\n }: { warnings?: Issue[]; errors?: Issue[]; sessionId: string; batch: BatchOperationUrl[] }): void {\n if (!warnings && !errors) return;\n\n const details: { sessionId: string; urls: BatchOperationUrl[]; warnings?: Issue[]; errors?: Issue[] } = {\n sessionId,\n urls: batch,\n };\n\n if (warnings) {\n details.warnings = warnings;\n this.logger\n .withMetadata({\n details,\n })\n .warn(\"Received warnings creating session\");\n }\n\n // Log an object with the errors and the URL\n if (errors) {\n details.errors = errors;\n this.logger.withMetadata({ details }).error(\"Received errors creating session\");\n }\n }\n}\n","import type { Issue } from \"@airtop/sdk\";\nimport type { AirtopClient } from \"@airtop/sdk\";\nimport type { ILogLayer } from \"@airtop/sdk\";\nimport { Mutex } from \"async-mutex\";\nimport type { EventEmitter } from \"eventemitter3\";\nimport type { BatchOperationError, BatchOperationInput, BatchOperationResponse, BatchOperationUrl } from \"./types.js\";\n\nexport class WindowQueue<T> {\n private activePromises: Promise<void>[] = [];\n private urlQueue: BatchOperationUrl[] = [];\n private activePromisesMutex = new Mutex();\n private urlQueueMutex = new Mutex();\n\n private maxWindowsPerSession: number;\n private runEmitter: EventEmitter;\n private sessionId: string;\n private client: AirtopClient;\n private operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>;\n private onError?: (error: BatchOperationError) => Promise<void>;\n private isHalted;\n private logger: ILogLayer;\n\n constructor(\n maxWindowsPerSession: number,\n runEmitter: EventEmitter,\n sessionId: string,\n client: AirtopClient,\n operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>,\n onError?: (error: BatchOperationError) => Promise<void>,\n isHalted = false,\n ) {\n if (!Number.isInteger(maxWindowsPerSession) || maxWindowsPerSession <= 0) {\n throw new Error(\"maxWindowsPerSession must be a positive integer\");\n }\n\n this.maxWindowsPerSession = maxWindowsPerSession;\n this.runEmitter = runEmitter;\n this.sessionId = sessionId;\n this.client = client;\n this.operation = operation;\n this.onError = onError;\n this.isHalted = isHalted;\n this.logger = this.client.getLogger();\n }\n\n public async addUrlToQueue(url: BatchOperationUrl): Promise<void> {\n await this.urlQueueMutex.runExclusive(() => {\n this.urlQueue.push(url);\n });\n }\n\n private handleHaltEvent(): void {\n this.logger.info(\"Halt event received\");\n this.isHalted = true;\n }\n\n async processInBatches(urls: BatchOperationUrl[]): Promise<T[]> {\n const results: T[] = [];\n this.runEmitter.on(\"halt\", this.handleHaltEvent.bind(this));\n\n await this.urlQueueMutex.runExclusive(() => {\n this.urlQueue = [...urls];\n });\n this.logger\n .withMetadata({\n urls,\n sessionId: this.sessionId,\n })\n .info(`Processing batch for session ${this.sessionId}`);\n\n while (this.urlQueue.length > 0) {\n // Wait for any window to complete before starting a new one\n let shouldContinue = false;\n await this.activePromisesMutex.runExclusive(async () => {\n if (this.activePromises.length >= this.maxWindowsPerSession) {\n await Promise.race(this.activePromises);\n shouldContinue = true;\n }\n });\n\n if (shouldContinue) continue;\n\n let urlData: BatchOperationUrl | undefined;\n await this.urlQueueMutex.runExclusive(() => {\n urlData = this.urlQueue.shift(); // Take the next url from the queue\n });\n if (!urlData) break; // No more urls to process\n\n // If we have less than the max concurrent operations, start a new one\n const promise = (async () => {\n // Do not process any more urls if the processing has been halted\n if (this.isHalted) {\n this.logger.info(`Processing halted, skipping window creation for ${urlData.url}`);\n return;\n }\n\n let windowId: string | undefined;\n let liveViewUrl: string | undefined;\n try {\n // Create a new window pointed to the url\n this.logger.info(`Creating window for ${urlData.url} in session ${this.sessionId}`);\n const { data, errors, warnings } = await this.client.withSessionId(this.sessionId).createWindow(urlData.url);\n\n windowId = data.windowId;\n\n this.handleErrorAndWarningResponses({\n warnings,\n errors,\n sessionId: this.sessionId,\n url: urlData,\n operation: \"window creation\",\n });\n\n if (!windowId) {\n throw new Error(`WindowId not found, errors: ${JSON.stringify(errors)}`);\n }\n\n const {\n data: windowInfo,\n warnings: windowWarnings,\n errors: windowErrors,\n } = await this.client.withSessionId(this.sessionId).withWindowId(windowId).getWindowInfo();\n liveViewUrl = windowInfo.liveViewUrl;\n\n this.handleErrorAndWarningResponses({\n warnings: windowWarnings,\n errors: windowErrors,\n sessionId: this.sessionId,\n url: urlData,\n operation: \"window info retrieval\",\n });\n\n // Run the operation on the window\n const result = await this.operation({\n windowId,\n sessionId: this.sessionId,\n liveViewUrl,\n operationUrl: urlData,\n });\n\n if (result) {\n const { shouldHaltBatch, additionalUrls, data } = result;\n\n if (data) {\n results.push(data);\n }\n\n if (shouldHaltBatch) {\n this.logger.info(\"Emitting halt event\");\n this.runEmitter.emit(\"halt\");\n }\n\n if (additionalUrls && additionalUrls.length > 0) {\n this.logger\n .withMetadata({\n additionalUrls,\n })\n .info(\"Emitting addUrls event\");\n this.runEmitter.emit(\"addUrls\", additionalUrls);\n }\n }\n } catch (error) {\n if (this.onError) {\n await this.handleErrorWithCallback({\n originalError: error,\n url: urlData,\n callback: this.onError,\n windowId,\n liveViewUrl,\n });\n } else {\n // By default, log the error and continue\n const message = `Error for URL ${urlData.url}: ${this.formatError(error)}`;\n this.logger.error(message);\n }\n } finally {\n if (windowId) {\n await this.safelyTerminateWindow(windowId);\n }\n }\n })();\n\n await this.activePromisesMutex.runExclusive(() => {\n this.activePromises.push(promise);\n });\n\n // Remove the promise from the active list when it resolves\n promise.finally(async () => {\n await this.activePromisesMutex.runExclusive(() => {\n const index = this.activePromises.indexOf(promise);\n if (index > -1) {\n this.activePromises.splice(index, 1);\n }\n });\n });\n }\n\n // Wait for all processes to complete\n await Promise.allSettled(this.activePromises);\n\n // Remove the halt listener\n this.runEmitter.removeListener(\"halt\", this.handleHaltEvent);\n\n return results;\n }\n\n private async handleErrorWithCallback({\n originalError,\n url,\n windowId,\n liveViewUrl,\n callback,\n }: {\n originalError: unknown;\n url: BatchOperationUrl;\n windowId?: string;\n liveViewUrl?: string;\n callback: (error: BatchOperationError) => Promise<void>;\n }): Promise<void> {\n // Catch any errors in the onError callback to avoid halting the entire process\n try {\n await callback({\n error: this.formatError(originalError),\n operationUrls: [url],\n sessionId: this.sessionId,\n windowId,\n liveViewUrl,\n });\n } catch (newError) {\n this.logger.error(\n `Error in onError callback: ${this.formatError(newError)}. Original error: ${this.formatError(originalError)}`,\n );\n }\n }\n\n private async safelyTerminateWindow(windowId: string): Promise<void> {\n try {\n await this.client.withSessionId(this.sessionId).withWindowId(windowId).close();\n } catch (error) {\n this.logger.withError(error).error(`Error closing window ${windowId}`);\n }\n }\n\n private formatError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n }\n\n private handleErrorAndWarningResponses({\n warnings,\n errors,\n sessionId,\n url,\n operation,\n }: { warnings?: Issue[]; errors?: Issue[]; sessionId: string; url: BatchOperationUrl; operation: string }): void {\n if (!warnings && !errors) return;\n\n const details: { sessionId: string; url: BatchOperationUrl; warnings?: Issue[]; errors?: Issue[] } = {\n sessionId,\n url,\n };\n\n if (warnings) {\n details.warnings = warnings;\n this.logger\n .withMetadata({\n details,\n })\n .warn(`Received warnings for ${operation}`);\n }\n\n if (errors) {\n details.errors = errors;\n this.logger\n .withMetadata({\n details,\n })\n .error(`Received errors for ${operation}`);\n }\n }\n}\n","import type { BatchOperationUrl } from \"./types.js\";\n\nexport const distributeUrlsToBatches = (\n urls: BatchOperationUrl[],\n maxConcurrentSessions: number,\n): BatchOperationUrl[][] => {\n if (urls.length === 0) return [];\n\n // Calculate optimal number of batches\n const batchCount = Math.min(maxConcurrentSessions, urls.length);\n const batches: BatchOperationUrl[][] = Array.from({ length: batchCount }, () => []);\n\n urls.forEach((url, index) => {\n const batchIndex = index % batchCount;\n if (!batches[batchIndex]) {\n batches[batchIndex] = [];\n }\n batches[batchIndex].push(url);\n });\n\n return batches;\n};\n"]}
package/dist/index.d.cts CHANGED
@@ -33,10 +33,28 @@ interface BatchOperationError {
33
33
  * The methods provided by the Batch Operate plugin.
34
34
  */
35
35
  interface BatchOperateMethods {
36
- batchOperate: <T>(urls: BatchOperationUrl[], operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>, config?: BatchOperateConfig) => Promise<T[]>;
36
+ /**
37
+ * Perform a batch operation on a list of URLs.
38
+ * @param urls The list of URLs to perform the operation on.
39
+ * @param operation The operation to perform on each URL.
40
+ * @param config Configuration options for the batch operation.
41
+ *
42
+ * @template T The type of the response from the operation.
43
+ *
44
+ * @example
45
+ * // AirtopClient instance
46
+ * client.batchOperate(urls, operation, config);
47
+ */
48
+ batchOperate<T>(urls: BatchOperationUrl[], operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>, config?: BatchOperateConfig): Promise<T[]>;
37
49
  }
38
50
  declare module "@airtop/sdk" {
39
51
  interface AirtopClient {
52
+ /**
53
+ * Perform a batch operation on a list of URLs.
54
+ * @param urls The list of URLs to perform the operation on.
55
+ * @param operation The operation to perform on each URL.
56
+ * @param config Configuration options for the batch operation.
57
+ */
40
58
  batchOperate<T>(urls: BatchOperationUrl[], operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>, config?: BatchOperateConfig): Promise<T[]>;
41
59
  }
42
60
  }
package/dist/index.d.ts CHANGED
@@ -33,10 +33,28 @@ interface BatchOperationError {
33
33
  * The methods provided by the Batch Operate plugin.
34
34
  */
35
35
  interface BatchOperateMethods {
36
- batchOperate: <T>(urls: BatchOperationUrl[], operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>, config?: BatchOperateConfig) => Promise<T[]>;
36
+ /**
37
+ * Perform a batch operation on a list of URLs.
38
+ * @param urls The list of URLs to perform the operation on.
39
+ * @param operation The operation to perform on each URL.
40
+ * @param config Configuration options for the batch operation.
41
+ *
42
+ * @template T The type of the response from the operation.
43
+ *
44
+ * @example
45
+ * // AirtopClient instance
46
+ * client.batchOperate(urls, operation, config);
47
+ */
48
+ batchOperate<T>(urls: BatchOperationUrl[], operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>, config?: BatchOperateConfig): Promise<T[]>;
37
49
  }
38
50
  declare module "@airtop/sdk" {
39
51
  interface AirtopClient {
52
+ /**
53
+ * Perform a batch operation on a list of URLs.
54
+ * @param urls The list of URLs to perform the operation on.
55
+ * @param operation The operation to perform on each URL.
56
+ * @param config Configuration options for the batch operation.
57
+ */
40
58
  batchOperate<T>(urls: BatchOperationUrl[], operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>, config?: BatchOperateConfig): Promise<T[]>;
41
59
  }
42
60
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/batch-operate.plugin.ts","../src/lib/batch-util.ts","../src/lib/SessionQueue.ts","../src/lib/WindowQueue.ts","../src/lib/helpers.ts"],"sourcesContent":["import { AirtopPluginAugmentationType } from \"@airtop/sdk\";\nimport type { AirtopPluginRegistration } from \"@airtop/sdk\";\nimport type { AirtopClientPlugin } from \"@airtop/sdk\";\nimport type { AirtopClient } from \"@airtop/sdk\";\nimport { batchOperate } from \"./lib/batch-util.js\";\nimport type {\n BatchOperateConfig,\n BatchOperationInput,\n BatchOperationResponse,\n BatchOperationUrl,\n} from \"./lib/types.js\";\n\n// This is exported so the docs can expose it as we can't generate docs against the\n// module declaration\n/**\n * The methods provided by the Batch Operate plugin.\n */\nexport interface BatchOperateMethods {\n batchOperate: <T>(\n urls: BatchOperationUrl[],\n operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>,\n config?: BatchOperateConfig,\n ) => Promise<T[]>;\n}\n\ndeclare module \"@airtop/sdk\" {\n interface AirtopClient {\n batchOperate<T>(\n urls: BatchOperationUrl[],\n operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>,\n config?: BatchOperateConfig,\n ): Promise<T[]>;\n }\n}\n\nconst plugin: AirtopClientPlugin = {\n augmentationType: AirtopPluginAugmentationType.AirtopClient,\n augment: (prototype: typeof AirtopClient.prototype) => {\n prototype.batchOperate = async function <T>(\n urls: BatchOperationUrl[],\n operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>,\n config?: BatchOperateConfig,\n ): Promise<T[]> {\n return await batchOperate(urls, operation, this, config);\n };\n },\n};\n\n/**\n * Use with registerAirtopPlugin() from the Airtop SDK to add this plugin.\n */\nexport function batchOperatePlugin(): AirtopPluginRegistration {\n return {\n pluginsToAdd: [plugin],\n };\n}\n","import type { AirtopClient } from \"@airtop/sdk\";\nimport { EventEmitter } from \"eventemitter3\";\nimport { SessionQueue } from \"./SessionQueue.js\";\nimport { distributeUrlsToBatches } from \"./helpers.js\";\nimport type { BatchOperateConfig, BatchOperationInput, BatchOperationResponse, BatchOperationUrl } from \"./types.js\";\n\nconst DEFAULT_MAX_WINDOWS_PER_SESSION = 1;\nconst DEFAULT_MAX_CONCURRENT_SESSIONS = 30;\n\nexport const batchOperate = async <T>(\n urls: BatchOperationUrl[],\n operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>, // operation to invoke on each url\n client: AirtopClient,\n config?: BatchOperateConfig,\n): Promise<T[]> => {\n // Validate the urls before proceeding\n if (!Array.isArray(urls)) {\n throw new Error(\"Please provide a valid list of urls\");\n }\n\n for (const url of urls) {\n if (!url || typeof url !== \"object\" || !(\"url\" in url)) {\n throw new Error(\"Please provide a valid list of urls\");\n }\n }\n\n const runEmitter = new EventEmitter();\n\n const {\n maxConcurrentSessions = DEFAULT_MAX_CONCURRENT_SESSIONS,\n maxWindowsPerSession = DEFAULT_MAX_WINDOWS_PER_SESSION,\n sessionConfig,\n onError,\n } = config ?? {};\n\n // Split the urls into batches\n const initialBatches = distributeUrlsToBatches(urls, maxConcurrentSessions);\n const sessionQueue = new SessionQueue({\n maxConcurrentSessions,\n runEmitter,\n maxWindowsPerSession,\n initialBatches,\n operation,\n client,\n sessionConfig,\n onError,\n });\n\n runEmitter.on(\"addUrls\", (additionalUrls: BatchOperationUrl[]) => {\n sessionQueue.addUrlsToBatchQueue(additionalUrls);\n });\n\n await sessionQueue.processInitialBatches();\n\n return await sessionQueue.waitForProcessingToComplete();\n};\n","import type { Issue } from \"@airtop/sdk\";\nimport type { AirtopClient, ILogLayer } from \"@airtop/sdk\";\nimport type { CreateSessionConfig } from \"@airtop/sdk\";\nimport { Mutex } from \"async-mutex\";\nimport type { EventEmitter } from \"eventemitter3\";\nimport { WindowQueue } from \"./WindowQueue.js\";\nimport { distributeUrlsToBatches } from \"./helpers.js\";\nimport type { BatchOperationError, BatchOperationInput, BatchOperationResponse, BatchOperationUrl } from \"./types.js\";\n\nexport class SessionQueue<T> {\n private activePromises: Promise<void>[] = [];\n private activePromisesMutex = new Mutex();\n\n private maxConcurrentSessions: number;\n private runEmitter: EventEmitter;\n private maxWindowsPerSession: number;\n private sessionConfig?: CreateSessionConfig;\n private initialBatches: BatchOperationUrl[][] = [];\n private operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>;\n private onError?: (error: BatchOperationError) => Promise<void>;\n private isHalted: boolean;\n\n private batchQueue: BatchOperationUrl[][] = [];\n private batchQueueMutex = new Mutex();\n private latestProcessingPromise: Promise<void> | null = null;\n private processingPromisesCount = 0;\n\n private client: AirtopClient;\n private sessionPool: string[] = [];\n private sessionPoolMutex = new Mutex();\n\n private results: T[];\n private logger: ILogLayer;\n\n constructor({\n maxConcurrentSessions,\n runEmitter,\n maxWindowsPerSession,\n initialBatches,\n operation,\n client,\n sessionConfig,\n onError,\n }: {\n maxConcurrentSessions: number;\n runEmitter: EventEmitter;\n maxWindowsPerSession: number;\n initialBatches: BatchOperationUrl[][];\n operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>;\n client: AirtopClient;\n sessionConfig?: CreateSessionConfig;\n onError?: (error: BatchOperationError) => Promise<void>;\n }) {\n if (!Number.isInteger(maxConcurrentSessions) || maxConcurrentSessions <= 0) {\n throw new Error(\"maxConcurrentSessions must be a positive integer\");\n }\n\n this.maxConcurrentSessions = maxConcurrentSessions;\n this.runEmitter = runEmitter;\n this.maxWindowsPerSession = maxWindowsPerSession;\n this.sessionConfig = sessionConfig;\n this.initialBatches = initialBatches;\n this.operation = operation;\n this.onError = onError;\n this.latestProcessingPromise = null;\n this.results = [];\n this.client = client;\n this.isHalted = false;\n this.logger = client.getLogger();\n }\n\n public handleHaltEvent(): void {\n this.logger.info(\"Halt event received\");\n this.isHalted = true;\n }\n\n public async addUrlsToBatchQueue(newBatch: BatchOperationUrl[]): Promise<void> {\n // Distribute new URLs across batches\n const newBatches = distributeUrlsToBatches(newBatch, this.maxConcurrentSessions);\n\n this.logger\n .withMetadata({\n newBatches,\n })\n .info(\"Adding new batches to queue\");\n\n // Add new batches to the queue\n await this.batchQueueMutex.runExclusive(() => {\n this.batchQueue.push(...newBatches);\n });\n\n // Update existing processing promise\n this.processingPromisesCount++;\n this.latestProcessingPromise = this.processPendingBatches();\n }\n\n public async processInitialBatches(): Promise<void> {\n await this.batchQueueMutex.runExclusive(() => {\n this.batchQueue = [...this.initialBatches];\n });\n this.processingPromisesCount++;\n this.runEmitter.on(\"halt\", this.handleHaltEvent.bind(this));\n this.latestProcessingPromise = this.processPendingBatches();\n await this.latestProcessingPromise;\n }\n\n public async waitForProcessingToComplete(): Promise<T[]> {\n while (this.processingPromisesCount > 0) {\n await this.latestProcessingPromise;\n }\n\n await this.terminateAllSessions();\n\n this.runEmitter.removeListener(\"halt\", this.handleHaltEvent);\n\n return this.results;\n }\n\n private async terminateAllSessions(): Promise<void> {\n for (const sessionId of this.sessionPool) {\n this.safelyTerminateSession(sessionId);\n }\n }\n\n private async processPendingBatches(): Promise<void> {\n try {\n while (this.batchQueue.length > 0) {\n // Wait for any session to complete before starting a new one\n let shouldContinue = false;\n await this.activePromisesMutex.runExclusive(async () => {\n if (this.activePromises.length >= this.maxConcurrentSessions) {\n await Promise.race(this.activePromises);\n shouldContinue = true;\n }\n });\n\n if (shouldContinue) continue;\n\n let batch: BatchOperationUrl[] | undefined;\n await this.batchQueueMutex.runExclusive(() => {\n batch = this.batchQueue.shift();\n });\n\n if (!batch || batch.length === 0) break;\n\n const promise = (async () => {\n if (this.isHalted) {\n this.logger.info(\"Halt event received, skipping batch\");\n return;\n }\n\n let sessionId: string | undefined;\n try {\n // Check if there's an available session in the pool\n await this.sessionPoolMutex.runExclusive(() => {\n if (this.sessionPool.length > 0) {\n sessionId = this.sessionPool.pop();\n }\n });\n\n // Otherwise, create a new session\n if (!sessionId) {\n const { data: session, warnings, errors } = await this.client.createSession(this.sessionConfig);\n sessionId = session.id;\n\n this.handleErrorAndWarningResponses({ warnings, errors, sessionId, batch });\n }\n\n const queue = new WindowQueue(\n this.maxWindowsPerSession,\n this.runEmitter,\n sessionId,\n this.client,\n this.operation,\n this.onError,\n this.isHalted,\n );\n const windowResults = await queue.processInBatches(batch);\n this.results.push(...windowResults);\n\n // Return the session to the pool\n await this.sessionPoolMutex.runExclusive(() => {\n if (!sessionId) {\n throw new Error(\"Missing sessionId, cannot return to pool\");\n }\n\n this.sessionPool.push(sessionId);\n });\n } catch (error) {\n if (this.onError) {\n await this.handleErrorWithCallback({ originalError: error, batch, sessionId, callback: this.onError });\n } else {\n // By default, log the error and continue\n const urls = batch.map((url) => url.url);\n this.logErrorForUrls(urls, error);\n }\n\n // Clean up the session in case of error\n if (sessionId) {\n this.safelyTerminateSession(sessionId);\n }\n }\n })();\n\n await this.activePromisesMutex.runExclusive(() => {\n this.activePromises.push(promise);\n });\n\n // Remove the promise when it completes\n promise.finally(async () => {\n await this.activePromisesMutex.runExclusive(() => {\n const index = this.activePromises.indexOf(promise);\n if (index > -1) {\n this.activePromises.splice(index, 1);\n }\n });\n });\n }\n\n // Wait for all remaining sessions to complete\n await Promise.allSettled(this.activePromises);\n } finally {\n this.processingPromisesCount--;\n }\n }\n\n private async handleErrorWithCallback({\n originalError,\n batch,\n sessionId,\n callback,\n }: {\n originalError: unknown;\n batch: BatchOperationUrl[];\n sessionId?: string;\n callback: (error: BatchOperationError) => Promise<void>;\n }): Promise<void> {\n // Catch any errors in the onError callback to avoid halting the entire process\n try {\n await callback({\n error: this.formatError(originalError),\n operationUrls: batch,\n sessionId,\n });\n } catch (newError) {\n this.logger.error(\n `Error in onError callback: ${this.formatError(newError)}. Original error: ${this.formatError(originalError)}`,\n );\n }\n }\n\n private logErrorForUrls(urls: string[], error: unknown): void {\n this.logger\n .withError(error)\n .withMetadata({\n urls,\n })\n .error(\"Error for URLs\");\n }\n\n private safelyTerminateSession(sessionId: string): void {\n // Do not await since we don't want to block the main thread\n this.client\n .withSessionId(sessionId)\n .terminateSession()\n .catch((error) => {\n this.logger\n .withMetadata({\n sessionId,\n })\n .withError(error)\n .error(`Error terminating session ${sessionId}`);\n });\n }\n\n private formatError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n }\n\n private handleErrorAndWarningResponses({\n warnings,\n errors,\n sessionId,\n batch,\n }: { warnings?: Issue[]; errors?: Issue[]; sessionId: string; batch: BatchOperationUrl[] }): void {\n if (!warnings && !errors) return;\n\n const details: { sessionId: string; urls: BatchOperationUrl[]; warnings?: Issue[]; errors?: Issue[] } = {\n sessionId,\n urls: batch,\n };\n\n if (warnings) {\n details.warnings = warnings;\n this.logger\n .withMetadata({\n details,\n })\n .warn(\"Received warnings creating session\");\n }\n\n // Log an object with the errors and the URL\n if (errors) {\n details.errors = errors;\n this.logger.withMetadata({ details }).error(\"Received errors creating session\");\n }\n }\n}\n","import type { Issue } from \"@airtop/sdk\";\nimport type { AirtopClient } from \"@airtop/sdk\";\nimport type { ILogLayer } from \"@airtop/sdk\";\nimport { Mutex } from \"async-mutex\";\nimport type { EventEmitter } from \"eventemitter3\";\nimport type { BatchOperationError, BatchOperationInput, BatchOperationResponse, BatchOperationUrl } from \"./types.js\";\n\nexport class WindowQueue<T> {\n private activePromises: Promise<void>[] = [];\n private urlQueue: BatchOperationUrl[] = [];\n private activePromisesMutex = new Mutex();\n private urlQueueMutex = new Mutex();\n\n private maxWindowsPerSession: number;\n private runEmitter: EventEmitter;\n private sessionId: string;\n private client: AirtopClient;\n private operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>;\n private onError?: (error: BatchOperationError) => Promise<void>;\n private isHalted;\n private logger: ILogLayer;\n\n constructor(\n maxWindowsPerSession: number,\n runEmitter: EventEmitter,\n sessionId: string,\n client: AirtopClient,\n operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>,\n onError?: (error: BatchOperationError) => Promise<void>,\n isHalted = false,\n ) {\n if (!Number.isInteger(maxWindowsPerSession) || maxWindowsPerSession <= 0) {\n throw new Error(\"maxWindowsPerSession must be a positive integer\");\n }\n\n this.maxWindowsPerSession = maxWindowsPerSession;\n this.runEmitter = runEmitter;\n this.sessionId = sessionId;\n this.client = client;\n this.operation = operation;\n this.onError = onError;\n this.isHalted = isHalted;\n this.logger = this.client.getLogger();\n }\n\n public async addUrlToQueue(url: BatchOperationUrl): Promise<void> {\n await this.urlQueueMutex.runExclusive(() => {\n this.urlQueue.push(url);\n });\n }\n\n private handleHaltEvent(): void {\n this.logger.info(\"Halt event received\");\n this.isHalted = true;\n }\n\n async processInBatches(urls: BatchOperationUrl[]): Promise<T[]> {\n const results: T[] = [];\n this.runEmitter.on(\"halt\", this.handleHaltEvent.bind(this));\n\n await this.urlQueueMutex.runExclusive(() => {\n this.urlQueue = [...urls];\n });\n this.logger\n .withMetadata({\n urls,\n sessionId: this.sessionId,\n })\n .info(`Processing batch for session ${this.sessionId}`);\n\n while (this.urlQueue.length > 0) {\n // Wait for any window to complete before starting a new one\n let shouldContinue = false;\n await this.activePromisesMutex.runExclusive(async () => {\n if (this.activePromises.length >= this.maxWindowsPerSession) {\n await Promise.race(this.activePromises);\n shouldContinue = true;\n }\n });\n\n if (shouldContinue) continue;\n\n let urlData: BatchOperationUrl | undefined;\n await this.urlQueueMutex.runExclusive(() => {\n urlData = this.urlQueue.shift(); // Take the next url from the queue\n });\n if (!urlData) break; // No more urls to process\n\n // If we have less than the max concurrent operations, start a new one\n const promise = (async () => {\n // Do not process any more urls if the processing has been halted\n if (this.isHalted) {\n this.logger.info(`Processing halted, skipping window creation for ${urlData.url}`);\n return;\n }\n\n let windowId: string | undefined;\n let liveViewUrl: string | undefined;\n try {\n // Create a new window pointed to the url\n this.logger.info(`Creating window for ${urlData.url} in session ${this.sessionId}`);\n const { data, errors, warnings } = await this.client.withSessionId(this.sessionId).createWindow(urlData.url);\n\n windowId = data.windowId;\n\n this.handleErrorAndWarningResponses({\n warnings,\n errors,\n sessionId: this.sessionId,\n url: urlData,\n operation: \"window creation\",\n });\n\n if (!windowId) {\n throw new Error(`WindowId not found, errors: ${JSON.stringify(errors)}`);\n }\n\n const {\n data: windowInfo,\n warnings: windowWarnings,\n errors: windowErrors,\n } = await this.client.withSessionId(this.sessionId).withWindowId(windowId).getWindowInfo();\n liveViewUrl = windowInfo.liveViewUrl;\n\n this.handleErrorAndWarningResponses({\n warnings: windowWarnings,\n errors: windowErrors,\n sessionId: this.sessionId,\n url: urlData,\n operation: \"window info retrieval\",\n });\n\n // Run the operation on the window\n const result = await this.operation({\n windowId,\n sessionId: this.sessionId,\n liveViewUrl,\n operationUrl: urlData,\n });\n\n if (result) {\n const { shouldHaltBatch, additionalUrls, data } = result;\n\n if (data) {\n results.push(data);\n }\n\n if (shouldHaltBatch) {\n this.logger.info(\"Emitting halt event\");\n this.runEmitter.emit(\"halt\");\n }\n\n if (additionalUrls && additionalUrls.length > 0) {\n this.logger\n .withMetadata({\n additionalUrls,\n })\n .info(\"Emitting addUrls event\");\n this.runEmitter.emit(\"addUrls\", additionalUrls);\n }\n }\n } catch (error) {\n if (this.onError) {\n await this.handleErrorWithCallback({\n originalError: error,\n url: urlData,\n callback: this.onError,\n windowId,\n liveViewUrl,\n });\n } else {\n // By default, log the error and continue\n const message = `Error for URL ${urlData.url}: ${this.formatError(error)}`;\n this.logger.error(message);\n }\n } finally {\n if (windowId) {\n await this.safelyTerminateWindow(windowId);\n }\n }\n })();\n\n await this.activePromisesMutex.runExclusive(() => {\n this.activePromises.push(promise);\n });\n\n // Remove the promise from the active list when it resolves\n promise.finally(async () => {\n await this.activePromisesMutex.runExclusive(() => {\n const index = this.activePromises.indexOf(promise);\n if (index > -1) {\n this.activePromises.splice(index, 1);\n }\n });\n });\n }\n\n // Wait for all processes to complete\n await Promise.allSettled(this.activePromises);\n\n // Remove the halt listener\n this.runEmitter.removeListener(\"halt\", this.handleHaltEvent);\n\n return results;\n }\n\n private async handleErrorWithCallback({\n originalError,\n url,\n windowId,\n liveViewUrl,\n callback,\n }: {\n originalError: unknown;\n url: BatchOperationUrl;\n windowId?: string;\n liveViewUrl?: string;\n callback: (error: BatchOperationError) => Promise<void>;\n }): Promise<void> {\n // Catch any errors in the onError callback to avoid halting the entire process\n try {\n await callback({\n error: this.formatError(originalError),\n operationUrls: [url],\n sessionId: this.sessionId,\n windowId,\n liveViewUrl,\n });\n } catch (newError) {\n this.logger.error(\n `Error in onError callback: ${this.formatError(newError)}. Original error: ${this.formatError(originalError)}`,\n );\n }\n }\n\n private async safelyTerminateWindow(windowId: string): Promise<void> {\n try {\n await this.client.withSessionId(this.sessionId).withWindowId(windowId).close();\n } catch (error) {\n this.logger.withError(error).error(`Error closing window ${windowId}`);\n }\n }\n\n private formatError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n }\n\n private handleErrorAndWarningResponses({\n warnings,\n errors,\n sessionId,\n url,\n operation,\n }: { warnings?: Issue[]; errors?: Issue[]; sessionId: string; url: BatchOperationUrl; operation: string }): void {\n if (!warnings && !errors) return;\n\n const details: { sessionId: string; url: BatchOperationUrl; warnings?: Issue[]; errors?: Issue[] } = {\n sessionId,\n url,\n };\n\n if (warnings) {\n details.warnings = warnings;\n this.logger\n .withMetadata({\n details,\n })\n .warn(`Received warnings for ${operation}`);\n }\n\n if (errors) {\n details.errors = errors;\n this.logger\n .withMetadata({\n details,\n })\n .error(`Received errors for ${operation}`);\n }\n }\n}\n","import type { BatchOperationUrl } from \"./types.js\";\n\nexport const distributeUrlsToBatches = (\n urls: BatchOperationUrl[],\n maxConcurrentSessions: number,\n): BatchOperationUrl[][] => {\n if (urls.length === 0) return [];\n\n // Calculate optimal number of batches\n const batchCount = Math.min(maxConcurrentSessions, urls.length);\n const batches: BatchOperationUrl[][] = Array.from({ length: batchCount }, () => []);\n\n urls.forEach((url, index) => {\n const batchIndex = index % batchCount;\n if (!batches[batchIndex]) {\n batches[batchIndex] = [];\n }\n batches[batchIndex].push(url);\n });\n\n return batches;\n};\n"],"mappings":"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","names":["Mutex","data","Mutex"]}
1
+ {"version":3,"sources":["../src/batch-operate.plugin.ts","../src/lib/batch-util.ts","../src/lib/SessionQueue.ts","../src/lib/WindowQueue.ts","../src/lib/helpers.ts"],"sourcesContent":["import { AirtopPluginAugmentationType } from \"@airtop/sdk\";\nimport type { AirtopPluginRegistration } from \"@airtop/sdk\";\nimport type { AirtopClientPlugin } from \"@airtop/sdk\";\nimport type { AirtopClient } from \"@airtop/sdk\";\nimport { batchOperate } from \"./lib/batch-util.js\";\nimport type {\n BatchOperateConfig,\n BatchOperationInput,\n BatchOperationResponse,\n BatchOperationUrl,\n} from \"./lib/types.js\";\n\n// This is exported so the docs can expose it as we can't generate docs against the\n// module declaration\n/**\n * The methods provided by the Batch Operate plugin.\n */\nexport interface BatchOperateMethods {\n /**\n * Perform a batch operation on a list of URLs.\n * @param urls The list of URLs to perform the operation on.\n * @param operation The operation to perform on each URL.\n * @param config Configuration options for the batch operation.\n *\n * @template T The type of the response from the operation.\n *\n * @example\n * // AirtopClient instance\n * client.batchOperate(urls, operation, config);\n */\n batchOperate<T>(\n urls: BatchOperationUrl[],\n operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>,\n config?: BatchOperateConfig,\n ): Promise<T[]>;\n}\n\ndeclare module \"@airtop/sdk\" {\n interface AirtopClient {\n /**\n * Perform a batch operation on a list of URLs.\n * @param urls The list of URLs to perform the operation on.\n * @param operation The operation to perform on each URL.\n * @param config Configuration options for the batch operation.\n */\n batchOperate<T>(\n urls: BatchOperationUrl[],\n operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>,\n config?: BatchOperateConfig,\n ): Promise<T[]>;\n }\n}\n\nconst plugin: AirtopClientPlugin = {\n augmentationType: AirtopPluginAugmentationType.AirtopClient,\n augment: (prototype: typeof AirtopClient.prototype) => {\n prototype.batchOperate = async function <T>(\n urls: BatchOperationUrl[],\n operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>,\n config?: BatchOperateConfig,\n ): Promise<T[]> {\n return await batchOperate(urls, operation, this, config);\n };\n },\n};\n\n/**\n * Use with registerAirtopPlugin() from the Airtop SDK to add this plugin.\n */\nexport function batchOperatePlugin(): AirtopPluginRegistration {\n return {\n pluginsToAdd: [plugin],\n };\n}\n","import type { AirtopClient } from \"@airtop/sdk\";\nimport { EventEmitter } from \"eventemitter3\";\nimport { SessionQueue } from \"./SessionQueue.js\";\nimport { distributeUrlsToBatches } from \"./helpers.js\";\nimport type { BatchOperateConfig, BatchOperationInput, BatchOperationResponse, BatchOperationUrl } from \"./types.js\";\n\nconst DEFAULT_MAX_WINDOWS_PER_SESSION = 1;\nconst DEFAULT_MAX_CONCURRENT_SESSIONS = 30;\n\nexport const batchOperate = async <T>(\n urls: BatchOperationUrl[],\n operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>, // operation to invoke on each url\n client: AirtopClient,\n config?: BatchOperateConfig,\n): Promise<T[]> => {\n // Validate the urls before proceeding\n if (!Array.isArray(urls)) {\n throw new Error(\"Please provide a valid list of urls\");\n }\n\n for (const url of urls) {\n if (!url || typeof url !== \"object\" || !(\"url\" in url)) {\n throw new Error(\"Please provide a valid list of urls\");\n }\n }\n\n const runEmitter = new EventEmitter();\n\n const {\n maxConcurrentSessions = DEFAULT_MAX_CONCURRENT_SESSIONS,\n maxWindowsPerSession = DEFAULT_MAX_WINDOWS_PER_SESSION,\n sessionConfig,\n onError,\n } = config ?? {};\n\n // Split the urls into batches\n const initialBatches = distributeUrlsToBatches(urls, maxConcurrentSessions);\n const sessionQueue = new SessionQueue({\n maxConcurrentSessions,\n runEmitter,\n maxWindowsPerSession,\n initialBatches,\n operation,\n client,\n sessionConfig,\n onError,\n });\n\n runEmitter.on(\"addUrls\", (additionalUrls: BatchOperationUrl[]) => {\n sessionQueue.addUrlsToBatchQueue(additionalUrls);\n });\n\n await sessionQueue.processInitialBatches();\n\n return await sessionQueue.waitForProcessingToComplete();\n};\n","import type { Issue } from \"@airtop/sdk\";\nimport type { AirtopClient, ILogLayer } from \"@airtop/sdk\";\nimport type { CreateSessionConfig } from \"@airtop/sdk\";\nimport { Mutex } from \"async-mutex\";\nimport type { EventEmitter } from \"eventemitter3\";\nimport { WindowQueue } from \"./WindowQueue.js\";\nimport { distributeUrlsToBatches } from \"./helpers.js\";\nimport type { BatchOperationError, BatchOperationInput, BatchOperationResponse, BatchOperationUrl } from \"./types.js\";\n\nexport class SessionQueue<T> {\n private activePromises: Promise<void>[] = [];\n private activePromisesMutex = new Mutex();\n\n private maxConcurrentSessions: number;\n private runEmitter: EventEmitter;\n private maxWindowsPerSession: number;\n private sessionConfig?: CreateSessionConfig;\n private initialBatches: BatchOperationUrl[][] = [];\n private operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>;\n private onError?: (error: BatchOperationError) => Promise<void>;\n private isHalted: boolean;\n\n private batchQueue: BatchOperationUrl[][] = [];\n private batchQueueMutex = new Mutex();\n private latestProcessingPromise: Promise<void> | null = null;\n private processingPromisesCount = 0;\n\n private client: AirtopClient;\n private sessionPool: string[] = [];\n private sessionPoolMutex = new Mutex();\n\n private results: T[];\n private logger: ILogLayer;\n\n constructor({\n maxConcurrentSessions,\n runEmitter,\n maxWindowsPerSession,\n initialBatches,\n operation,\n client,\n sessionConfig,\n onError,\n }: {\n maxConcurrentSessions: number;\n runEmitter: EventEmitter;\n maxWindowsPerSession: number;\n initialBatches: BatchOperationUrl[][];\n operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>;\n client: AirtopClient;\n sessionConfig?: CreateSessionConfig;\n onError?: (error: BatchOperationError) => Promise<void>;\n }) {\n if (!Number.isInteger(maxConcurrentSessions) || maxConcurrentSessions <= 0) {\n throw new Error(\"maxConcurrentSessions must be a positive integer\");\n }\n\n this.maxConcurrentSessions = maxConcurrentSessions;\n this.runEmitter = runEmitter;\n this.maxWindowsPerSession = maxWindowsPerSession;\n this.sessionConfig = sessionConfig;\n this.initialBatches = initialBatches;\n this.operation = operation;\n this.onError = onError;\n this.latestProcessingPromise = null;\n this.results = [];\n this.client = client;\n this.isHalted = false;\n this.logger = client.getLogger();\n }\n\n public handleHaltEvent(): void {\n this.logger.info(\"Halt event received\");\n this.isHalted = true;\n }\n\n public async addUrlsToBatchQueue(newBatch: BatchOperationUrl[]): Promise<void> {\n // Distribute new URLs across batches\n const newBatches = distributeUrlsToBatches(newBatch, this.maxConcurrentSessions);\n\n this.logger\n .withMetadata({\n newBatches,\n })\n .info(\"Adding new batches to queue\");\n\n // Add new batches to the queue\n await this.batchQueueMutex.runExclusive(() => {\n this.batchQueue.push(...newBatches);\n });\n\n // Update existing processing promise\n this.processingPromisesCount++;\n this.latestProcessingPromise = this.processPendingBatches();\n }\n\n public async processInitialBatches(): Promise<void> {\n await this.batchQueueMutex.runExclusive(() => {\n this.batchQueue = [...this.initialBatches];\n });\n this.processingPromisesCount++;\n this.runEmitter.on(\"halt\", this.handleHaltEvent.bind(this));\n this.latestProcessingPromise = this.processPendingBatches();\n await this.latestProcessingPromise;\n }\n\n public async waitForProcessingToComplete(): Promise<T[]> {\n while (this.processingPromisesCount > 0) {\n await this.latestProcessingPromise;\n }\n\n await this.terminateAllSessions();\n\n this.runEmitter.removeListener(\"halt\", this.handleHaltEvent);\n\n return this.results;\n }\n\n private async terminateAllSessions(): Promise<void> {\n for (const sessionId of this.sessionPool) {\n this.safelyTerminateSession(sessionId);\n }\n }\n\n private async processPendingBatches(): Promise<void> {\n try {\n while (this.batchQueue.length > 0) {\n // Wait for any session to complete before starting a new one\n let shouldContinue = false;\n await this.activePromisesMutex.runExclusive(async () => {\n if (this.activePromises.length >= this.maxConcurrentSessions) {\n await Promise.race(this.activePromises);\n shouldContinue = true;\n }\n });\n\n if (shouldContinue) continue;\n\n let batch: BatchOperationUrl[] | undefined;\n await this.batchQueueMutex.runExclusive(() => {\n batch = this.batchQueue.shift();\n });\n\n if (!batch || batch.length === 0) break;\n\n const promise = (async () => {\n if (this.isHalted) {\n this.logger.info(\"Halt event received, skipping batch\");\n return;\n }\n\n let sessionId: string | undefined;\n try {\n // Check if there's an available session in the pool\n await this.sessionPoolMutex.runExclusive(() => {\n if (this.sessionPool.length > 0) {\n sessionId = this.sessionPool.pop();\n }\n });\n\n // Otherwise, create a new session\n if (!sessionId) {\n const { data: session, warnings, errors } = await this.client.createSession(this.sessionConfig);\n sessionId = session.id;\n\n this.handleErrorAndWarningResponses({ warnings, errors, sessionId, batch });\n }\n\n const queue = new WindowQueue(\n this.maxWindowsPerSession,\n this.runEmitter,\n sessionId,\n this.client,\n this.operation,\n this.onError,\n this.isHalted,\n );\n const windowResults = await queue.processInBatches(batch);\n this.results.push(...windowResults);\n\n // Return the session to the pool\n await this.sessionPoolMutex.runExclusive(() => {\n if (!sessionId) {\n throw new Error(\"Missing sessionId, cannot return to pool\");\n }\n\n this.sessionPool.push(sessionId);\n });\n } catch (error) {\n if (this.onError) {\n await this.handleErrorWithCallback({ originalError: error, batch, sessionId, callback: this.onError });\n } else {\n // By default, log the error and continue\n const urls = batch.map((url) => url.url);\n this.logErrorForUrls(urls, error);\n }\n\n // Clean up the session in case of error\n if (sessionId) {\n this.safelyTerminateSession(sessionId);\n }\n }\n })();\n\n await this.activePromisesMutex.runExclusive(() => {\n this.activePromises.push(promise);\n });\n\n // Remove the promise when it completes\n promise.finally(async () => {\n await this.activePromisesMutex.runExclusive(() => {\n const index = this.activePromises.indexOf(promise);\n if (index > -1) {\n this.activePromises.splice(index, 1);\n }\n });\n });\n }\n\n // Wait for all remaining sessions to complete\n await Promise.allSettled(this.activePromises);\n } finally {\n this.processingPromisesCount--;\n }\n }\n\n private async handleErrorWithCallback({\n originalError,\n batch,\n sessionId,\n callback,\n }: {\n originalError: unknown;\n batch: BatchOperationUrl[];\n sessionId?: string;\n callback: (error: BatchOperationError) => Promise<void>;\n }): Promise<void> {\n // Catch any errors in the onError callback to avoid halting the entire process\n try {\n await callback({\n error: this.formatError(originalError),\n operationUrls: batch,\n sessionId,\n });\n } catch (newError) {\n this.logger.error(\n `Error in onError callback: ${this.formatError(newError)}. Original error: ${this.formatError(originalError)}`,\n );\n }\n }\n\n private logErrorForUrls(urls: string[], error: unknown): void {\n this.logger\n .withError(error)\n .withMetadata({\n urls,\n })\n .error(\"Error for URLs\");\n }\n\n private safelyTerminateSession(sessionId: string): void {\n // Do not await since we don't want to block the main thread\n this.client\n .withSessionId(sessionId)\n .terminateSession()\n .catch((error) => {\n this.logger\n .withMetadata({\n sessionId,\n })\n .withError(error)\n .error(`Error terminating session ${sessionId}`);\n });\n }\n\n private formatError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n }\n\n private handleErrorAndWarningResponses({\n warnings,\n errors,\n sessionId,\n batch,\n }: { warnings?: Issue[]; errors?: Issue[]; sessionId: string; batch: BatchOperationUrl[] }): void {\n if (!warnings && !errors) return;\n\n const details: { sessionId: string; urls: BatchOperationUrl[]; warnings?: Issue[]; errors?: Issue[] } = {\n sessionId,\n urls: batch,\n };\n\n if (warnings) {\n details.warnings = warnings;\n this.logger\n .withMetadata({\n details,\n })\n .warn(\"Received warnings creating session\");\n }\n\n // Log an object with the errors and the URL\n if (errors) {\n details.errors = errors;\n this.logger.withMetadata({ details }).error(\"Received errors creating session\");\n }\n }\n}\n","import type { Issue } from \"@airtop/sdk\";\nimport type { AirtopClient } from \"@airtop/sdk\";\nimport type { ILogLayer } from \"@airtop/sdk\";\nimport { Mutex } from \"async-mutex\";\nimport type { EventEmitter } from \"eventemitter3\";\nimport type { BatchOperationError, BatchOperationInput, BatchOperationResponse, BatchOperationUrl } from \"./types.js\";\n\nexport class WindowQueue<T> {\n private activePromises: Promise<void>[] = [];\n private urlQueue: BatchOperationUrl[] = [];\n private activePromisesMutex = new Mutex();\n private urlQueueMutex = new Mutex();\n\n private maxWindowsPerSession: number;\n private runEmitter: EventEmitter;\n private sessionId: string;\n private client: AirtopClient;\n private operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>;\n private onError?: (error: BatchOperationError) => Promise<void>;\n private isHalted;\n private logger: ILogLayer;\n\n constructor(\n maxWindowsPerSession: number,\n runEmitter: EventEmitter,\n sessionId: string,\n client: AirtopClient,\n operation: (input: BatchOperationInput) => Promise<BatchOperationResponse<T>>,\n onError?: (error: BatchOperationError) => Promise<void>,\n isHalted = false,\n ) {\n if (!Number.isInteger(maxWindowsPerSession) || maxWindowsPerSession <= 0) {\n throw new Error(\"maxWindowsPerSession must be a positive integer\");\n }\n\n this.maxWindowsPerSession = maxWindowsPerSession;\n this.runEmitter = runEmitter;\n this.sessionId = sessionId;\n this.client = client;\n this.operation = operation;\n this.onError = onError;\n this.isHalted = isHalted;\n this.logger = this.client.getLogger();\n }\n\n public async addUrlToQueue(url: BatchOperationUrl): Promise<void> {\n await this.urlQueueMutex.runExclusive(() => {\n this.urlQueue.push(url);\n });\n }\n\n private handleHaltEvent(): void {\n this.logger.info(\"Halt event received\");\n this.isHalted = true;\n }\n\n async processInBatches(urls: BatchOperationUrl[]): Promise<T[]> {\n const results: T[] = [];\n this.runEmitter.on(\"halt\", this.handleHaltEvent.bind(this));\n\n await this.urlQueueMutex.runExclusive(() => {\n this.urlQueue = [...urls];\n });\n this.logger\n .withMetadata({\n urls,\n sessionId: this.sessionId,\n })\n .info(`Processing batch for session ${this.sessionId}`);\n\n while (this.urlQueue.length > 0) {\n // Wait for any window to complete before starting a new one\n let shouldContinue = false;\n await this.activePromisesMutex.runExclusive(async () => {\n if (this.activePromises.length >= this.maxWindowsPerSession) {\n await Promise.race(this.activePromises);\n shouldContinue = true;\n }\n });\n\n if (shouldContinue) continue;\n\n let urlData: BatchOperationUrl | undefined;\n await this.urlQueueMutex.runExclusive(() => {\n urlData = this.urlQueue.shift(); // Take the next url from the queue\n });\n if (!urlData) break; // No more urls to process\n\n // If we have less than the max concurrent operations, start a new one\n const promise = (async () => {\n // Do not process any more urls if the processing has been halted\n if (this.isHalted) {\n this.logger.info(`Processing halted, skipping window creation for ${urlData.url}`);\n return;\n }\n\n let windowId: string | undefined;\n let liveViewUrl: string | undefined;\n try {\n // Create a new window pointed to the url\n this.logger.info(`Creating window for ${urlData.url} in session ${this.sessionId}`);\n const { data, errors, warnings } = await this.client.withSessionId(this.sessionId).createWindow(urlData.url);\n\n windowId = data.windowId;\n\n this.handleErrorAndWarningResponses({\n warnings,\n errors,\n sessionId: this.sessionId,\n url: urlData,\n operation: \"window creation\",\n });\n\n if (!windowId) {\n throw new Error(`WindowId not found, errors: ${JSON.stringify(errors)}`);\n }\n\n const {\n data: windowInfo,\n warnings: windowWarnings,\n errors: windowErrors,\n } = await this.client.withSessionId(this.sessionId).withWindowId(windowId).getWindowInfo();\n liveViewUrl = windowInfo.liveViewUrl;\n\n this.handleErrorAndWarningResponses({\n warnings: windowWarnings,\n errors: windowErrors,\n sessionId: this.sessionId,\n url: urlData,\n operation: \"window info retrieval\",\n });\n\n // Run the operation on the window\n const result = await this.operation({\n windowId,\n sessionId: this.sessionId,\n liveViewUrl,\n operationUrl: urlData,\n });\n\n if (result) {\n const { shouldHaltBatch, additionalUrls, data } = result;\n\n if (data) {\n results.push(data);\n }\n\n if (shouldHaltBatch) {\n this.logger.info(\"Emitting halt event\");\n this.runEmitter.emit(\"halt\");\n }\n\n if (additionalUrls && additionalUrls.length > 0) {\n this.logger\n .withMetadata({\n additionalUrls,\n })\n .info(\"Emitting addUrls event\");\n this.runEmitter.emit(\"addUrls\", additionalUrls);\n }\n }\n } catch (error) {\n if (this.onError) {\n await this.handleErrorWithCallback({\n originalError: error,\n url: urlData,\n callback: this.onError,\n windowId,\n liveViewUrl,\n });\n } else {\n // By default, log the error and continue\n const message = `Error for URL ${urlData.url}: ${this.formatError(error)}`;\n this.logger.error(message);\n }\n } finally {\n if (windowId) {\n await this.safelyTerminateWindow(windowId);\n }\n }\n })();\n\n await this.activePromisesMutex.runExclusive(() => {\n this.activePromises.push(promise);\n });\n\n // Remove the promise from the active list when it resolves\n promise.finally(async () => {\n await this.activePromisesMutex.runExclusive(() => {\n const index = this.activePromises.indexOf(promise);\n if (index > -1) {\n this.activePromises.splice(index, 1);\n }\n });\n });\n }\n\n // Wait for all processes to complete\n await Promise.allSettled(this.activePromises);\n\n // Remove the halt listener\n this.runEmitter.removeListener(\"halt\", this.handleHaltEvent);\n\n return results;\n }\n\n private async handleErrorWithCallback({\n originalError,\n url,\n windowId,\n liveViewUrl,\n callback,\n }: {\n originalError: unknown;\n url: BatchOperationUrl;\n windowId?: string;\n liveViewUrl?: string;\n callback: (error: BatchOperationError) => Promise<void>;\n }): Promise<void> {\n // Catch any errors in the onError callback to avoid halting the entire process\n try {\n await callback({\n error: this.formatError(originalError),\n operationUrls: [url],\n sessionId: this.sessionId,\n windowId,\n liveViewUrl,\n });\n } catch (newError) {\n this.logger.error(\n `Error in onError callback: ${this.formatError(newError)}. Original error: ${this.formatError(originalError)}`,\n );\n }\n }\n\n private async safelyTerminateWindow(windowId: string): Promise<void> {\n try {\n await this.client.withSessionId(this.sessionId).withWindowId(windowId).close();\n } catch (error) {\n this.logger.withError(error).error(`Error closing window ${windowId}`);\n }\n }\n\n private formatError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n }\n\n private handleErrorAndWarningResponses({\n warnings,\n errors,\n sessionId,\n url,\n operation,\n }: { warnings?: Issue[]; errors?: Issue[]; sessionId: string; url: BatchOperationUrl; operation: string }): void {\n if (!warnings && !errors) return;\n\n const details: { sessionId: string; url: BatchOperationUrl; warnings?: Issue[]; errors?: Issue[] } = {\n sessionId,\n url,\n };\n\n if (warnings) {\n details.warnings = warnings;\n this.logger\n .withMetadata({\n details,\n })\n .warn(`Received warnings for ${operation}`);\n }\n\n if (errors) {\n details.errors = errors;\n this.logger\n .withMetadata({\n details,\n })\n .error(`Received errors for ${operation}`);\n }\n }\n}\n","import type { BatchOperationUrl } from \"./types.js\";\n\nexport const distributeUrlsToBatches = (\n urls: BatchOperationUrl[],\n maxConcurrentSessions: number,\n): BatchOperationUrl[][] => {\n if (urls.length === 0) return [];\n\n // Calculate optimal number of batches\n const batchCount = Math.min(maxConcurrentSessions, urls.length);\n const batches: BatchOperationUrl[][] = Array.from({ length: batchCount }, () => []);\n\n urls.forEach((url, index) => {\n const batchIndex = index % batchCount;\n if (!batches[batchIndex]) {\n batches[batchIndex] = [];\n }\n batches[batchIndex].push(url);\n });\n\n return batches;\n};\n"],"mappings":";AAAA,SAAS,oCAAoC;;;ACC7C,SAAS,oBAAoB;;;ACE7B,SAAS,SAAAA,cAAa;;;ACAtB,SAAS,aAAa;AAIf,IAAM,cAAN,MAAqB;AAAA,EAClB,iBAAkC,CAAC;AAAA,EACnC,WAAgC,CAAC;AAAA,EACjC,sBAAsB,IAAI,MAAM;AAAA,EAChC,gBAAgB,IAAI,MAAM;AAAA,EAE1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAER,YACE,sBACA,YACA,WACA,QACA,WACA,SACA,WAAW,OACX;AACA,QAAI,CAAC,OAAO,UAAU,oBAAoB,KAAK,wBAAwB,GAAG;AACxE,YAAM,IAAI,MAAM,iDAAiD;AAAA,IACnE;AAEA,SAAK,uBAAuB;AAC5B,SAAK,aAAa;AAClB,SAAK,YAAY;AACjB,SAAK,SAAS;AACd,SAAK,YAAY;AACjB,SAAK,UAAU;AACf,SAAK,WAAW;AAChB,SAAK,SAAS,KAAK,OAAO,UAAU;AAAA,EACtC;AAAA,EAEA,MAAa,cAAc,KAAuC;AAChE,UAAM,KAAK,cAAc,aAAa,MAAM;AAC1C,WAAK,SAAS,KAAK,GAAG;AAAA,IACxB,CAAC;AAAA,EACH;AAAA,EAEQ,kBAAwB;AAC9B,SAAK,OAAO,KAAK,qBAAqB;AACtC,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,MAAM,iBAAiB,MAAyC;AAC9D,UAAM,UAAe,CAAC;AACtB,SAAK,WAAW,GAAG,QAAQ,KAAK,gBAAgB,KAAK,IAAI,CAAC;AAE1D,UAAM,KAAK,cAAc,aAAa,MAAM;AAC1C,WAAK,WAAW,CAAC,GAAG,IAAI;AAAA,IAC1B,CAAC;AACD,SAAK,OACF,aAAa;AAAA,MACZ;AAAA,MACA,WAAW,KAAK;AAAA,IAClB,CAAC,EACA,KAAK,gCAAgC,KAAK,SAAS,EAAE;AAExD,WAAO,KAAK,SAAS,SAAS,GAAG;AAE/B,UAAI,iBAAiB;AACrB,YAAM,KAAK,oBAAoB,aAAa,YAAY;AACtD,YAAI,KAAK,eAAe,UAAU,KAAK,sBAAsB;AAC3D,gBAAM,QAAQ,KAAK,KAAK,cAAc;AACtC,2BAAiB;AAAA,QACnB;AAAA,MACF,CAAC;AAED,UAAI,eAAgB;AAEpB,UAAI;AACJ,YAAM,KAAK,cAAc,aAAa,MAAM;AAC1C,kBAAU,KAAK,SAAS,MAAM;AAAA,MAChC,CAAC;AACD,UAAI,CAAC,QAAS;AAGd,YAAM,WAAW,YAAY;AAE3B,YAAI,KAAK,UAAU;AACjB,eAAK,OAAO,KAAK,mDAAmD,QAAQ,GAAG,EAAE;AACjF;AAAA,QACF;AAEA,YAAI;AACJ,YAAI;AACJ,YAAI;AAEF,eAAK,OAAO,KAAK,uBAAuB,QAAQ,GAAG,eAAe,KAAK,SAAS,EAAE;AAClF,gBAAM,EAAE,MAAM,QAAQ,SAAS,IAAI,MAAM,KAAK,OAAO,cAAc,KAAK,SAAS,EAAE,aAAa,QAAQ,GAAG;AAE3G,qBAAW,KAAK;AAEhB,eAAK,+BAA+B;AAAA,YAClC;AAAA,YACA;AAAA,YACA,WAAW,KAAK;AAAA,YAChB,KAAK;AAAA,YACL,WAAW;AAAA,UACb,CAAC;AAED,cAAI,CAAC,UAAU;AACb,kBAAM,IAAI,MAAM,+BAA+B,KAAK,UAAU,MAAM,CAAC,EAAE;AAAA,UACzE;AAEA,gBAAM;AAAA,YACJ,MAAM;AAAA,YACN,UAAU;AAAA,YACV,QAAQ;AAAA,UACV,IAAI,MAAM,KAAK,OAAO,cAAc,KAAK,SAAS,EAAE,aAAa,QAAQ,EAAE,cAAc;AACzF,wBAAc,WAAW;AAEzB,eAAK,+BAA+B;AAAA,YAClC,UAAU;AAAA,YACV,QAAQ;AAAA,YACR,WAAW,KAAK;AAAA,YAChB,KAAK;AAAA,YACL,WAAW;AAAA,UACb,CAAC;AAGD,gBAAM,SAAS,MAAM,KAAK,UAAU;AAAA,YAClC;AAAA,YACA,WAAW,KAAK;AAAA,YAChB;AAAA,YACA,cAAc;AAAA,UAChB,CAAC;AAED,cAAI,QAAQ;AACV,kBAAM,EAAE,iBAAiB,gBAAgB,MAAAC,MAAK,IAAI;AAElD,gBAAIA,OAAM;AACR,sBAAQ,KAAKA,KAAI;AAAA,YACnB;AAEA,gBAAI,iBAAiB;AACnB,mBAAK,OAAO,KAAK,qBAAqB;AACtC,mBAAK,WAAW,KAAK,MAAM;AAAA,YAC7B;AAEA,gBAAI,kBAAkB,eAAe,SAAS,GAAG;AAC/C,mBAAK,OACF,aAAa;AAAA,gBACZ;AAAA,cACF,CAAC,EACA,KAAK,wBAAwB;AAChC,mBAAK,WAAW,KAAK,WAAW,cAAc;AAAA,YAChD;AAAA,UACF;AAAA,QACF,SAAS,OAAO;AACd,cAAI,KAAK,SAAS;AAChB,kBAAM,KAAK,wBAAwB;AAAA,cACjC,eAAe;AAAA,cACf,KAAK;AAAA,cACL,UAAU,KAAK;AAAA,cACf;AAAA,cACA;AAAA,YACF,CAAC;AAAA,UACH,OAAO;AAEL,kBAAM,UAAU,iBAAiB,QAAQ,GAAG,KAAK,KAAK,YAAY,KAAK,CAAC;AACxE,iBAAK,OAAO,MAAM,OAAO;AAAA,UAC3B;AAAA,QACF,UAAE;AACA,cAAI,UAAU;AACZ,kBAAM,KAAK,sBAAsB,QAAQ;AAAA,UAC3C;AAAA,QACF;AAAA,MACF,GAAG;AAEH,YAAM,KAAK,oBAAoB,aAAa,MAAM;AAChD,aAAK,eAAe,KAAK,OAAO;AAAA,MAClC,CAAC;AAGD,cAAQ,QAAQ,YAAY;AAC1B,cAAM,KAAK,oBAAoB,aAAa,MAAM;AAChD,gBAAM,QAAQ,KAAK,eAAe,QAAQ,OAAO;AACjD,cAAI,QAAQ,IAAI;AACd,iBAAK,eAAe,OAAO,OAAO,CAAC;AAAA,UACrC;AAAA,QACF,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAGA,UAAM,QAAQ,WAAW,KAAK,cAAc;AAG5C,SAAK,WAAW,eAAe,QAAQ,KAAK,eAAe;AAE3D,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,wBAAwB;AAAA,IACpC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAMkB;AAEhB,QAAI;AACF,YAAM,SAAS;AAAA,QACb,OAAO,KAAK,YAAY,aAAa;AAAA,QACrC,eAAe,CAAC,GAAG;AAAA,QACnB,WAAW,KAAK;AAAA,QAChB;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH,SAAS,UAAU;AACjB,WAAK,OAAO;AAAA,QACV,8BAA8B,KAAK,YAAY,QAAQ,CAAC,qBAAqB,KAAK,YAAY,aAAa,CAAC;AAAA,MAC9G;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,sBAAsB,UAAiC;AACnE,QAAI;AACF,YAAM,KAAK,OAAO,cAAc,KAAK,SAAS,EAAE,aAAa,QAAQ,EAAE,MAAM;AAAA,IAC/E,SAAS,OAAO;AACd,WAAK,OAAO,UAAU,KAAK,EAAE,MAAM,wBAAwB,QAAQ,EAAE;AAAA,IACvE;AAAA,EACF;AAAA,EAEQ,YAAY,OAAwB;AAC1C,WAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,EAC9D;AAAA,EAEQ,+BAA+B;AAAA,IACrC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAAiH;AAC/G,QAAI,CAAC,YAAY,CAAC,OAAQ;AAE1B,UAAM,UAA+F;AAAA,MACnG;AAAA,MACA;AAAA,IACF;AAEA,QAAI,UAAU;AACZ,cAAQ,WAAW;AACnB,WAAK,OACF,aAAa;AAAA,QACZ;AAAA,MACF,CAAC,EACA,KAAK,yBAAyB,SAAS,EAAE;AAAA,IAC9C;AAEA,QAAI,QAAQ;AACV,cAAQ,SAAS;AACjB,WAAK,OACF,aAAa;AAAA,QACZ;AAAA,MACF,CAAC,EACA,MAAM,uBAAuB,SAAS,EAAE;AAAA,IAC7C;AAAA,EACF;AACF;;;ACrRO,IAAM,0BAA0B,CACrC,MACA,0BAC0B;AAC1B,MAAI,KAAK,WAAW,EAAG,QAAO,CAAC;AAG/B,QAAM,aAAa,KAAK,IAAI,uBAAuB,KAAK,MAAM;AAC9D,QAAM,UAAiC,MAAM,KAAK,EAAE,QAAQ,WAAW,GAAG,MAAM,CAAC,CAAC;AAElF,OAAK,QAAQ,CAAC,KAAK,UAAU;AAC3B,UAAM,aAAa,QAAQ;AAC3B,QAAI,CAAC,QAAQ,UAAU,GAAG;AACxB,cAAQ,UAAU,IAAI,CAAC;AAAA,IACzB;AACA,YAAQ,UAAU,EAAE,KAAK,GAAG;AAAA,EAC9B,CAAC;AAED,SAAO;AACT;;;AFZO,IAAM,eAAN,MAAsB;AAAA,EACnB,iBAAkC,CAAC;AAAA,EACnC,sBAAsB,IAAIC,OAAM;AAAA,EAEhC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAwC,CAAC;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EAEA,aAAoC,CAAC;AAAA,EACrC,kBAAkB,IAAIA,OAAM;AAAA,EAC5B,0BAAgD;AAAA,EAChD,0BAA0B;AAAA,EAE1B;AAAA,EACA,cAAwB,CAAC;AAAA,EACzB,mBAAmB,IAAIA,OAAM;AAAA,EAE7B;AAAA,EACA;AAAA,EAER,YAAY;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GASG;AACD,QAAI,CAAC,OAAO,UAAU,qBAAqB,KAAK,yBAAyB,GAAG;AAC1E,YAAM,IAAI,MAAM,kDAAkD;AAAA,IACpE;AAEA,SAAK,wBAAwB;AAC7B,SAAK,aAAa;AAClB,SAAK,uBAAuB;AAC5B,SAAK,gBAAgB;AACrB,SAAK,iBAAiB;AACtB,SAAK,YAAY;AACjB,SAAK,UAAU;AACf,SAAK,0BAA0B;AAC/B,SAAK,UAAU,CAAC;AAChB,SAAK,SAAS;AACd,SAAK,WAAW;AAChB,SAAK,SAAS,OAAO,UAAU;AAAA,EACjC;AAAA,EAEO,kBAAwB;AAC7B,SAAK,OAAO,KAAK,qBAAqB;AACtC,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,MAAa,oBAAoB,UAA8C;AAE7E,UAAM,aAAa,wBAAwB,UAAU,KAAK,qBAAqB;AAE/E,SAAK,OACF,aAAa;AAAA,MACZ;AAAA,IACF,CAAC,EACA,KAAK,6BAA6B;AAGrC,UAAM,KAAK,gBAAgB,aAAa,MAAM;AAC5C,WAAK,WAAW,KAAK,GAAG,UAAU;AAAA,IACpC,CAAC;AAGD,SAAK;AACL,SAAK,0BAA0B,KAAK,sBAAsB;AAAA,EAC5D;AAAA,EAEA,MAAa,wBAAuC;AAClD,UAAM,KAAK,gBAAgB,aAAa,MAAM;AAC5C,WAAK,aAAa,CAAC,GAAG,KAAK,cAAc;AAAA,IAC3C,CAAC;AACD,SAAK;AACL,SAAK,WAAW,GAAG,QAAQ,KAAK,gBAAgB,KAAK,IAAI,CAAC;AAC1D,SAAK,0BAA0B,KAAK,sBAAsB;AAC1D,UAAM,KAAK;AAAA,EACb;AAAA,EAEA,MAAa,8BAA4C;AACvD,WAAO,KAAK,0BAA0B,GAAG;AACvC,YAAM,KAAK;AAAA,IACb;AAEA,UAAM,KAAK,qBAAqB;AAEhC,SAAK,WAAW,eAAe,QAAQ,KAAK,eAAe;AAE3D,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAc,uBAAsC;AAClD,eAAW,aAAa,KAAK,aAAa;AACxC,WAAK,uBAAuB,SAAS;AAAA,IACvC;AAAA,EACF;AAAA,EAEA,MAAc,wBAAuC;AACnD,QAAI;AACF,aAAO,KAAK,WAAW,SAAS,GAAG;AAEjC,YAAI,iBAAiB;AACrB,cAAM,KAAK,oBAAoB,aAAa,YAAY;AACtD,cAAI,KAAK,eAAe,UAAU,KAAK,uBAAuB;AAC5D,kBAAM,QAAQ,KAAK,KAAK,cAAc;AACtC,6BAAiB;AAAA,UACnB;AAAA,QACF,CAAC;AAED,YAAI,eAAgB;AAEpB,YAAI;AACJ,cAAM,KAAK,gBAAgB,aAAa,MAAM;AAC5C,kBAAQ,KAAK,WAAW,MAAM;AAAA,QAChC,CAAC;AAED,YAAI,CAAC,SAAS,MAAM,WAAW,EAAG;AAElC,cAAM,WAAW,YAAY;AAC3B,cAAI,KAAK,UAAU;AACjB,iBAAK,OAAO,KAAK,qCAAqC;AACtD;AAAA,UACF;AAEA,cAAI;AACJ,cAAI;AAEF,kBAAM,KAAK,iBAAiB,aAAa,MAAM;AAC7C,kBAAI,KAAK,YAAY,SAAS,GAAG;AAC/B,4BAAY,KAAK,YAAY,IAAI;AAAA,cACnC;AAAA,YACF,CAAC;AAGD,gBAAI,CAAC,WAAW;AACd,oBAAM,EAAE,MAAM,SAAS,UAAU,OAAO,IAAI,MAAM,KAAK,OAAO,cAAc,KAAK,aAAa;AAC9F,0BAAY,QAAQ;AAEpB,mBAAK,+BAA+B,EAAE,UAAU,QAAQ,WAAW,MAAM,CAAC;AAAA,YAC5E;AAEA,kBAAM,QAAQ,IAAI;AAAA,cAChB,KAAK;AAAA,cACL,KAAK;AAAA,cACL;AAAA,cACA,KAAK;AAAA,cACL,KAAK;AAAA,cACL,KAAK;AAAA,cACL,KAAK;AAAA,YACP;AACA,kBAAM,gBAAgB,MAAM,MAAM,iBAAiB,KAAK;AACxD,iBAAK,QAAQ,KAAK,GAAG,aAAa;AAGlC,kBAAM,KAAK,iBAAiB,aAAa,MAAM;AAC7C,kBAAI,CAAC,WAAW;AACd,sBAAM,IAAI,MAAM,0CAA0C;AAAA,cAC5D;AAEA,mBAAK,YAAY,KAAK,SAAS;AAAA,YACjC,CAAC;AAAA,UACH,SAAS,OAAO;AACd,gBAAI,KAAK,SAAS;AAChB,oBAAM,KAAK,wBAAwB,EAAE,eAAe,OAAO,OAAO,WAAW,UAAU,KAAK,QAAQ,CAAC;AAAA,YACvG,OAAO;AAEL,oBAAM,OAAO,MAAM,IAAI,CAAC,QAAQ,IAAI,GAAG;AACvC,mBAAK,gBAAgB,MAAM,KAAK;AAAA,YAClC;AAGA,gBAAI,WAAW;AACb,mBAAK,uBAAuB,SAAS;AAAA,YACvC;AAAA,UACF;AAAA,QACF,GAAG;AAEH,cAAM,KAAK,oBAAoB,aAAa,MAAM;AAChD,eAAK,eAAe,KAAK,OAAO;AAAA,QAClC,CAAC;AAGD,gBAAQ,QAAQ,YAAY;AAC1B,gBAAM,KAAK,oBAAoB,aAAa,MAAM;AAChD,kBAAM,QAAQ,KAAK,eAAe,QAAQ,OAAO;AACjD,gBAAI,QAAQ,IAAI;AACd,mBAAK,eAAe,OAAO,OAAO,CAAC;AAAA,YACrC;AAAA,UACF,CAAC;AAAA,QACH,CAAC;AAAA,MACH;AAGA,YAAM,QAAQ,WAAW,KAAK,cAAc;AAAA,IAC9C,UAAE;AACA,WAAK;AAAA,IACP;AAAA,EACF;AAAA,EAEA,MAAc,wBAAwB;AAAA,IACpC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAKkB;AAEhB,QAAI;AACF,YAAM,SAAS;AAAA,QACb,OAAO,KAAK,YAAY,aAAa;AAAA,QACrC,eAAe;AAAA,QACf;AAAA,MACF,CAAC;AAAA,IACH,SAAS,UAAU;AACjB,WAAK,OAAO;AAAA,QACV,8BAA8B,KAAK,YAAY,QAAQ,CAAC,qBAAqB,KAAK,YAAY,aAAa,CAAC;AAAA,MAC9G;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,gBAAgB,MAAgB,OAAsB;AAC5D,SAAK,OACF,UAAU,KAAK,EACf,aAAa;AAAA,MACZ;AAAA,IACF,CAAC,EACA,MAAM,gBAAgB;AAAA,EAC3B;AAAA,EAEQ,uBAAuB,WAAyB;AAEtD,SAAK,OACF,cAAc,SAAS,EACvB,iBAAiB,EACjB,MAAM,CAAC,UAAU;AAChB,WAAK,OACF,aAAa;AAAA,QACZ;AAAA,MACF,CAAC,EACA,UAAU,KAAK,EACf,MAAM,6BAA6B,SAAS,EAAE;AAAA,IACnD,CAAC;AAAA,EACL;AAAA,EAEQ,YAAY,OAAwB;AAC1C,WAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,EAC9D;AAAA,EAEQ,+BAA+B;AAAA,IACrC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAAkG;AAChG,QAAI,CAAC,YAAY,CAAC,OAAQ;AAE1B,UAAM,UAAkG;AAAA,MACtG;AAAA,MACA,MAAM;AAAA,IACR;AAEA,QAAI,UAAU;AACZ,cAAQ,WAAW;AACnB,WAAK,OACF,aAAa;AAAA,QACZ;AAAA,MACF,CAAC,EACA,KAAK,oCAAoC;AAAA,IAC9C;AAGA,QAAI,QAAQ;AACV,cAAQ,SAAS;AACjB,WAAK,OAAO,aAAa,EAAE,QAAQ,CAAC,EAAE,MAAM,kCAAkC;AAAA,IAChF;AAAA,EACF;AACF;;;AD7SA,IAAM,kCAAkC;AACxC,IAAM,kCAAkC;AAEjC,IAAM,eAAe,OAC1B,MACA,WACA,QACA,WACiB;AAEjB,MAAI,CAAC,MAAM,QAAQ,IAAI,GAAG;AACxB,UAAM,IAAI,MAAM,qCAAqC;AAAA,EACvD;AAEA,aAAW,OAAO,MAAM;AACtB,QAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,EAAE,SAAS,MAAM;AACtD,YAAM,IAAI,MAAM,qCAAqC;AAAA,IACvD;AAAA,EACF;AAEA,QAAM,aAAa,IAAI,aAAa;AAEpC,QAAM;AAAA,IACJ,wBAAwB;AAAA,IACxB,uBAAuB;AAAA,IACvB;AAAA,IACA;AAAA,EACF,IAAI,UAAU,CAAC;AAGf,QAAM,iBAAiB,wBAAwB,MAAM,qBAAqB;AAC1E,QAAM,eAAe,IAAI,aAAa;AAAA,IACpC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,aAAW,GAAG,WAAW,CAAC,mBAAwC;AAChE,iBAAa,oBAAoB,cAAc;AAAA,EACjD,CAAC;AAED,QAAM,aAAa,sBAAsB;AAEzC,SAAO,MAAM,aAAa,4BAA4B;AACxD;;;ADFA,IAAM,SAA6B;AAAA,EACjC,kBAAkB,6BAA6B;AAAA,EAC/C,SAAS,CAAC,cAA6C;AACrD,cAAU,eAAe,eACvB,MACA,WACA,QACc;AACd,aAAO,MAAM,aAAa,MAAM,WAAW,MAAM,MAAM;AAAA,IACzD;AAAA,EACF;AACF;AAKO,SAAS,qBAA+C;AAC7D,SAAO;AAAA,IACL,cAAc,CAAC,MAAM;AAAA,EACvB;AACF;","names":["Mutex","data","Mutex"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@airtop/plugin-batch-operate",
3
3
  "description": "Adds batch operation support to the Airtop SDK",
4
- "version": "1.0.0-alpha2.0",
4
+ "version": "1.0.0-alpha2.2",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
7
7
  "module": "./dist/index.js",
@@ -24,27 +24,20 @@
24
24
  "batch",
25
25
  "plugin"
26
26
  ],
27
- "scripts": {
28
- "build": "tsup src/index.ts",
29
- "build:dev": "node_modules/.bin/hash-runner",
30
- "clean": "rm -rf .turbo node_modules dist",
31
- "lint": "biome check --write --unsafe src && biome format src --write && biome lint src --fix",
32
- "verify-types": "tsc --noEmit"
33
- },
34
27
  "dependencies": {
35
28
  "async-mutex": "0.5.0",
36
29
  "eventemitter3": "5.0.1"
37
30
  },
38
31
  "devDependencies": {
39
32
  "@biomejs/biome": "1.9.4",
40
- "@internal/tsconfig": "workspace:*",
41
33
  "hash-runner": "2.0.1",
42
34
  "tsup": "8.4.0",
43
35
  "typescript": "5.8.2",
44
- "vitest": "3.1.1"
36
+ "vitest": "3.1.1",
37
+ "@internal/tsconfig": "3.0.0-alpha2.1"
45
38
  },
46
39
  "peerDependencies": {
47
- "@airtop/sdk": "workspace:*"
40
+ "@airtop/sdk": "1.0.0-alpha2.1"
48
41
  },
49
42
  "bugs": "https://github.com/airtop-ai/airtop-sdk/issues",
50
43
  "engines": {
@@ -54,5 +47,11 @@
54
47
  "dist"
55
48
  ],
56
49
  "homepage": "https://airtop.ai",
57
- "packageManager": "pnpm@10.5.0"
58
- }
50
+ "scripts": {
51
+ "build": "tsup src/index.ts",
52
+ "build:dev": "node_modules/.bin/hash-runner",
53
+ "clean": "rm -rf .turbo node_modules dist",
54
+ "lint": "biome check --write --unsafe src && biome format src --write && biome lint src --fix",
55
+ "verify-types": "tsc --noEmit"
56
+ }
57
+ }