core-app-api: update WebStorage implementation and drop immidiate emit

Co-authored-by: Fredrik Adelöw <freben@gmail.com>
Co-authored-by: blam <ben@blam.sh>
Co-authored-by: Johan Haals <johan.haals@gmail.com>
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2021-12-16 15:23:30 +01:00
parent 61fa5d0171
commit a283b7396f
2 changed files with 43 additions and 26 deletions
@@ -16,10 +16,10 @@
import {
StorageApi,
StorageValueChange,
StorageValueSnapshot,
ErrorApi,
} from '@backstage/core-plugin-api';
import { Observable } from '@backstage/types';
import { JsonValue, Observable } from '@backstage/types';
import ObservableImpl from 'zen-observable';
const buckets = new Map<string, WebStorage>();
@@ -43,16 +43,29 @@ export class WebStorage implements StorageApi {
}
get<T>(key: string): T | undefined {
return this.snapshot(key).value as T | undefined;
}
snapshot<T extends JsonValue>(key: string): StorageValueSnapshot<T> {
let value = undefined;
let presence: 'present' | 'absent' = 'absent';
try {
const storage = JSON.parse(localStorage.getItem(this.getKeyName(key))!);
return storage ?? undefined;
const item = localStorage.getItem(this.getKeyName(key));
if (item) {
value = JSON.parse(item, (_key, val) => {
if (typeof val === 'object' && val !== null) {
Object.freeze(val);
}
return val;
});
presence = 'present';
}
} catch (e) {
this.errorApi.post(
new Error(`Error when parsing JSON config from storage for: ${key}`),
);
}
return undefined;
return { key, value, newValue: value, presence };
}
forBucket(name: string): WebStorage {
@@ -64,16 +77,16 @@ export class WebStorage implements StorageApi {
}
async set<T>(key: string, data: T): Promise<void> {
localStorage.setItem(this.getKeyName(key), JSON.stringify(data, null, 2));
this.notifyChanges({ key, newValue: data });
localStorage.setItem(this.getKeyName(key), JSON.stringify(data));
this.notifyChanges(key);
}
async remove(key: string): Promise<void> {
localStorage.removeItem(this.getKeyName(key));
this.notifyChanges({ key, newValue: undefined });
this.notifyChanges(key);
}
observe$<T>(key: string): Observable<StorageValueChange<T>> {
observe$<T>(key: string): Observable<StorageValueSnapshot<T>> {
return this.observable.filter(({ key: messageKey }) => messageKey === key);
}
@@ -81,22 +94,23 @@ export class WebStorage implements StorageApi {
return `${this.namespace}/${encodeURIComponent(key)}`;
}
private notifyChanges<T>(message: StorageValueChange<T>) {
private notifyChanges(key: string) {
const snapshot = this.snapshot(key);
for (const subscription of this.subscribers) {
subscription.next(message);
subscription.next(snapshot);
}
}
private subscribers = new Set<
ZenObservable.SubscriptionObserver<StorageValueChange>
ZenObservable.SubscriptionObserver<StorageValueSnapshot<JsonValue>>
>();
private readonly observable = new ObservableImpl<StorageValueChange>(
subscriber => {
this.subscribers.add(subscriber);
return () => {
this.subscribers.delete(subscriber);
};
},
);
private readonly observable = new ObservableImpl<
StorageValueSnapshot<JsonValue>
>(subscriber => {
this.subscribers.add(subscriber);
return () => {
this.subscribers.delete(subscriber);
};
});
}
@@ -85,12 +85,15 @@ export interface StorageApi {
*
* @remarks
*
* The returned observable will immediately emit a value snapshot when
* subscribed to, even if the presence of the value is unknown at that time.
* The observable will only emit values when the value changes in the underlying
* storage, although multiple values with the same shape may be emitted in a row.
*
* The values emitted by the observe method aim to keep reference equality
* across snapshots if the value is unchanged. This means that it is important
* not to mutate the returned values, and they may be frozen as a precaution.
* If a {@link StorageApi.snapshot} of a key is retrieved and the presence is
* `'unknown'`, then you are guaranteed to receive a snapshot with a known
* presence, as long as you observe the key within the same tick.
*
* Since the emitted values are shared across all subscribers, it is important
* not to mutate the returned values. The values may be frozen as a precaution.
*
* @param key - Unique key associated with the data
*/