@@ -28,6 +28,7 @@ import {
2828 createOpfsPersister ,
2929 createSessionPersister ,
3030} from 'tinybase/persisters/persister-browser' ;
31+ import { createDurableObjectStoragePersister } from 'tinybase/persisters/persister-durable-object-storage' ;
3132import { createFilePersister } from 'tinybase/persisters/persister-file' ;
3233import { createIndexedDbPersister } from 'tinybase/persisters/persister-indexed-db' ;
3334import { createRemotePersister } from 'tinybase/persisters/persister-remote' ;
@@ -778,3 +779,133 @@ export const mockAutomerge: Persistable<DocHandle<any>> = {
778779 testMissing : false ,
779780 testAutoLoad : true ,
780781} ;
782+
783+ // Mock DurableObjectStorage - simple Map-based implementation
784+ class MockDurableObjectStorage {
785+ private data = new Map < string , any > ( ) ;
786+
787+ async get < T > ( key : string ) : Promise < T | undefined > ;
788+ async get < T > ( keys : string [ ] ) : Promise < Map < string , T > > ;
789+ async get < T > (
790+ keyOrKeys : string | string [ ] ,
791+ ) : Promise < T | undefined | Map < string , T > > {
792+ if ( Array . isArray ( keyOrKeys ) ) {
793+ const result = new Map < string , T > ( ) ;
794+ for ( const key of keyOrKeys ) {
795+ const value = this . data . get ( key ) ;
796+ if ( value !== undefined ) result . set ( key , value ) ;
797+ }
798+ return result ;
799+ }
800+ return this . data . get ( keyOrKeys ) ;
801+ }
802+
803+ async put ( entries : Record < string , any > ) : Promise < void > {
804+ for ( const [ key , value ] of Object . entries ( entries ) ) {
805+ this . data . set ( key , value ) ;
806+ }
807+ }
808+
809+ async list < T > ( options ?: { prefix ?: string } ) : Promise < Map < string , T > > {
810+ const result = new Map < string , T > ( ) ;
811+ const prefix = options ?. prefix ?? '' ;
812+ for ( const [ key , value ] of this . data . entries ( ) ) {
813+ if ( key . startsWith ( prefix ) ) {
814+ result . set ( key , value ) ;
815+ }
816+ }
817+ return result ;
818+ }
819+
820+ async delete ( key : string ) : Promise < boolean > {
821+ return this . data . delete ( key ) ;
822+ }
823+
824+ clear ( ) : void {
825+ this . data . clear ( ) ;
826+ }
827+ }
828+
829+ const STORAGE_PREFIX = 'tinybase_' ;
830+ const T = 't' ;
831+ const V = 'v' ;
832+
833+ // Key construction matching the persister's format
834+ const constructStorageKey = ( type : string , ...ids : string [ ] ) =>
835+ STORAGE_PREFIX + type + JSON . stringify ( ids ) . slice ( 1 , - 1 ) ;
836+
837+ export const mockDurableObjectStorage : Persistable < MockDurableObjectStorage > = {
838+ autoLoadPause : 10 ,
839+ getLocation : async ( ) => new MockDurableObjectStorage ( ) ,
840+ getLocationMethod : [ 'getStorage' , ( storage ) => storage ] ,
841+ getPersister : (
842+ store : Store | MergeableStore ,
843+ storage : MockDurableObjectStorage ,
844+ ) =>
845+ createDurableObjectStoragePersister (
846+ store as MergeableStore ,
847+ storage as unknown as DurableObjectStorage ,
848+ STORAGE_PREFIX ,
849+ ) ,
850+ get : async (
851+ storage : MockDurableObjectStorage ,
852+ ) : Promise < MergeableContent | void > => {
853+ const entries = await storage . list ( { prefix : STORAGE_PREFIX } ) ;
854+ if ( entries . size > 0 ) {
855+ return undefined ;
856+ }
857+ } ,
858+ set : async (
859+ storage : MockDurableObjectStorage ,
860+ content : Content | MergeableContent ,
861+ ) : Promise < void > => {
862+ // Convert MergeableContent to the key-value format the persister uses
863+ const [ [ tablesObj , tablesHlc , tablesHash ] , [ valuesObj , valuesHlc , valuesHash ] ] =
864+ content as MergeableContent ;
865+ const entries : Record < string , any > = { } ;
866+
867+ // Store tables root
868+ entries [ constructStorageKey ( T ) ] = [ 0 , tablesHlc , tablesHash ] ;
869+
870+ // Process tables
871+ Object . entries ( tablesObj ) . forEach (
872+ ( [ tableId , [ tableObj , tableHlc , tableHash ] ] : any ) => {
873+ entries [ constructStorageKey ( T , tableId ) ] = [ 0 , tableHlc , tableHash ] ;
874+ Object . entries ( tableObj ) . forEach (
875+ ( [ rowId , [ rowObj , rowHlc , rowHash ] ] : any ) => {
876+ entries [ constructStorageKey ( T , tableId , rowId ) ] = [
877+ 0 ,
878+ rowHlc ,
879+ rowHash ,
880+ ] ;
881+ Object . entries ( rowObj ) . forEach ( ( [ cellId , cellStamp ] ) => {
882+ entries [ constructStorageKey ( T , tableId , rowId , cellId ) ] =
883+ cellStamp ;
884+ } ) ;
885+ } ,
886+ ) ;
887+ } ,
888+ ) ;
889+
890+ // Store values root
891+ entries [ constructStorageKey ( V ) ] = [ 0 , valuesHlc , valuesHash ] ;
892+
893+ // Process values
894+ Object . entries ( valuesObj ) . forEach ( ( [ valueId , valueStamp ] ) => {
895+ entries [ constructStorageKey ( V , valueId ) ] = valueStamp ;
896+ } ) ;
897+
898+ await storage . put ( entries ) ;
899+ } ,
900+ write : async (
901+ _storage : MockDurableObjectStorage ,
902+ _rawContent : any ,
903+ ) : Promise < void > => {
904+ // Not used for DO storage
905+ } ,
906+ del : async ( storage : MockDurableObjectStorage ) : Promise < void > => {
907+ storage . clear ( ) ;
908+ } ,
909+ testMissing : false ,
910+ testAutoLoad : false ,
911+ } ;
0 commit comments