diff --git a/runtime/storage-locations/deno-kv.ts b/runtime/storage-locations/deno-kv.ts index ad9e9eab..9071dda3 100644 --- a/runtime/storage-locations/deno-kv.ts +++ b/runtime/storage-locations/deno-kv.ts @@ -34,9 +34,10 @@ export class DenoKVStorageLocation extends AsyncStorageLocation { return client_type == "deno" && !!globalThis.Deno?.openKv; } - async setItem(key: string, value: unknown): Promise { - await this.set(itemDB!, key, Compiler.encodeValue(value)); - return true; + async setItem(key: string, value: unknown) { + const inserted_ptrs = new Set(); + await this.set(itemDB!, key, Compiler.encodeValue(value, inserted_ptrs)); + return inserted_ptrs; } async getItem(key: string, conditions?: ExecConditions): Promise { const result = await this.get(itemDB!, key); diff --git a/runtime/storage-locations/indexed-db.ts b/runtime/storage-locations/indexed-db.ts index aea89a1a..4cdb4265 100644 --- a/runtime/storage-locations/indexed-db.ts +++ b/runtime/storage-locations/indexed-db.ts @@ -23,9 +23,10 @@ export class IndexedDBStorageLocation extends AsyncStorageLocation { return !!globalThis.indexedDB; } - async setItem(key: string,value: unknown): Promise { - await datex_item_storage.setItem(key, Compiler.encodeValue(value)); // value to buffer (no header) - return true; + async setItem(key: string,value: unknown) { + const inserted_ptrs = new Set(); + await datex_item_storage.setItem(key, Compiler.encodeValue(value, inserted_ptrs)); + return inserted_ptrs; } async getItem(key: string, conditions: ExecConditions): Promise { const buffer = await datex_item_storage.getItem(key); diff --git a/runtime/storage-locations/local-storage.ts b/runtime/storage-locations/local-storage.ts index 48c911bb..e16b6738 100644 --- a/runtime/storage-locations/local-storage.ts +++ b/runtime/storage-locations/local-storage.ts @@ -22,9 +22,10 @@ export class LocalStorageLocation extends SyncStorageLocation { if (!isExit && localStorage.saveFile) localStorage.saveFile(); // deno local storage, save file afer save on exit or interval } - setItem(key: string, value: unknown): boolean { - localStorage.setItem(Storage.item_prefix+key, Compiler.encodeValueBase64(value)) - return true; + setItem(key: string, value: unknown) { + const inserted_ptrs = new Set(); + localStorage.setItem(Storage.item_prefix+key, Compiler.encodeValueBase64(value, inserted_ptrs)); // serialized pointer + return inserted_ptrs; } getItem(key: string, conditions?: ExecConditions) { diff --git a/runtime/storage-locations/sql-db.ts b/runtime/storage-locations/sql-db.ts index 7efba8f1..d39125c5 100644 --- a/runtime/storage-locations/sql-db.ts +++ b/runtime/storage-locations/sql-db.ts @@ -273,7 +273,7 @@ export class SQLDBStorageLocation extends AsyncStorageLocation { return client_type === "deno"; } - async setItem(key: string,value: unknown): Promise { + async setItem(key: string,value: unknown) { } async getItem(key: string): Promise { diff --git a/runtime/storage.ts b/runtime/storage.ts index ce4c8f72..200d81ab 100644 --- a/runtime/storage.ts +++ b/runtime/storage.ts @@ -39,7 +39,7 @@ export interface StorageLocation|boolean + setItem(key:string, value:unknown): Promise>|Set getItem(key:string, conditions?:ExecConditions): Promise|unknown hasItem(key:string):Promise|boolean removeItem(key:string): Promise|void @@ -65,7 +65,7 @@ export abstract class SyncStorageLocation implements StorageLocation abstract getItem(key:string, conditions?:ExecConditions): Promise|unknown abstract hasItem(key:string): boolean abstract getItemKeys(): Generator @@ -74,7 +74,7 @@ export abstract class SyncStorageLocation implements StorageLocation, partialUpdateKey: unknown|typeof NOT_EXISTING): Set> + abstract setPointer(pointer: Pointer, partialUpdateKey: unknown|typeof NOT_EXISTING): Set abstract getPointerValue(pointerId: string, outer_serialized:boolean, conditions?:ExecConditions): unknown abstract getPointerIds(): Generator @@ -93,7 +93,7 @@ export abstract class AsyncStorageLocation implements StorageLocation + abstract setItem(key: string,value: unknown): Promise> abstract getItem(key:string, conditions?:ExecConditions): Promise abstract hasItem(key:string): Promise abstract getItemKeys(): Promise> @@ -102,7 +102,7 @@ export abstract class AsyncStorageLocation implements StorageLocation abstract setItemValueDXB(key:string, value: ArrayBuffer):Promise - abstract setPointer(pointer: Pointer, partialUpdateKey: unknown|typeof NOT_EXISTING): Promise>> + abstract setPointer(pointer: Pointer, partialUpdateKey: unknown|typeof NOT_EXISTING): Promise> abstract getPointerValue(pointerId: string, outer_serialized:boolean, conditions?:ExecConditions): Promise abstract getPointerIds(): Promise> @@ -373,37 +373,27 @@ export class Storage { Storage.cache.set(key, value); // save in cache // cache deletion does not work, problems with storage item backup // setTimeout(()=>Storage.cache.delete(key), 10000); - const pointer = value instanceof Pointer ? value : Pointer.getByValue(value); if (location) { - if (location.isAsync) return this.setItemAsync(location as AsyncStorageLocation, key, value, pointer, listen_for_pointer_changes); - else return this.setItemSync(location as SyncStorageLocation, key, value, pointer, listen_for_pointer_changes); + if (location.isAsync) return this.setItemAsync(location as AsyncStorageLocation, key, value, listen_for_pointer_changes); + else return this.setItemSync(location as SyncStorageLocation, key, value, listen_for_pointer_changes); } else return false; } - static async setItemAsync(location:AsyncStorageLocation, key: string,value: unknown,pointer: Pointer|undefined,listen_for_pointer_changes: boolean): Promise { - this.setDirty(location, true) - // also store pointer - if (pointer) { - const res = await this.setPointer(pointer, listen_for_pointer_changes, location); - if (!res) return false; - } + static async setItemAsync(location:AsyncStorageLocation, key: string,value: unknown,listen_for_pointer_changes: boolean) { this.setDirty(location, true) // store value (might be pointer reference) - const res = await location.setItem(key, value); + const dependencies = await location.setItem(key, value); + await this.saveDependencyPointersAsync(dependencies, listen_for_pointer_changes, location); this.setDirty(location, false) - return res; + return true; } - static setItemSync(location:SyncStorageLocation, key: string,value: unknown,pointer: Pointer|undefined,listen_for_pointer_changes: boolean): boolean { - // also store pointer - if (pointer) { - const res = this.setPointer(pointer, listen_for_pointer_changes, location); - if (!res) return false; - } - - return location.setItem(key, value); + static setItemSync(location:SyncStorageLocation, key: string,value: unknown,listen_for_pointer_changes: boolean) { + const dependencies = location.setItem(key, value); + this.saveDependencyPointersSync(dependencies, listen_for_pointer_changes, location); + return true; } public static setPointer(pointer:Pointer, listen_for_changes = true, location:StorageLocation|undefined = this.#primary_location, partialUpdateKey: unknown = NOT_EXISTING): Promise|boolean { @@ -425,12 +415,8 @@ export class Storage { // if (pointer.transform_scope && this.hasPointer(pointer)) return true; // ignore transform pointer, initial transform scope already stored, does not change const dependencies = this.updatePointerSync(location, pointer, partialUpdateKey); - - // add required pointers for this pointer (only same-origin pointers) - for (const ptr of dependencies) { - // add if not yet in storage - if (ptr != pointer && /*ptr.is_origin &&*/ !localStorage.getItem(this.pointer_prefix+ptr.id)) this.setPointer(ptr, listen_for_changes, location) - } + dependencies.delete(pointer); + this.saveDependencyPointersSync(dependencies, listen_for_changes, location); // listen for changes if (listen_for_changes) this.syncPointer(pointer, location); @@ -448,12 +434,8 @@ export class Storage { // if (pointer.transform_scope && await this.hasPointer(pointer)) return true; // ignore transform pointer, initial transform scope already stored, does not change const dependencies = await this.updatePointerAsync(location, pointer, partialUpdateKey); - - // add required pointers for this pointer (only same-origin pointers) - for (const ptr of dependencies) { - // add if not yet in storage - if (ptr != pointer && /*ptr.is_origin &&*/ !await this.hasPointer(ptr)) await this.setPointer(ptr, listen_for_changes, location) - } + dependencies.delete(pointer); + await this.saveDependencyPointersAsync(dependencies, listen_for_changes, location); // listen for changes if (listen_for_changes) this.syncPointer(pointer, location); @@ -470,6 +452,34 @@ export class Storage { return res; } + /** + * Save dependency pointers to storage (SyncStorageLocation) + * Not all pointers in the set are saved, only those which are not yet in storage or not accessible in other ways + * @param dependencies List of dependency pointers + * @param listen_for_changes should update pointers in storage when value changes + * @param location storage location + */ + private static saveDependencyPointersSync(dependencies: Set, listen_for_changes = true, location: SyncStorageLocation) { + for (const ptr of dependencies) { + // add if not yet in storage + if (!location.hasPointer(ptr.id)) this.setPointer(ptr, listen_for_changes, location) + } + } + + /** + * Save dependency pointers to storage (AsyncStorageLocation) + * Not all pointers in the set are saved, only those which are not yet in storage or not accessible in other ways + * @param dependencies List of dependency pointers + * @param listen_for_changes should update pointers in storage when value changes + * @param location storage location + */ + private static async saveDependencyPointersAsync(dependencies: Set, listen_for_changes = true, location: AsyncStorageLocation) { + for (const ptr of dependencies) { + // add if not yet in storage + if (!await location.hasPointer(ptr.id)) await this.setPointer(ptr, listen_for_changes, location) + } + } + private static synced_pointers = new Set();