@dereekb/util 13.0.5 → 13.0.7

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.
@@ -1,30 +1,81 @@
1
1
  import { type DecisionFunction, type IndexNumber, type IndexRef, type Maybe, type Milliseconds, type PromiseOrValue, type PerformAsyncTasksConfig, type PerformAsyncTasksResult } from '@dereekb/util';
2
2
  import { type FetchPage, type FetchPageFactory, type FetchPageFactoryInputOptions, type FetchPageResult, type FetchPageResultWithInput } from './fetch.page';
3
3
  /**
4
- * Function called for each item that was fetched, along with the index and fetch results.
4
+ * Callback invoked for each individual item fetched across all pages.
5
5
  *
6
- * The index is the overall index this item is from the returned items.
6
+ * Receives the item, its global index (cumulative across all pages, not just the current page),
7
+ * and the full page result context. This enables per-item processing with awareness of both
8
+ * the item's position in the overall iteration and the page it originated from.
9
+ *
10
+ * @param item - The individual item extracted from a page result
11
+ * @param i - Global index of this item across all pages visited so far
12
+ * @param fetchPageResult - The page result containing this item, including input and pagination info
7
13
  */
8
14
  export type IterateFetchPagesByEachItemFunction<I, O, T, R> = (item: T, i: IndexNumber, fetchPageResult: FetchPageResultWithInput<I, O>) => Promise<R>;
15
+ /**
16
+ * A tuple pairing an item with its global index across all iterated pages.
17
+ *
18
+ * Used internally as the task input for {@link performAsyncTasks}, allowing
19
+ * each item to carry its positional context through parallel/sequential processing.
20
+ */
9
21
  export type IterateFetchPagesByEachItemPair<T> = readonly [T, IndexNumber];
22
+ /**
23
+ * Configuration for {@link iterateFetchPagesByEachItem}.
24
+ *
25
+ * Extends {@link IterateFetchPagesByItemsConfig} but replaces the batch-level `iteratePageItems`
26
+ * with a per-item `iterateEachPageItem` callback. Each item on every page is processed individually,
27
+ * either sequentially (default) or in parallel via `iteratePerformTasksConfig`.
28
+ */
10
29
  export interface IterateFetchPagesByEachItemConfig<I, O, T, R> extends Omit<IterateFetchPagesByItemsConfig<I, O, T, IterateFetchPagesByEachItemResult<T, R>>, 'iteratePageItems'> {
11
30
  /**
12
- * The iterate function per each page result.
31
+ * Callback invoked once per item on each fetched page.
32
+ *
33
+ * Items are processed via {@link performAsyncTasks} — sequentially by default,
34
+ * but configurable to run in parallel via `iteratePerformTasksConfig`.
13
35
  */
14
36
  readonly iterateEachPageItem: IterateFetchPagesByEachItemFunction<I, O, T, R>;
15
37
  /**
16
- * Optional additional configuration to pass to the
38
+ * Optional configuration passed to {@link performAsyncTasks} controlling
39
+ * how items within a single page are processed.
17
40
  *
18
- * By default, sequential is true.
41
+ * By default, `sequential` is true, meaning items are processed one at a time.
42
+ * Override to enable parallel processing, set concurrency limits, or configure retry behavior.
19
43
  */
20
44
  readonly iteratePerformTasksConfig?: Partial<PerformAsyncTasksConfig<IterateFetchPagesByEachItemPair<T>>>;
21
45
  }
46
+ /**
47
+ * Result of {@link iterateFetchPagesByEachItem}, containing success/failure details
48
+ * for each individually processed item.
49
+ *
50
+ * Wraps {@link PerformAsyncTasksResult} with item-index pairs, providing access to
51
+ * which items succeeded, failed, and their corresponding results or errors.
52
+ */
22
53
  export type IterateFetchPagesByEachItemResult<T, R> = PerformAsyncTasksResult<IterateFetchPagesByEachItemPair<T>, R>;
