brick-module 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,224 @@
1
+ package com.brickmodule
2
+
3
+ import com.facebook.react.bridge.Arguments
4
+ import com.facebook.react.bridge.ReactApplicationContext
5
+ import com.facebook.react.modules.core.DeviceEventManagerModule
6
+
7
+ /**
8
+ * Central registry for all Brick modules Manages module lifecycle and provides simple module
9
+ * storage
10
+ */
11
+ object BrickModuleRegistry {
12
+ private val modules: MutableMap<String, Any> = mutableMapOf()
13
+ private var isRegistered: Boolean = false
14
+ private var reactContext: ReactApplicationContext? = null
15
+
16
+ /**
17
+ * Registers an array of Brick modules Each module must implement their respective TypeModule
18
+ * interface This should be called once during app initialization
19
+ */
20
+ fun register(modules: List<Any>) {
21
+ modules.forEach { module -> registerSingleModule(module) }
22
+
23
+ if (!isRegistered) {
24
+ isRegistered = true
25
+ }
26
+
27
+ println("✅ BrickModuleRegistry: Successfully registered ${modules.size} modules (total: ${this.modules.size})")
28
+ }
29
+
30
+ /**
31
+ * Registers a single module (push-style registration)
32
+ * Useful for manual module registration or testing
33
+ */
34
+ fun push(module: Any) {
35
+ registerSingleModule(module)
36
+ println("✅ BrickModuleRegistry: Module pushed successfully (total: ${this.modules.size})")
37
+ }
38
+
39
+ private fun registerSingleModule(module: Any) {
40
+ // Use reflection to get moduleName
41
+ val moduleNameProperty =
42
+ try {
43
+ module.javaClass.getMethod("getModuleName").invoke(module) as? String
44
+ } catch (e: Exception) {
45
+ try {
46
+ module.javaClass
47
+ .getDeclaredField("moduleName")
48
+ .apply { isAccessible = true }
49
+ .get(module) as?
50
+ String
51
+ } catch (e: Exception) {
52
+ null
53
+ }
54
+ }
55
+
56
+ val moduleName =
57
+ moduleNameProperty
58
+ ?: run {
59
+ println(
60
+ "❌ BrickModuleRegistry: Module has no accessible moduleName property"
61
+ )
62
+ return
63
+ }
64
+
65
+ // Validate module name
66
+ if (moduleName.isEmpty()) {
67
+ println("❌ BrickModuleRegistry: Module has empty moduleName")
68
+ return
69
+ }
70
+
71
+ // Check for module name conflicts
72
+ if (modules.containsKey(moduleName)) {
73
+ println("⚠️ BrickModuleRegistry: Module '$moduleName' is already registered. Skipping.")
74
+ return
75
+ }
76
+
77
+ // Get version using reflection
78
+ val version =
79
+ try {
80
+ module.javaClass.getMethod("getVersion").invoke(module) as? String ?: "1.0.0"
81
+ } catch (e: Exception) {
82
+ try {
83
+ module.javaClass
84
+ .getDeclaredField("version")
85
+ .apply { isAccessible = true }
86
+ .get(module) as?
87
+ String
88
+ ?: "1.0.0"
89
+ } catch (e: Exception) {
90
+ "1.0.0"
91
+ }
92
+ }
93
+
94
+ // Register the module
95
+ modules[moduleName] = module
96
+ println("📦 BrickModuleRegistry: Registered module '$moduleName' (v$version)")
97
+ }
98
+
99
+ /**
100
+ * Returns the actual module instance for reflection-based method calls Used by BrickModuleImpl
101
+ * for direct protocol casting
102
+ */
103
+ fun getModuleInstance(moduleName: String): Any? {
104
+ return modules[moduleName]
105
+ }
106
+
107
+ /** Returns list of registered module names */
108
+ fun getRegisteredModules(): List<String> {
109
+ return modules.keys.toList().sorted()
110
+ }
111
+
112
+ /**
113
+ * Sets the React Native context for event emission This should be called during app
114
+ * initialization from the main BrickModule
115
+ */
116
+ fun setReactContext(context: ReactApplicationContext) {
117
+ reactContext = context
118
+ println("📡 BrickModuleRegistry: React context connected for event emission")
119
+ }
120
+
121
+ /** Sends an event to JavaScript Used by modules to emit events to JavaScript */
122
+ fun sendEvent(eventName: String, data: Any?) {
123
+ reactContext?.let { context ->
124
+ try {
125
+ // Convert data to React Native compatible format
126
+ val eventData =
127
+ when (data) {
128
+ is Map<*, *> -> {
129
+ val writableMap = Arguments.createMap()
130
+ data.forEach { (key, value) ->
131
+ when (value) {
132
+ is String -> writableMap.putString(key.toString(), value)
133
+ is Number ->
134
+ writableMap.putDouble(
135
+ key.toString(),
136
+ value.toDouble()
137
+ )
138
+ is Boolean -> writableMap.putBoolean(key.toString(), value)
139
+ is List<*> -> {
140
+ val writableArray = Arguments.createArray()
141
+ value.forEach { item ->
142
+ when (item) {
143
+ is String -> writableArray.pushString(item)
144
+ is Number ->
145
+ writableArray.pushDouble(
146
+ item.toDouble()
147
+ )
148
+ is Boolean -> writableArray.pushBoolean(item)
149
+ else ->
150
+ writableArray.pushString(
151
+ item.toString()
152
+ )
153
+ }
154
+ }
155
+ writableMap.putArray(key.toString(), writableArray)
156
+ }
157
+ null -> writableMap.putNull(key.toString())
158
+ else ->
159
+ writableMap.putString(
160
+ key.toString(),
161
+ value.toString()
162
+ )
163
+ }
164
+ }
165
+ writableMap
166
+ }
167
+ else -> data
168
+ }
169
+
170
+ context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
171
+ .emit(eventName, eventData)
172
+
173
+ println("📡 BrickModuleRegistry: Event sent successfully: $eventName")
174
+ } catch (e: Exception) {
175
+ println("❌ BrickModuleRegistry: Failed to send event $eventName: ${e.message}")
176
+ e.printStackTrace()
177
+ }
178
+ }
179
+ ?: run {
180
+ println(
181
+ "⚠️ BrickModuleRegistry: No React context available for event: $eventName"
182
+ )
183
+ }
184
+ }
185
+
186
+ /** Returns all constants from registered modules */
187
+ fun getAllConstants(): Map<String, Any> {
188
+ val allConstants = mutableMapOf<String, Any>()
189
+
190
+ modules.forEach { (moduleName, module) ->
191
+ // Use reflection to get constants from the module
192
+ val moduleConstants = mutableMapOf<String, Any>()
193
+ val moduleClass = module.javaClass
194
+
195
+ // Get all public fields that might be constants
196
+ moduleClass.declaredFields.forEach { field ->
197
+ try {
198
+ field.isAccessible = true
199
+ val fieldName = field.name
200
+ // Skip internal fields
201
+ if (!fieldName.startsWith("_") && !fieldName.contains("$")) {
202
+ val value = field.get(module)
203
+ if (value != null) {
204
+ moduleConstants[fieldName] = value
205
+ }
206
+ }
207
+ } catch (e: Exception) {
208
+ // Ignore reflection errors
209
+ }
210
+ }
211
+
212
+ allConstants[moduleName] = moduleConstants
213
+ }
214
+
215
+ return allConstants
216
+ }
217
+
218
+ /** Clears all registered modules (for testing purposes only) */
219
+ fun clearRegistry() {
220
+ modules.clear()
221
+ isRegistered = false
222
+ println("🧹 BrickModuleRegistry: Registry cleared")
223
+ }
224
+ }
@@ -0,0 +1 @@
1
+ implementation-class=com.brickmodule.BrickModulePlugin
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Brick Codegen CLI Entry Point
5
+ * This wrapper allows brick-module to provide the brick-codegen CLI
6
+ */
7
+
8
+ import 'brick-codegen/cli';
@@ -0,0 +1,64 @@
1
+ import { TurboModule } from "react-native";
2
+
3
+ //#region src/BrickModule.d.ts
4
+
5
+ /**
6
+ * Base interface that all Brick module specs must extend
7
+ */
8
+ interface BrickModuleInterface extends TurboModule {
9
+ readonly moduleName: string;
10
+ readonly supportedEvents?: readonly string[];
11
+ }
12
+ /**
13
+ * Type alias for module specifications
14
+ * Use this as the base interface when defining your module specs
15
+ * @example
16
+ * export interface MyModuleSpec extends BrickModuleSpec {
17
+ * readonly moduleName: "MyModule";
18
+ * readonly supportedEvents: ["eventA", "eventB"];
19
+ * myMethod(param: string): Promise<string>;
20
+ * }
21
+ */
22
+ type BrickModuleSpec = BrickModuleInterface;
23
+ /**
24
+ * Enhanced typed module interface with event listeners
25
+ */
26
+ type BrickModuleWithEvents<T extends BrickModuleInterface> = T & {
27
+ /**
28
+ * Adds a type-safe event listener for this module
29
+ * @param eventName - One of the supported events defined in the module spec
30
+ * @param listener - Callback function to handle the event
31
+ * @returns Unsubscription function to remove the listener
32
+ */
33
+ addEventListener<TEvent = unknown>(eventName: T["supportedEvents"] extends readonly (infer U)[] ? U : never, listener: (event: TEvent) => void): () => void;
34
+ };
35
+ /**
36
+ * Gets a typed module instance by name with explicit type parameter
37
+ * @param moduleName - The exact name of the module as defined in its spec
38
+ * @returns Typed module interface with all methods, constants, and event listeners
39
+ */
40
+ declare function get<T extends BrickModuleInterface>(moduleName: string): BrickModuleWithEvents<T>;
41
+ /**
42
+ * Gets list of all registered modules
43
+ * @returns Promise resolving to array of module names
44
+ */
45
+ declare function getRegisteredModules(): string[];
46
+ /**
47
+ * Clears the module cache (useful for testing or hot reloading)
48
+ * @internal
49
+ */
50
+ declare function clearCache(): void;
51
+ /**
52
+ * Main Brick Module API object
53
+ * Provides type-safe access to native modules
54
+ */
55
+ declare const BrickModule: {
56
+ readonly get: typeof get;
57
+ readonly getRegisteredModules: typeof getRegisteredModules;
58
+ readonly clearCache: typeof clearCache;
59
+ };
60
+ /**
61
+ * Default export for convenience
62
+ */
63
+ //#endregion
64
+ export { BrickModule, BrickModuleInterface, BrickModuleSpec };
@@ -0,0 +1,91 @@
1
+ import { NativeEventEmitter, TurboModuleRegistry } from "react-native";
2
+
3
+ //#region src/BrickModule.ts
4
+ const moduleCache = /* @__PURE__ */ new Map();
5
+ let nativeModule = null;
6
+ let eventEmitter = null;
7
+ /**
8
+ * Gets the native TurboModule instance
9
+ * @private
10
+ */
11
+ function getNativeModule() {
12
+ if (!nativeModule) nativeModule = TurboModuleRegistry.getEnforcing("BrickModule");
13
+ return nativeModule;
14
+ }
15
+ function getEventEmitter() {
16
+ if (!eventEmitter) {
17
+ const nativeModuleInstance = getNativeModule();
18
+ eventEmitter = new NativeEventEmitter(nativeModuleInstance);
19
+ console.log("eventEmitter", eventEmitter);
20
+ }
21
+ return eventEmitter;
22
+ }
23
+ /**
24
+ * Gets a typed module instance by name with explicit type parameter
25
+ * @param moduleName - The exact name of the module as defined in its spec
26
+ * @returns Typed module interface with all methods, constants, and event listeners
27
+ */
28
+ function get(moduleName) {
29
+ const cacheKey = moduleName;
30
+ if (moduleCache.has(cacheKey)) return moduleCache.get(cacheKey);
31
+ const nativeModuleInstance = getNativeModule();
32
+ const moduleProxy = new Proxy({}, {
33
+ get: (_target, property) => {
34
+ if (typeof property !== "string") return void 0;
35
+ if (property === "addEventListener") return (eventName, listener) => {
36
+ const emitter = getEventEmitter();
37
+ const subscription = emitter.addListener(`${moduleName}_${eventName}`, listener);
38
+ return () => {
39
+ subscription.remove();
40
+ };
41
+ };
42
+ const allConstants = nativeModuleInstance?.getConstants?.() ?? {};
43
+ const constantKey = `${moduleName}_${property}`;
44
+ if (constantKey in allConstants) return allConstants[constantKey];
45
+ return (...args) => {
46
+ const methodKey = `${moduleName}_${property}`;
47
+ if (typeof nativeModuleInstance[methodKey] === "function") return nativeModuleInstance[methodKey](...args);
48
+ throw new Error(`Method ${methodKey} not found`);
49
+ };
50
+ },
51
+ has: (_target, property) => {
52
+ return typeof property === "string";
53
+ },
54
+ ownKeys: (_target) => {
55
+ return [];
56
+ }
57
+ });
58
+ moduleCache.set(cacheKey, moduleProxy);
59
+ return moduleProxy;
60
+ }
61
+ /**
62
+ * Gets list of all registered modules
63
+ * @returns Promise resolving to array of module names
64
+ */
65
+ function getRegisteredModules() {
66
+ const nativeModuleInstance = getNativeModule();
67
+ return nativeModuleInstance?.getRegisteredModules() ?? [];
68
+ }
69
+ /**
70
+ * Clears the module cache (useful for testing or hot reloading)
71
+ * @internal
72
+ */
73
+ function clearCache() {
74
+ moduleCache.clear();
75
+ }
76
+ /**
77
+ * Main Brick Module API object
78
+ * Provides type-safe access to native modules
79
+ */
80
+ const BrickModule = {
81
+ get,
82
+ getRegisteredModules,
83
+ clearCache
84
+ };
85
+ /**
86
+ * Default export for convenience
87
+ */
88
+ var BrickModule_default = BrickModule;
89
+
90
+ //#endregion
91
+ export { BrickModule_default as default };
@@ -0,0 +1,31 @@
1
+ import { BrickModule, BrickModuleInterface, BrickModuleSpec } from "./BrickModule.js";
2
+
3
+ //#region src/index.d.ts
4
+
5
+ /**
6
+ * Error types for Brick modules
7
+ */
8
+ declare class BrickModuleError extends Error {
9
+ code: string;
10
+ moduleName?: string | undefined;
11
+ methodName?: string | undefined;
12
+ constructor(message: string, code?: string, moduleName?: string | undefined, methodName?: string | undefined);
13
+ }
14
+ /**
15
+ * Configuration interface for brick-codegen
16
+ */
17
+ interface BrickCodegenConfig {
18
+ outputIos?: string;
19
+ outputAndroid?: string;
20
+ cacheDir?: string;
21
+ incremental?: boolean;
22
+ watch?: boolean;
23
+ excludeModules?: string[];
24
+ typeCheck?: boolean;
25
+ verbose?: boolean;
26
+ newArchOnly?: boolean;
27
+ oldArchOnly?: boolean;
28
+ dev?: boolean;
29
+ }
30
+ //#endregion
31
+ export { BrickCodegenConfig, BrickModule, BrickModuleError, BrickModuleInterface, BrickModuleSpec };
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ import BrickModule_default from "./BrickModule.js";
2
+
3
+ //#region src/index.ts
4
+ /**
5
+ * Error types for Brick modules
6
+ */
7
+ var BrickModuleError = class extends Error {
8
+ constructor(message, code = "GRANITE_ERROR", moduleName, methodName) {
9
+ super(message);
10
+ this.code = code;
11
+ this.moduleName = moduleName;
12
+ this.methodName = methodName;
13
+ this.name = "BrickModuleError";
14
+ }
15
+ };
16
+
17
+ //#endregion
18
+ export { BrickModule_default as BrickModule, BrickModuleError };
@@ -0,0 +1,51 @@
1
+ import Foundation
2
+
3
+ /**
4
+ * Minimal protocol that all Brick modules must implement
5
+ * Contains only essential properties for module identification
6
+ */
7
+ public protocol BrickModuleBase {
8
+ /// The name of the module (required for registration)
9
+ var moduleName: String { get }
10
+ }
11
+
12
+ /**
13
+ * Error types for Brick modules
14
+ */
15
+ public enum BrickModuleError: Error, LocalizedError {
16
+ case typeMismatch(String)
17
+ case executionError(String)
18
+ case invalidDefinition(String)
19
+ case methodNotFound(String)
20
+ case moduleNotFound(String)
21
+
22
+ public var errorDescription: String? {
23
+ switch self {
24
+ case .typeMismatch(let message):
25
+ return "Type mismatch: \(message)"
26
+ case .executionError(let message):
27
+ return "Execution error: \(message)"
28
+ case .invalidDefinition(let message):
29
+ return "Invalid definition: \(message)"
30
+ case .methodNotFound(let message):
31
+ return "Method not found: \(message)"
32
+ case .moduleNotFound(let message):
33
+ return "Module not found: \(message)"
34
+ }
35
+ }
36
+
37
+ public var errorCode: String {
38
+ switch self {
39
+ case .typeMismatch:
40
+ return "TYPE_ERROR"
41
+ case .executionError:
42
+ return "EXECUTION_ERROR"
43
+ case .invalidDefinition:
44
+ return "DEFINITION_ERROR"
45
+ case .methodNotFound:
46
+ return "METHOD_NOT_FOUND"
47
+ case .moduleNotFound:
48
+ return "MODULE_NOT_FOUND"
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,138 @@
1
+ import Foundation
2
+ import React
3
+
4
+ /**
5
+ * Central registry for all Brick modules
6
+ * Manages module lifecycle and provides simple module storage
7
+ */
8
+ @objc public class BrickModuleRegistry: NSObject {
9
+ @objc public static let shared = BrickModuleRegistry()
10
+
11
+ private var modules: [String: Any] = [:]
12
+ private var isRegistered: Bool = false
13
+ private weak var eventEmitter: RCTEventEmitter?
14
+
15
+ private override init() {
16
+ super.init()
17
+ }
18
+
19
+ // MARK: - Module Registration
20
+
21
+ /**
22
+ * Registers an array of Brick modules (Swift API)
23
+ * Each module must implement BrickModuleBase and its respective TypeModule protocol
24
+ * This should be called once during app initialization
25
+ */
26
+ public func register(_ modules: [BrickModuleBase]) {
27
+ guard !isRegistered else {
28
+ print("⚠️ BrickModuleRegistry: Modules already registered. Skipping re-registration.")
29
+ return
30
+ }
31
+
32
+ for module in modules {
33
+ registerSingleModule(module)
34
+ }
35
+
36
+ isRegistered = true
37
+ print("✅ BrickModuleRegistry: Successfully registered \(self.modules.count) modules")
38
+
39
+ if ProcessInfo.processInfo.environment["GRANITE_DEBUG"] == "1" {
40
+ debugPrintRegisteredModules()
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Registers an array of Brick modules (Objective-C API)
46
+ * Each module must implement BrickModuleBase protocol
47
+ * This should be called once during app initialization
48
+ */
49
+ @objc(registerModules:)
50
+ public func registerModules(_ modules: NSArray) {
51
+ let brickModules = modules.compactMap { $0 as? BrickModuleBase }
52
+ register(brickModules)
53
+ }
54
+
55
+ private func registerSingleModule(_ module: BrickModuleBase) {
56
+ let moduleName = module.moduleName
57
+
58
+ // Validate module name
59
+ guard !moduleName.isEmpty else {
60
+ print("❌ BrickModuleRegistry: Module has empty moduleName")
61
+ return
62
+ }
63
+
64
+ // Check for module name conflicts
65
+ if modules.keys.contains(moduleName) {
66
+ print("⚠️ BrickModuleRegistry: Module '\(moduleName)' is already registered. Skipping.")
67
+ return
68
+ }
69
+
70
+ // Register the module
71
+ modules[moduleName] = module
72
+ print("📦 BrickModuleRegistry: Registered module '\(moduleName)'")
73
+ }
74
+
75
+ // MARK: - Module Information
76
+
77
+ /**
78
+ * Returns the actual module instance for reflection-based method calls
79
+ * Used by BrickModuleImpl for direct protocol casting
80
+ */
81
+ public func getModuleInstance(_ moduleName: String) -> Any? {
82
+ return modules[moduleName]
83
+ }
84
+
85
+ /**
86
+ * Returns list of registered module names
87
+ */
88
+ @objc public func getRegisteredModules() -> [String] {
89
+ return Array(modules.keys).sorted()
90
+ }
91
+
92
+ // MARK: - Event Emitter Management
93
+
94
+ /**
95
+ * Sets the React Native event emitter instance for event emission
96
+ * This should be called during app initialization from the main BrickModule
97
+ */
98
+ @objc public func setEventEmitter(_ eventEmitter: RCTEventEmitter) {
99
+ self.eventEmitter = eventEmitter
100
+ print("📡 BrickModuleRegistry: Event emitter connected for event emission")
101
+ }
102
+
103
+ /**
104
+ * Returns the React Native event emitter instance for event emission
105
+ * Used by generated module code to emit events to JavaScript
106
+ */
107
+ @objc public func getEventEmitter() -> RCTEventEmitter? {
108
+ return eventEmitter
109
+ }
110
+
111
+ // MARK: - Debug & Development
112
+
113
+ private func debugPrintRegisteredModules() {
114
+ print("🔍 BrickModuleRegistry Debug Info:")
115
+ print(" Total Modules: \(modules.count)")
116
+
117
+ for (moduleName, module) in modules.sorted(by: { $0.key < $1.key }) {
118
+ let moduleType = String(describing: type(of: module))
119
+ print(" 📦 \(moduleName) (\(moduleType))")
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Clears all registered modules (for testing purposes only)
125
+ */
126
+ @objc public func clearRegistry() {
127
+ guard ProcessInfo.processInfo.environment["NODE_ENV"] == "test" ||
128
+ ProcessInfo.processInfo.environment["GRANITE_ALLOW_CLEAR"] == "1" else {
129
+ print("⚠️ BrickModuleRegistry: clearRegistry() is only allowed in test environment")
130
+ return
131
+ }
132
+
133
+ modules.removeAll()
134
+ isRegistered = false
135
+
136
+ print("🧹 BrickModuleRegistry: Registry cleared")
137
+ }
138
+ }