@@ -18,13 +18,51 @@ interface PaymentStore {
1818 delete ( id : string ) : Promise < boolean > ;
1919}
2020
21+ /**
22+ * Simple async mutex implementation for concurrency safety
23+ */
24+ class AsyncMutex {
25+ private locked = false ;
26+ private waitQueue : Array < ( ) => void > = [ ] ;
27+
28+ async acquire ( ) : Promise < void > {
29+ return new Promise < void > ( ( resolve ) => {
30+ if ( ! this . locked ) {
31+ this . locked = true ;
32+ resolve ( ) ;
33+ } else {
34+ this . waitQueue . push ( resolve ) ;
35+ }
36+ } ) ;
37+ }
38+
39+ release ( ) : void {
40+ if ( this . waitQueue . length > 0 ) {
41+ const next = this . waitQueue . shift ( ) ;
42+ if ( next ) next ( ) ;
43+ } else {
44+ this . locked = false ;
45+ }
46+ }
47+
48+ async withLock < T > ( fn : ( ) => Promise < T > ) : Promise < T > {
49+ await this . acquire ( ) ;
50+ try {
51+ return await fn ( ) ;
52+ } finally {
53+ this . release ( ) ;
54+ }
55+ }
56+ }
57+
2158/**
2259 * In-memory payment store implementation
2360 *
2461 * ⚠️ CONCURRENCY & SCALING LIMITATIONS:
25- * This in-memory store is NOT suitable for production use beyond demos due to:
62+ * This in-memory store includes basic concurrency safety via async mutex but is still
63+ * NOT suitable for production use beyond demos due to:
2664 *
27- * 1. NO CONCURRENCY SAFETY: Multiple concurrent operations can cause data corruption
65+ * 1. LIMITED CONCURRENCY: Basic mutex prevents corruption but reduces throughput
2866 * 2. NO PERSISTENCE: Data is lost when the process restarts
2967 * 3. NO SCALING: Limited by single-process memory constraints
3068 * 4. NO DISTRIBUTION: Cannot share data across multiple instances
@@ -37,43 +75,31 @@ interface PaymentStore {
3775 */
3876class MemoryPaymentStore implements PaymentStore {
3977 private payments = new Map < string , PaymentRecord > ( ) ;
40-
41- // Track concurrent operations to warn about potential issues
42- private activeOperations = 0 ;
78+ private mutex = new AsyncMutex ( ) ;
4379
4480 async save ( record : PaymentRecord ) : Promise < void > {
45- this . activeOperations ++ ;
46- if ( this . activeOperations > 1 ) {
47- console . warn ( '⚠️ CONCURRENCY WARNING: Multiple concurrent operations detected in MemoryPaymentStore' ) ;
48- console . warn ( ' This can lead to data corruption. Consider upgrading to a database-backed store.' ) ;
49- }
50-
51- try {
81+ await this . mutex . withLock ( async ( ) => {
5282 this . payments . set ( record . id , { ...record } ) ;
53- } finally {
54- this . activeOperations -- ;
55- }
83+ } ) ;
5684 }
5785
5886 async load ( id : string ) : Promise < PaymentRecord | null > {
59- const record = this . payments . get ( id ) ;
60- return record ? { ...record } : null ;
87+ return this . mutex . withLock ( async ( ) => {
88+ const record = this . payments . get ( id ) ;
89+ return record ? { ...record } : null ;
90+ } ) ;
6191 }
6292
6393 async loadByType ( type : RequestType ) : Promise < PaymentRecord [ ] > {
64- return Array . from ( this . payments . values ( ) )
65- . filter ( record => record . request . type === type )
66- . map ( record => ( { ...record } ) ) ;
94+ return this . mutex . withLock ( async ( ) => {
95+ return Array . from ( this . payments . values ( ) )
96+ . filter ( record => record . request . type === type )
97+ . map ( record => ( { ...record } ) ) ;
98+ } ) ;
6799 }
68100
69101 async update ( id : string , updates : Partial < PaymentRecord > ) : Promise < PaymentRecord > {
70- this . activeOperations ++ ;
71- if ( this . activeOperations > 1 ) {
72- console . warn ( '⚠️ CONCURRENCY WARNING: Multiple concurrent operations detected in MemoryPaymentStore' ) ;
73- console . warn ( ' This can lead to race conditions. Consider upgrading to a database with transactions.' ) ;
74- }
75-
76- try {
102+ return this . mutex . withLock ( async ( ) => {
77103 const existing = this . payments . get ( id ) ;
78104 if ( ! existing ) {
79105 throw new Error ( `Payment record not found: ${ id } ` ) ;
@@ -87,55 +113,106 @@ class MemoryPaymentStore implements PaymentStore {
87113
88114 this . payments . set ( id , updated ) ;
89115 return { ...updated } ;
90- } finally {
91- this . activeOperations -- ;
92- }
116+ } ) ;
93117 }
94118
95119 async delete ( id : string ) : Promise < boolean > {
96- return this . payments . delete ( id ) ;
120+ return this . mutex . withLock ( async ( ) => {
121+ return this . payments . delete ( id ) ;
122+ } ) ;
97123 }
98124}
99125
100126/**
101127 * Payment ID parser for extracting type and network information
102128 */
103129class PaymentIdParser {
130+ // Regex pattern for valid payment ID format
131+ private static readonly ID_PATTERN = / ^ ( [ a - z A - Z ] + ) _ ( [ a - z A - Z 0 - 9 ] + ) _ ( \d + ) _ ( [ a - z A - Z 0 - 9 ] + ) $ / ;
132+
104133 /**
105- * Parse payment ID to extract metadata
134+ * Parse payment ID to extract metadata with robust validation
106135 */
107136 static parse ( paymentId : string ) : {
108137 id : string ;
109138 type ?: RequestType ;
110139 network ?: SVMNetwork ;
111140 timestamp ?: number ;
112141 } {
113- // Expected format: {type}_{network}_{timestamp}_{random}
114- const parts = paymentId . split ( '_' ) ;
142+ if ( ! paymentId || typeof paymentId !== 'string' ) {
143+ throw new Error ( 'Invalid payment ID: must be a non-empty string' ) ;
144+ }
115145
116- if ( parts . length >= 4 ) {
117- const [ typeStr , networkStr , timestampStr ] = parts ;
146+ // Test against expected format: {type}_{network}_{timestamp}_{random}
147+ const match = this . ID_PATTERN . exec ( paymentId ) ;
148+
149+ if ( match ) {
150+ const [ , typeStr , networkStr , timestampStr ] = match ;
151+
152+ // Validate type
153+ const type = typeStr . toUpperCase ( ) as RequestType ;
154+ if ( ! Object . values ( RequestType ) . includes ( type ) ) {
155+ console . warn ( `Warning: Unknown request type '${ typeStr } ' in payment ID ${ paymentId } ` ) ;
156+ }
157+
158+ // Validate network
159+ const network = networkStr . toUpperCase ( ) as SVMNetwork ;
160+ if ( ! Object . values ( SVMNetwork ) . includes ( network ) ) {
161+ console . warn ( `Warning: Unknown network '${ networkStr } ' in payment ID ${ paymentId } ` ) ;
162+ }
163+
164+ // Validate timestamp
165+ const timestamp = parseInt ( timestampStr , 10 ) ;
166+ if ( isNaN ( timestamp ) || timestamp <= 0 ) {
167+ throw new Error ( `Invalid timestamp in payment ID: ${ paymentId } ` ) ;
168+ }
118169
119170 return {
120171 id : paymentId ,
121- type : typeStr . toUpperCase ( ) as RequestType ,
122- network : networkStr . toUpperCase ( ) as SVMNetwork ,
123- timestamp : parseInt ( timestampStr , 10 )
172+ type,
173+ network,
174+ timestamp
124175 } ;
125176 }
126177
178+ // Log warning for malformed IDs but don't throw to maintain backward compatibility
179+ console . warn ( `Warning: Payment ID '${ paymentId } ' does not match expected format. Using as simple ID.` ) ;
180+ console . warn ( 'Expected format: {type}_{network}_{timestamp}_{random}' ) ;
181+
127182 // Fallback for simple IDs
128183 return { id : paymentId } ;
129184 }
130185
131186 /**
132- * Generate a structured payment ID
187+ * Generate a structured payment ID with validation
133188 */
134189 static generate ( type : RequestType , network : SVMNetwork ) : string {
190+ if ( ! type || ! network ) {
191+ throw new Error ( 'Type and network are required to generate payment ID' ) ;
192+ }
193+
194+ if ( ! Object . values ( RequestType ) . includes ( type ) ) {
195+ throw new Error ( `Invalid request type: ${ type } ` ) ;
196+ }
197+
198+ if ( ! Object . values ( SVMNetwork ) . includes ( network ) ) {
199+ throw new Error ( `Invalid network: ${ network } ` ) ;
200+ }
201+
135202 const timestamp = Date . now ( ) ;
136203 const random = Math . random ( ) . toString ( 36 ) . substring ( 2 , 8 ) ;
137204 return `${ type . toLowerCase ( ) } _${ network . toLowerCase ( ) } _${ timestamp } _${ random } ` ;
138205 }
206+
207+ /**
208+ * Validate payment ID format without parsing
209+ */
210+ static isValid ( paymentId : string ) : boolean {
211+ if ( ! paymentId || typeof paymentId !== 'string' ) {
212+ return false ;
213+ }
214+ return this . ID_PATTERN . test ( paymentId ) ;
215+ }
139216}
140217
141218/**
0 commit comments