ustor 0.0.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/.github/workflows/node.js.yml +30 -0
- package/README.md +240 -0
- package/package.json +56 -0
- package/rollup.config.js +39 -0
- package/src/index.js +40 -0
- package/test.spec.js +370 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
|
2
|
+
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
|
3
|
+
|
|
4
|
+
name: test
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [main]
|
|
9
|
+
pull_request:
|
|
10
|
+
branches: [main]
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
|
|
16
|
+
strategy:
|
|
17
|
+
matrix:
|
|
18
|
+
node-version: [18.x]
|
|
19
|
+
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v3
|
|
23
|
+
- name: Use Node.js ${{ matrix.node-version }}
|
|
24
|
+
uses: actions/setup-node@v3
|
|
25
|
+
with:
|
|
26
|
+
node-version: ${{ matrix.node-version }}
|
|
27
|
+
cache: "npm"
|
|
28
|
+
- run: npm ci
|
|
29
|
+
- run: npm run build --if-present
|
|
30
|
+
- run: npm test
|
package/README.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# UStor
|
|
2
|
+
|
|
3
|
+
[](https://github.com/ustor/actions/workflows/node.js.yml) [](https://www.npmjs.com/package/ustor) [](https://unpkg.com/ustor) [](https://unpkg.com/ustor)
|
|
4
|
+
|
|
5
|
+
# Reactive Store Library
|
|
6
|
+
|
|
7
|
+
This library provides a powerful reactivity system for creating stateful objects using signals, integrated seamlessly into JavaScript objects and arrays. This library does not depend on any specific reactive library and can be used by any UI framework to manage and react to changes in state effectively.
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Installation](#installation)
|
|
12
|
+
- [Setup](#setup)
|
|
13
|
+
- [API](#api)
|
|
14
|
+
- [store(values, proto)](#storevalues-proto)
|
|
15
|
+
- [is(value)](#isvalue)
|
|
16
|
+
- [api.is](#apiis)
|
|
17
|
+
- [api.signal](#apisignal)
|
|
18
|
+
- [api.get](#apiget)
|
|
19
|
+
- [api.set](#apiset)
|
|
20
|
+
- [Usage](#usage)
|
|
21
|
+
- [Basic Usage](#basic-usage)
|
|
22
|
+
- [Advanced Usage](#advanced-usage)
|
|
23
|
+
- [Deeply Nested Structures](#deeply-nested-structures)
|
|
24
|
+
- [Arrays and Objects](#arrays-and-objects)
|
|
25
|
+
- [Reactivity and Effects](#reactivity-and-effects)
|
|
26
|
+
- [Integration with Solid.js, Preact Signals, and React](#integration-with-solidjs-preact-signals-and-react)
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
To use this library, you can install it using npm or yarn:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install ustor
|
|
34
|
+
yarn add ustor
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This library can be integrated `solid-js`, `@preact/signals-core`, `ulive`, `usignal`, `oby`, `sinuous`, `react` for managing reactive signals or any other libarary you can think of.
|
|
38
|
+
|
|
39
|
+
## Setup
|
|
40
|
+
|
|
41
|
+
First, import the `store` function and the `api` object. You need to initialize `api` with your preferred signal implementation.
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
import { store, api } from "./src";
|
|
45
|
+
// import { signal } from '@preact/signals-core';
|
|
46
|
+
import { createSignal } from "solid-js";
|
|
47
|
+
|
|
48
|
+
// Solid.js setup
|
|
49
|
+
api.signal = createSignal;
|
|
50
|
+
api.get = (v) => v?.[0]();
|
|
51
|
+
api.set = (signal, v) => signal?.[1](v);
|
|
52
|
+
api.is = (v) => v?.[0]?.name?.includes("readSignal");
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## API
|
|
56
|
+
|
|
57
|
+
### `store(values, proto)`
|
|
58
|
+
|
|
59
|
+
The `store` function creates a stateful object that automatically converts properties into reactive signals.
|
|
60
|
+
|
|
61
|
+
- **values**: The initial values for the store, which can be objects, arrays, or primitives.
|
|
62
|
+
- **proto**: Optional prototype to set for the new store object.
|
|
63
|
+
|
|
64
|
+
The function returns a reactive store object that maintains the structure of the original `values` while adding reactivity.
|
|
65
|
+
|
|
66
|
+
### `is(value)`
|
|
67
|
+
|
|
68
|
+
The `is` function checks if a given value is a reactive store instance.
|
|
69
|
+
|
|
70
|
+
- **value**: The value to check.
|
|
71
|
+
|
|
72
|
+
Returns `true` if the value is a reactive store, otherwise `false`.
|
|
73
|
+
|
|
74
|
+
### `api.is`
|
|
75
|
+
|
|
76
|
+
The api.is function checks if a given value is a signal.
|
|
77
|
+
|
|
78
|
+
- **value**: The value to check.
|
|
79
|
+
|
|
80
|
+
Returns true if the value is a signal, otherwise false.
|
|
81
|
+
|
|
82
|
+
### `api.signal`
|
|
83
|
+
|
|
84
|
+
The `api.signal` function is used to create a reactive signal.
|
|
85
|
+
|
|
86
|
+
- **value**: The initial value for the signal.
|
|
87
|
+
|
|
88
|
+
Returns a signal that can be used to create reactive state within the store.
|
|
89
|
+
|
|
90
|
+
### `api.get`
|
|
91
|
+
|
|
92
|
+
The `api.get` function retrieves the current value of a signal.
|
|
93
|
+
|
|
94
|
+
- **signal**: The signal whose value you want to retrieve.
|
|
95
|
+
|
|
96
|
+
Returns the current value of the signal.
|
|
97
|
+
|
|
98
|
+
### `api.set`
|
|
99
|
+
|
|
100
|
+
The `api.set` function updates the value of a signal.
|
|
101
|
+
|
|
102
|
+
- **signal**: The signal whose value you want to update.
|
|
103
|
+
- **value**: The new value to set.
|
|
104
|
+
|
|
105
|
+
Updates the signal with the provided value.
|
|
106
|
+
|
|
107
|
+
## Usage
|
|
108
|
+
|
|
109
|
+
### Basic Usage
|
|
110
|
+
|
|
111
|
+
```javascript
|
|
112
|
+
const s = store({ a: 1, b: 2 });
|
|
113
|
+
console.log(s.a); // 1
|
|
114
|
+
s.a = 5;
|
|
115
|
+
console.log(s.a); // 5
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
You can use the `store` function to create a reactive state object. Assigning a new value to `s.a` will automatically trigger updates wherever `s.a` is used.
|
|
119
|
+
|
|
120
|
+
### Advanced Usage
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
const s = store({
|
|
124
|
+
a: 2,
|
|
125
|
+
b: 3,
|
|
126
|
+
get sum() {
|
|
127
|
+
return this.a + this.b;
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
console.log(s.sum); // 5
|
|
132
|
+
s.a = 5;
|
|
133
|
+
console.log(s.sum); // 8
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Here, you can define getters that automatically compute derived state values, and these getters will update whenever the underlying signals change.
|
|
137
|
+
|
|
138
|
+
### Deeply Nested Structures
|
|
139
|
+
|
|
140
|
+
The `store` function can also handle deeply nested objects, converting nested properties into reactive signals:
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
const s = store({ nested: { deep: { value: 10 } } });
|
|
144
|
+
const deepValue = api.get(s.nested.deep.value) * 2;
|
|
145
|
+
|
|
146
|
+
console.log(deepValue); // 20
|
|
147
|
+
s.nested.deep.value = 15;
|
|
148
|
+
console.log(api.get(s.nested.deep.value) * 2); // 30
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Arrays and Objects
|
|
152
|
+
|
|
153
|
+
This library can handle arrays and objects seamlessly, automatically wrapping array elements with reactivity:
|
|
154
|
+
|
|
155
|
+
```javascript
|
|
156
|
+
const s = store({ list: [1, 2, 3] });
|
|
157
|
+
const sum = s.list.reduce((acc, item) => acc + item, 0);
|
|
158
|
+
|
|
159
|
+
console.log(sum); // 6
|
|
160
|
+
s.list = [1, 5, 3];
|
|
161
|
+
console.log(s.list.reduce((acc, item) => acc + item, 0)); // 9
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Reactivity and Effects
|
|
165
|
+
|
|
166
|
+
The library integrates with effects to track dependencies and automatically re-run whenever dependencies change:
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
const s = store({ a: 1 });
|
|
170
|
+
let effectValue = 0;
|
|
171
|
+
|
|
172
|
+
api.effect(() => {
|
|
173
|
+
effectValue = s.a * 2;
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
console.log(effectValue); // 2
|
|
177
|
+
s.a = 4;
|
|
178
|
+
console.log(effectValue); // 8
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Integration with Solid.js, Preact Signals, and React
|
|
182
|
+
|
|
183
|
+
This library can be used with Solid.js, Preact Signals, React, or any other UI framework to provide reactive signals.
|
|
184
|
+
|
|
185
|
+
To use Solid.js:
|
|
186
|
+
|
|
187
|
+
```javascript
|
|
188
|
+
import { createSignal } from "solid-js";
|
|
189
|
+
|
|
190
|
+
api.signal = createSignal;
|
|
191
|
+
api.get = (v) => v?.[0]();
|
|
192
|
+
api.set = (signal, v) => signal?.[1](v);
|
|
193
|
+
api.is = (v) =>
|
|
194
|
+
(Array.isArray(v) &&
|
|
195
|
+
typeof v[0] === "function" &&
|
|
196
|
+
typeof v[1] === "function") ||
|
|
197
|
+
v?.[0]?.name?.includes("readSignal");
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
To use Preact Signals, usignal, ulive, etc:
|
|
201
|
+
|
|
202
|
+
```javascript
|
|
203
|
+
import { signal } from "@preact/signals-core";
|
|
204
|
+
|
|
205
|
+
api.signal = signal;
|
|
206
|
+
api.get = (v) => v?.value;
|
|
207
|
+
api.set = (signal, v) => (signal.value = v);
|
|
208
|
+
api.is = (v) => v?.peek;
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
To use React:
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
import { useState, useMemo } from "react";
|
|
215
|
+
|
|
216
|
+
api.signal = useState;
|
|
217
|
+
api.get = (signal) => signal?.[0]();
|
|
218
|
+
api.set = (signal, value) => signal?.[1](value);
|
|
219
|
+
api.is = (v) => Array.isArray(v) && typeof v[1] === "function";
|
|
220
|
+
|
|
221
|
+
const state = store({
|
|
222
|
+
count: 0,
|
|
223
|
+
get double() { //useMemo
|
|
224
|
+
return useMemo(() => {
|
|
225
|
+
return this.count * this.count;
|
|
226
|
+
}, [this.count]);
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
The library provides a unified API to work with different reactive systems, allowing you to switch between Solid.js, Preact Signals, React, or any other UI framework easily.
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
### License
|
|
235
|
+
|
|
236
|
+
This library is provided "as-is" under the MIT license. Feel free to use, modify, and distribute it in your projects.
|
|
237
|
+
|
|
238
|
+
### Thanks and Inspiration
|
|
239
|
+
|
|
240
|
+
- **[dy](https://github.com/dy)**
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ustor",
|
|
3
|
+
"description": "A lightweight reactive store library that provides a powerful reactivity system for managing state, compatible with any UI framework, including React, Solid.js, and Preact Signals.",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"source": "./src/index.js",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"unpkg": "./dist/index.min.js",
|
|
10
|
+
"umd:main": "./dist/index.umd.js",
|
|
11
|
+
"exports": {
|
|
12
|
+
"browser": "./dist/index.es.js",
|
|
13
|
+
"umd": "./dist/index.umd.js",
|
|
14
|
+
"require": "./dist/index.js",
|
|
15
|
+
"import": "./dist/index.es.js",
|
|
16
|
+
"default": "./dist/index.es.js"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/kethan/ustor.git"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "rollup --config rollup.config.js",
|
|
24
|
+
"test": "vitest"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@preact/signals-core": "^1.8.0",
|
|
28
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
29
|
+
"rollup": "^4.24.0",
|
|
30
|
+
"rollup-plugin-bundle-size": "^1.0.3",
|
|
31
|
+
"solid-js": "^1.9.2",
|
|
32
|
+
"ulive": "^1.0.7",
|
|
33
|
+
"vitest": "^2.1.3"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"ustor",
|
|
37
|
+
"ustore",
|
|
38
|
+
"store",
|
|
39
|
+
"signal",
|
|
40
|
+
"createmutable",
|
|
41
|
+
"mutable",
|
|
42
|
+
"createstore",
|
|
43
|
+
"react",
|
|
44
|
+
"preact",
|
|
45
|
+
"solid.js",
|
|
46
|
+
"state",
|
|
47
|
+
"observable",
|
|
48
|
+
"reactive"
|
|
49
|
+
],
|
|
50
|
+
"author": "Kethan Surana",
|
|
51
|
+
"license": "ISC",
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/kethan/ustor/issues"
|
|
54
|
+
},
|
|
55
|
+
"homepage": "https://github.com/kethan/ustor#readme"
|
|
56
|
+
}
|
package/rollup.config.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import terser from '@rollup/plugin-terser';
|
|
2
|
+
import bundleSize from 'rollup-plugin-bundle-size';
|
|
3
|
+
|
|
4
|
+
const resolve = (pkg, input = "src/index", output = "dist/index") => ({
|
|
5
|
+
input: `${input}.js`,
|
|
6
|
+
plugins: [
|
|
7
|
+
bundleSize()
|
|
8
|
+
],
|
|
9
|
+
output: [
|
|
10
|
+
{
|
|
11
|
+
file: `${output}.es.js`,
|
|
12
|
+
format: 'es',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
file: `${output}.js`,
|
|
16
|
+
format: 'cjs',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
file: `${output}.min.js`,
|
|
20
|
+
format: 'iife',
|
|
21
|
+
name: pkg,
|
|
22
|
+
strict: false,
|
|
23
|
+
compact: true,
|
|
24
|
+
plugins: [terser()]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
file: `${output}.umd.js`,
|
|
28
|
+
format: 'umd',
|
|
29
|
+
name: pkg,
|
|
30
|
+
strict: false,
|
|
31
|
+
compact: true,
|
|
32
|
+
plugins: [terser()]
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export default [
|
|
38
|
+
resolve("template")
|
|
39
|
+
]
|
package/src/index.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
let api = {},
|
|
2
|
+
_s = Symbol("s"),
|
|
3
|
+
is = (v) => v[_s],
|
|
4
|
+
isObject = (v) => v && v.constructor === Object,
|
|
5
|
+
resolve = (v) => Array.isArray(v)
|
|
6
|
+
? store(v)
|
|
7
|
+
: isObject(v)
|
|
8
|
+
? Object.seal(store(v))
|
|
9
|
+
: v,
|
|
10
|
+
store = (values, proto) => {
|
|
11
|
+
if ((is(values) && !proto) || !(api.signal && api.get && api.set && api.is)) return values;
|
|
12
|
+
if (Array.isArray(values)) values.forEach((item, i) => values[i] = is(item) ? item : store(item));
|
|
13
|
+
if (isObject(values)) {
|
|
14
|
+
let state = Object.create(proto || Object.getPrototypeOf(values));
|
|
15
|
+
Object.entries(Object.getOwnPropertyDescriptors(values)).forEach(
|
|
16
|
+
([key, desc]) => {
|
|
17
|
+
let signal = api.is(desc.value) ? desc.value : api.signal(resolve(desc.value)),
|
|
18
|
+
memo = desc.get ?
|
|
19
|
+
api.memo ?
|
|
20
|
+
api.memo(desc.get.bind(state)) :
|
|
21
|
+
desc.get.bind(state)?.() :
|
|
22
|
+
signal;
|
|
23
|
+
Object.defineProperty(state, key, {
|
|
24
|
+
get: () => desc.get ? memo : api.get(memo),
|
|
25
|
+
set: desc.get ? desc.set?.bind(state) : v => api.set(signal, resolve(v)),
|
|
26
|
+
enumerable: true,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
Object.defineProperty(state, _s, { value: true });
|
|
31
|
+
return state;
|
|
32
|
+
}
|
|
33
|
+
return values;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
is,
|
|
38
|
+
store,
|
|
39
|
+
api
|
|
40
|
+
}
|
package/test.spec.js
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import { store, api } from './src';
|
|
2
|
+
// import { signal, computed, effect, batch } from '@preact/signals-core';
|
|
3
|
+
import { createSignal, createEffect, createMemo, batch } from './node_modules/solid-js/dist/solid'
|
|
4
|
+
import { assert, describe, it } from 'vitest';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
//Solid.js
|
|
8
|
+
api.memo = createMemo;
|
|
9
|
+
api.signal = createSignal;
|
|
10
|
+
api.effect = createEffect;
|
|
11
|
+
api.batch = batch;
|
|
12
|
+
api.getMemo = v => v?.();
|
|
13
|
+
api.is = v => v?.[0]?.name?.includes("readSignal");
|
|
14
|
+
api.get = v => v?.[0]();
|
|
15
|
+
api.set = (signal, v) => signal?.[1](v);
|
|
16
|
+
|
|
17
|
+
// @preact/signals-core, usignal, ulive
|
|
18
|
+
// api.memo = computed;
|
|
19
|
+
// api.signal = signal;
|
|
20
|
+
// api.effect = effect;
|
|
21
|
+
// api.batch = batch;
|
|
22
|
+
// api.getMemo = v => v?.value;
|
|
23
|
+
// api.is = (v) => v?.peek;
|
|
24
|
+
// api.get = (v) => v?.value;
|
|
25
|
+
// api.set = (signal, v) => (signal.value = v);
|
|
26
|
+
|
|
27
|
+
describe('Store functionality', () => {
|
|
28
|
+
let i = api.signal(3);
|
|
29
|
+
let s;
|
|
30
|
+
let len;
|
|
31
|
+
let xy;
|
|
32
|
+
let s1;
|
|
33
|
+
it('should initialize store with signals and functions', () => {
|
|
34
|
+
let i = api.signal(3);
|
|
35
|
+
s = store({
|
|
36
|
+
x: 0,
|
|
37
|
+
y: api.signal(1),
|
|
38
|
+
z: { r: 2, i },
|
|
39
|
+
v: function () {
|
|
40
|
+
return 1;
|
|
41
|
+
},
|
|
42
|
+
w: [1, 2],
|
|
43
|
+
get xy() {
|
|
44
|
+
return this.x + this.y;
|
|
45
|
+
},
|
|
46
|
+
set xy([x, y]) {
|
|
47
|
+
this.x = x;
|
|
48
|
+
this.y = y;
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should treat functions as signals', () => {
|
|
54
|
+
assert.equal(s.v(), 1);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should subscribe to signals without explicit .value access', () => {
|
|
58
|
+
const zilog = [];
|
|
59
|
+
api.effect(() => zilog.push(s.z.i));
|
|
60
|
+
xy = api.memo(() => s.x + s.y);
|
|
61
|
+
assert.equal(api.getMemo(xy), 1);
|
|
62
|
+
s.x = 2;
|
|
63
|
+
s.y = 3;
|
|
64
|
+
assert.equal(api.getMemo(xy), 5);
|
|
65
|
+
s.y = 4;
|
|
66
|
+
assert.equal(api.getMemo(xy), 6);
|
|
67
|
+
assert.deepEqual(zilog, [3]);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should handle getter computed properties correctly', () => {
|
|
71
|
+
assert.equal(api.getMemo(s.xy), 6);
|
|
72
|
+
s.xy = [4, 2];
|
|
73
|
+
assert.equal(s.x, 4);
|
|
74
|
+
assert.equal(s.y, 2);
|
|
75
|
+
assert.equal(api.getMemo(s.xy), 6);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should subscribe to deep values and update results', () => {
|
|
79
|
+
len = api.memo(() => (s.z.r ** 2 + s.z.i ** 2) ** 0.5);
|
|
80
|
+
s.z.r = 3;
|
|
81
|
+
s.z.i = 4;
|
|
82
|
+
assert.equal(api.getMemo(len), 5);
|
|
83
|
+
s.z.r = 4;
|
|
84
|
+
s.z.i = 3;
|
|
85
|
+
assert.equal(api.getMemo(len), 5);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should update internal objects/arrays and turn them into signals', () => {
|
|
89
|
+
s.z = { r: 5, i: 12 };
|
|
90
|
+
assert.equal(api.getMemo(len), 13);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
it('should handle updating arrays', () => {
|
|
95
|
+
let mult = api.memo(() => s.w[0] * s.w[1]);
|
|
96
|
+
assert.equal(api.getMemo(mult), 2);
|
|
97
|
+
s.w = [3, 4];
|
|
98
|
+
assert.equal(api.getMemo(mult), 12);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should handle bulk updates with batch', () => {
|
|
102
|
+
api.batch(() => Object.assign(s, { x: 1, y: 1, z: { r: 3, i: 4 } }));
|
|
103
|
+
assert.equal(api.getMemo(xy), 2);
|
|
104
|
+
assert.equal(api.getMemo(len), 5, 'len after update');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should retain the same type as the initial data for signals', () => {
|
|
108
|
+
assert.equal(s.constructor, Object);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should return the same instance when re-initializing store', () => {
|
|
112
|
+
s1 = store(s);
|
|
113
|
+
assert.equal(s, s1);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should ensure that store is not enumerable', () => {
|
|
117
|
+
let s2 = store([]);
|
|
118
|
+
let log = [];
|
|
119
|
+
for (let i of s2) log.push(i);
|
|
120
|
+
assert.deepEqual(log, [], "doesn't iterate");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should detect descendants as instances', () => {
|
|
124
|
+
let s3 = Object.create(s1),
|
|
125
|
+
s3s = store(s3);
|
|
126
|
+
assert.equal(s3, s3s);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should convert array items to signal structs', () => {
|
|
130
|
+
let s5 = store({ list: [{ x: 1 }, { x: 2 }] });
|
|
131
|
+
let sum = api.memo(() => s5.list.reduce((sum, item) => item.x + sum, 0));
|
|
132
|
+
assert.equal(api.getMemo(sum), 3);
|
|
133
|
+
s5.list[0].x = 2;
|
|
134
|
+
assert.equal(api.getMemo(sum), 4);
|
|
135
|
+
s5.list = [{ x: 3 }, { x: 3 }];
|
|
136
|
+
assert.equal(api.getMemo(sum), 6);
|
|
137
|
+
s5.list = [{ x: 3 }, { x: 3 }, { x: 4 }];
|
|
138
|
+
assert.equal(api.getMemo(sum), 10);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should retain reference for arrays', () => {
|
|
142
|
+
let list = [1, 2, 3];
|
|
143
|
+
let s6 = store({ list });
|
|
144
|
+
s6.list[1] = 4;
|
|
145
|
+
assert.deepEqual(list, [1, 4, 3])
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('Store Functionality - Basic to Complex', () => {
|
|
150
|
+
// Simple Test Cases
|
|
151
|
+
it('should initialize a store with simple values', () => {
|
|
152
|
+
const s = store({ a: 1, b: 2 });
|
|
153
|
+
assert.equal(s.a, 1);
|
|
154
|
+
assert.equal(s.b, 2);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('should update store values', () => {
|
|
158
|
+
const s = store({ a: 1 });
|
|
159
|
+
s.a = 5;
|
|
160
|
+
assert.equal(s.a, 5);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should initialize store with signals', () => {
|
|
164
|
+
const s = store({ a: api.signal(3) });
|
|
165
|
+
assert.equal(s.a, 3);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should update signal value within store', () => {
|
|
169
|
+
const s = store({ a: api.signal(3) });
|
|
170
|
+
s.a = 10;
|
|
171
|
+
assert.equal(s.a, 10);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should compute a derived value', () => {
|
|
175
|
+
const s = store({ a: api.signal(2), b: api.signal(3) });
|
|
176
|
+
const sum = api.memo(() => s.a + s.b);
|
|
177
|
+
assert.equal(api.getMemo(sum), 5);
|
|
178
|
+
s.a = 5;
|
|
179
|
+
assert.equal(api.getMemo(sum), 8);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Intermediate Test Cases
|
|
183
|
+
it('should handle nested store updates', () => {
|
|
184
|
+
const s = store({ nested: { a: api.signal(1) } });
|
|
185
|
+
assert.equal(s.nested.a, 1);
|
|
186
|
+
s.nested.a = 4;
|
|
187
|
+
assert.equal(s.nested.a, 4);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should react to changes in nested signals', () => {
|
|
191
|
+
const s = store({ nested: { a: api.signal(1) } });
|
|
192
|
+
const double = api.memo(() => s.nested.a * 2);
|
|
193
|
+
assert.equal(api.getMemo(double), 2);
|
|
194
|
+
s.nested.a = 3;
|
|
195
|
+
assert.equal(api.getMemo(double), 6);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should allow effect to react to signal changes', () => {
|
|
199
|
+
const s = store({ a: api.signal(1) });
|
|
200
|
+
let effectValue = 0;
|
|
201
|
+
api.effect(() => {
|
|
202
|
+
effectValue = s.a * 2;
|
|
203
|
+
});
|
|
204
|
+
assert.equal(effectValue, 2);
|
|
205
|
+
s.a = 4;
|
|
206
|
+
assert.equal(effectValue, 8);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should batch updates to prevent redundant computations', () => {
|
|
210
|
+
const s = store({ a: api.signal(1), b: api.signal(2) });
|
|
211
|
+
let computeCount = 0;
|
|
212
|
+
const sum = api.memo(() => {
|
|
213
|
+
computeCount++;
|
|
214
|
+
return s.a + s.b;
|
|
215
|
+
});
|
|
216
|
+
assert.equal(api.getMemo(sum), 3);
|
|
217
|
+
assert.equal(computeCount, 1);
|
|
218
|
+
api.batch(() => {
|
|
219
|
+
s.a = 3;
|
|
220
|
+
s.b = 4;
|
|
221
|
+
});
|
|
222
|
+
assert.equal(api.getMemo(sum), 7);
|
|
223
|
+
assert.equal(computeCount, 2);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Complex Test Cases
|
|
227
|
+
it('should handle arrays within the store', () => {
|
|
228
|
+
const s = store({ list: [1, 2, 3] });
|
|
229
|
+
const sum = api.memo(() => s.list.reduce((acc, item) => acc + item, 0));
|
|
230
|
+
assert.equal(api.getMemo(sum), 6);
|
|
231
|
+
s.list = [1, 5, 3]
|
|
232
|
+
assert.equal(api.getMemo(sum), 9);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('should handle nested computed properties', () => {
|
|
236
|
+
const s = store({ a: api.signal(2), b: api.signal(3) });
|
|
237
|
+
const product = api.memo(() => s.a * s.b);
|
|
238
|
+
const doubleProduct = api.memo(() => api.getMemo(product) * 2);
|
|
239
|
+
assert.equal(api.getMemo(doubleProduct), 12);
|
|
240
|
+
s.a = 4;
|
|
241
|
+
assert.equal(api.getMemo(doubleProduct), 24);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('should allow dynamic property addition and maintain reactivity', () => {
|
|
245
|
+
const s = store({ a: api.signal(1) });
|
|
246
|
+
s.b = api.signal(2);
|
|
247
|
+
const sum = api.memo(() => s.a + api.get(s.b));
|
|
248
|
+
assert.equal(api.getMemo(sum), 3);
|
|
249
|
+
api.set(s.b, 5)
|
|
250
|
+
assert.equal(api.getMemo(sum), 6);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('should react to changes in deeply nested structures', () => {
|
|
254
|
+
const s = store({ nested: { deep: { value: api.signal(10) } } });
|
|
255
|
+
const deepValue = api.memo(() => s.nested.deep.value * 2);
|
|
256
|
+
assert.equal(api.getMemo(deepValue), 20);
|
|
257
|
+
s.nested.deep.value = 15;
|
|
258
|
+
assert.equal(api.getMemo(deepValue), 30);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Combination of Arrays and Objects
|
|
262
|
+
it('should handle arrays of objects within the store', () => {
|
|
263
|
+
const s = store({ list: [{ x: api.signal(1) }, { x: api.signal(2) }, { x: api.signal(3) }] });
|
|
264
|
+
const sum = api.memo(() => s.list.reduce((acc, item) => acc + item.x, 0));
|
|
265
|
+
assert.equal(api.getMemo(sum), 6);
|
|
266
|
+
s.list[1].x = 5;
|
|
267
|
+
assert.equal(api.getMemo(sum), 9);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('should handle objects with array properties and nested signals', () => {
|
|
271
|
+
const s = store({ a: api.signal(1), b: [2, 3] });
|
|
272
|
+
const sum = api.memo(() => s.a + s.b.reduce((acc, item) => acc + item, 0));
|
|
273
|
+
assert.equal(api.getMemo(sum), 6);
|
|
274
|
+
s.a = 2;
|
|
275
|
+
s.b = [2, 4]
|
|
276
|
+
assert.equal(api.getMemo(sum), 8);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('should handle deeply nested arrays and objects', () => {
|
|
280
|
+
const s = store({ nested: [{ x: { y: api.signal(2) } }, { x: { y: api.signal(3) } }] });
|
|
281
|
+
const total = api.memo(() => s.nested.reduce((acc, item) => acc + item.x.y, 0));
|
|
282
|
+
assert.equal(api.getMemo(total), 5);
|
|
283
|
+
s.nested[0].x.y = 4;
|
|
284
|
+
assert.equal(api.getMemo(total), 7);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('should update array elements that are objects with signals', () => {
|
|
288
|
+
const s = store({ list: [{ a: api.signal(1) }, { a: api.signal(2) }] });
|
|
289
|
+
const sum = api.memo(() => s.list.reduce((acc, item) => acc + item.a, 0));
|
|
290
|
+
assert.equal(api.getMemo(sum), 3);
|
|
291
|
+
s.list = [...s.list, { a: api.signal(4) }]
|
|
292
|
+
assert.equal(api.getMemo(sum), 7);
|
|
293
|
+
s.list[0].a = 5;
|
|
294
|
+
assert.equal(api.getMemo(sum), 11);
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
describe('Extreme Testing - Simple Reactivity', () => {
|
|
300
|
+
// Stress test rapid updates
|
|
301
|
+
it('should handle rapid signal updates', () => {
|
|
302
|
+
const s = store({ a: api.signal(0) });
|
|
303
|
+
let updates = 0;
|
|
304
|
+
api.effect(() => {
|
|
305
|
+
updates = s.a;
|
|
306
|
+
});
|
|
307
|
+
for (let i = 0; i < 10000; i++) {
|
|
308
|
+
s.a = i;
|
|
309
|
+
}
|
|
310
|
+
assert.equal(s.a, 9999);
|
|
311
|
+
assert.equal(updates, 9999);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// Stress test deep nesting with correct access
|
|
315
|
+
it('should handle deeply nested signals within the store', () => {
|
|
316
|
+
let deep = { value: 1 };
|
|
317
|
+
for (let i = 0; i < 50; i++) {
|
|
318
|
+
deep = { nested: deep };
|
|
319
|
+
}
|
|
320
|
+
const s = store({ deep });
|
|
321
|
+
// // Access the 50th level of nesting for example
|
|
322
|
+
let nestedValue = s.deep;
|
|
323
|
+
for (let i = 0; i < 50; i++) {
|
|
324
|
+
nestedValue = nestedValue.nested;
|
|
325
|
+
}
|
|
326
|
+
assert.equal(nestedValue.value, 1);
|
|
327
|
+
nestedValue.value = 50; // Update deeply nested signal
|
|
328
|
+
|
|
329
|
+
assert.equal(s.deep.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.nested.value, 50); // Confirm update at the correct depth
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// Test massive batch updates
|
|
333
|
+
it('should handle massive batch updates without redundant computations', () => {
|
|
334
|
+
const s = store({ a: api.signal(1), b: api.signal(1) });
|
|
335
|
+
let computeCount = 0;
|
|
336
|
+
const sum = api.memo(() => {
|
|
337
|
+
computeCount++;
|
|
338
|
+
return s.a + s.b;
|
|
339
|
+
});
|
|
340
|
+
assert.equal(api.getMemo(sum), 2);
|
|
341
|
+
api.batch(() => {
|
|
342
|
+
for (let i = 0; i < 10000; i++) {
|
|
343
|
+
s.a = i;
|
|
344
|
+
s.b = i + 1;
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
assert.equal(api.getMemo(sum), 19999);
|
|
348
|
+
assert.equal(computeCount, 2); // Only computed twice (initial and after batch)
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// Test bulk effect cleanup
|
|
352
|
+
it('should clean up effects properly during bulk updates', () => {
|
|
353
|
+
const s = store({ a: api.signal(0) });
|
|
354
|
+
let activeEffects = 0;
|
|
355
|
+
const cleanupEffect = api.effect(() => {
|
|
356
|
+
s.a = activeEffects++;
|
|
357
|
+
// if(api.cleanup) api.cleanup(() => s.a = activeEffects--)
|
|
358
|
+
// else
|
|
359
|
+
// return () => s.a = activeEffects--;
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
for (let i = 0; i < 1000; i++) {
|
|
363
|
+
s.a = i;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
assert.equal(s.a, 999);
|
|
367
|
+
// cleanupEffect();
|
|
368
|
+
// assert.equal(s.a, 1);
|
|
369
|
+
});
|
|
370
|
+
});
|