ustor 0.2.0 → 0.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/README.md CHANGED
@@ -18,6 +18,7 @@ A tiny, dependency-free reactive store for JavaScript objects and arrays. UStor
18
18
  - [Installation](#installation)
19
19
  - [Quickstart](#quickstart)
20
20
  - [Common use cases](#common-use-cases)
21
+ - [Examples](#examples)
21
22
  - [API reference](#api-reference)
22
23
  - [Arrays](#arrays)
23
24
  - [Supported adapters](#supported-adapters)
@@ -53,10 +54,10 @@ import { signal, computed, effect } from "@preact/signals-core";
53
54
 
54
55
  Object.assign(api, {
55
56
  signal,
56
- memo: computed,
57
+ computed,
57
58
  effect,
58
59
  get: (value) => value.value,
59
- getMemo: (value) => value.value,
60
+ getComputed: (value) => value.value,
60
61
  set: (target, value) => { target.value = value; },
61
62
  is: (value) => typeof value?.peek === "function",
62
63
  });
@@ -147,6 +148,10 @@ ui.theme = "dark";
147
148
 
148
149
  Create request- or component-specific stores when the state must not be shared. See [SSR and Next.js](#ssr-and-nextjs).
149
150
 
151
+ ## Examples
152
+
153
+ The [source example collection](examples/README.md) contains runnable Node programs for counters, carts, validation, async race handling, undo history, Vue, and RxJS; React and Next.js components; a browser import-map demo; and TypeScript examples. Run `npm run examples` to build, execute the isolated Node examples, and type-check the component/TypeScript sources.
154
+
150
155
  ## API reference
151
156
 
152
157
  ### `store(values, proto?)`
@@ -190,7 +195,7 @@ is(null); // false
190
195
 
191
196
  ### `state.$property`
192
197
 
193
- Exposes the underlying signal for a data property, or the underlying memo for a memoized getter.
198
+ Exposes the underlying signal for a data property, or the underlying computed value for a computed getter.
194
199
 
195
200
  ```javascript
196
201
  const state = store({ count: 0 });
@@ -199,7 +204,7 @@ state.$count.value = 2; // Preact signal example
199
204
  console.log(state.count); // 2
200
205
  ```
201
206
 
202
- Aliases are non-enumerable. Getters without `api.memo` do not have a `$` alias. If an input explicitly defines a conflicting key such as `$count`, the user-defined property takes precedence.
207
+ Aliases are non-enumerable. Getters without `api.computed` do not have a `$` alias. If an input explicitly defines a conflicting key such as `$count`, the user-defined property takes precedence.
203
208
 
204
209
  ### Adapter API
205
210
 
@@ -216,11 +221,11 @@ Optional operations:
216
221
 
217
222
  | Operation | Purpose |
218
223
  | --- | --- |
219
- | `api.memo(fn)` | Create a lazy computed value. |
220
- | `api.getMemo(memo)` | Read a memo; defaults to `api.get`. |
224
+ | `api.computed(fn)` | Create a lazy computed value. |
225
+ | `api.getComputed(computed)` | Read a computed value; defaults to `api.get`. |
221
226
  | `api.effect(fn)` | Required by `ustor/react`; must auto-track synchronously and return a cleanup function. |
222
227
 
223
- Without `api.memo`, getters execute on each access and still participate in compatible effects. With `api.memo`, getters are lazy and cached while their dependencies remain unchanged.
228
+ Without `api.computed`, getters execute on each access and still participate in compatible effects. With `api.computed`, getters are lazy and cached while their dependencies remain unchanged.
224
229
 
225
230
  ## Arrays
226
231
 
@@ -253,35 +258,35 @@ The configurations in `adapters.spec.js` are executable documentation and includ
253
258
 
254
259
  | Package | Getter support |
255
260
  | --- | --- |
256
- | `@preact/signals-core` | Automatic memoization |
257
- | `solid-js` | Automatic memoization |
258
- | `oby` | Automatic memoization |
259
- | `sinuous/observable` | Automatic memoization |
260
- | `ulive` | Automatic memoization |
261
- | `usignal` | Automatic memoization |
262
- | `@vue/reactivity` | Automatic memoization using `shallowRef` |
263
- | `alien-signals` | Automatic memoization |
264
- | `s-js` | Automatic memoization using the provided branded factory |
265
- | `mobx` | Automatic memoization using shallow observable boxes |
266
- | `@tanstack/store` | Automatic memoization using the provided value wrapper |
267
- | `@webreflection/signal` | Automatic memoization |
268
- | `@webreflection/signals` | Automatic memoization |
269
- | `@webreflection/alien-signals` | Automatic memoization |
261
+ | `@preact/signals-core` | Automatic computed values |
262
+ | `solid-js` | Automatic computed values |
263
+ | `oby` | Automatic computed values |
264
+ | `sinuous/observable` | Automatic computed values |
265
+ | `ulive` | Automatic computed values |
266
+ | `usignal` | Automatic computed values |
267
+ | `@vue/reactivity` | Automatic computed values using `shallowRef` |
268
+ | `alien-signals` | Automatic computed values |
269
+ | `s-js` | Automatic computed values using the provided branded factory |
270
+ | `mobx` | Automatic computed values using shallow observable boxes |
271
+ | `@tanstack/store` | Automatic computed values using the provided value wrapper |
272
+ | `@webreflection/signal` | Automatic computed values |
273
+ | `@webreflection/signals` | Automatic computed values |
274
+ | `@webreflection/alien-signals` | Automatic computed values |
270
275
  | `rxjs` | Basic: `BehaviorSubject` values and direct getters |
271
276
  | `nanostores` | Basic: atoms and direct getters |
272
277
 
273
- **Basic adapters** support synchronous values, nested objects, arrays, and getters evaluated on access. RxJS and Nanostores do not automatically discover arbitrary getter dependencies, so automatic memoization is not advertised for them.
278
+ **Basic adapters** support synchronous values, nested objects, arrays, and getters evaluated on access. RxJS and Nanostores do not automatically discover arbitrary getter dependencies, so automatic computed values are not advertised for them.
274
279
 
275
280
  Adapter-specific notes:
276
281
 
277
282
  - Vue and MobX use shallow containers to avoid wrapping UStor's proxies twice.
278
283
  - TanStack Store treats bare functions as computed values; its tested adapter wraps writable values.
279
- - S.js has no built-in signal brand; use the tested factory and create memoized stores inside an S.js root.
280
- - Solid.js, Vue, S.js, and similar libraries may require their own owner/scope lifecycle for short-lived memos.
284
+ - S.js has no built-in signal brand; use the tested factory and create stores with computed values inside an S.js root.
285
+ - Solid.js, Vue, S.js, and similar libraries may require their own owner/scope lifecycle for short-lived computed values.
281
286
 
282
287
  ## Porting any signal library
283
288
 
284
- You normally **do not need to modify UStor's core**. Port a library by translating its synchronous read/write API into four required functions, then add optional memo/effect functions when supported.
289
+ You normally **do not need to modify UStor's core**. Port a library by translating its synchronous read/write API into four required functions, then add optional computed/effect functions when supported.
285
290
 
286
291
  ### 1. Map the four required operations
287
292
 
@@ -370,20 +375,20 @@ api.is = (value) => signals.has(value);
370
375
 
371
376
  Create external signals through `api.signal` when using this approach. The S.js configuration in `adapters.spec.js` demonstrates it.
372
377
 
373
- ### 4. Add memoization only when dependencies are automatic
378
+ ### 4. Add computed values only when dependencies are automatic
374
379
 
375
380
  If the library can compute a value by automatically tracking signals read inside `fn`, add:
376
381
 
377
382
  ```javascript
378
383
  Object.assign(api, {
379
- memo: (fn) => library.computed(fn),
380
- getMemo: (memo) => library.read(memo),
384
+ computed: (fn) => library.computed(fn),
385
+ getComputed: (computed) => library.read(computed),
381
386
  });
382
387
  ```
383
388
 
384
- The memo may be lazy or eager, but it must invalidate when any dependency changes, including conditional dependencies.
389
+ The computed value may be lazy or eager, but it must invalidate when any dependency changes, including conditional dependencies.
385
390
 
386
- If the library requires an explicit dependency list—as with RxJS or Nanostores—**omit `memo` and `getMemo`**. UStor then uses direct getters evaluated on access. Do not emulate automatic memoization incorrectly.
391
+ If the library requires an explicit dependency list—as with RxJS or Nanostores—**omit `computed` and `getComputed`**. UStor then uses direct getters evaluated on access. Do not emulate automatic dependency tracking incorrectly.
387
392
 
388
393
  ### 5. Add effects for React integration only when compatible
389
394
 
@@ -406,11 +411,11 @@ The core store does not require `api.effect`. Basic adapters without automatic d
406
411
 
407
412
  ### 6. Configure once and manage lifecycle
408
413
 
409
- Configure `api` before creating stores and keep the same implementation for their lifetime. If your library requires an owner, scope, root, or disposer for memos/effects, create short-lived stores in that lifecycle and dispose them with their owner.
414
+ Configure `api` before creating stores and keep the same implementation for their lifetime. If your library requires an owner, scope, root, or disposer for computed values/effects, create short-lived stores in that lifecycle and dispose them with their owner.
410
415
 
411
416
  ### 7. Validate the adapter
412
417
 
413
- This Node script checks the required behavior, function preservation, nested updates, arrays, and optional memoization:
418
+ This Node script checks the required behavior, function preservation, nested updates, arrays, and optional computed values:
414
419
 
415
420
  ```javascript
416
421
  import assert from "node:assert/strict";
@@ -444,15 +449,15 @@ assert.equal(state.total, 11);
444
449
  state.list[0] = 5;
445
450
  assert.equal(state.total, 15);
446
451
 
447
- if (api.memo) {
448
- assert.equal((api.getMemo || api.get)(state.$total), 15);
452
+ if (api.computed) {
453
+ assert.equal((api.getComputed || api.get)(state.$total), 15);
449
454
  }
450
455
  ```
451
456
 
452
457
  A production-quality adapter should also verify:
453
458
 
454
459
  - Whole-array replacement, deletion, and length changes.
455
- - Conditional memo dependencies and no-op writes.
460
+ - Conditional computed dependencies and no-op writes.
456
461
  - Effect cleanup and behavior under nested/batched updates.
457
462
  - React StrictMode and unmount behavior when used with `ustor/react`.
458
463
  - Owner/scope disposal and long-running memory behavior.
@@ -468,22 +473,23 @@ npm run profile
468
473
 
469
474
  ## React
470
475
 
471
- Install React and import the optional hooks:
476
+ Install React and import the optional hook:
472
477
 
473
478
  ```bash
474
479
  npm install react
475
480
  ```
476
481
 
477
482
  ```jsx
478
- import { useSignal, useStore } from "ustor/react";
483
+ import { useSelector } from "ustor/react";
479
484
 
480
485
  // Uses the Preact configuration from Quickstart, including api.effect.
481
486
  const state = store({ count: 1, items: [1, 2] });
487
+ const selectCount = () => state.count;
482
488
  const selectTotal = () => state.items.reduce((sum, value) => sum + value, 0);
483
489
 
484
490
  export function Counter() {
485
- const count = useSignal(state.$count);
486
- const total = useStore(selectTotal);
491
+ const count = useSelector(selectCount);
492
+ const total = useSelector(selectTotal);
487
493
 
488
494
  return (
489
495
  <button onClick={() => { state.count++; state.items.push(state.count); }}>
@@ -493,18 +499,18 @@ export function Counter() {
493
499
  }
494
500
  ```
495
501
 
496
- - `useSignal(signal)` subscribes to one `$property` signal.
497
- - `useStore(selector)` tracks all compatible signal reads in the selector, including array versions.
502
+ - `useSelector(selector)` is the single React hook for both simple properties and derived values.
503
+ - It tracks all compatible signal reads in the selector, including array versions.
498
504
  - Selectors must return a primitive or stable reference, not a fresh object on every read.
499
505
  - Keep selector functions stable with `useCallback` when they are created inside a component.
500
506
  - React is an optional peer dependency and is excluded from the core bundle.
501
507
  - StrictMode and unmount cleanup are tested.
502
508
 
503
- Plain property reads do not schedule React renders by themselves; use the hooks or another React binding.
509
+ Plain property reads do not schedule React renders by themselves; use the hook or another React binding.
504
510
 
505
511
  ## SSR and Next.js
506
512
 
507
- Yes. `useStore` and `useSignal` provide a server snapshot through `useSyncExternalStore`. Server rendering creates no effects; subscriptions start on the client.
513
+ Yes. `useSelector` provides a server snapshot through `useSyncExternalStore`. Server rendering creates no effects; subscriptions start on the client.
508
514
 
509
515
  The test suite verifies:
510
516
 
@@ -515,14 +521,14 @@ The test suite verifies:
515
521
 
516
522
  ### Next.js App Router example
517
523
 
518
- Use the hooks in a **Client Component**. Create request- or component-owned state instead of exporting a request-specific singleton.
524
+ Use the hook in a **Client Component**. Create request- or component-owned state instead of exporting a request-specific singleton.
519
525
 
520
526
  ```jsx
521
527
  "use client";
522
528
 
523
529
  import { useCallback, useState } from "react";
524
530
  import { store } from "ustor";
525
- import { useSignal, useStore } from "ustor/react";
531
+ import { useSelector } from "ustor/react";
526
532
  // Import your shared adapter configuration once:
527
533
  // import "@/lib/ustor-setup";
528
534
 
@@ -531,12 +537,13 @@ export default function Counter({ initialCount }) {
531
537
  count: initialCount,
532
538
  items: [1, 2],
533
539
  }));
540
+ const selectCount = useCallback(() => state.count, [state]);
534
541
  const selectTotal = useCallback(
535
542
  () => state.items.reduce((sum, value) => sum + value, 0),
536
543
  [state],
537
544
  );
538
- const count = useSignal(state.$count);
539
- const total = useStore(selectTotal);
545
+ const count = useSelector(selectCount);
546
+ const total = useSelector(selectTotal);
540
547
 
541
548
  return (
542
549
  <button onClick={() => { state.count++; state.items.push(1); }}>
@@ -572,7 +579,7 @@ Declarations are included for both entry points; no separate `@types/ustor` pack
572
579
 
573
580
  ```typescript
574
581
  import { store } from "ustor";
575
- import { useStore } from "ustor/react";
582
+ import { useSelector } from "ustor/react";
576
583
 
577
584
  const state = store({
578
585
  count: 1,
@@ -586,14 +593,13 @@ state.count = 2; // valid
586
593
 
587
594
  const doubled: number = state.double;
588
595
  const text: string = state.nested.text;
589
- const total = useStore(() => state.list.reduce((sum, value) => sum + value, 0));
596
+ const total = useSelector(() => state.list.reduce((sum, value) => sum + value, 0));
590
597
  ```
591
598
 
592
599
  Exported type utilities:
593
600
 
594
- - `SignalApi<Signal, Memo>` for custom adapters.
601
+ - `SignalApi<Signal, Computed>` for custom adapters.
595
602
  - `Store<T>` for reactive store inference.
596
- - `SignalValue<Signal>` for React signal-value inference.
597
603
 
598
604
  Run the repository's strict compile-time tests with:
599
605
 
@@ -605,7 +611,7 @@ npm run typecheck
605
611
 
606
612
  The profiler uses Preact Signals and measures:
607
613
 
608
- - High-frequency memoized property updates.
614
+ - High-frequency computed property updates.
609
615
  - Batched property updates.
610
616
  - Bounded array mutation workloads.
611
617
  - Repeated store/effect creation, updates, and disposal with heap/RSS samples.
@@ -672,6 +678,7 @@ npm test # build, type tests, unit/adapter/React/SSR tests, pack
672
678
  npm run typecheck # strict TypeScript declaration tests
673
679
  npm run test:coverage # enforced 100% runtime coverage
674
680
  npm run test:watch
681
+ npm run examples # isolated Node examples and JSX/TypeScript checks
675
682
  npm run profile:smoke
676
683
  npm run build
677
684
  ```
package/dist/index.cjs CHANGED
@@ -76,14 +76,14 @@ const objectStore = (values, proto) => {
76
76
  const desc = Object.getOwnPropertyDescriptor(values, key),
77
77
  accessor = desc.get || desc.set,
78
78
  getter = desc.get?.bind(state),
79
- memoize = getter && api.memo,
79
+ createComputed = getter && api.computed,
80
80
  signal = accessor ? undefined : api.is(desc.value) ? desc.value : api.signal(resolve(desc.value));
81
- let memo;
82
- const getSignal = () => getter ? (memo ||= memoize(getter)) : signal;
81
+ let computed;
82
+ const getSignal = () => getter ? (computed ||= createComputed(getter)) : signal;
83
83
  Object.defineProperty(state, key, {
84
84
  get: accessor
85
85
  ? getter
86
- ? () => memoize ? (api.getMemo || api.get)(getSignal()) : getter()
86
+ ? () => createComputed ? (api.getComputed || api.get)(getSignal()) : getter()
87
87
  : undefined
88
88
  : () => api.get(signal),
89
89
  set: accessor
@@ -94,7 +94,7 @@ const objectStore = (values, proto) => {
94
94
  enumerable: desc.enumerable,
95
95
  });
96
96
  if (
97
- (!accessor || memoize) &&
97
+ (!accessor || createComputed) &&
98
98
  typeof key === "string" &&
99
99
  !key.startsWith("$") &&
100
100
  !Object.hasOwn(values, "$" + key)
package/dist/index.es.js CHANGED
@@ -74,14 +74,14 @@ const objectStore = (values, proto) => {
74
74
  const desc = Object.getOwnPropertyDescriptor(values, key),
75
75
  accessor = desc.get || desc.set,
76
76
  getter = desc.get?.bind(state),
77
- memoize = getter && api.memo,
77
+ createComputed = getter && api.computed,
78
78
  signal = accessor ? undefined : api.is(desc.value) ? desc.value : api.signal(resolve(desc.value));
79
- let memo;
80
- const getSignal = () => getter ? (memo ||= memoize(getter)) : signal;
79
+ let computed;
80
+ const getSignal = () => getter ? (computed ||= createComputed(getter)) : signal;
81
81
  Object.defineProperty(state, key, {
82
82
  get: accessor
83
83
  ? getter
84
- ? () => memoize ? (api.getMemo || api.get)(getSignal()) : getter()
84
+ ? () => createComputed ? (api.getComputed || api.get)(getSignal()) : getter()
85
85
  : undefined
86
86
  : () => api.get(signal),
87
87
  set: accessor
@@ -92,7 +92,7 @@ const objectStore = (values, proto) => {
92
92
  enumerable: desc.enumerable,
93
93
  });
94
94
  if (
95
- (!accessor || memoize) &&
95
+ (!accessor || createComputed) &&
96
96
  typeof key === "string" &&
97
97
  !key.startsWith("$") &&
98
98
  !Object.hasOwn(values, "$" + key)
package/dist/index.min.js CHANGED
@@ -1 +1 @@
1
- var ustor=function(e){const t={},r=Symbol(),n=new WeakMap,s=/^(copyWithin|fill|pop|push|reverse|shift|sort|splice|unshift)$/,o=e=>!!e?.[r],i=e=>{const t=e&&Object.getPrototypeOf(e);return null!=e&&(null===t||t?.constructor===Object)},c=e=>Array.isArray(e)||i(e)?a(e):e,a=(e,a)=>{if(o(e))return e;if(!Array.isArray(e)&&!i(e))return e;const l=n.get(e);if(l)return l;if(!(t.signal&&t.get&&t.set&&t.is))throw new Error("ustor: configure api.signal, api.get, api.set and api.is");return Array.isArray(e)?(e=>{const o=t.signal(0);let i=0,a=!1;const l=()=>t.get(o),u=()=>{a=!1,t.set(o,t.get(o)+1)},f=new Proxy(e,{get:(e,t,n)=>{if(t===r)return!0;l();const o=Reflect.get(e,t,n);return"string"==typeof t&&s.test(t)&&"function"==typeof o?(...e)=>{i++;try{return o.apply(n,e)}finally{! --i&&a&&u()}}:o},set:(e,t,r)=>{const n=c(r);return!!Object.is(e[t],n)||!!Reflect.set(e,t,n)&&(i?a=!0:u(),!0)},deleteProperty:(e,t)=>!Object.hasOwn(e,t)||!!Reflect.deleteProperty(e,t)&&(i?a=!0:u(),!0),ownKeys:e=>(l(),Reflect.ownKeys(e)),has:(e,t)=>(l(),Reflect.has(e,t))});return n.set(e,f),e.forEach(((t,r)=>{e[r]=c(t)})),f})(e):((e,s)=>{const o=Object.create(void 0===s?Object.getPrototypeOf(e):s);return n.set(e,o),Reflect.ownKeys(e).forEach((r=>{const n=Object.getOwnPropertyDescriptor(e,r),s=n.get||n.set,i=n.get?.bind(o),a=i&&t.memo,l=s?void 0:t.is(n.value)?n.value:t.signal(c(n.value));let u;const f=()=>i?u||=a(i):l;Object.defineProperty(o,r,{get:s?i?()=>a?(t.getMemo||t.get)(f()):i():void 0:()=>t.get(l),set:s?n.set?.bind(o):!1===n.writable?void 0:e=>t.set(l,c(e)),enumerable:n.enumerable}),s&&!a||"string"!=typeof r||r.startsWith("$")||Object.hasOwn(e,"$"+r)||Object.defineProperty(o,"$"+r,{get:f,enumerable:!1})})),Object.defineProperty(o,r,{value:!0}),Object.seal(o)})(e,a)};return e.api=t,e.is=o,e.store=a,e}({});
1
+ var ustor=function(e){const t={},r=Symbol(),n=new WeakMap,s=/^(copyWithin|fill|pop|push|reverse|shift|sort|splice|unshift)$/,o=e=>!!e?.[r],i=e=>{const t=e&&Object.getPrototypeOf(e);return null!=e&&(null===t||t?.constructor===Object)},c=e=>Array.isArray(e)||i(e)?a(e):e,a=(e,a)=>{if(o(e))return e;if(!Array.isArray(e)&&!i(e))return e;const l=n.get(e);if(l)return l;if(!(t.signal&&t.get&&t.set&&t.is))throw new Error("ustor: configure api.signal, api.get, api.set and api.is");return Array.isArray(e)?(e=>{const o=t.signal(0);let i=0,a=!1;const l=()=>t.get(o),u=()=>{a=!1,t.set(o,t.get(o)+1)},f=new Proxy(e,{get:(e,t,n)=>{if(t===r)return!0;l();const o=Reflect.get(e,t,n);return"string"==typeof t&&s.test(t)&&"function"==typeof o?(...e)=>{i++;try{return o.apply(n,e)}finally{! --i&&a&&u()}}:o},set:(e,t,r)=>{const n=c(r);return!!Object.is(e[t],n)||!!Reflect.set(e,t,n)&&(i?a=!0:u(),!0)},deleteProperty:(e,t)=>!Object.hasOwn(e,t)||!!Reflect.deleteProperty(e,t)&&(i?a=!0:u(),!0),ownKeys:e=>(l(),Reflect.ownKeys(e)),has:(e,t)=>(l(),Reflect.has(e,t))});return n.set(e,f),e.forEach((t,r)=>{e[r]=c(t)}),f})(e):((e,s)=>{const o=Object.create(void 0===s?Object.getPrototypeOf(e):s);return n.set(e,o),Reflect.ownKeys(e).forEach(r=>{const n=Object.getOwnPropertyDescriptor(e,r),s=n.get||n.set,i=n.get?.bind(o),a=i&&t.computed,l=s?void 0:t.is(n.value)?n.value:t.signal(c(n.value));let u;const f=()=>i?u||=a(i):l;Object.defineProperty(o,r,{get:s?i?()=>a?(t.getComputed||t.get)(f()):i():void 0:()=>t.get(l),set:s?n.set?.bind(o):!1===n.writable?void 0:e=>t.set(l,c(e)),enumerable:n.enumerable}),s&&!a||"string"!=typeof r||r.startsWith("$")||Object.hasOwn(e,"$"+r)||Object.defineProperty(o,"$"+r,{get:f,enumerable:!1})}),Object.defineProperty(o,r,{value:!0}),Object.seal(o)})(e,a)};return e.api=t,e.is=o,e.store=a,e}({});
package/dist/index.umd.js CHANGED
@@ -1 +1 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).ustor={})}(this,(function(e){const t={},r=Symbol(),n=new WeakMap,s=/^(copyWithin|fill|pop|push|reverse|shift|sort|splice|unshift)$/,o=e=>!!e?.[r],i=e=>{const t=e&&Object.getPrototypeOf(e);return null!=e&&(null===t||t?.constructor===Object)},c=e=>Array.isArray(e)||i(e)?a(e):e,a=(e,a)=>{if(o(e))return e;if(!Array.isArray(e)&&!i(e))return e;const f=n.get(e);if(f)return f;if(!(t.signal&&t.get&&t.set&&t.is))throw new Error("ustor: configure api.signal, api.get, api.set and api.is");return Array.isArray(e)?(e=>{const o=t.signal(0);let i=0,a=!1;const f=()=>t.get(o),l=()=>{a=!1,t.set(o,t.get(o)+1)},u=new Proxy(e,{get:(e,t,n)=>{if(t===r)return!0;f();const o=Reflect.get(e,t,n);return"string"==typeof t&&s.test(t)&&"function"==typeof o?(...e)=>{i++;try{return o.apply(n,e)}finally{! --i&&a&&l()}}:o},set:(e,t,r)=>{const n=c(r);return!!Object.is(e[t],n)||!!Reflect.set(e,t,n)&&(i?a=!0:l(),!0)},deleteProperty:(e,t)=>!Object.hasOwn(e,t)||!!Reflect.deleteProperty(e,t)&&(i?a=!0:l(),!0),ownKeys:e=>(f(),Reflect.ownKeys(e)),has:(e,t)=>(f(),Reflect.has(e,t))});return n.set(e,u),e.forEach(((t,r)=>{e[r]=c(t)})),u})(e):((e,s)=>{const o=Object.create(void 0===s?Object.getPrototypeOf(e):s);return n.set(e,o),Reflect.ownKeys(e).forEach((r=>{const n=Object.getOwnPropertyDescriptor(e,r),s=n.get||n.set,i=n.get?.bind(o),a=i&&t.memo,f=s?void 0:t.is(n.value)?n.value:t.signal(c(n.value));let l;const u=()=>i?l||=a(i):f;Object.defineProperty(o,r,{get:s?i?()=>a?(t.getMemo||t.get)(u()):i():void 0:()=>t.get(f),set:s?n.set?.bind(o):!1===n.writable?void 0:e=>t.set(f,c(e)),enumerable:n.enumerable}),s&&!a||"string"!=typeof r||r.startsWith("$")||Object.hasOwn(e,"$"+r)||Object.defineProperty(o,"$"+r,{get:u,enumerable:!1})})),Object.defineProperty(o,r,{value:!0}),Object.seal(o)})(e,a)};e.api=t,e.is=o,e.store=a}));
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).ustor={})}(this,function(e){const t={},r=Symbol(),n=new WeakMap,s=/^(copyWithin|fill|pop|push|reverse|shift|sort|splice|unshift)$/,o=e=>!!e?.[r],i=e=>{const t=e&&Object.getPrototypeOf(e);return null!=e&&(null===t||t?.constructor===Object)},c=e=>Array.isArray(e)||i(e)?a(e):e,a=(e,a)=>{if(o(e))return e;if(!Array.isArray(e)&&!i(e))return e;const f=n.get(e);if(f)return f;if(!(t.signal&&t.get&&t.set&&t.is))throw new Error("ustor: configure api.signal, api.get, api.set and api.is");return Array.isArray(e)?(e=>{const o=t.signal(0);let i=0,a=!1;const f=()=>t.get(o),l=()=>{a=!1,t.set(o,t.get(o)+1)},u=new Proxy(e,{get:(e,t,n)=>{if(t===r)return!0;f();const o=Reflect.get(e,t,n);return"string"==typeof t&&s.test(t)&&"function"==typeof o?(...e)=>{i++;try{return o.apply(n,e)}finally{! --i&&a&&l()}}:o},set:(e,t,r)=>{const n=c(r);return!!Object.is(e[t],n)||!!Reflect.set(e,t,n)&&(i?a=!0:l(),!0)},deleteProperty:(e,t)=>!Object.hasOwn(e,t)||!!Reflect.deleteProperty(e,t)&&(i?a=!0:l(),!0),ownKeys:e=>(f(),Reflect.ownKeys(e)),has:(e,t)=>(f(),Reflect.has(e,t))});return n.set(e,u),e.forEach((t,r)=>{e[r]=c(t)}),u})(e):((e,s)=>{const o=Object.create(void 0===s?Object.getPrototypeOf(e):s);return n.set(e,o),Reflect.ownKeys(e).forEach(r=>{const n=Object.getOwnPropertyDescriptor(e,r),s=n.get||n.set,i=n.get?.bind(o),a=i&&t.computed,f=s?void 0:t.is(n.value)?n.value:t.signal(c(n.value));let l;const u=()=>i?l||=a(i):f;Object.defineProperty(o,r,{get:s?i?()=>a?(t.getComputed||t.get)(u()):i():void 0:()=>t.get(f),set:s?n.set?.bind(o):!1===n.writable?void 0:e=>t.set(f,c(e)),enumerable:n.enumerable}),s&&!a||"string"!=typeof r||r.startsWith("$")||Object.hasOwn(e,"$"+r)||Object.defineProperty(o,"$"+r,{get:u,enumerable:!1})}),Object.defineProperty(o,r,{value:!0}),Object.seal(o)})(e,a)};e.api=t,e.is=o,e.store=a});
package/dist/react.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";var e=require("react");const r={},t=t=>{const c=e.useCallback((e=>{if(!r.effect)throw new Error("ustor/react: configure api.effect with cleanup support");let c=!0;return r.effect((()=>{t(),c?c=!1:e()}))}),[t]);return e.useSyncExternalStore(c,t,t)};exports.useSignal=c=>{const s=e.useCallback((()=>r.get(c)),[c]);return t(s)},exports.useStore=t;
1
+ "use strict";var e=require("react");const r={};exports.useSelector=t=>{const c=e.useCallback(e=>{if(!r.effect)throw new Error("ustor/react: configure api.effect with cleanup support");let c=!0;return r.effect(()=>{t(),c?c=!1:e()})},[t]);return e.useSyncExternalStore(c,t,t)};
package/dist/react.es.js CHANGED
@@ -1 +1 @@
1
- import{useCallback as t,useSyncExternalStore as r}from"react";const e={},o=o=>{const c=t((t=>{if(!e.effect)throw new Error("ustor/react: configure api.effect with cleanup support");let r=!0;return e.effect((()=>{o(),r?r=!1:t()}))}),[o]);return r(c,o,o)},c=r=>{const c=t((()=>e.get(r)),[r]);return o(c)};export{c as useSignal,o as useStore};
1
+ import{useCallback as r,useSyncExternalStore as t}from"react";const e={},o=o=>{const c=r(r=>{if(!e.effect)throw new Error("ustor/react: configure api.effect with cleanup support");let t=!0;return e.effect(()=>{o(),t?t=!1:r()})},[o]);return t(c,o,o)};export{o as useSelector};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ustor",
3
3
  "description": "A tiny, dependency-free reactive store with pluggable signal adapters and optional React hooks.",
4
- "version": "0.2.0",
4
+ "version": "0.3.1",
5
5
  "type": "module",
6
6
  "source": "./src/index.js",
7
7
  "types": "./types/index.d.ts",
@@ -40,6 +40,9 @@
40
40
  "typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.nodenext.json",
41
41
  "test:watch": "vitest",
42
42
  "test:coverage": "vitest --run --coverage",
43
+ "examples": "npm run examples:node && npm run examples:typecheck",
44
+ "examples:node": "npm run build && node scripts/run-examples.mjs",
45
+ "examples:typecheck": "tsc -p tsconfig.examples.json",
43
46
  "profile": "node --expose-gc scripts/profile.mjs",
44
47
  "profile:smoke": "node --expose-gc scripts/profile.mjs --updates=5000 --array-updates=500 --seconds=0.5 --cycles=500 --sample-ms=100",
45
48
  "prepack": "npm run build"
@@ -48,6 +51,7 @@
48
51
  "@preact/signals-core": "^1.14.4",
49
52
  "@rollup/plugin-terser": "^1.0.2",
50
53
  "@tanstack/store": "^0.11.2",
54
+ "@types/react": "^19.3.0",
51
55
  "@vitest/coverage-v8": "^5.0.5",
52
56
  "@vue/reactivity": "^3.5.44",
53
57
  "@webreflection/alien-signals": "^0.4.2",
package/types/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- export interface SignalApi<Signal = any, Memo = any> {
1
+ export interface SignalApi<Signal = any, Computed = any> {
2
2
  signal(value: any): Signal;
3
3
  get(signal: Signal): any;
4
4
  set(signal: Signal, value: any): void;
5
5
  is(value: unknown): boolean;
6
- memo?(fn: () => any): Memo;
7
- getMemo?(memo: Memo): any;
6
+ computed?(fn: () => any): Computed;
7
+ getComputed?(computed: Computed): any;
8
8
  effect?(fn: () => void): () => void;
9
9
  [key: string]: unknown;
10
10
  }
package/types/react.d.ts CHANGED
@@ -1,17 +1,2 @@
1
- export type SignalValue<Signal> = Signal extends { state: readonly (infer Value)[] }
2
- ? Value
3
- : Signal extends { value: infer Value }
4
- ? Value
5
- : Signal extends { get(): infer Value }
6
- ? Value
7
- : Signal extends () => infer Value
8
- ? Value
9
- : Signal extends readonly [() => infer Value, ...any[]]
10
- ? Value
11
- : unknown;
12
-
13
1
  /** Subscribe to a selector that returns a primitive or stable reference. */
14
- export declare function useStore<Value>(selector: () => Value): Value;
15
-
16
- /** Subscribe to one underlying signal exposed as state.$property. */
17
- export declare function useSignal<Signal>(signal: Signal): SignalValue<Signal>;
2
+ export declare function useSelector<Value>(selector: () => Value): Value;