fix(lib/modules/finders)!: remove find* apis

- `find` APIs are merely a wrapper for `wait` and `lookup` APIs. I think the developer should already know whether the module they're trying to get is initialized already or not by the time their plugin runs.
In the `init` phase, most important packages such as React, React Native, React JSX Runtime, Native modules, etc. are already initialized. So, the `lookup` APIs can be used here instead.

- While I am not sure what exactly gets initialized after `init` until `start`, you can simply do trial and error to see if the module is initialized or not. If it is not, you can use `wait` API to wait for it to be initialized.

- `lookup` can also be used inside `wait` if you know a certain module would be initialized after another module.

With all the above reasons, I think the `find*` APIs are redundant and should be removed.
This commit is contained in:
PalmDevs 2025-05-13 10:29:25 +07:00
parent 4f3621ecd4
commit 273294df82
No known key found for this signature in database
GPG key ID: 6FAE8F8677ACA8BA
3 changed files with 1 additions and 306 deletions

View file

@ -6,9 +6,6 @@
"./finders/filters": {
"default": "./src/finders/filters.ts"
},
"./finders/find": {
"default": "./src/finders/find.ts"
},
"./finders/lookup": {
"default": "./src/finders/lookup.ts"
},

View file

@ -1,300 +0,0 @@
import { type ProxifyOptions, proxify } from '@revenge-mod/utils/proxy'
import { type LookupModulesOptions, lookupModule, lookupModuleByImportedPath, lookupModuleId } from './lookup'
import { type WaitForModulesOptions, waitForModuleByImportedPath, waitForModules } from './wait'
import type { If } from '@revenge-mod/utils/types'
import type { MaybeDefaultExportMatched } from '../types'
import type { Metro } from '../types'
import type { RunFilterReturnExportsOptions } from './_internal'
import type { Filter, FilterResult } from './filters'
import type { LookupModuleIdsOptions } from './lookup'
export interface BaseFindModuleOptions {
/**
* Abort signal for the find.
*/
abortSignal?: AbortSignal
// TODO(modules/finders/find::options): Add an option to force initialize when module ID is known (find is cached).
}
export type FindModuleOptions<
ReturnNamespace extends boolean = boolean,
IncludeUninitialized extends boolean = boolean,
> = WaitForModulesOptions<ReturnNamespace> &
LookupModulesOptions<ReturnNamespace, IncludeUninitialized> &
BaseFindModuleOptions
export type FindModuleIdOptions<
IncludeUninitialized extends boolean = boolean,
IncludeAll extends If<IncludeUninitialized, false, boolean> = If<IncludeUninitialized, false, boolean>,
> = LookupModuleIdsOptions<IncludeUninitialized, IncludeAll> & BaseFindModuleOptions
export type FindModuleResult<
F extends Filter,
O extends FindModuleOptions,
> = O extends RunFilterReturnExportsOptions<true> ? MaybeDefaultExportMatched<FilterResult<F>> : FilterResult<F>
/**
* Find a module.
*
* @see {@link lookupModule}
* @see {@link waitForModules}
*
* @param filter The filter to use to find the module.
* @param options The options to use for the find.
* @returns A promise that resolves to the module's exports or rejects if the find is aborted before a module is found.
*
* @example
* ```ts
* // Resolves after 1 event loop, because the module is already initialized
* const React = await findModule(byProps<typeof import('react')>('createElement'))
*
* // Resolves when the module is initialized
* const FlashList = await findModule(byProps<typeof import('@shopify/flash-list')>('FlashList'))
* ```
*/
export function findModule<F extends Filter>(filter: F): Promise<FilterResult<F>>
export function findModule<
F extends O extends FindModuleOptions<boolean, true> ? Filter<any, false> : Filter,
O extends FindModuleOptions,
>(filter: F, options: O): Promise<FindModuleResult<F, O>>
export function findModule(filter: Filter, options?: FindModuleOptions) {
return new Promise((ok, err) => {
const exports = lookupModule(filter, options!)
if (exports != null) return ok(exports)
const unsub = waitForModules(
filter,
(_, exports) => {
unsub()
ok(exports)
},
options!,
)
const onAbort = () => {
unsub()
err(new Error(`${findModule.name} aborted before resolved: ${filter.key}`))
}
const signal = options?.abortSignal
if (signal) {
if (signal.aborted) {
onAbort()
return
}
signal.addEventListener('abort', onAbort, { once: true })
}
})
}
/**
* Find a module ID.
*
* @see {@link lookupModuleId}
* @see {@link waitForModules}
*
* @param filter The filter to use to find the module.
* @param options The options to use for the find.
* @returns A promise that resolves to the module ID or rejects if the find is aborted before a module ID is found.
*
* @example
* ```ts
* // Resolves after 1 event loop, because the module is already initialized
* const ReactId = await findModuleId(byProps('createElement'))
* const React = __r(ReactId)
*
* // Resolves when the module is initialized
* const FlashListId = await findModuleId(byProps('FlashList'))
* const FlashListModule = __r(FlashListId)
* ```
*/
export function findModuleId<O extends FindModuleIdOptions>(
filter: O extends FindModuleIdOptions<true> ? Filter<any, false> : Filter,
options?: O,
): Promise<Metro.ModuleID> {
return new Promise((ok, err) => {
const id = lookupModuleId(filter, options)
if (id != null) {
ok(id)
return
}
const unsub = waitForModules(
filter,
id => {
unsub()
ok(id)
},
options!,
)
const onAbort = () => {
unsub()
err(new Error(`${findModuleId.name} aborted before resolved: ${filter.key}`))
}
const signal = options?.abortSignal
if (signal) {
if (signal.aborted) {
onAbort()
return
}
signal.addEventListener('abort', onAbort, { once: true })
}
})
}
/**
* Find a module by its imported path.
*
* @param path The path to find the module by.
* @param options The options to use for the find.
* @returns A promise that resolves to the module's exports or rejects if the find is aborted before the module is found.
*
* @example
* ```ts
* const SettingsConstants = await findModuleByImportedPath('modules/main_tabs_v2/native/settings/SettingsConstants.tsx')
* console.log('Settings page opened') // Logs once the module is initialized
* ```
*/
export function findModuleByImportedPath<T>(path: string, options?: BaseFindModuleOptions): Promise<T> {
return new Promise((ok, err) => {
const exports = lookupModuleByImportedPath(path)
if (exports != null) {
ok(exports)
return
}
const unsub = waitForModuleByImportedPath(path, (_, exports) => {
unsub()
ok(exports)
})
const onAbort = () => {
unsub()
err(new Error(`${findModuleByImportedPath.name} aborted before resolved: ${path}`))
}
const signal = options?.abortSignal
if (signal) {
if (signal.aborted) {
onAbort()
return
}
signal.addEventListener('abort', onAbort, { once: true })
}
})
}
export interface BaseFindModuleSyncOptions extends BaseFindModuleOptions, ProxifyOptions {}
export type FindModuleSyncOptions<
ReturnNamespace extends boolean = boolean,
IncludeUninitialized extends boolean = boolean,
> = FindModuleOptions<ReturnNamespace, IncludeUninitialized> & BaseFindModuleSyncOptions
/**
* Find a module synchronously.
*
* @see {@link lookupModule}
* @see {@link waitForModules}
*
* @see {@link proxify} for how proxification works.
*
* @param filter The filter to use to find the module.
* @param options The options to use for the find.
* @returns The module exports object if the module is already initialized, or proxified object of the module exports once the module is initialized, or undefined if otherwise.
*
* @example
* ```ts
* const React = findModuleSync(byProps<typeof import('react')>('createElement'), { hint: 'object' })
* const App = findModuleSync(byName<React.FC>('App'))
*
* console.log(React)
* // No proxification happens because React is loaded before index module is required
* // Logs: { createElement: ..., ... }
*
* console.log(App)
* // Logs: [Function], but is actually undefined, calling it will throw an error
* console.log(unproxify(App))
* // Logs: undefined
*
* onAppInitialized(() => {
* console.log(App)
* // When the app is initialized, the App component must also be initialized
* // Logs: [Function], but is actually function App() { ... }
* console.log(unproxify(App))
* // Logs: function App() { ... }
* })
* ```
*/
export function findModuleSync<F extends Filter>(filter: F): FilterResult<F> | undefined
export function findModuleSync<
F extends O extends FindModuleSyncOptions<boolean, true> ? Filter<any, false> : Filter,
O extends FindModuleSyncOptions,
>(filter: F, options: O): FindModuleResult<F, O> | undefined
export function findModuleSync(filter: Filter, options?: FindModuleSyncOptions) {
const exports = lookupModule(filter, options!)
if (exports != null) return exports
let value: unknown | undefined
const unsub = waitForModules(
filter,
(_, exports) => {
unsub()
value = exports
},
options!,
)
const signal = options?.abortSignal
if (signal) {
if (signal.aborted) unsub()
else signal.addEventListener('abort', unsub, { once: true })
}
return proxify(() => value, options)
}
/**
* Find a module by its imported path synchronously.
*
* @see {@link findModuleSync} for more details on proxification.
*
* @param path The path to find the module by.
* @param options The options to use for the find.
* @returns The module exports object if the module is already initialized, or proxified object of the module exports once the module is initialized, or undefined if otherwise.
*
* @example
* ```ts
* const Logger = findModuleByImportedPathSync<typeof DiscordModules.Logger>('modules/debug/Logger.tsx')
* ```
*/
export function findModuleByImportedPathSync<T = any>(
path: string,
options?: BaseFindModuleSyncOptions,
): T | undefined {
const exports = lookupModuleByImportedPath<T>(path)
if (exports != null) return exports
let value: unknown | undefined
const unsub = waitForModuleByImportedPath(path, (_, exports) => {
unsub()
value = exports
})
const signal = options?.abortSignal
if (signal) {
if (signal.aborted) unsub()
else signal.addEventListener('abort', unsub, { once: true })
}
return proxify(() => value as T | undefined, options)
}

View file

@ -14,8 +14,7 @@ export type PluginApiModulesMetro =
export type PluginApiModulesFinders =
// biome-ignore format: Don't
typeof import('@revenge-mod/modules/finders/lookup') &
typeof import('@revenge-mod/modules/finders/wait') &
typeof import('@revenge-mod/modules/finders/find') & {
typeof import('@revenge-mod/modules/finders/wait') & {
filters: typeof import('@revenge-mod/modules/finders/filters')
}
@ -23,7 +22,6 @@ export const modules: PluginApiModules = {
finders: {
...require('@revenge-mod/modules/finders/lookup'),
...require('@revenge-mod/modules/finders/wait'),
...require('@revenge-mod/modules/finders/find'),
filters: require('@revenge-mod/modules/finders/filters'),
},
metro: {