Skip to content

iterateFind ​

Category
Tags
Export size
min 7.64 kB · gzip 3.11 kB
See also

Use for await to iterate over the results of a find method.

This function is useful for iterating over large datasets without loading everything into memory at once. It uses pagination to fetch results in chunks, allowing you to process each item as it is retrieved.

ts
  import { 
iterateFind
} from 'feathers-utils/utils';

Example ​

ts
import { iterateFind } from 'feathers-utils/utils'

const app = feathers()

// Assuming 'users' service has many records
for await (const user of iterateFind(app, 'users', {
 params: { query: { active: true }, // Custom query parameters
} })) {
 console.log(user) // Process each user record
}

Options ​

paramsP & {paginate?: PaginateOption}

The params of every find call. query.$limit is the page size, capped by paginate.max; paginate defaults to { default: 10, max: 100 }.

keysetbooleandefaultfalse

Page by the id instead of by $skip: every page asks for the items after the last id of the page before, sorted by id.

Use it when the loop changes items so that they leave the query, or when others write to the service meanwhile - with $skip the pages shift, so items are skipped or seen twice. It also stays linear on large tables, where the database reads and discards every skipped row per page, and it needs no total, so the find calls run with paginate: false.

The items come sorted by id, so a query.$sort throws. The id is added to a query.$select that leaves it out.

Type declaration ​

Show Type Declarations
ts
type PaginateOption = {
  default?: number
  max?: number
}
export type IterateFindOptions<P extends Params = Params> = {
  /**
   * The params of every `find` call. `query.$limit` is the page size, capped by
   * `paginate.max`; `paginate` defaults to `{ default: 10, max: 100 }`.
   */
  params?: P & {
    paginate?: PaginateOption
  }
  /**
   * Page by the id instead of by `$skip`: every page asks for the items after
   * the last id of the page before, sorted by id.
   *
   * Use it when the loop changes items so that they leave the query, or when
   * others write to the service meanwhile - with `$skip` the pages shift, so
   * items are skipped or seen twice. It also stays linear on large tables,
   * where the database reads and discards every skipped row per page, and it
   * needs no `total`, so the `find` calls run with `paginate: false`.
   *
   * The items come sorted by id, so a `query.$sort` throws. The id is added to
   * a `query.$select` that leaves it out.
   *
   * @default false
   */
  keyset?: boolean
}
/**
 * Use `for await` to iterate over the results of a `find` method.
 *
 * This function is useful for iterating over large datasets without loading everything into memory at once.
 * It uses pagination to fetch results in chunks, allowing you to process each item as it is retrieved.
 *
 * @example
 * ```ts
 *
 *
 * const app = feathers()
 *
 * // Assuming 'users' service has many records
 * for await (const user of iterateFind(app, 'users', {
 *  params: { query: { active: true }, // Custom query parameters
 * } })) {
 *  console.log(user) // Process each user record
 * }
 * ```
 *
 * @see https://utils.feathersjs.com/utils/iterate-find.html
 */
export declare function iterateFind<
  Services,
  Path extends KeyOf<Services>,
  Service extends Services[Path] = Services[Path],
  P extends Params = InferFindParams<Service>,
  Item = InferFindResultSingle<Service>,
>(
  app: Application<Services>,
  servicePath: Path,
  options?: IterateFindOptions<P>,
): AsyncGenerator<Item, void, unknown>
ArgumentTypeDescription
appApplication<Services>
servicePathPath
optionsIterateFindOptions<P>

Changing Items While Iterating ​

By default, iterateFind pages with $skip. When the loop changes items so that they no longer match the query, or others write to the service meanwhile, the pages shift: items are skipped or seen twice, without an error.

ts
for await (const job of iterateFind(app, 'jobs', {
  params: { query: { status: 'pending' } },
  // pages by id instead of `$skip`, so every job comes exactly once
  keyset: true,
})) {
  await app.service('jobs').patch(job.id, { status: 'done' })
}

Without keyset, this loop leaves jobs pending: after the first page, those jobs no longer match, so $skip jumps over as many unprocessed ones.

With keyset: true, every page asks for the items after the last id of the page before. That also keeps large tables cheap, since the database no longer reads and discards every skipped row per page. The items come sorted by id, so query.$sort is not supported.

Released under the MIT License.