mirror of
https://github.com/pezkuwichain/pezkuwi-api.git
synced 2026-10-20 18:56:29 +00:00
- @pezkuwi/dev, dev-test, dev-ts ^0.86.3; disable directives renamed for the new rule names, or removed where the rule no longer reports - rethrown errors carry the original as `cause` (13 sites) - optional chains where a guard expression tested the same value - Init: the registry cache settles with the creator's own promise, dropping the entry in finally(), instead of re-wrapping it - Combinator: a typed call instead of `Function` - checkTypes.manual: synchronous checks run outside Promise.all - sortValues, createTypeUnsafe: initial values that were never read - typegen util: load docs.js for its side effect (it registers the `docs` Handlebars partial) now that its types are re-exported type-only - MockProvider keeps its Header assertion: the package's own build does not load api-augment, so createType returns a plain Codec there - Decorate keeps `delete queue[i]`: the hole marks a fired queue and later indexes must not shift
90 lines
2.4 KiB
TypeScript
90 lines
2.4 KiB
TypeScript
// Copyright 2017-2026 @pezkuwi/api authors & contributors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
import type { Callback } from '@pezkuwi/types/types';
|
|
import type { UnsubscribePromise } from '../types/index.js';
|
|
|
|
import { isFunction, noop } from '@pezkuwi/util';
|
|
|
|
export type CombinatorCallback <T extends unknown[]> = Callback<T>;
|
|
|
|
export type CombinatorFunction = (cb: Callback<any>) => UnsubscribePromise;
|
|
|
|
export class Combinator<T extends unknown[] = unknown[]> {
|
|
#allHasFired = false;
|
|
#callback: CombinatorCallback<T>;
|
|
#fired: boolean[] = [];
|
|
#fns: CombinatorFunction[] = [];
|
|
#isActive = true;
|
|
#results: unknown[] = [];
|
|
#subscriptions: UnsubscribePromise[] = [];
|
|
|
|
constructor (fns: (CombinatorFunction | [CombinatorFunction, ...unknown[]])[], callback: CombinatorCallback<T>) {
|
|
this.#callback = callback;
|
|
|
|
this.#subscriptions = fns.map(async (input, index): UnsubscribePromise => {
|
|
const [fn, ...args] = Array.isArray(input)
|
|
? input
|
|
: [input];
|
|
|
|
this.#fired.push(false);
|
|
this.#fns.push(fn);
|
|
|
|
// the leading arguments vary per function, the callback is always last
|
|
return (fn as (...params: unknown[]) => UnsubscribePromise)(...args, this._createCallback(index));
|
|
});
|
|
}
|
|
|
|
protected _allHasFired (): boolean {
|
|
this.#allHasFired ||= this.#fired.filter((hasFired): boolean => !hasFired).length === 0;
|
|
|
|
return this.#allHasFired;
|
|
}
|
|
|
|
protected _createCallback (index: number): (value: any) => void {
|
|
return (value: unknown): void => {
|
|
this.#fired[index] = true;
|
|
this.#results[index] = value;
|
|
|
|
this._triggerUpdate();
|
|
};
|
|
}
|
|
|
|
protected _triggerUpdate (): void {
|
|
if (!this.#isActive || !isFunction(this.#callback) || !this._allHasFired()) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
Promise
|
|
.resolve(this.#callback(this.#results as T))
|
|
.catch(noop);
|
|
} catch {
|
|
// swallow, we don't want the handler to trip us up
|
|
}
|
|
}
|
|
|
|
public unsubscribe (): void {
|
|
if (!this.#isActive) {
|
|
return;
|
|
}
|
|
|
|
this.#isActive = false;
|
|
|
|
Promise
|
|
.all(this.#subscriptions.map(async (subscription): Promise<void> => {
|
|
try {
|
|
const unsubscribe = await subscription;
|
|
|
|
if (isFunction(unsubscribe)) {
|
|
unsubscribe();
|
|
}
|
|
} catch {
|
|
// ignore
|
|
}
|
|
})).catch(() => {
|
|
// ignore, already ignored above, should never throw
|
|
});
|
|
}
|
|
}
|