@deijose/nix-js 2.1.0 → 2.2.0

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.
@@ -1,24 +1,54 @@
1
- import { Signal } from "./reactivity";
2
- /** Maps each state property to its corresponding Signal. */
1
+ import { Signal, type WatchOptions } from "./reactivity";
3
2
  export type StoreSignals<T extends Record<string, unknown>> = {
4
3
  readonly [K in keyof T]: Signal<T[K]>;
5
4
  };
6
- /** Subscriber callback used by `$subscribe`. */
7
- export type StoreSubscriber<T extends Record<string, unknown>> = (key: keyof T, newValue: T[keyof T], oldValue: T[keyof T] | undefined) => void;
8
- /** The store as seen by the consumer: signals + actions + getters + built-ins. */
9
- export type Store<T extends Record<string, unknown>, A extends Record<string, unknown> = Record<never, never>, G extends Record<string, Signal<unknown>> = Record<never, never>> = StoreSignals<T> & A & G & {
10
- /** The current state snapshot. Reactive. */
5
+ export type ReadonlySignal<T> = Omit<Signal<T>, "value" | "update" | "dispose"> & {
6
+ readonly value: T;
7
+ readonly dispose: never;
8
+ };
9
+ export type StoreGetters<G extends Record<string, Signal<unknown>>> = {
10
+ readonly [K in keyof G]: ReadonlySignal<G[K] extends Signal<infer V> ? V : never>;
11
+ };
12
+ export type Store<T extends Record<string, unknown>, A extends Record<string, unknown> = Record<never, never>, G extends Record<string, Signal<unknown>> = Record<never, never>> = StoreSignals<T> & A & StoreGetters<G> & {
13
+ readonly $id: string;
14
+ /** Current snapshot — reading inside effect/computed creates subscription. */
11
15
  readonly $state: T;
12
- /** Resets all signals to their initial values. */
16
+ /**
17
+ * The computed Signal that backs $state.
18
+ * Plugins receive this to compose new reactive nodes on top.
19
+ * This is what makes the plugin system reactive-native:
20
+ * no hooks, just signals all the way down.
21
+ */
22
+ readonly $stateSignal: ReadonlySignal<T>;
23
+ /** Reset to initial values (batched). */
13
24
  $reset(): void;
14
- /** Patches the store with a partial state. */
25
+ /** Partial update (batched). */
15
26
  $patch(partial: Partial<T>): void;
16
- /** Subscribes to all store signal changes. Returns an unsubscribe function. */
17
- $subscribe(listener: StoreSubscriber<T>): () => void;
27
+ /**
28
+ * Watches state changes. This is exactly watch() from reactivity.ts —
29
+ * no new primitive to learn.
30
+ */
31
+ $watch(cb: (next: T, prev: T | undefined) => void, options?: WatchOptions): () => void;
32
+ /** Disposes the store and runs all plugin cleanups. */
33
+ $dispose(): void;
18
34
  };
19
35
  /**
20
- * Creates a reactive global store. Each property becomes a Signal.
21
- * Optional `actionsFactory` receives the signals and returns action methods.
22
- * Optional `gettersFactory` receives the signals and returns derived getter signals.
36
+ * A NixPlugin is a function that receives the assembled store and
37
+ * optionally returns a cleanup function.
38
+ *
39
+ * There are NO lifecycle hooks. The plugin extends the signal graph directly:
40
+ *
41
+ * watch(store.$stateSignal, ...) — react to any state change
42
+ * computed(() => store.someSignal.value) — derive new nodes
43
+ * store.mySignal = signal(...) — attach new reactive state
44
+ * wrapMethod(store, '$patch', ...) — intercept mutations
45
+ *
46
+ * Because plugins only use Nix.js primitives, they compose with each other
47
+ * naturally — one plugin can observe a signal that another plugin added.
23
48
  */
24
- export declare function createStore<T extends Record<string, unknown>, A extends Record<string, unknown> = Record<never, never>, G extends Record<string, Signal<unknown>> = Record<never, never>>(initialState: T, actionsFactory?: (signals: StoreSignals<T>) => A, gettersFactory?: (signals: StoreSignals<T>) => G): Store<T, A, G>;
49
+ export type NixPlugin<T extends Record<string, unknown>, A extends Record<string, unknown> = Record<never, never>, G extends Record<string, Signal<unknown>> = Record<never, never>> = (store: Store<T, A, G>) => (() => void) | void;
50
+ export interface CreateStoreOptions<T extends Record<string, unknown>, A extends Record<string, unknown> = Record<never, never>, G extends Record<string, Signal<unknown>> = Record<never, never>> {
51
+ name?: string;
52
+ plugins?: NixPlugin<T, A, G>[];
53
+ }
54
+ export declare function createStore<T extends Record<string, unknown>, A extends Record<string, unknown> = Record<never, never>, G extends Record<string, Signal<unknown>> = Record<never, never>>(initialState: T, actionsFactory?: (signals: StoreSignals<T>) => A, gettersFactory?: (signals: StoreSignals<T>) => G, options?: CreateStoreOptions<T, A, G>): Store<T, A, G>;
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./signals.cjs");function t(t,r,o){let n={},s=new Set(["$reset","$patch","$state","$subscribe"]);for(let r of Object.keys(t)){if(s.has(r))throw Error(`[Nix] Store key "${r}" is reserved.`);n[r]=e.signal(t[r])}let l=n;let i=Object.assign(Object.create(null),l,{$reset:function(){for(let e of Object.keys(t))n[e].value=t[e]},$patch:function(e){for(let t of Object.keys(e))t in n&&(n[t].value=e[t])},$subscribe:function(r){let o=[];for(let n of Object.keys(t)){let t=l[n],s=e.watch(t,(e,t)=>{r(n,e,t)});o.push(s)}return()=>{for(let e of o)e()}}});if(Object.defineProperty(i,"$state",{get(){let e={};for(let t in n)e[t]=n[t].value;return e},enumerable:!0,configurable:!1}),r){let e=r(l);for(let t of Object.keys(e))s.has(t)?console.warn(`[Nix] Store action name "${t}" is reserved and will be ignored.`):i[t]=e[t]}if(o){let e=o(l);for(let t of Object.keys(e))s.has(t)?console.warn(`[Nix] Store getter name "${t}" is reserved and will be ignored.`):i[t]=e[t]}return i}exports.createStore=t;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./signals.cjs");var t=new Set(["$id","$state","$stateSignal","$reset","$patch","$watch","$dispose"]);function n(e){if("__proto__"===e||"constructor"===e||"prototype"===e)throw Error(`[Nix] Store key "${e}" is not allowed for security reasons.`);if(t.has(e))throw Error(`[Nix] Store key "${e}" is reserved.`)}function r(e,r){return!t.has(e)||(console.warn(`[Nix] Store ${r} "${e}" is reserved and will be ignored.`),!1)}function i(e,t){let r=Object.create(e);return Object.defineProperty(r,"dispose",{value:()=>{throw Error(`[Nix] Cannot dispose readonly getter "${t}". Dispose the store instead with store.$dispose().`)},writable:!1,configurable:!1}),Object.defineProperty(r,"value",{get:()=>e.value,set(){throw Error(`[Nix] "${t}" is read-only.`)},configurable:!1}),Object.defineProperty(r,"update",{value:()=>{throw Error(`[Nix] "${t}" is read-only.`)},writable:!1,configurable:!1}),r}function a(o,a,l,s={}){let{name:c="store",plugins:f=[]}=s,u=Object.keys(o),d={};for(let t of u)n(t),d[t]=e.signal(o[t]);let b,$=d,g=e.computed(()=>{let e={};for(let t of u)e[t]=d[t].value;return e}),p=i(g,`store "${c}".$stateSignal`);try{b=structuredClone(o)}catch(e){throw Error(`[Nix] Store "${c}" initialState contains non-serializable data (functions, DOM nodes, Symbols, or WeakRefs). Remove these before creating the store. Original error: ${e}`)}let h=Object.assign(Object.create(null),$,{$reset:function(){e.batch(()=>{for(let e of u)d[e].value=b[e]})},$patch:function(t){e.batch(()=>{for(let e of Object.keys(t))Object.prototype.hasOwnProperty.call(d,e)&&(d[e].value=t[e])})},$watch:function(t,r){return e.watch(g,t,r)}});Object.defineProperty(h,"$id",{value:c,writable:!1,enumerable:!1,configurable:!1}),Object.defineProperty(h,"$state",{get:()=>g.value,enumerable:!0,configurable:!1}),Object.defineProperty(h,"$stateSignal",{value:p,writable:!1,enumerable:!1,configurable:!1});let w=new Set([...u,...Array.from(t)]);if(a){let e=a($);for(let t of Object.keys(e))if(r(t,"action")){if(w.has(t)){console.warn(`[Nix] Store "${c}": action "${t}" collides with an existing signal or getter and will be ignored.`);continue}w.add(t),h[t]=e[t]}}if(l){let t=l($);for(let o of Object.keys(t)){if(!r(o,"getter"))continue;if(w.has(o)){console.warn(`[Nix] Store "${c}": getter "${o}" collides with an existing signal or action and will be ignored.`);continue}let n=t[o];if(!(n instanceof e.Signal))throw TypeError(`[Nix] Store "${c}": getter "${o}" must return a Signal (wrap it with computed()). Got: ${typeof n}`);w.add(o),h[o]=i(n,`getter "${o}" in store "${c}"`)}}let y=[()=>g.dispose()];for(let e of f)try{let t=e(h);"function"==typeof t&&y.push(t)}catch(e){console.error(`[Nix] Plugin initialization failed for store "${c}":`,e)}return h.$dispose=()=>{for(let e of y)e()},h}exports.createStore=a;
package/dist/lib/store.js CHANGED
@@ -1 +1 @@
1
- import{signal as e,watch as t}from"./signals.js";function n(r,o,s){let n={},l=new Set(["$reset","$patch","$state","$subscribe"]);for(let t of Object.keys(r)){if(l.has(t))throw Error(`[Nix] Store key "${t}" is reserved.`);n[t]=e(r[t])}let a=n;let i=Object.assign(Object.create(null),a,{$reset:function(){for(let e of Object.keys(r))n[e].value=r[e]},$patch:function(e){for(let t of Object.keys(e))t in n&&(n[t].value=e[t])},$subscribe:function(e){let o=[];for(let s of Object.keys(r)){let r=a[s],n=t(r,(t,r)=>{e(s,t,r)});o.push(n)}return()=>{for(let e of o)e()}}});if(Object.defineProperty(i,"$state",{get(){let e={};for(let t in n)e[t]=n[t].value;return e},enumerable:!0,configurable:!1}),o){let e=o(a);for(let t of Object.keys(e))l.has(t)?console.warn(`[Nix] Store action name "${t}" is reserved and will be ignored.`):i[t]=e[t]}if(s){let e=s(a);for(let t of Object.keys(e))l.has(t)?console.warn(`[Nix] Store getter name "${t}" is reserved and will be ignored.`):i[t]=e[t]}return i}export{n as createStore};
1
+ import{Signal as e,batch as t,computed as n,signal as r,watch as i}from"./signals.js";var a=new Set(["$id","$state","$stateSignal","$reset","$patch","$watch","$dispose"]);function o(e){if("__proto__"===e||"constructor"===e||"prototype"===e)throw Error(`[Nix] Store key "${e}" is not allowed for security reasons.`);if(a.has(e))throw Error(`[Nix] Store key "${e}" is reserved.`)}function s(e,t){return!a.has(e)||(console.warn(`[Nix] Store ${t} "${e}" is reserved and will be ignored.`),!1)}function c(e,t){let r=Object.create(e);return Object.defineProperty(r,"dispose",{value:()=>{throw Error(`[Nix] Cannot dispose readonly getter "${t}". Dispose the store instead with store.$dispose().`)},writable:!1,configurable:!1}),Object.defineProperty(r,"value",{get:()=>e.value,set(){throw Error(`[Nix] "${t}" is read-only.`)},configurable:!1}),Object.defineProperty(r,"update",{value:()=>{throw Error(`[Nix] "${t}" is read-only.`)},writable:!1,configurable:!1}),r}function l(l,f,u,d={}){let{name:$="store",plugins:b=[]}=d,g=Object.keys(l),p={};for(let e of g)o(e),p[e]=r(l[e]);let w,h=p,y=n(()=>{let e={};for(let t of g)e[t]=p[t].value;return e}),O=c(y,`store "${$}".$stateSignal`);try{w=structuredClone(l)}catch(e){throw Error(`[Nix] Store "${$}" initialState contains non-serializable data (functions, DOM nodes, Symbols, or WeakRefs). Remove these before creating the store. Original error: ${e}`)}let S=Object.assign(Object.create(null),h,{$reset:function(){t(()=>{for(let e of g)p[e].value=w[e]})},$patch:function(e){t(()=>{for(let t of Object.keys(e))Object.prototype.hasOwnProperty.call(p,t)&&(p[t].value=e[t])})},$watch:function(e,t){return i(y,e,t)}});Object.defineProperty(S,"$id",{value:$,writable:!1,enumerable:!1,configurable:!1}),Object.defineProperty(S,"$state",{get:()=>y.value,enumerable:!0,configurable:!1}),Object.defineProperty(S,"$stateSignal",{value:O,writable:!1,enumerable:!1,configurable:!1});let j=new Set([...g,...Array.from(a)]);if(f){let e=f(h);for(let t of Object.keys(e))if(s(t,"action")){if(j.has(t)){console.warn(`[Nix] Store "${$}": action "${t}" collides with an existing signal or getter and will be ignored.`);continue}j.add(t),S[t]=e[t]}}if(u){let t=u(h);for(let r of Object.keys(t)){if(!s(r,"getter"))continue;if(j.has(r)){console.warn(`[Nix] Store "${$}": getter "${r}" collides with an existing signal or action and will be ignored.`);continue}let o=t[r];if(!(o instanceof e))throw TypeError(`[Nix] Store "${$}": getter "${r}" must return a Signal (wrap it with computed()). Got: ${typeof o}`);j.add(r),S[r]=c(o,`getter "${r}" in store "${$}"`)}}let v=[()=>y.dispose()];for(let e of b)try{let t=e(S);"function"==typeof t&&v.push(t)}catch(e){console.error(`[Nix] Plugin initialization failed for store "${$}":`,e)}return S.$dispose=()=>{for(let e of v)e()},S}export{l as createStore};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deijose/nix-js",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "A lightweight, fully reactive micro-framework — no virtual DOM, no compiler, just signals and tagged templates.",
5
5
  "license": "MIT",
6
6
  "author": "Deiver Vasquez",