Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion demo/FakeSharedEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)!;
Expand Down
27 changes: 18 additions & 9 deletions demo/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -42,8 +44,9 @@ const Comp1 = () => {
);
}

const Comp2 = () => {
const {state, trigger, unsubscribe} = useSharedSubscription<string>('test-sub', (set, onError, onCompletion) => {
const use = () => {
return useSharedSubscription<string>('test-sub', (set, onError, onCompletion) => {

return FakeSharedEmitter.subscribe("x", (data: string) => {
if (data === "do-error") {
onError(new Error("Error"));
Expand All @@ -52,12 +55,17 @@ const Comp2 = () => {
set(data);
console.log("data loaded...", data);
}, onError, onCompletion)

});
}

const Comp2 = () => {
const {state, trigger, unsubscribe} = use();


return (
<div>
<h1 className="text-red-600">Comp2</h1>
<h1 className="text-red-600">Comp2 - {state.isLoading && "loading"}</h1>
<button onClick={() => trigger()}>subscribe</button>
<button onClick={() => unsubscribe()}>unsubscribe</button>
results: {state.data}
Expand Down Expand Up @@ -87,16 +95,17 @@ const App = () => {
<button onClick={() => handle()}>Increment x: {x}</button>
<br/>
<Comp1/>
<SharedStatesProvider scopeName="aaa">
<Comp1/>
{x > 1 && <Comp1/>}
{x > 3 && <Comp2/>}
{x > 6 && <Comp2/>}
</SharedStatesProvider>

<SharedStatesProvider>
<Comp1/>
<Comp1/>
<SharedStatesProvider>
<Comp1/>
<Comp1/>
</SharedStatesProvider>
</SharedStatesProvider>
{x > 3 && <Comp2/>}
{x > 6 && <Comp2/>}
</div>
);
};
Expand Down
97 changes: 88 additions & 9 deletions src/SharedData.ts
Original file line number Diff line number Diff line change
@@ -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<T> = DataMapValue & T;
Expand Down Expand Up @@ -79,7 +79,12 @@ export abstract class SharedData<T> {
}

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) {
Expand All @@ -95,11 +100,85 @@ export abstract class SharedData<T> {
}
}

export interface SharedApi<T> {
get: <S extends string = string>(key: S, scopeName: Prefix) => T;
set: <S extends string = string>(key: S, value: T, scopeName: Prefix) => void;
clearAll: () => void;
clear: (key: string, scopeName: Prefix) => void;
has: (key: string, scopeName: Prefix) => boolean;
getAll: () => Map<string, DataMapValue>;
// noinspection JSUnusedGlobalSymbols
export class SharedApi<T>{
constructor(private sharedData: SharedData<T>) {}

/**
* get a value from the shared data
* @param key
* @param scopeName
*/
get<S extends string = string>(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<S extends string = string>(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<string, Record<string, any>> = {};
this.sharedData.data.forEach((value, key) => {
const [prefix, keyWithoutPrefix] = SharedData.extractPrefix(key);
all[prefix] = all[prefix] || {};
all[prefix][keyWithoutPrefix] = value;
});
return all;
}
}
1 change: 1 addition & 0 deletions src/context/SharedStatesContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface SharedStatesProviderProps<T extends string = string> extends PropsWith
}

export const SharedStatesProvider = <T extends string = string>({ children, scopeName }: SharedStatesProviderProps<T>) => {
if (scopeName && scopeName.includes("//")) throw new Error("scopeName cannot contain '//'");

if (!scopeName) scopeName = useMemo(() => Math.random().toString(36).substring(2, 15) as NonNullable<SharedStatesProviderProps<T>['scopeName']>, []);

Expand Down
22 changes: 4 additions & 18 deletions src/hooks/use-shared-function.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -32,7 +32,7 @@ class SharedFunctionsData extends SharedData<SharedFunctionsState<unknown>> {
}
}

export class SharedFunctionsApi implements SharedApi<SharedFunctionsState<unknown>>{
export class SharedFunctionsApi extends SharedApi<SharedFunctionsState<unknown>>{
get<T, S extends string = string>(key: S, scopeName: Prefix = "_global") {
key = ensureNonEmptyString(key);
const prefix: Prefix = scopeName || "_global";
Expand All @@ -43,26 +43,12 @@ export class SharedFunctionsApi implements SharedApi<SharedFunctionsState<unknow
const prefix: Prefix = scopeName || "_global";
sharedFunctionsData.setValue(key, prefix, fnState);
}
clearAll() {
sharedFunctionsData.clearAll();
}
clear(key: string, scopeName: Prefix = "_global") {
const prefix: Prefix = scopeName || "_global";
sharedFunctionsData.clear(key, prefix);
}
has(key: string, scopeName: Prefix = "_global") {
const prefix: Prefix = scopeName || "_global";
return Boolean(sharedFunctionsData.has(key, prefix));
}
getAll() {
return sharedFunctionsData.data;
}
}

export const sharedFunctionsApi = new SharedFunctionsApi();

const sharedFunctionsData = new SharedFunctionsData();

export const sharedFunctionsApi = new SharedFunctionsApi(sharedFunctionsData);

export const useSharedFunction = <T, Args extends unknown[], S extends string = string>(key: S, fn: AFunction<T, Args>, scopeName?: Prefix) => {

key = ensureNonEmptyString(key);
Expand Down
22 changes: 4 additions & 18 deletions src/hooks/use-shared-state.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -24,7 +24,7 @@ class SharedStatesData extends SharedData<{
}
}

class SharedStatesApi implements SharedApi<{
class SharedStatesApi extends SharedApi<{
value: unknown
}>{
get<T, S extends string = string>(key: S, scopeName: Prefix = "_global") {
Expand All @@ -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 = <T, S extends string = string>(key: S, value: T, scopeName?: Prefix) => {
Expand Down
22 changes: 4 additions & 18 deletions src/hooks/use-shared-subscription.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -68,7 +68,7 @@ class SharedSubscriptionsData extends SharedData<SharedSubscriptionsState<unknow
}
}

export class SharedSubscriptionsApi implements SharedApi<SharedSubscriptionsState<unknown>>{
export class SharedSubscriptionsApi extends SharedApi<SharedSubscriptionsState<unknown>>{
get<T, S extends string = string>(key: S, scopeName: Prefix = "_global") {
key = ensureNonEmptyString(key);
const prefix: Prefix = scopeName || "_global";
Expand All @@ -79,26 +79,12 @@ export class SharedSubscriptionsApi implements SharedApi<SharedSubscriptionsStat
const prefix: Prefix = scopeName || "_global";
sharedSubscriptionsData.setValue(key, prefix, fnState);
}
clearAll() {
sharedSubscriptionsData.clearAll();
}
clear(key: string, scopeName: Prefix = "_global") {
const prefix: Prefix = scopeName || "_global";
sharedSubscriptionsData.clear(key, prefix);
}
has(key: string, scopeName: Prefix = "_global") {
const prefix: Prefix = scopeName || "_global";
return Boolean(sharedSubscriptionsData.has(key, prefix));
}
getAll() {
return sharedSubscriptionsData.data;
}
}

export const sharedSubscriptionsApi = new SharedSubscriptionsApi();

const sharedSubscriptionsData = new SharedSubscriptionsData();

export const sharedSubscriptionsApi = new SharedSubscriptionsApi(sharedSubscriptionsData);

export const useSharedSubscription = <T, S extends string = string>(key: S, subscriber: Subscriber<T>, scopeName?: Prefix) => {

key = ensureNonEmptyString(key);
Expand Down