23
54
  /**
24
- * Iterates through the pages of a created FetchPage instance by each item individually.
55
+ * Iterates through all pages of a paginated fetch and processes each item individually.
56
+ *
57
+ * Built on top of {@link iterateFetchPagesByItems}, this function handles per-item granularity
58
+ * by extracting items from each page and delegating to {@link performAsyncTasks}. Items are
59
+ * processed sequentially by default to preserve ordering guarantees, but can be parallelized
60
+ * via `iteratePerformTasksConfig`.
61
+ *
62
+ * Each item's callback receives a global index that reflects its position across all pages,
63
+ * not just within the current page.
64
+ *
65
+ * @param config - Configuration specifying the fetch page source, item extraction, and per-item callback
66
+ * @returns Combined result from {@link iterateFetchPagesByItems} including page/item counts and per-item task results
25
67
  *
26
- * @param config
27
- * @returns
68
+ * @example
69
+ * ```typescript
70
+ * const result = await iterateFetchPagesByEachItem({
71
+ * fetchPageFactory: myPageFactory,
72
+ * input: { query: 'active' },
73
+ * readItemsFromPageResult: (r) => r.result.items,
74
+ * iterateEachPageItem: async (item, index, pageResult) => {
75
+ * return processItem(item);
76
+ * }
77
+ * });
78
+ * ```
28
79
  */
