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:
@@ -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
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user