brick-module 0.3.1 → 0.4.0

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() ?: [])
@@ -402,7 +425,12 @@ ${moduleNames}
402
425
  // which contains package-aware-r.txt needed by dependent modules
403
426
  def brickOutputDir = getBrickAndroidPath(workingDir)
404
427
  def generatedMarker = new File(brickOutputDir, "src/main/kotlin/BrickModule.kt")
405
- if (generatedMarker.exists()) {
428
+ def androidAppPath = getBrickAndroidAppPath(workingDir)
429
+ def jniDir = new File(androidAppPath, "build/generated/autolinking/src/main/jni")
430
+ def hasJniOutputs = new File(jniDir, "BrickModuleSpec-generated.cpp").exists() &&
431
+ new File(jniDir, "BrickModuleSpec.h").exists() &&
432
+ new File(jniDir, "CMakeLists.txt").exists()
433
+ if (generatedMarker.exists() && hasJniOutputs) {
406
434
  // Already generated, skip to preserve build outputs
407
435
  return
408
436
  }
@@ -419,7 +447,11 @@ ${moduleNames}
419
447
  def codegenPath = new File(brickModuleDir, "bin/brick-codegen.js")
420
448
 
421
449
  if (codegenPath.exists()) {
422
- def codegenProc = ['node', codegenPath.absolutePath, '--platform', 'android', '--projectRoot', workingDir.absolutePath].execute(null, workingDir)
450
+ def codegenArgs = ['node', codegenPath.absolutePath, '--platform', 'android', '--projectRoot', workingDir.absolutePath]
451
+ if (generatedMarker.exists()) {
452
+ codegenArgs.add('--no-clean')
453
+ }
454
+ def codegenProc = codegenArgs.execute(null, workingDir)
423
455
  codegenProc.waitFor()
424
456
  }
425
457
  }
@@ -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")
@@ -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,17 +1,9 @@
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
- * Error types for Brick modules
8
- */
9
- declare class BrickModuleError extends Error {
10
- code: string;
11
- moduleName?: string | undefined;
12
- methodName?: string | undefined;
13
- constructor(message: string, code?: string, moduleName?: string | undefined, methodName?: string | undefined);
14
- }
15
7
  /**
16
8
  * Configuration interface for brick-codegen
17
9
  */
@@ -29,4 +21,4 @@ interface BrickCodegenConfig {
29
21
  dev?: boolean;
30
22
  }
31
23
  //#endregion
32
- export { Any, AnyObject, BrickCodegenConfig, BrickModule, BrickModuleError, type BrickModuleInterface, type BrickModuleSpec };
24
+ export { Any, AnyObject, BrickCodegenConfig, BrickError, BrickModule, type BrickModuleInterface, type BrickModuleSpec };
package/dist/index.js CHANGED
@@ -1,18 +1,4 @@
1
+ import { BrickError } from "./BrickError.js";
1
2
  import BrickModule_default from "./BrickModule.js";
2
3
 
3
- //#region src/index.ts
4
- /**
5
- * Error types for Brick modules
6
- */
7
- var BrickModuleError = class extends Error {
8
- constructor(message, code = "BRICK_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 };
4
+ export { BrickError, BrickModule_default as BrickModule };
@@ -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.0",
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.0"
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,20 +4,7 @@
4
4
  export type { BrickModuleInterface, BrickModuleSpec } from "./BrickModule";
5
5
  // Main API exports
6
6
  export { default as BrickModule } from "./BrickModule";
7
- /**
8
- * Error types for Brick modules
9
- */
10
- export class BrickModuleError extends Error {
11
- constructor(
12
- message: string,
13
- public code: string = "BRICK_ERROR",
14
- public moduleName?: string,
15
- public methodName?: string
16
- ) {
17
- super(message);
18
- this.name = "BrickModuleError";
19
- }
20
- }
7
+ export { BrickError } from "./BrickError";
21
8
 
22
9
  /**
23
10
  * Configuration interface for brick-codegen