diff --git a/demo/FakeSharedEmitter.ts b/demo/FakeSharedEmitter.ts index 12c7adb..5171ba2 100644 --- a/demo/FakeSharedEmitter.ts +++ b/demo/FakeSharedEmitter.ts @@ -36,7 +36,10 @@ export const FakeSharedEmitter = (() => { onCompletion } - await fakeAwait(1000); + //TODO add persistance + console.log("start loading") + await fakeAwait(2000); + console.log("connected") callback.onCompletion?.(); const entry = listeners.get(key)!; diff --git a/demo/app.tsx b/demo/app.tsx index 9570731..eb565cc 100644 --- a/demo/app.tsx +++ b/demo/app.tsx @@ -16,6 +16,8 @@ window.sharedSubscriptionsApi = sharedSubscriptionsApi; window.sharedFunctionsApi = sharedFunctionsApi; window.sharedStatesApi = sharedStatesApi; +sharedStatesApi.set("x", 55); + const Comp1 = () => { const [x, setX] = useSharedState('x', 0); const handle = (by = 1) => { @@ -42,8 +44,9 @@ const Comp1 = () => { ); } -const Comp2 = () => { - const {state, trigger, unsubscribe} = useSharedSubscription('test-sub', (set, onError, onCompletion) => { +const use = () => { + return useSharedSubscription('test-sub', (set, onError, onCompletion) => { + return FakeSharedEmitter.subscribe("x", (data: string) => { if (data === "do-error") { onError(new Error("Error")); @@ -52,12 +55,17 @@ const Comp2 = () => { set(data); console.log("data loaded...", data); }, onError, onCompletion) + }); +} + +const Comp2 = () => { + const {state, trigger, unsubscribe} = use(); return (
-

Comp2

+

Comp2 - {state.isLoading && "loading"}

results: {state.data} @@ -87,16 +95,17 @@ const App = () => {
+ + + {x > 1 && } + {x > 3 && } + {x > 6 && } + + - - - - - {x > 3 && } - {x > 6 && }
); }; diff --git a/src/SharedData.ts b/src/SharedData.ts index 698a500..9a39d53 100644 --- a/src/SharedData.ts +++ b/src/SharedData.ts @@ -1,6 +1,6 @@ import type {AFunction, DataMapValue, Prefix} from "./types"; import {useEffect} from "react"; -import {log} from "./lib/utils"; +import {ensureNonEmptyString, log} from "./lib/utils"; type SharedDataType = DataMapValue & T; @@ -79,7 +79,12 @@ export abstract class SharedData { } static prefix(key: string, prefix: Prefix) { - return `${prefix}_${key}`; + if (key.includes("//")) throw new Error("key cannot contain '//'"); + return `${prefix}//${key}`; + } + + static extractPrefix(mapKey: string) { + return mapKey.split("//"); } useEffect(key: string, prefix: Prefix, unsub: (() => void)|null = null) { @@ -95,11 +100,85 @@ export abstract class SharedData { } } -export interface SharedApi { - get: (key: S, scopeName: Prefix) => T; - set: (key: S, value: T, scopeName: Prefix) => void; - clearAll: () => void; - clear: (key: string, scopeName: Prefix) => void; - has: (key: string, scopeName: Prefix) => boolean; - getAll: () => Map; +// noinspection JSUnusedGlobalSymbols +export class SharedApi{ + constructor(private sharedData: SharedData) {} + + /** + * get a value from the shared data + * @param key + * @param scopeName + */ + get(key: S, scopeName: Prefix) { + key = ensureNonEmptyString(key); + const prefix: Prefix = scopeName || "_global"; + return this.sharedData.get(key, prefix) as T; + } + + /** + * set a value in the shared data + * @param key + * @param value + * @param scopeName + */ + set(key: S, value: T, scopeName: Prefix) { + key = ensureNonEmptyString(key); + const prefix: Prefix = scopeName || "_global"; + this.sharedData.setValue(key, prefix, value); + } + + /** + * clear all values from the shared data + */ + clearAll() { + this.sharedData.clearAll(); + } + + /** + * clear all values from the shared data in a scope + * @param scopeName + */ + clearScope(scopeName?: Prefix) { + const prefixToSearch: Prefix = scopeName || "_global"; + this.sharedData.data.forEach((_, key) => { + const [prefix] = SharedData.extractPrefix(key); + if (prefix === prefixToSearch) { + this.sharedData.clear(key, prefix); + return; + } + }); + } + + /** + * clear a value from the shared data + * @param key + * @param scopeName + */ + clear(key: string, scopeName: Prefix) { + const prefix: Prefix = scopeName || "_global"; + this.sharedData.clear(key, prefix); + } + + /** + * check if a value exists in the shared data + * @param key + * @param scopeName + */ + has(key: string, scopeName: Prefix = "_global") { + const prefix: Prefix = scopeName || "_global"; + return Boolean(this.sharedData.has(key, prefix)); + } + + /** + * get all values from the shared data + */ + getAll() { + const all: Record> = {}; + this.sharedData.data.forEach((value, key) => { + const [prefix, keyWithoutPrefix] = SharedData.extractPrefix(key); + all[prefix] = all[prefix] || {}; + all[prefix][keyWithoutPrefix] = value; + }); + return all; + } } \ No newline at end of file diff --git a/src/context/SharedStatesContext.tsx b/src/context/SharedStatesContext.tsx index 4cf1213..c20adff 100644 --- a/src/context/SharedStatesContext.tsx +++ b/src/context/SharedStatesContext.tsx @@ -12,6 +12,7 @@ interface SharedStatesProviderProps extends PropsWith } export const SharedStatesProvider = ({ children, scopeName }: SharedStatesProviderProps) => { + if (scopeName && scopeName.includes("//")) throw new Error("scopeName cannot contain '//'"); if (!scopeName) scopeName = useMemo(() => Math.random().toString(36).substring(2, 15) as NonNullable['scopeName']>, []); diff --git a/src/hooks/use-shared-function.ts b/src/hooks/use-shared-function.ts index 935294f..affc35d 100644 --- a/src/hooks/use-shared-function.ts +++ b/src/hooks/use-shared-function.ts @@ -1,6 +1,6 @@ import type {AFunction, Prefix} from "../types"; import {useMemo, useSyncExternalStore} from "react"; -import {type SharedApi, SharedData} from "../SharedData"; +import {SharedApi, SharedData} from "../SharedData"; import useShared from "./use-shared"; import {ensureNonEmptyString} from "../lib/utils"; @@ -32,7 +32,7 @@ class SharedFunctionsData extends SharedData> { } } -export class SharedFunctionsApi implements SharedApi>{ +export class SharedFunctionsApi extends SharedApi>{ get(key: S, scopeName: Prefix = "_global") { key = ensureNonEmptyString(key); const prefix: Prefix = scopeName || "_global"; @@ -43,26 +43,12 @@ export class SharedFunctionsApi implements SharedApi(key: S, fn: AFunction, scopeName?: Prefix) => { key = ensureNonEmptyString(key); diff --git a/src/hooks/use-shared-state.ts b/src/hooks/use-shared-state.ts index 13727b4..20230b6 100644 --- a/src/hooks/use-shared-state.ts +++ b/src/hooks/use-shared-state.ts @@ -1,6 +1,6 @@ import {useMemo, useSyncExternalStore} from "react"; import type {AFunction, Prefix} from "../types"; -import {type SharedApi, SharedData} from "../SharedData"; +import {SharedApi, SharedData} from "../SharedData"; import useShared from "./use-shared"; import {ensureNonEmptyString} from "../lib/utils"; @@ -24,7 +24,7 @@ class SharedStatesData extends SharedData<{ } } -class SharedStatesApi implements SharedApi<{ +class SharedStatesApi extends SharedApi<{ value: unknown }>{ get(key: S, scopeName: Prefix = "_global") { @@ -37,26 +37,12 @@ class SharedStatesApi implements SharedApi<{ const prefix: Prefix = scopeName || "_global"; sharedStatesData.setValue(key, prefix, {value}); } - clearAll() { - sharedStatesData.clearAll(); - } - clear(key: string, scopeName: Prefix = "_global") { - const prefix: Prefix = scopeName || "_global"; - sharedStatesData.clear(key, prefix); - } - has(key: string, scopeName: Prefix = "_global") { - const prefix: Prefix = scopeName || "_global"; - return Boolean(sharedStatesData.has(key, prefix)); - } - getAll() { - return sharedStatesData.data; - } } -export const sharedStatesApi = new SharedStatesApi(); - const sharedStatesData = new SharedStatesData(); +export const sharedStatesApi = new SharedStatesApi(sharedStatesData); + export const useSharedState = (key: S, value: T, scopeName?: Prefix) => { diff --git a/src/hooks/use-shared-subscription.ts b/src/hooks/use-shared-subscription.ts index e5812c7..efdd37b 100644 --- a/src/hooks/use-shared-subscription.ts +++ b/src/hooks/use-shared-subscription.ts @@ -1,6 +1,6 @@ import type {PotentialPromise, Prefix} from "../types"; import {useEffect, useMemo, useSyncExternalStore} from "react"; -import {type SharedApi, SharedData} from "../SharedData"; +import {SharedApi, SharedData} from "../SharedData"; import useShared from "./use-shared"; import {ensureNonEmptyString, log} from "../lib/utils"; @@ -68,7 +68,7 @@ class SharedSubscriptionsData extends SharedData>{ +export class SharedSubscriptionsApi extends SharedApi>{ get(key: S, scopeName: Prefix = "_global") { key = ensureNonEmptyString(key); const prefix: Prefix = scopeName || "_global"; @@ -79,26 +79,12 @@ export class SharedSubscriptionsApi implements SharedApi(key: S, subscriber: Subscriber, scopeName?: Prefix) => { key = ensureNonEmptyString(key);