UNPKG

brick-module

Version:

Better React Native native module development

176 lines (151 loc) 4.96 kB
/** * Brick Module Framework - Main API * Type-safe access to native modules with automatic code generation */ import type { TurboModule } from "react-native"; import { TurboModuleRegistry, NativeEventEmitter } from "react-native"; /** * Base interface that all Brick module specs must extend */ export interface BrickModuleInterface extends TurboModule { readonly moduleName: string; readonly supportedEvents?: readonly string[]; } /** * Type alias for module specifications * Use this as the base interface when defining your module specs * @example * export interface MyModuleSpec extends BrickModuleSpec { * readonly moduleName: "MyModule"; * readonly supportedEvents: ["eventA", "eventB"]; * myMethod(param: string): Promise<string>; * } */ export type BrickModuleSpec = BrickModuleInterface; // Module-level state (previously static class members) const moduleCache = new Map<string, any>(); let nativeModule: any = null; let eventEmitter: NativeEventEmitter | null = null; /** * Gets the native TurboModule instance * @private */ function getNativeModule() { if (!nativeModule) { nativeModule = TurboModuleRegistry.getEnforcing("BrickModule"); } return nativeModule; } function getEventEmitter() { if (!eventEmitter) { const nativeModuleInstance = getNativeModule(); eventEmitter = new NativeEventEmitter(nativeModuleInstance); console.log("eventEmitter", eventEmitter); } return eventEmitter; } /** * Enhanced typed module interface with event listeners */ export type BrickModuleWithEvents<T extends BrickModuleInterface> = T & { /** * Adds a type-safe event listener for this module * @param eventName - One of the supported events defined in the module spec * @param listener - Callback function to handle the event * @returns Unsubscription function to remove the listener */ addEventListener<TEvent = unknown>( eventName: T["supportedEvents"] extends readonly (infer U)[] ? U : never, listener: (event: TEvent) => void ): () => void; }; /** * Gets a typed module instance by name with explicit type parameter * @param moduleName - The exact name of the module as defined in its spec * @returns Typed module interface with all methods, constants, and event listeners */ function get<T extends BrickModuleInterface>( moduleName: string ): BrickModuleWithEvents<T> { // Check cache first const cacheKey = moduleName; if (moduleCache.has(cacheKey)) { return moduleCache.get(cacheKey); } const nativeModuleInstance = getNativeModule(); // Create a proxy that intercepts method calls and forwards them to the native module const moduleProxy = new Proxy({} as BrickModuleWithEvents<T>, { get: (_target, property: string | symbol) => { if (typeof property !== "string") { return undefined; } // Handle event listener methods if (property === "addEventListener") { return (eventName: string, listener: (event: unknown) => void) => { const emitter = getEventEmitter(); const subscription = emitter.addListener( `${moduleName}_${eventName}`, listener ); return () => { subscription.remove(); }; }; } // Handle individual constants - check if this property exists as a constant const allConstants = nativeModuleInstance?.getConstants?.() ?? {}; const constantKey = `${moduleName}_${property}`; if (constantKey in allConstants) { return allConstants[constantKey]; } // Handle method calls return (...args: any[]) => { const methodKey = `${moduleName}_${property}`; // Try direct method call first (generated by codegen) if (typeof nativeModuleInstance[methodKey] === "function") { return nativeModuleInstance[methodKey](...args); } throw new Error(`Method ${methodKey} not found`); }; }, has: (_target, property) => { return typeof property === "string"; }, ownKeys: (_target) => { // Return empty array since we're proxying all property access return []; }, }); // Cache the proxy moduleCache.set(cacheKey, moduleProxy); return moduleProxy; } /** * Gets list of all registered modules * @returns Promise resolving to array of module names */ function getRegisteredModules(): string[] { const nativeModuleInstance = getNativeModule(); return nativeModuleInstance?.getRegisteredModules() ?? []; } /** * Clears the module cache (useful for testing or hot reloading) * @internal */ function clearCache(): void { moduleCache.clear(); } /** * Main Brick Module API object * Provides type-safe access to native modules */ export const BrickModule = { get, getRegisteredModules, clearCache, } as const; /** * Default export for convenience */ export default BrickModule; // Re-export types handled by main index.ts