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

    Class DataSource<TItem>

    Data source driven only by its extensions: it knows nothing about filters, pagination and so on, it just runs the read cycle described in IDataSourceExtension over its pipeline of extensions: the build phase (buildPayload of each extension, in the order of the keys), then the read phase (readItems of each extension as an onion around the reader, the first key outermost).

    Extensions by key. The extensions are given as a record (DataSourceExtensionsDefinition) and exposed under the same keys as readonly own properties, set only when given: "sorting" in source is false for a source without sorting. The pipeline runs them in the order of the keys of the record, the order they are written in (JavaScript lists integer-like keys first: name the extensions). The order is yours and it matters: write filtering before pagination, so the count reader receives the filters; in the read phase the first key wraps the others, so it transforms the items last. A key that is already a member of the source (items, read, …) is refused.

    The class cannot type its members from a generic record, so it exposes no keyed property in its type. Build a source with createDataSource, which returns the IDataSource typed with every key of the record.

    Inheritance. The class is exported to be extended, not to be built with new: a subclass knows its extensions, so it types the keys itself, one declare readonly per key given to super. declare matters: it emits nothing, where a plain field declaration would, under useDefineForClassFields (the default from the ES2022 target on), redefine the property to undefined after super has set it. The compiler does not check the declarations against the record, the subclass keeps them in line. A subclass reaches the pipeline (_extensions), the reader (_reader) and the _eventBus; a method or an accessor it adds is refused as a key like the members of the class.

    The latest read wins. The read cycle runs in one AbortableDelegate of ts-core: a read() requested while another is running aborts it, so the superseded read() rejects with AbortedException at once, its signal is aborted (the reader and the hooks receive it, e.g. for fetch) and the cycle stops at the next await without touching items nor raising items or error: the remaining buildPayload hooks are not invoked, and every layer of the read phase, next included, rejects with AbortedException instead of running. items and the items event therefore always come from the latest read, whatever the order the readers resolve in, and isReading stays true until the latest read settles. dispose() aborts the running read the same way.

    const filtering = new FilterExtension<User>();
    const pagination = new PaginationExtension<User>(countUsers, 20);
    const dataSource = createDataSource(
    (payload, signal) => api.getUsers(payload, { signal }),
    { filtering, pagination }, // filtering first: the count receives the filters
    ); // IDataSource<User, { filtering: FilterExtension<User>; pagination: PaginationExtension<User> }>
    dataSource.on("items", (users) => render(users));
    dataSource.filtering.on("filtersChange", () => dataSource.read());
    await dataSource.read();
    // Two reads in a row: the first rejects with AbortedException, the second
    // is the one that sets `items`, even if the first reader resolves last.
    dataSource.read().catch((error) => {
    if (!isAbortedException(error)) throw error;
    });
    await dataSource.read();
    // A subclass: the keys are declared, the record is given to `super`.
    class UsersDataSource extends DataSource<User> {
    declare public readonly filtering: FilterExtension<User>;
    declare public readonly pagination: PaginationExtension<User>;

    public constructor() {
    super((payload, signal) => api.getUsers(payload, { signal }), {
    filtering: new FilterExtension<User>(),
    pagination: new PaginationExtension<User>(countUsers, 20),
    });
    }

    public async readAdults(): Promise<void> {
    this.filtering.setFilters([adult]);
    await this.read();
    }
    }
    const users: IDataSourceWith<User, "filtering" | "pagination"> =
    new UsersDataSource();

    Type Parameters

    • TItem = Any

      Type of the items.

    Hierarchy

    Implements

    Index
    _eventBus: EventBus<DataSourceEvents<TItem>>

    Bus used to register handlers and to raise the events.

    _extensions: List<IDataSourceExtension<TItem, EventsDefinition>>

    The pipeline: every extension given, in the order of the keys.

    • get isReading(): boolean

      true from the first read() until the latest one settles: a read superseded by a newer one does not reset it.

      Returns boolean

    • Aborts the running read (its read() rejects with AbortedException and it raises no event afterwards), removes every handler and disposes every extension.

      Returns Promise<void>

    • Registers a handler for an event.

      Type Parameters

      • TKey extends string

        Name of the event to listen to.

      Parameters

      Returns IDisposable

      a subscription; dispose it to remove this single handler.

    • Runs the read cycle and updates items. Rejects with the error of the reader or of an extension, after raising the error event.

      A read() requested while another is running supersedes it: the superseded read() rejects with the AbortedException of ts-core at once, raises no error event and leaves items untouched; the new one runs. A consumer awaiting read() ignores the supersession with isAbortedException; a consumer that only listens to items and error has nothing to handle. Note that a read() requested from a handler raised during the cycle (e.g. the page event of a pagination going back to a valid page) supersedes the running read as well.

      Returns Promise<void>

      try {
      await dataSource.read();
      } catch (error) {
      if (!isAbortedException(error)) throw error; // a newer read superseded this one
      }