1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Fix remaining type errors

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2023-03-16 12:01:08 -04:00
parent dbb9a0f5a8
commit 31a3369dd0
22 changed files with 121 additions and 132 deletions

View File

@ -5,13 +5,12 @@
import { action, comparer, computed, observable } from "mobx";
import type { BaseStore } from "../persistent-storage/base-store";
import { Cluster } from "../cluster/cluster";
import { toJS } from "../utils";
import type { ClusterModel, ClusterId } from "../cluster-types";
import type { ReadClusterConfigSync } from "./read-cluster-config.injectable";
import type { EmitAppEvent } from "../app-event-bus/emit-event.injectable";
import type { CreatePersistentStorage } from "../persistent-storage/create.injectable";
import type { CreatePersistentStorage, PersistentStorage } from "../persistent-storage/create.injectable";
import type { Migrations } from "conf/dist/source/types";
import type { Logger } from "../logger";
@ -30,10 +29,10 @@ interface Dependencies {
export class ClusterStore {
readonly clusters = observable.map<ClusterId, Cluster>();
private readonly store: BaseStore<ClusterStoreModel>;
private readonly store: PersistentStorage;
constructor(protected readonly dependencies: Dependencies) {
this.store = this.dependencies.createPersistentStorage({
this.store = this.dependencies.createPersistentStorage<ClusterStoreModel>({
configName: "lens-cluster-store",
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
syncOptions: {
@ -102,6 +101,6 @@ export class ClusterStore {
}
load() {
this.store.load();
this.store.loadAndStartSyncing();
}
}

View File

@ -5,7 +5,6 @@
import type { IObservableValue } from "mobx";
import { runInAction, action, comparer, observable } from "mobx";
import type { BaseStore } from "../persistent-storage/base-store";
import type { CatalogEntity } from "../catalog";
import { broadcastMessage } from "../ipc";
import type { Hotbar, CreateHotbarData, CreateHotbarOptions } from "./types";
@ -16,7 +15,7 @@ import type { Logger } from "../logger";
import assert from "assert";
import { getShortName } from "../catalog/helpers";
import type { Migrations } from "conf/dist/source/types";
import type { CreatePersistentStorage } from "../persistent-storage/create.injectable";
import type { CreatePersistentStorage, PersistentStorage } from "../persistent-storage/create.injectable";
export interface HotbarStoreModel {
hotbars: Hotbar[];
@ -32,14 +31,14 @@ interface Dependencies {
}
export class HotbarStore {
private readonly store: BaseStore<HotbarStoreModel>;
private readonly store: PersistentStorage;
readonly hotbars = observable.array<Hotbar>();
readonly activeHotbarId = observable.box() as IObservableValue<string>;
constructor(protected readonly dependencies: Dependencies) {
this.store = this.dependencies.createPersistentStorage({
this.store = this.dependencies.createPersistentStorage<HotbarStoreModel>({
configName: "lens-hotbar-store",
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
syncOptions: {
@ -96,7 +95,7 @@ export class HotbarStore {
}
load() {
this.store.load();
this.store.loadAndStartSyncing();
}
/**

View File

@ -31,7 +31,6 @@ export interface PersistentStorageParams<T extends object> extends Omit<Options<
equals?: IEqualsComparer<T>;
};
readonly configName: string;
readonly cwd: string;
migrations?: Migrations<Record<string, unknown>>;

View File

@ -10,13 +10,13 @@ import { isDefined, iter } from "@k8slens/utilities";
import { pathToRegexp } from "path-to-regexp";
import type Url from "url-parse";
import { RoutingError, RoutingErrorType } from "./error";
import type { ExtensionsStore } from "../../extensions/extensions-store/extensions-store";
import type { ExtensionLoader } from "../../extensions/extension-loader";
import type { LensExtension } from "../../extensions/lens-extension";
import type { RouteHandler, RouteParams } from "./registration";
import { when } from "mobx";
import { ipcRenderer } from "electron";
import type { Logger } from "../logger";
import type { EnabledExtensionsState } from "../../extensions/enabled-extensions-state.injectable";
// IPC channel for protocol actions. Main broadcasts the open-url events to this channel.
export const ProtocolHandlerIpcPrefix = "protocol-handler";
@ -65,7 +65,7 @@ export function foldAttemptResults(mainAttempt: RouteAttempt, rendererAttempt: R
export interface LensProtocolRouterDependencies {
readonly extensionLoader: ExtensionLoader;
readonly extensionsStore: ExtensionsStore;
readonly enabledExtensionsState: EnabledExtensionsState;
readonly logger: Logger;
}
@ -209,7 +209,7 @@ export abstract class LensProtocolRouter {
return name;
}
if (!this.dependencies.extensionsStore.isEnabled(extension)) {
if (!this.dependencies.enabledExtensionsState.isEnabled(extension)) {
this.dependencies.logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but not enabled`);
return name;

View File

@ -4,7 +4,6 @@
*/
import { action, observable, makeObservable, isObservableArray, isObservableSet, isObservableMap, runInAction } from "mobx";
import type { BaseStore } from "../persistent-storage/base-store";
import { getOrInsertSet, toggle, object } from "@k8slens/utilities";
import type { UserPreferencesModel, StoreType } from "./preferences-helpers";
import type { EmitAppEvent } from "../app-event-bus/emit-event.injectable";
@ -13,7 +12,7 @@ import type { EmitAppEvent } from "../app-event-bus/emit-event.injectable";
import type { SelectedUpdateChannel } from "../../features/application-update/common/selected-update-channel/selected-update-channel.injectable";
import type { ReleaseChannel } from "../../features/application-update/common/update-channels";
import type { PreferenceDescriptors } from "./preference-descriptors.injectable";
import type { CreatePersistentStorage } from "../persistent-storage/create.injectable";
import type { CreatePersistentStorage, PersistentStorage } from "../persistent-storage/create.injectable";
import type { Logger } from "../logger";
import type { Migrations } from "conf/dist/source/types";
import { toJS } from "../utils";
@ -33,10 +32,10 @@ interface Dependencies {
}
export class UserStore {
private readonly store: BaseStore<UserStoreModel>;
private readonly store: PersistentStorage;
constructor(protected readonly dependencies: Dependencies) {
this.store = this.dependencies.createPersistentStorage({
this.store = this.dependencies.createPersistentStorage<UserStoreModel>({
configName: "lens-user-store",
projectVersion: this.dependencies.storeMigrationVersion,
migrations: this.dependencies.migrations,
@ -153,6 +152,6 @@ export class UserStore {
}
load() {
this.store.load();
this.store.loadAndStartSyncing();
}
}

View File

@ -4,9 +4,8 @@
*/
import { action, comparer, observable, runInAction } from "mobx";
import type { BaseStore } from "../persistent-storage/base-store";
import * as uuid from "uuid";
import type { CreatePersistentStorage } from "../persistent-storage/create.injectable";
import type { CreatePersistentStorage, PersistentStorage } from "../persistent-storage/create.injectable";
import type { Migrations } from "conf/dist/source/types";
export interface WeblinkData {
@ -33,12 +32,12 @@ interface Dependencies {
}
export class WeblinkStore {
private readonly store: BaseStore<WeblinkStoreModel>;
private readonly store: PersistentStorage;
readonly weblinks = observable.array<WeblinkData>();
constructor(private readonly dependencies: Dependencies) {
this.store = this.dependencies.createPersistentStorage({
this.store = this.dependencies.createPersistentStorage<WeblinkStoreModel>({
configName: "lens-weblink-store",
accessPropertiesByDotNotation: false, // To make dots safe in cluster context names
syncOptions: {
@ -54,7 +53,7 @@ export class WeblinkStore {
}),
});
this.store.load();
this.store.loadAndStartSyncing();
}
add(data: WeblinkCreateOptions) {

View File

@ -3,11 +3,11 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import extensionsStoreInjectable from "../../extensions-store/extensions-store.injectable";
import enabledExtensionsStateInjectable from "../../enabled-extensions-state.injectable";
const enabledExtensionsInjectable = getInjectable({
id: "enabled-extensions",
instantiate: (di) => di.inject(extensionsStoreInjectable).enabledExtensions,
instantiate: (di) => di.inject(enabledExtensionsStateInjectable).enabledExtensions,
});
export default enabledExtensionsInjectable;

View File

@ -3,10 +3,13 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { BaseStoreParams } from "../../common/persistent-storage/base-store";
import type { PersistentStorageParams } from "../../common/persistent-storage/create.injectable";
import type { ExtensionStoreParams } from "../base-extension-store";
import { BaseExtensionStore as ExtensionStore } from "../base-extension-store";
export {
BaseStoreParams,
ExtensionStore,
export type {
ExtensionStoreParams,
PersistentStorageParams,
};
export { ExtensionStore };

View File

@ -0,0 +1,69 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LensExtensionId } from "@k8slens/legacy-extensions";
import { iter, object } from "@k8slens/utilities";
import { getInjectable } from "@ogre-tools/injectable";
import type { IComputedValue } from "mobx";
import { action, computed, observable } from "mobx";
import createPersistentStorageInjectable from "../common/persistent-storage/create.injectable";
import storeMigrationVersionInjectable from "../common/vars/store-migration-version.injectable";
export interface LensExtensionState {
enabled?: boolean;
name: string;
}
export interface IsEnabledExtensionDescriptor {
id: string;
isBundled: boolean;
}
export interface EnabledExtensionsState {
readonly enabledExtensions: IComputedValue<string[]>;
isEnabled: (desc: IsEnabledExtensionDescriptor) => boolean;
mergeState: (newPartialState: Partial<Record<LensExtensionId, LensExtensionState>> | [LensExtensionId, LensExtensionState][]) => void;
}
const enabledExtensionsStateInjectable = getInjectable({
id: "enabled-extensions-state",
instantiate: (di): EnabledExtensionsState => {
const storeMigrationVersion = di.inject(storeMigrationVersionInjectable);
const createPersistentStorage = di.inject(createPersistentStorageInjectable);
const state = observable.map<LensExtensionId, LensExtensionState>();
const storage = createPersistentStorage({
configName: "lens-extensions",
fromStore: action(({ extensions = {}}) => {
state.merge(extensions);
}),
toJSON: () => ({
extensions: Object.fromEntries(state),
}),
projectVersion: storeMigrationVersion,
});
// NOTE: this is done implicitly here currently
storage.loadAndStartSyncing();
return {
enabledExtensions: computed(() => (
iter.chain(state.values())
.filter(({ enabled }) => enabled)
.map(({ name }) => name)
.toArray()
)),
isEnabled: ({ id, isBundled }) => isBundled || (state.get(id)?.enabled ?? false),
mergeState: action((newPartialState) => {
if (Array.isArray(newPartialState)) {
state.merge(newPartialState);
} else {
state.merge(object.entries(newPartialState));
}
}),
};
},
});
export default enabledExtensionsStateInjectable;

View File

@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import { ExtensionDiscovery } from "./extension-discovery";
import extensionLoaderInjectable from "../extension-loader/extension-loader.injectable";
import isCompatibleExtensionInjectable from "./is-compatible-extension/is-compatible-extension.injectable";
import extensionsStoreInjectable from "../extensions-store/extensions-store.injectable";
import enabledExtensionsStateInjectable from "../enabled-extensions-state.injectable";
import extensionInstallationStateStoreInjectable from "../extension-installation-state-store/extension-installation-state-store.injectable";
import installExtensionInjectable from "../install-extension/install-extension.injectable";
import extensionPackageRootDirectoryInjectable from "../install-extension/extension-package-root-directory.injectable";
@ -34,7 +34,7 @@ const extensionDiscoveryInjectable = getInjectable({
instantiate: (di) => new ExtensionDiscovery({
extensionLoader: di.inject(extensionLoaderInjectable),
extensionsStore: di.inject(extensionsStoreInjectable),
enabledExtensionsState: di.inject(enabledExtensionsStateInjectable),
extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable),
isCompatibleExtension: di.inject(isCompatibleExtensionInjectable),
installExtension: di.inject(installExtensionInjectable),

View File

@ -9,7 +9,6 @@ import { makeObservable, observable, reaction, when } from "mobx";
import { broadcastMessage, ipcMainHandle, ipcRendererOn } from "../../common/ipc";
import { toJS } from "../../common/utils";
import { isErrnoException } from "@k8slens/utilities";
import type { ExtensionsStore } from "../extensions-store/extensions-store";
import type { ExtensionLoader } from "../extension-loader";
import type { InstalledExtension, LensExtensionId, LensExtensionManifest } from "@k8slens/legacy-extensions";
import type { ExtensionInstallationStateStore } from "../extension-installation-state-store/extension-installation-state-store";
@ -31,10 +30,11 @@ import type { GetDirnameOfPath } from "../../common/path/get-dirname.injectable"
import type { GetRelativePath } from "../../common/path/get-relative-path.injectable";
import type { RemovePath } from "../../common/fs/remove.injectable";
import type TypedEventEmitter from "typed-emitter";
import type { EnabledExtensionsState } from "../enabled-extensions-state.injectable";
interface Dependencies {
readonly extensionLoader: ExtensionLoader;
readonly extensionsStore: ExtensionsStore;
readonly enabledExtensionsState: EnabledExtensionsState;
readonly extensionInstallationStateStore: ExtensionInstallationStateStore;
readonly extensionPackageRootDirectory: string;
readonly resourcesDirectory: string;
@ -334,7 +334,7 @@ export class ExtensionDiscovery {
try {
const manifest = await this.dependencies.readJsonFile(manifestPath) as unknown as LensExtensionManifest;
const id = isBundled ? manifestPath : this.getInstalledManifestPath(manifest.name);
const isEnabled = this.dependencies.extensionsStore.isEnabled({ id, isBundled });
const isEnabled = this.dependencies.enabledExtensionsState.isEnabled({ id, isBundled });
const extensionDir = this.dependencies.getDirnameOfPath(manifestPath);
const npmPackage = this.dependencies.joinPaths(extensionDir, `${manifest.name}-${manifest.version}.tgz`);
const absolutePath = this.dependencies.isProduction && await this.dependencies.pathExists(npmPackage)

View File

@ -10,7 +10,6 @@ import { action, computed, makeObservable, toJS, observable, observe, reaction,
import { broadcastMessage, ipcMainOn, ipcRendererOn, ipcMainHandle } from "../../common/ipc";
import { isDefined } from "@k8slens/utilities";
import type { LensExtension } from "../lens-extension";
import type { LensExtensionState } from "../extensions-store/extensions-store";
import { extensionLoaderFromMainChannel, extensionLoaderFromRendererChannel } from "../../common/ipc/extension-handling";
import { requestExtensionLoaderInitialState } from "../../renderer/ipc";
import assert from "assert";
@ -21,6 +20,7 @@ import type { Logger } from "../../common/logger";
import type { JoinPaths } from "../../common/path/join-paths.injectable";
import type { GetDirnameOfPath } from "../../common/path/get-dirname.injectable";
import type { LensExtensionId, BundledExtension, InstalledExtension, LensExtensionConstructor } from "@k8slens/legacy-extensions";
import type { LensExtensionState } from "../enabled-extensions-state.injectable";
const logModule = "[EXTENSIONS-LOADER]";

View File

@ -30,7 +30,7 @@ const fileSystemProvisionerStoreInjectable = getInjectable({
});
return {
load: () => store.load(),
load: () => store.loadAndStartSyncing(),
};
},
});

View File

@ -3,11 +3,11 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import extensionsStoreInjectable from "../../extensions-store/extensions-store.injectable";
import enabledExtensionsStateInjectable from "../../enabled-extensions-state.injectable";
const updateExtensionsStateInjectable = getInjectable({
id: "update-extensions-state",
instantiate: (di) => di.inject(extensionsStoreInjectable).mergeState,
instantiate: (di) => di.inject(enabledExtensionsStateInjectable).mergeState,
});
export default updateExtensionsStateInjectable;

View File

@ -1,18 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import createPersistentStorageInjectable from "../../common/persistent-storage/create.injectable";
import storeMigrationVersionInjectable from "../../common/vars/store-migration-version.injectable";
import { ExtensionsStore } from "./extensions-store";
const extensionsStoreInjectable = getInjectable({
id: "extensions-store",
instantiate: (di) => new ExtensionsStore({
storeMigrationVersion: di.inject(storeMigrationVersionInjectable),
createPersistentStorage: di.inject(createPersistentStorageInjectable),
}),
});
export default extensionsStoreInjectable;

View File

@ -1,64 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { LensExtensionId } from "@k8slens/legacy-extensions";
import { action, computed, observable } from "mobx";
import type { BaseStore } from "../../common/persistent-storage/base-store";
import type { CreatePersistentStorage } from "../../common/persistent-storage/create.injectable";
export interface LensExtensionsStoreModel {
extensions?: Record<LensExtensionId, LensExtensionState>;
}
export interface LensExtensionState {
enabled?: boolean;
name: string;
}
export interface IsEnabledExtensionDescriptor {
id: string;
isBundled: boolean;
}
export interface ExtensionsStoreDependencies {
createPersistentStorage: CreatePersistentStorage;
readonly storeMigrationVersion: string;
}
export class ExtensionsStore {
private readonly store: BaseStore<LensExtensionsStoreModel>;
constructor(private readonly dependencies: ExtensionsStoreDependencies) {
this.store = this.dependencies.createPersistentStorage({
configName: "lens-extensions",
fromStore: action(({ extensions = {}}) => {
this.state.merge(extensions);
}),
toJSON: () => ({
extensions: Object.fromEntries(this.state),
}),
projectVersion: this.dependencies.storeMigrationVersion,
});
this.store.load();
}
readonly enabledExtensions = computed(() => (
Array.from(this.state.values())
.filter(({ enabled }) => enabled)
.map(({ name }) => name)
));
protected readonly state = observable.map<LensExtensionId, LensExtensionState>();
isEnabled({ id, isBundled }: IsEnabledExtensionDescriptor): boolean {
// By default false, so that copied extensions are disabled by default.
// If user installs the extension from the UI, the Extensions component will specifically enable it.
return isBundled || Boolean(this.state.get(id)?.enabled);
}
mergeState = action((extensionsState: Record<LensExtensionId, LensExtensionState> | [LensExtensionId, LensExtensionState][]) => {
this.state.merge(extensionsState);
});
}

View File

@ -7,14 +7,14 @@ import * as uuid from "uuid";
import { ProtocolHandlerExtension, ProtocolHandlerInternal, ProtocolHandlerInvalid } from "../../../common/protocol-handler";
import { delay, noop } from "@k8slens/utilities";
import type { ExtensionsStore, IsEnabledExtensionDescriptor } from "../../../extensions/extensions-store/extensions-store";
import type { LensProtocolRouterMain } from "../lens-protocol-router-main/lens-protocol-router-main";
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
import lensProtocolRouterMainInjectable from "../lens-protocol-router-main/lens-protocol-router-main.injectable";
import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable";
import enabledExtensionsStateInjectable from "../../../extensions/enabled-extensions-state.injectable";
import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable";
import { LensExtension } from "../../../extensions/lens-extension";
import type { ObservableMap } from "mobx";
import { computed } from "mobx";
import extensionInstancesInjectable from "../../../extensions/extension-loader/extension-instances.injectable";
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.injectable";
@ -46,9 +46,11 @@ describe("protocol router tests", () => {
enabledExtensions = new Set();
di.override(extensionsStoreInjectable, () => ({
isEnabled: ({ id, isBundled }: IsEnabledExtensionDescriptor) => isBundled || enabledExtensions.has(id),
} as unknown as ExtensionsStore));
di.override(enabledExtensionsStateInjectable, () => ({
isEnabled: ({ id, isBundled }) => isBundled || enabledExtensions.has(id),
enabledExtensions: computed(() => []),
mergeState: noop,
}));
di.permitSideEffects(getConfigurationFileModelInjectable);

View File

@ -5,7 +5,7 @@
import { getInjectable } from "@ogre-tools/injectable";
import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable";
import { LensProtocolRouterMain } from "./lens-protocol-router-main";
import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable";
import enabledExtensionsStateInjectable from "../../../extensions/enabled-extensions-state.injectable";
import showApplicationWindowInjectable from "../../start-main-application/lens-window/show-application-window.injectable";
import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.injectable";
import loggerInjectable from "../../../common/logger.injectable";
@ -16,7 +16,7 @@ const lensProtocolRouterMainInjectable = getInjectable({
instantiate: (di) =>
new LensProtocolRouterMain({
extensionLoader: di.inject(extensionLoaderInjectable),
extensionsStore: di.inject(extensionsStoreInjectable),
extensionsStore: di.inject(enabledExtensionsStateInjectable),
showApplicationWindow: di.inject(showApplicationWindowInjectable),
broadcastMessage: di.inject(broadcastMessageInjectable),
logger: di.inject(loggerInjectable),

View File

@ -5,7 +5,7 @@
import { getInjectable } from "@ogre-tools/injectable";
import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable";
import { LensProtocolRouterRenderer } from "./lens-protocol-router-renderer";
import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable";
import enabledExtensionsStateInjectable from "../../../extensions/enabled-extensions-state.injectable";
import loggerInjectable from "../../../common/logger.injectable";
import showErrorNotificationInjectable from "../../components/notifications/show-error-notification.injectable";
import showShortInfoNotificationInjectable from "../../components/notifications/show-short-info.injectable";
@ -15,7 +15,7 @@ const lensProtocolRouterRendererInjectable = getInjectable({
instantiate: (di) => new LensProtocolRouterRenderer({
extensionLoader: di.inject(extensionLoaderInjectable),
extensionsStore: di.inject(extensionsStoreInjectable),
extensionsStore: di.inject(enabledExtensionsStateInjectable),
logger: di.inject(loggerInjectable),
showErrorNotification: di.inject(showErrorNotificationInjectable),
showShortInfoNotification: di.inject(showShortInfoNotificationInjectable),

View File

@ -10,7 +10,7 @@ const enlistMessageChannelListenerInjectable = getInjectable({
const ipcMain = di.inject(ipcMainInjectable);
return ({ channel, handler }) => {
const nativeOnCallback = (nativeEvent: IpcMainEvent, message: unknown) => {
const nativeOnCallback = (nativeEvent: IpcMainEvent, message: any) => {
handler(message, { frameId: nativeEvent.frameId, processId: nativeEvent.processId });
};

View File

@ -10,7 +10,7 @@ const enlistMessageChannelListenerInjectable = getInjectable({
const ipcRenderer = di.inject(ipcRendererInjectable);
return ({ channel, handler }) => {
const nativeCallback = (event: IpcRendererEvent, message: unknown) => {
const nativeCallback = (_: IpcRendererEvent, message: any) => {
handler(message);
};

View File

@ -10,6 +10,7 @@ interface Iterator<T> extends Iterable<T> {
filterMap<U>(fn: (val: T) => Falsy | U): Iterator<U>;
find(fn: (val: T) => unknown): T | undefined;
collect<U>(fn: (values: Iterable<T>) => U): U;
toArray(): T[];
map<U>(fn: (val: T) => U): Iterator<U>;
flatMap<U>(fn: (val: T) => U[]): Iterator<U>;
concat(src2: IterableIterator<T>): Iterator<T>;
@ -26,6 +27,7 @@ function chain<T>(src: IterableIterator<T>): Iterator<T> {
find: (fn) => find(src, fn),
join: (sep) => join(src, sep),
collect: (fn) => fn(src),
toArray: () => [...src],
concat: (src2) => chain(concat(src, src2)),
take: (count) => chain(take(src, count)),
[Symbol.iterator]: () => src,