feat(lib/modules/finders/filters)!: improve filters

- Filter generators now expose a `keyFor` method to generate filter keys.
- Dependency filtering is now more usable
  - Destructure helpers from `byDependencies` instead:
    - `looseDeps` -> `byDependencies.loose`
    - `relativeDep` -> `byDependencies.relative`
  - Nested dependencies check can now be done with relative checks via `byDependencies.relative.withDependencies`
- Dependency filtering is now more performant
  - Relative dependencies are done by bit manipulation instead
  - Reverted from stack system to recursion
  - Skip relative dependencies resolution (there is no need for it)
- Filter keys for `byDependencies` fixed
This commit is contained in:
PalmDevs 2025-06-13 23:35:23 +07:00
parent 63b7c6c648
commit d5fa40fabd
No known key found for this signature in database
GPG key ID: 6FAE8F8677ACA8BA
11 changed files with 256 additions and 198 deletions

View file

@ -3,7 +3,6 @@ import {
byProps,
every,
preferExports,
relativeDep,
withoutProps,
} from '@revenge-mod/modules/finders/filters'
import {
@ -19,6 +18,8 @@ import { proxify } from '@revenge-mod/utils/proxy'
import { DispatcherModuleId } from './common/flux'
import type { DiscordModules } from './types'
const { relative } = byDependencies
export let ActionSheetActionCreators: DiscordModules.Actions.ActionSheetActionCreators =
proxify(
() => {
@ -33,8 +34,8 @@ export let ActionSheetActionCreators: DiscordModules.Actions.ActionSheetActionCr
ReactModuleId,
ReactJsxRuntimeModuleId,
DispatcherModuleId,
relativeDep(1),
relativeDep(2),
relative(1),
relative(2),
2,
]),
),
@ -59,7 +60,7 @@ export let AlertActionCreators: DiscordModules.Actions.AlertActionCreators =
'openAlert',
),
byDependencies([
[[], relativeDep(1)],
[[], relative(1)],
[ReactNativeModuleId, 2],
2,
]),

View file

