@@ -231,4 +231,267 @@ describe('pongoCollection cache integration', () => {
231231 expect ( doc ?. name ) . toBe ( 'Karl' ) ;
232232 } ) ;
233233 } ) ;
234+
235+ describe ( 'cache type resolution cascade' , ( ) => {
236+ it ( 'collection identity-map overrides client in-memory' , async ( ) => {
237+ client = pongoClient ( {
238+ driver : sqlite3Driver ,
239+ connectionString : memoryConnectionString ( ) ,
240+ cache : { type : 'in-memory' , max : 5 } ,
241+ } ) ;
242+ await client . connect ( ) ;
243+
244+ const col = client
245+ . db ( 'db' )
246+ . collection < User > ( 'users' , { cache : { type : 'identity-map' } } ) ;
247+
248+ const ids : string [ ] = [ ] ;
249+ for ( let i = 0 ; i < 10 ; i ++ ) {
250+ const { insertedId } = await col . insertOne ( { name : `User${ i } ` } ) ;
251+ ids . push ( insertedId ! ) ;
252+ }
253+
254+ for ( const id of ids ) {
255+ const doc = await col . findOne ( { _id : id } ) ;
256+ expect ( doc ) . not . toBeNull ( ) ;
257+ }
258+ } ) ;
259+
260+ it ( 'client identity-map is used by collection with no override' , async ( ) => {
261+ client = pongoClient ( {
262+ driver : sqlite3Driver ,
263+ connectionString : memoryConnectionString ( ) ,
264+ cache : { type : 'identity-map' } ,
265+ } ) ;
266+ await client . connect ( ) ;
267+
268+ const col = client . db ( 'db' ) . collection < User > ( 'users' ) ;
269+ const ids : string [ ] = [ ] ;
270+ for ( let i = 0 ; i < 2000 ; i ++ ) {
271+ const { insertedId } = await col . insertOne ( { name : `User${ i } ` } ) ;
272+ ids . push ( insertedId ! ) ;
273+ }
274+
275+ for ( const id of ids ) {
276+ const doc = await col . findOne ( { _id : id } ) ;
277+ expect ( doc ) . not . toBeNull ( ) ;
278+ }
279+ } ) ;
280+
281+ it ( 'collection disabled overrides client identity-map' , async ( ) => {
282+ client = pongoClient ( {
283+ driver : sqlite3Driver ,
284+ connectionString : memoryConnectionString ( ) ,
285+ cache : { type : 'identity-map' } ,
286+ } ) ;
287+ await client . connect ( ) ;
288+
289+ const db = client . db ( 'db' ) ;
290+ const col = db . collection < User > ( 'users' , { cache : 'disabled' } ) ;
291+ const noCache = db . collection < User > ( 'users' , { cache : 'disabled' } ) ;
292+
293+ const { insertedId } = await col . insertOne ( { name : 'Leo' } ) ;
294+ await noCache . updateOne (
295+ { _id : insertedId ! } ,
296+ { $set : { name : 'LeoUpdated' } } ,
297+ ) ;
298+
299+ const doc = await col . findOne ( { _id : insertedId ! } ) ;
300+ expect ( doc ?. name ) . toBe ( 'LeoUpdated' ) ;
301+ } ) ;
302+
303+ it ( 'two collections can use different cache types on the same db' , async ( ) => {
304+ client = pongoClient ( {
305+ driver : sqlite3Driver ,
306+ connectionString : memoryConnectionString ( ) ,
307+ } ) ;
308+ await client . connect ( ) ;
309+
310+ const db = client . db ( 'db' ) ;
311+ const lruCol = db . collection < User > ( 'users' , {
312+ cache : { type : 'in-memory' , max : 5 } ,
313+ } ) ;
314+ const idMapCol = db . collection < User > ( 'orders' , {
315+ cache : { type : 'identity-map' } ,
316+ } ) ;
317+
318+ const lruIds : string [ ] = [ ] ;
319+ const idMapIds : string [ ] = [ ] ;
320+
321+ for ( let i = 0 ; i < 10 ; i ++ ) {
322+ const { insertedId : u } = await lruCol . insertOne ( { name : `User${ i } ` } ) ;
323+ lruIds . push ( u ! ) ;
324+ const { insertedId : o } = await idMapCol . insertOne ( {
325+ name : `Order${ i } ` ,
326+ } ) ;
327+ idMapIds . push ( o ! ) ;
328+ }
329+
330+ // LRU with max 5: oldest 5 should be evicted from cache (reads go to DB)
331+ // identity-map: all 10 present in cache
332+ for ( const id of idMapIds ) {
333+ const doc = await idMapCol . findOne ( { _id : id } ) ;
334+ expect ( doc ) . not . toBeNull ( ) ;
335+ }
336+
337+ // At least the most-recent LRU entries should still be in cache
338+ const recent = lruIds . slice ( - 5 ) ;
339+ for ( const id of recent ) {
340+ const doc = await lruCol . findOne ( { _id : id } ) ;
341+ expect ( doc ) . not . toBeNull ( ) ;
342+ }
343+ } ) ;
344+ } ) ;
345+
346+ describe ( 'transaction cache integration' , ( ) => {
347+ beforeEach ( async ( ) => {
348+ client = pongoClient ( {
349+ driver : sqlite3Driver ,
350+ connectionString : memoryConnectionString ( ) ,
351+ } ) ;
352+ await client . connect ( ) ;
353+ } ) ;
354+
355+ it ( 'insertOne within transaction does NOT populate collection cache until commit' , async ( ) => {
356+ const { cache, spies } = spyCache ( 'col' ) ;
357+ const col = client . db ( 'db' ) . collection < User > ( 'users' , { cache } ) ;
358+
359+ const session = client . startSession ( ) ;
360+ session . startTransaction ( ) ;
361+ await col . insertOne ( { name : 'Alice' } , { session } ) ;
362+
363+ expect ( spies . set ) . not . toHaveBeenCalled ( ) ;
364+
365+ await session . commitTransaction ( ) ;
366+ await session . endSession ( ) ;
367+
368+ expect ( spies . set ) . toHaveBeenCalled ( ) ;
369+ } ) ;
370+
371+ it ( 'findOne within transaction returns doc from transaction cache' , async ( ) => {
372+ const { cache, spies } = spyCache ( 'col' ) ;
373+ const col = client . db ( 'db' ) . collection < User > ( 'users' , { cache } ) ;
374+
375+ const session = client . startSession ( ) ;
376+ session . startTransaction ( ) ;
377+ const { insertedId } = await col . insertOne (
378+ { name : 'Carol' } ,
379+ { session } ,
380+ ) ;
381+
382+ const doc = await col . findOne ( { _id : insertedId ! } , { session } ) ;
383+ expect ( doc ?. name ) . toBe ( 'Carol' ) ;
384+ // should have hit tx cache, not collection cache get
385+ expect ( spies . get ) . not . toHaveBeenCalled ( ) ;
386+
387+ await session . abortTransaction ( ) ;
388+ await session . endSession ( ) ;
389+ } ) ;
390+
391+ it ( 'findOne within transaction falls through to collection cache on tx miss' , async ( ) => {
392+ const { cache } = spyCache ( 'col' ) ;
393+ const col = client . db ( 'db' ) . collection < User > ( 'users' , { cache } ) ;
394+
395+ const { insertedId } = await col . insertOne ( { name : 'Dave' } ) ;
396+
397+ const session = client . startSession ( ) ;
398+ session . startTransaction ( ) ;
399+ const doc = await col . findOne ( { _id : insertedId ! } , { session } ) ;
400+ expect ( doc ?. name ) . toBe ( 'Dave' ) ;
401+
402+ await session . abortTransaction ( ) ;
403+ await session . endSession ( ) ;
404+ } ) ;
405+
406+ it ( 'rollback discards buffered cache ops — collection cache stays clean' , async ( ) => {
407+ const { cache, spies } = spyCache ( 'col' ) ;
408+ const col = client . db ( 'db' ) . collection < User > ( 'users' , { cache } ) ;
409+
410+ const session = client . startSession ( ) ;
411+ session . startTransaction ( ) ;
412+ await col . insertOne ( { name : 'Bob' } , { session } ) ;
413+ await session . abortTransaction ( ) ;
414+ await session . endSession ( ) ;
415+
416+ expect ( spies . set ) . not . toHaveBeenCalled ( ) ;
417+ } ) ;
418+
419+ it ( 'deleteOne within transaction does NOT evict from collection cache until commit' , async ( ) => {
420+ const { cache, spies } = spyCache ( 'col' ) ;
421+ const col = client . db ( 'db' ) . collection < User > ( 'users' , { cache } ) ;
422+
423+ const { insertedId } = await col . insertOne ( { name : 'Eve' } ) ;
424+ spies . delete . mockClear ( ) ;
425+
426+ const session = client . startSession ( ) ;
427+ session . startTransaction ( ) ;
428+ await col . deleteOne ( { _id : insertedId ! } , { session } ) ;
429+
430+ expect ( spies . delete ) . not . toHaveBeenCalled ( ) ;
431+
432+ await session . commitTransaction ( ) ;
433+ await session . endSession ( ) ;
434+
435+ expect ( spies . delete ) . toHaveBeenCalled ( ) ;
436+ } ) ;
437+
438+ it ( 'updateOne within transaction does NOT evict from collection cache until commit' , async ( ) => {
439+ const { cache, spies } = spyCache ( 'col' ) ;
440+ const col = client . db ( 'db' ) . collection < User > ( 'users' , { cache } ) ;
441+
442+ const { insertedId } = await col . insertOne ( { name : 'Frank' } ) ;
443+ spies . delete . mockClear ( ) ;
444+
445+ const session = client . startSession ( ) ;
446+ session . startTransaction ( ) ;
447+ await col . updateOne (
448+ { _id : insertedId ! } ,
449+ { $set : { name : 'Updated' } } ,
450+ { session } ,
451+ ) ;
452+
453+ expect ( spies . delete ) . not . toHaveBeenCalled ( ) ;
454+
455+ // reading without session should still return cached 'Frank'
456+ const doc = await col . findOne ( { _id : insertedId ! } ) ;
457+ expect ( doc ?. name ) . toBe ( 'Frank' ) ;
458+
459+ await session . commitTransaction ( ) ;
460+ await session . endSession ( ) ;
461+
462+ expect ( spies . delete ) . toHaveBeenCalled ( ) ;
463+ } ) ;
464+
465+ it ( 'replaceOne within transaction buffers cache set until commit' , async ( ) => {
466+ const { cache, spies } = spyCache ( 'col' ) ;
467+ const col = client . db ( 'db' ) . collection < User > ( 'users' , { cache } ) ;
468+
469+ const { insertedId } = await col . insertOne ( { name : 'Grace' } ) ;
470+ spies . set . mockClear ( ) ;
471+
472+ const session = client . startSession ( ) ;
473+ session . startTransaction ( ) ;
474+ await col . replaceOne (
475+ { _id : insertedId ! } ,
476+ { name : 'Replaced' } ,
477+ { session } ,
478+ ) ;
479+
480+ expect ( spies . set ) . not . toHaveBeenCalled ( ) ;
481+
482+ await session . commitTransaction ( ) ;
483+ await session . endSession ( ) ;
484+
485+ expect ( spies . set ) . toHaveBeenCalled ( ) ;
486+ } ) ;
487+
488+ it ( 'without transaction, operations update collection cache directly' , async ( ) => {
489+ const { cache, spies } = spyCache ( 'col' ) ;
490+ const col = client . db ( 'db' ) . collection < User > ( 'users' , { cache } ) ;
491+
492+ await col . insertOne ( { name : 'Hank' } ) ;
493+
494+ expect ( spies . set ) . toHaveBeenCalled ( ) ;
495+ } ) ;
496+ } ) ;
234497} ) ;
0 commit comments