signalforge
Version:
Fine-grained reactive state management with automatic dependency tracking - Ultra-optimized, zero dependencies
268 lines (251 loc) • 8.18 kB
text/typescript
/**
* NativeSignalForge - TurboModule Specification
*
* This file defines the TurboModule interface for SignalForge.
* TurboModules are the new architecture for React Native native modules,
* providing better type safety and performance through code generation.
*
* Note: While our JSI implementation provides direct function bindings,
* this TurboModule spec serves as:
* 1. A formal interface definition for tooling/documentation
* 2. An alternative integration path for projects using TurboModules
* 3. A bridge for future React Native features that require TurboModules
*
* The JSI implementation (jsiStore.cpp) is more performant for signal
* operations as it avoids the TurboModule bridge overhead.
*/
import { TurboModule, TurboModuleRegistry } from 'react-native';
// Ensure React Native codegen sees a direct TurboModuleRegistry usage.
// This must remain a literal call inside a function body (no indirection).
export function __ensureTurboModuleLoaded__(): void {
TurboModuleRegistry.get<Spec>('NativeSignalForge');
}
/**
* TurboModule Interface Specification
*
* These methods mirror the JSI bindings but are accessed through
* the TurboModule system instead of direct global function calls.
*
* Code generation will create native bindings from this specification.
*/
export interface Spec extends TurboModule {
/**
* Create a new signal with an initial value
*
* @param initialValue - The initial value (any serializable type)
* @returns Unique signal identifier
*
* Native implementation:
* - Allocates Signal in C++ memory (shared_ptr)
* - Stores in thread-safe unordered_map
* - Returns generated unique ID
*/
createSignal(initialValue: Object): string;
/**
* Get the current value of a signal
*
* @param signalId - Unique signal identifier
* @returns Current signal value
* @throws Error if signal doesn't exist
*
* Native implementation:
* - Looks up signal by ID
* - Acquires read lock
* - Returns value (converted to JS type)
*/
getSignal(signalId: string): Object;
/**
* Update a signal's value
*
* @param signalId - Unique signal identifier
* @param newValue - New value to set
* @throws Error if signal doesn't exist
*
* Native implementation:
* - Looks up signal by ID
* - Acquires write lock
* - Updates value
* - Atomically increments version
* - Notifies subscribers
*/
setSignal(signalId: string, newValue: Object): void;
/**
* Check if a signal exists
*
* @param signalId - Unique signal identifier
* @returns true if signal exists, false otherwise
*
* Native implementation:
* - Quick hash map lookup
*/
hasSignal(signalId: string): boolean;
/**
* Delete a signal from the store
*
* @param signalId - Unique signal identifier
*
* Native implementation:
* - Removes from store
* - shared_ptr handles memory cleanup
*/
deleteSignal(signalId: string): void;
/**
* Get the current version number of a signal
*
* Version numbers enable efficient change detection:
* - Increments on every setValue call
* - Lock-free atomic read
* - No need to compare values
*
* @param signalId - Unique signal identifier
* @returns Current version number
* @throws Error if signal doesn't exist
*
* Native implementation:
* - Atomic load from std::atomic<uint64_t>
* - memory_order_acquire semantics
*/
getSignalVersion(signalId: string): number;
/**
* Batch update multiple signals
*
* More efficient than individual updates for multiple signals:
* - Single TurboModule call
* - Reduced serialization overhead
* - All updates processed in native code
*
* @param updates - Array of [signalId, value] pairs
*
* Native implementation:
* - Validates all signals exist
* - Updates each signal in sequence
* - Version increments are atomic per signal
*/
batchUpdate(updates: Array<[string, Object]>): void;
/**
* Get the total number of signals in the store
*
* Useful for:
* - Memory monitoring
* - Debugging and diagnostics
* - Performance profiling
*
* @returns Number of active signals
*
* Native implementation:
* - Returns size of internal unordered_map
*/
getSignalCount(): number;
/**
* Clear all signals from the store
*
* Warning: This will invalidate all existing signal references!
* Use with caution - primarily for testing/cleanup scenarios.
*
* Native implementation:
* - Clears the unordered_map
* - All Signal objects are destroyed (shared_ptr cleanup)
*/
clearAllSignals(): void;
/**
* Get implementation metadata
*
* Returns information about the native implementation:
* - Version number
* - Build configuration
* - Performance characteristics
*
* @returns Metadata object
*/
getMetadata(): {
version: string;
buildType: 'Debug' | 'Release';
threadSafe: boolean;
supportsAtomic: boolean;
};
}
/**
* Get the TurboModule instance
*
* This function retrieves the native module using React Native's
* TurboModuleRegistry. If the module is not available (not linked,
* old architecture, etc.), returns null.
*
* Usage:
* ```typescript
* const NativeSignalForge = getNativeModule();
* if (NativeSignalForge) {
* const signalId = NativeSignalForge.createSignal(0);
* }
* ```
*
* Note: In most cases, you should use jsiBridge.ts instead, which
* automatically handles fallback and provides a cleaner API.
*/
// React Native codegen expects the module to be referenced with the exact
// TurboModule spec name. Load using the primary TurboModule name first to
// satisfy codegen's "Unused NativeModule spec" validation. Codegen requires
// the literal module name to appear in a `TurboModuleRegistry.get` call, so
// avoid additional direct references outside the ensure function above.
const moduleName = 'NativeSignalForge';
const legacyModuleName = 'SignalForge';
export function getNativeModule(): Spec | null {
try {
// Access TurboModuleRegistry from React Native environment
const registry = (global as any)?.TurboModuleRegistry ?? TurboModuleRegistry;
if (registry && typeof registry.get === 'function') {
return (
registry.get(moduleName) ??
registry.get(legacyModuleName)
) as Spec | null;
}
return null;
} catch (e) {
// Module not available (not using new architecture or not linked)
return null;
}
}
/**
* Default export for convenience
*/
export default getNativeModule();
/**
* Integration Notes:
*
* 1. For JSI Direct Binding (Recommended):
* - Use jsiBridge.ts for best performance
* - JSI bindings installed via global functions
* - No TurboModule overhead
*
* 2. For TurboModule Integration:
* - Import this module
* - Call methods through the TurboModule interface
* - Slightly more overhead than JSI direct binding
*
* 3. Code Generation:
* - Run React Native's codegen on this file
* - Generates native C++/ObjC++ bridge code
* - Add generated code to your native project
*
* Example Implementation Mapping:
*
* C++ JSI Direct:
* global.__signalForgeCreateSignal(value)
*
* TurboModule:
* NativeSignalForge.createSignal(value)
*
* TypeScript Wrapper (jsiBridge.ts):
* jsiBridge.createSignal(value)
* └─> Tries JSI direct first
* └─> Falls back to TurboModule
* └─> Falls back to JS implementation
*
* Performance Comparison (1 million operations):
* - JSI Direct: ~50ms (fastest, no bridge)
* - TurboModule: ~150ms (good, some serialization)
* - Old Bridge: ~500ms (slow, JSON serialization)
* - JS Fallback: ~200ms (pure JS, no native)
*
* Recommendation: Use JSI direct binding (jsiBridge.ts) for production.
*/