hermes-io 2.11.95 → 2.12.97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/withNotify/withNotify.d.ts +11 -0
- package/lib/components/withNotify/withNotify.js +5 -1
- package/lib/constants.d.ts +10 -0
- package/lib/constants.js +10 -1
- package/lib/context/context.d.ts +29 -0
- package/lib/context/context.js +120 -1
- package/lib/hooks/index.d.ts +3 -0
- package/lib/hooks/index.js +3 -1
- package/lib/hooks/useMutations.d.ts +23 -0
- package/lib/hooks/useMutations.js +93 -1
- package/lib/hooks/useObserver.d.ts +11 -0
- package/lib/hooks/useObserver.js +25 -1
- package/lib/hooks/useStore.d.ts +22 -0
- package/lib/hooks/useStore.js +24 -1
- package/lib/index.d.ts +5 -0
- package/lib/index.js +5 -1
- package/lib/observer/observer.d.ts +14 -0
- package/lib/observer/observer.js +25 -1
- package/lib/store/store.d.ts +34 -0
- package/lib/store/store.js +95 -1
- package/lib/utils.d.ts +2 -0
- package/lib/utils.js +5 -1
- package/package.json +10 -9
- package/tsconfig.json +17 -0
- package/lib/components/withNotify/withNotify.jsx +0 -6
- package/lib/hooks/useMicroStore.js +0 -1
- package/lib/hooks/useObservableStore.js +0 -1
- package/lib/hooks/useStoreFactory.js +0 -1
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ComponentType } from "react";
|
|
2
|
+
import type { Context } from "../../context/context";
|
|
3
|
+
import type { Observer } from "../../observer/observer";
|
|
4
|
+
interface WithNotifyConfig {
|
|
5
|
+
context: Context;
|
|
6
|
+
observer: Observer;
|
|
7
|
+
}
|
|
8
|
+
export declare function withNotify<P extends Record<string, unknown>>(Component: ComponentType<P & {
|
|
9
|
+
notify: (value: unknown) => Promise<unknown>;
|
|
10
|
+
}>, { context, observer }: WithNotifyConfig): (props: P) => import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -1 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
export function withNotify(Component, { context, observer }) {
|
|
3
|
+
const notify = (value) => observer.notify({ context, value });
|
|
4
|
+
return (props) => _jsx(Component, { notify: notify, ...props });
|
|
5
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
declare const CONSTANTS: {
|
|
2
|
+
readonly CHROME_EXTENSION: "hermes-io-devtools";
|
|
3
|
+
readonly CONTEXT_SNAPSHOT: "CONTEXT_SNAPSHOT";
|
|
4
|
+
readonly START_RECORDING: "START_RECORDING";
|
|
5
|
+
readonly STOP_RECORDING: "STOP_RECORDING";
|
|
6
|
+
readonly SET_CONTEXT: "SET_CONTEXT";
|
|
7
|
+
readonly LOAD_RECORDING: "LOAD_RECORDING";
|
|
8
|
+
readonly RESET_RECORDING: "RESET_RECORDING";
|
|
9
|
+
};
|
|
10
|
+
export default CONSTANTS;
|
package/lib/constants.js
CHANGED
|
@@ -1 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
const CONSTANTS = {
|
|
2
|
+
CHROME_EXTENSION: "hermes-io-devtools",
|
|
3
|
+
CONTEXT_SNAPSHOT: "CONTEXT_SNAPSHOT",
|
|
4
|
+
START_RECORDING: "START_RECORDING",
|
|
5
|
+
STOP_RECORDING: "STOP_RECORDING",
|
|
6
|
+
SET_CONTEXT: "SET_CONTEXT",
|
|
7
|
+
LOAD_RECORDING: "LOAD_RECORDING",
|
|
8
|
+
RESET_RECORDING: "RESET_RECORDING",
|
|
9
|
+
};
|
|
10
|
+
export default CONSTANTS;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
interface SnapshotItem {
|
|
2
|
+
_internalId: string;
|
|
3
|
+
value: unknown;
|
|
4
|
+
date: Date | null;
|
|
5
|
+
listener: ((...args: unknown[]) => unknown) | null;
|
|
6
|
+
stackTrace: string | undefined;
|
|
7
|
+
isFromExternalRecording?: boolean;
|
|
8
|
+
id?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const listenersMap: Map<string, (...args: unknown[]) => unknown>;
|
|
11
|
+
export declare function subscribeDevtools(): void;
|
|
12
|
+
export declare function unsubscribeDevtools(): void;
|
|
13
|
+
export declare class Context {
|
|
14
|
+
id: symbol;
|
|
15
|
+
_internalId: string | null;
|
|
16
|
+
date: Date | null;
|
|
17
|
+
value: unknown;
|
|
18
|
+
listener: ((...args: unknown[]) => unknown) | null;
|
|
19
|
+
stackTrace: string | undefined;
|
|
20
|
+
constructor(description: string);
|
|
21
|
+
update: ({ value, listener }: {
|
|
22
|
+
value: unknown;
|
|
23
|
+
listener: (...args: unknown[]) => unknown;
|
|
24
|
+
}) => void;
|
|
25
|
+
sendSnapshot: () => void;
|
|
26
|
+
takeSnapshot: () => SnapshotItem;
|
|
27
|
+
getStackTrace: () => string | undefined;
|
|
28
|
+
}
|
|
29
|
+
export {};
|
package/lib/context/context.js
CHANGED
|
@@ -1 +1,120 @@
|
|
|
1
|
-
|
|
1
|
+
import CONSTANTS from "../constants";
|
|
2
|
+
let recording = false;
|
|
3
|
+
let collection = [];
|
|
4
|
+
export const listenersMap = new Map();
|
|
5
|
+
function startRecording() {
|
|
6
|
+
recording = true;
|
|
7
|
+
}
|
|
8
|
+
function stopRecording() {
|
|
9
|
+
recording = false;
|
|
10
|
+
}
|
|
11
|
+
function resetRecording() {
|
|
12
|
+
collection = [];
|
|
13
|
+
recording = false;
|
|
14
|
+
}
|
|
15
|
+
function setContext({ id = "" }) {
|
|
16
|
+
const context = collection.find((context) => context._internalId === id);
|
|
17
|
+
if (context) {
|
|
18
|
+
if (context.isFromExternalRecording) {
|
|
19
|
+
const listener = listenersMap.get(context.listener);
|
|
20
|
+
listener?.({ value: JSON.parse(context.value) });
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
context.listener?.(context.value);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function loadRecording(payload) {
|
|
27
|
+
collection = (payload.recording ?? []).map((item = {}) => ({
|
|
28
|
+
...item,
|
|
29
|
+
isFromExternalRecording: true,
|
|
30
|
+
_internalId: item.id,
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
const actions = {
|
|
34
|
+
[CONSTANTS.START_RECORDING]: startRecording,
|
|
35
|
+
[CONSTANTS.STOP_RECORDING]: stopRecording,
|
|
36
|
+
[CONSTANTS.RESET_RECORDING]: resetRecording,
|
|
37
|
+
[CONSTANTS.SET_CONTEXT]: setContext,
|
|
38
|
+
[CONSTANTS.LOAD_RECORDING]: loadRecording,
|
|
39
|
+
};
|
|
40
|
+
const handleMessageFromDevtools = (event) => {
|
|
41
|
+
try {
|
|
42
|
+
if (event.origin !== window.location.origin)
|
|
43
|
+
return;
|
|
44
|
+
const { source, payload } = event.data;
|
|
45
|
+
if (source === CONSTANTS.CHROME_EXTENSION) {
|
|
46
|
+
if (payload?.type) {
|
|
47
|
+
actions[payload.type]?.(payload);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
actions[payload]?.();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error(error);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
let listenerRegistered = false;
|
|
58
|
+
export function subscribeDevtools() {
|
|
59
|
+
if (typeof window === "undefined" || listenerRegistered)
|
|
60
|
+
return;
|
|
61
|
+
listenerRegistered = true;
|
|
62
|
+
window.addEventListener("message", handleMessageFromDevtools);
|
|
63
|
+
}
|
|
64
|
+
export function unsubscribeDevtools() {
|
|
65
|
+
if (typeof window === "undefined" || !listenerRegistered)
|
|
66
|
+
return;
|
|
67
|
+
listenerRegistered = false;
|
|
68
|
+
window.removeEventListener("message", handleMessageFromDevtools);
|
|
69
|
+
}
|
|
70
|
+
subscribeDevtools();
|
|
71
|
+
export class Context {
|
|
72
|
+
constructor(description) {
|
|
73
|
+
this._internalId = null;
|
|
74
|
+
this.date = null;
|
|
75
|
+
this.value = null;
|
|
76
|
+
this.listener = null;
|
|
77
|
+
this.stackTrace = undefined;
|
|
78
|
+
this.update = ({ value, listener }) => {
|
|
79
|
+
this.date = new Date();
|
|
80
|
+
this.value = value;
|
|
81
|
+
this.stackTrace = this.getStackTrace();
|
|
82
|
+
this.listener = listener;
|
|
83
|
+
if (recording) {
|
|
84
|
+
this.sendSnapshot();
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
this.sendSnapshot = () => {
|
|
88
|
+
const snapshot = this.takeSnapshot();
|
|
89
|
+
const { listener, stackTrace, value, date, _internalId } = snapshot;
|
|
90
|
+
collection.push(snapshot);
|
|
91
|
+
if (typeof window !== "undefined") {
|
|
92
|
+
window.postMessage({
|
|
93
|
+
type: CONSTANTS.CONTEXT_SNAPSHOT,
|
|
94
|
+
payload: {
|
|
95
|
+
value: JSON.stringify(value?.value),
|
|
96
|
+
listener: listener?.name,
|
|
97
|
+
stackTrace,
|
|
98
|
+
date,
|
|
99
|
+
id: _internalId,
|
|
100
|
+
},
|
|
101
|
+
source: "hermes-io",
|
|
102
|
+
}, window.location.origin);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
this.takeSnapshot = () => {
|
|
106
|
+
return {
|
|
107
|
+
_internalId: crypto.randomUUID(),
|
|
108
|
+
value: this.value,
|
|
109
|
+
date: this.date,
|
|
110
|
+
listener: this.listener,
|
|
111
|
+
stackTrace: this.stackTrace,
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
this.getStackTrace = () => {
|
|
115
|
+
const err = new Error();
|
|
116
|
+
return err.stack;
|
|
117
|
+
};
|
|
118
|
+
this.id = Symbol(description);
|
|
119
|
+
}
|
|
120
|
+
}
|
package/lib/hooks/index.js
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Store } from "../store/store";
|
|
2
|
+
type OnChangeCallback = (value: unknown, resolver: (value?: unknown) => void, setNoUpdate: (value: boolean) => void, prevState: Record<string, unknown>) => Record<string, unknown> | void;
|
|
3
|
+
interface CustomEvent {
|
|
4
|
+
event: string;
|
|
5
|
+
onChange: OnChangeCallback;
|
|
6
|
+
}
|
|
7
|
+
interface MutationRef {
|
|
8
|
+
id: string;
|
|
9
|
+
state: Record<string, unknown>;
|
|
10
|
+
events: CustomEvent[];
|
|
11
|
+
onEvent: (event: string, onChange: OnChangeCallback) => void;
|
|
12
|
+
}
|
|
13
|
+
export interface UseMutationsProps {
|
|
14
|
+
events?: string[];
|
|
15
|
+
onChange?: OnChangeCallback;
|
|
16
|
+
store?: Store;
|
|
17
|
+
id?: string;
|
|
18
|
+
initialState?: Record<string, unknown>;
|
|
19
|
+
name?: string;
|
|
20
|
+
noUpdate?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare const useMutations: (props?: UseMutationsProps) => MutationRef;
|
|
23
|
+
export {};
|
|
@@ -1 +1,93 @@
|
|
|
1
|
-
|
|
1
|
+
import { useState, useEffect, useRef, useCallback } from "react";
|
|
2
|
+
import { useObserver } from "./useObserver";
|
|
3
|
+
import { MicroStore } from "../store/store";
|
|
4
|
+
import { getStackTrace, randomId } from "../utils";
|
|
5
|
+
export const useMutations = (props = {}) => {
|
|
6
|
+
const { events = [], onChange, store, id, initialState = {}, name = "", noUpdate: initialNoUpdate, } = props;
|
|
7
|
+
const [_renderId, setReRenderId] = useState(randomId());
|
|
8
|
+
const noUpdateRef = useRef(initialNoUpdate);
|
|
9
|
+
const mutationRef = useRef({
|
|
10
|
+
id: `${getStackTrace()}_${id ?? ""}`,
|
|
11
|
+
state: { ...initialState },
|
|
12
|
+
events: [],
|
|
13
|
+
onEvent: (event, onChange) => {
|
|
14
|
+
const events = mutationRef.current.events;
|
|
15
|
+
const isAlreadyIn = events.some((item) => item.event === event);
|
|
16
|
+
if (!isAlreadyIn)
|
|
17
|
+
events.push({ event, onChange });
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
const setNoUpdate = (value) => {
|
|
21
|
+
noUpdateRef.current = value;
|
|
22
|
+
};
|
|
23
|
+
const executeEvent = (targets, value, resolver, onChange) => {
|
|
24
|
+
const hasNotTargets = !targets;
|
|
25
|
+
let match = false;
|
|
26
|
+
targets?.forEach?.((target) => {
|
|
27
|
+
if (target === id) {
|
|
28
|
+
match = true;
|
|
29
|
+
let result = onChange?.(value, resolver, setNoUpdate, mutationRef.current.state) ??
|
|
30
|
+
{};
|
|
31
|
+
if (noUpdateRef.current)
|
|
32
|
+
result = {};
|
|
33
|
+
mutationRef.current.state = { ...mutationRef.current.state, ...result };
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
if (hasNotTargets) {
|
|
37
|
+
let result = onChange?.(value, resolver, setNoUpdate, mutationRef.current.state) ??
|
|
38
|
+
{};
|
|
39
|
+
if (noUpdateRef.current)
|
|
40
|
+
result = {};
|
|
41
|
+
mutationRef.current.state = { ...mutationRef.current.state, ...result };
|
|
42
|
+
}
|
|
43
|
+
if (noUpdateRef.current === true || match === false)
|
|
44
|
+
return;
|
|
45
|
+
setReRenderId(randomId());
|
|
46
|
+
};
|
|
47
|
+
const handleNotification = useCallback(((e, resolver) => {
|
|
48
|
+
const payload = e.value?.payload;
|
|
49
|
+
const value = payload?.value;
|
|
50
|
+
const targets = e.value?.targets;
|
|
51
|
+
const customs = mutationRef.current?.events;
|
|
52
|
+
const hasCustomEvents = customs.length > 0;
|
|
53
|
+
if (hasCustomEvents) {
|
|
54
|
+
for (const custom of customs) {
|
|
55
|
+
if (e.value.type !== custom.event)
|
|
56
|
+
continue;
|
|
57
|
+
executeEvent(targets, value, resolver, custom.onChange);
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
for (const event of events) {
|
|
62
|
+
if (e.value.type !== event)
|
|
63
|
+
continue;
|
|
64
|
+
executeEvent(targets, value, resolver, onChange);
|
|
65
|
+
}
|
|
66
|
+
}), [events, mutationRef.current?.events]);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
const isMicroStore = store instanceof MicroStore;
|
|
69
|
+
if (isMicroStore) {
|
|
70
|
+
const microStore = store;
|
|
71
|
+
const { id: mutationId } = mutationRef.current;
|
|
72
|
+
handleNotification.id = mutationId;
|
|
73
|
+
const isPopulated = microStore.has(id);
|
|
74
|
+
if (!microStore.hasListener(mutationId)) {
|
|
75
|
+
microStore.registerListener(id, handleNotification);
|
|
76
|
+
}
|
|
77
|
+
if (isPopulated && !microStore.get(id).observer.has(mutationId)) {
|
|
78
|
+
microStore.subscribeStore(id, microStore.get(id));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return () => {
|
|
82
|
+
if (isMicroStore)
|
|
83
|
+
store.remove(id, mutationRef.current.id);
|
|
84
|
+
};
|
|
85
|
+
}, []);
|
|
86
|
+
useObserver({
|
|
87
|
+
store,
|
|
88
|
+
listener: handleNotification,
|
|
89
|
+
contexts: store?.context ? [store.context] : [],
|
|
90
|
+
observer: store?.observer,
|
|
91
|
+
});
|
|
92
|
+
return mutationRef.current;
|
|
93
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Store } from "../store/store";
|
|
2
|
+
import type { Context } from "../context/context";
|
|
3
|
+
import type { Observer, Subscriber } from "../observer/observer";
|
|
4
|
+
export declare const hasValidList: (contexts: Context[] | undefined, payload: Record<string, unknown>) => Context | undefined;
|
|
5
|
+
export interface UseObserverProps {
|
|
6
|
+
store?: Store;
|
|
7
|
+
listener?: Subscriber;
|
|
8
|
+
contexts?: Context[];
|
|
9
|
+
observer?: Observer;
|
|
10
|
+
}
|
|
11
|
+
export declare const useObserver: (props: UseObserverProps) => void;
|
package/lib/hooks/useObserver.js
CHANGED
|
@@ -1 +1,25 @@
|
|
|
1
|
-
import{useEffect
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { Store } from "../store/store";
|
|
3
|
+
export const hasValidList = (contexts = [], payload) => contexts?.find?.((ctx) => ctx.id === payload?.context?.id);
|
|
4
|
+
export const useObserver = (props) => {
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
const { listener, observer, contexts, store } = props;
|
|
7
|
+
const isStore = store instanceof Store;
|
|
8
|
+
const subscriber = function (payload, resolve) {
|
|
9
|
+
if (!hasValidList(contexts, payload))
|
|
10
|
+
return;
|
|
11
|
+
payload?.context &&
|
|
12
|
+
payload.context.update?.({
|
|
13
|
+
value: payload,
|
|
14
|
+
listener: listener,
|
|
15
|
+
});
|
|
16
|
+
listener?.(payload, resolve);
|
|
17
|
+
};
|
|
18
|
+
if (isStore)
|
|
19
|
+
observer?.subscribe?.(subscriber);
|
|
20
|
+
return () => {
|
|
21
|
+
if (isStore)
|
|
22
|
+
observer?.unsubscribe?.(subscriber);
|
|
23
|
+
};
|
|
24
|
+
}, [props.listener, props.observer, props.contexts]);
|
|
25
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Store } from "../store/store";
|
|
2
|
+
import type { MicroStore } from "../store/store";
|
|
3
|
+
export interface UseStoreProps {
|
|
4
|
+
store: Store;
|
|
5
|
+
reducer: (state: unknown, action: {
|
|
6
|
+
type: string;
|
|
7
|
+
payload?: unknown;
|
|
8
|
+
}) => unknown;
|
|
9
|
+
data?: unknown;
|
|
10
|
+
microStore?: MicroStore;
|
|
11
|
+
id?: string;
|
|
12
|
+
name?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function useStore(props: UseStoreProps): {
|
|
15
|
+
query: (cb: (store: Store) => unknown, defaultValue?: unknown) => unknown;
|
|
16
|
+
mutate: ({ type, payload, targets }: {
|
|
17
|
+
type: string;
|
|
18
|
+
payload?: unknown;
|
|
19
|
+
targets?: string[];
|
|
20
|
+
}) => Promise<unknown>;
|
|
21
|
+
store: Store;
|
|
22
|
+
};
|
package/lib/hooks/useStore.js
CHANGED
|
@@ -1 +1,24 @@
|
|
|
1
|
-
import{useCallback
|
|
1
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
2
|
+
export function useStore(props) {
|
|
3
|
+
const { name, reducer, data, microStore, id } = props;
|
|
4
|
+
const storeRef = useRef(props.store);
|
|
5
|
+
const store = storeRef.current;
|
|
6
|
+
const reducerRef = useRef(reducer);
|
|
7
|
+
reducerRef.current = reducer;
|
|
8
|
+
const mutate = (store.mutate = useCallback(({ type, payload, targets }) => {
|
|
9
|
+
const newState = reducerRef.current(store.state, { type, payload });
|
|
10
|
+
return store.notify({ type, payload, state: newState, targets });
|
|
11
|
+
}, [store]));
|
|
12
|
+
const query = (store.query = useCallback((cb, defaultValue) => cb(store) ?? defaultValue, [store]));
|
|
13
|
+
if (!store.state)
|
|
14
|
+
store.state = data;
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (!id || !microStore || microStore.has?.(id))
|
|
17
|
+
return;
|
|
18
|
+
microStore.add?.(id, store, name);
|
|
19
|
+
return () => {
|
|
20
|
+
microStore.removeAll?.(id);
|
|
21
|
+
};
|
|
22
|
+
}, [microStore, store, id]);
|
|
23
|
+
return { query, mutate, store };
|
|
24
|
+
}
|
package/lib/index.d.ts
ADDED
package/lib/index.js
CHANGED
|
@@ -1 +1,5 @@
|
|
|
1
|
-
export*from"./hooks/index
|
|
1
|
+
export * from "./hooks/index";
|
|
2
|
+
export * from "./observer/observer";
|
|
3
|
+
export * from "./context/context";
|
|
4
|
+
export * from "./store/store";
|
|
5
|
+
export * from "./components/withNotify/withNotify";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type Subscriber = {
|
|
2
|
+
(args: Record<string, unknown>, resolve: (value?: unknown) => void): void;
|
|
3
|
+
id?: string;
|
|
4
|
+
};
|
|
5
|
+
export interface NotifyOptions {
|
|
6
|
+
timeout?: number;
|
|
7
|
+
}
|
|
8
|
+
export declare class Observer {
|
|
9
|
+
subscriptors: Subscriber[];
|
|
10
|
+
subscribe(callback: Subscriber): void;
|
|
11
|
+
unsubscribe(callback: Subscriber): void;
|
|
12
|
+
has: (id: string) => boolean;
|
|
13
|
+
notify(args?: Record<string, unknown>, { timeout }?: NotifyOptions): Promise<unknown>;
|
|
14
|
+
}
|
package/lib/observer/observer.js
CHANGED
|
@@ -1 +1,25 @@
|
|
|
1
|
-
export
|
|
1
|
+
export class Observer {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.subscriptors = [];
|
|
4
|
+
this.has = (id) => this.subscriptors.some((subscriber) => id === subscriber.id);
|
|
5
|
+
}
|
|
6
|
+
subscribe(callback) {
|
|
7
|
+
this.subscriptors.push(callback);
|
|
8
|
+
}
|
|
9
|
+
unsubscribe(callback) {
|
|
10
|
+
const index = this.subscriptors.findIndex((cb) => cb === callback);
|
|
11
|
+
if (index > -1)
|
|
12
|
+
this.subscriptors.splice(index, 1);
|
|
13
|
+
}
|
|
14
|
+
notify(args = {}, { timeout = 30000 } = {}) {
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
const timer = timeout > 0 ? setTimeout(() => resolve(null), timeout) : null;
|
|
17
|
+
const wrappedResolve = (value) => {
|
|
18
|
+
if (timer)
|
|
19
|
+
clearTimeout(timer);
|
|
20
|
+
resolve(value);
|
|
21
|
+
};
|
|
22
|
+
this.subscriptors.forEach((callback) => callback(args, wrappedResolve));
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Observer, Subscriber } from "../observer/observer";
|
|
2
|
+
import type { Context } from "../context/context";
|
|
3
|
+
export interface StoreConfig {
|
|
4
|
+
context: Context;
|
|
5
|
+
observer: Observer;
|
|
6
|
+
id?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class Store {
|
|
9
|
+
context: Context;
|
|
10
|
+
observer: Observer;
|
|
11
|
+
id: string | undefined;
|
|
12
|
+
state: unknown;
|
|
13
|
+
constructor({ context, observer, id }: StoreConfig);
|
|
14
|
+
notify: (value: Record<string, unknown>) => Promise<unknown>;
|
|
15
|
+
query: (cb: (store: Store) => unknown, defaultValue?: unknown) => unknown;
|
|
16
|
+
mutate: (action: {
|
|
17
|
+
type: string;
|
|
18
|
+
payload?: unknown;
|
|
19
|
+
targets?: string[];
|
|
20
|
+
}) => unknown;
|
|
21
|
+
}
|
|
22
|
+
export type ListenerCallback = Subscriber;
|
|
23
|
+
export declare class MicroStore {
|
|
24
|
+
collection: Map<string, Store>;
|
|
25
|
+
listeners: Map<string, Subscriber[]>;
|
|
26
|
+
subscribeStore: (id: string, store: Store) => void;
|
|
27
|
+
add: (id: string, store: Store, _name?: string) => Store;
|
|
28
|
+
registerListener: (id: string, cb: ListenerCallback) => void;
|
|
29
|
+
hasListener: (id: string) => boolean;
|
|
30
|
+
remove: (id: string, mutationId: string) => void;
|
|
31
|
+
removeAll: (id: string) => void;
|
|
32
|
+
get: (id: string) => Store | undefined;
|
|
33
|
+
has: (id: string) => boolean;
|
|
34
|
+
}
|
package/lib/store/store.js
CHANGED
|
@@ -1 +1,95 @@
|
|
|
1
|
-
|
|
1
|
+
import { hasValidList } from "../hooks/useObserver";
|
|
2
|
+
export class Store {
|
|
3
|
+
constructor({ context, observer, id }) {
|
|
4
|
+
this.notify = (value) => this.observer.notify({ context: this.context, value });
|
|
5
|
+
this.query = () => console.log("method is not connected to the reducer, please call useStore first");
|
|
6
|
+
this.mutate = () => console.log("method is not connected to the store, you need call useStore first");
|
|
7
|
+
this.id = id;
|
|
8
|
+
this.context = context;
|
|
9
|
+
this.observer = observer;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export class MicroStore {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.collection = new Map();
|
|
15
|
+
this.listeners = new Map();
|
|
16
|
+
this.subscribeStore = (id, store) => {
|
|
17
|
+
const { observer, context } = store;
|
|
18
|
+
const listeners = this.listeners.get(id) ?? [];
|
|
19
|
+
for (const listener of listeners) {
|
|
20
|
+
if (observer.has(listener.id))
|
|
21
|
+
continue;
|
|
22
|
+
const subscriber = function (payload, resolve) {
|
|
23
|
+
const isInvalidContext = !hasValidList([context], payload);
|
|
24
|
+
if (isInvalidContext)
|
|
25
|
+
return;
|
|
26
|
+
payload?.context &&
|
|
27
|
+
payload.context.update?.({
|
|
28
|
+
value: payload,
|
|
29
|
+
listener: listener,
|
|
30
|
+
});
|
|
31
|
+
listener?.(payload, resolve);
|
|
32
|
+
};
|
|
33
|
+
subscriber.id = listener.id;
|
|
34
|
+
if (!observer.has(listener.id))
|
|
35
|
+
observer.subscribe(subscriber);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
this.add = (id, store, _name) => {
|
|
39
|
+
this.subscribeStore(id, store);
|
|
40
|
+
this.collection.set(id, store);
|
|
41
|
+
return store;
|
|
42
|
+
};
|
|
43
|
+
this.registerListener = (id, cb) => {
|
|
44
|
+
if (this.listeners.has(id)) {
|
|
45
|
+
const listeners = this.listeners.get(id);
|
|
46
|
+
listeners.push(cb);
|
|
47
|
+
this.listeners.set(id, listeners);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
this.listeners.set(id, [cb]);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
this.hasListener = (id) => {
|
|
54
|
+
let result = false;
|
|
55
|
+
for (const [, listener] of this.listeners) {
|
|
56
|
+
result = listener.some((cb) => cb.id === id);
|
|
57
|
+
if (result)
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
};
|
|
62
|
+
this.remove = (id, mutationId) => {
|
|
63
|
+
const store = this.collection.get(id);
|
|
64
|
+
if (!store)
|
|
65
|
+
return;
|
|
66
|
+
const listeners = this.listeners.get(id) ?? [];
|
|
67
|
+
for (const listener of listeners) {
|
|
68
|
+
if (listener.id !== mutationId)
|
|
69
|
+
continue;
|
|
70
|
+
if (store.observer.has(listener.id)) {
|
|
71
|
+
store.observer.unsubscribe(listener);
|
|
72
|
+
}
|
|
73
|
+
const index = listeners.indexOf(listener);
|
|
74
|
+
if (index > -1)
|
|
75
|
+
listeners.splice(index, 1);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
this.removeAll = (id) => {
|
|
79
|
+
const store = this.collection.get(id);
|
|
80
|
+
if (!store)
|
|
81
|
+
return;
|
|
82
|
+
const listeners = this.listeners.get(id) ?? [];
|
|
83
|
+
for (const listener of listeners) {
|
|
84
|
+
if (store.observer.has(listener.id)) {
|
|
85
|
+
store.observer.unsubscribe(listener);
|
|
86
|
+
}
|
|
87
|
+
const index = listeners.indexOf(listener);
|
|
88
|
+
if (index > -1)
|
|
89
|
+
listeners.splice(index, 1);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
this.get = (id) => this.collection.get(id);
|
|
93
|
+
this.has = (id) => this.collection.has(id);
|
|
94
|
+
}
|
|
95
|
+
}
|
package/lib/utils.d.ts
ADDED
package/lib/utils.js
CHANGED
|
@@ -1 +1,5 @@
|
|
|
1
|
-
export
|
|
1
|
+
export const getStackTrace = () => {
|
|
2
|
+
const error = new Error();
|
|
3
|
+
return error.stack;
|
|
4
|
+
};
|
|
5
|
+
export const randomId = () => Math.random().toString(36).substring(2, 16);
|
package/package.json
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hermes-io",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.97",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A lightweight React library that allows communication between Reactjs components by using the observer pattern and the hook api",
|
|
6
6
|
"main": "./lib/index.js",
|
|
7
7
|
"module": "./lib/index.js",
|
|
8
|
+
"types": "./lib/index.d.ts",
|
|
8
9
|
"scripts": {
|
|
9
10
|
"test": "vitest __test__",
|
|
10
|
-
"
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"prepare": "tsc"
|
|
11
13
|
},
|
|
12
14
|
"exports": {
|
|
13
15
|
".": {
|
|
16
|
+
"types": "./lib/index.d.ts",
|
|
14
17
|
"import": "./lib/index.js",
|
|
15
18
|
"require": "./lib/index.js"
|
|
16
19
|
}
|
|
@@ -27,21 +30,19 @@
|
|
|
27
30
|
"react": "^18.2.0"
|
|
28
31
|
},
|
|
29
32
|
"devDependencies": {
|
|
30
|
-
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
|
31
|
-
"@swc/cli": "^0.1.62",
|
|
32
|
-
"@swc/core": "^1.3.71",
|
|
33
33
|
"@testing-library/dom": "^9.3.4",
|
|
34
34
|
"@testing-library/jest-dom": "^6.4.2",
|
|
35
35
|
"@testing-library/react": "^14.2.1",
|
|
36
36
|
"@testing-library/react-hooks": "^8.0.1",
|
|
37
|
-
"@testing-library/svelte": "^4.1.0",
|
|
38
37
|
"@testing-library/user-event": "^14.5.2",
|
|
39
|
-
"@
|
|
38
|
+
"@types/react": "^19.2.14",
|
|
39
|
+
"@types/react-dom": "^19.2.3",
|
|
40
|
+
"@vitejs/plugin-react-swc": "^3.6.0",
|
|
40
41
|
"happy-dom": "^13.3.8",
|
|
41
42
|
"react": "^18.2.0",
|
|
42
43
|
"react-dom": "^18.2.0",
|
|
44
|
+
"typescript": "^6.0.2",
|
|
43
45
|
"vite": "^5.1.0",
|
|
44
|
-
"vitest": "^1.2.2"
|
|
45
|
-
"@vitejs/plugin-react-swc": "^3.6.0"
|
|
46
|
+
"vitest": "^1.2.2"
|
|
46
47
|
}
|
|
47
48
|
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"declarationDir": "./lib",
|
|
12
|
+
"outDir": "./lib",
|
|
13
|
+
"rootDir": "./src"
|
|
14
|
+
},
|
|
15
|
+
"include": ["src"],
|
|
16
|
+
"exclude": ["node_modules", "lib", "__test__", "src/setupTests.ts"]
|
|
17
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{useEffect as l}from"react";export var useMicroStore=function(r){var n=r.name,o=r.id,a=r.microStore,u=r.store;l(function(){var l,r;if(!(!o||(null==a?void 0:null==(l=a.has)?void 0:l.call(a,o))))return null==a||null==(r=a.add)||r.call(a,o,u,n),function(){var l;null==a||null==(l=a.removeAll)||l.call(a,o)}},[a,u,o])};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{useStore as r}from"./useStore.js";import{Context as e}from"../context/context.js";import{Observer as o}from"../observer/observer.js";import{Store as t}from"../store/store.js";import{useMicroStore as s}from"./useMicroStore.js";export var useObservableStore=function(m,i,n,c,a){var f=r({microStore:c,store:new t({id:m,context:new e("Context_"+m),observer:new o}),reducer:n,data:i}).store;return s({id:m,microStore:c,store:f,name:a}),{store:f}};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{useStore as r}from"./useStore";import{Context as e}from"../context/context";import{Observer as o}from"../observer/observer";import{Store as t}from"../store/store";export var useStoreFactory=function(n,s,m,c){var i={store:new t({id:n,context:new e("Context_"+n),observer:new o}),reducer:m,data:s};return c&&(i.microStore=c),{store:r(i).store}};
|