@@ -174,6 +174,52 @@ describe('IDBKeyValProvider', () => {
174174 } ) ;
175175 } ) ;
176176
177+ describe ( 'write-error normalization (aborted transactions)' , ( ) => {
178+ // A write transaction aborted by something other than its own request (connection close,
179+ // versionchange, a sibling transaction) leaves `transaction.error === null`. idb-keyval
180+ // rejects with that null, which is unclassifiable: it slips past createStore's heal guards
181+ // (they require an Error/DOMException) and renders as the production log line
182+ // "[Onyx] Failed to save to storage. Error: null". Every write path must instead reject
183+ // with a real Error so the failure can be classified and retried sanely.
184+ function abortTransactionOnPut ( ) {
185+ const originalPut = IDBObjectStore . prototype . put ;
186+ jest . spyOn ( IDBObjectStore . prototype , 'put' ) . mockImplementation ( function put ( this : IDBObjectStore , ...args : Parameters < IDBObjectStore [ 'put' ] > ) {
187+ const request = originalPut . apply ( this , args ) ;
188+ this . transaction . abort ( ) ;
189+ return request ;
190+ } ) ;
191+ }
192+
193+ function expectAbortError ( error : unknown ) {
194+ expect ( error ) . not . toBeNull ( ) ;
195+ expect ( error ) . toBeInstanceOf ( DOMException ) ;
196+ expect ( ( error as DOMException ) . name ) . toBe ( 'AbortError' ) ;
197+ expect ( ( error as DOMException ) . message . length ) . toBeGreaterThan ( 0 ) ;
198+ }
199+
200+ afterEach ( ( ) => {
201+ jest . restoreAllMocks ( ) ;
202+ } ) ;
203+
204+ it ( 'setItem rejects with a tagged AbortError, never null' , async ( ) => {
205+ abortTransactionOnPut ( ) ;
206+ const error = await IDBKeyValProvider . setItem ( ONYXKEYS . TEST_KEY , 'value' ) . catch ( ( e : unknown ) => e ) ;
207+ expectAbortError ( error ) ;
208+ } ) ;
209+
210+ it ( 'multiSet rejects with a tagged AbortError, never null' , async ( ) => {
211+ abortTransactionOnPut ( ) ;
212+ const error = await IDBKeyValProvider . multiSet ( [ [ ONYXKEYS . TEST_KEY , 'value' ] ] ) . catch ( ( e : unknown ) => e ) ;
213+ expectAbortError ( error ) ;
214+ } ) ;
215+
216+ it ( 'multiMerge rejects with a tagged AbortError, never null' , async ( ) => {
217+ abortTransactionOnPut ( ) ;
218+ const error = await IDBKeyValProvider . multiMerge ( [ [ ONYXKEYS . TEST_KEY , 'value' ] ] ) . catch ( ( e : unknown ) => e ) ;
219+ expectAbortError ( error ) ;
220+ } ) ;
221+ } ) ;
222+
177223 describe ( 'mergeItem' , ( ) => {
178224 it ( 'should merge all the supported kinds of data correctly' , async ( ) => {
179225 await IDB . set ( ONYXKEYS . TEST_KEY , 'value' , IDBKeyValProvider . store ) ;
0 commit comments