UNPKG

fauna

Version:

A driver to query Fauna databases in browsers, Node.js, and other Javascript runtimes

282 lines (254 loc) 8.69 kB
import { Client } from "../client"; import { Query, fql } from "../query-builder"; import { QueryOptions, QueryValue } from "../wire-protocol"; /** * A materialized view of a Set. * @see {@link https://docs.fauna.com/fauna/current/reference/fql_reference/types#set} */ export class Page<T extends QueryValue> { /** A materialized page of data */ readonly data: T[]; /** * A pagination cursor, used to obtain additional information in the Set. * If `after` is not provided, then `data` must be present and represents the * last Page in the Set. */ readonly after?: string; constructor({ data, after }: { data: T[]; after?: string }) { this.data = data; this.after = after; } } /** * A un-materialized Set. Typically received when a materialized Set contains * another set, the EmbeddedSet does not contain any data to avoid potential * issues such as self-reference and infinite recursion * @see {@link https://docs.fauna.com/fauna/current/reference/fql_reference/types#set} */ export class EmbeddedSet { /** * A pagination cursor, used to obtain additional information in the Set. */ readonly after: string; constructor(after: string) { this.after = after; } } /** * A class to provide an iterable API for fetching multiple pages of data, given * a Fauna Set */ export class SetIterator<T extends QueryValue> implements AsyncGenerator<T[], void, unknown> { readonly #generator: AsyncGenerator<T[], void, unknown>; /** * Constructs a new {@link SetIterator}. * * @remarks Though you can use {@link SetIterator} class directly, it is * most common to create an instance through the {@link Client.paginate} `paginate` * method. * * @typeParam T - The expected type of the items returned from Fauna on each * iteration * @param client - The {@link Client} that will be used to fetch new data on * each iteration * @param initial - An existing fauna Set ({@link Page} or * {@link EmbeddedSet}) or function which returns a promise. If the Promise * resolves to a {@link Page} or {@link EmbeddedSet} then the iterator will * use the client to fetch additional pages of data. * @param options - a {@link QueryOptions} to apply to the queries. Optional. */ constructor( client: Client, initial: Page<T> | EmbeddedSet | (() => Promise<T | Page<T> | EmbeddedSet>), options?: QueryOptions, ) { options = options ?? {}; if (initial instanceof Function) { this.#generator = generateFromThunk(client, initial, options); } else if (initial instanceof Page || initial instanceof EmbeddedSet) { this.#generator = generatePages(client, initial, options); } else { throw new TypeError( `Expected 'Page<T> | EmbeddedSet | (() => Promise<T | Page<T> | EmbeddedSet>)', but received ${JSON.stringify( initial, )}`, ); } } /** * Constructs a new {@link SetIterator} from an {@link Query} * * @internal Though you can use {@link SetIterator.fromQuery} directly, it is * intended as a convenience for use in the {@link Client.paginate} method */ static fromQuery<T extends QueryValue>( client: Client, query: Query, options?: QueryOptions, ): SetIterator<T> { return new SetIterator<T>( client, async () => { const response = await client.query<T | Page<T> | EmbeddedSet>( query, options, ); return response.data; }, options, ); } /** * Constructs a new {@link SetIterator} from an {@link Page} or * {@link EmbeddedSet} * * @internal Though you can use {@link SetIterator.fromPageable} directly, it * is intended as a convenience for use in the {@link Client.paginate} method */ static fromPageable<T extends QueryValue>( client: Client, pageable: Page<T> | EmbeddedSet, options?: QueryOptions, ): SetIterator<T> { return new SetIterator<T>(client, pageable, options); } /** * Constructs a new {@link FlattenedSetIterator} from the current instance * * @returns A new {@link FlattenedSetIterator} from the current instance */ flatten(): FlattenedSetIterator<T> { return new FlattenedSetIterator(this); } /** Implement * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator/next| AsyncGenerator.next} * */ async next(): Promise<IteratorResult<T[], void>> { return this.#generator.next(); } /** Implement * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator/return| AsyncGenerator.return} * */ async return(): Promise<IteratorResult<T[], void>> { return this.#generator.return(); } /** Implement * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator/throw| AsyncGenerator.throw} * */ async throw(e: any): Promise<IteratorResult<T[], void>> { return this.#generator.throw(e); } /** Implement * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator| AsyncGenerator} * */ [Symbol.asyncIterator]() { return this; } } /** * A class to provide an iterable API for fetching multiple pages of data, given * a Fauna Set. This class takes a {@link SetIterator} and flattens the results * to yield the items directly. */ export class FlattenedSetIterator<T extends QueryValue> implements AsyncGenerator<T, void, unknown> { readonly #generator: AsyncGenerator<T, void, unknown>; /** * Constructs a new {@link FlattenedSetIterator}. * * @remarks Though you can use {@link FlattenedSetIterator} class directly, it * is most common to create an instance through the * {@link SetIterator.flatten} method. * * @typeParam T - The expected type of the items returned from Fauna on each * iteration * @param setIterator - The {@link SetIterator} */ constructor(setIterator: SetIterator<T>) { this.#generator = generateItems(setIterator); } /** Implement * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator/next| AsyncGenerator.next} * */ async next(): Promise<IteratorResult<T, void>> { return this.#generator.next(); } /** Implement * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator/return| AsyncGenerator.return} * */ async return(): Promise<IteratorResult<T, void>> { return this.#generator.return(); } /** Implement * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator/throw| AsyncGenerator.throw} * */ async throw(e: any): Promise<IteratorResult<T, void>> { return this.#generator.throw(e); } /** Implement * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator| AsyncGenerator} * */ [Symbol.asyncIterator]() { return this; } } /** * Internal async generator function to use with {@link Page} and * {@link EmbeddedSet} values */ async function* generatePages<T extends QueryValue>( client: Client, initial: Page<T> | EmbeddedSet, options: QueryOptions, ): AsyncGenerator<T[], void, unknown> { let currentPage = initial; if (currentPage instanceof Page) { yield currentPage.data; } while (currentPage.after) { // cursor means there is more data to fetch const query = fql`Set.paginate(${currentPage.after})`; const response = await client.query<Page<T>>(query, options); const nextPage = response.data; currentPage = nextPage; yield currentPage.data; } } /** * Internal async generator function to use with a function that returns a * promise of data. If the promise resolves to a {@link Page} or * {@link EmbeddedSet} then continue iterating. */ async function* generateFromThunk<T extends QueryValue>( client: Client, thunk: () => Promise<T | Page<T> | EmbeddedSet>, options: QueryOptions, ): AsyncGenerator<T[], void, unknown> { const result = await thunk(); if (result instanceof Page || result instanceof EmbeddedSet) { for await (const page of generatePages( client, result as Page<T> | EmbeddedSet, options, )) { yield page; } return; } yield [result]; } /** * Internal async generator function that flattens a {@link SetIterator} */ async function* generateItems<T extends QueryValue>( setIterator: SetIterator<T>, ) { for await (const page of setIterator) { for (const item of page) { yield item; } } }