@ -3,7 +3,6 @@ import {
byProps,
createFilterGenerator,
preferExports,
relativeDep,
} from '@revenge-mod/modules/finders/filters'
import { lookupModule } from '@revenge-mod/modules/finders/lookup'
import { waitForModules } from '@revenge-mod/modules/finders/wait'
@ -12,13 +11,15 @@ import type { Filter } from '@revenge-mod/modules/finders/filters'
import type { Metro } from '@revenge-mod/modules/types'
import type { DiscordModules } from '../types'
const { relative } = byDependencies
// ../discord_common/js/packages/flux
export const [Dispatcher, DispatcherModuleId] = lookupModule(
preferExports(
byProps<DiscordModules.Flux.Dispatcher>('_interceptors'),
byDependencies([
relativeDep(1),
relative(1),
undefined,
undefined,
undefined,

View file

@ -2,10 +2,7 @@ import {
byDependencies,
byName,
byProps,
every,
looseDeps,
preferExports,
relativeDep,
} from '@revenge-mod/modules/finders/filters'
import { lookupModule } from '@revenge-mod/modules/finders/lookup'
import { proxify } from '@revenge-mod/utils/proxy'
@ -15,6 +12,8 @@ import type { DiscordModules } from '../types'
export { AppStartPerformance } from '../preinit'
export * as Flux from './flux'
const { loose, relative } = byDependencies
// ../discord_common/js/shared/Logger.tsx
export const [Logger] = lookupModule(
byName<DiscordModules.Logger>('Logger'),
@ -33,22 +32,18 @@ export let Constants: DiscordModules.Constants = proxify(
const [module] = lookupModule(
preferExports(
byProps<DiscordModules.Constants>('ME'),
every(
// ID: 1236
// Deps: 26, 1237, 1238, 1239, 1240, ...
// Every module has only one dependency, which is the import tracker
byDependencies(
looseDeps([
undefined,
relativeDep(1),
relativeDep(2),
relativeDep(3),
relativeDep(4),
]),
),
byDependencies(
looseDeps(new Array(5).fill(OnlyImportTrackerDep)),
),
// ID: 1236
// Deps: 26, 1237, 1238, 1239, 1240, ...
// Every module has only one dependency, which is the import tracker
byDependencies(
loose([
undefined,
relative.withDependencies(OnlyImportTrackerDep, 1),
relative.withDependencies(OnlyImportTrackerDep, 2),
relative.withDependencies(OnlyImportTrackerDep, 3),
relative.withDependencies(OnlyImportTrackerDep, 4),
]),
),
),
)

View file

@ -2,9 +2,7 @@ import {
byDependencies,
byProps,
bySingleProp,
looseDeps,
preferExports,
relativeDep,
} from '@revenge-mod/modules/finders/filters'
import { lookupModule } from '@revenge-mod/modules/finders/lookup'
import {
@ -15,6 +13,8 @@ import {
import { proxify } from '@revenge-mod/utils/proxy'
import type { DiscordModules } from './types'
const { loose, relative } = byDependencies
// design/native.tsx
export let Design: Design = proxify(
() => {
@ -24,11 +24,11 @@ export let Design: Design = proxify(
preferExports(
byProps<Design>('TableRow', 'Button'),
byDependencies(
looseDeps([
relativeDep(1),
loose([
relative(1),
undefined,
relativeDep(2),
relativeDep(3),
relative(2),
relative(3),
2,
]),
),
@ -58,7 +58,7 @@ export let FormSwitch: DiscordModules.Components.FormSwitch = proxify(() => {
'FormSwitch',
),
byDependencies([
[relativeDep(1), relativeDep(2), undefined, relativeDep(3)],
[relative(1), relative(2), undefined, relative(3)],
ReactModuleId,
ReactNativeModuleId,
ReactJsxRuntimeModuleId,
@ -69,8 +69,8 @@ export let FormSwitch: DiscordModules.Components.FormSwitch = proxify(() => {
undefined,
undefined,
undefined,
relativeDep(1),
relativeDep(2),
relative(1),
relative(2),
2,
]),
),

View file

@ -1,15 +1,14 @@
import {
byDependencies,
byProps,
every,
looseDeps,
preferExports,
relativeDep,
} from '@revenge-mod/modules/finders/filters'
import { lookupModule } from '@revenge-mod/modules/finders/lookup'
import { proxify } from '@revenge-mod/utils/proxy'
import type { NavigationContainerRef } from '@react-navigation/core'
const { loose, relative } = byDependencies
export interface RootNavigationRef {
getRootNavigationRef<
T extends object = Record<string, unknown>,
@ -21,15 +20,10 @@ export let RootNavigationRef: RootNavigationRef = proxify(
const [module] = lookupModule(
preferExports(
byProps<RootNavigationRef>('getRootNavigationRef'),
every(
byDependencies([
looseDeps([
[],
looseDeps([relativeDep(1), relativeDep(2)]),
]),
2,
]),
),
byDependencies([
loose([[], loose([relative(1), relative(2)])]),
2,
]),
),
{
includeUninitialized: true,

View file

@ -1,9 +1,7 @@
import {
byDependencies,
byProps,
looseDeps,
preferExports,
relativeDep,
} from '@revenge-mod/modules/finders/filters'
import { lookupModule } from '@revenge-mod/modules/finders/lookup'
import {
@ -14,6 +12,8 @@ import {
import { proxify } from '@revenge-mod/utils/proxy'
import type { DiscordModules } from '../../types'
const { loose, relative } = byDependencies
export type SettingListRenderer =
DiscordModules.Modules.Settings.SettingListRenderer
@ -23,11 +23,11 @@ export let SettingListRenderer: SettingListRenderer = proxify(
preferExports(
byProps<SettingListRenderer>('SettingsList'),
byDependencies(
looseDeps([
loose([
ReactModuleId,
ReactNativeModuleId,
relativeDep(1),
relativeDep(2),
relative(1),
relative(2),
undefined,
ReactJsxRuntimeModuleId,
]),

View file

@ -2,17 +2,18 @@ import {
byDependencies,
byProps,
preferExports,
relativeDep,
} from '@revenge-mod/modules/finders/filters'
import { lookupModule } from '@revenge-mod/modules/finders/lookup'
import type { Metro } from '@revenge-mod/modules/types'
import type { DiscordModules } from './types'
const { relative } = byDependencies
// ../discord_common/js/shared/AppStartPerformance.tsx
export const [AppStartPerformance] = lookupModule(
preferExports(
byProps<DiscordModules.AppStartPerformance>('markAndLog'),
byDependencies([relativeDep(-1), undefined, undefined, 2]),
byDependencies([relative(-1), undefined, undefined, 2]),
),
{
includeUninitialized: true,

View file

@ -1,20 +1,20 @@
import {
byDependencies,
byProps,
looseDeps,
preferExports,
relativeDep,
} from '@revenge-mod/modules/finders/filters'
import { lookupModule } from '@revenge-mod/modules/finders/lookup'
import { proxify } from '@revenge-mod/utils/proxy'
const { loose, relative } = byDependencies
export let nodeUtil: typeof import('node:util') = proxify(
() => {
const [module] = lookupModule(
preferExports(
byProps<typeof nodeUtil>('inspect'),
byDependencies([
looseDeps([relativeDep(2, true), relativeDep(4, true)]),
loose([relative(2, true), relative(4, true)]),
[],
[],
]),

View file

@ -1,14 +1,14 @@
import {
byDependencies,
byProps,
every,
preferExports,
relativeDep,
} from '@revenge-mod/modules/finders/filters'
import { lookupModule } from '@revenge-mod/modules/finders/lookup'
import { ReactModuleId, ReactNativeModuleId } from '@revenge-mod/react'
import { destructure, proxify } from '@revenge-mod/utils/proxy'
const { relative } = byDependencies
let ClipboardModule: typeof import('@react-native-clipboard/clipboard') =
proxify(
() => {
@ -26,13 +26,16 @@ let ClipboardModule: typeof import('@react-native-clipboard/clipboard') =
const [module] = lookupModule(
preferExports(
byProps<typeof ClipboardModule>('useClipboard'),
every(
byDependencies([relativeDep(1), relativeDep(2)]),
byDependencies([
[ReactModuleId, relativeDep(2, true)],
[ReactNativeModuleId, relativeDep(3, true)],
]),
),
byDependencies([
relative.withDependencies(
[ReactModuleId, relative(2, true)],
1,
),
relative.withDependencies(
[ReactNativeModuleId, relative(3, true)],
2,
),
]),
),
{
includeUninitialized: true,

View file

@ -2,15 +2,14 @@ import {
byDependencies,
byName,
byProps,
every,
looseDeps,
preferExports,
relativeDep,
} from '@revenge-mod/modules/finders/filters'
import { lookupModule } from '@revenge-mod/modules/finders/lookup'
import { ReactJsxRuntimeModuleId, ReactModuleId } from '@revenge-mod/react'
import { proxify } from '@revenge-mod/utils/proxy'
const { loose, relative } = byDependencies
const [, _createClassModuleId] = lookupModule(byName('_createClass'))
const [, _classCallCheckModuleId] = lookupModule(byName('_classCallCheck'))
@ -21,9 +20,9 @@ export let ReactNavigationNative: typeof import('@react-navigation/native') =
preferExports(
byProps<typeof ReactNavigationNative>('useLinkTo'),
byDependencies(
looseDeps([
loose([
[],
looseDeps([
loose([
[_createClassModuleId, _classCallCheckModuleId],
]),
]),
@ -49,23 +48,19 @@ export let ReactNavigationStack: typeof import('@react-navigation/stack') =
byProps<typeof ReactNavigationStack>(
'createStackNavigator',
),
every(
byDependencies(
looseDeps([
looseDeps([
byDependencies(
loose([
relative.withDependencies(
loose([
[[]],
ReactModuleId,
ReactJsxRuntimeModuleId,
]),
]),
),
byDependencies(
looseDeps([
relativeDep(1),
undefined,
relativeDep(3),
]),
),
1,
),
undefined,
relative(3),
]),
),
),
{

View file

@ -27,6 +27,12 @@ export interface Filter<
key: string
}
// @ts-expect-error: This works perfectly fine!
export interface FilterGenerator<G extends (...args: any[]) => Filter>
extends G {
keyFor: (args: Parameters<G>) => string
}
/**
* Create a filter generator.
*
@ -54,29 +60,33 @@ export function createFilterGenerator<A extends any[]>(
exports: Metro.ModuleExports,
) => boolean,
keyFor: (args: A) => string,
): (...args: A) => Filter<object, true>
): FilterGenerator<(...args: A) => Filter<any, true>>
export function createFilterGenerator<A extends any[]>(
filter: (args: A, id: Metro.ModuleID) => boolean,
keyFor: (args: A) => string,
): (...args: A) => Filter<object, false>
): FilterGenerator<(...args: A) => Filter<any, false>>
export function createFilterGenerator<A extends any[]>(
f: (args: A, id: Metro.ModuleID, exports?: Metro.ModuleExports) => boolean,
keyFor: (args: A) => string,
): (...args: any[]) => Filter {
return (...args: A) => {
): FilterGenerator<(...args: A) => Filter> {
const generator = (...args: A) => {
const filter = (id: Metro.ModuleID, exports?: Metro.ModuleExports) =>
f(args, id, exports)
filter.key = keyFor(args)
return filter
}
generator.keyFor = keyFor
return generator
}
export type ByProps = <T extends Record<string, any> = Record<string, any>>(
prop: keyof T,
...props: Array<keyof T>
) => Filter<T, true>
export type ByProps = FilterGenerator<
<T extends Record<string, any> = Record<string, any>>(
prop: keyof T,
...props: Array<keyof T>
) => Filter<T, true>
>
/**
* Filter modules by their exports having all of the specified properties.
@ -106,10 +116,12 @@ export const byProps = createFilterGenerator<Parameters<ByProps>>(
props => `revenge.props(${props.join(',')})`,
) as ByProps
export type WithoutProps = <T extends Record<string, any>>(
prop: string,
...props: string[]
) => Filter<T, true>
export type WithoutProps = FilterGenerator<
<T extends Record<string, any>>(
prop: string,
...props: string[]
) => Filter<T, true>
>
/**
* Filter modules by their exports having none of the specified properties.
@ -127,9 +139,9 @@ export const withoutProps = createFilterGenerator<Parameters<WithoutProps>>(
props => `revenge.withoutProps(${props.join(',')})`,
) as WithoutProps
export type BySingleProp = <T extends Record<string, any>>(
prop: keyof T,
) => Filter<T, true>
export type BySingleProp = FilterGenerator<
<T extends Record<string, any>>(prop: keyof T) => Filter<T, true>
>
/**
* Filter modules by their exports having only the specified property.
@ -151,9 +163,9 @@ export const bySingleProp = createFilterGenerator<Parameters<BySingleProp>>(
([prop]) => `revenge.singleProp(${prop})`,
) as BySingleProp
export type ByName = <T extends object = object>(
name: string,
) => Filter<T, true>
export type ByName = FilterGenerator<
<T extends object = object>(name: string) => Filter<T, true>
>
/**
* Filter modules by their exports having the specified name.
@ -190,57 +202,63 @@ export const byName = createFilterGenerator<Parameters<ByName>>(
([name]) => `revenge.name(${name})`,
) as ByName
export interface RelativeDependency {
valueOf(): Metro.ModuleID
r?: boolean
}
export interface ComparableDependencyMap
extends Array<
| Metro.ModuleID
| RelativeDependency
| undefined
| ComparableDependencyMap
Metro.ModuleID | number | undefined | ComparableDependencyMap
> {
l?: boolean
r?: number
}
type ComputedComparableDependencyMap = Exclude<
ComparableDependencyMap,
Array<RelativeDependency>
>
type ByDependencies = <T>(deps: ComparableDependencyMap) => Filter<T, false>
type ByDependencies = FilterGenerator<
<T>(deps: ComparableDependencyMap) => Filter<T, false>
> & {
loose: typeof loose
relative: typeof relative
}
/**
* Filter modules by their dependency map.
*
* @param deps The dependency map to check for, can be a sparse array or have `undefined` to be any dependency ("dynamic"). **Order and size matters!**
* @see {@link looseDeps} to make comparisons less strict.
*
* To do proper fingerprinting for modules:
* @see {@link byDependencies.loose} to loosen the checks.
* @see {@link byDependencies.relative} to compare dependencies relatively.
*
* @example
* ```ts
* const { loose, relative } = byDependencies
*
* // Logger's module ID is 5
* // It has 3 dependencies [4, ?, 2]
*
* const [Logger] = lookupModule(byDependencies([4, undefined, 2]))
* // or
* const [Logger] = lookupModule(byDependencies([4, , 2]))
* // or with relative dependencies
* // If Logger's module ID is 20, [19, ..., ..., 2] would match:
* const [Logger] = lookupModule(byDependencies([relativeDep(-1), , 2]))
* // or with nested dependencies
* // The last dependency would need to have zero dependencies:
* const [Logger] = lookupModule(byDependencies([4, , []]))
* // or with loose dependencies
* // Module having 4 dependencies [4, ...], [4, ..., ...], [4, ..., ..., ...] etc. would match:
* const [SomeOtherModule] = lookupModule(byDependencies(looseDeps([4, ,])))
*
* // Relative dependencies
* const [Logger] = lookupModule(byDependencies([relative(-1), undefined, 2]))
*
* // Nested dependencies
* // The last dependency (module ID 2) would need to have zero dependencies:
* const [Logger] = lookupModule(byDependencies([4, undefined, []]))
*
* // Loose dependencies
* // Module having these dependencies: [4, ...], [4, ..., ...], [4, ..., ..., ...], etc. would match:
* const [SomeOtherModule] = lookupModule(byDependencies(loose([4])))
* ```
*/
export const byDependencies = createFilterGenerator<Parameters<ByDependencies>>(
([deps], id) => compareDeps(id, deps, deps.l),
deps => `revenge.deps(${genDepsKey(deps)})`,
([deps], id) => depCompare(getModuleDependencies(id)!, deps, id, id),
deps => `revenge.deps(${depGenFilterKey(deps)})`,
) as ByDependencies
byDependencies.loose = loose
byDependencies.relative = relative
/**
* Make this set of comparable dependencies as loose, to be used with {@link byDependencies}.
* Make this set of comparable dependencies as loose.
*
* Making a dependency loose skips the exact length check, but the order of the set dependencies still matters.
* If you mark an index as dynamic, the same index must also be present in the other map during comparison to pass.
@ -248,93 +266,139 @@ export const byDependencies = createFilterGenerator<Parameters<ByDependencies>>(
* @param deps The dependency map to make loose. This permanently modifies the array.
* @returns The modified dependency map.
*/
export function looseDeps(deps: ComparableDependencyMap) {
function loose(deps: ComparableDependencyMap) {
deps.l = true
return deps
}
const RelativeSignBit = 1 << 30
const RelativeBit = 1 << 29
const RelativeRootBit = 1 << 28
const RelativeBitMask = ~(RelativeSignBit | RelativeBit | RelativeRootBit)
/**
* Marks this dependency to compare relatively to the module ID being compared.
*
* @param id The dependency ID to mark as relative.
* @param root Whether this dependency should be relatively to the root module ID. Useless for single-depth comparisons.
* @param root Marks this dependency to compare relatively to the root (returning) module ID being compared. Useful for nested comparisons where you want to compare by the root module ID instead of the parent's module ID of the nested dependency.
*/
export function relativeDep(
id: Metro.ModuleID,
root?: boolean,
): RelativeDependency {
const o = Object(id) as RelativeDependency
if (root) o.r = true
return o
function relative(id: Metro.ModuleID, root?: boolean) {
id = (id < 0 ? -id | RelativeSignBit : id) | RelativeBit
if (root) id |= RelativeRootBit
return id
}
function compareDeps(
rootOf: Metro.ModuleID,
compare: ComparableDependencyMap,
loose = false,
/**
* Marks this dependency to compare relatively to the module ID being compared, with an additional dependencies check.
*
* @param deps The dependency map to add the relative dependency to. This permanently modifies the array.
* @param id The dependency ID to mark as relative.
* @param root Whether to use {@link relative.toRoot} instead of {@link relative}. Defaults to `false`.
* @returns The modified dependency map.
*
* @see {@link byDependencies}
* @see {@link relative}
* @see {@link relative.toRoot}
*
* @example
* ```ts
* const { relative } = byDependencies
*
* // This filter will match modules having one dependency that is its module ID + 1
* // And module ID + 1 would have exactly two dependencies: [Any, 2]
* byDependencies(
* relative.withDependencies(
* [undefined, 2],
* 1, // Always the next module to the one being compared
* true, // The module ID being compared matches the returning (root) module ID
* )
* )
* ```
*/
relative.withDependencies = (
deps: ComparableDependencyMap,
id: Metro.ModuleID,
root?: boolean,
) => {
deps.r = relative(id, root)
return deps
}
function depCompare(
a: Metro.ModuleID[],
b: ComparableDependencyMap,
root: Metro.ModuleID,
parent: Metro.ModuleID,
): boolean {
const stack: Array<
[
of: Metro.ModuleID,
deps: Metro.ModuleID[],
compare: ComparableDependencyMap,
loose?: boolean,
]
> = []
if (b.l ? a.length < b.length : a.length !== b.length) return false
stack.push([rootOf, getModuleDependencies(rootOf)!, compare, loose])
for (let i = 0; i < b.length; i++) {
const compare = b[i]
// Skip dynamic
if (compare === undefined) continue
while (stack.length) {
const [of, deps, compare, loose] = stack.pop()!
if (
loose
? deps.length < compare.length
: deps.length !== compare.length
)
return false
const id = a[i]
const ccmp: ComputedComparableDependencyMap = []
for (let i = 0; i < compare.length; i++) {
const cmp = compare[i]
if (cmp === undefined) continue
if (Array.isArray(compare)) {
// relative.withDependencies?
if (compare.r && !depShallowCompare(compare.r, id, root, id))
return false
if (Array.isArray(cmp)) ccmp[i] = cmp
else if (typeof cmp === 'object')
ccmp[i] = cmp.r ? rootOf + cmp.valueOf() : of + cmp.valueOf()
else ccmp[i] = cmp
}
if (depCompare(getModuleDependencies(id)!, compare, root, id))
continue
} else if (depShallowCompare(compare, id, root, parent)) continue
for (let i = 0; i < ccmp.length; i++) {
const orig = deps[i]
const cmp = ccmp[i]
if (cmp === undefined) continue
if (Array.isArray(cmp)) {
const dependencies = getModuleDependencies(orig)
if (!dependencies) return false
stack.push([orig, dependencies, cmp, cmp.l])
} else if (cmp !== orig) return false
}
return false
}
return true
}
function genDepsKey(deps: ComparableDependencyMap): string {
function depShallowCompare(
compare: number,
id: Metro.ModuleID,
root: Metro.ModuleID,
parent: Metro.ModuleID,
) {
// relative?
if (compare & RelativeBit)
compare =
(compare & RelativeRootBit ? root : parent) +
depGetRelMagnitude(compare)
return compare === id
}
function depGetRelMagnitude(dep: number) {
const rootRelative = dep & RelativeSignBit
dep = dep & RelativeBitMask
if (rootRelative) dep = -dep
return dep
}
function depGenFilterKey(deps: ComparableDependencyMap): string {
let key = ''
for (const dep of deps)
for (let i = 0; i < deps.length; i++) {
const dep = deps[i]
if (dep === undefined) key += ','
else if (Array.isArray(dep)) {
if (dep.l) key += '#'
key += `[${genDepsKey(dep)}],`
} else
key += `${(dep as RelativeDependency).r ? `r${dep.valueOf()}` : dep.valueOf()},`
key += `[${depGenFilterKey(dep)}],`
} else {
if (dep & RelativeBit) {
const magnitude = depGetRelMagnitude(dep)
const prefix = dep & RelativeRootBit ? '~' : '^'
key += `${prefix}${magnitude},`
} else key += `${dep},`
}
}
return key.substring(0, key.length - 1)
}
export type Every = {
export type Every = FilterGenerator<{
<F1 extends Filter, F2 extends Filter>(
f1: F1,
f2: F2,
@ -354,7 +418,7 @@ export type Every = {
>
>
(...filters: Filter[]): Filter
}
}>
/**
* Combines multiple filters into one, returning true if **every** filter matches.
@ -382,7 +446,7 @@ export const every = createFilterGenerator<[...filters: Filter[]]>(
filters => `revenge.every(${filtersToKey(filters)})`,
) as Every
export type Some = {
export type Some = FilterGenerator<{
<F1 extends Filter, F2 extends Filter>(
f1: F1,
f2: F2,
@ -401,7 +465,7 @@ export type Some = {
| IsFilterWithExports<F3>
>
(...filters: Filter[]): Filter
}
}>
/**
* Combines multiple filters into one, returning true if **some** filters match.
@ -431,11 +495,13 @@ function filtersToKey(filters: Filter[]): string {
return s.substring(0, s.length - 1)
}
export type ModuleStateAware = <IF extends Filter>(
initializedFilter: IF,
uninitializedFilter: Filter<any, false>,
strict?: boolean,
) => Filter<FilterResult<IF>, false>
export type ModuleStateAware = FilterGenerator<
<IF extends Filter>(
initializedFilter: IF,
uninitializedFilter: Filter<any, false>,
strict?: boolean,
) => Filter<FilterResult<IF>, false>
>
/**
* Filter modules depending on their initialized state. **Initialized modules with bad exports are skipped.**
@ -470,11 +536,13 @@ export const moduleStateAware = createFilterGenerator<
([f1, f2]) => `revenge.moduleStateAware(${f1.key},${f2.key})`,
) as ModuleStateAware
export type PreferExports = <WEF extends Filter>(
withExportsFilter: WEF,
exportslessFilter: Filter<any, false>,
strict?: boolean,
) => Filter<FilterResult<WEF>, false>
export type PreferExports = FilterGenerator<
<WEF extends Filter>(
withExportsFilter: WEF,
exportslessFilter: Filter<any, false>,
strict?: boolean,
) => Filter<FilterResult<WEF>, false>
>
/**
* Filter modules depending on if their exports are available and filterable.