A calm place to write long-form, and publish it to the open social web. skypress.blog/
0

Configure Feed

Select the types of activity you want to include in your feed.

at trunk 3.7 kB View raw
1/** 2 * A small async key→value store for held image bytes (data: URLs) in the writing-first flow. 3 * Held bytes can be large, so they live here (IndexedDB) rather than in localStorage with the 4 * draft metadata. `put` MERGES (does not replace the whole store) so adding an image keeps the 5 * earlier ones; `clear` empties everything (called after a successful publish or a discard). 6 */ 7export interface AssetStore { 8 put( assets: Record< string, string > ): Promise< void >; 9 getAll(): Promise< Record< string, string > >; 10 clear(): Promise< void >; 11} 12 13/** In-memory store: the test/SSR/quota-failure fallback. Survives nothing past a reload. */ 14export function createMemoryAssetStore(): AssetStore { 15 let map: Record< string, string > = {}; 16 return { 17 async put( assets ) { 18 map = { ...map, ...assets }; 19 }, 20 async getAll() { 21 return { ...map }; 22 }, 23 async clear() { 24 map = {}; 25 }, 26 }; 27} 28 29const DEFAULT_DB = 'skypress-write'; 30const DEFAULT_STORE = 'assets'; 31 32function openDb( dbName: string, storeName: string ): Promise< IDBDatabase > { 33 return new Promise( ( resolve, reject ) => { 34 const req = indexedDB.open( dbName, 1 ); 35 req.onupgradeneeded = () => { 36 if ( ! req.result.objectStoreNames.contains( storeName ) ) { 37 req.result.createObjectStore( storeName ); 38 } 39 }; 40 req.onsuccess = () => resolve( req.result ); 41 req.onerror = () => reject( req.error ?? new Error( 'IndexedDB open failed' ) ); 42 } ); 43} 44 45/** 46 * IndexedDB-backed asset store. Each token is one record keyed by the token string. Falls back 47 * to an in-memory store when IndexedDB is unavailable (e.g. SSR/tests) so callers never crash. 48 */ 49export function createIndexedDbAssetStore( 50 dbName: string = DEFAULT_DB, 51 storeName: string = DEFAULT_STORE 52): AssetStore { 53 if ( typeof indexedDB === 'undefined' ) { 54 return createMemoryAssetStore(); 55 } 56 57 const tx = async < T >( 58 mode: IDBTransactionMode, 59 run: ( store: IDBObjectStore ) => IDBRequest | void, 60 read?: ( store: IDBObjectStore ) => IDBRequest< T > 61 ): Promise< T | void > => { 62 const db = await openDb( dbName, storeName ); 63 return new Promise< T | void >( ( resolve, reject ) => { 64 const transaction = db.transaction( storeName, mode ); 65 const store = transaction.objectStore( storeName ); 66 let readReq: IDBRequest< T > | undefined; 67 if ( read ) { 68 readReq = read( store ); 69 } else { 70 run( store ); 71 } 72 transaction.oncomplete = () => { 73 db.close(); 74 resolve( readReq ? readReq.result : undefined ); 75 }; 76 transaction.onerror = () => { 77 db.close(); 78 reject( transaction.error ?? new Error( 'IndexedDB transaction failed' ) ); 79 }; 80 } ); 81 }; 82 83 return { 84 async put( assets ) { 85 await tx( 'readwrite', ( store ) => { 86 for ( const [ key, value ] of Object.entries( assets ) ) { 87 store.put( value, key ); 88 } 89 } ); 90 }, 91 async getAll() { 92 const db = await openDb( dbName, storeName ); 93 return new Promise< Record< string, string > >( ( resolve, reject ) => { 94 const transaction = db.transaction( storeName, 'readonly' ); 95 const store = transaction.objectStore( storeName ); 96 const keysReq = store.getAllKeys(); 97 const valsReq = store.getAll(); 98 transaction.oncomplete = () => { 99 db.close(); 100 const out: Record< string, string > = {}; 101 ( keysReq.result as IDBValidKey[] ).forEach( ( k, i ) => { 102 out[ String( k ) ] = valsReq.result[ i ] as string; 103 } ); 104 resolve( out ); 105 }; 106 transaction.onerror = () => { 107 db.close(); 108 reject( transaction.error ?? new Error( 'IndexedDB read failed' ) ); 109 }; 110 } ); 111 }, 112 async clear() { 113 await tx( 'readwrite', ( store ) => store.clear() ); 114 }, 115 }; 116}