29
80
  export declare function iterateFetchPagesByEachItem<I, O, T, R>(config: IterateFetchPagesByEachItemConfig<I, O, T, R>): Promise<{
30
81
  totalItemsLoaded: number;
@@ -33,59 +84,127 @@ export declare function iterateFetchPagesByEachItem<I, O, T, R>(config: IterateF
33
84
  totalPagesLimitReached: boolean;
34
85
  }>;
35
86
  /**
36
- * Filter function used to filter out items.
87
+ * Filters items extracted from a page result before they are passed to the iteration callback.
37
88
  *
38
- * @param snapshot
39
- * @returns
89
+ * Receives all items from a single page along with the page result context.
90
+ * Filtered-out items do not count toward `iterateItemsLimit` but the filtering
91
+ * does not affect pagination continuation — pages continue to be fetched regardless
92
+ * of how many items pass the filter.
93
+ *
94
+ * @param items - All items extracted from the current page
95
+ * @param pageResult - The full page result including input and pagination metadata
96
+ * @returns The filtered subset of items to process, or a promise resolving to it
40
97
  */
41
98
  export type IterateFetchPagesByItemsFilterFunction<I, O, T> = (items: T[], pageResult: FetchPageResultWithInput<I, O>) => PromiseOrValue<T[]>;
99
+ /**
100
+ * Callback invoked with all (optionally filtered) items from a single fetched page.
101
+ *
102
+ * Unlike {@link IterateFetchPagesByEachItemFunction} which operates per-item, this receives
103
+ * the entire batch of items for a page, enabling bulk processing strategies.
104
+ *
105
+ * @param items - Items from the current page (after filtering, if configured)
106
+ * @param fetchPageResult - The page result with input and pagination context
107
+ * @param totalItemsVisited - Running total of items visited across all previous pages (before this batch)
108
+ */
42
109
  export type IterateFetchPagesByItemsFunction<I, O, T, R> = (items: T[], fetchPageResult: FetchPageResultWithInput<I, O>, totalItemsVisited: number) => Promise<R>;
110
+ /**
111
+ * Configuration for {@link iterateFetchPagesByItems}.
112
+ *
113
+ * Extends page-level iteration with item extraction, filtering, and item-count-based
114
+ * termination. Pages are fetched via a {@link FetchPage} or {@link FetchPageFactory},
115
+ * items are extracted from each page result, optionally filtered, then passed to
116
+ * `iteratePageItems` as a batch.
117
+ *
118
+ * Supports two independent limits: `loadItemLimit` caps the total raw items loaded
119
+ * from the API, while `iterateItemsLimit` caps items that pass filtering.
120
+ */
43
121
  export interface IterateFetchPagesByItemsConfig<I, O, T, R> extends Omit<IterateFetchPagesConfig<I, O, R>, 'iteratePage'> {
44
122
  /**
45
- * Read individual items from page result.
123
+ * Extracts typed items from a raw page result.
46
124
  *
47
- * @param items
48
- * @returns
125
+ * Called once per page to transform the API response into the items
126
+ * that will be filtered and iterated over.
127
+ *
128
+ * @param results - The raw page result from the fetch
129
+ * @returns Array of items extracted from this page
49
130
  */
50
131
  readItemsFromPageResult(results: FetchPageResult<O>): T[];
51
132
  /**
52
- * The total number of items allowed to be visited/used.
53
- *
54
- * If items are filtered out, they do not count towards the visit total.
133
+ * Maximum number of items allowed to be visited (post-filter) across all pages.
55
134
  *
56
- * Ends on the page that reaches this limit.
135
+ * Items that are filtered out by `filterPageItems` do not count toward this limit.
136
+ * Iteration ends after the page where this limit is reached; items on that final
137
+ * page are still fully processed.
57
138
  */
58
139
  readonly iterateItemsLimit?: Maybe<number>;
59
140
  /**
60
- * The total number of items allowed to be loaded from all pages.
141
+ * Maximum number of raw items allowed to be loaded (pre-filter) across all pages.
61
142
  *
62
- * Ends on the page that reaches this limit.
143
+ * Counts all items returned by `readItemsFromPageResult`, regardless of filtering.
144
+ * Iteration ends after the page where this limit is reached.
63
145
  */
64
146
  readonly loadItemLimit?: Maybe<number>;
65
147
  /**
66
- * Filter function that can be used to filter out items from a result
148
+ * Optional filter applied to items on each page before they reach `iteratePageItems`.
67
149
  *
68
- * If all items are filtered out then the iteration will continue with final item of the snapshot regardless of filtering. The filtering does not impact the continuation decision.
69
- * Use the handleRepeatCursor to properly exit the loop in unwanted repeat cursor cases.
150
+ * Filtered-out items are excluded from processing and do not count toward `iterateItemsLimit`,
151
+ * but filtering does not affect pagination the next page is still fetched based on the
152
+ * original (unfiltered) page result. If all items on a page are filtered out, iteration
153
+ * continues using the last item for cursor positioning.
70
154
  *
71
- * @param snapshot
72
- * @returns
155
+ * Use `endEarly` or `handleRepeatCursor` to handle cases where filtering causes
156
+ * repeated cursors or unwanted looping.
73
157
  */
74
158
  readonly filterPageItems?: IterateFetchPagesByItemsFilterFunction<I, O, T>;
75
159
  /**
76
- * The iterate function per each page result.
160
+ * Callback invoked with the batch of items (post-filter) from each page.
161
+ *
162
+ * Receives the filtered items, the page result context, and the running count
163
+ * of total items visited before this page.
77
164
  */
78
165
  readonly iteratePageItems: IterateFetchPagesByItemsFunction<I, O, T, R>;
79
166
  }
167
+ /**
168
+ * Result of {@link iterateFetchPagesByItems}, extending page-level results
169
+ * with item-level counters.
170
+ */
80
171
  export interface IterateFetchPagesByItemsResult<I, O, T, R> extends IterateFetchPagesResult {
172
+ /**
173
+ * Total number of raw items loaded from all pages (pre-filter).
174
+ */
81
175
  readonly totalItemsLoaded: number;
176
+ /**
177
+ * Total number of items visited across all pages (post-filter).
178
+ */
82
179
  readonly totalItemsVisited: number;
83
180
  }
84
181
  /**
85
- * Iterates through the pages of a created FetchPage instance.
182
+ * Iterates through paginated fetch results at the item batch level.
86
183
  *
87
- * @param config
88
- * @returns
184
+ * Fetches pages sequentially (or in parallel via `maxParallelPages`), extracts items
185
+ * from each page using `readItemsFromPageResult`, optionally filters them, then
186
+ * passes the batch to `iteratePageItems`. Tracks both raw loaded counts and
187
+ * post-filter visited counts, terminating when either limit is reached or pages
188
+ * are exhausted.
189
+ *
190
+ * For per-item processing instead of batch processing, use {@link iterateFetchPagesByEachItem}.
191
+ *
192
+ * @param config - Configuration specifying fetch source, item extraction, filtering, limits, and batch callback
193
+ * @returns Result with page count and item counters (loaded and visited)
194
+ *
195
+ * @example
196
+ * ```typescript
197
+ * const result = await iterateFetchPagesByItems({
198
+ * fetchPageFactory: myPageFactory,
199
+ * input: { status: 'active' },
200
+ * readItemsFromPageResult: (r) => r.result.records,
201
+ * filterPageItems: (items) => items.filter(x => x.isValid),
202
+ * iterateItemsLimit: 500,
203
+ * iteratePageItems: async (items, pageResult, totalVisited) => {
204
+ * await bulkInsert(items);
205
+ * }
206
+ * });
207
+ * ```
89
208
  */
90
209
  export declare function iterateFetchPagesByItems<I, O, T, R>(config: IterateFetchPagesByItemsConfig<I, O, T, R>): Promise<{
91
210
  totalItemsLoaded: number;
@@ -93,81 +212,190 @@ export declare function iterateFetchPagesByItems<I, O, T, R>(config: IterateFetc
93
212
  totalPages: number;
94
213
  totalPagesLimitReached: boolean;
95
214
  }>;
215
+ /**
216
+ * Union type for {@link iterateFetchPages} configuration.
217
+ *
218
+ * Accepts either a factory-based config (providing `input` + `fetchPageFactory` to create
219
+ * the {@link FetchPage} on demand) or an instance-based config (providing a pre-created
220
+ * `fetchPage` directly). This flexibility supports both lazy and eager page initialization.
221
+ */
96
222
  export type IterateFetchPagesConfig<I, O, R> = IterateFetchPagesConfigWithFactoryAndInput<I, O, R> | IterateFetchPagesConfigWithFetchPageInstance<I, O, R>;
223
+ /**
224
+ * Configuration variant that creates a {@link FetchPage} from a factory and input.
225
+ *
226
+ * Use this when you have a reusable {@link FetchPageFactory} and want the iterator
227
+ * to handle page instantiation. The factory receives the input along with any
228
+ * `maxPage`/`maxItemsPerPage` options from {@link FetchPageFactoryInputOptions}.
229
+ */
97
230
  export interface IterateFetchPagesConfigWithFactoryAndInput<I, O, R> extends BaseIterateFetchPagesConfig<I, O, R> {
98
231
  /**
99
- * Input for the page fetch.
232
+ * The query/filter input passed to the fetch page factory to initialize pagination.
100
233
  */
101
234
  readonly input: I;
235
+ /**
236
+ * Factory that creates a {@link FetchPage} from the given input and options.
237
+ */
102
238
  readonly fetchPageFactory: FetchPageFactory<I, O>;
103
239
  }
240
+ /**
241
+ * Configuration variant that uses a pre-created {@link FetchPage} instance directly.
242
+ *
243
+ * Use this when you already have a configured {@link FetchPage} and don't need
244
+ * the iterator to create one via a factory.
245
+ */
104
246
  export interface IterateFetchPagesConfigWithFetchPageInstance<I, O, R> extends BaseIterateFetchPagesConfig<I, O, R> {
247
+ /**
248
+ * Pre-created fetch page instance to iterate through.
249
+ */
105
250
  readonly fetchPage: FetchPage<I, O>;
106
251
  }
252
+ /**
253
+ * Base configuration shared by all {@link iterateFetchPages} config variants.
254
+ *
255
+ * Provides the core iteration hooks (`iteratePage`, `usePageResult`, `endEarly`),
256
+ * concurrency controls (`maxParallelPages`, `waitBetweenPages`), and pagination
257
+ * limits inherited from {@link FetchPageFactoryInputOptions}.
258
+ */
107
259
  export interface BaseIterateFetchPagesConfig<I, O, R> extends FetchPageFactoryInputOptions {
108
260
  /**
109
- * Input for the page fetch.
261
+ * Optional input for the page fetch. Required when using `fetchPageFactory`,
262
+ * ignored when using a pre-created `fetchPage`.
110
263
  */
111
264
  readonly input?: I;
265
+ /**
266
+ * Optional factory for creating the {@link FetchPage}. Mutually exclusive
267
+ * with `fetchPage` — provide one or the other.
268
+ */
112
269
  readonly fetchPageFactory?: FetchPageFactory<I, O>;
270
+ /**
271
+ * Optional pre-created {@link FetchPage} instance. Mutually exclusive
272
+ * with `fetchPageFactory` + `input`.
273
+ */
113
274
  readonly fetchPage?: FetchPage<I, O>;
114
275
  /**
115
- * The number of max parallel pages to run.
276
+ * Maximum number of pages to process concurrently.
116
277
  *
117
- * By default pages are run serially (max of 1), but can be run in parallel.
278
+ * Defaults to 1 (serial execution). When set higher, pages are fetched
279
+ * and processed in parallel using {@link performTasksFromFactoryInParallelFunction}.
280
+ * Note that page *fetching* is always sequential (each page depends on the
281
+ * previous page's cursor), but page *processing* via `iteratePage` can overlap.
118
282
  */
119
283
  readonly maxParallelPages?: number;
120
284
  /**
121
- * The amount of time to add as a delay between beginning a new page.
285
+ * Minimum delay in milliseconds between initiating consecutive page fetches.
122
286
  *
123
- * If in parallel this is the minimum amount of time to wait before starting a new page.
287
+ * Useful for rate limiting API calls. When running in parallel, this ensures
288
+ * at least this much time passes between starting each new page request.
124
289
  */
125
290
  readonly waitBetweenPages?: Milliseconds;
126
291
  /**
127
- * The iterate function per each page result.
292
+ * Core iteration callback invoked once per fetched page.
293
+ *
294
+ * Receives the full page result including the original input and pagination metadata.
295
+ * The return value is captured in the {@link IterateFetchPagesIterationResult} and
296
+ * made available to `usePageResult` and `endEarly`.
297
+ *
298
+ * @param result - The fetched page result with input context
299
+ * @returns The processing result for this page
128
300
  */
129
301
  iteratePage(result: FetchPageResultWithInput<I, O>): Promise<R>;
130
302
  /**
131
- * (Optional) Called at the end of each page.
303
+ * Optional side-effect callback invoked after each page is fully processed.
304
+ *
305
+ * Called after `iteratePage` completes, receiving the full iteration result
306
+ * including the page index, fetch result, and processing result. Useful for
307
+ * logging, progress tracking, or accumulating results externally.
308
+ *
309
+ * @param pageResult - The complete iteration result for this page
132
310
  */
133
311
  usePageResult?(pageResult: IterateFetchPagesIterationResult<I, O, R>): PromiseOrValue<void>;
134
312
  /**
135
- * (Optional) Function to check whether or not to end the iteration early based on the result.
313
+ * Optional early termination predicate evaluated after each page.
136
314
  *
137
- * @param pageResult
138
- * @returns
315
+ * When this returns `true`, no further pages will be fetched. Any pages
316
+ * already in-flight (when using parallel processing) will still complete.
317
+ * Checked after both `iteratePage` and `usePageResult` have finished.
318
+ *
319
+ * @param pageResult - The complete iteration result for the most recent page
320
+ * @returns `true` to stop iteration after this page
139
321
  */
140
322
  endEarly?: DecisionFunction<IterateFetchPagesIterationResult<I, O, R>>;
141
323
  }
324
+ /**
325
+ * Intermediate result produced after processing a single page during iteration.
326
+ *
327
+ * Passed to {@link BaseIterateFetchPagesConfig.usePageResult} and
328
+ * {@link BaseIterateFetchPagesConfig.endEarly} to enable post-page logic
329
+ * and conditional termination.
330
+ */
142
331
  export interface IterateFetchPagesIterationResult<I, O, R> extends IndexRef {
143
- /***
144
- * Page index number
332
+ /**
333
+ * Zero-based page index within this iteration run.
145
334
  */
146
335
  readonly i: IndexNumber;
147
336
  /**
148
- * The returned fetch page result
337
+ * The raw fetch page result including pagination metadata and the original input.
149
338
  */
150
339
  readonly fetchPageResult: FetchPageResultWithInput<I, O>;
151
340
  /**
152
- * Results returned from each page.
341
+ * Value returned by `iteratePage` for this page.
153
342
  */
154
343
  readonly result: R;
155
344
  }
345
+ /**
346
+ * Final result returned by {@link iterateFetchPages} after all pages have been processed.
347
+ */
156
348
  export interface IterateFetchPagesResult {
157
349
  /**
158
- * The total number of pages visited.
350
+ * Total number of pages fetched and processed during this iteration.
159
351
  */
160
352
  readonly totalPages: number;
161
353
  /**
162
- * Whether or not the total page limit was reached.
354
+ * Whether iteration stopped because the configured `maxPage` limit was reached,
355
+ * as opposed to running out of pages or an early termination via `endEarly`.
163
356
  */
164
357
  readonly totalPagesLimitReached: boolean;
165
358
  }
166
359
  /**
167
- * Iterates through the pages of a created FetchPage instance.
360
+ * Core pagination iterator that fetches and processes pages from a {@link FetchPage} source.
361
+ *
362
+ * This is the foundational function in the fetch page iteration hierarchy. It drives
363
+ * sequential page fetching (each page depends on the previous page's cursor/state),
364
+ * with optional parallel *processing* of fetched pages via `maxParallelPages`.
365
+ *
366
+ * The iteration loop continues until one of these conditions is met:
367
+ * - No more pages are available (`hasNext` is false)
368
+ * - The `maxPage` limit from {@link FetchPageFactoryInputOptions} is reached
369
+ * - The `endEarly` predicate returns true
370
+ *
371
+ * Higher-level functions {@link iterateFetchPagesByItems} and {@link iterateFetchPagesByEachItem}
372
+ * build on this to add item extraction, filtering, and per-item processing.
373
+ *
374
+ * @param config - Configuration specifying the page source, processing callback, and iteration controls
375
+ * @returns Summary of the iteration including total pages visited and whether the page limit was hit
376
+ *
377
+ * @example
378
+ * ```typescript
379
+ * // Using a factory
380
+ * const result = await iterateFetchPages({
381
+ * input: { query: 'active', pageSize: 50 },
382
+ * fetchPageFactory: myFactory,
383
+ * maxPage: 10,
384
+ * iteratePage: async (pageResult) => {
385
+ * console.log(`Page ${pageResult.page}:`, pageResult.result);
386
+ * return pageResult.result.items.length;
387
+ * },
388
+ * endEarly: ({ result }) => result === 0
389
+ * });
168
390
  *
169
- * @param config
170
- * @returns
391
+ * // Using a pre-created FetchPage instance
392
+ * const result = await iterateFetchPages({
393
+ * fetchPage: existingFetchPage,
394
+ * iteratePage: async (pageResult) => {
395
+ * await processBatch(pageResult.result);
396
+ * }
397
+ * });
398
+ * ```
171
399
  */
172
400
  export declare function iterateFetchPages<I, O, R>(config: IterateFetchPagesConfigWithFactoryAndInput<I, O, R>): Promise<IterateFetchPagesResult>;
173
401
  export declare function iterateFetchPages<I, O, R>(config: IterateFetchPagesConfigWithFetchPageInstance<I, O, R>): Promise<IterateFetchPagesResult>;