@@ -6,42 +6,61 @@ generated by the `wasm-pack` command (which uses `wasm-bindgen`).
66While the ` wasm-bindgen ` crate allows some customization of the emitted type signatures, it
77is a bit painful to use and has certain limitations that cannot be easily worked around.
88
9- ## Architecture Pattern
9+ ## Architecture Patterns
1010
11- This directory implements a ** namespace wrapper pattern** that provides a cleaner, more
12- type-safe API over the raw WASM bindings.
11+ This directory implements two complementary patterns to provide cleaner, more type-safe APIs over the raw WASM bindings:
1312
14- ### Pattern Overview
13+ 1 . ** Namespace Wrapper Pattern** - For static utility functions
14+ 2 . ** Class Wrapper Pattern** - For stateful objects with methods
15+
16+ ### Common Elements
1517
16181 . ** WASM Generation** (` wasm/wasm_utxo.d.ts ` )
1719
1820 - Generated by ` wasm-bindgen ` from Rust code
19- - Exports classes with static methods (e.g., ` AddressNamespace ` , ` UtxolibCompatNamespace ` )
20- - Uses ` snake_case ` naming (Rust convention)
21+ - Uses ` snake_case ` naming (Rust convention) - ** no ` js_name ` overrides in Rust**
2122 - Uses loose types (` any ` , ` string | null ` ) due to WASM-bindgen limitations
23+ - TypeScript wrapper layer handles conversion to ` camelCase `
2224
23- 2 . ** Namespace Wrapper Files** (e.g., ` address.ts ` , ` utxolibCompat.ts ` , ` fixedScriptWallet.ts ` )
24-
25- - Import the generated WASM namespace classes
26- - Define precise TypeScript types to replace ` any ` types
27- - Export individual functions that wrap the static WASM methods
28- - Convert ` snake_case ` WASM methods to ` camelCase ` (JavaScript convention)
29- - Re-export related types for convenience
30-
31- 3 . ** Shared Type Files** (e.g., ` coinName.ts ` , ` triple.ts ` )
25+ 2 . ** Shared Type Files** (e.g., ` coinName.ts ` , ` triple.ts ` )
3226
3327 - Define common types used across multiple modules
3428 - Single source of truth to avoid duplication
3529 - Imported by wrapper files as needed
3630
37- 4 . ** Main Entry Point** (` index.ts ` )
31+ 3 . ** Main Entry Point** (` index.ts ` )
3832 - Uses ` export * as ` to group related functionality into namespaces
39- - Re-exports shared types for top-level access
33+ - Re-exports shared types and classes for top-level access
4034 - Augments WASM types with additional TypeScript declarations
4135
42- ### Example
36+ ### Pattern 1: Namespace Wrapper Pattern
37+
38+ Used for static utility functions (e.g., ` address.ts ` , ` utxolibCompat.ts ` ).
39+
40+ ** Characteristics:**
41+
42+ - Import the generated WASM namespace classes
43+ - Define precise TypeScript types to replace ` any ` types
44+ - Export individual functions that wrap the static WASM methods
45+ - Convert ` snake_case ` WASM methods to ` camelCase ` (JavaScript convention)
46+ - Re-export related types for convenience
47+
48+ ### Pattern 2: Class Wrapper Pattern
49+
50+ Used for stateful objects that maintain WASM instances (e.g., ` BIP32 ` , ` RootWalletKeys ` , ` BitGoPsbt ` ).
51+
52+ ** Characteristics:**
4353
44- Given a WASM-generated class:
54+ - Private ` _wasm ` property holds the underlying WASM instance
55+ - Private constructor prevents direct instantiation
56+ - Static factory methods (camelCase) for object creation
57+ - Instance methods (camelCase) wrap WASM methods and return wrapped instances when appropriate
58+ - Public ` wasm ` getter for internal access to WASM instance (marked ` @internal ` )
59+ - Implements interfaces to ensure compatibility with existing code
60+
61+ ### Example 1: Namespace Wrapper Pattern
62+
63+ Given a WASM-generated namespace class:
4564
4665``` typescript
4766// wasm/wasm_utxo.d.ts (generated by wasm-bindgen)
@@ -88,10 +107,110 @@ And expose it via the main entry point:
88107export * as address from " ./address" ;
89108```
90109
110+ ### Example 2: Class Wrapper Pattern
111+
112+ Given a WASM-generated class with instance methods:
113+
114+ ``` typescript
115+ // wasm/wasm_utxo.d.ts (generated by wasm-bindgen)
116+ export class WasmBIP32 {
117+ private constructor ();
118+ // Note: snake_case naming from Rust (no js_name overrides)
119+ static from_base58(base58_str : string ): WasmBIP32 ;
120+ derive(index : number ): WasmBIP32 ;
121+ derive_path(path : string ): WasmBIP32 ;
122+ to_base58(): string ;
123+ readonly public_key: Uint8Array ;
124+ }
125+ ```
126+
127+ We create a wrapper class that encapsulates the WASM instance:
128+
129+ ``` typescript
130+ // bip32.ts
131+ import { WasmBIP32 } from " ./wasm/wasm_utxo" ;
132+
133+ export class BIP32 {
134+ // Private property with underscore prefix
135+ private constructor (private _wasm : WasmBIP32 ) {}
136+
137+ // Static factory method (camelCase) calls snake_case WASM method
138+ static fromBase58(base58Str : string ): BIP32 {
139+ const wasm = WasmBIP32 .from_base58 (base58Str );
140+ return new BIP32 (wasm );
141+ }
142+
143+ // Property getter (camelCase) accesses snake_case WASM property
144+ get publicKey(): Uint8Array {
145+ return this ._wasm .public_key ;
146+ }
147+
148+ // Instance method (camelCase) returns wrapped instance
149+ derive(index : number ): BIP32 {
150+ const wasm = this ._wasm .derive (index );
151+ return new BIP32 (wasm );
152+ }
153+
154+ // Convert snake_case to camelCase
155+ derivePath(path : string ): BIP32 {
156+ const wasm = this ._wasm .derive_path (path );
157+ return new BIP32 (wasm );
158+ }
159+
160+ // Convert snake_case to camelCase
161+ toBase58(): string {
162+ return this ._wasm .to_base58 ();
163+ }
164+
165+ // Public getter for internal use (marked @internal)
166+ /**
167+ * @internal
168+ */
169+ get wasm(): WasmBIP32 {
170+ return this ._wasm ;
171+ }
172+ }
173+ ```
174+
175+ And expose it directly:
176+
177+ ``` typescript
178+ // index.ts
179+ export { BIP32 } from " ./bip32" ;
180+ ```
181+
91182### Benefits
92183
184+ ** Common to Both Patterns:**
185+
93186- ** Type Safety** : Replace loose ` any ` and ` string ` types with precise union types
94- - ** Idiomatic Naming** : Each layer uses its native convention (` snake_case ` in Rust, ` camelCase ` in JavaScript)
187+ - ** Idiomatic Naming** : Each layer uses its native convention (` snake_case ` in Rust/WASM, ` camelCase ` in TypeScript/JavaScript)
188+ - Rust exports use ` snake_case ` (no ` js_name ` overrides)
189+ - TypeScript wrappers provide ` camelCase ` API
95190- ** Better DX** : IDE autocomplete works better with concrete types and familiar naming
96191- ** Maintainability** : Centralized type definitions prevent duplication
97192- ** Clear Separation** : WASM bindings stay pure to Rust conventions, TypeScript handles JS conventions
193+
194+ ** Class Wrapper Pattern Specific:**
195+
196+ - ** Encapsulation** : Private ` _wasm ` property hides implementation details
197+ - ** Controlled Access** : Private constructor forces use of factory methods
198+ - ** Consistent Returns** : Methods that return new instances automatically wrap them
199+ - ** Internal Access** : Public ` wasm ` getter allows internal code to access WASM instance when needed
200+ - ** Type Compatibility** : Can implement interfaces to maintain backward compatibility
201+
202+ ### When to Use Which Pattern
203+
204+ ** Use Namespace Wrapper Pattern when:**
205+
206+ - Functions are stateless utilities
207+ - No need to maintain WASM instance state
208+ - Simple input → output transformations
209+ - Examples: address encoding/decoding, network conversions
210+
211+ ** Use Class Wrapper Pattern when:**
212+
213+ - Object represents stateful data (keys, PSBTs, etc.)
214+ - Methods need to return new instances of the same type
215+ - Need to encapsulate underlying WASM instance
216+ - Examples: BIP32 keys, RootWalletKeys, BitGoPsbt
0 commit comments