@praxisjs/store 1.2.3 → 1.3.1
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/CHANGELOG.md +20 -0
- package/dist/__tests__/plugins.test.d.ts +2 -0
- package/dist/__tests__/plugins.test.d.ts.map +1 -0
- package/dist/__tests__/plugins.test.js +379 -0
- package/dist/__tests__/plugins.test.js.map +1 -0
- package/dist/apply-plugins.d.ts +3 -0
- package/dist/apply-plugins.d.ts.map +1 -0
- package/dist/apply-plugins.js +66 -0
- package/dist/apply-plugins.js.map +1 -0
- package/dist/create-store.d.ts +6 -1
- package/dist/create-store.d.ts.map +1 -1
- package/dist/create-store.js +47 -4
- package/dist/create-store.js.map +1 -1
- package/dist/decorators.d.ts +5 -1
- package/dist/decorators.d.ts.map +1 -1
- package/dist/decorators.js +12 -2
- package/dist/decorators.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/plugin-registry.d.ts +5 -0
- package/dist/plugin-registry.d.ts.map +1 -0
- package/dist/plugin-registry.js +11 -0
- package/dist/plugin-registry.js.map +1 -0
- package/dist/plugin-types.d.ts +28 -0
- package/dist/plugin-types.d.ts.map +1 -0
- package/dist/plugin-types.js +2 -0
- package/dist/plugin-types.js.map +1 -0
- package/package.json +3 -3
- package/src/__tests__/plugins.test.ts +445 -0
- package/src/apply-plugins.ts +77 -0
- package/src/create-store.ts +56 -4
- package/src/decorators.ts +23 -2
- package/src/index.ts +10 -1
- package/src/plugin-registry.ts +15 -0
- package/src/plugin-types.ts +31 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { StorePlugin } from "./plugin-types.js";
|
|
2
|
+
|
|
3
|
+
export function applyPluginsToStore<T extends object>(
|
|
4
|
+
instance: T,
|
|
5
|
+
storeName: string,
|
|
6
|
+
plugins: StorePlugin[],
|
|
7
|
+
): T {
|
|
8
|
+
if (plugins.length === 0) return instance;
|
|
9
|
+
|
|
10
|
+
const extensions: Record<string, unknown> = {};
|
|
11
|
+
|
|
12
|
+
const proxy: T = new Proxy(instance, {
|
|
13
|
+
get(target, prop: string | symbol): unknown {
|
|
14
|
+
if (typeof prop === "symbol") return Reflect.get(target, prop) as unknown;
|
|
15
|
+
|
|
16
|
+
if (prop in extensions) {
|
|
17
|
+
const ext = extensions[prop];
|
|
18
|
+
return typeof ext === "function"
|
|
19
|
+
? (ext as (...a: unknown[]) => unknown).bind(proxy)
|
|
20
|
+
: ext;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const val = Reflect.get(target, prop, proxy) as unknown;
|
|
24
|
+
|
|
25
|
+
if (typeof val === "function" && !prop.startsWith("$")) {
|
|
26
|
+
return (...args: unknown[]): unknown => {
|
|
27
|
+
for (const p of plugins) p.onAction?.({ name: prop, args, storeName });
|
|
28
|
+
const result = (val as (...a: unknown[]) => unknown).apply(proxy, args);
|
|
29
|
+
if (result instanceof Promise) {
|
|
30
|
+
return result
|
|
31
|
+
.then((resolved: unknown) => {
|
|
32
|
+
for (const p of plugins)
|
|
33
|
+
p.onActionDone?.({ name: prop, args, storeName, result: resolved });
|
|
34
|
+
return resolved;
|
|
35
|
+
})
|
|
36
|
+
.catch((err: unknown) => {
|
|
37
|
+
for (const p of plugins)
|
|
38
|
+
p.onActionDone?.({ name: prop, args, storeName, result: undefined, error: err });
|
|
39
|
+
throw err;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
for (const p of plugins)
|
|
43
|
+
p.onActionDone?.({ name: prop, args, storeName, result });
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return val;
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
set(target, prop: string | symbol, value: unknown): boolean {
|
|
52
|
+
if (typeof prop === "symbol") {
|
|
53
|
+
(target as Record<symbol, unknown>)[prop] = value;
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
const prevValue = Reflect.get(target, prop, proxy) as unknown;
|
|
57
|
+
const ok: boolean = Reflect.set(target, prop, value, proxy);
|
|
58
|
+
if (ok) {
|
|
59
|
+
for (const p of plugins)
|
|
60
|
+
p.onMutation?.({ key: prop, value, prevValue, storeName });
|
|
61
|
+
}
|
|
62
|
+
return ok;
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
for (const plugin of plugins) {
|
|
67
|
+
plugin.onInit?.({
|
|
68
|
+
store: proxy as unknown as Record<string, unknown>,
|
|
69
|
+
storeName,
|
|
70
|
+
extend(props) {
|
|
71
|
+
Object.assign(extensions, props);
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return proxy;
|
|
77
|
+
}
|
package/src/create-store.ts
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import { signal, effect } from "@praxisjs/core/internal";
|
|
2
2
|
|
|
3
|
+
import { getGlobalPlugins } from "./plugin-registry.js";
|
|
4
|
+
|
|
5
|
+
import type { StorePlugin } from "./plugin-types.js";
|
|
6
|
+
|
|
7
|
+
export interface CreateStoreOptions {
|
|
8
|
+
name?: string;
|
|
9
|
+
plugins?: StorePlugin[];
|
|
10
|
+
}
|
|
11
|
+
|
|
3
12
|
export function createStore<T extends Record<string, unknown>>(
|
|
4
13
|
definition: T & ThisType<T>,
|
|
14
|
+
options: CreateStoreOptions = {},
|
|
5
15
|
) {
|
|
16
|
+
const storeName = options.name ?? "anonymous";
|
|
6
17
|
const initialState: Record<string, unknown> = {};
|
|
7
18
|
const methods: Record<string, (...args: unknown[]) => unknown> = {};
|
|
8
19
|
|
|
@@ -17,12 +28,40 @@ export function createStore<T extends Record<string, unknown>>(
|
|
|
17
28
|
signals[key] = signal(value);
|
|
18
29
|
}
|
|
19
30
|
|
|
31
|
+
const activePlugins = [...getGlobalPlugins(), ...(options.plugins ?? [])];
|
|
32
|
+
const extensions: Record<string, unknown> = {};
|
|
33
|
+
|
|
20
34
|
const store = new Proxy({} as T, {
|
|
21
35
|
get(_t, key: string | symbol) {
|
|
22
36
|
if (typeof key !== "string") return undefined;
|
|
23
|
-
if (key in
|
|
24
|
-
|
|
25
|
-
|
|
37
|
+
if (key in extensions) {
|
|
38
|
+
const ext = extensions[key];
|
|
39
|
+
return typeof ext === "function"
|
|
40
|
+
? (ext as (...a: unknown[]) => unknown).bind(store)
|
|
41
|
+
: ext;
|
|
42
|
+
}
|
|
43
|
+
if (key in methods) {
|
|
44
|
+
return (...args: unknown[]): unknown => {
|
|
45
|
+
for (const p of activePlugins) p.onAction?.({ name: key, args, storeName });
|
|
46
|
+
const result = methods[key].call(store, ...args);
|
|
47
|
+
if (result instanceof Promise) {
|
|
48
|
+
return result
|
|
49
|
+
.then((resolved: unknown) => {
|
|
50
|
+
for (const p of activePlugins)
|
|
51
|
+
p.onActionDone?.({ name: key, args, storeName, result: resolved });
|
|
52
|
+
return resolved;
|
|
53
|
+
})
|
|
54
|
+
.catch((err: unknown) => {
|
|
55
|
+
for (const p of activePlugins)
|
|
56
|
+
p.onActionDone?.({ name: key, args, storeName, result: undefined, error: err });
|
|
57
|
+
throw err;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
for (const p of activePlugins)
|
|
61
|
+
p.onActionDone?.({ name: key, args, storeName, result });
|
|
62
|
+
return result;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
26
65
|
if (key === "$subscribe") return subscribe;
|
|
27
66
|
if (key === "$reset") return reset;
|
|
28
67
|
if (key === "$patch") return patch;
|
|
@@ -35,13 +74,26 @@ export function createStore<T extends Record<string, unknown>>(
|
|
|
35
74
|
set(_t, key: string | symbol, value: unknown) {
|
|
36
75
|
if (typeof key !== "string") return true;
|
|
37
76
|
if (key in signals) {
|
|
77
|
+
const prevValue = signals[key]();
|
|
38
78
|
signals[key].set(value);
|
|
79
|
+
for (const p of activePlugins)
|
|
80
|
+
p.onMutation?.({ key, value, prevValue, storeName });
|
|
39
81
|
return true;
|
|
40
82
|
}
|
|
41
83
|
return false;
|
|
42
84
|
},
|
|
43
85
|
});
|
|
44
86
|
|
|
87
|
+
for (const plugin of activePlugins) {
|
|
88
|
+
plugin.onInit?.({
|
|
89
|
+
store: store as unknown as Record<string, unknown>,
|
|
90
|
+
storeName,
|
|
91
|
+
extend(props) {
|
|
92
|
+
Object.assign(extensions, props);
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
45
97
|
function getState(): Record<string, unknown> {
|
|
46
98
|
const s: Record<string, unknown> = {};
|
|
47
99
|
for (const k of Object.keys(signals)) s[k] = signals[k]();
|
|
@@ -61,7 +113,7 @@ export function createStore<T extends Record<string, unknown>>(
|
|
|
61
113
|
function patch(partial: Partial<Record<string, unknown>>): void {
|
|
62
114
|
for (const [k, v] of Object.entries(partial)) {
|
|
63
115
|
if (v === undefined) continue;
|
|
64
|
-
if (k in signals)
|
|
116
|
+
if (k in signals) Reflect.set(store as object, k, v);
|
|
65
117
|
}
|
|
66
118
|
}
|
|
67
119
|
|
package/src/decorators.ts
CHANGED
|
@@ -4,25 +4,46 @@ import {
|
|
|
4
4
|
type ReactiveHost,
|
|
5
5
|
} from "@praxisjs/decorators";
|
|
6
6
|
|
|
7
|
+
import { applyPluginsToStore } from "./apply-plugins.js";
|
|
8
|
+
import { getGlobalPlugins } from "./plugin-registry.js";
|
|
9
|
+
|
|
10
|
+
import type { StorePlugin } from "./plugin-types.js";
|
|
11
|
+
|
|
7
12
|
const storeRegistry = new Map<new (...args: unknown[]) => unknown, unknown>();
|
|
13
|
+
const storePluginsMap = new WeakMap<
|
|
14
|
+
new (...args: unknown[]) => ReactiveStore,
|
|
15
|
+
StorePlugin[]
|
|
16
|
+
>();
|
|
8
17
|
|
|
9
18
|
/** Base class for store classes. Extend this to enable `@State` and `@DeepState` on store fields. */
|
|
10
19
|
export class ReactiveStore implements ReactiveHost {
|
|
11
20
|
_stateDirty = false;
|
|
12
21
|
}
|
|
13
22
|
|
|
14
|
-
export
|
|
23
|
+
export interface StorableOptions {
|
|
24
|
+
plugins?: StorePlugin[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function Storable(options: StorableOptions = {}) {
|
|
15
28
|
return function (
|
|
16
29
|
constructor: new (...args: unknown[]) => ReactiveStore,
|
|
17
30
|
_context: ClassDecoratorContext,
|
|
18
31
|
): void {
|
|
19
32
|
storeRegistry.set(constructor, null);
|
|
33
|
+
if (options.plugins?.length) {
|
|
34
|
+
storePluginsMap.set(constructor, options.plugins);
|
|
35
|
+
}
|
|
20
36
|
};
|
|
21
37
|
}
|
|
22
38
|
|
|
23
39
|
export function store<T extends ReactiveStore>(StoreClass: new () => T): T {
|
|
24
40
|
if (!storeRegistry.has(StoreClass) || storeRegistry.get(StoreClass) === null) {
|
|
25
|
-
|
|
41
|
+
const instance = new StoreClass();
|
|
42
|
+
const perStore =
|
|
43
|
+
storePluginsMap.get(StoreClass as new (...args: unknown[]) => ReactiveStore) ?? [];
|
|
44
|
+
const plugins = [...getGlobalPlugins(), ...perStore];
|
|
45
|
+
const wrapped = applyPluginsToStore(instance, StoreClass.name, plugins);
|
|
46
|
+
storeRegistry.set(StoreClass, wrapped);
|
|
26
47
|
}
|
|
27
48
|
return storeRegistry.get(StoreClass) as T;
|
|
28
49
|
}
|
package/src/index.ts
CHANGED
|
@@ -1 +1,10 @@
|
|
|
1
|
-
export { Storable, Store, store, ReactiveStore } from "./decorators";
|
|
1
|
+
export { Storable, Store, store, ReactiveStore } from "./decorators.js";
|
|
2
|
+
export type { StorableOptions } from "./decorators.js";
|
|
3
|
+
export { useStorePlugin, clearPlugins } from "./plugin-registry.js";
|
|
4
|
+
export type {
|
|
5
|
+
StorePlugin,
|
|
6
|
+
StoreMutation,
|
|
7
|
+
StoreActionContext,
|
|
8
|
+
StoreActionResult,
|
|
9
|
+
StoreInitContext,
|
|
10
|
+
} from "./plugin-types.js";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { StorePlugin } from "./plugin-types.js";
|
|
2
|
+
|
|
3
|
+
const globalRegistry: StorePlugin[] = [];
|
|
4
|
+
|
|
5
|
+
export function useStorePlugin(plugin: StorePlugin): void {
|
|
6
|
+
globalRegistry.push(plugin);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function getGlobalPlugins(): StorePlugin[] {
|
|
10
|
+
return globalRegistry.slice();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function clearPlugins(): void {
|
|
14
|
+
globalRegistry.length = 0;
|
|
15
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface StoreMutation {
|
|
2
|
+
key: string;
|
|
3
|
+
value: unknown;
|
|
4
|
+
prevValue: unknown;
|
|
5
|
+
storeName: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface StoreActionContext {
|
|
9
|
+
name: string;
|
|
10
|
+
args: unknown[];
|
|
11
|
+
storeName: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface StoreActionResult extends StoreActionContext {
|
|
15
|
+
result: unknown;
|
|
16
|
+
error?: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface StoreInitContext {
|
|
20
|
+
store: Record<string, unknown>;
|
|
21
|
+
storeName: string;
|
|
22
|
+
extend(props: Record<string, unknown>): void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface StorePlugin {
|
|
26
|
+
name: string;
|
|
27
|
+
onInit?(context: StoreInitContext): void;
|
|
28
|
+
onMutation?(mutation: StoreMutation): void;
|
|
29
|
+
onAction?(context: StoreActionContext): void;
|
|
30
|
+
onActionDone?(result: StoreActionResult): void;
|
|
31
|
+
}
|