@praxisjs/store 0.2.1 → 0.2.2
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 +13 -0
- package/dist/__tests__/create-store.test.d.ts +2 -0
- package/dist/__tests__/create-store.test.d.ts.map +1 -0
- package/dist/__tests__/create-store.test.js +82 -0
- package/dist/__tests__/create-store.test.js.map +1 -0
- package/dist/__tests__/decorators.test.d.ts +2 -0
- package/dist/__tests__/decorators.test.d.ts.map +1 -0
- package/dist/__tests__/decorators.test.js +47 -0
- package/dist/__tests__/decorators.test.js.map +1 -0
- package/dist/create-store.js +1 -1
- package/dist/create-store.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/create-store.test.ts +98 -0
- package/src/__tests__/decorators.test.ts +59 -0
- package/src/create-store.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @praxisjs/store
|
|
2
2
|
|
|
3
|
+
## 0.2.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- fe39901: fix(store): fix `TypeError` when assigning symbol keys on the store Proxy
|
|
8
|
+
|
|
9
|
+
The Proxy `set` trap returned `false` for non-string keys (symbols). In ES modules with strict mode, returning `false` from a `set` trap throws `TypeError: 'set' on proxy: trap returned falsish`. This broke internal JavaScript operations that use symbol keys (e.g. `Symbol.toPrimitive`, `Symbol.iterator`).
|
|
10
|
+
|
|
11
|
+
The fix returns `true` in those cases, silently ignoring the assignment — the correct behaviour, since the store only manages string keys.
|
|
12
|
+
|
|
13
|
+
- Updated dependencies [fe39901]
|
|
14
|
+
- @praxisjs/core@0.4.1
|
|
15
|
+
|
|
3
16
|
## 0.2.1
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-store.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/create-store.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { createStore } from "../create-store";
|
|
3
|
+
function makeCounter() {
|
|
4
|
+
return createStore({
|
|
5
|
+
count: 0,
|
|
6
|
+
step: 1,
|
|
7
|
+
increment() {
|
|
8
|
+
this.count +=
|
|
9
|
+
this.step;
|
|
10
|
+
},
|
|
11
|
+
decrement() {
|
|
12
|
+
this.count--;
|
|
13
|
+
},
|
|
14
|
+
})();
|
|
15
|
+
}
|
|
16
|
+
describe("createStore", () => {
|
|
17
|
+
it("returns a factory that provides the store proxy", () => {
|
|
18
|
+
const store = makeCounter();
|
|
19
|
+
expect(store).toBeDefined();
|
|
20
|
+
});
|
|
21
|
+
it("reads signal state via property access", () => {
|
|
22
|
+
const store = makeCounter();
|
|
23
|
+
expect(store.count).toBe(0);
|
|
24
|
+
});
|
|
25
|
+
it("sets signal state via property assignment", () => {
|
|
26
|
+
const store = makeCounter();
|
|
27
|
+
store.count = 5;
|
|
28
|
+
expect(store.count).toBe(5);
|
|
29
|
+
});
|
|
30
|
+
it("calls methods bound to the store proxy", () => {
|
|
31
|
+
const store = makeCounter();
|
|
32
|
+
store.increment();
|
|
33
|
+
expect(store.count).toBe(1);
|
|
34
|
+
});
|
|
35
|
+
it("$state returns a plain snapshot of all state", () => {
|
|
36
|
+
const store = makeCounter();
|
|
37
|
+
const state = store.$state();
|
|
38
|
+
expect(state).toEqual({ count: 0, step: 1 });
|
|
39
|
+
});
|
|
40
|
+
it("$patch updates multiple keys at once", () => {
|
|
41
|
+
const store = makeCounter();
|
|
42
|
+
store.$patch({ count: 10, step: 5 });
|
|
43
|
+
expect(store.count).toBe(10);
|
|
44
|
+
expect(store.step).toBe(5);
|
|
45
|
+
});
|
|
46
|
+
it("$reset restores initial values", () => {
|
|
47
|
+
const store = makeCounter();
|
|
48
|
+
store.count = 99;
|
|
49
|
+
store.$reset();
|
|
50
|
+
expect(store.count).toBe(0);
|
|
51
|
+
});
|
|
52
|
+
it("$subscribe calls listener with state snapshot on each change", () => {
|
|
53
|
+
const store = makeCounter();
|
|
54
|
+
const snapshots = [];
|
|
55
|
+
store.$subscribe((s) => snapshots.push(s.count));
|
|
56
|
+
store.count = 42;
|
|
57
|
+
store.count = 99;
|
|
58
|
+
expect(snapshots).toEqual([0, 42, 99]);
|
|
59
|
+
});
|
|
60
|
+
it("definition getters are exposed on the store proxy", () => {
|
|
61
|
+
const definition = {
|
|
62
|
+
a: 2,
|
|
63
|
+
b: 3,
|
|
64
|
+
get sum() { return this.a + this.b; },
|
|
65
|
+
};
|
|
66
|
+
const store = createStore(definition)();
|
|
67
|
+
expect(store.sum).toBe(5);
|
|
68
|
+
});
|
|
69
|
+
it("setting a non-signal key via proxy returns false silently", () => {
|
|
70
|
+
const store = makeCounter();
|
|
71
|
+
// symbol keys are skipped — should not throw
|
|
72
|
+
expect(() => {
|
|
73
|
+
store[Symbol("noop")] = 1;
|
|
74
|
+
}).not.toThrow();
|
|
75
|
+
});
|
|
76
|
+
it("$patch ignores unknown keys", () => {
|
|
77
|
+
const store = makeCounter();
|
|
78
|
+
expect(() => { store.$patch({ unknownKey: 123 }); }).not.toThrow();
|
|
79
|
+
expect(store.count).toBe(0);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
//# sourceMappingURL=create-store.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-store.test.js","sourceRoot":"","sources":["../../src/__tests__/create-store.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decorators.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/decorators.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { Store, UseStore } from "../decorators";
|
|
3
|
+
describe("Store decorator", () => {
|
|
4
|
+
it("registers the class without throwing", () => {
|
|
5
|
+
class CounterStore {
|
|
6
|
+
value = 0;
|
|
7
|
+
}
|
|
8
|
+
expect(() => { Store()(CounterStore, {}); }).not.toThrow();
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
describe("UseStore decorator", () => {
|
|
12
|
+
it("injects a singleton store instance via getter", () => {
|
|
13
|
+
class AppStore {
|
|
14
|
+
name = "app";
|
|
15
|
+
}
|
|
16
|
+
Store()(AppStore, {});
|
|
17
|
+
const initializers = [];
|
|
18
|
+
const ctx = {
|
|
19
|
+
name: "store",
|
|
20
|
+
kind: "field",
|
|
21
|
+
addInitializer(fn) { initializers.push(fn); },
|
|
22
|
+
};
|
|
23
|
+
UseStore(AppStore)(undefined, ctx);
|
|
24
|
+
const instance = {};
|
|
25
|
+
initializers.forEach((fn) => { fn.call(instance); });
|
|
26
|
+
expect(instance.store).toBeInstanceOf(AppStore);
|
|
27
|
+
expect(instance.store.name).toBe("app");
|
|
28
|
+
});
|
|
29
|
+
it("returns the same instance on repeated access (singleton)", () => {
|
|
30
|
+
class SharedStore {
|
|
31
|
+
}
|
|
32
|
+
Store()(SharedStore, {});
|
|
33
|
+
const initializers = [];
|
|
34
|
+
const ctx = {
|
|
35
|
+
name: "sharedStore",
|
|
36
|
+
kind: "field",
|
|
37
|
+
addInitializer(fn) { initializers.push(fn); },
|
|
38
|
+
};
|
|
39
|
+
UseStore(SharedStore)(undefined, ctx);
|
|
40
|
+
const a = {};
|
|
41
|
+
const b = {};
|
|
42
|
+
initializers.forEach((fn) => { fn.call(a); });
|
|
43
|
+
initializers.forEach((fn) => { fn.call(b); });
|
|
44
|
+
expect(a.sharedStore).toBe(b.sharedStore);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
//# sourceMappingURL=decorators.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decorators.test.js","sourceRoot":"","sources":["../../src/__tests__/decorators.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEhD,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,YAAY;YAChB,KAAK,GAAG,CAAC,CAAC;SACX;QACD,MAAM,CAAC,GAAG,EAAE,GACR,KAAK,EAAE,CAAC,YAAY,EAAE,EAA2B,CAAC,CAAC,CAAC,CAAC,CACxD,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,QAAQ;YACZ,IAAI,GAAG,KAAK,CAAC;SACd;QACD,KAAK,EAAE,CAAC,QAAQ,EAAE,EAA2B,CAAC,CAAC;QAE/C,MAAM,YAAY,GAAmC,EAAE,CAAC;QACxD,MAAM,GAAG,GAAG;YACV,IAAI,EAAE,OAAO;YACb,IAAI,EAAE,OAAgB;YACtB,cAAc,CAAC,EAA2B,IAAI,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;SACzC,CAAC;QAEhC,QAAQ,CAAC,QAAQ,CAAC,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QAEnC,MAAM,QAAQ,GAA4B,EAAE,CAAC;QAC7C,YAAY,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAErD,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;QAChD,MAAM,CAAE,QAAQ,CAAC,KAAkB,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,WAAW;SAAG;QACpB,KAAK,EAAE,CAAC,WAAW,EAAE,EAA2B,CAAC,CAAC;QAElD,MAAM,YAAY,GAAmC,EAAE,CAAC;QACxD,MAAM,GAAG,GAAG;YACV,IAAI,EAAE,aAAa;YACnB,IAAI,EAAE,OAAgB;YACtB,cAAc,CAAC,EAA2B,IAAI,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;SACzC,CAAC;QAEhC,QAAQ,CAAC,WAAW,CAAC,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QAEtC,MAAM,CAAC,GAA4B,EAAE,CAAC;QACtC,MAAM,CAAC,GAA4B,EAAE,CAAC;QACtC,YAAY,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9C,YAAY,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAE9C,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/dist/create-store.js
CHANGED
package/dist/create-store.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-store.js","sourceRoot":"","sources":["../src/create-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEzD,MAAM,UAAU,WAAW,CACzB,UAA2B;IAE3B,MAAM,YAAY,GAA4B,EAAE,CAAC;IACjD,MAAM,OAAO,GAAoD,EAAE,CAAC;IAEpE,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QACtD,IAAI,OAAO,KAAK,KAAK,UAAU;YAC7B,OAAO,CAAC,GAAG,CAAC,GAAG,KAAwC,CAAC;;YACrD,YAAY,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACjC,CAAC;IAED,MAAM,OAAO,GAA8C,EAAE,CAAC;IAC9D,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;QACxD,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,EAAO,EAAE;QAC/B,GAAG,CAAC,EAAE,EAAE,GAAoB;YAC1B,IAAI,OAAO,GAAG,KAAK,QAAQ;gBAAE,OAAO,SAAS,CAAC;YAC9C,IAAI,GAAG,IAAI,OAAO;gBAChB,OAAO,CAAC,GAAG,IAAe,EAAW,EAAE,CACrC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,CAAC;YACtC,IAAI,GAAG,KAAK,YAAY;gBAAE,OAAO,SAAS,CAAC;YAC3C,IAAI,GAAG,KAAK,QAAQ;gBAAE,OAAO,KAAK,CAAC;YACnC,IAAI,GAAG,KAAK,QAAQ;gBAAE,OAAO,KAAK,CAAC;YACnC,IAAI,GAAG,KAAK,QAAQ;gBAAE,OAAO,QAAQ,CAAC;YACtC,IAAI,GAAG,IAAI,OAAO;gBAAE,OAAO,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1C,MAAM,IAAI,GAAG,MAAM,CAAC,wBAAwB,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;YAC9D,IAAI,IAAI,EAAE,GAAG;gBAAE,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAY,CAAC;YACtD,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,GAAG,CAAC,EAAE,EAAE,GAAoB,EAAE,KAAc;YAC1C,IAAI,OAAO,GAAG,KAAK,QAAQ;gBAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"create-store.js","sourceRoot":"","sources":["../src/create-store.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@praxisjs/store",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"typescript": "^5.9.3"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@praxisjs/core": "0.4.
|
|
17
|
+
"@praxisjs/core": "0.4.1"
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "tsc",
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { createStore } from "../create-store";
|
|
4
|
+
|
|
5
|
+
function makeCounter() {
|
|
6
|
+
return createStore({
|
|
7
|
+
count: 0,
|
|
8
|
+
step: 1,
|
|
9
|
+
increment() {
|
|
10
|
+
(this as unknown as { count: number; step: number }).count +=
|
|
11
|
+
(this as unknown as { count: number; step: number }).step;
|
|
12
|
+
},
|
|
13
|
+
decrement() {
|
|
14
|
+
(this as unknown as { count: number }).count--;
|
|
15
|
+
},
|
|
16
|
+
})();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
describe("createStore", () => {
|
|
20
|
+
it("returns a factory that provides the store proxy", () => {
|
|
21
|
+
const store = makeCounter();
|
|
22
|
+
expect(store).toBeDefined();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("reads signal state via property access", () => {
|
|
26
|
+
const store = makeCounter();
|
|
27
|
+
expect(store.count).toBe(0);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("sets signal state via property assignment", () => {
|
|
31
|
+
const store = makeCounter();
|
|
32
|
+
(store as unknown as { count: number }).count = 5;
|
|
33
|
+
expect(store.count).toBe(5);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("calls methods bound to the store proxy", () => {
|
|
37
|
+
const store = makeCounter();
|
|
38
|
+
(store as unknown as { increment: () => void }).increment();
|
|
39
|
+
expect(store.count).toBe(1);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("$state returns a plain snapshot of all state", () => {
|
|
43
|
+
const store = makeCounter();
|
|
44
|
+
const state = (store as unknown as { $state: () => Record<string, unknown> }).$state();
|
|
45
|
+
expect(state).toEqual({ count: 0, step: 1 });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("$patch updates multiple keys at once", () => {
|
|
49
|
+
const store = makeCounter();
|
|
50
|
+
(store as unknown as { $patch: (p: Record<string, unknown>) => void }).$patch({ count: 10, step: 5 });
|
|
51
|
+
expect(store.count).toBe(10);
|
|
52
|
+
expect(store.step).toBe(5);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("$reset restores initial values", () => {
|
|
56
|
+
const store = makeCounter();
|
|
57
|
+
(store as unknown as { count: number }).count = 99;
|
|
58
|
+
(store as unknown as { $reset: () => void }).$reset();
|
|
59
|
+
expect(store.count).toBe(0);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("$subscribe calls listener with state snapshot on each change", () => {
|
|
63
|
+
const store = makeCounter();
|
|
64
|
+
const snapshots: number[] = [];
|
|
65
|
+
(store as unknown as { $subscribe: (fn: (s: Record<string, unknown>) => void) => () => void }).$subscribe(
|
|
66
|
+
(s) => snapshots.push(s.count as number),
|
|
67
|
+
);
|
|
68
|
+
(store as unknown as { count: number }).count = 42;
|
|
69
|
+
(store as unknown as { count: number }).count = 99;
|
|
70
|
+
expect(snapshots).toEqual([0, 42, 99]);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("definition getters are exposed on the store proxy", () => {
|
|
74
|
+
const definition = {
|
|
75
|
+
a: 2,
|
|
76
|
+
b: 3,
|
|
77
|
+
get sum() { return (this as unknown as { a: number; b: number }).a + (this as unknown as { a: number; b: number }).b; },
|
|
78
|
+
};
|
|
79
|
+
const store = createStore(definition)();
|
|
80
|
+
expect((store as unknown as { sum: number }).sum).toBe(5);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("setting a non-signal key via proxy returns false silently", () => {
|
|
84
|
+
const store = makeCounter();
|
|
85
|
+
// symbol keys are skipped — should not throw
|
|
86
|
+
expect(() => {
|
|
87
|
+
(store as unknown as Record<symbol, unknown>)[Symbol("noop")] = 1;
|
|
88
|
+
}).not.toThrow();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("$patch ignores unknown keys", () => {
|
|
92
|
+
const store = makeCounter();
|
|
93
|
+
expect(() =>
|
|
94
|
+
{ (store as unknown as { $patch: (p: Record<string, unknown>) => void }).$patch({ unknownKey: 123 }); },
|
|
95
|
+
).not.toThrow();
|
|
96
|
+
expect(store.count).toBe(0);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { Store, UseStore } from "../decorators";
|
|
4
|
+
|
|
5
|
+
describe("Store decorator", () => {
|
|
6
|
+
it("registers the class without throwing", () => {
|
|
7
|
+
class CounterStore {
|
|
8
|
+
value = 0;
|
|
9
|
+
}
|
|
10
|
+
expect(() =>
|
|
11
|
+
{ Store()(CounterStore, {} as ClassDecoratorContext); },
|
|
12
|
+
).not.toThrow();
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe("UseStore decorator", () => {
|
|
17
|
+
it("injects a singleton store instance via getter", () => {
|
|
18
|
+
class AppStore {
|
|
19
|
+
name = "app";
|
|
20
|
+
}
|
|
21
|
+
Store()(AppStore, {} as ClassDecoratorContext);
|
|
22
|
+
|
|
23
|
+
const initializers: Array<(this: unknown) => void> = [];
|
|
24
|
+
const ctx = {
|
|
25
|
+
name: "store",
|
|
26
|
+
kind: "field" as const,
|
|
27
|
+
addInitializer(fn: (this: unknown) => void) { initializers.push(fn); },
|
|
28
|
+
} as ClassFieldDecoratorContext;
|
|
29
|
+
|
|
30
|
+
UseStore(AppStore)(undefined, ctx);
|
|
31
|
+
|
|
32
|
+
const instance: Record<string, unknown> = {};
|
|
33
|
+
initializers.forEach((fn) => { fn.call(instance); });
|
|
34
|
+
|
|
35
|
+
expect(instance.store).toBeInstanceOf(AppStore);
|
|
36
|
+
expect((instance.store as AppStore).name).toBe("app");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("returns the same instance on repeated access (singleton)", () => {
|
|
40
|
+
class SharedStore {}
|
|
41
|
+
Store()(SharedStore, {} as ClassDecoratorContext);
|
|
42
|
+
|
|
43
|
+
const initializers: Array<(this: unknown) => void> = [];
|
|
44
|
+
const ctx = {
|
|
45
|
+
name: "sharedStore",
|
|
46
|
+
kind: "field" as const,
|
|
47
|
+
addInitializer(fn: (this: unknown) => void) { initializers.push(fn); },
|
|
48
|
+
} as ClassFieldDecoratorContext;
|
|
49
|
+
|
|
50
|
+
UseStore(SharedStore)(undefined, ctx);
|
|
51
|
+
|
|
52
|
+
const a: Record<string, unknown> = {};
|
|
53
|
+
const b: Record<string, unknown> = {};
|
|
54
|
+
initializers.forEach((fn) => { fn.call(a); });
|
|
55
|
+
initializers.forEach((fn) => { fn.call(b); });
|
|
56
|
+
|
|
57
|
+
expect(a.sharedStore).toBe(b.sharedStore);
|
|
58
|
+
});
|
|
59
|
+
});
|
package/src/create-store.ts
CHANGED
|
@@ -33,7 +33,7 @@ export function createStore<T extends Record<string, unknown>>(
|
|
|
33
33
|
return undefined;
|
|
34
34
|
},
|
|
35
35
|
set(_t, key: string | symbol, value: unknown) {
|
|
36
|
-
if (typeof key !== "string") return
|
|
36
|
+
if (typeof key !== "string") return true;
|
|
37
37
|
if (key in signals) {
|
|
38
38
|
signals[key].set(value);
|
|
39
39
|
return true;
|