brick-module 0.3.1 → 0.4.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.
@@ -17,7 +17,7 @@ Pod::Spec.new do |s|
17
17
  s.public_header_files = "ios/BrickModule/Public/*.h"
18
18
  s.private_header_files = "ios/BrickModule/Internal/*.h"
19
19
 
20
- # Swift 설정
20
+ # Swift settings
21
21
  s.swift_version = "5.0"
22
22
  s.requires_arc = true
23
23
 
@@ -68,6 +68,29 @@ ext.getBrickAndroidPath = { projectRoot ->
68
68
  return new File(projectRoot, 'android/.brick').canonicalPath
69
69
  }
70
70
 
71
+ ext.getBrickAndroidAppPath = { projectRoot ->
72
+ // Read brick.json from explicit project root
73
+ try {
74
+ def brickConfigFile = new File(projectRoot, 'brick.json')
75
+ if (brickConfigFile.exists()) {
76
+ def config = new JsonSlurper().parse(brickConfigFile)
77
+ def androidAppPath = config?.output?.androidAppPath
78
+ if (androidAppPath && !androidAppPath.isEmpty()) {
79
+ if (new File(androidAppPath).isAbsolute()) {
80
+ println("[Brick] Warning: Using absolute path for Android app path is not recommended: ${androidAppPath}")
81
+ return androidAppPath
82
+ }
83
+ return new File(projectRoot, androidAppPath).canonicalPath
84
+ }
85
+ }
86
+ } catch (Exception e) {
87
+ println("[Brick] Warning: Failed to read brick.json from ${projectRoot}: ${e.message}")
88
+ }
89
+
90
+ // Default: android/app in project root
91
+ return new File(projectRoot, 'android/app').canonicalPath
92
+ }
93
+
71
94
  ext.getAllDependencies = { packageJson ->
72
95
  def deps = []
73
96
  deps.addAll(packageJson.dependencies?.keySet() ?: [])
@@ -79,7 +102,7 @@ ext.isBrickModule = { packageJson ->
79
102
  return packageJson?.brickModule != null
80
103
  }
81
104
 
82
- ext.runBrickCodegen = { workingDir ->
105
+ ext.runBrickCodegen = { workingDir, options = [:] ->
83
106
  def brickModuleDir = resolveModule(workingDir, "brick-module")
84
107
  if (brickModuleDir == null) {
85
108
  brickLog("brick-module not found, skipping code generation")
@@ -94,7 +117,18 @@ ext.runBrickCodegen = { workingDir ->
94
117
 
95
118
  try {
96
119
  def wdPath = (workingDir instanceof File) ? workingDir.absolutePath : workingDir
97
- def proc = ['node', codegenPath.absolutePath, '--platform', 'android', '--projectRoot', wdPath].execute(null, workingDir)
120
+ def codegenArgs = [
121
+ 'node',
122
+ codegenPath.absolutePath,
123
+ '--platform',
124
+ 'android',
125
+ '--projectRoot',
126
+ wdPath
127
+ ]
128
+ if (options.noClean == true) {
129
+ codegenArgs.add('--no-clean')
130
+ }
131
+ def proc = codegenArgs.execute(null, workingDir)
98
132
  proc.waitFor()
99
133
  return proc.exitValue() == 0
100
134
  } catch (Exception e) {
@@ -194,10 +228,12 @@ ext.applyBrickModules = { settings, args ->
194
228
  def androidBrickPath = getBrickAndroidPath(projectRoot)
195
229
  def brickDir = new File(androidBrickPath)
196
230
  def needsCodegen = !brickDir.exists() || !new File(brickDir, "src/main/kotlin/BrickModule.kt").exists()
197
-
231
+ def codegenAttempted = false
198
232
  if (needsCodegen) {
199
- runBrickCodegen(projectRoot)
233
+ codegenAttempted = true
234
+ runBrickCodegen(projectRoot, [noClean: true])
200
235
  }
236
+ settings.ext.brickCodegenRan = codegenAttempted
201
237
 
202
238
  // Include generated module (use configured path)
203
239
  if (brickDir.exists()) {
@@ -216,6 +252,8 @@ ext.applyBrickModules = { settings, args ->
216
252
  gradle.rootProject.ext.brickModulesList = foundModules
217
253
  gradle.rootProject.ext.brickProjectRoot = projectRoot
218
254
  gradle.rootProject.ext.brickAppProjectName = appProjectName
255
+ gradle.rootProject.ext.brickCodegenRanInBuild =
256
+ settings.ext.has("brickCodegenRan") ? settings.ext.brickCodegenRan : false
219
257
  configureAppProject(gradle)
220
258
  // Inline: ensure RN autolinking doesn't fail due to missing codegen/jni for brick-module
221
259
  gradle.rootProject.subprojects { subproj ->
@@ -395,19 +433,16 @@ ${moduleNames}
395
433
  project.preBuild.dependsOn('generateBrickModulesList')
396
434
  project.preBuild.doFirst {
397
435
  def workingDir = projectRoot
398
-
399
- // Skip codegen if already generated to prevent deleting Gradle build outputs
400
- // The codegen is already run during settings.gradle phase (applyBrickModules -> runBrickCodegen)
401
- // Re-running here with --clean would delete .brick_android/build/ directory
402
- // which contains package-aware-r.txt needed by dependent modules
403
- def brickOutputDir = getBrickAndroidPath(workingDir)
404
- def generatedMarker = new File(brickOutputDir, "src/main/kotlin/BrickModule.kt")
405
- if (generatedMarker.exists()) {
406
- // Already generated, skip to preserve build outputs
436
+
437
+ def alreadyRan = rootProject.ext.has("brickCodegenRanInBuild") ?
438
+ rootProject.ext.brickCodegenRanInBuild : false
439
+ if (alreadyRan) {
440
+ // Avoid double-running if settings already executed codegen.
407
441
  return
408
442
  }
443
+ rootProject.ext.brickCodegenRanInBuild = true
409
444
 
410
- // Run codegen only if not yet generated
445
+ // Run codegen once per build without cleaning outputs.
411
446
  try {
412
447
  def proc = ['node', '-p', "require.resolve('brick-module/package.json')"].execute(null, workingDir)
413
448
  proc.waitFor()
@@ -419,7 +454,16 @@ ${moduleNames}
419
454
  def codegenPath = new File(brickModuleDir, "bin/brick-codegen.js")
420
455
 
421
456
  if (codegenPath.exists()) {
422
- def codegenProc = ['node', codegenPath.absolutePath, '--platform', 'android', '--projectRoot', workingDir.absolutePath].execute(null, workingDir)
457
+ def codegenArgs = [
458
+ 'node',
459
+ codegenPath.absolutePath,
460
+ '--platform',
461
+ 'android',
462
+ '--projectRoot',
463
+ workingDir.absolutePath,
464
+ '--no-clean'
465
+ ]
466
+ def codegenProc = codegenArgs.execute(null, workingDir)
423
467
  codegenProc.waitFor()
424
468
  }
425
469
  }
@@ -58,8 +58,45 @@ abstract class BrickModuleSpec(private val reactContext: ReactContext) : BrickMo
58
58
  }
59
59
  }
60
60
 
61
- /** Error types for Brick modules */
62
- open class BrickModuleError(message: String, val errorCode: String) : Exception(message) {
61
+ // MARK: - BrickError Interface
62
+
63
+ /**
64
+ * Error interface for Brick modules.
65
+ * Implement this interface to propagate all error properties to JavaScript.
66
+ * Follows the same pattern as iOS BrickError protocol.
67
+ *
68
+ * Note: message is inherited from Throwable, so it's not defined in this interface.
69
+ * Classes implementing BrickError must extend Exception/Throwable.
70
+ */
71
+ interface BrickError {
72
+ /** Error code (accessed as error.name in JS) */
73
+ val code: String
74
+
75
+ /** Additional properties (accessed as error.userInfo.xxx in JS) */
76
+ val userInfo: Map<String, Any>?
77
+ get() = null
78
+ }
79
+
80
+ /**
81
+ * Default implementation of BrickError.
82
+ * Extend this class to create custom errors.
83
+ */
84
+ open class BrickException(
85
+ override val code: String,
86
+ message: String,
87
+ override val userInfo: Map<String, Any>? = null,
88
+ cause: Throwable? = null
89
+ ) : Exception(message, cause), BrickError
90
+
91
+ /** Error types for Brick modules - implements BrickError interface */
92
+ open class BrickModuleError(
93
+ message: String,
94
+ override val code: String
95
+ ) : Exception(message), BrickError {
96
+
97
+ override val userInfo: Map<String, Any>?
98
+ get() = null
99
+
63
100
  class TypeMismatch(message: String) : BrickModuleError("Type mismatch: $message", "TYPE_ERROR")
64
101
  class ExecutionError(message: String) :
65
102
  BrickModuleError("Execution error: $message", "EXECUTION_ERROR")
@@ -3,33 +3,45 @@ package com.brickmodule
3
3
  import com.facebook.react.bridge.*
4
4
 
5
5
  /**
6
- * Helper class for converting data to React Native compatible formats
7
- * Ensures type safety when sending events to JavaScript
6
+ * Helper class for converting data to React Native compatible formats Ensures type safety when
7
+ * sending events to JavaScript
8
8
  */
9
9
  object EventDataConverter {
10
-
10
+
11
+ private fun isNullSentinel(value: Any?): Boolean {
12
+ return value is Null ||
13
+ (value != null && value.javaClass.name == "com.brickmodule.Null") ||
14
+ (value is String && value.startsWith("com.brickmodule.Null@"))
15
+ }
16
+
11
17
  /**
12
18
  * Convert any data to React Native compatible format
13
- *
19
+ *
14
20
  * @param data The data to convert
15
21
  * @return React Native compatible data or null if conversion fails
16
22
  */
17
23
  fun convert(data: Any?): Any? {
18
24
  return try {
19
- when (data) {
20
- null -> null
21
- is String -> data
22
- is Boolean -> data
23
- is Int -> data
24
- is Double -> data
25
- is Float -> data.toDouble()
26
- is Long -> data.toDouble()
27
- is Map<*, *> -> convertMap(data)
28
- is List<*> -> convertList(data)
29
- is Array<*> -> convertList(data.toList())
30
- else -> {
31
- println("⚠️ EventDataConverter: Unsupported data type ${data.javaClass.simpleName}, converting to string")
32
- data.toString()
25
+ if (isNullSentinel(data)) {
26
+ null
27
+ } else {
28
+ when (data) {
29
+ null -> null
30
+ is String -> data
31
+ is Boolean -> data
32
+ is Int -> data
33
+ is Double -> data
34
+ is Float -> data.toDouble()
35
+ is Long -> data.toDouble()
36
+ is Map<*, *> -> convertMap(data)
37
+ is List<*> -> convertList(data)
38
+ is Array<*> -> convertList(data.toList())
39
+ else -> {
40
+ println(
41
+ "⚠️ EventDataConverter: Unsupported data type ${data.javaClass.simpleName}, converting to string"
42
+ )
43
+ data.toString()
44
+ }
33
45
  }
34
46
  }
35
47
  } catch (e: Exception) {
@@ -37,17 +49,17 @@ object EventDataConverter {
37
49
  null
38
50
  }
39
51
  }
40
-
52
+
41
53
  /**
42
54
  * Convert a map to WritableMap for React Native
43
- *
55
+ *
44
56
  * @param map The map to convert
45
57
  * @return WritableMap or null if conversion fails
46
58
  */
47
59
  private fun convertMap(map: Map<*, *>): WritableMap? {
48
60
  return try {
49
61
  val writableMap = Arguments.createMap()
50
-
62
+
51
63
  map.forEach { (key, value) ->
52
64
  val keyString = key?.toString()
53
65
  if (keyString != null) {
@@ -63,24 +75,24 @@ object EventDataConverter {
63
75
  }
64
76
  }
65
77
  }
66
-
78
+
67
79
  writableMap
68
80
  } catch (e: Exception) {
69
81
  println("❌ EventDataConverter: Failed to convert map: ${e.message}")
70
82
  null
71
83
  }
72
84
  }
73
-
85
+
74
86
  /**
75
87
  * Convert a list to WritableArray for React Native
76
- *
88
+ *
77
89
  * @param list The list to convert
78
90
  * @return WritableArray or null if conversion fails
79
91
  */
80
92
  private fun convertList(list: List<*>): WritableArray? {
81
93
  return try {
82
94
  val writableArray = Arguments.createArray()
83
-
95
+
84
96
  list.forEach { item ->
85
97
  when (val convertedValue = convert(item)) {
86
98
  null -> writableArray.pushNull()
@@ -93,30 +105,31 @@ object EventDataConverter {
93
105
  else -> writableArray.pushString(convertedValue.toString())
94
106
  }
95
107
  }
96
-
108
+
97
109
  writableArray
98
110
  } catch (e: Exception) {
99
111
  println("❌ EventDataConverter: Failed to convert list: ${e.message}")
100
112
  null
101
113
  }
102
114
  }
103
-
115
+
104
116
  /**
105
117
  * Validate if data can be safely converted to React Native format
106
- *
118
+ *
107
119
  * @param data The data to validate
108
120
  * @return true if data can be safely converted, false otherwise
109
121
  */
110
122
  fun isConvertible(data: Any?): Boolean {
123
+ if (isNullSentinel(data)) {
124
+ return true
125
+ }
111
126
  return when (data) {
112
127
  null -> true
113
128
  is String, is Boolean, is Number -> true
114
- is Map<*, *> -> data.all { (key, value) ->
115
- key is String && isConvertible(value)
116
- }
129
+ is Map<*, *> -> data.all { (key, value) -> key is String && isConvertible(value) }
117
130
  is List<*> -> data.all { isConvertible(it) }
118
131
  is Array<*> -> data.all { isConvertible(it) }
119
132
  else -> false
120
133
  }
121
134
  }
122
- }
135
+ }
@@ -0,0 +1,7 @@
1
+
2
+ package com.brickmodule
3
+
4
+ /**
5
+ * Sentinel value used to represent null for Any conversions.
6
+ */
7
+ object Null
@@ -0,0 +1,29 @@
1
+ //#region src/BrickError.d.ts
2
+ /**
3
+ * BrickError - Type-safe class for custom errors from native modules.
4
+ * Matches the structure of iOS/Android BrickError.
5
+ */
6
+ declare class BrickError extends Error {
7
+ /** Error code (same as error.name) */
8
+ readonly code: string;
9
+ /** Additional properties (e.g., amount, currency) */
10
+ readonly userInfo: Record<string, unknown>;
11
+ /** Name of the module where the error occurred */
12
+ readonly moduleName?: string;
13
+ constructor(message: string, code: string, userInfo?: Record<string, unknown>, moduleName?: string);
14
+ /**
15
+ * Type guard to check if an error object is a BrickError.
16
+ * @param error - The error object to check
17
+ * @returns true if the error is a BrickError
18
+ */
19
+ static isBrickError(error: unknown): error is BrickError;
20
+ /**
21
+ * Converts a general error object to a BrickError.
22
+ * @param error - The error object to convert
23
+ * @param moduleName - Name of the module where the error occurred
24
+ * @returns A BrickError instance
25
+ */
26
+ static from(error: unknown, moduleName?: string): BrickError;
27
+ }
28
+ //#endregion
29
+ export { BrickError };
@@ -0,0 +1,49 @@
1
+ //#region src/BrickError.ts
2
+ /**
3
+ * BrickError - Type-safe class for custom errors from native modules.
4
+ * Matches the structure of iOS/Android BrickError.
5
+ */
6
+ var BrickError = class BrickError extends Error {
7
+ /** Error code (same as error.name) */
8
+ code;
9
+ /** Additional properties (e.g., amount, currency) */
10
+ userInfo;
11
+ /** Name of the module where the error occurred */
12
+ moduleName;
13
+ constructor(message, code, userInfo = {}, moduleName) {
14
+ super(message);
15
+ this.name = code;
16
+ this.code = code;
17
+ this.userInfo = userInfo;
18
+ this.moduleName = moduleName;
19
+ }
20
+ /**
21
+ * Type guard to check if an error object is a BrickError.
22
+ * @param error - The error object to check
23
+ * @returns true if the error is a BrickError
24
+ */
25
+ static isBrickError(error) {
26
+ return error instanceof Error && typeof error.code === "string" && typeof error.userInfo === "object" && error.userInfo !== null;
27
+ }
28
+ /**
29
+ * Converts a general error object to a BrickError.
30
+ * @param error - The error object to convert
31
+ * @param moduleName - Name of the module where the error occurred
32
+ * @returns A BrickError instance
33
+ */
34
+ static from(error, moduleName) {
35
+ if (error instanceof BrickError) {
36
+ if (moduleName && !error.moduleName) return new BrickError(error.message, error.code, error.userInfo, moduleName);
37
+ return error;
38
+ }
39
+ if (BrickError.isBrickError(error)) return new BrickError(error.message, error.code, error.userInfo, moduleName || error.moduleName);
40
+ if (error instanceof Error) {
41
+ const anyError = error;
42
+ return new BrickError(error.message, anyError.code || anyError.name || "UNKNOWN_ERROR", anyError.userInfo || {}, moduleName || anyError.moduleName);
43
+ }
44
+ return new BrickError(String(error), "UNKNOWN_ERROR", {}, moduleName);
45
+ }
46
+ };
47
+
48
+ //#endregion
49
+ export { BrickError };
@@ -1,3 +1,4 @@
1
+ import { BrickError } from "./BrickError.js";
1
2
  import { TurboModuleRegistry } from "react-native";
2
3
 
3
4
  //#region src/BrickModule.ts
@@ -49,11 +50,15 @@ function get(moduleName) {
49
50
  if (result && typeof result === "object" && result["~sync"] === true) if (result.success === true) return result.value;
50
51
  else {
51
52
  const errorInfo = result.error || {};
52
- const error = new Error(errorInfo.message || "Unknown error");
53
- error.name = errorInfo.code || "BRICK_ERROR";
54
- if (errorInfo.details) error.details = errorInfo.details;
55
- throw error;
53
+ const message = errorInfo.message || errorInfo.errorMessage || "Unknown error";
54
+ const code = errorInfo.code || errorInfo.errorCode || "BRICK_ERROR";
55
+ const userInfo = { code };
56
+ for (const key of Object.keys(errorInfo)) if (key !== "code" && key !== "message" && key !== "errorCode" && key !== "errorMessage") userInfo[key] = errorInfo[key];
57
+ throw new BrickError(message, code, userInfo, moduleName);
56
58
  }
59
+ if (result && typeof result.then === "function") return result.catch((error) => {
60
+ throw BrickError.from(error, moduleName);
61
+ });
57
62
  return result;
58
63
  }
59
64
  throw new Error(`Method ${methodKey} not found`);
package/dist/index.d.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import { BrickModule, BrickModuleInterface, BrickModuleSpec } from "./BrickModule.js";
2
+ import { BrickError } from "./BrickError.js";
2
3
  import { Any, AnyObject } from "./types.js";
3
4
 
4
5
  //#region src/index.d.ts
5
6
 
6
7
  /**
7
8
  * Error types for Brick modules
9
+ * @deprecated Use BrickError instead
8
10
  */
9
11
  declare class BrickModuleError extends Error {
10
12
  code: string;
@@ -29,4 +31,4 @@ interface BrickCodegenConfig {
29
31
  dev?: boolean;
30
32
  }
31
33
  //#endregion
32
- export { Any, AnyObject, BrickCodegenConfig, BrickModule, BrickModuleError, type BrickModuleInterface, type BrickModuleSpec };
34
+ export { Any, AnyObject, BrickCodegenConfig, BrickError, BrickModule, BrickModuleError, type BrickModuleInterface, type BrickModuleSpec };
package/dist/index.js CHANGED
@@ -1,8 +1,10 @@
1
+ import { BrickError } from "./BrickError.js";
1
2
  import BrickModule_default from "./BrickModule.js";
2
3
 
3
4
  //#region src/index.ts
4
5
  /**
5
6
  * Error types for Brick modules
7
+ * @deprecated Use BrickError instead
6
8
  */
7
9
  var BrickModuleError = class extends Error {
8
10
  constructor(message, code = "BRICK_ERROR", moduleName, methodName) {
@@ -15,4 +17,4 @@ var BrickModuleError = class extends Error {
15
17
  };
16
18
 
17
19
  //#endregion
18
- export { BrickModule_default as BrickModule, BrickModuleError };
20
+ export { BrickError, BrickModule_default as BrickModule, BrickModuleError };
@@ -1,6 +1,48 @@
1
1
  import Foundation
2
2
  import React
3
3
 
4
+ // MARK: - BrickError Protocol
5
+
6
+ /// Error protocol for Brick modules.
7
+ /// Conforming to this protocol propagates all error properties to JavaScript.
8
+ public protocol BrickError: Error {
9
+ /// Error message (accessed as error.message in JS)
10
+ var message: String { get }
11
+
12
+ /// Error code (accessed as error.code in JS, optional)
13
+ var code: String? { get }
14
+
15
+ /// Additional properties (accessed as error.userInfo.xxx in JS)
16
+ var userInfo: [String: Any]? { get }
17
+
18
+ /// Convert to NSError (for React Native reject)
19
+ func asNSError() -> NSError
20
+ }
21
+
22
+ public extension BrickError {
23
+ /// Default code implementation
24
+ var code: String? { nil }
25
+
26
+ /// Default userInfo implementation
27
+ var userInfo: [String: Any]? { nil }
28
+
29
+ /// Default NSError conversion implementation
30
+ func asNSError() -> NSError {
31
+ var info: [String: Any] = userInfo ?? [:]
32
+ info["code"] = code ?? "EXECUTION_ERROR"
33
+ info["message"] = message
34
+ info[NSLocalizedDescriptionKey] = message
35
+ return NSError(domain: "BrickModule", code: 0, userInfo: info)
36
+ }
37
+
38
+ /// Error code to use when calling reject
39
+ var rejectCode: String {
40
+ code ?? "EXECUTION_ERROR"
41
+ }
42
+ }
43
+
44
+ // MARK: - BrickModuleBase
45
+
4
46
  /**
5
47
  * Base class for all Brick modules
6
48
  * Provides common functionality including event emission
@@ -30,43 +72,35 @@ open class BrickModuleBase: NSObject {
30
72
  }
31
73
  }
32
74
 
75
+ // MARK: - BrickModuleError
76
+
33
77
  /**
34
78
  * Error types for Brick modules
35
79
  */
36
- public enum BrickModuleError: Error, LocalizedError {
80
+ public enum BrickModuleError: BrickError {
37
81
  case typeMismatch(String)
38
82
  case executionError(String)
39
83
  case invalidDefinition(String)
40
84
  case methodNotFound(String)
41
85
  case moduleNotFound(String)
42
-
43
- public var errorDescription: String? {
86
+
87
+ public var code: String? {
44
88
  switch self {
45
- case .typeMismatch(let message):
46
- return "Type mismatch: \(message)"
47
- case .executionError(let message):
48
- return "Execution error: \(message)"
49
- case .invalidDefinition(let message):
50
- return "Invalid definition: \(message)"
51
- case .methodNotFound(let message):
52
- return "Method not found: \(message)"
53
- case .moduleNotFound(let message):
54
- return "Module not found: \(message)"
89
+ case .typeMismatch: return "TYPE_ERROR"
90
+ case .executionError: return "EXECUTION_ERROR"
91
+ case .invalidDefinition: return "DEFINITION_ERROR"
92
+ case .methodNotFound: return "METHOD_NOT_FOUND"
93
+ case .moduleNotFound: return "MODULE_NOT_FOUND"
55
94
  }
56
95
  }
57
-
58
- public var errorCode: String {
96
+
97
+ public var message: String {
59
98
  switch self {
60
- case .typeMismatch:
61
- return "TYPE_ERROR"
62
- case .executionError:
63
- return "EXECUTION_ERROR"
64
- case .invalidDefinition:
65
- return "DEFINITION_ERROR"
66
- case .methodNotFound:
67
- return "METHOD_NOT_FOUND"
68
- case .moduleNotFound:
69
- return "MODULE_NOT_FOUND"
99
+ case .typeMismatch(let msg): return "Type mismatch: \(msg)"
100
+ case .executionError(let msg): return "Execution error: \(msg)"
101
+ case .invalidDefinition(let msg): return "Invalid definition: \(msg)"
102
+ case .methodNotFound(let msg): return "Method not found: \(msg)"
103
+ case .moduleNotFound(let msg): return "Module not found: \(msg)"
70
104
  }
71
105
  }
72
106
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brick-module",
3
- "version": "0.3.1",
3
+ "version": "0.4.1",
4
4
  "description": "Better React Native native module development",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -58,7 +58,7 @@
58
58
  "brick-codegen": "./bin/brick-codegen.js"
59
59
  },
60
60
  "dependencies": {
61
- "brick-codegen": "0.3.1"
61
+ "brick-codegen": "0.4.1"
62
62
  },
63
63
  "peerDependencies": {
64
64
  "react": ">=18.2.0",
package/podfile_helper.rb CHANGED
@@ -27,7 +27,14 @@ def use_brick_modules!(app_path: nil)
27
27
  else
28
28
  File.expand_path(File.join(brick_root, ios_brick_path))
29
29
  end
30
- brick_codegen_podspec_path = File.join(brick_codegen_pod_path, 'BrickCodegen.podspec')
30
+ brick_codegen_podspec_path = File.join(
31
+ brick_codegen_pod_path,
32
+ 'BrickCodegen.podspec'
33
+ )
34
+ brick_codegen_pod_relative_path = relative_pod_path(
35
+ brick_codegen_pod_path,
36
+ podfile_dir
37
+ )
31
38
 
32
39
  # Run brick-codegen with real-time output and colors (iOS only)
33
40
  exit_status = system("cd #{brick_root} && FORCE_COLOR=1 npx brick-codegen --platform ios --projectRoot \"#{brick_root}\"")
@@ -42,8 +49,8 @@ def use_brick_modules!(app_path: nil)
42
49
  end
43
50
 
44
51
  # Link generated BrickCodegen pod
45
- pod 'BrickCodegen', :path => brick_codegen_pod_path
46
- Pod::UI.puts "[Brick] Linked BrickCodegen from #{brick_codegen_pod_path}"
52
+ pod 'BrickCodegen', :path => brick_codegen_pod_relative_path
53
+ Pod::UI.puts "[Brick] Linked BrickCodegen from #{brick_codegen_pod_relative_path}"
47
54
  rescue => e
48
55
  # Re-raise so CocoaPods fails the install
49
56
  raise e
@@ -74,3 +81,19 @@ def get_brick_ios_path(project_root)
74
81
 
75
82
  return File.expand_path(File.join(project_root, 'ios/.brick'))
76
83
  end
84
+
85
+ def podfile_dir
86
+ podfile_path = Pod::Config.instance.podfile_path
87
+ return Pathname.new(Dir.pwd).expand_path if podfile_path.nil?
88
+
89
+ podfile_path.dirname.expand_path
90
+ end
91
+
92
+ def relative_pod_path(target_path, base_dir)
93
+ Pathname.new(target_path)
94
+ .expand_path
95
+ .relative_path_from(base_dir)
96
+ .to_s
97
+ rescue ArgumentError
98
+ target_path
99
+ end
@@ -0,0 +1,78 @@
1
+ /**
2
+ * BrickError - Type-safe class for custom errors from native modules.
3
+ * Matches the structure of iOS/Android BrickError.
4
+ */
5
+ export class BrickError extends Error {
6
+ /** Error code (same as error.name) */
7
+ readonly code: string;
8
+
9
+ /** Additional properties (e.g., amount, currency) */
10
+ readonly userInfo: Record<string, unknown>;
11
+
12
+ /** Name of the module where the error occurred */
13
+ readonly moduleName?: string;
14
+
15
+ constructor(
16
+ message: string,
17
+ code: string,
18
+ userInfo: Record<string, unknown> = {},
19
+ moduleName?: string
20
+ ) {
21
+ super(message);
22
+ this.name = code;
23
+ this.code = code;
24
+ this.userInfo = userInfo;
25
+ this.moduleName = moduleName;
26
+ }
27
+
28
+ /**
29
+ * Type guard to check if an error object is a BrickError.
30
+ * @param error - The error object to check
31
+ * @returns true if the error is a BrickError
32
+ */
33
+ static isBrickError(error: unknown): error is BrickError {
34
+ return (
35
+ error instanceof Error &&
36
+ typeof (error as any).code === "string" &&
37
+ typeof (error as any).userInfo === "object" &&
38
+ (error as any).userInfo !== null
39
+ );
40
+ }
41
+
42
+ /**
43
+ * Converts a general error object to a BrickError.
44
+ * @param error - The error object to convert
45
+ * @param moduleName - Name of the module where the error occurred
46
+ * @returns A BrickError instance
47
+ */
48
+ static from(error: unknown, moduleName?: string): BrickError {
49
+ if (error instanceof BrickError) {
50
+ // Update moduleName if provided
51
+ if (moduleName && !error.moduleName) {
52
+ return new BrickError(error.message, error.code, error.userInfo, moduleName);
53
+ }
54
+ return error;
55
+ }
56
+
57
+ if (BrickError.isBrickError(error)) {
58
+ return new BrickError(
59
+ error.message,
60
+ error.code,
61
+ error.userInfo,
62
+ moduleName || (error as any).moduleName
63
+ );
64
+ }
65
+
66
+ if (error instanceof Error) {
67
+ const anyError = error as any;
68
+ return new BrickError(
69
+ error.message,
70
+ anyError.code || anyError.name || "UNKNOWN_ERROR",
71
+ anyError.userInfo || {},
72
+ moduleName || anyError.moduleName
73
+ );
74
+ }
75
+
76
+ return new BrickError(String(error), "UNKNOWN_ERROR", {}, moduleName);
77
+ }
78
+ }
@@ -5,6 +5,7 @@
5
5
 
6
6
  import type { TurboModule } from "react-native";
7
7
  import { TurboModuleRegistry } from "react-native";
8
+ import { BrickError } from "./BrickError";
8
9
 
9
10
  /**
10
11
  * Base interface that all Brick module specs must extend
@@ -133,18 +134,32 @@ function get<T extends BrickModuleInterface>(
133
134
  // Success: return the unwrapped value
134
135
  return result.value;
135
136
  } else {
136
- // Failure: throw an Error with details from error object
137
+ // Failure: throw a BrickError with all properties from error object
137
138
  const errorInfo = result.error || {};
138
- const error = new Error(errorInfo.message || "Unknown error");
139
- error.name = errorInfo.code || "BRICK_ERROR";
140
- if (errorInfo.details) {
141
- (error as any).details = errorInfo.details;
139
+ const message = errorInfo.message || errorInfo.errorMessage || "Unknown error";
140
+ const code = errorInfo.code || errorInfo.errorCode || "BRICK_ERROR";
141
+
142
+ // Build userInfo from additional properties
143
+ const userInfo: Record<string, unknown> = { code };
144
+ for (const key of Object.keys(errorInfo)) {
145
+ if (key !== "code" && key !== "message" && key !== "errorCode" && key !== "errorMessage") {
146
+ userInfo[key] = errorInfo[key];
147
+ }
142
148
  }
143
- throw error;
149
+
150
+ throw new BrickError(message, code, userInfo, moduleName);
144
151
  }
145
152
  }
146
153
 
147
- // Not wrapped (async method or other): return as-is
154
+ // Async method: wrap Promise and convert to BrickError
155
+ if (result && typeof result.then === "function") {
156
+ return result.catch((error: unknown) => {
157
+ // Convert to BrickError with moduleName for type-safe error handling
158
+ throw BrickError.from(error, moduleName);
159
+ });
160
+ }
161
+
162
+ // Not wrapped: return as-is
148
163
  return result;
149
164
  }
150
165
 
package/src/index.ts CHANGED
@@ -4,8 +4,11 @@
4
4
  export type { BrickModuleInterface, BrickModuleSpec } from "./BrickModule";
5
5
  // Main API exports
6
6
  export { default as BrickModule } from "./BrickModule";
7
+ export { BrickError } from "./BrickError";
8
+
7
9
  /**
8
10
  * Error types for Brick modules
11
+ * @deprecated Use BrickError instead
9
12
  */
10
13
  export class BrickModuleError extends Error {
11
14
  constructor(