char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
cbc8039
main
1import { 2ObjectCreate , 3ObjectGetOwnPropertyDescriptor as getDescriptor , 4Proxy , 5ReflectDefineProperty as defineReflect , 6ReflectDeleteProperty as deleteReflect , 7ReflectGet as reflectGet , 8ReflectHas as reflectHas , 9ReflectOwnKeys as reflectOwnKeys , 10ReflectSet as reflectSet , 11StringPrototypeIsWellFormed as isWellFormed , 12TypeError , 13WeakMap , 14WeakMapPrototypeGet as getState , 15WeakMapPrototypeSet as setState , 16} from "ext:core/primordials" ; 17import { 18op_webstorage_clear as clearOp , 19op_webstorage_get as getOp , 20op_webstorage_key as keyOp , 21op_webstorage_keys as keysOp , 22op_webstorage_length as lengthOp , 23op_webstorage_remove as removeOp , 24op_webstorage_set as setOp , 25} from "ext:core/ops" ; 26import { createException } from "ext:web/events.js" ; 27import { required , state , string } from "ext:web/idl.js" ; 28import { registerUncloneable } from "ext:web/serialization.js" ; 29 30const states = new WeakMap < object , { persistent :boolean }>(); 31registerUncloneable ( states ); 32const secret :object = ObjectCreate ( null ); 33 34// Strings cross into Rust as UTF-8; refuse lone surrogates on writes 35// rather than letting that conversion silently replace them. Ill-formed 36// names can never have been stored, so reads simply report them absent. 37function wellFormed ( value :unknown ) :string { 38const result = string ( value ); 39if ( ! isWellFormed ( result )) { 40throw new TypeError ( "Storage keys and values must be well-formed strings" ); 41} 42return result ; 43} 44 45function readStored ( persistent :boolean , key :string ) :string | null { 46if ( ! isWellFormed ( key )) return null ; 47return getOp ([ persistent , key ]); 48} 49 50function writeStored ( persistent :boolean , key :string , value :unknown ) :void { 51if ( ! isWellFormed ( key )) { 52throw new TypeError ( "Storage keys and values must be well-formed strings" ); 53} 54const stored = setOp ([ persistent , key , wellFormed ( value )]); 55if ( ! stored ) { 56throw createException ({ 57message :"Exceeded maximum storage size" , 58name :"QuotaExceededError" , 59}); 60} 61} 62 63function removeStored ( persistent :boolean , key :string ) :void { 64if ( isWellFormed ( key )) removeOp ([ persistent , key ]); 65} 66 67class Storage { 68[ key :string | symbol ] :unknown ; 69constructor (); 70constructor ( token :object , persistent :boolean ); 71constructor () { 72if ( arguments [ 0 ] !== secret ) throw new TypeError ( "Illegal constructor" ); 73// Only createStorage holds the token and supplies the persistence flag. 74setState ( states , this , { persistent :arguments [ 1 ] as boolean }); 75} 76get length () :number { 77return lengthOp ( state ( states , this ). persistent ); 78} 79key ( index :unknown ) :string | null { 80const current = state ( states , this ); 81required ( arguments . length , 1 ); 82// Keep ToUint32 coercion: Number() would accept BigInt. 83return keyOp ([ current . persistent , ( index as number ) >>> 0 ]); 84} 85getItem ( key :unknown ) :string | null { 86const current = state ( states , this ); 87required ( arguments . length , 1 ); 88return readStored ( current . persistent , string ( key )); 89} 90setItem ( key :unknown , value :unknown ) :void { 91const current = state ( states , this ); 92required ( arguments . length , 2 ); 93writeStored ( current . persistent , string ( key ), value ); 94} 95removeItem ( key :unknown ) :void { 96const current = state ( states , this ); 97required ( arguments . length , 1 ); 98removeStored ( current . persistent , string ( key )); 99} 100clear () :void { 101clearOp ( state ( states , this ). persistent ); 102} 103} 104 105function createStorage ( persistent :boolean ) :Storage { 106const storage = new Storage ( secret , persistent ); 107const proxy :Storage = new Proxy ( storage , { 108deleteProperty ( target , key ) { 109if ( typeof key === "symbol" || reflectHas ( target , key )) { 110return deleteReflect ( target , key ); 111} 112removeStored ( persistent , key ); 113return true ; 114}, 115defineProperty ( target , key , descriptor ) { 116if ( typeof key === "symbol" ) return defineReflect ( target , key , descriptor ); 117// Accessor, generic, and non-configurable descriptors have no 118// named-property equivalent; WebIDL rejects them. 119if ( ! ( "value" in descriptor ) || descriptor . configurable === false ) { 120return false ; 121} 122writeStored ( persistent , key , descriptor . value ); 123return true ; 124}, 125get ( target , key , receiver :unknown ) :unknown { 126if ( typeof key !== "symbol" && ! reflectHas ( target , key )) { 127const value = readStored ( persistent , key ); 128return value === null ?undefined :value ; 129} 130return reflectGet ( target , key , receiver ); 131}, 132set ( target , key , value :unknown , receiver :unknown ) :boolean { 133if ( typeof key === "symbol" ) { 134return defineReflect ( target , key , { 135 value, 136writable :true , 137enumerable :true , 138configurable :true , 139}); 140} 141if ( receiver !== proxy ) return reflectSet ( target , key , value , receiver ); 142writeStored ( persistent , key , value ); 143return true ; 144}, 145has ( target , key ) { 146if ( reflectHas ( target , key )) return true ; 147return typeof key === "string" && readStored ( persistent , key ) !== null ; 148}, 149ownKeys ( target ) { 150// Named properties shadowed by a prototype member are not visible 151// as own properties (and have no descriptor below), so they are not 152// listed either. 153const stored = keysOp ( persistent ); 154const keys :( string | symbol )[] = []; 155for ( let i = 0 ; i < stored . length ; i ++ ) { 156if ( ! reflectHas ( target , stored [ i ])) keys [ keys . length ] = stored [ i ]; 157} 158const own = reflectOwnKeys ( target ); 159for ( let i = 0 ; i < own . length ; i ++ ) { 160if ( typeof own [ i ] === "symbol" ) keys [ keys . length ] = own [ i ]; 161} 162return keys ; 163}, 164getOwnPropertyDescriptor ( target , key ) { 165if ( typeof key === "symbol" ) return getDescriptor ( target , key ); 166if ( reflectHas ( target , key )) return undefined ; 167const value = readStored ( persistent , key ); 168if ( value === null ) return undefined ; 169return { value, writable :true , enumerable :true , configurable :true }; 170}, 171preventExtensions () { 172return false ; 173}, 174}); 175// The proxy, not the hidden target, is the branded object guests hold. 176setState ( states , proxy , getState ( states , storage ) ! ); 177return proxy ; 178} 179 180const localStorage = createStorage ( true ); 181const sessionStorage = createStorage ( false ); 182 183export { localStorage , sessionStorage , Storage };