@deijose/nix-js 2.2.2 → 2.4.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.
@@ -24,9 +24,7 @@ export interface RouteRecord {
24
24
  /** Route-level guard. Runs only when entering this specific route. */
25
25
  beforeEnter?: NavigationGuard;
26
26
  }
27
- /**
28
- * Callback for `afterEach` hooks — receives the committed `to` and `from` paths.
29
- */
27
+ /** Callback for `afterEach` hooks — receives the committed `to` and `from` paths. */
30
28
  export type AfterEachHook = (to: string, from: string) => void;
31
29
  /** Named route target for programmatic navigation. */
32
30
  export interface NamedRouteLocation {
@@ -50,118 +48,96 @@ export interface ScrollPosition {
50
48
  export type ScrollBehavior = (to: string, from: string, savedPosition: ScrollPosition | null) => ScrollPosition | false | void;
51
49
  /** Router URL mode strategy. */
52
50
  export type RouterMode = "history" | "hash";
53
- /**
54
- * Result of `router.resolve(path)` — inspect what would match without navigating.
55
- */
51
+ /** Result of `router.resolve(path)`. */
56
52
  export interface ResolvedRoute {
57
- /** Whether the path matched any registered route. */
58
53
  matched: boolean;
59
- /** Extracted route params (empty object if no match). */
60
54
  params: Record<string, string>;
61
- /** The matched route record, or `undefined` if no match. */
62
55
  route: RouteRecord | undefined;
63
56
  }
64
- /**
65
- * Options for `createRouter()`.
66
- */
57
+ export type NavigationAction = "push" | "replace" | "pop" | "initial";
58
+ export type NavigationDirection = "forward" | "back" | "root" | "none";
59
+ export interface NavigationIntent {
60
+ /** Which router method produced the navigation. */
61
+ action: NavigationAction;
62
+ /** Logical direction — used by Ionic, custom animation builders, etc. */
63
+ direction: NavigationDirection;
64
+ /** Opaque animation builder passed through to the outlet. */
65
+ animation?: unknown;
66
+ }
67
+ /** Options accepted by `navigate` / `replace`. */
68
+ export interface NavigateOptions {
69
+ query?: Record<string, string | number | boolean | null | undefined>;
70
+ /** Override the inferred direction (e.g. tab change should be `"none"`). */
71
+ direction?: NavigationDirection;
72
+ /** Animation builder passed to outlets that animate. Ionic AnimationBuilder, etc. */
73
+ animation?: unknown;
74
+ }
67
75
  export interface RouterOptions {
68
76
  /**
69
- * Base path for the application.
70
- * Useful when deploying under a sub-path (e.g. GitHub Pages).
71
- *
72
- * If omitted, the router auto-detects the base from the `<base href>` tag
73
- * that tools like Vite inject when you set `base` in your config.
74
- *
75
- * @example
76
- * // vite.config.ts sets base: "/my-app/"
77
- * // No need to pass base — auto-detected from <base href>
78
- * createRouter(routes);
79
- *
80
- * // Or pass it explicitly:
81
- * createRouter(routes, { base: "/my-app/" });
77
+ * Base path for the application (sub-path deployments).
78
+ * If omitted, auto-detected from `<base href>`.
82
79
  */
83
80
  base?: string;
84
81
  /** URL handling mode. `history` by default. */
85
82
  mode?: RouterMode;
86
- /**
87
- * Optional custom scroll behavior for navigation.
88
- * If omitted, router scrolls to top on push/replace and restores saved
89
- * positions on back/forward when available.
90
- */
83
+ /** Optional custom scroll behavior. */
91
84
  scrollBehavior?: ScrollBehavior;
92
85
  }
93
86
  export interface Router {
94
- /** Signal with the current active pathname (without the base prefix). */
95
87
  readonly current: Signal<string>;
96
- /** Signal with the extracted dynamic route params. */
97
88
  readonly params: Signal<Record<string, string>>;
98
- /** Signal with the URL query params. */
99
89
  readonly query: Signal<Record<string, string>>;
100
- /** The resolved base path used by the router. */
101
90
  readonly base: string;
102
- /** Navigate to a new path via `pushState`. Guards run before committing. */
103
- navigate(location: RouteLocation, query?: Record<string, string | number | boolean | null | undefined>): void;
104
- /** Navigate via `replaceState` (no new history entry). Guards still run. */
105
- replace(location: RouteLocation, query?: Record<string, string | number | boolean | null | undefined>): void;
106
- /** Go back one entry in the browser history. */
107
- back(): void;
108
- /** Go forward one entry in the browser history. */
109
- forward(): void;
110
- /** Move `delta` entries in the browser history. */
91
+ /**
92
+ * Last navigation intent. Updated SYNCHRONOUSLY before `current` whenever
93
+ * navigation commits, so an effect that reads `current` will also see the
94
+ * matching `intent`.
95
+ */
96
+ readonly intent: Signal<NavigationIntent>;
97
+ /**
98
+ * Whether there is a previous entry in this router's logical stack.
99
+ * Differs from `history.length` — only counts entries this router pushed.
100
+ */
101
+ readonly canGoBack: Signal<boolean>;
102
+ navigate(location: RouteLocation, options?: NavigateOptions): void;
103
+ replace(location: RouteLocation, options?: NavigateOptions): void;
104
+ back(animation?: unknown): void;
105
+ forward(animation?: unknown): void;
111
106
  go(delta: number): void;
112
- /** Check if `path` is currently active. `exact=false` enables prefix matching. */
113
107
  isActive(path: string, exact?: boolean): boolean;
114
- /** Inspect what route would match `path` without navigating. */
115
108
  resolve(path: string): ResolvedRoute;
116
- /** Original route tree passed to `createRouter`. */
117
109
  readonly routes: RouteRecord[];
118
- /** Register a global navigation guard. Returns a removal function. */
119
110
  beforeEach(guard: NavigationGuard): () => void;
120
- /** Register a hook that runs after every successful navigation. Returns a removal function. */
121
111
  afterEach(hook: AfterEachHook): () => void;
122
112
  }
123
- /** DI key for router instances. Useful to mount multiple app trees with isolated routers. */
113
+ /** DI key for router instances. */
124
114
  export declare const RouterKey: import("./context").InjectionKey<Router>;
125
- export interface _RouterDebugInternal {
126
- mode: RouterMode;
127
- base: string;
128
- currentPath: string;
129
- params: Record<string, string>;
130
- query: Record<string, string>;
131
- matchedPath: string | null;
132
- activeGuards: {
133
- globalCount: number;
134
- hasRouteGuard: boolean;
135
- names: string[];
136
- };
137
- }
138
- /**
139
- * Creates the History API router and sets it as the active singleton.
140
- * In production the server must serve `index.html` for all non-file routes.
141
- *
142
- * @param routes The route tree.
143
- * @param options Optional configuration — use `base` for sub-path deployments.
144
- */
145
115
  export declare function createRouter(routes: RouteRecord[], options?: RouterOptions): Router;
146
- /** Returns the active router singleton. */
147
116
  export declare function nixRouter(): Router;
148
- /**
149
- * @internal — Resets the router singleton. Used by tests to avoid
150
- * "A router already exists" warnings between test cases.
151
- */
117
+ /** @internal */
152
118
  export declare function _resetRouter(): void;
153
- /** @internal — lightweight router state accessor for optional devtools modules. */
154
- export declare function _debugGetRouterInternal(): _RouterDebugInternal | null;
155
- /** Renders the matched route component at the given nesting `depth`. */
156
119
  export declare class RouterView extends NixComponent {
157
120
  private _depth;
158
121
  constructor(depth?: number);
159
122
  render(): NixTemplate;
160
123
  }
161
- /** Reactive navigation link styled as active/inactive based on the current route. */
162
124
  export declare class Link extends NixComponent {
163
125
  private _to;
164
126
  private _label;
165
127
  constructor(to: string, label: string);
166
128
  render(): NixTemplate;
167
129
  }
130
+ export interface _RouterDebugInternal {
131
+ mode: RouterMode;
132
+ base: string;
133
+ currentPath: string;
134
+ params: Record<string, string>;
135
+ query: Record<string, string>;
136
+ matchedPath: string | null;
137
+ activeGuards: {
138
+ globalCount: number;
139
+ hasRouteGuard: boolean;
140
+ names: string[];
141
+ };
142
+ }
143
+ export declare function _debugGetRouterInternal(): _RouterDebugInternal | null;
@@ -1,60 +1,125 @@
1
1
  import { Signal, type WatchOptions } from "./reactivity";
2
- export type StoreSignals<T extends Record<string, unknown>> = {
2
+ /**
3
+ * Maps a plain state object T into a record of Signals — one per key.
4
+ * This is what the actions/getters factories receive as their argument.
5
+ *
6
+ * Given: { count: number, name: string }
7
+ * Becomes: { count: Signal<number>, name: Signal<string> }
8
+ */
9
+ export type StoreSignals<T extends object> = {
3
10
  readonly [K in keyof T]: Signal<T[K]>;
4
11
  };
12
+ /**
13
+ * A read-only Signal — extends Signal so it satisfies `instanceof Signal`
14
+ * checks (used by `watch()`), but throws on any mutation attempt.
15
+ */
5
16
  export declare class ReadonlySignal<T> extends Signal<T> {
6
17
  private readonly label;
7
18
  constructor(source: Signal<T>, label?: string);
8
19
  }
20
+ /**
21
+ * Maps a getters factory result (a record of Signals) into a record of
22
+ * read-only Signals exposed on the store.
23
+ *
24
+ * The Omit pattern enforces `readonly value` at the type level — TypeScript
25
+ * blocks `getter.value = x` at compile time. The runtime ReadonlySignal class
26
+ * also throws on mutation as defense in depth.
27
+ */
9
28
  export type StoreGetters<G extends Record<string, Signal<unknown>>> = {
10
- readonly [K in keyof G]: ReadonlySignal<G[K] extends Signal<infer V> ? V : never>;
29
+ readonly [K in keyof G]: Omit<ReadonlySignal<G[K] extends Signal<infer V> ? V : never>, "value"> & {
30
+ readonly value: G[K] extends Signal<infer V> ? V : never;
31
+ };
11
32
  };
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> & {
33
+ /**
34
+ * The full store type — combines reactive state signals, action methods,
35
+ * computed getters (as ReadonlySignals), and the framework-level $-prefixed API.
36
+ */
37
+ export type Store<T extends object, A extends object = Record<never, never>, G extends Record<string, Signal<unknown>> = Record<never, never>> = StoreSignals<T> & A & StoreGetters<G> & {
13
38
  readonly $id: string;
14
- /** Current snapshot — reading inside effect/computed creates subscription. */
39
+ /** Reactive snapshot — reading inside effect/computed creates a subscription to the whole state. */
15
40
  readonly $state: T;
16
41
  /**
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.
42
+ * Passive snapshot returns the current state values WITHOUT creating
43
+ * a reactive subscription. Use this in plugins, loggers, persistence,
44
+ * or anywhere you need a one-shot read.
45
+ */
46
+ $snapshot(): T;
47
+ /**
48
+ * The computed Signal that backs $state. Plugins receive this to
49
+ * compose new reactive nodes on top.
21
50
  */
22
51
  readonly $stateSignal: ReadonlySignal<T>;
23
52
  /** Reset to initial values (batched). */
24
53
  $reset(): void;
25
54
  /** Partial update (batched). */
26
55
  $patch(partial: Partial<T>): void;
27
- /**
28
- * Watches state changes. This is exactly watch() from reactivity.ts —
29
- * no new primitive to learn.
30
- */
56
+ /** Watches state changes. Equivalent to `watch(store.$stateSignal, cb, opts)`. */
31
57
  $watch(cb: (next: T, prev: T | undefined) => void, options?: WatchOptions): () => void;
32
58
  /** Disposes the store and runs all plugin cleanups. */
33
59
  $dispose(): void;
34
60
  };
61
+ export type ActionsFactory<T extends object, A extends object> = (signals: StoreSignals<T>) => A;
62
+ export type GettersFactory<T extends object, G extends Record<string, Signal<unknown>>> = (signals: StoreSignals<T>) => G;
35
63
  /**
36
64
  * A NixPlugin is a function that receives the assembled store and
37
- * optionally returns a cleanup function.
65
+ * optionally returns a cleanup function called on $dispose().
38
66
  *
39
- * There are NO lifecycle hooks. The plugin extends the signal graph directly:
67
+ * There are NO lifecycle hooks. Plugins extend the signal graph directly
68
+ * using the framework primitives:
40
69
  *
41
70
  * watch(store.$stateSignal, ...) — react to any state change
42
71
  * 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.
72
+ * store.$snapshot() passive read for logging/persistence
73
+ */
74
+ export type NixPlugin<T extends object, A extends object = Record<never, never>, G extends Record<string, Signal<unknown>> = Record<never, never>> = (store: Store<T, A, G>) => (() => void) | void;
75
+ /**
76
+ * Options object for createStore. NoInfer<T> on factory parameters ensures
77
+ * T is inferred ONLY from initialState, never from the factories — this is
78
+ * what restores the type inference that broke in v2.2.1.
48
79
  */
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
- /** Display name for the store. Used in error messages and devtools. */
80
+ export type CreateStoreOptions<T extends object, A extends object, G extends Record<string, Signal<unknown>>> = {
81
+ /** Display name. Used in error messages, devtools, and $id. */
52
82
  name?: string;
53
- /** Factory that receives the raw signals and returns action methods. */
54
- actions?: (signals: StoreSignals<T>) => A;
55
- /** Factory that receives the raw signals and returns computed getters. */
56
- getters?: (signals: StoreSignals<T>) => G;
83
+ /** Action factory receives raw signals, returns methods exposed on the store. */
84
+ actions?: (signals: StoreSignals<NoInfer<T>>) => A;
85
+ /** Getter factory receives raw signals, returns computed Signals exposed as ReadonlySignals. */
86
+ getters?: (signals: StoreSignals<NoInfer<T>>) => G;
57
87
  /** Plugins to extend the store. Each receives the assembled store. */
58
- plugins?: NixPlugin<T, A, G>[];
59
- }
60
- 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, options?: CreateStoreOptions<T, A, G>): Store<T, A, G>;
88
+ plugins?: NixPlugin<NoInfer<T>, A, G>[];
89
+ };
90
+ /**
91
+ * Creates a reactive store with optional actions, getters, and plugins.
92
+ *
93
+ * @example Just state
94
+ * ```ts
95
+ * const counter = createStore({ count: 0 });
96
+ * counter.count.value++;
97
+ * ```
98
+ *
99
+ * @example State + actions
100
+ * ```ts
101
+ * const counter = createStore({ count: 0 }, {
102
+ * actions: (s) => ({ increment: () => s.count.value++ }),
103
+ * });
104
+ * counter.increment();
105
+ * ```
106
+ *
107
+ * @example State + getters (no actions)
108
+ * ```ts
109
+ * const inventory = createStore({ items: [] as Item[] }, {
110
+ * getters: (s) => ({ count: computed(() => s.items.value.length) }),
111
+ * });
112
+ * inventory.count.value;
113
+ * ```
114
+ *
115
+ * @example Full store
116
+ * ```ts
117
+ * const cart = createStore({ items: [] as Item[], discount: 0 }, {
118
+ * name: "cart",
119
+ * actions: (s) => ({ addItem: (i: Item) => { s.items.value = [...s.items.value, i] } }),
120
+ * getters: (s) => ({ total: computed(() => s.items.value.reduce((sum, i) => sum + i.price, 0) * (1 - s.discount.value)) }),
121
+ * plugins: [persistPlugin({ key: "cart" })],
122
+ * });
123
+ * ```
124
+ */
125
+ export declare function createStore<T extends object, A extends object = Record<never, never>, G extends Record<string, Signal<unknown>> = Record<never, never>>(initialState: T, options?: CreateStoreOptions<T, A, G>): Store<T, A, G>;
@@ -1,7 +1,7 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./signals.cjs"),t=require("./template2.cjs"),n=require("./lifecycle.cjs"),r=require("./context.cjs");var i=r.createInjectionKey("nix:router"),a=null,o=null,s="__nix_scroll";function c(){if(!a)throw Error("[Nix] No active router. Call createRouter() first.");return a}function l(){return{left:window.scrollX??window.pageXOffset??0,top:window.scrollY??window.pageYOffset??0}}function u(e){if(!e||"object"!=typeof e)return null;let t=e[s];if(!t||"object"!=typeof t)return null;let r=t.left,n=t.top;return"number"!=typeof r||"number"!=typeof n?null:{left:r,top:n}}function d(e,t){let r=e&&"object"==typeof e?{...e}:{};return r[s]={left:t.left,top:t.top},r}function f(e){let t={};return new URLSearchParams(e).forEach((e,r)=>{t[r]=e}),t}function p(e){let t=new URLSearchParams;for(let[r,n]of Object.entries(e))null!=n&&!1!==n&&t.set(r,String(n));let r=t.toString();return r?"?"+r:""}function m(e){return"*"===e?[{kind:"wildcard"}]:e.split("/").filter(Boolean).map(e=>"*"===e?{kind:"wildcard"}:e.startsWith(":")?{kind:"param",name:e.slice(1)}:{kind:"literal",value:e})}function h(e,t){return"*"===t?""===e?"*":e+"/*":(e+(t.startsWith("/")?t:"/"+t)).replace(/\/+/g,"/")||"/"}function g(e,t="",r=[]){let n=[];for(let a of e){let e=h(t,a.path),o=[...r,a.component],l=m(e);n.push({fullPath:e,segments:l,chain:o,name:a.name,meta:a.meta,beforeEnter:a.beforeEnter,record:a}),a.children?.length&&n.push(...g(a.children,e,o))}return n}function _(e,t){let r=e.split("/").filter(Boolean),n=t.segments;if(1===n.length&&"wildcard"===n[0].kind)return{};let a=n.length>0&&"wildcard"===n[n.length-1].kind,o=a?n.slice(0,-1):n;if(a){if(r.length<o.length)return null}else if(r.length!==o.length)return null;let l={};for(let e=0;e<o.length;e++){let t=o[e];if("literal"===t.kind){if(r[e]!==t.value)return null}else if("param"===t.kind)try{l[t.name]=decodeURIComponent(r[e]??"")}catch{l[t.name]=r[e]??""}}return l}function v(e){return e.segments.reduce((e,t)=>"literal"===t.kind?e+2:"param"===t.kind?e+1:e,0)}function y(e,t){let r,n={},a=-1;for(let o of t){let t=_(e,o);if(null===t)continue;let l=v(o);l>a&&(r=o,n=t,a=l)}return r?{route:r,params:n}:void 0}function b(e){let t=e.trim();return t&&"/"!==t?(t.startsWith("/")||(t="/"+t),t.endsWith("/")&&(t=t.slice(0,-1)),t):""}function x(){if(typeof document>"u")return"";let e=document.querySelector("base");if(!e)return"";let t=e.getAttribute("href")||"";try{return b(new URL(t,window.location.origin).pathname)}catch{return b(t)}}function S(t,r){let n=null==r?.base?x():b(r.base),i=r?.mode??"history",s="hash"===i,c=r?.scrollBehavior,h=new Map,m=!1;function v(e){return e?e.startsWith("/")?e:"/"+e:"/"}function w(e){let t=v(e||"/");if(n&&t.startsWith(n)){let e=t.slice(n.length);return""===e?"/":v(e)}return t}function _(){return s?function(){let e=window.location.hash||"";if(e.startsWith("#")&&(e=e.slice(1)),!e)return{pathname:"/",search:""};e.startsWith("/")||(e="/"+e);let t=e.indexOf("?"),r=-1===t?e:e.slice(0,t),n=-1===t?"":e.slice(t);return{pathname:w(r),search:n}}():{pathname:w(window.location.pathname||"/"),search:window.location.search||""}}function E(e,t){let r=function(e){let t=v(e);return n?(n+t).replace(/\/+/g,"/")||"/":t}(e)+p(t);return s?"#"+r:r}function k(e,t){return v(e)+p(t)}let S=_(),R=S.pathname,j=f(S.search),q=g(t),W=new Map;for(let e of q)e.name&&(W.has(e.name)&&console.warn(`[Nix Router] Duplicate route name: "${e.name}"`),W.set(e.name,e));let $=y(R,q),C=e.signal(R),O=e.signal($?.params??{}),N=e.signal(j);function L(e){window.scrollTo(e.left,e.top)}function P(e,t,r){if(c){let n=c(e,t,r);if(!1===n||null==n)return;return void L(n)}L(r??{left:0,top:0})}s?h.set(k(R,j),l()):history.replaceState(d(history.state,l()),"");let M=[],T=[],U=0;function B(e,t,r,n,a){let o=[...M];r&&o.push(r);let l=++U;if(0===o.length)return void n();let i=0;!function r(u){if(l!==U)return;if(!1===u)return void a?.();if("string"==typeof u)return void(u===e?n():K(u));if(i>=o.length)return void n();let s=o[i++](e,t);s instanceof Promise?s.then(r):r(s)}(void 0)}let D=!1;function I(e,t){let r=e.indexOf("?"),n=v((-1===r?e:e.slice(0,r))||"/"),a=-1===r?{}:f(e.slice(r)),o=t?{...a,...t}:a,l={};for(let[e,t]of Object.entries(o))null!=t&&!1!==t&&(l[e]=String(t));return{pathname:n,stringQuery:l}}function A(e,t){return"string"==typeof e?I(e,t):I(function(e){let t=W.get(e.name);if(!t)throw Error(`[Nix Router] No route with name "${e.name}"`);return"/"+t.segments.map(t=>{if("literal"===t.kind)return t.value;if("wildcard"===t.kind)return"";let r=e.params?.[t.name];if(null==r)throw Error(`[Nix Router] Missing param "${t.name}" for route "${e.name}"`);return encodeURIComponent(String(r))}).filter(Boolean).join("/")}(e),{...e.query??{},...t??{}})}o&&=(o(),null);let G=(e,t,r,n)=>{let a=C.value,o={...N.value},l=y(e,q);B(e,a,l?.route.beforeEnter,()=>{O.value=l?.params??{},N.value=t,C.value=e,P(e,a,r);for(let t of T)try{t(e,a)}catch{}},()=>n(a,o))};if(s){let e=()=>{if(m)return void(m=!1);let e=_(),t=f(e.search),r=h.get(k(e.pathname,t))??null;G(e.pathname,t,r,(e,t)=>{m=!0,window.location.hash=E(e,t).slice(1),queueMicrotask(()=>{m=!1})})};window.addEventListener("hashchange",e),o=()=>window.removeEventListener("hashchange",e)}else{let e=e=>{let t=_(),r=f(t.search),n=u(e.state??history.state);G(t.pathname,r,n,(e,t)=>{history.pushState(d({},l()),"",E(e,t))})};window.addEventListener("popstate",e),o=()=>window.removeEventListener("popstate",e)}function Q(e,t,r,n,a,o){o||function(e,t){let r=l();s?h.set(k(e,t),r):history.replaceState(d(history.state,r),"")}(r,n),O.value=a?.params??{},N.value=t,C.value=e;let i=E(e,t);if(s)h.set(k(e,t),{left:0,top:0}),o?history.replaceState(history.state,"",i):(m=!0,window.location.hash=i.slice(1),queueMicrotask(()=>{m=!1}));else{let e=d({},{left:0,top:0});o?history.replaceState(e,"",i):history.pushState(e,"",i)}P(e,r,null);for(let t of T)try{t(e,r)}catch{}}function K(e,t){D=!0;let{pathname:r,stringQuery:n}=A(e,t),a=C.value,o={...N.value},l=y(r,q);B(r,a,l?.route.beforeEnter,()=>Q(r,n,a,o,l,!1))}let X={current:C,params:O,query:N,base:n||"/",navigate:K,replace:function(e,t){D=!0;let{pathname:r,stringQuery:n}=A(e,t),a=C.value,o={...N.value},l=y(r,q);B(r,a,l?.route.beforeEnter,()=>Q(r,n,a,o,l,!0))},back:function(){history.back()},forward:function(){history.forward()},go:function(e){history.go(e)},isActive:function(e,t=!0){let r=C.value;return t?r===e:r===e||r.startsWith(e.endsWith("/")?e:e+"/")},resolve:function(e){let t=y(e,q);return t?{matched:!0,params:t.params,route:t.route.record}:{matched:!1,params:{},route:void 0}},beforeEach:function(e){return M.push(e),()=>{let t=M.indexOf(e);-1!==t&&M.splice(t,1)}},afterEach:function(e){return T.push(e),()=>{let t=T.indexOf(e);-1!==t&&T.splice(t,1)}},routes:t,_flat:q,_guards:M,_base:n,_mode:i};return a&&console.warn("[Nix] A router already exists. The previous router is being replaced. Only one router instance should be active at a time."),a=X,queueMicrotask(()=>{D||B(R,"",y(R,q)?.route.beforeEnter,()=>{},()=>{let e=E("/",{});s?(h.set(k("/",{}),{left:0,top:0}),history.replaceState(history.state,"",e)):history.replaceState(d({},{left:0,top:0}),"",e);let t=y("/",q);C.value="/",O.value=t?.params??{},N.value={},P("/",R,null)})}),X}function C(){return r.inject(i)||c()}function w(){o&&=(o(),null),a=null}function T(){if(!a)return null;let e=a.current.value,t=y(e,a._flat),r=t?.route.beforeEnter,n=a._guards.map((e,t)=>e.name||`beforeEach#${t+1}`);return r&&n.push(r.name||"beforeEnter"),{mode:a._mode,base:a._base||"/",currentPath:e,params:{...a.params.value},query:{...a.query.value},matchedPath:t?.route.fullPath??null,activeGuards:{globalCount:a._guards.length,hasRouteGuard:!!r,names:n}}}var E=class extends n.NixComponent{_depth;constructor(e=0){super(),this._depth=e}render(){let e=this._depth;return t.l`<div class="router-view">${()=>{let r=C(),n=y(r.current.value,r._flat);return n?e>=n.route.chain.length?t.l`<span></span>`:n.route.chain[e]():t.l`<div style="color:#f87171;padding:16px 0">
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./signals.cjs"),t=require("./template2.cjs"),n=require("./lifecycle.cjs"),r=require("./context.cjs");var i=r.createInjectionKey("nix:router"),a=null,o=null,s="__nix_scroll",c="__nix_pos";function l(){if(!a)throw Error("[Nix] No active router. Call createRouter() first.");return a}function u(){return{left:window.scrollX??window.pageXOffset??0,top:window.scrollY??window.pageYOffset??0}}function d(e){if(!e||"object"!=typeof e)return null;let t=e[s];if(!t||"object"!=typeof t)return null;let r=t.left,n=t.top;return"number"!=typeof r||"number"!=typeof n?null:{left:r,top:n}}function f(e){if(!e||"object"!=typeof e)return null;let t=e[c];return"number"==typeof t?t:null}function p(e,t,r){let n=e&&"object"==typeof e?{...e}:{};return n[s]={left:t.left,top:t.top},n[c]=r,n}function m(e){let t={};return new URLSearchParams(e).forEach((e,r)=>{t[r]=e}),t}function h(e){let t=new URLSearchParams;for(let[r,n]of Object.entries(e))null!=n&&!1!==n&&t.set(r,String(n));let r=t.toString();return r?"?"+r:""}function g(e){return"*"===e?[{kind:"wildcard"}]:e.split("/").filter(Boolean).map(e=>"*"===e?{kind:"wildcard"}:e.startsWith(":")?{kind:"param",name:e.slice(1)}:{kind:"literal",value:e})}function _(e,t){return"*"===t?""===e?"*":e+"/*":(e+(t.startsWith("/")?t:"/"+t)).replace(/\/+/g,"/")||"/"}function v(e,t="",r=[]){let n=[];for(let o of e){let e=_(t,o.path),a=[...r,o.component],i=g(e);n.push({fullPath:e,segments:i,chain:a,name:o.name,meta:o.meta,beforeEnter:o.beforeEnter,record:o}),o.children?.length&&n.push(...v(o.children,e,a))}return n}function y(e,t){let r=e.split("/").filter(Boolean),n=t.segments;if(1===n.length&&"wildcard"===n[0].kind)return{};let o=n.length>0&&"wildcard"===n[n.length-1].kind,a=o?n.slice(0,-1):n;if(o){if(r.length<a.length)return null}else if(r.length!==a.length)return null;let i={};for(let e=0;e<a.length;e++){let t=a[e];if("literal"===t.kind){if(r[e]!==t.value)return null}else if("param"===t.kind)try{i[t.name]=decodeURIComponent(r[e]??"")}catch{i[t.name]=r[e]??""}}return i}function b(e){return e.segments.reduce((e,t)=>"literal"===t.kind?e+2:"param"===t.kind?e+1:e,0)}function x(e,t){let r,n={},o=-1;for(let a of t){let t=y(e,a);if(null===t)continue;let i=b(a);i>o&&(r=a,n=t,o=i)}return r?{route:r,params:n}:void 0}function S(e){let t=e.trim();return t&&"/"!==t?(t.startsWith("/")||(t="/"+t),t.endsWith("/")&&(t=t.slice(0,-1)),t):""}function ee(){if(typeof document>"u")return"";let e=document.querySelector("base");if(!e)return"";let t=e.getAttribute("href")||"";try{return S(new URL(t,window.location.origin).pathname)}catch{return S(t)}}function C(t,r){let n=null==r?.base?ee():S(r.base),i=r?.mode??"history",l="hash"===i,s=r?.scrollBehavior,c=new Map,g=!1;function w(e){return e?e.startsWith("/")?e:"/"+e:"/"}function y(e){let t=w(e||"/");if(n&&t.startsWith(n)){let e=t.slice(n.length);return""===e?"/":w(e)}return t}function b(){return l?function(){let e=window.location.hash||"";if(e.startsWith("#")&&(e=e.slice(1)),!e)return{pathname:"/",search:""};e.startsWith("/")||(e="/"+e);let t=e.indexOf("?"),r=-1===t?e:e.slice(0,t),n=-1===t?"":e.slice(t);return{pathname:y(r),search:n}}():{pathname:y(window.location.pathname||"/"),search:window.location.search||""}}function _(e,t){let r=function(e){let t=w(e);return n?(n+t).replace(/\/+/g,"/")||"/":t}(e)+h(t);return l?"#"+r:r}function k(e,t){return w(e)+h(t)}let E=b(),R=E.pathname,j=m(E.search),q=v(t),O=new Map;for(let e of q)e.name&&(O.has(e.name)&&console.warn(`[Nix Router] Duplicate route name: "${e.name}"`),O.set(e.name,e));let W=x(R,q),$=e.signal(R),N=e.signal(W?.params??{}),C=e.signal(j),L=f(history.state)??0,P=e.signal({action:"initial",direction:"none"}),M=e.signal(L>0);function B(e){window.scrollTo(e.left,e.top)}function T(e,t,r){if(s){let n=s(e,t,r);if(!n)return;return void B(n)}B(r??{left:0,top:0})}l?c.set(k(R,j),u()):history.replaceState(p(history.state,u(),L),"");let U=[],D=[],G=0;function I(e,t,r,n,o){let a=[...U];r&&a.push(r);let i=++G;if(0===a.length)return void n();let l=0;!function r(u){if(i!==G)return;if(!1===u)return void o?.();if("string"==typeof u)return void(u===e?n():z(u));if(l>=a.length)return void n();let s=a[l++](e,t);s instanceof Promise?s.then(r):r(s)}(void 0)}let A=!1;function Q(e,t){let r=e.indexOf("?"),n=w((-1===r?e:e.slice(0,r))||"/"),o=-1===r?{}:m(e.slice(r)),a=t?{...o,...t}:o,i={};for(let[e,t]of Object.entries(a))null!=t&&!1!==t&&(i[e]=String(t));return{pathname:n,stringQuery:i}}function K(e,t){return"string"==typeof e?Q(e,t?.query):Q(function(e){let t=O.get(e.name);if(!t)throw Error(`[Nix Router] No route with name "${e.name}"`);return"/"+t.segments.map(t=>{if("literal"===t.kind)return t.value;if("wildcard"===t.kind)return"";let r=e.params?.[t.name];if(null==r)throw Error(`[Nix Router] Missing param "${t.name}" for route "${e.name}"`);return encodeURIComponent(String(r))}).filter(Boolean).join("/")}(e),{...e.query??{},...t?.query??{}})}o&&=(o(),null);let X,Y=(e,t,r,n,o)=>{let a=$.value,i={...C.value},l=x(e,q),u="none";null!=n&&(n<L?u="back":n>L&&(u="forward")),I(e,a,l?.route.beforeEnter,()=>{null!=n&&(L=n);let o=X;X=void 0,P.value={action:"pop",direction:u,animation:o},N.value=l?.params??{},C.value=t,$.value=e,M.value=L>0,T(e,a,r);for(let t of D)try{t(e,a)}catch{}},()=>o(a,i))};if(l){let e=()=>{if(g)return void(g=!1);let e=b(),t=m(e.search),r=c.get(k(e.pathname,t))??null;Y(e.pathname,t,r,null,(e,t)=>{g=!0,window.location.hash=_(e,t).slice(1),queueMicrotask(()=>{g=!1})})};window.addEventListener("hashchange",e),o=()=>window.removeEventListener("hashchange",e)}else{let e=e=>{let t=b(),r=m(t.search),n=d(e.state??history.state),o=f(e.state??history.state);Y(t.pathname,r,n,o,(e,t)=>{history.pushState(p({},u(),L),"",_(e,t))})};window.addEventListener("popstate",e),o=()=>window.removeEventListener("popstate",e)}function V(e,t,r,n,o,a,i){i||(function(e,t){let r=u();l?c.set(k(e,t),r):history.replaceState(p(history.state,r,L),"")}(r,n),L+=1),P.value=a,N.value=o?.params??{},C.value=t,$.value=e,M.value=L>0;let s=_(e,t);if(l)c.set(k(e,t),{left:0,top:0}),i?history.replaceState(history.state,"",s):(g=!0,window.location.hash=s.slice(1),queueMicrotask(()=>{g=!1}));else{let e=p({},{left:0,top:0},L);i?history.replaceState(e,"",s):history.pushState(e,"",s)}T(e,r,null);for(let t of D)try{t(e,r)}catch{}}function z(e,t){A=!0;let{pathname:r,stringQuery:n}=K(e,t),o=$.value,a={...C.value},i=x(r,q),l={action:"push",direction:t?.direction??"forward",animation:t?.animation};I(r,o,i?.route.beforeEnter,()=>V(r,n,o,a,i,l,!1))}let F={current:$,params:N,query:C,intent:P,canGoBack:M,base:n||"/",navigate:z,replace:function(e,t){A=!0;let{pathname:r,stringQuery:n}=K(e,t),o=$.value,a={...C.value},i=x(r,q),l={action:"replace",direction:t?.direction??"root",animation:t?.animation};I(r,o,i?.route.beforeEnter,()=>V(r,n,o,a,i,l,!0))},back:function(e){void 0!==e&&(X=e),history.back()},forward:function(e){void 0!==e&&(X=e),history.forward()},go:function(e){history.go(e)},isActive:function(e,t=!0){let r=$.value;return t?r===e:r===e||r.startsWith(e.endsWith("/")?e:e+"/")},resolve:function(e){let t=x(e,q);return t?{matched:!0,params:t.params,route:t.route.record}:{matched:!1,params:{},route:void 0}},beforeEach:function(e){return U.push(e),()=>{let t=U.indexOf(e);-1!==t&&U.splice(t,1)}},afterEach:function(e){return D.push(e),()=>{let t=D.indexOf(e);-1!==t&&D.splice(t,1)}},routes:t,_flat:q,_guards:U,_base:n,_mode:i};return a&&console.warn("[Nix] A router already exists. The previous router is being replaced. Only one router instance should be active at a time."),a=F,queueMicrotask(()=>{A||I(R,"",x(R,q)?.route.beforeEnter,()=>{},()=>{let e=_("/",{});l?(c.set(k("/",{}),{left:0,top:0}),history.replaceState(history.state,"",e)):history.replaceState(p({},{left:0,top:0},L),"",e);let t=x("/",q);P.value={action:"replace",direction:"root"},$.value="/",N.value=t?.params??{},C.value={},M.value=L>0,T("/",R,null)})}),F}function w(){return r.inject(i)||l()}function T(){o&&=(o(),null),a=null}var E=class extends n.NixComponent{_depth;constructor(e=0){super(),this._depth=e}render(){let e=this._depth;return t.l`<div class="router-view">${()=>{let r=w(),n=x(r.current.value,r._flat);return n?e>=n.route.chain.length?t.l`<span></span>`:n.route.chain[e]():t.l`<div style="color:#f87171;padding:16px 0">
2
2
  404 — Route not found: <strong>${r.current.value}</strong>
3
- </div>`}}</div>`}},D=class extends n.NixComponent{_to;_label;constructor(e,t){super(),this._to=e,this._label=t}render(){let e=this._to,r=this._label,n=C(),a=e.startsWith("/")?e:"/"+e,o=(n._base?n._base+a:a).replace(/\/+/g,"/");return t.l`<a
4
- href=${"hash"===n._mode?"#"+o:o}
3
+ </div>`}}</div>`}},D=class extends n.NixComponent{_to;_label;constructor(e,t){super(),this._to=e,this._label=t}render(){let e=this._to,r=this._label,n=w(),o=e.startsWith("/")?e:"/"+e,a=(n._base?n._base+o:o).replace(/\/+/g,"/");return t.l`<a
4
+ href=${"hash"===n._mode?"#"+a:a}
5
5
  style=${()=>n.current.value===e?"color:#38bdf8;font-weight:700;text-decoration:none;cursor:pointer;padding:4px 10px;border-radius:4px;background:#0c2a3a":"color:#a3a3a3;text-decoration:none;cursor:pointer;padding:4px 10px;border-radius:4px"}
6
6
  @click=${t=>{t.preventDefault(),n.navigate(e)}}
7
- >${r}</a>`}};exports.Link=D,exports.RouterKey=i,exports.RouterView=E,exports._debugGetRouterInternal=T,exports._resetRouter=w,exports.createRouter=S,exports.nixRouter=C;
7
+ >${r}</a>`}};function O(){if(!a)return null;let e=a.current.value,t=x(e,a._flat),r=t?.route.beforeEnter,n=a._guards.map((e,t)=>e.name||`beforeEach#${t+1}`);return r&&n.push(r.name||"beforeEnter"),{mode:a._mode,base:a._base||"/",currentPath:e,params:{...a.params.value},query:{...a.query.value},matchedPath:t?.route.fullPath??null,activeGuards:{globalCount:a._guards.length,hasRouteGuard:!!r,names:n}}}exports.Link=D,exports.RouterKey=i,exports.RouterView=E,exports._debugGetRouterInternal=O,exports._resetRouter=T,exports.createRouter=C,exports.nixRouter=w;
@@ -1,7 +1,7 @@
1
- import{signal as e}from"./signals.js";import{l as t}from"./template2.js";import{NixComponent as n}from"./lifecycle.js";import{createInjectionKey as r,inject as i}from"./context.js";var a=r("nix:router"),o=null,s=null,c="__nix_scroll";function l(){if(!o)throw Error("[Nix] No active router. Call createRouter() first.");return o}function u(){return{left:window.scrollX??window.pageXOffset??0,top:window.scrollY??window.pageYOffset??0}}function d(e){if(!e||"object"!=typeof e)return null;let t=e[c];if(!t||"object"!=typeof t)return null;let r=t.left,n=t.top;return"number"!=typeof r||"number"!=typeof n?null:{left:r,top:n}}function f(e,t){let r=e&&"object"==typeof e?{...e}:{};return r[c]={left:t.left,top:t.top},r}function p(e){let t={};return new URLSearchParams(e).forEach((e,r)=>{t[r]=e}),t}function m(e){let t=new URLSearchParams;for(let[r,n]of Object.entries(e))null!=n&&!1!==n&&t.set(r,String(n));let r=t.toString();return r?"?"+r:""}function h(e){return"*"===e?[{kind:"wildcard"}]:e.split("/").filter(Boolean).map(e=>"*"===e?{kind:"wildcard"}:e.startsWith(":")?{kind:"param",name:e.slice(1)}:{kind:"literal",value:e})}function g(e,t){return"*"===t?""===e?"*":e+"/*":(e+(t.startsWith("/")?t:"/"+t)).replace(/\/+/g,"/")||"/"}function _(e,t="",r=[]){let n=[];for(let o of e){let e=g(t,o.path),a=[...r,o.component],l=h(e);n.push({fullPath:e,segments:l,chain:a,name:o.name,meta:o.meta,beforeEnter:o.beforeEnter,record:o}),o.children?.length&&n.push(..._(o.children,e,a))}return n}function v(e,t){let r=e.split("/").filter(Boolean),n=t.segments;if(1===n.length&&"wildcard"===n[0].kind)return{};let o=n.length>0&&"wildcard"===n[n.length-1].kind,a=o?n.slice(0,-1):n;if(o){if(r.length<a.length)return null}else if(r.length!==a.length)return null;let l={};for(let e=0;e<a.length;e++){let t=a[e];if("literal"===t.kind){if(r[e]!==t.value)return null}else if("param"===t.kind)try{l[t.name]=decodeURIComponent(r[e]??"")}catch{l[t.name]=r[e]??""}}return l}function y(e){return e.segments.reduce((e,t)=>"literal"===t.kind?e+2:"param"===t.kind?e+1:e,0)}function b(e,t){let r,n={},o=-1;for(let a of t){let t=v(e,a);if(null===t)continue;let l=y(a);l>o&&(r=a,n=t,o=l)}return r?{route:r,params:n}:void 0}function x(e){let t=e.trim();return t&&"/"!==t?(t.startsWith("/")||(t="/"+t),t.endsWith("/")&&(t=t.slice(0,-1)),t):""}function S(){if(typeof document>"u")return"";let e=document.querySelector("base");if(!e)return"";let t=e.getAttribute("href")||"";try{return x(new URL(t,window.location.origin).pathname)}catch{return x(t)}}function C(t,r){let n=null==r?.base?S():x(r.base),a=r?.mode??"history",l="hash"===a,i=r?.scrollBehavior,c=new Map,h=!1;function g(e){return e?e.startsWith("/")?e:"/"+e:"/"}function v(e){let t=g(e||"/");if(n&&t.startsWith(n)){let e=t.slice(n.length);return""===e?"/":g(e)}return t}function w(){return l?function(){let e=window.location.hash||"";if(e.startsWith("#")&&(e=e.slice(1)),!e)return{pathname:"/",search:""};e.startsWith("/")||(e="/"+e);let t=e.indexOf("?"),r=-1===t?e:e.slice(0,t),n=-1===t?"":e.slice(t);return{pathname:v(r),search:n}}():{pathname:v(window.location.pathname||"/"),search:window.location.search||""}}function y(e,t){let r=function(e){let t=g(e);return n?(n+t).replace(/\/+/g,"/")||"/":t}(e)+m(t);return l?"#"+r:r}function E(e,t){return g(e)+m(t)}let k=w(),R=k.pathname,j=p(k.search),O=_(t),W=new Map;for(let e of O)e.name&&(W.has(e.name)&&console.warn(`[Nix Router] Duplicate route name: "${e.name}"`),W.set(e.name,e));let $=b(R,O),q=e(R),L=e($?.params??{}),N=e(j);function C(e){window.scrollTo(e.left,e.top)}function P(e,t,r){if(i){let n=i(e,t,r);if(!1===n||null==n)return;return void C(n)}C(r??{left:0,top:0})}l?c.set(E(R,j),u()):history.replaceState(f(history.state,u()),"");let M=[],U=[],B=0;function D(e,t,r,n,o){let a=[...M];r&&a.push(r);let l=++B;if(0===a.length)return void n();let i=0;!function r(u){if(l!==B)return;if(!1===u)return void o?.();if("string"==typeof u)return void(u===e?n():K(u));if(i>=a.length)return void n();let s=a[i++](e,t);s instanceof Promise?s.then(r):r(s)}(void 0)}let I=!1;function T(e,t){let r=e.indexOf("?"),n=g((-1===r?e:e.slice(0,r))||"/"),o=-1===r?{}:p(e.slice(r)),a=t?{...o,...t}:o,l={};for(let[e,t]of Object.entries(a))null!=t&&!1!==t&&(l[e]=String(t));return{pathname:n,stringQuery:l}}function A(e,t){return"string"==typeof e?T(e,t):T(function(e){let t=W.get(e.name);if(!t)throw Error(`[Nix Router] No route with name "${e.name}"`);return"/"+t.segments.map(t=>{if("literal"===t.kind)return t.value;if("wildcard"===t.kind)return"";let r=e.params?.[t.name];if(null==r)throw Error(`[Nix Router] Missing param "${t.name}" for route "${e.name}"`);return encodeURIComponent(String(r))}).filter(Boolean).join("/")}(e),{...e.query??{},...t??{}})}s&&=(s(),null);let G=(e,t,r,n)=>{let o=q.value,a={...N.value},l=b(e,O);D(e,o,l?.route.beforeEnter,()=>{L.value=l?.params??{},N.value=t,q.value=e,P(e,o,r);for(let t of U)try{t(e,o)}catch{}},()=>n(o,a))};if(l){let e=()=>{if(h)return void(h=!1);let e=w(),t=p(e.search),r=c.get(E(e.pathname,t))??null;G(e.pathname,t,r,(e,t)=>{h=!0,window.location.hash=y(e,t).slice(1),queueMicrotask(()=>{h=!1})})};window.addEventListener("hashchange",e),s=()=>window.removeEventListener("hashchange",e)}else{let e=e=>{let t=w(),r=p(t.search),n=d(e.state??history.state);G(t.pathname,r,n,(e,t)=>{history.pushState(f({},u()),"",y(e,t))})};window.addEventListener("popstate",e),s=()=>window.removeEventListener("popstate",e)}function Q(e,t,r,n,o,a){a||function(e,t){let r=u();l?c.set(E(e,t),r):history.replaceState(f(history.state,r),"")}(r,n),L.value=o?.params??{},N.value=t,q.value=e;let i=y(e,t);if(l)c.set(E(e,t),{left:0,top:0}),a?history.replaceState(history.state,"",i):(h=!0,window.location.hash=i.slice(1),queueMicrotask(()=>{h=!1}));else{let e=f({},{left:0,top:0});a?history.replaceState(e,"",i):history.pushState(e,"",i)}P(e,r,null);for(let t of U)try{t(e,r)}catch{}}function K(e,t){I=!0;let{pathname:r,stringQuery:n}=A(e,t),o=q.value,a={...N.value},l=b(r,O);D(r,o,l?.route.beforeEnter,()=>Q(r,n,o,a,l,!1))}let X={current:q,params:L,query:N,base:n||"/",navigate:K,replace:function(e,t){I=!0;let{pathname:r,stringQuery:n}=A(e,t),o=q.value,a={...N.value},l=b(r,O);D(r,o,l?.route.beforeEnter,()=>Q(r,n,o,a,l,!0))},back:function(){history.back()},forward:function(){history.forward()},go:function(e){history.go(e)},isActive:function(e,t=!0){let r=q.value;return t?r===e:r===e||r.startsWith(e.endsWith("/")?e:e+"/")},resolve:function(e){let t=b(e,O);return t?{matched:!0,params:t.params,route:t.route.record}:{matched:!1,params:{},route:void 0}},beforeEach:function(e){return M.push(e),()=>{let t=M.indexOf(e);-1!==t&&M.splice(t,1)}},afterEach:function(e){return U.push(e),()=>{let t=U.indexOf(e);-1!==t&&U.splice(t,1)}},routes:t,_flat:O,_guards:M,_base:n,_mode:a};return o&&console.warn("[Nix] A router already exists. The previous router is being replaced. Only one router instance should be active at a time."),o=X,queueMicrotask(()=>{I||D(R,"",b(R,O)?.route.beforeEnter,()=>{},()=>{let e=y("/",{});l?(c.set(E("/",{}),{left:0,top:0}),history.replaceState(history.state,"",e)):history.replaceState(f({},{left:0,top:0}),"",e);let t=b("/",O);q.value="/",L.value=t?.params??{},N.value={},P("/",R,null)})}),X}function w(){return i(a)||l()}function T(){s&&=(s(),null),o=null}function E(){if(!o)return null;let e=o.current.value,t=b(e,o._flat),r=t?.route.beforeEnter,n=o._guards.map((e,t)=>e.name||`beforeEach#${t+1}`);return r&&n.push(r.name||"beforeEnter"),{mode:o._mode,base:o._base||"/",currentPath:e,params:{...o.params.value},query:{...o.query.value},matchedPath:t?.route.fullPath??null,activeGuards:{globalCount:o._guards.length,hasRouteGuard:!!r,names:n}}}var D=class extends n{_depth;constructor(e=0){super(),this._depth=e}render(){let e=this._depth;return t`<div class="router-view">${()=>{let r=w(),n=b(r.current.value,r._flat);return n?e>=n.route.chain.length?t`<span></span>`:n.route.chain[e]():t`<div style="color:#f87171;padding:16px 0">
1
+ import{signal as e}from"./signals.js";import{l as t}from"./template2.js";import{NixComponent as n}from"./lifecycle.js";import{createInjectionKey as r,inject as i}from"./context.js";var a=r("nix:router"),o=null,s=null,c="__nix_scroll",l="__nix_pos";function u(){if(!o)throw Error("[Nix] No active router. Call createRouter() first.");return o}function d(){return{left:window.scrollX??window.pageXOffset??0,top:window.scrollY??window.pageYOffset??0}}function ee(e){if(!e||"object"!=typeof e)return null;let t=e[c];if(!t||"object"!=typeof t)return null;let r=t.left,n=t.top;return"number"!=typeof r||"number"!=typeof n?null:{left:r,top:n}}function f(e){if(!e||"object"!=typeof e)return null;let t=e[l];return"number"==typeof t?t:null}function p(e,t,r){let n=e&&"object"==typeof e?{...e}:{};return n[c]={left:t.left,top:t.top},n[l]=r,n}function m(e){let t={};return new URLSearchParams(e).forEach((e,r)=>{t[r]=e}),t}function h(e){let t=new URLSearchParams;for(let[r,n]of Object.entries(e))null!=n&&!1!==n&&t.set(r,String(n));let r=t.toString();return r?"?"+r:""}function g(e){return"*"===e?[{kind:"wildcard"}]:e.split("/").filter(Boolean).map(e=>"*"===e?{kind:"wildcard"}:e.startsWith(":")?{kind:"param",name:e.slice(1)}:{kind:"literal",value:e})}function _(e,t){return"*"===t?""===e?"*":e+"/*":(e+(t.startsWith("/")?t:"/"+t)).replace(/\/+/g,"/")||"/"}function v(e,t="",r=[]){let n=[];for(let o of e){let e=_(t,o.path),a=[...r,o.component],i=g(e);n.push({fullPath:e,segments:i,chain:a,name:o.name,meta:o.meta,beforeEnter:o.beforeEnter,record:o}),o.children?.length&&n.push(...v(o.children,e,a))}return n}function y(e,t){let r=e.split("/").filter(Boolean),n=t.segments;if(1===n.length&&"wildcard"===n[0].kind)return{};let o=n.length>0&&"wildcard"===n[n.length-1].kind,a=o?n.slice(0,-1):n;if(o){if(r.length<a.length)return null}else if(r.length!==a.length)return null;let i={};for(let e=0;e<a.length;e++){let t=a[e];if("literal"===t.kind){if(r[e]!==t.value)return null}else if("param"===t.kind)try{i[t.name]=decodeURIComponent(r[e]??"")}catch{i[t.name]=r[e]??""}}return i}function b(e){return e.segments.reduce((e,t)=>"literal"===t.kind?e+2:"param"===t.kind?e+1:e,0)}function x(e,t){let r,n={},o=-1;for(let a of t){let t=y(e,a);if(null===t)continue;let i=b(a);i>o&&(r=a,n=t,o=i)}return r?{route:r,params:n}:void 0}function S(e){let t=e.trim();return t&&"/"!==t?(t.startsWith("/")||(t="/"+t),t.endsWith("/")&&(t=t.slice(0,-1)),t):""}function te(){if(typeof document>"u")return"";let e=document.querySelector("base");if(!e)return"";let t=e.getAttribute("href")||"";try{return S(new URL(t,window.location.origin).pathname)}catch{return S(t)}}function C(t,r){let n=null==r?.base?te():S(r.base),a=r?.mode??"history",i="hash"===a,l=r?.scrollBehavior,u=new Map,c=!1;function g(e){return e?e.startsWith("/")?e:"/"+e:"/"}function w(e){let t=g(e||"/");if(n&&t.startsWith(n)){let e=t.slice(n.length);return""===e?"/":g(e)}return t}function y(){return i?function(){let e=window.location.hash||"";if(e.startsWith("#")&&(e=e.slice(1)),!e)return{pathname:"/",search:""};e.startsWith("/")||(e="/"+e);let t=e.indexOf("?"),r=-1===t?e:e.slice(0,t),n=-1===t?"":e.slice(t);return{pathname:w(r),search:n}}():{pathname:w(window.location.pathname||"/"),search:window.location.search||""}}function b(e,t){let r=function(e){let t=g(e);return n?(n+t).replace(/\/+/g,"/")||"/":t}(e)+h(t);return i?"#"+r:r}function _(e,t){return g(e)+h(t)}let k=y(),E=k.pathname,R=m(k.search),j=v(t),O=new Map;for(let e of j)e.name&&(O.has(e.name)&&console.warn(`[Nix Router] Duplicate route name: "${e.name}"`),O.set(e.name,e));let W=x(E,j),$=e(E),q=e(W?.params??{}),L=e(R),N=f(history.state)??0,C=e({action:"initial",direction:"none"}),P=e(N>0);function M(e){window.scrollTo(e.left,e.top)}function B(e,t,r){if(l){let n=l(e,t,r);if(!n)return;return void M(n)}M(r??{left:0,top:0})}i?u.set(_(E,R),d()):history.replaceState(p(history.state,d(),N),"");let U=[],D=[],G=0;function I(e,t,r,n,o){let a=[...U];r&&a.push(r);let i=++G;if(0===a.length)return void n();let l=0;!function r(u){if(i!==G)return;if(!1===u)return void o?.();if("string"==typeof u)return void(u===e?n():V(u));if(l>=a.length)return void n();let s=a[l++](e,t);s instanceof Promise?s.then(r):r(s)}(void 0)}let T=!1;function A(e,t){let r=e.indexOf("?"),n=g((-1===r?e:e.slice(0,r))||"/"),o=-1===r?{}:m(e.slice(r)),a=t?{...o,...t}:o,i={};for(let[e,t]of Object.entries(a))null!=t&&!1!==t&&(i[e]=String(t));return{pathname:n,stringQuery:i}}function Q(e,t){return"string"==typeof e?A(e,t?.query):A(function(e){let t=O.get(e.name);if(!t)throw Error(`[Nix Router] No route with name "${e.name}"`);return"/"+t.segments.map(t=>{if("literal"===t.kind)return t.value;if("wildcard"===t.kind)return"";let r=e.params?.[t.name];if(null==r)throw Error(`[Nix Router] Missing param "${t.name}" for route "${e.name}"`);return encodeURIComponent(String(r))}).filter(Boolean).join("/")}(e),{...e.query??{},...t?.query??{}})}s&&=(s(),null);let K,X=(e,t,r,n,o)=>{let a=$.value,i={...L.value},l=x(e,j),u="none";null!=n&&(n<N?u="back":n>N&&(u="forward")),I(e,a,l?.route.beforeEnter,()=>{null!=n&&(N=n);let o=K;K=void 0,C.value={action:"pop",direction:u,animation:o},q.value=l?.params??{},L.value=t,$.value=e,P.value=N>0,B(e,a,r);for(let t of D)try{t(e,a)}catch{}},()=>o(a,i))};if(i){let e=()=>{if(c)return void(c=!1);let e=y(),t=m(e.search),r=u.get(_(e.pathname,t))??null;X(e.pathname,t,r,null,(e,t)=>{c=!0,window.location.hash=b(e,t).slice(1),queueMicrotask(()=>{c=!1})})};window.addEventListener("hashchange",e),s=()=>window.removeEventListener("hashchange",e)}else{let e=e=>{let t=y(),r=m(t.search),n=ee(e.state??history.state),o=f(e.state??history.state);X(t.pathname,r,n,o,(e,t)=>{history.pushState(p({},d(),N),"",b(e,t))})};window.addEventListener("popstate",e),s=()=>window.removeEventListener("popstate",e)}function Y(e,t,r,n,o,a,l){l||(function(e,t){let r=d();i?u.set(_(e,t),r):history.replaceState(p(history.state,r,N),"")}(r,n),N+=1),C.value=a,q.value=o?.params??{},L.value=t,$.value=e,P.value=N>0;let s=b(e,t);if(i)u.set(_(e,t),{left:0,top:0}),l?history.replaceState(history.state,"",s):(c=!0,window.location.hash=s.slice(1),queueMicrotask(()=>{c=!1}));else{let e=p({},{left:0,top:0},N);l?history.replaceState(e,"",s):history.pushState(e,"",s)}B(e,r,null);for(let t of D)try{t(e,r)}catch{}}function V(e,t){T=!0;let{pathname:r,stringQuery:n}=Q(e,t),o=$.value,a={...L.value},i=x(r,j),l={action:"push",direction:t?.direction??"forward",animation:t?.animation};I(r,o,i?.route.beforeEnter,()=>Y(r,n,o,a,i,l,!1))}let z={current:$,params:q,query:L,intent:C,canGoBack:P,base:n||"/",navigate:V,replace:function(e,t){T=!0;let{pathname:r,stringQuery:n}=Q(e,t),o=$.value,a={...L.value},i=x(r,j),l={action:"replace",direction:t?.direction??"root",animation:t?.animation};I(r,o,i?.route.beforeEnter,()=>Y(r,n,o,a,i,l,!0))},back:function(e){void 0!==e&&(K=e),history.back()},forward:function(e){void 0!==e&&(K=e),history.forward()},go:function(e){history.go(e)},isActive:function(e,t=!0){let r=$.value;return t?r===e:r===e||r.startsWith(e.endsWith("/")?e:e+"/")},resolve:function(e){let t=x(e,j);return t?{matched:!0,params:t.params,route:t.route.record}:{matched:!1,params:{},route:void 0}},beforeEach:function(e){return U.push(e),()=>{let t=U.indexOf(e);-1!==t&&U.splice(t,1)}},afterEach:function(e){return D.push(e),()=>{let t=D.indexOf(e);-1!==t&&D.splice(t,1)}},routes:t,_flat:j,_guards:U,_base:n,_mode:a};return o&&console.warn("[Nix] A router already exists. The previous router is being replaced. Only one router instance should be active at a time."),o=z,queueMicrotask(()=>{T||I(E,"",x(E,j)?.route.beforeEnter,()=>{},()=>{let e=b("/",{});i?(u.set(_("/",{}),{left:0,top:0}),history.replaceState(history.state,"",e)):history.replaceState(p({},{left:0,top:0},N),"",e);let t=x("/",j);C.value={action:"replace",direction:"root"},$.value="/",q.value=t?.params??{},L.value={},P.value=N>0,B("/",E,null)})}),z}function w(){return i(a)||u()}function T(){s&&=(s(),null),o=null}var E=class extends n{_depth;constructor(e=0){super(),this._depth=e}render(){let e=this._depth;return t`<div class="router-view">${()=>{let r=w(),n=x(r.current.value,r._flat);return n?e>=n.route.chain.length?t`<span></span>`:n.route.chain[e]():t`<div style="color:#f87171;padding:16px 0">
2
2
  404 — Route not found: <strong>${r.current.value}</strong>
3
- </div>`}}</div>`}},O=class extends n{_to;_label;constructor(e,t){super(),this._to=e,this._label=t}render(){let e=this._to,r=this._label,n=w(),o=e.startsWith("/")?e:"/"+e,a=(n._base?n._base+o:o).replace(/\/+/g,"/");return t`<a
3
+ </div>`}}</div>`}},D=class extends n{_to;_label;constructor(e,t){super(),this._to=e,this._label=t}render(){let e=this._to,r=this._label,n=w(),o=e.startsWith("/")?e:"/"+e,a=(n._base?n._base+o:o).replace(/\/+/g,"/");return t`<a
4
4
  href=${"hash"===n._mode?"#"+a:a}
5
5
  style=${()=>n.current.value===e?"color:#38bdf8;font-weight:700;text-decoration:none;cursor:pointer;padding:4px 10px;border-radius:4px;background:#0c2a3a":"color:#a3a3a3;text-decoration:none;cursor:pointer;padding:4px 10px;border-radius:4px"}
6
6
  @click=${t=>{t.preventDefault(),n.navigate(e)}}
7
- >${r}</a>`}};export{O as Link,a as RouterKey,D as RouterView,E as _debugGetRouterInternal,T as _resetRouter,C as createRouter,w as nixRouter};
7
+ >${r}</a>`}};function O(){if(!o)return null;let e=o.current.value,t=x(e,o._flat),r=t?.route.beforeEnter,n=o._guards.map((e,t)=>e.name||`beforeEach#${t+1}`);return r&&n.push(r.name||"beforeEnter"),{mode:o._mode,base:o._base||"/",currentPath:e,params:{...o.params.value},query:{...o.query.value},matchedPath:t?.route.fullPath??null,activeGuards:{globalCount:o._guards.length,hasRouteGuard:!!r,names:n}}}export{D as Link,a as RouterKey,E as RouterView,O as _debugGetRouterInternal,T as _resetRouter,C as createRouter,w as nixRouter};
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./signals.cjs");var t=class extends e.Signal{label;constructor(e,t="ReadonlySignal"){super(e.peek()),this.label=t,Object.defineProperty(this,"value",{get:()=>e.value,set:()=>{throw Error(`[Nix] "${this.label}" is read-only.`)},configurable:!1}),this.update=()=>{throw Error(`[Nix] "${this.label}" is read-only.`)},this.dispose=()=>{throw Error(`[Nix] Cannot dispose "${this.label}" directly.`)}}},n=new Set(["$id","$state","$stateSignal","$reset","$patch","$watch","$dispose"]);function r(e){if("__proto__"===e||"constructor"===e||"prototype"===e)throw Error(`[Nix] Store key "${e}" is not allowed for security reasons.`);if(n.has(e))throw Error(`[Nix] Store key "${e}" is reserved.`)}function i(e,t){return!n.has(e)||(console.warn(`[Nix] Store ${t} "${e}" is reserved and will be ignored.`),!1)}function a(e,r){return new t(e,r)}function o(t,o={}){let{name:l="store",actions:s,getters:c,plugins:f=[]}=o,u=Object.keys(t),d={};for(let o of u)r(o),d[o]=e.signal(t[o]);let g,b=d,$=e.computed(()=>{let e={};for(let t of u)e[t]=d[t].value;return e}),h=a($,`store "${l}".$stateSignal`);try{g=structuredClone(t)}catch(e){throw Error(`[Nix] Store "${l}" initialState contains non-serializable data (functions, DOM nodes, Symbols, or WeakRefs). Remove these before creating the store. Original error: ${e}`)}let p=Object.assign(Object.create(null),b,{$reset:function(){e.batch(()=>{for(let e of u)d[e].value=g[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($,t,r)}});Object.defineProperty(p,"$id",{value:l,writable:!1,enumerable:!1,configurable:!1}),Object.defineProperty(p,"$state",{get:()=>$.value,enumerable:!0,configurable:!1}),Object.defineProperty(p,"$stateSignal",{value:h,writable:!1,enumerable:!1,configurable:!1});let y=new Set([...u,...Array.from(n)]);if(s){let e=s(b);for(let t of Object.keys(e))if(i(t,"action")){if(y.has(t)){console.warn(`[Nix] Store "${l}": action "${t}" collides with an existing signal or getter and will be ignored.`);continue}y.add(t),p[t]=e[t]}}if(c){let t=c(b);for(let r of Object.keys(t)){if(!i(r,"getter"))continue;if(y.has(r)){console.warn(`[Nix] Store "${l}": getter "${r}" collides with an existing signal or action and will be ignored.`);continue}let o=t[r];if(!(o instanceof e.Signal))throw TypeError(`[Nix] Store "${l}": getter "${r}" must return a Signal (wrap it with computed()). Got: ${typeof o}`);y.add(r),p[r]=a(o,`getter "${r}" in store "${l}"`)}}let w=[()=>$.dispose()];for(let e of f)try{let t=e(p);"function"==typeof t&&w.push(t)}catch(e){console.error(`[Nix] Plugin initialization failed for store "${l}":`,e)}return p.$dispose=()=>{for(let e of w)e()},p}exports.ReadonlySignal=t,exports.createStore=o;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./signals.cjs");var t=class extends e.Signal{label;constructor(e,t="ReadonlySignal"){super(e.peek()),this.label=t,Object.defineProperty(this,"value",{get:()=>e.value,set:()=>{throw Error(`[Nix] "${this.label}" is read-only.`)},configurable:!1}),this.update=()=>{throw Error(`[Nix] "${this.label}" is read-only.`)},this.dispose=()=>{throw Error(`[Nix] Cannot dispose "${this.label}" directly.`)}}},n=new Set(["$id","$state","$stateSignal","$snapshot","$reset","$patch","$watch","$dispose"]);function r(e){if("__proto__"===e||"constructor"===e||"prototype"===e)throw Error(`[Nix] Store key "${e}" is not allowed for security reasons.`);if(n.has(e))throw Error(`[Nix] Store key "${e}" is reserved.`)}function i(e,t){return!n.has(e)||(console.warn(`[Nix] Store ${t} "${e}" is reserved and will be ignored.`),!1)}function a(e,r){return new t(e,r)}function o(t,o){let{name:l="store",actions:s,getters:c,plugins:f=[]}=o??{},u=Object.keys(t),d={};for(let o of u)r(o),d[o]=e.signal(t[o]);let $,g=d,h=e.computed(()=>{let e={};for(let t of u)e[t]=d[t].value;return e}),b=a(h,`store "${l}".$stateSignal`);try{$=structuredClone(t)}catch(e){throw Error(`[Nix] Store "${l}" initialState contains non-serializable data (functions, DOM nodes, Symbols, or WeakRefs). Remove these before creating the store. Original error: ${e}`)}let p=Object.assign(Object.create(null),g,{$reset:function(){e.batch(()=>{for(let e of u)d[e].value=$[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(h,t,r)},$snapshot:function(){let e={};for(let t of u)e[t]=d[t].peek();return e}});Object.defineProperty(p,"$id",{value:l,writable:!1,enumerable:!1,configurable:!1}),Object.defineProperty(p,"$state",{get:()=>h.value,enumerable:!0,configurable:!1}),Object.defineProperty(p,"$stateSignal",{value:b,writable:!1,enumerable:!1,configurable:!1});let y=new Set([...u,...Array.from(n)]);if(s){let e=s(g);for(let t of Object.keys(e))if(i(t,"action")){if(y.has(t)){console.warn(`[Nix] Store "${l}": action "${t}" collides with an existing signal or getter and will be ignored.`);continue}y.add(t),p[t]=e[t]}}if(c){let t=c(g);for(let r of Object.keys(t)){if(!i(r,"getter"))continue;if(y.has(r)){console.warn(`[Nix] Store "${l}": getter "${r}" collides with an existing signal or action and will be ignored.`);continue}let o=t[r];if(!(o instanceof e.Signal))throw TypeError(`[Nix] Store "${l}": getter "${r}" must return a Signal (wrap it with computed()). Got: ${typeof o}`);y.add(r),p[r]=a(o,`getter "${r}" in store "${l}"`)}}let w=[()=>h.dispose()];for(let e of f)try{let t=e(p);"function"==typeof t&&w.push(t)}catch(e){console.error(`[Nix] Plugin initialization failed for store "${l}":`,e)}return p.$dispose=()=>{for(let e of w)e()},p}exports.ReadonlySignal=t,exports.createStore=o;
package/dist/lib/store.js CHANGED
@@ -1 +1 @@
1
- import{Signal as e,batch as t,computed as n,signal as r,watch as i}from"./signals.js";var a=class extends e{label;constructor(e,t="ReadonlySignal"){super(e.peek()),this.label=t,Object.defineProperty(this,"value",{get:()=>e.value,set:()=>{throw Error(`[Nix] "${this.label}" is read-only.`)},configurable:!1}),this.update=()=>{throw Error(`[Nix] "${this.label}" is read-only.`)},this.dispose=()=>{throw Error(`[Nix] Cannot dispose "${this.label}" directly.`)}}},o=new Set(["$id","$state","$stateSignal","$reset","$patch","$watch","$dispose"]);function s(e){if("__proto__"===e||"constructor"===e||"prototype"===e)throw Error(`[Nix] Store key "${e}" is not allowed for security reasons.`);if(o.has(e))throw Error(`[Nix] Store key "${e}" is reserved.`)}function c(e,t){return!o.has(e)||(console.warn(`[Nix] Store ${t} "${e}" is reserved and will be ignored.`),!1)}function l(e,t){return new a(e,t)}function u(a,f={}){let{name:u="store",actions:d,getters:$,plugins:g=[]}=f,h=Object.keys(a),b={};for(let e of h)s(e),b[e]=r(a[e]);let p,w=b,y=n(()=>{let e={};for(let t of h)e[t]=b[t].value;return e}),S=l(y,`store "${u}".$stateSignal`);try{p=structuredClone(a)}catch(e){throw Error(`[Nix] Store "${u}" initialState contains non-serializable data (functions, DOM nodes, Symbols, or WeakRefs). Remove these before creating the store. Original error: ${e}`)}let x=Object.assign(Object.create(null),w,{$reset:function(){t(()=>{for(let e of h)b[e].value=p[e]})},$patch:function(e){t(()=>{for(let t of Object.keys(e))Object.prototype.hasOwnProperty.call(b,t)&&(b[t].value=e[t])})},$watch:function(e,t){return i(y,e,t)}});Object.defineProperty(x,"$id",{value:u,writable:!1,enumerable:!1,configurable:!1}),Object.defineProperty(x,"$state",{get:()=>y.value,enumerable:!0,configurable:!1}),Object.defineProperty(x,"$stateSignal",{value:S,writable:!1,enumerable:!1,configurable:!1});let O=new Set([...h,...Array.from(o)]);if(d){let e=d(w);for(let t of Object.keys(e))if(c(t,"action")){if(O.has(t)){console.warn(`[Nix] Store "${u}": action "${t}" collides with an existing signal or getter and will be ignored.`);continue}O.add(t),x[t]=e[t]}}if($){let t=$(w);for(let r of Object.keys(t)){if(!c(r,"getter"))continue;if(O.has(r)){console.warn(`[Nix] Store "${u}": 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 "${u}": getter "${r}" must return a Signal (wrap it with computed()). Got: ${typeof o}`);O.add(r),x[r]=l(o,`getter "${r}" in store "${u}"`)}}let j=[()=>y.dispose()];for(let e of g)try{let t=e(x);"function"==typeof t&&j.push(t)}catch(e){console.error(`[Nix] Plugin initialization failed for store "${u}":`,e)}return x.$dispose=()=>{for(let e of j)e()},x}export{a as ReadonlySignal,u as createStore};
1
+ import{Signal as e,batch as t,computed as n,signal as r,watch as i}from"./signals.js";var a=class extends e{label;constructor(e,t="ReadonlySignal"){super(e.peek()),this.label=t,Object.defineProperty(this,"value",{get:()=>e.value,set:()=>{throw Error(`[Nix] "${this.label}" is read-only.`)},configurable:!1}),this.update=()=>{throw Error(`[Nix] "${this.label}" is read-only.`)},this.dispose=()=>{throw Error(`[Nix] Cannot dispose "${this.label}" directly.`)}}},o=new Set(["$id","$state","$stateSignal","$snapshot","$reset","$patch","$watch","$dispose"]);function s(e){if("__proto__"===e||"constructor"===e||"prototype"===e)throw Error(`[Nix] Store key "${e}" is not allowed for security reasons.`);if(o.has(e))throw Error(`[Nix] Store key "${e}" is reserved.`)}function c(e,t){return!o.has(e)||(console.warn(`[Nix] Store ${t} "${e}" is reserved and will be ignored.`),!1)}function l(e,t){return new a(e,t)}function u(a,f){let{name:u="store",actions:d,getters:$,plugins:h=[]}=f??{},g=Object.keys(a),p={};for(let e of g)s(e),p[e]=r(a[e]);let b,w=p,y=n(()=>{let e={};for(let t of g)e[t]=p[t].value;return e}),S=l(y,`store "${u}".$stateSignal`);try{b=structuredClone(a)}catch(e){throw Error(`[Nix] Store "${u}" initialState contains non-serializable data (functions, DOM nodes, Symbols, or WeakRefs). Remove these before creating the store. Original error: ${e}`)}let x=Object.assign(Object.create(null),w,{$reset:function(){t(()=>{for(let e of g)p[e].value=b[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)},$snapshot:function(){let e={};for(let t of g)e[t]=p[t].peek();return e}});Object.defineProperty(x,"$id",{value:u,writable:!1,enumerable:!1,configurable:!1}),Object.defineProperty(x,"$state",{get:()=>y.value,enumerable:!0,configurable:!1}),Object.defineProperty(x,"$stateSignal",{value:S,writable:!1,enumerable:!1,configurable:!1});let O=new Set([...g,...Array.from(o)]);if(d){let e=d(w);for(let t of Object.keys(e))if(c(t,"action")){if(O.has(t)){console.warn(`[Nix] Store "${u}": action "${t}" collides with an existing signal or getter and will be ignored.`);continue}O.add(t),x[t]=e[t]}}if($){let t=$(w);for(let r of Object.keys(t)){if(!c(r,"getter"))continue;if(O.has(r)){console.warn(`[Nix] Store "${u}": 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 "${u}": getter "${r}" must return a Signal (wrap it with computed()). Got: ${typeof o}`);O.add(r),x[r]=l(o,`getter "${r}" in store "${u}"`)}}let j=[()=>y.dispose()];for(let e of h)try{let t=e(x);"function"==typeof t&&j.push(t)}catch(e){console.error(`[Nix] Plugin initialization failed for store "${u}":`,e)}return x.$dispose=()=>{for(let e of j)e()},x}export{a as ReadonlySignal,u as createStore};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deijose/nix-js",
3
- "version": "2.2.2",
3
+ "version": "2.4.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",