@mantlebee/ts-data
    Preparing search index...

    Interface IDataSourceExtension<TItem, TEvents>

    Hooks of the read cycle of an IDataSource, both optional. The cycle has two phases, each one over every extension in the order of the keys of the record they are given with:

    1. build: buildPayload of each extension adds its contribution to the payload. The phase completes before any item is read, so the second phase always sees the whole query.
    2. read: readItems of each extension wraps the reading, like a middleware. The extensions form an onion around the reader of the data source: the first key is the outermost layer, the reader is the core, and next(payload) hands the payload to the next layer in. An extension without the hook is skipped.

    The order of the keys is up to the consumer and it matters: e.g. the filtering key must precede the pagination one for the count reader to receive the filters. In the read phase it also sets the order of the transformations: the result of next flows outward, so the outermost extension transforms last.

    Both hooks receive the AbortSignal of the read, aborted when a newer read supersedes it, for the reads of their own (a hook that reads something hands it to its reader, the others ignore it). next has the signal bound already: nothing to forward. Once the signal is aborted the data source stops the cycle: the remaining hooks and the reader are not invoked, and a next called after the abort rejects with the AbortedException of ts-core. An implementer declaring fewer parameters still type-checks.

    // A read-through cache: a hit skips `next`, hence the reader.
    class CacheExtension<TItem>
    extends Subscribable<EventsDefinition>
    implements IDataSourceExtension<TItem>
    {
    private readonly _cache = new Map<string, List<TItem>>();
    public async readItems(payload: DataSourcePayload<TItem>, next: DataSourceReadNext<TItem>) {
    const key = createHash(payload);
    const hit = this._cache.get(key);
    if (hit) return hit;
    const items = await next(payload);
    this._cache.set(key, items);
    return items;
    }
    }
    // A backend accepting one sort per call: one read per sort, results merged.
    class OneSortPerCallExtension<TItem>
    extends Subscribable<EventsDefinition>
    implements IDataSourceExtension<TItem>
    {
    public async readItems(payload: DataSourcePayload<TItem>, next: DataSourceReadNext<TItem>) {
    const sorts = payload.sorts ?? [];
    if (sorts.length < 2) return next(payload);
    const reads = await Promise.all(sorts.map((sort) => next({ ...payload, sorts: [sort] })));
    return merge(reads);
    }
    }
    const users = createDataSource(readUsers, { filtering, cache: new CacheExtension<User>() });
    interface IDataSourceExtension<
        TItem = Any,
        TEvents extends EventsDefinition = EventsDefinition,
    > {
        buildPayload?(
            payload: DataSourcePayload<TItem>,
            signal?: AbortSignal,
        ): Promise<DataSourcePayload<TItem>>;
        dispose(): Promise<void>;
        on<TKey extends string>(
            event: TKey,
            handler: EventHandler<TEvents, TKey>,
        ): IDisposable;
        readItems?(
            payload: DataSourcePayload<TItem>,
            next: DataSourceReadNext<TItem>,
            signal?: AbortSignal,
        ): Promise<List<TItem>>;
    }

    Type Parameters

    • TItem = Any
    • TEvents extends EventsDefinition = EventsDefinition

    Hierarchy (View Summary)

    Implemented by

    Index
    • Releases the held resource. Implementations should be safe to call more than once.

      Returns Promise<void>

      a promise that settles once the resource has been released.

    • Registers a handler for an event.

      Type Parameters

      • TKey extends string

        Name of the event to listen to.

      Parameters

      • event: TKey

        Event to subscribe to.

      • handler: EventHandler<TEvents, TKey>

        Callback invoked on every trigger of event.

      Returns IDisposable

      a subscription; dispose it to remove this single handler.

    • Wraps the reading of the items. Read phase: the extension is a layer of the onion around the reader, next the layers inside it (the inner extensions, then the reader of the data source). The payload is the whole query, whatever the position of the extension.

      next may be called zero times (e.g. a cache hit: the reader is never invoked), once, with this or another payload, or several times (e.g. a payload split into several reads whose results are merged); every call runs the inner layers and the reader again, so the hooks and the events of the inner extensions may fire as many times. The result may be transformed before being returned, and flows outward: the outermost extension transforms last.

      Parameters

      • payload: DataSourcePayload<TItem>

        The whole query, built by the build phase.

      • next: DataSourceReadNext<TItem>

        Reads the items through the inner layers and the reader; the signal of the read is bound already.

      • Optionalsignal: AbortSignal

        Signal of the read, aborted when the read is superseded, for the reads of the extension itself.

      Returns Promise<List<TItem>>

      the items for the outer extension, or the data source.